diff --git a/.github/ISSUE_TEMPLATE/ar_bug_report.md b/.github/ISSUE_TEMPLATE/ar_bug_report.md index 0d4aee9397..93b6e70b03 100644 --- a/.github/ISSUE_TEMPLATE/ar_bug_report.md +++ b/.github/ISSUE_TEMPLATE/ar_bug_report.md @@ -1,5 +1,5 @@ --- -name: ar_bug_report.md +name: Automated Review bug report about: Create a bug for a an issue found in the Automated Review title: 'AR Bug Report' labels: 'needs-triage,kind/bug,kind/automation' diff --git a/.gitignore b/.gitignore index 9fd70a927f..9012ec7576 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ Editor/EditorEventLog.xml Editor/EditorLayout.xml **/*egg-info/** **/*egg-link +**/[Rr]estricted UserSettings.xml [Uu]ser/ FrameCapture/** diff --git a/Assets/Editor/Scripts/export_all_project_levels.py b/Assets/Editor/Scripts/export_all_project_levels.py deleted file mode 100755 index ba900b37a8..0000000000 --- a/Assets/Editor/Scripts/export_all_project_levels.py +++ /dev/null @@ -1,78 +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 -# -# -""" -This script loads every level in a game project and exports them. You will be prompted with the standard -Check-Out/Overwrite/Cancel dialog for each level. -This is useful when there is a version update to a file that requires re-exporting. -""" -import sys, os -import azlmbr.legacy.general as general -import azlmbr.legacy.checkout_dialog as checkout_dialog - - -class CheckOutDialogEnableAll: - """ - Helper class to wrap enabling the "Apply to all" checkbox in the CheckOutDialog. - Guarantees that the old setting will be restored. - To use, do: - - with CheckOutDialogEnableAll(): - # your code here - """ - - def __init__(self): - self.old_setting = False - - def __enter__(self): - self.old_setting = checkout_dialog.enable_for_all(True) - print(self.old_setting) - - def __exit__(self, type, value, traceback): - checkout_dialog.enable_for_all(self.old_setting) - - -level_list = [] - - -def is_in_special_folder(file_full_path): - if file_full_path.find("_savebackup") >= 0: - return True - if file_full_path.find("_autobackup") >= 0: - return True - if file_full_path.find("_hold") >= 0: - return True - if file_full_path.find("_tmpresize") >= 0: - return True - - return False - - -game_folder = general.get_game_folder() - - -# Recursively search every directory in the game project for files ending with .cry and .ly -for root, dirs, files in os.walk(game_folder): - for file in files: - if file.endswith(".cry") or file.endswith(".ly"): - # The engine expects the full path of the .cry file - file_full_path = os.path.abspath(os.path.join(root, file)) - # Exclude files in special directories - if not is_in_special_folder(file_full_path): - level_list.append(file_full_path) - -# make the checkout dialog enable the 'Apply to all' checkbox -with CheckOutDialogEnableAll(): - - # For each valid .cry file found, open it in the editor and export it - for level in level_list: - if not isinstance(level, str): - # general.open_level_no_prompt expects the file path in utf8 format - level = level.encode("utf-8") - - general.open_level_no_prompt(level) - general.export_to_engine() diff --git a/Assets/Editor/Scripts/generatelod.py b/Assets/Editor/Scripts/generatelod.py deleted file mode 100755 index 618827c5b4..0000000000 --- a/Assets/Editor/Scripts/generatelod.py +++ /dev/null @@ -1,40 +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 -# -# -''' -Generates a 50% lod for the selected model - -@argument name="Lod Percentage", type="string", default="50.0f" -''' - -import sys -import time - -percentage = float(sys.argv[1]) - -selectedcgf = lodtools.getselected() -selectedmaterial = lodtools.getselectedmaterial() - -loadedmodel = lodtools.loadcgf(selectedcgf) -loadedmaterial = lodtools.loadmaterial(selectedmaterial) - -if loadedmodel == True and loadedmaterial == True: - lodtools.generatelodchain() - finished = 0.0 - while finished >= 0.0: - finished = lodtools.generatetick() - print 'Lod Chain Generation progress: ' + str(finished) - time.sleep(1) - if finished == 1.0: - break - - lodtools.createlod(1,percentage) - lodtools.generatematerials(1,512,512) - lodtools.savetextures(1) - lodtools.savesettings() - lodtools.reloadmodel() - diff --git a/Assets/Editor/Scripts/rename_cgf.py b/Assets/Editor/Scripts/rename_cgf.py deleted file mode 100755 index 1196c4c6a2..0000000000 --- a/Assets/Editor/Scripts/rename_cgf.py +++ /dev/null @@ -1,14 +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 -# -# -objects = general.get_all_objects("", "") # Get the name list of all objects in the level. -# If there is any object with the geometry file of "objects\\default\\primitive_box.cgf", -# change it to "objects\\default\\primitive_cube.cgf". -for obj in objects: - geometry_file = general.get_entity_geometry_file(obj) - if geometry_file == "objects\\default\\primitive_box.cgf": - general.set_entity_geometry_file(obj, "objects\\default\\primitive_cube.cgf") diff --git a/Assets/Editor/Scripts/select_story_anim_objects.py b/Assets/Editor/Scripts/select_story_anim_objects.py deleted file mode 100755 index 93021baac4..0000000000 --- a/Assets/Editor/Scripts/select_story_anim_objects.py +++ /dev/null @@ -1,14 +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 -# -# -objects = general.get_all_objects("AnimObject", "") # Get the name list of all anim objects in the level. -general.clear_selection() -# If there is any object with the geometry file whose name contains "\\story\\", select it -for obj in objects: - geometry_file = general.get_entity_geometry_file(obj) - if geometry_file.find("\\story\\") != -1: - general.select_object(obj) diff --git a/Assets/Editor/Scripts/tools_shelf_actions.py b/Assets/Editor/Scripts/tools_shelf_actions.py deleted file mode 100755 index 8b99d76e1f..0000000000 --- a/Assets/Editor/Scripts/tools_shelf_actions.py +++ /dev/null @@ -1,508 +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 __future__ -import time, re, os, sys, itertools, ast -import azlmbr.legacy.general as general - -global ctrlFile -ctrlFile = 'setState.txt' -global logfile -logfile = 'Editor.log' - -global HIDDEN_MASK_PREFIX -HIDDEN_MASK_PREFIX= 'hidden_mask_for_' -global HIDDEN_MASK_PREFIX_LENGTH -HIDDEN_MASK_PREFIX_LENGTH = len(HIDDEN_MASK_PREFIX) - -def getLogList(logfile): - logList = [x for x in open(logfile, 'r')] - return logList - -def getCheckList(ctrlFile): - try: - with open(ctrlFile): - stateList = open(ctrlFile, 'r') - - except: - open(ctrlFile, 'w').close() - stateList = open(ctrlFile, 'r') - - checkList = [x for x in stateList] - return checkList - -#Store/Restore CVars default values -----------------------------------------------------------------# -if 'CVARS' not in globals(): - CVARS = {} - -def saveDefaultValue(cVars, value): - if cVars not in CVARS: - CVARS[cVars] = value - -def restoreDefaultValue(cVars): - if cVars not in CVARS: - return - - defaultValue = CVARS[cVars] - del CVARS[cVars] - if cVars.startswith(HIDDEN_MASK_PREFIX): - type = cVars[HIDDEN_MASK_PREFIX_LENGTH:] - general.set_hidemask(type, defaultValue) - else: - general.set_cvar(cVars, defaultValue) - -#toggle CVars--------------------------------------------------------------------# - -def updateCvars(cVars, value): - saveDefaultValue(cVars, value) - general.set_cvar(cVars, value) - -def toggleCvarsRestartCheck(log, state, mode, cVars, onValue, offValue, ctrlFile): - if log in state: - toggleCvarsV(mode, cVars, onValue, offValue, ctrlFile) - else: - stateList = open(ctrlFile, 'w') - stateList.write(log) - stateList = open(ctrlFile, 'r') - toggleCvarsV(mode, cVars, onValue, offValue, ctrlFile) - -def toggleCvarsV(mode, cVars, onValue, offValue, ctrlFile): - stateList = open(ctrlFile, 'r') - setState = [x for x in enumerate(stateList)] - blankCheck = [x for x in setState] - getList = [x for x in stateList] - - if blankCheck == []: - stateList = open(ctrlFile, 'w') - stateList.write(''.join(str("%s,{'%s': %s}" % (mode, cVars, offValue))+'\n')) - general.set_cvar(cVars, offValue) - else: - stateList = open(ctrlFile, 'r') - checkFor = str([x for x in stateList]) - - if mode not in str(checkFor): - stateList = open(ctrlFile, 'r') - getList = [x for x in stateList] - getList.insert(1, str("%s,{'%s': %s}\n" % (mode, cVars , offValue))) - print (str("{'%s': %s}\n" % (cVars , offValue))) - stateList = open(ctrlFile, 'w') - stateList.write(''.join(getList)) - general.set_cvar(cVars, offValue) - - else: - stateList = open(ctrlFile, 'r') - for d in enumerate(stateList): - values = d[1].split(',') - stateList = open(ctrlFile, 'r') - getList = [x for x in stateList] - - if mode in values[0]: - getDict = ast.literal_eval(values[1]) - getState = getDict.get(cVars) - - if getState == offValue: - getDict[cVars] = onValue - joinStr = [mode,",",str(getDict), '\n'] - newLine = ''.join(joinStr) - print (getDict) - getList[d[0]] = newLine - stateList = open(ctrlFile, 'w') - stateList.write(''.join(str(''.join(getList)))) - general.set_cvar(cVars, onValue) - else: - getDict[cVars] = offValue - joinStr = [mode,",",str(getDict), '\n'] - newLine = ''.join(joinStr) - print (getDict) - getList[d[0]] = newLine - stateList = open(ctrlFile, 'w') - stateList.write(''.join(str(''.join(getList)))) - general.set_cvar(cVars, offValue) - -def toggleCvarsValue(mode, cVars, onValue, offValue): - currentValue = general.get_cvar(cVars) - - if type(onValue) is str: - saveDefaultValue(cVars, currentValue) - elif type(onValue) is int: - saveDefaultValue(cVars, int(currentValue)) - elif type(onValue) is float: - saveDefaultValue(cVars, float(currentValue)) - else: - general.log('Failed to store default value for {0}'.format(cVars)) - - if currentValue == str(onValue): - general.set_cvar(cVars, offValue) - else: - general.set_cvar(cVars, onValue) - -#toggleConsol--------------------------------------------------------------------# - -def toggleConsolRestartCheck(log,state, mode, onValue, offValue, ctrlFile): - if log in state: - toggleConsolV(mode, onValue, offValue, ctrlFile) - else: - stateList = open(ctrlFile, 'w') - stateList.write(log) - stateList = open(ctrlFile, 'r') - toggleConsolV(mode, onValue, offValue, ctrlFile) - -def toggleConsolV(mode, onValue, offValue): - stateList = open(ctrlFile, 'r') - setState = [x for x in enumerate(stateList)] - blankCheck = [x for x in setState] - getList = [x for x in stateList] - onOffList = [onValue, offValue] - - if blankCheck == []: - stateList = open(ctrlFile, 'w') - stateList.write(''.join(str("%s,'%s'" % (mode, offValue))+'\n')) - general.run_console(offValue) - else: - stateList = open(ctrlFile, 'r') - checkFor = str([x for x in stateList]) - - if mode not in str(checkFor): - stateList = open(ctrlFile, 'r') - getList = [x for x in stateList] - getList.insert(1, str("%s,'%s'\n" % (mode, offValue))) - print (str("{'%s': %s}\n" % (cVars , offValue))) - stateList = open(ctrlFile, 'w') - stateList.write(''.join(getList)) - general.run_console(onValue) - - else: - stateList = open(ctrlFile, 'r') - for d in enumerate(stateList): - - values = d[1].split(',') - - stateList = open(ctrlFile, 'r') - getList = [x for x in stateList] - - if mode in values[0]: - getDict = values[1] - off = ["'",str(offValue),"'", '\n'] - joinoff = ''.join(off) - - if values[1] == joinoff: - getDict = onValue - joinStr = [mode,",","'",getDict,"'", '\n'] - newLine = ''.join(joinStr) - print (getDict) - getList[d[0]] = str(newLine) - stateList = open(ctrlFile, 'w') - stateList.write(''.join(str(''.join(getList)))) - general.run_console(onValue) - else: - getDict = offValue - joinStr = [mode,",","'",getDict,"'", '\n'] - newLine = ''.join(joinStr) - print (getDict) - getList[d[0]] = str(newLine) - stateList = open(ctrlFile, 'w') - stateList.write(''.join(str(''.join(getList)))) - general.run_console(offValue) - -def toggleConsolValue(log, state, mode, onValue, offValue): - logList = getLogList(logfile) - checkList = getCheckList(ctrlFile) - toggleConsolRestartCheck(logList[1],checkList, mode, onValue, offValue, ctrlFile) - -#cycleCvars----------------------------------------------------------------------# - -def cycleCvarsRestartCheck(log, state, mode, cVars, cycleList, ctrlFile): - if log in state: - cycleCvarsV(mode, cVars, cycleList, ctrlFile) - else: - stateList = open(ctrlFile, 'w') - stateList.write(log) - stateList = open(ctrlFile, 'r') - cycleCvarsV(mode, cVars, cycleList, ctrlFile) - -def cycleCvarsV(mode, cVars, cycleList, ctrlFile): - stateList = open(ctrlFile, 'r') - setState = [x for x in enumerate(stateList)] - blankCheck = [x for x in setState] - getList = [x for x in stateList] - - if blankCheck == []: - stateList = open(ctrlFile, 'w') - stateList.write(''.join(str("%s,{'%s': %s}" % (mode, cVars, cycleList[1]))+'\n')) - general.set_cvar(cVars, cycleList[1]) - else: - stateList = open(ctrlFile, 'r') - checkFor = str([x for x in stateList]) - - if mode not in str(checkFor): - stateList = open(ctrlFile, 'r') - getList = [x for x in stateList] - getList.insert(1, str("%s,{'%s': %s}\n" % (mode, cVars , cycleList[1]))) - stateList = open(ctrlFile, 'w') - stateList.write(''.join(getList)) - general.set_cvar(cVars, cycleList[1]) - - else: - stateList = open(ctrlFile, 'r') - - for d in enumerate(stateList): - stateList = open(ctrlFile, 'r') - getList = [x for x in stateList] - values = d[1].split(',') - - if mode in values[0]: - getDict = ast.literal_eval(values[1]) - getState = getDict.get(cVars) - - cycleL = [x for x in enumerate(cycleList)] - - for x in cycleL: - if getState == x[1]: - number = [n[1] for n in cycleL] - getMax = max(number) - nextNum = x[0]+1 - - if nextNum > getMax: - getDict[cVars] = cycleList[0] - joinStr = [mode,",",str(getDict), '\n'] - newLine = ''.join(joinStr) - getList[d[0]] = newLine - print (getDict) - stateList = open(ctrlFile, 'w') - stateList.write(''.join(str(''.join(getList)))) - general.set_cvar(cVars, cycleList[0]) - else: - getDict[cVars] = cycleList[x[0]+1] - joinStr = [mode,",",str(getDict), '\n'] - newLine = ''.join(joinStr) - getList[d[0]] = newLine - print (getDict) - stateList = open(ctrlFile, 'w') - stateList.write(''.join(str(''.join(getList)))) - general.set_cvar(cVars, cycleList[x[0]+1]) - -def cycleCvarsValue(log, state, mode, cVars, cycleList): - logList = getLogList(logfile) - checkList = getCheckList(ctrlFile) - cycleCvarsRestartCheck(logList[1],checkList, mode, cVars, cycleList, ctrlFile) - -def cycleCvarsFloatValue(cVars, cycleList): - currentValueAsString = general.get_cvar(cVars) - try: - currentValue = float(currentValueAsString) - except: - currentValue = -1.0 - - saveDefaultValue(cVars, currentValue) - - # make sure we sort the list in ascending fashion - cycleList = sorted(cycleList) - - # find out what the next closest index is - newIndex = 0 - for x in cycleList: - if (currentValue < x): - break - newIndex = newIndex + 1 - - # loop around, if we need to - if newIndex >= len(cycleList): - newIndex = 0 - - general.set_cvar(cVars, cycleList[newIndex]) - - -def cycleCvarsIntValue(cVars, cycleList): - currentValueAsString = general.get_cvar(cVars) - try: - currentValue = int(currentValueAsString) - except: - currentValue = 0 - - saveDefaultValue(cVars, currentValue) - - # find out what index we're on already - # default to -1 so that when we increment to the next, we'll be at 0 - newIndex = -1 - for x in cycleList: - if (currentValue == x): - break - newIndex = newIndex + 1 - - # move to the next item - newIndex = newIndex + 1 - - # validate - if newIndex >= len(cycleList): - newIndex = 0 - - general.set_cvar(cVars, cycleList[newIndex]) - -#cycleConsol----------------------------------------------------------------------# - -def cycleConsolRestartCheck(log, state, mode, cycleList, ctrlFile): - if log in state: - cycleConsolV(mode, cycleList, ctrlFile) - else: - stateList = open(ctrlFile, 'w') - stateList.write(log) - stateList = open(ctrlFile, 'r') - cycleConsolV(mode, cycleList, ctrlFile) - -def cycleConsolV(mode, cycleList, ctrlFile): - stateList = open(ctrlFile, 'r') - setState = [x for x in enumerate(stateList)] - blankCheck = [x for x in setState] - getList = [x for x in stateList] - - if blankCheck == []: - stateList = open(ctrlFile, 'w') - stateList.write(''.join(str("%s,'%s'" % (mode, cycleList[0]))+'\n')) - general.run_console(cycleList[0]) - else: - stateList = open(ctrlFile, 'r') - checkFor = str([x for x in stateList]) - - if mode not in str(checkFor): - stateList = open(ctrlFile, 'r') - getList = [x for x in stateList] - getList.insert(1, str("%s,'%s'\n" % (mode, cycleList[0]))) - stateList = open(ctrlFile, 'w') - stateList.write(''.join(getList)) - general.run_console(cycleList[0]) - - else: - stateList = open(ctrlFile, 'r') - for d in enumerate(stateList): - stateList = open(ctrlFile, 'r') - getList = [x for x in stateList] - values = d[1].split(',') - - if mode in values[0]: - newValue = ''.join(values[1].split('\n')) - cycleL = [e for e in enumerate(cycleList)] - getDict = ''.join(values[1].split('\n')) - - for x in cycleL: - - if newValue in "'%s'" % x[1]: - number = [n[0] for n in cycleL] - getMax = max(number) - nextNum = x[0]+1 - - if nextNum > getMax: - getDict = '%s' % cycleList[0] - joinStr = [mode,",","'",getDict,"'", '\n'] - newLine = ''.join(joinStr) - getList[d[0]] = newLine - print (getDict) - stateList = open(ctrlFile, 'w') - stateList.write(''.join(str(''.join(getList)))) - general.run_console(getDict) - else: - getDict = '%s' % cycleList[x[0]+1] - joinStr = [mode,",","'",getDict,"'", '\n'] - newLine = ''.join(joinStr) - getList[d[0]] = newLine - print (getDict) - stateList = open(ctrlFile, 'w') - stateList.write(''.join(str(''.join(getList)))) - general.run_console(getDict) - -def cycleConsolValue(mode, cycleList): - logList = getLogList(logfile) - checkList = getCheckList(ctrlFile) - cycleConsolRestartCheck(logList[1],checkList, mode, cycleList, ctrlFile) - -def toggleHideMaskValues(type): - cVars = "%s%s" % (HIDDEN_MASK_PREFIX, type) - currentValue = general.get_hidemask(type) - saveDefaultValue(cVars, int(currentValue)) - if (currentValue): - general.set_hidemask(type, 0) - else: - general.set_hidemask(type, 1) - -#toggleHide------------------------------------------------------------------------# - -def toggleHideRestartCheck(log, state, mode, type, onValue, offValue, ctrlFile): - if log in state: - toggleHideByT(mode, type, onValue, offValue, ctrlFile) - else: - stateList = open(ctrlFile, 'w') - stateList.write(log) - stateList = open(ctrlFile, 'r') - toggleHideByT(mode, type, onValue, offValue, ctrlFile) - -def toggleHideByType(mode, type, onValue, offValue): - logList = getLogList(logfile) - checkList = getCheckList(ctrlFile) - toggleHideRestartCheck(logList[1],checkList, mode, type, onValue, offValue, ctrlFile) - -def toggleHideByT(mode, type, onValue, offValue, ctrlFile): - stateList = open(ctrlFile, 'r') - setState = [x for x in enumerate(stateList)] - blankCheck = [x for x in setState] - getList = [x for x in stateList] - - if blankCheck == []: - stateList = open(ctrlFile, 'w') - stateList.write(''.join(str("%s,{'%s': %s}" % (mode, type, offValue))+'\n')) - - hideByType(type) - else: - stateList = open(ctrlFile, 'r') - checkFor = str([x for x in stateList]) - - if mode not in str(checkFor): - stateList = open(ctrlFile, 'r') - getList = [x for x in stateList] - getList.insert(1, str("%s,{'%s': %s}\n" % (mode, type , offValue))) - print (str("{'%s': %s}\n" % (type , offValue))) - stateList = open(ctrlFile, 'w') - stateList.write(''.join(getList)) - hideByType(type) - - else: - stateList = open(ctrlFile, 'r') - for d in enumerate(stateList): - values = d[1].split(',') - stateList = open(ctrlFile, 'r') - getList = [x for x in stateList] - - if mode in values[0]: - getDict = ast.literal_eval(values[1]) - getState = getDict.get(type) - - if getState == offValue: - getDict[type] = onValue - joinStr = [mode,",",str(getDict), '\n'] - newLine = ''.join(joinStr) - print (getDict) - getList[d[0]] = newLine - stateList = open(ctrlFile, 'w') - stateList.write(''.join(str(''.join(getList)))) - unHideByType(type) - else: - getDict[type] = offValue - joinStr = [mode,",",str(getDict), '\n'] - newLine = ''.join(joinStr) - print (getDict) - getList[d[0]] = newLine - stateList = open(ctrlFile, 'w') - stateList.write(''.join(str(''.join(getList)))) - hideByType(type) - -def hideByType(type): - typeList = general.get_all_objects(str(type), "") - for x in typeList: - general.hide_object(x) - -def unHideByType(type): - typeList = general.get_all_objects(str(type), "") - for x in typeList: - general.unhide_object(x) diff --git a/AutomatedTesting/Assets/Prefabs/PinkFlower.prefab b/AutomatedTesting/Assets/Prefabs/PinkFlower.prefab new file mode 100644 index 0000000000..47dd6bc8d3 --- /dev/null +++ b/AutomatedTesting/Assets/Prefabs/PinkFlower.prefab @@ -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 + } + } + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Assets/TestAnim/rin_skeleton.fbx b/AutomatedTesting/Assets/TestAnim/rin_skeleton.fbx new file mode 100644 index 0000000000..f727c26ad2 --- /dev/null +++ b/AutomatedTesting/Assets/TestAnim/rin_skeleton.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27f87510ae07771dbad3e430e31502b1d1d13dee868f143b7f1de2a7febc8eb9 +size 7046400 diff --git a/AutomatedTesting/Assets/TestAnim/rin_skeleton.fbx.assetinfo b/AutomatedTesting/Assets/TestAnim/rin_skeleton.fbx.assetinfo new file mode 100644 index 0000000000..2d5502f42f --- /dev/null +++ b/AutomatedTesting/Assets/TestAnim/rin_skeleton.fbx.assetinfo @@ -0,0 +1,8 @@ +{ + "values": [ + { + "$type": "ScriptProcessorRule", + "scriptFilename": "Assets/TestAnim/scene_export_actor.py" + } + ] +} \ No newline at end of file diff --git a/AutomatedTesting/Assets/TestAnim/scene_export_actor.py b/AutomatedTesting/Assets/TestAnim/scene_export_actor.py new file mode 100644 index 0000000000..9dddc056a2 --- /dev/null +++ b/AutomatedTesting/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 diff --git a/AutomatedTesting/Assets/Textures/image.png b/AutomatedTesting/Assets/Textures/image.png new file mode 100644 index 0000000000..2f558c634e --- /dev/null +++ b/AutomatedTesting/Assets/Textures/image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:011454252e40c927343cce16296412f02f45d1f345c75c036651bdcca473bda5 +size 2672 diff --git a/AutomatedTesting/Assets/Textures/normal.png b/AutomatedTesting/Assets/Textures/normal.png new file mode 100644 index 0000000000..d3355e6699 --- /dev/null +++ b/AutomatedTesting/Assets/Textures/normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7bafcc4aefab827e1414e64bcdde235500b51392e52c9ccd588b2d7a24b865a0 +size 20214 diff --git a/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_01_1B66C428-1D7C-4A53-BC9B-6F23E420FEC0_Irradiance_lutrgba16.dds b/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_01_1B66C428-1D7C-4A53-BC9B-6F23E420FEC0_Irradiance_lutrgba16.dds new file mode 100644 index 0000000000..e208940f0d --- /dev/null +++ b/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_01_1B66C428-1D7C-4A53-BC9B-6F23E420FEC0_Irradiance_lutrgba16.dds @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac841c02e81c9ae23476522b7e517889d746d4ff32b3251ac4360168f39b30a3 +size 450708 diff --git a/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_01_52D75B1C-90BE-40A8-A874-3376F6B39299_Relocation_lutrgba16f.dds b/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_01_52D75B1C-90BE-40A8-A874-3376F6B39299_Relocation_lutrgba16f.dds new file mode 100644 index 0000000000..32f2ee1b17 --- /dev/null +++ b/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_01_52D75B1C-90BE-40A8-A874-3376F6B39299_Relocation_lutrgba16f.dds @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3653ca7fb42c7e5991815c17fc9ebeab14a1aa861bfb1d37e233ada66a835f00 +size 7188 diff --git a/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_01_5A2C9A6B-F914-4D9E-8098-2AD411B69F87_Distance_lutrg32f.dds b/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_01_5A2C9A6B-F914-4D9E-8098-2AD411B69F87_Distance_lutrg32f.dds new file mode 100644 index 0000000000..12c1d8d112 --- /dev/null +++ b/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_01_5A2C9A6B-F914-4D9E-8098-2AD411B69F87_Distance_lutrg32f.dds @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a30b85428202c548e77cdcc0a595f9f26e82d29622be26891d569f4779a75830 +size 1802388 diff --git a/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_01_A483018F-78ED-4814-986F-F925B0AA5EF8_Classification_lutr32f.dds b/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_01_A483018F-78ED-4814-986F-F925B0AA5EF8_Classification_lutr32f.dds new file mode 100644 index 0000000000..df2c69bfe9 --- /dev/null +++ b/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_01_A483018F-78ED-4814-986F-F925B0AA5EF8_Classification_lutr32f.dds @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3d76b7c93f8873ca7c4013dcc4eff887c25552c9c5847290481306656b22069 +size 3668 diff --git a/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_02_222F1F65-BF31-4E0D-9957-14AE73194A37_Classification_lutr32f.dds b/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_02_222F1F65-BF31-4E0D-9957-14AE73194A37_Classification_lutr32f.dds new file mode 100644 index 0000000000..5c990de348 --- /dev/null +++ b/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_02_222F1F65-BF31-4E0D-9957-14AE73194A37_Classification_lutr32f.dds @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:219f57137c5bd44093762ea9d0fc24308679956b980f26bf194edd3a1798fcda +size 3668 diff --git a/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_02_84D0F8A4-AD4F-4FD1-BA26-4803EAD88FE2_Distance_lutrg32f.dds b/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_02_84D0F8A4-AD4F-4FD1-BA26-4803EAD88FE2_Distance_lutrg32f.dds new file mode 100644 index 0000000000..367da91f13 --- /dev/null +++ b/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_02_84D0F8A4-AD4F-4FD1-BA26-4803EAD88FE2_Distance_lutrg32f.dds @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96ed61c66d1d1f71e940ab75899ee253007e704004e1157c900c6308151a8bf1 +size 1802388 diff --git a/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_02_9DC8C208-5327-4F0F-B1DF-C98F7F81F07D_Relocation_lutrgba16f.dds b/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_02_9DC8C208-5327-4F0F-B1DF-C98F7F81F07D_Relocation_lutrgba16f.dds new file mode 100644 index 0000000000..b6d735cf50 --- /dev/null +++ b/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_02_9DC8C208-5327-4F0F-B1DF-C98F7F81F07D_Relocation_lutrgba16f.dds @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70a7dc4e2455624067e842b059954f6c4bb9b0debc3cb1ffa783b14bffc61786 +size 7188 diff --git a/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_02_A78EEAF4-7CB2-4635-AA6F-2ED677328706_Irradiance_lutrgba16.dds b/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_02_A78EEAF4-7CB2-4635-AA6F-2ED677328706_Irradiance_lutrgba16.dds new file mode 100644 index 0000000000..e46f54586a --- /dev/null +++ b/AutomatedTesting/DiffuseProbeGrids/DiffuseGI_02_A78EEAF4-7CB2-4635-AA6F-2ED677328706_Irradiance_lutrgba16.dds @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f5a945c61f92f3da77dfd9fdd2c95aa257f4df6bcb82483c608ab660bb72e5f +size 450708 diff --git a/Gems/AssetMemoryAnalyzer/CMakeLists.txt b/AutomatedTesting/Editor/Scripts/Profiler/__init__.py similarity index 89% rename from Gems/AssetMemoryAnalyzer/CMakeLists.txt rename to AutomatedTesting/Editor/Scripts/Profiler/__init__.py index 2bb380fae3..7a325ca97e 100644 --- a/Gems/AssetMemoryAnalyzer/CMakeLists.txt +++ b/AutomatedTesting/Editor/Scripts/Profiler/__init__.py @@ -5,5 +5,3 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT # # - -add_subdirectory(Code) diff --git a/AutomatedTesting/Editor/Scripts/Profiler/profiler_system_example.py b/AutomatedTesting/Editor/Scripts/Profiler/profiler_system_example.py new file mode 100644 index 0000000000..16f161c50e --- /dev/null +++ b/AutomatedTesting/Editor/Scripts/Profiler/profiler_system_example.py @@ -0,0 +1,30 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +import azlmbr.debug as debug + +import pathlib + +def test_profiler_system(): + if not debug.g_ProfilerSystem.IsValid(): + print('g_ProfilerSystem is INVALID') + return + + state = 'ACTIVE' if debug.g_ProfilerSystem.IsActive() else 'INACTIVE' + print(f'Profiler system is currently {state}') + + capture_location = pathlib.Path(debug.g_ProfilerSystem.GetCaptureLocation()) + print(f'Capture location set to {capture_location}') + + print('Capturing single frame...' ) + capture_file = str(capture_location / 'script_capture_frame.json') + debug.g_ProfilerSystem.CaptureFrame(capture_file) + +# Invoke main function +if __name__ == '__main__': + test_profiler_system() diff --git a/AutomatedTesting/Editor/Scripts/auto_lod.py b/AutomatedTesting/Editor/Scripts/auto_lod.py new file mode 100644 index 0000000000..058303242a --- /dev/null +++ b/AutomatedTesting/Editor/Scripts/auto_lod.py @@ -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 diff --git a/AutomatedTesting/Editor/Scripts/scene_helpers.py b/AutomatedTesting/Editor/Scripts/scene_helpers.py new file mode 100644 index 0000000000..761068e796 --- /dev/null +++ b/AutomatedTesting/Editor/Scripts/scene_helpers.py @@ -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)) diff --git a/AutomatedTesting/Editor/Scripts/scene_mesh_to_prefab.py b/AutomatedTesting/Editor/Scripts/scene_mesh_to_prefab.py index e832b1f82b..db8df09091 100644 --- a/AutomatedTesting/Editor/Scripts/scene_mesh_to_prefab.py +++ b/AutomatedTesting/Editor/Scripts/scene_mesh_to_prefab.py @@ -5,55 +5,17 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT # # -import os, traceback, binascii, sys, json, pathlib -import azlmbr.math import azlmbr.bus +import azlmbr.math + +from scene_api.scene_data import PrimitiveShape, DecompositionMode +from scene_helpers import * + # # SceneAPI Processor # - -def log_exception_traceback(): - exc_type, exc_value, exc_tb = sys.exc_info() - data = traceback.format_exception(exc_type, exc_value, exc_tb) - print(str(data)) - -def get_mesh_node_names(sceneGraph): - import azlmbr.scene as sceneApi - import azlmbr.scene.graph - from scene_api import scene_data as sceneData - - meshDataList = [] - node = sceneGraph.get_root() - children = [] - paths = [] - - while node.IsValid(): - # store children to process after siblings - if sceneGraph.has_node_child(node): - children.append(sceneGraph.get_node_child(node)) - - nodeName = sceneData.SceneGraphName(sceneGraph.get_node_name(node)) - paths.append(nodeName.get_path()) - - # store any node that has mesh data content - nodeContent = sceneGraph.get_node_content(node) - if nodeContent.CastWithTypeName('MeshData'): - if sceneGraph.is_node_end_point(node) is False: - if (len(nodeName.get_path())): - meshDataList.append(sceneData.SceneGraphName(sceneGraph.get_node_name(node))) - - # advance to next node - if sceneGraph.has_node_sibling(node): - node = sceneGraph.get_node_sibling(node) - elif children: - node = children.pop() - else: - node = azlmbr.scene.graph.NodeIndex() - - return meshDataList, paths - def add_material_component(entity_id): # Create an override AZ::Render::EditorMaterialComponent editor_material_component = azlmbr.entity.EntityUtilityBus( @@ -64,24 +26,53 @@ def add_material_component(entity_id): # this fills out the material asset to a known product AZMaterial asset relative path json_update = json.dumps({ - "Controller": { "Configuration": { "materials": [ - { - "Key": {}, - "Value": { "MaterialAsset":{ - "assetHint": "materials/basic_grey.azmaterial" - }} - }] - }} - }); - result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, editor_material_component, json_update) + "Controller": {"Configuration": {"materials": [ + { + "Key": {}, + "Value": {"MaterialAsset": { + "assetHint": "materials/basic_grey.azmaterial" + }} + }] + }} + }) + result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, + editor_material_component, json_update) if not result: raise RuntimeError("UpdateComponentForEntity for editor_material_component failed") + +def add_physx_meshes(scene_manifest: sceneData.SceneManifest, source_file_name: str, mesh_name_list: List, all_node_paths: List[str]): + first_mesh = mesh_name_list[0].get_path() + + # Add a Box Primitive PhysX mesh with a comment + physx_box = scene_manifest.add_physx_primitive_mesh_group(source_file_name + "_box", PrimitiveShape.BOX, 0.0, None) + scene_manifest.physx_mesh_group_add_comment(physx_box, "This is a box primitive") + # Select the first mesh, unselect every other node + scene_manifest.physx_mesh_group_add_selected_node(physx_box, first_mesh) + + for node in all_node_paths: + if node != first_mesh: + scene_manifest.physx_mesh_group_add_unselected_node(physx_box, node) + + # Add a Convex Mesh PhysX mesh with a comment + convex_mesh = scene_manifest.add_physx_convex_mesh_group(source_file_name + "_convex", 0.08, .0004, + True, True, True, True, True, 24, True, "Glass") + scene_manifest.physx_mesh_group_add_comment(convex_mesh, "This is a convex mesh") + # Select/Unselect nodes using lists + all_except_first_mesh = [x for x in all_node_paths if x != first_mesh] + scene_manifest.physx_mesh_group_add_selected_unselected_nodes(convex_mesh, [first_mesh], all_except_first_mesh) + + # Configure mesh decomposition for this mesh + scene_manifest.physx_mesh_group_decompose_meshes(convex_mesh, 512, 32, .002, 100100, DecompositionMode.TETRAHEDRON, + 0.06, 0.055, 0.00015, 3, 3, True, False) + + # Add a Triangle mesh + triangle = scene_manifest.add_physx_triangle_mesh_group(source_file_name + "_triangle", False, True, True, True, True, True) + scene_manifest.physx_mesh_group_add_selected_unselected_nodes(triangle, [first_mesh], all_except_first_mesh) + def update_manifest(scene): - import json import uuid, os - import azlmbr.scene as sceneApi import azlmbr.scene.graph from scene_api import scene_data as sceneData @@ -89,9 +80,9 @@ def update_manifest(scene): # 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) 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)) @@ -101,6 +92,8 @@ def update_manifest(scene): previous_entity_id = azlmbr.entity.InvalidEntityId first_mesh = True + add_physx_meshes(scene_manifest, source_filename_only, mesh_name_list, all_node_paths) + # Loop every mesh node in the scene for activeMeshIndex in range(len(mesh_name_list)): mesh_name = mesh_name_list[activeMeshIndex] @@ -108,52 +101,82 @@ def update_manifest(scene): # Create a unique mesh group name using the filename + node name mesh_group_name = '{}_{}'.format(source_filename_only, mesh_name.get_name()) # Remove forbidden filename characters from the name since this will become a file on disk later - mesh_group_name = "".join(char for char in mesh_group_name if char not in "|<>:\"/?*\\") + mesh_group_name = sanitize_name_for_disk(mesh_group_name) # Add the MeshGroup to the manifest and give it a unique ID mesh_group = scene_manifest.add_mesh_group(mesh_group_name) mesh_group['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, source_filename_only + mesh_path)) + '}' # Set our current node as the only node that is included in this MeshGroup scene_manifest.mesh_group_select_node(mesh_group, mesh_path) + scene_manifest.mesh_group_add_comment(mesh_group, "Hello World") # Explicitly remove all other nodes to prevent implicit inclusions for node in all_node_paths: if node != mesh_path: scene_manifest.mesh_group_unselect_node(mesh_group, node) + scene_manifest.mesh_group_add_cloth_rule(mesh_group, mesh_path, "Col0", 1, "Col0", 2, "Col0", 2, 3) + scene_manifest.mesh_group_add_advanced_mesh_rule(mesh_group, True, False, True, "Col0") + scene_manifest.mesh_group_add_skin_rule(mesh_group, 3, 0.002) + scene_manifest.mesh_group_add_tangent_rule(mesh_group, 1, 0) + # Create an editor entity entity_id = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "CreateEditorReadyEntity", mesh_group_name) # Add an EditorMeshComponent to the entity - editor_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", entity_id, "AZ::Render::EditorMeshComponent") - # Set the ModelAsset assetHint to the relative path of the input asset + the name of the MeshGroup we just created + the azmodel extension - # The MeshGroup we created will be output as a product in the asset's path named mesh_group_name.azmodel - # The assetHint will be converted to an AssetId later during prefab loading + editor_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", + entity_id, "AZ::Render::EditorMeshComponent") + # Set the ModelAsset assetHint to the relative path of the input asset + the name of the MeshGroup we just + # created + the azmodel extension The MeshGroup we created will be output as a product in the asset's path + # named mesh_group_name.azmodel The assetHint will be converted to an AssetId later during prefab loading json_update = json.dumps({ - "Controller": { "Configuration": { "ModelAsset": { - "assetHint": os.path.join(source_relative_path, mesh_group_name) + ".azmodel" }}} - }); + "Controller": {"Configuration": {"ModelAsset": { + "assetHint": os.path.join(source_relative_path, mesh_group_name) + ".azmodel"}}} + }) # Apply the JSON above to the component we created - result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, editor_mesh_component, json_update) + result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, + editor_mesh_component, json_update) if not result: raise RuntimeError("UpdateComponentForEntity failed for Mesh component") + # Add a physics component referencing the triangle mesh we made for the first node + if previous_entity_id is None: + physx_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", + entity_id, "{FD429282-A075-4966-857F-D0BBF186CFE6} EditorColliderComponent") + + json_update = json.dumps({ + "ShapeConfiguration": { + "PhysicsAsset": { + "Asset": { + "assetHint": os.path.join(source_relative_path, source_filename_only + "_triangle.pxmesh") + } + } + } + }) + + result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, physx_mesh_component, json_update) + + if not result: + raise RuntimeError("UpdateComponentForEntity failed for PhysX mesh component") + # an example of adding a material component to override the default material if previous_entity_id is not None and first_mesh: first_mesh = False add_material_component(entity_id) # Get the transform component - transform_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", entity_id, "27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0") + transform_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", + entity_id, "27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0") # Set this entity to be a child of the last entity we created # This is just an example of how to do parenting and isn't necessarily useful to parent everything like this if previous_entity_id is not None: transform_json = json.dumps({ - "Parent Entity" : previous_entity_id.to_json() - }); + "Parent Entity": previous_entity_id.to_json() + }) # Apply the JSON update - result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, transform_component, transform_json) + result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, + transform_component, transform_json) if not result: raise RuntimeError("UpdateComponentForEntity failed for Transform component") @@ -165,37 +188,23 @@ def update_manifest(scene): created_entities.append(entity_id) # Create a prefab with all our entities - prefab_filename = source_filename_only + ".prefab" - created_template_id = azlmbr.prefab.PrefabSystemScriptingBus(azlmbr.bus.Broadcast, "CreatePrefab", created_entities, prefab_filename) - - if created_template_id == azlmbr.prefab.InvalidTemplateId: - raise RuntimeError("CreatePrefab {} failed".format(prefab_filename)) - - # Convert the prefab to a JSON string - output = azlmbr.prefab.PrefabLoaderScriptingBus(azlmbr.bus.Broadcast, "SaveTemplateToString", created_template_id) - - if output.IsSuccess(): - jsonString = output.GetValue() - uuid = azlmbr.math.Uuid_CreateRandom().ToString() - jsonResult = json.loads(jsonString) - # Add a PrefabGroup to the manifest and store the JSON on it - scene_manifest.add_prefab_group(source_filename_only, uuid, jsonResult) - else: - raise RuntimeError("SaveTemplateToString failed for template id {}, prefab {}".format(created_template_id, prefab_filename)) + create_prefab(scene_manifest, source_filename_only, created_entities) # Convert the manifest to a JSON string and return it new_manifest = scene_manifest.export() return new_manifest + sceneJobHandler = None + def on_update_manifest(args): try: scene = args[0] return update_manifest(scene) except RuntimeError as err: - print (f'ERROR - {err}') + print(f'ERROR - {err}') log_exception_traceback() except: log_exception_traceback() @@ -203,10 +212,12 @@ def on_update_manifest(args): global sceneJobHandler sceneJobHandler = None + # try to create SceneAPI handler for processing try: import azlmbr.scene as sceneApi - if (sceneJobHandler == None): + + if sceneJobHandler is None: sceneJobHandler = sceneApi.ScriptBuildingNotificationBusHandler() sceneJobHandler.connect() sceneJobHandler.add_callback('OnUpdateManifest', on_update_manifest) diff --git a/AutomatedTesting/Gem/AssetProcessorGemConfig.setreg b/AutomatedTesting/Gem/AssetProcessorGemConfig.setreg index 043774c9cc..d4de26a2dc 100644 --- a/AutomatedTesting/Gem/AssetProcessorGemConfig.setreg +++ b/AutomatedTesting/Gem/AssetProcessorGemConfig.setreg @@ -3,19 +3,19 @@ "AssetProcessor": { "Settings": { "Exclude PythonTest Benchmark Settings Assets": { - "pattern": ".*\\\\/PythonTests\\\\/.*benchmarksettings" + "pattern": "(^|.+/)PythonTests/.*benchmarksettings" }, "Exclude fbx_tests": { - "pattern": ".*\\\\/fbx_tests\\\\/assets\\\\/.*" + "pattern": "(^|.+/)fbx_tests/assets(/.+)$" }, "Exclude wwise_bank_dependency_tests": { - "pattern": ".*\\\\/wwise_bank_dependency_tests\\\\/assets\\\\/.*" + "pattern": "(^|.+/)wwise_bank_dependency_tests/assets(/.+)$" }, "Exclude AssetProcessorTestAssets": { - "pattern": ".*\\\\/asset_processor_tests\\\\/assets\\\\/.*" + "pattern": "(^|.+/)asset_processor_tests/assets(/.+)$" }, "Exclude Restricted AssetProcessorTestAssets": { - "pattern": ".*\\\\/asset_processor_tests\\\\/restricted\\\\/.*" + "pattern": "(^|.+/)asset_processor_tests/restricted(/.+)$" } } } diff --git a/AutomatedTesting/Gem/Code/CMakeLists.txt b/AutomatedTesting/Gem/Code/CMakeLists.txt index 6f55cc2764..8808507765 100644 --- a/AutomatedTesting/Gem/Code/CMakeLists.txt +++ b/AutomatedTesting/Gem/Code/CMakeLists.txt @@ -14,15 +14,25 @@ ly_add_target( FILES_CMAKE automatedtesting_files.cmake ${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake + automatedtesting_autogen_files.cmake INCLUDE_DIRECTORIES PRIVATE Source PUBLIC Include BUILD_DEPENDENCIES + PUBLIC + AZ::AzNetworking + Gem::Multiplayer PRIVATE AZ::AzCore Gem::Atom_AtomBridge.Static + Gem::Multiplayer.Static + AUTOGEN_RULES + *.AutoComponent.xml,AutoComponent_Header.jinja,$path/$fileprefix.AutoComponent.h + *.AutoComponent.xml,AutoComponent_Source.jinja,$path/$fileprefix.AutoComponent.cpp + *.AutoComponent.xml,AutoComponentTypes_Header.jinja,$path/AutoComponentTypes.h + *.AutoComponent.xml,AutoComponentTypes_Source.jinja,$path/AutoComponentTypes.cpp ) # if enabled, AutomatedTesting is used by all kinds of applications diff --git a/AutomatedTesting/Gem/Code/Source/AutoGen/NetworkTestPlayerComponent.AutoComponent.xml b/AutomatedTesting/Gem/Code/Source/AutoGen/NetworkTestPlayerComponent.AutoComponent.xml new file mode 100644 index 0000000000..46d6191835 --- /dev/null +++ b/AutomatedTesting/Gem/Code/Source/AutoGen/NetworkTestPlayerComponent.AutoComponent.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/Code/Source/AutomatedTestingModule.cpp b/AutomatedTesting/Gem/Code/Source/AutomatedTestingModule.cpp index 7ed37e89ca..b5be8410ef 100644 --- a/AutomatedTesting/Gem/Code/Source/AutomatedTestingModule.cpp +++ b/AutomatedTesting/Gem/Code/Source/AutomatedTestingModule.cpp @@ -8,6 +8,7 @@ #include #include +#include #include @@ -27,6 +28,8 @@ namespace AutomatedTesting m_descriptors.insert(m_descriptors.end(), { AutomatedTestingSystemComponent::CreateDescriptor(), }); + + CreateComponentDescriptors(m_descriptors); //< Register multiplayer components } /** diff --git a/AutomatedTesting/Gem/Code/Source/AutomatedTestingSystemComponent.cpp b/AutomatedTesting/Gem/Code/Source/AutomatedTestingSystemComponent.cpp index 3b373b1f65..06a0775d70 100644 --- a/AutomatedTesting/Gem/Code/Source/AutomatedTestingSystemComponent.cpp +++ b/AutomatedTesting/Gem/Code/Source/AutomatedTestingSystemComponent.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -45,7 +46,7 @@ namespace AutomatedTesting void AutomatedTestingSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) { - AZ_UNUSED(required); + required.push_back(AZ_CRC_CE("MultiplayerService")); } void AutomatedTestingSystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) @@ -60,6 +61,7 @@ namespace AutomatedTesting void AutomatedTestingSystemComponent::Activate() { AutomatedTestingRequestBus::Handler::BusConnect(); + RegisterMultiplayerComponents(); //< Register AutomatedTesting's multiplayer components to assign NetComponentIds } void AutomatedTestingSystemComponent::Deactivate() diff --git a/AutomatedTesting/Gem/Code/automatedtesting_autogen_files.cmake b/AutomatedTesting/Gem/Code/automatedtesting_autogen_files.cmake new file mode 100644 index 0000000000..b3c6fcaa0b --- /dev/null +++ b/AutomatedTesting/Gem/Code/automatedtesting_autogen_files.cmake @@ -0,0 +1,14 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(FILES + ${LY_ROOT_FOLDER}/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Common.jinja + ${LY_ROOT_FOLDER}/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Header.jinja + ${LY_ROOT_FOLDER}/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponent_Source.jinja + ${LY_ROOT_FOLDER}/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponentTypes_Header.jinja + ${LY_ROOT_FOLDER}/Gems/Multiplayer/Code/Include/Multiplayer/AutoGen/AutoComponentTypes_Source.jinja +) diff --git a/AutomatedTesting/Gem/Code/automatedtesting_files.cmake b/AutomatedTesting/Gem/Code/automatedtesting_files.cmake index 6bf2bf72d9..eb619104a4 100644 --- a/AutomatedTesting/Gem/Code/automatedtesting_files.cmake +++ b/AutomatedTesting/Gem/Code/automatedtesting_files.cmake @@ -11,4 +11,5 @@ set(FILES Source/AutomatedTestingModule.cpp Source/AutomatedTestingSystemComponent.cpp Source/AutomatedTestingSystemComponent.h + Source/AutoGen/NetworkTestPlayerComponent.AutoComponent.xml ) diff --git a/AutomatedTesting/Gem/Code/enabled_gems.cmake b/AutomatedTesting/Gem/Code/enabled_gems.cmake index 7d33a65bc7..3915fd36da 100644 --- a/AutomatedTesting/Gem/Code/enabled_gems.cmake +++ b/AutomatedTesting/Gem/Code/enabled_gems.cmake @@ -56,4 +56,5 @@ set(ENABLED_GEMS AudioSystem Terrain Profiler + Multiplayer ) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/Atom/CMakeLists.txt index ff3cd5c465..26eb96e7ec 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/Atom/CMakeLists.txt @@ -20,19 +20,6 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED) COMPONENT Atom ) - ly_add_pytest( - NAME AutomatedTesting::Atom_TestSuite_Main_Optimized - TEST_SUITE main - PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main_Optimized.py - TEST_SERIAL - TIMEOUT 600 - RUNTIME_DEPENDENCIES - AssetProcessor - AutomatedTesting.Assets - Editor - COMPONENT - Atom - ) ly_add_pytest( NAME AutomatedTesting::Atom_TestSuite_Sandbox TEST_SUITE sandbox @@ -60,18 +47,4 @@ if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED) COMPONENT Atom ) - ly_add_pytest( - NAME AutomatedTesting::Atom_TestSuite_Main_GPU_Optimized - TEST_SUITE main - TEST_REQUIRES gpu - TEST_SERIAL - TIMEOUT 1200 - PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main_GPU_Optimized.py - RUNTIME_DEPENDENCIES - AssetProcessor - AutomatedTesting.Assets - Editor - COMPONENT - Atom - ) endif() diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py index 6cc48984ab..d79e144ae0 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main.py @@ -4,252 +4,117 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -import logging -import os - import pytest -import ly_test_tools.environment.file_system as file_system -import editor_python_test_tools.hydra_test_utils as hydra -from Atom.atom_utils.atom_constants import LIGHT_TYPES - -logger = logging.getLogger(__name__) -TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "tests") +from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite @pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.parametrize("launcher_platform", ['windows_editor']) -@pytest.mark.parametrize("level", ["auto_test"]) -class TestAtomEditorComponentsMain(object): - """Holds tests for Atom components.""" +class TestAutomation(EditorTestSuite): - @pytest.mark.test_case_id("C32078118") # Decal - @pytest.mark.test_case_id("C32078119") # DepthOfField - @pytest.mark.test_case_id("C32078120") # Directional Light - @pytest.mark.test_case_id("C32078121") # Exposure Control - @pytest.mark.test_case_id("C32078115") # Global Skylight (IBL) - @pytest.mark.test_case_id("C32078125") # Physical Sky - @pytest.mark.test_case_id("C32078127") # PostFX Layer - @pytest.mark.test_case_id("C32078131") # PostFX Radius Weight Modifier - @pytest.mark.test_case_id("C32078117") # Light - @pytest.mark.test_case_id("C36525660") # Display Mapper - def test_AtomEditorComponents_AddedToEntity(self, request, editor, level, workspace, project, launcher_platform): - """ - Please review the hydra script run by this test for more specific test info. - Tests the Atom components & verifies all "expected_lines" appear in Editor.log - """ - cfg_args = [level] + enable_prefab_system = False - expected_lines = [ - # Decal Component - "Decal Entity successfully created", - "Decal_test: Component added to the entity: True", - "Decal_test: Component removed after UNDO: True", - "Decal_test: Component added after REDO: True", - "Decal_test: Entered game mode: True", - "Decal_test: Exit game mode: True", - "Decal Controller|Configuration|Material: SUCCESS", - "Decal_test: Entity is hidden: True", - "Decal_test: Entity is shown: True", - "Decal_test: Entity deleted: True", - "Decal_test: UNDO entity deletion works: True", - "Decal_test: REDO entity deletion works: True", - # DepthOfField Component - "DepthOfField Entity successfully created", - "DepthOfField_test: Component added to the entity: True", - "DepthOfField_test: Component removed after UNDO: True", - "DepthOfField_test: Component added after REDO: True", - "DepthOfField_test: Entered game mode: True", - "DepthOfField_test: Exit game mode: True", - "DepthOfField_test: Entity disabled initially: True", - "DepthOfField_test: Entity enabled after adding required components: True", - "DepthOfField Controller|Configuration|Camera Entity: SUCCESS", - "DepthOfField_test: Entity is hidden: True", - "DepthOfField_test: Entity is shown: True", - "DepthOfField_test: Entity deleted: True", - "DepthOfField_test: UNDO entity deletion works: True", - "DepthOfField_test: REDO entity deletion works: True", - # Directional Light Component - "Directional Light Entity successfully created", - "Directional Light_test: Component added to the entity: True", - "Directional Light_test: Component removed after UNDO: True", - "Directional Light_test: Component added after REDO: True", - "Directional Light_test: Entered game mode: True", - "Directional Light_test: Exit game mode: True", - "Directional Light_test: Entity is hidden: True", - "Directional Light_test: Entity is shown: True", - "Directional Light_test: Entity deleted: True", - "Directional Light_test: UNDO entity deletion works: True", - "Directional Light_test: REDO entity deletion works: True", - # Exposure Control Component - "Exposure Control Entity successfully created", - "Exposure Control_test: Component added to the entity: True", - "Exposure Control_test: Component removed after UNDO: True", - "Exposure Control_test: Component added after REDO: True", - "Exposure Control_test: Entered game mode: True", - "Exposure Control_test: Exit game mode: True", - "Exposure Control_test: Entity disabled initially: True", - "Exposure Control_test: Entity enabled after adding required components: True", - "Exposure Control_test: Entity is hidden: True", - "Exposure Control_test: Entity is shown: True", - "Exposure Control_test: Entity deleted: True", - "Exposure Control_test: UNDO entity deletion works: True", - "Exposure Control_test: REDO entity deletion works: True", - # Global Skylight (IBL) Component - "Global Skylight (IBL) Entity successfully created", - "Global Skylight (IBL)_test: Component added to the entity: True", - "Global Skylight (IBL)_test: Component removed after UNDO: True", - "Global Skylight (IBL)_test: Component added after REDO: True", - "Global Skylight (IBL)_test: Entered game mode: True", - "Global Skylight (IBL)_test: Exit game mode: True", - "Global Skylight (IBL) Controller|Configuration|Diffuse Image: SUCCESS", - "Global Skylight (IBL) Controller|Configuration|Specular Image: SUCCESS", - "Global Skylight (IBL)_test: Entity is hidden: True", - "Global Skylight (IBL)_test: Entity is shown: True", - "Global Skylight (IBL)_test: Entity deleted: True", - "Global Skylight (IBL)_test: UNDO entity deletion works: True", - "Global Skylight (IBL)_test: REDO entity deletion works: True", - # Physical Sky Component - "Physical Sky Entity successfully created", - "Physical Sky component was added to entity", - "Entity has a Physical Sky component", - "Physical Sky_test: Component added to the entity: True", - "Physical Sky_test: Component removed after UNDO: True", - "Physical Sky_test: Component added after REDO: True", - "Physical Sky_test: Entered game mode: True", - "Physical Sky_test: Exit game mode: True", - "Physical Sky_test: Entity is hidden: True", - "Physical Sky_test: Entity is shown: True", - "Physical Sky_test: Entity deleted: True", - "Physical Sky_test: UNDO entity deletion works: True", - "Physical Sky_test: REDO entity deletion works: True", - # PostFX Layer Component - "PostFX Layer Entity successfully created", - "PostFX Layer_test: Component added to the entity: True", - "PostFX Layer_test: Component removed after UNDO: True", - "PostFX Layer_test: Component added after REDO: True", - "PostFX Layer_test: Entered game mode: True", - "PostFX Layer_test: Exit game mode: True", - "PostFX Layer_test: Entity is hidden: True", - "PostFX Layer_test: Entity is shown: True", - "PostFX Layer_test: Entity deleted: True", - "PostFX Layer_test: UNDO entity deletion works: True", - "PostFX Layer_test: REDO entity deletion works: True", - # PostFX Radius Weight Modifier Component - "PostFX Radius Weight Modifier Entity successfully created", - "PostFX Radius Weight Modifier_test: Component added to the entity: True", - "PostFX Radius Weight Modifier_test: Component removed after UNDO: True", - "PostFX Radius Weight Modifier_test: Component added after REDO: True", - "PostFX Radius Weight Modifier_test: Entered game mode: True", - "PostFX Radius Weight Modifier_test: Exit game mode: True", - "PostFX Radius Weight Modifier_test: Entity is hidden: True", - "PostFX Radius Weight Modifier_test: Entity is shown: True", - "PostFX Radius Weight Modifier_test: Entity deleted: True", - "PostFX Radius Weight Modifier_test: UNDO entity deletion works: True", - "PostFX Radius Weight Modifier_test: REDO entity deletion works: True", - # Light Component - "Light Entity successfully created", - "Light_test: Component added to the entity: True", - "Light_test: Component removed after UNDO: True", - "Light_test: Component added after REDO: True", - "Light_test: Entered game mode: True", - "Light_test: Exit game mode: True", - "Light_test: Entity is hidden: True", - "Light_test: Entity is shown: True", - "Light_test: Entity deleted: True", - "Light_test: UNDO entity deletion works: True", - "Light_test: REDO entity deletion works: True", - # Display Mapper Component - "Display Mapper Entity successfully created", - "Display Mapper_test: Component added to the entity: True", - "Display Mapper_test: Component removed after UNDO: True", - "Display Mapper_test: Component added after REDO: True", - "Display Mapper_test: Entered game mode: True", - "Display Mapper_test: Exit game mode: True", - "Display Mapper_test: Entity is hidden: True", - "Display Mapper_test: Entity is shown: True", - "Display Mapper_test: Entity deleted: True", - "Display Mapper_test: UNDO entity deletion works: True", - "Display Mapper_test: REDO entity deletion works: True", - ] + @pytest.mark.test_case_id("C36525657") + class AtomEditorComponents_BloomAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_BloomAdded as test_module - unexpected_lines = [ - "Trace::Assert", - "Trace::Error", - "Traceback (most recent call last):", - ] + @pytest.mark.test_case_id("C32078118") + class AtomEditorComponents_DecalAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_DecalAdded as test_module - hydra.launch_and_validate_results( - request, - TEST_DIRECTORY, - editor, - "hydra_AtomEditorComponents_AddedToEntity.py", - timeout=120, - expected_lines=expected_lines, - unexpected_lines=unexpected_lines, - halt_on_unexpected=True, - null_renderer=True, - cfg_args=cfg_args, - ) + @pytest.mark.test_case_id("C36525658") + class AtomEditorComponents_DeferredFogAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_DeferredFogAdded as test_module - @pytest.mark.test_case_id("C34525095") - def test_AtomEditorComponents_LightComponent( - self, request, editor, workspace, project, launcher_platform, level): - """ - Please review the hydra script run by this test for more specific test info. - Tests that the Light component has the expected property options available to it. - """ - cfg_args = [level] + @pytest.mark.test_case_id("C32078119") + class AtomEditorComponents_DepthOfFieldAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_DepthOfFieldAdded as test_module - expected_lines = [ - "light_entity Entity successfully created", - "Entity has a Light component", - "light_entity_test: Component added to the entity: True", - f"light_entity_test: Property value is {LIGHT_TYPES['sphere']} which matches {LIGHT_TYPES['sphere']}", - "Controller|Configuration|Shadows|Enable shadow set to True", - "light_entity Controller|Configuration|Shadows|Shadowmap size: SUCCESS", - "Controller|Configuration|Shadows|Shadow filter method set to 1", # PCF - "Controller|Configuration|Shadows|Filtering sample count set to 4", - "Controller|Configuration|Shadows|Filtering sample count set to 64", - "Controller|Configuration|Shadows|Shadow filter method set to 2", # ESM - "Controller|Configuration|Shadows|ESM exponent set to 50.0", - "Controller|Configuration|Shadows|ESM exponent set to 5000.0", - "Controller|Configuration|Shadows|Shadow filter method set to 3", # ESM+PCF - f"light_entity_test: Property value is {LIGHT_TYPES['spot_disk']} which matches {LIGHT_TYPES['spot_disk']}", - f"light_entity_test: Property value is {LIGHT_TYPES['capsule']} which matches {LIGHT_TYPES['capsule']}", - f"light_entity_test: Property value is {LIGHT_TYPES['quad']} which matches {LIGHT_TYPES['quad']}", - "light_entity Controller|Configuration|Fast approximation: SUCCESS", - "light_entity Controller|Configuration|Both directions: SUCCESS", - f"light_entity_test: Property value is {LIGHT_TYPES['polygon']} which matches {LIGHT_TYPES['polygon']}", - f"light_entity_test: Property value is {LIGHT_TYPES['simple_point']} " - f"which matches {LIGHT_TYPES['simple_point']}", - "Controller|Configuration|Attenuation radius|Mode set to 0", - "Controller|Configuration|Attenuation radius|Radius set to 100.0", - f"light_entity_test: Property value is {LIGHT_TYPES['simple_spot']} " - f"which matches {LIGHT_TYPES['simple_spot']}", - "Controller|Configuration|Shutters|Outer angle set to 45.0", - "Controller|Configuration|Shutters|Outer angle set to 90.0", - "light_entity_test: Component added to the entity: True", - "Light component test (non-GPU) completed.", - ] + @pytest.mark.test_case_id("C36525659") + class AtomEditorComponents_DiffuseProbeGridAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_DiffuseProbeGridAdded as test_module - unexpected_lines = [ - "Trace::Assert", - "Trace::Error", - "Traceback (most recent call last):", - ] + @pytest.mark.test_case_id("C32078120") + class AtomEditorComponents_DirectionalLightAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_DirectionalLightAdded as test_module - hydra.launch_and_validate_results( - request, - TEST_DIRECTORY, - editor, - "hydra_AtomEditorComponents_LightComponent.py", - timeout=120, - expected_lines=expected_lines, - unexpected_lines=unexpected_lines, - halt_on_unexpected=True, - null_renderer=True, - cfg_args=cfg_args, - ) + @pytest.mark.test_case_id("C36525660") + class AtomEditorComponents_DisplayMapperAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_DisplayMapperAdded as test_module + @pytest.mark.test_case_id("C36525661") + class AtomEditorComponents_EntityReferenceAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_EntityReferenceAdded as test_module + @pytest.mark.test_case_id("C32078121") + class AtomEditorComponents_ExposureControlAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_ExposureControlAdded as test_module + + @pytest.mark.test_case_id("C32078115") + class AtomEditorComponents_GlobalSkylightIBLAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_GlobalSkylightIBLAdded as test_module + + @pytest.mark.test_case_id("C32078122") + class AtomEditorComponents_GridAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_GridAdded as test_module + + @pytest.mark.test_case_id("C36525671") + class AtomEditorComponents_HDRColorGradingAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_HDRColorGradingAdded as test_module + + @pytest.mark.test_case_id("C32078116") + class AtomEditorComponents_HDRiSkyboxAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_HDRiSkyboxAdded as test_module + + @pytest.mark.test_case_id("C32078117") + class AtomEditorComponents_LightAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_LightAdded as test_module + + @pytest.mark.test_case_id("C36525662") + class AtomEditorComponents_LookModificationAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_LookModificationAdded as test_module + + @pytest.mark.test_case_id("C32078123") + class AtomEditorComponents_MaterialAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_MaterialAdded as test_module + + @pytest.mark.test_case_id("C32078124") + class AtomEditorComponents_MeshAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_MeshAdded as test_module + + @pytest.mark.test_case_id("C36525663") + class AtomEditorComponents_OcclusionCullingPlaneAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_OcclusionCullingPlaneAdded as test_module + + @pytest.mark.test_case_id("C32078125") + class AtomEditorComponents_PhysicalSkyAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_PhysicalSkyAdded as test_module + + @pytest.mark.test_case_id("C36525664") + class AtomEditorComponents_PostFXGradientWeightModifierAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_PostFXGradientWeightModifierAdded as test_module + + @pytest.mark.test_case_id("C32078127") + class AtomEditorComponents_PostFXLayerAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_PostFXLayerAdded as test_module + + @pytest.mark.test_case_id("C32078131") + class AtomEditorComponents_PostFXRadiusWeightModifierAdded(EditorSharedTest): + from Atom.tests import ( + hydra_AtomEditorComponents_PostFXRadiusWeightModifierAdded as test_module) + + @pytest.mark.test_case_id("C36525665") + class AtomEditorComponents_PostFXShapeWeightModifierAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_PostFxShapeWeightModifierAdded as test_module + + @pytest.mark.test_case_id("C32078128") + class AtomEditorComponents_ReflectionProbeAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_ReflectionProbeAdded as test_module + + @pytest.mark.test_case_id("C36525666") + class AtomEditorComponents_SSAOAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponents_SSAOAdded as test_module + + class ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(EditorSharedTest): + from Atom.tests import hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges as test_module diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py index 23fb249761..14d850245c 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU.py @@ -4,131 +4,82 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ - -import datetime import logging import os -import zipfile import pytest -import ly_test_tools.environment.file_system as file_system -from ly_test_tools.image.screenshot_compare_qssim import qssim as compare_screenshots -from ly_test_tools.benchmark.data_aggregator import BenchmarkDataAggregator - import editor_python_test_tools.hydra_test_utils as hydra +import ly_test_tools.environment.file_system as file_system +from ly_test_tools.benchmark.data_aggregator import BenchmarkDataAggregator +from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite +from Atom.atom_utils.atom_component_helper import compare_screenshot_to_golden_image, golden_images_directory -logger = logging.getLogger(__name__) DEFAULT_SUBFOLDER_PATH = 'user/PythonTests/Automated/Screenshots' -TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "tests") - - -def golden_images_directory(): - """ - Uses this file location to return the valid location for golden image files. - :return: The path to the golden_images directory, but raises an IOError if the golden_images directory is missing. - """ - current_file_directory = os.path.join(os.path.dirname(__file__)) - golden_images_dir = os.path.join(current_file_directory, 'golden_images') - - if not os.path.exists(golden_images_dir): - raise IOError( - f'golden_images" directory was not found at path "{golden_images_dir}"' - f'Please add a "golden_images" directory inside: "{current_file_directory}"' - ) - - return golden_images_dir - - -def create_screenshots_archive(screenshot_path): - """ - Creates a new zip file archive at archive_path containing all files listed within archive_path. - :param screenshot_path: location containing the files to archive, the zip archive file will also be saved here. - :return: None, but creates a new zip file archive inside path containing all of the files inside archive_path. - """ - files_to_archive = [] - - # Search for .png and .ppm files to add to the zip archive file. - for (folder_name, sub_folders, file_names) in os.walk(screenshot_path): - for file_name in file_names: - if file_name.endswith(".png") or file_name.endswith(".ppm"): - file_path = os.path.join(folder_name, file_name) - files_to_archive.append(file_path) - - # Setup variables for naming the zip archive file. - timestamp = datetime.datetime.now().timestamp() - formatted_timestamp = datetime.datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d_%H-%M-%S") - screenshots_file = os.path.join(screenshot_path, f'zip_archive_{formatted_timestamp}.zip') - - # Write all of the valid .png and .ppm files to the archive file. - with zipfile.ZipFile(screenshots_file, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=True) as zip_archive: - for file_path in files_to_archive: - file_name = os.path.basename(file_path) - zip_archive.write(file_path, file_name) +logger = logging.getLogger(__name__) @pytest.mark.parametrize("project", ["AutomatedTesting"]) -@pytest.mark.parametrize("launcher_platform", ["windows_editor"]) -@pytest.mark.parametrize("level", ["auto_test"]) -class TestAllComponentsIndepthTests(object): +@pytest.mark.parametrize("launcher_platform", ['windows_editor']) +class TestAutomation(EditorTestSuite): + # Remove -autotest_mode from global_extra_cmdline_args since we need rendering for these tests. + global_extra_cmdline_args = ["-BatchMode"] # Default is ["-BatchMode", "-autotest_mode"] + + enable_prefab_system = False - @pytest.mark.parametrize("screenshot_name", ["AtomBasicLevelSetup.ppm"]) @pytest.mark.test_case_id("C34603773") - def test_BasicLevelSetup_SetsUpLevel( - self, request, editor, workspace, project, launcher_platform, level, screenshot_name): - """ - Please review the hydra script run by this test for more specific test info. - Tests that a basic rendering level setup can be created (lighting, meshes, materials, etc.). - """ + class AtomGPU_BasicLevelSetup_SetsUpLevel(EditorSharedTest): + use_null_renderer = False # Default is True + screenshot_name = "AtomBasicLevelSetup.ppm" + test_screenshots = [] # Gets set by setup() + screenshot_directory = "" # Gets set by setup() + # Clear existing test screenshots before starting test. - screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH) - test_screenshots = [os.path.join(screenshot_directory, screenshot_name)] - file_system.delete(test_screenshots, True, True) + 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) golden_images = [os.path.join(golden_images_directory(), screenshot_name)] - level_creation_expected_lines = [ - "Viewport is set to the expected size: True", - "Exited game mode" - ] - unexpected_lines = [ - "Trace::Assert", - "Trace::Error", - "Traceback (most recent call last):", - "Screenshot failed" - ] + from Atom.tests import hydra_AtomGPU_BasicLevelSetup as test_module - hydra.launch_and_validate_results( - request, - TEST_DIRECTORY, - editor, - "hydra_GPUTest_BasicLevelSetup.py", - timeout=180, - expected_lines=level_creation_expected_lines, - unexpected_lines=unexpected_lines, - halt_on_unexpected=True, - cfg_args=[level], - null_renderer=False, - ) - - for test_screenshot, golden_screenshot in zip(test_screenshots, golden_images): - compare_screenshots(test_screenshot, golden_screenshot) - - create_screenshots_archive(screenshot_directory) + assert compare_screenshot_to_golden_image(screenshot_directory, test_screenshots, golden_images, 0.99) is True @pytest.mark.test_case_id("C34525095") - def test_LightComponent_ScreenshotMatchesGoldenImage( - self, request, editor, workspace, project, launcher_platform, level): - """ - Please review the hydra script run by this test for more specific test info. - Tests that the Light component screenshots in a rendered level appear the same as the golden images. - """ + class AtomGPU_LightComponent_AreaLightScreenshotsMatchGoldenImages(EditorSharedTest): + use_null_renderer = False # Default is True screenshot_names = [ "AreaLight_1.ppm", "AreaLight_2.ppm", "AreaLight_3.ppm", "AreaLight_4.ppm", "AreaLight_5.ppm", + ] + test_screenshots = [] # Gets set by setup() + screenshot_directory = "" # Gets set by setup() + + # Clear existing test screenshots before starting test. + def setup(self, workspace): + screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH) + for screenshot in self.screenshot_names: + screenshot_path = os.path.join(screenshot_directory, screenshot) + self.test_screenshots.append(screenshot_path) + file_system.delete(self.test_screenshots, True, True) + + golden_images = [] + for golden_image in screenshot_names: + golden_image_path = os.path.join(golden_images_directory(), golden_image) + golden_images.append(golden_image_path) + + from Atom.tests import hydra_AtomGPU_AreaLightScreenshotTest as test_module + + assert compare_screenshot_to_golden_image(screenshot_directory, test_screenshots, golden_images, 0.99) is True + + @pytest.mark.test_case_id("C34525110") + class AtomGPU_LightComponent_SpotLightScreenshotsMatchGoldenImages(EditorSharedTest): + use_null_renderer = False # Default is True + screenshot_names = [ "SpotLight_1.ppm", "SpotLight_2.ppm", "SpotLight_3.ppm", @@ -136,42 +87,25 @@ class TestAllComponentsIndepthTests(object): "SpotLight_5.ppm", "SpotLight_6.ppm", ] - screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH) - test_screenshots = [] - for screenshot in screenshot_names: - screenshot_path = os.path.join(screenshot_directory, screenshot) - test_screenshots.append(screenshot_path) - file_system.delete(test_screenshots, True, True) + test_screenshots = [] # Gets set by setup() + screenshot_directory = "" # Gets set by setup() + + # Clear existing test screenshots before starting test. + def setup(self, workspace): + screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH) + for screenshot in self.screenshot_names: + screenshot_path = os.path.join(screenshot_directory, screenshot) + self.test_screenshots.append(screenshot_path) + file_system.delete(self.test_screenshots, True, True) golden_images = [] for golden_image in screenshot_names: golden_image_path = os.path.join(golden_images_directory(), golden_image) golden_images.append(golden_image_path) - expected_lines = ["spot_light Controller|Configuration|Shadows|Shadowmap size: SUCCESS"] - unexpected_lines = [ - "Trace::Assert", - "Trace::Error", - "Traceback (most recent call last):", - "Screenshot failed", - ] - hydra.launch_and_validate_results( - request, - TEST_DIRECTORY, - editor, - "hydra_GPUTest_LightComponent.py", - timeout=180, - expected_lines=expected_lines, - unexpected_lines=unexpected_lines, - halt_on_unexpected=True, - cfg_args=[level], - null_renderer=False, - ) + from Atom.tests import hydra_AtomGPU_SpotLightScreenshotTest as test_module - for test_screenshot, golden_screenshot in zip(test_screenshots, golden_images): - compare_screenshots(test_screenshot, golden_screenshot) - - create_screenshots_archive(screenshot_directory) + assert compare_screenshot_to_golden_image(screenshot_directory, test_screenshots, golden_images, 0.99) is True @pytest.mark.parametrize('rhi', ['dx12', 'vulkan']) @@ -202,7 +136,7 @@ class TestPerformanceBenchmarkSuite(object): hydra.launch_and_validate_results( request, - TEST_DIRECTORY, + os.path.join(os.path.dirname(__file__), "tests"), editor, "hydra_GPUTest_AtomFeatureIntegrationBenchmark.py", timeout=600, @@ -211,6 +145,7 @@ class TestPerformanceBenchmarkSuite(object): halt_on_unexpected=True, cfg_args=[level], null_renderer=False, + enable_prefab_system=False, ) aggregator = BenchmarkDataAggregator(workspace, logger, 'periodic') @@ -219,7 +154,6 @@ class TestPerformanceBenchmarkSuite(object): @pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.parametrize("launcher_platform", ['windows_generic']) -@pytest.mark.system class TestMaterialEditor(object): @pytest.mark.parametrize("cfg_args,expected_lines", [ @@ -239,7 +173,7 @@ class TestMaterialEditor(object): hydra.launch_and_validate_results( request, - TEST_DIRECTORY, + os.path.join(os.path.dirname(__file__), "tests"), generic_launcher, editor_script="", run_python="--runpython", @@ -249,5 +183,6 @@ class TestMaterialEditor(object): halt_on_unexpected=False, null_renderer=False, cfg_args=[cfg_args], - log_file_name="MaterialEditor.log" + log_file_name="MaterialEditor.log", + enable_prefab_system=False, ) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU_Optimized.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU_Optimized.py deleted file mode 100644 index 568768e12e..0000000000 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_GPU_Optimized.py +++ /dev/null @@ -1,44 +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"] - - @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) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_Optimized.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_Optimized.py deleted file mode 100644 index c29a391be4..0000000000 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Main_Optimized.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -Copyright (c) Contributors to the Open 3D Engine Project. -For complete copyright and license terms please see the LICENSE at the root of this distribution. - -SPDX-License-Identifier: Apache-2.0 OR MIT -""" -import pytest - -from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite - - -@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.") -@pytest.mark.parametrize("project", ["AutomatedTesting"]) -@pytest.mark.parametrize("launcher_platform", ['windows_editor']) -class TestAutomation(EditorTestSuite): - - @pytest.mark.test_case_id("C32078118") - class AtomEditorComponents_DecalAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_DecalAdded as test_module - - @pytest.mark.test_case_id("C32078119") - class AtomEditorComponents_DepthOfFieldAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_DepthOfFieldAdded as test_module - - @pytest.mark.test_case_id("C32078120") - class AtomEditorComponents_DirectionalLightAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_DirectionalLightAdded as test_module - - @pytest.mark.test_case_id("C32078121") - class AtomEditorComponents_ExposureControlAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_ExposureControlAdded as test_module - - @pytest.mark.test_case_id("C32078115") - class AtomEditorComponents_GlobalSkylightIBLAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_GlobalSkylightIBLAdded as test_module - - @pytest.mark.test_case_id("C32078125") - class AtomEditorComponents_PhysicalSkyAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_PhysicalSkyAdded as test_module - - @pytest.mark.test_case_id("C32078131") - class AtomEditorComponents_PostFXRadiusWeightModifierAdded(EditorSharedTest): - from Atom.tests import ( - hydra_AtomEditorComponents_PostFXRadiusWeightModifierAdded as test_module) - - @pytest.mark.test_case_id("C32078117") - class AtomEditorComponents_LightAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_LightAdded as test_module - - @pytest.mark.test_case_id("C36525660") - class AtomEditorComponents_DisplayMapperAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_DisplayMapperAdded as test_module - - @pytest.mark.test_case_id("C32078128") - class AtomEditorComponents_ReflectionProbeAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_ReflectionProbeAdded as test_module - - @pytest.mark.test_case_id("C32078124") - class AtomEditorComponents_MeshAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_MeshAdded as test_module - - @pytest.mark.test_case_id("C32078123") - class AtomEditorComponents_MaterialAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_MaterialAdded as test_module - - @pytest.mark.test_case_id("C32078127") - class AtomEditorComponents_PostFXLayerAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_PostFXLayerAdded as test_module - - @pytest.mark.test_case_id("C36525665") - class AtomEditorComponents_PostFXShapeWeightModifierAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_PostFxShapeWeightModifierAdded as test_module - - @pytest.mark.test_case_id("C36525664") - class AtomEditorComponents_PostFXGradientWeightModifierAdded(EditorSharedTest): - from Atom.tests import hydra_AtomEditorComponents_PostFXGradientWeightModifierAdded as test_module - - class ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(EditorSharedTest): - from Atom.tests import hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges as test_module diff --git a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py index ad45e51080..f0bb6e72ca 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/TestSuite_Sandbox.py @@ -9,63 +9,86 @@ import os import pytest +import ly_test_tools.environment.file_system as file_system import editor_python_test_tools.hydra_test_utils as hydra +from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite +from Atom.atom_utils.atom_constants import LIGHT_TYPES + logger = logging.getLogger(__name__) TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "tests") + class TestAtomEditorComponentsSandbox(object): # It requires at least one test def test_Dummy(self, request, editor, level, workspace, project, launcher_platform): pass - @pytest.mark.parametrize("project", ["AutomatedTesting"]) - @pytest.mark.parametrize("launcher_platform", ['windows_editor']) - @pytest.mark.parametrize("level", ["auto_test"]) - class TestAtomEditorComponentsMain(object): - """Holds tests for Atom components.""" - @pytest.mark.test_case_id("C32078128") - def test_AtomEditorComponents_ReflectionProbeAddedToEntity( - self, request, editor, level, workspace, project, launcher_platform): - """ - Please review the hydra script run by this test for more specific test info. - Tests the following Atom components and verifies all "expected_lines" appear in Editor.log: - 1. Reflection Probe - """ - cfg_args = [level] +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.parametrize("launcher_platform", ['windows_editor']) +@pytest.mark.parametrize("level", ["auto_test"]) +class TestAtomEditorComponentsMain(object): + """Holds tests for Atom components.""" - expected_lines = [ - # Reflection Probe Component - "Reflection Probe Entity successfully created", - "Reflection Probe_test: Component added to the entity: True", - "Reflection Probe_test: Component removed after UNDO: True", - "Reflection Probe_test: Component added after REDO: True", - "Reflection Probe_test: Entered game mode: True", - "Reflection Probe_test: Exit game mode: True", - "Reflection Probe_test: Entity disabled initially: True", - "Reflection Probe_test: Entity enabled after adding required components: True", - "Reflection Probe_test: Cubemap is generated: True", - "Reflection Probe_test: Entity is hidden: True", - "Reflection Probe_test: Entity is shown: True", - "Reflection Probe_test: Entity deleted: True", - "Reflection Probe_test: UNDO entity deletion works: True", - "Reflection Probe_test: REDO entity deletion works: True", - ] + @pytest.mark.test_case_id("C34525095") + def test_AtomEditorComponents_LightComponent( + self, request, editor, workspace, project, launcher_platform, level): + """ + Please review the hydra script run by this test for more specific test info. + Tests that the Light component has the expected property options available to it. + """ + cfg_args = [level] + + expected_lines = [ + "light_entity Entity successfully created", + "Entity has a Light component", + "light_entity_test: Component added to the entity: True", + f"light_entity_test: Property value is {LIGHT_TYPES['sphere']} which matches {LIGHT_TYPES['sphere']}", + "Controller|Configuration|Shadows|Enable shadow set to True", + "light_entity Controller|Configuration|Shadows|Shadowmap size: SUCCESS", + "Controller|Configuration|Shadows|Shadow filter method set to 1", # PCF + "Controller|Configuration|Shadows|Filtering sample count set to 4", + "Controller|Configuration|Shadows|Filtering sample count set to 64", + "Controller|Configuration|Shadows|Shadow filter method set to 2", # ESM + "Controller|Configuration|Shadows|ESM exponent set to 50.0", + "Controller|Configuration|Shadows|ESM exponent set to 5000.0", + "Controller|Configuration|Shadows|Shadow filter method set to 3", # ESM+PCF + f"light_entity_test: Property value is {LIGHT_TYPES['spot_disk']} which matches {LIGHT_TYPES['spot_disk']}", + f"light_entity_test: Property value is {LIGHT_TYPES['capsule']} which matches {LIGHT_TYPES['capsule']}", + f"light_entity_test: Property value is {LIGHT_TYPES['quad']} which matches {LIGHT_TYPES['quad']}", + "light_entity Controller|Configuration|Fast approximation: SUCCESS", + "light_entity Controller|Configuration|Both directions: SUCCESS", + f"light_entity_test: Property value is {LIGHT_TYPES['polygon']} which matches {LIGHT_TYPES['polygon']}", + f"light_entity_test: Property value is {LIGHT_TYPES['simple_point']} " + f"which matches {LIGHT_TYPES['simple_point']}", + "Controller|Configuration|Attenuation radius|Mode set to 0", + "Controller|Configuration|Attenuation radius|Radius set to 100.0", + f"light_entity_test: Property value is {LIGHT_TYPES['simple_spot']} " + f"which matches {LIGHT_TYPES['simple_spot']}", + "Controller|Configuration|Shutters|Outer angle set to 45.0", + "Controller|Configuration|Shutters|Outer angle set to 90.0", + "light_entity_test: Component added to the entity: True", + "Light component test (non-GPU) completed.", + ] + + unexpected_lines = ["Traceback (most recent call last):"] + + hydra.launch_and_validate_results( + request, + TEST_DIRECTORY, + editor, + "hydra_AtomEditorComponents_LightComponent.py", + timeout=120, + expected_lines=expected_lines, + unexpected_lines=unexpected_lines, + halt_on_unexpected=True, + null_renderer=True, + cfg_args=cfg_args, + enable_prefab_system=False, + ) - hydra.launch_and_validate_results( - request, - TEST_DIRECTORY, - editor, - "hydra_AtomEditorComponents_AddedToEntity.py", - timeout=120, - expected_lines=expected_lines, - unexpected_lines=[], - halt_on_unexpected=True, - null_renderer=True, - cfg_args=cfg_args, - ) @pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.parametrize("launcher_platform", ['windows_generic']) @@ -119,8 +142,6 @@ class TestMaterialEditorBasicTests(object): "Save All worked as expected: True", ] unexpected_lines = [ - # "Trace::Assert", - # "Trace::Error", "Traceback (most recent call last):" ] @@ -136,5 +157,20 @@ class TestMaterialEditorBasicTests(object): halt_on_unexpected=True, null_renderer=True, log_file_name="MaterialEditor.log", + enable_prefab_system=False, ) + +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.parametrize("launcher_platform", ['windows_editor']) +class TestAutomation(EditorTestSuite): + + enable_prefab_system = False + + @pytest.mark.test_case_id("C36529666") + class AtomEditorComponentsLevel_DiffuseGlobalIlluminationAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponentsLevel_DiffuseGlobalIlluminationAdded as test_module + + @pytest.mark.test_case_id("C36525660") + class AtomEditorComponentsLevel_DisplayMapperAdded(EditorSharedTest): + from Atom.tests import hydra_AtomEditorComponentsLevel_DisplayMapperAdded as test_module diff --git a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_component_helper.py b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_component_helper.py index a0db95c203..5fdd4c810d 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_component_helper.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_component_helper.py @@ -1,5 +1,6 @@ """ -Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. SPDX-License-Identifier: Apache-2.0 OR MIT @@ -8,12 +9,19 @@ import datetime import os import zipfile +from ly_test_tools.image.screenshot_compare_qssim import qssim as compare_screenshots + + +class ImageComparisonTestFailure(Exception): + """Custom test failure for failed image comparisons.""" + pass + def create_screenshots_archive(screenshot_path): """ Creates a new zip file archive at archive_path containing all files listed within archive_path. :param screenshot_path: location containing the files to archive, the zip archive file will also be saved here. - :return: None, but creates a new zip file archive inside path containing all of the files inside archive_path. + :return: path to the created .zip file archive. """ files_to_archive = [] @@ -27,14 +35,16 @@ def create_screenshots_archive(screenshot_path): # Setup variables for naming the zip archive file. timestamp = datetime.datetime.now().timestamp() formatted_timestamp = datetime.datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d_%H-%M-%S") - screenshots_file = os.path.join(screenshot_path, f'screenshots_{formatted_timestamp}.zip') + screenshots_zip_file = os.path.join(screenshot_path, f'screenshots_{formatted_timestamp}.zip') # Write all of the valid .png and .ppm files to the archive file. - with zipfile.ZipFile(screenshots_file, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=True) as zip_archive: + with zipfile.ZipFile(screenshots_zip_file, 'w', compression=zipfile.ZIP_DEFLATED, allowZip64=True) as zip_archive: for file_path in files_to_archive: file_name = os.path.basename(file_path) zip_archive.write(file_path, file_name) + return screenshots_zip_file + def golden_images_directory(): """ @@ -53,176 +63,195 @@ def golden_images_directory(): return golden_images_dir -def create_basic_atom_level(level_name): +def compare_screenshot_similarity( + test_screenshot, golden_image, similarity_threshold, create_zip_archive=False, screenshot_directory=""): """ - Creates a new level inside the Editor matching level_name & adds the following: - 1. "default_level" entity to hold all other entities. - 2. Adds Grid, Global Skylight (IBL), ground Mesh, Directional Light, Sphere w/ material+mesh, & Camera components. - 3. Each of these components has its settings tweaked slightly to match the ideal scene to test Atom rendering. - :param level_name: name of the level to create and apply this basic setup to. + Compares the similarity between a test screenshot and a golden image. + It returns a "Screenshots match" string if the comparison mean value is higher than the similarity threshold. + Otherwise, it returns an error string. + :param test_screenshot: path to the test screenshot to compare. + :param golden_image: path to the golden image to compare. + :param similarity_threshold: value for the comparison mean value to be asserted against. + :param create_zip_archive: toggle to create a zip archive containing the screenshots if the assert check fails. + :param screenshot_directory: directory containing screenshots to create zip archive from. + :return: Error string if compared mean value < similarity threshold or screenshot_directory is missing for .zip, + otherwise it returns a "Screenshots match" string. + """ + result = "Screenshots match" + if create_zip_archive and not screenshot_directory: + result = 'You must specify a screenshot_directory in order to create a zip archive.\n' + + mean_similarity = compare_screenshots(test_screenshot, golden_image) + if not mean_similarity > similarity_threshold: + if create_zip_archive: + create_screenshots_archive(screenshot_directory) + result = ( + f"When comparing the test_screenshot: '{test_screenshot}' " + f"to golden_image: '{golden_image}' the mean similarity of '{mean_similarity}' " + f"was lower than the similarity threshold of '{similarity_threshold}'. ") + + return result + + +def compare_screenshot_to_golden_image( + screenshot_directory, test_screenshots, golden_images, similarity_threshold=0.99): + """ + Compares a list of test_screenshots to a list of golden_images and return True if they match within the + similarity threshold set. Otherwise, it will raise ImageComparisonTestFailure with a failure message. + :param screenshot_directory: path to the directory containing screenshots for creating .zip archives. + :param test_screenshots: list of test screenshot path strings. + :param golden_images: list of golden image path strings. + :param similarity_threshold: float threshold tolerance to set when comparing screenshots to golden images. + """ + for test_screenshot, golden_image in zip(test_screenshots, golden_images): + screenshot_comparison_result = compare_screenshot_similarity( + test_screenshot, golden_image, similarity_threshold, True, screenshot_directory) + if screenshot_comparison_result != "Screenshots match": + raise ImageComparisonTestFailure(f"Screenshot test failed: {screenshot_comparison_result}") + + return True + + +def initial_viewport_setup(screen_width=1280, screen_height=720): + """ + For setting up the initial viewport resolution to expected default values before running a screenshot test. + Defaults to 1280 x 720 resolution (in pixels). + :param screen_width: Width in pixels to set the viewport width size to. + :param screen_height: Height in pixels to set the viewport height size to. :return: None """ - import azlmbr.asset as asset - import azlmbr.bus as bus - import azlmbr.camera as camera - import azlmbr.editor as editor - import azlmbr.entity as entity import azlmbr.legacy.general as general - import azlmbr.math as math - import azlmbr.object - import editor_python_test_tools.hydra_editor_utils as hydra - from editor_python_test_tools.editor_test_helper import EditorTestHelper - - helper = EditorTestHelper(log_prefix="Atom_EditorTestHelper") - - # Create a new level. - new_level_name = level_name - heightmap_resolution = 512 - heightmap_meters_per_pixel = 1 - terrain_texture_resolution = 412 - use_terrain = False - - # Return codes are ECreateLevelResult defined in CryEdit.h - return_code = general.create_level_no_prompt( - new_level_name, heightmap_resolution, heightmap_meters_per_pixel, terrain_texture_resolution, use_terrain) - if return_code == 1: - general.log(f"{new_level_name} level already exists") - elif return_code == 2: - general.log("Failed to create directory") - elif return_code == 3: - general.log("Directory length is too long") - elif return_code != 0: - general.log("Unknown error, failed to create level") - else: - general.log(f"{new_level_name} level created successfully") - - # Enable idle and update viewport. - 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) - - # Delete all existing entities & 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") - default_position = math.Vector3(0.0, 0.0, 0.0) - default_level.create_entity(default_position, ["Grid"]) - default_level.get_set_test(0, "Controller|Configuration|Secondary Grid Spacing", 1.0) - - # Set the viewport up correctly after adding the parent default_level entity. - screen_width = 1280 - screen_height = 720 - degree_radian_factor = 0.0174533 # Used by "Rotation" property for the Transform component. general.set_viewport_size(screen_width, screen_height) general.update_viewport() - helper.wait_for_condition( - function=lambda: helper.isclose(a=general.get_viewport_size().x, b=screen_width, rel_tol=0.1) - and helper.isclose(a=general.get_viewport_size().y, b=screen_height, rel_tol=0.1), - timeout_in_seconds=4.0 - ) - result = helper.isclose(a=general.get_viewport_size().x, b=screen_width, rel_tol=0.1) and helper.isclose( - a=general.get_viewport_size().y, b=screen_height, rel_tol=0.1) - general.log(general.get_viewport_size().x) - general.log(general.get_viewport_size().y) - general.log(general.get_viewport_size().z) - general.log(f"Viewport is set to the expected size: {result}") - general.log("Basic level created") - general.run_console("r_DisplayInfo = 0") - # Create global_skylight entity and set the properties - global_skylight = hydra.Entity("global_skylight") - global_skylight.create_entity( - entity_position=default_position, - components=["HDRi Skybox", "Global Skylight (IBL)"], - parent_id=default_level.id) - global_skylight_asset_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage") - global_skylight_asset_value = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", global_skylight_asset_path, math.Uuid(), False) - global_skylight.get_set_test(0, "Controller|Configuration|Cubemap Texture", global_skylight_asset_value) - global_skylight.get_set_test(1, "Controller|Configuration|Diffuse Image", global_skylight_asset_value) - global_skylight.get_set_test(1, "Controller|Configuration|Specular Image", global_skylight_asset_value) - # Create ground_plane entity and set the properties - ground_plane = hydra.Entity("ground_plane") - ground_plane.create_entity( - entity_position=default_position, - components=["Material"], - parent_id=default_level.id) - azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalUniformScale", ground_plane.id, 32.0) - ground_plane_material_asset_path = os.path.join( - "Materials", "Presets", "PBR", "metal_chrome.azmaterial") - ground_plane_material_asset_value = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", ground_plane_material_asset_path, math.Uuid(), False) - ground_plane.get_set_test(0, "Default Material|Material Asset", ground_plane_material_asset_value) +def enter_exit_game_mode_take_screenshot(screenshot_name, enter_game_tuple, exit_game_tuple, timeout_in_seconds=4): + """ + Enters game mode, takes a screenshot named screenshot_name (must include file extension), and exits game mode. + :param screenshot_name: string representing the name of the screenshot file, including file extension. + :param enter_game_tuple: tuple where the 1st string is success & 2nd string is failure for entering the game. + :param exit_game_tuple: tuple where the 1st string is success & 2nd string is failure for exiting the game. + :param timeout_in_seconds: int or float seconds to wait for entering/exiting game mode. + :return: None + """ + import azlmbr.legacy.general as general - # Work around to add the correct Atom Mesh component - mesh_type_id = azlmbr.globals.property.EditorMeshComponentTypeId - ground_plane.components.append( - editor.EditorComponentAPIBus( - bus.Broadcast, "AddComponentsOfType", ground_plane.id, [mesh_type_id] - ).GetValue()[0] - ) - ground_plane_mesh_asset_path = os.path.join("Models", "plane.azmodel") - ground_plane_mesh_asset_value = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", ground_plane_mesh_asset_path, math.Uuid(), False) - ground_plane.get_set_test(1, "Controller|Configuration|Mesh Asset", ground_plane_mesh_asset_value) + from editor_python_test_tools.utils import TestHelper - # Create directional_light entity and set the properties - directional_light = hydra.Entity("directional_light") - directional_light.create_entity( - entity_position=math.Vector3(0.0, 0.0, 10.0), - components=["Directional Light"], - parent_id=default_level.id) - directional_light_rotation = math.Vector3(degree_radian_factor * -90.0, 0.0, 0.0) - azlmbr.components.TransformBus( - azlmbr.bus.Event, "SetLocalRotation", directional_light.id, directional_light_rotation) + from Atom.atom_utils.screenshot_utils import ScreenshotHelper - # Create sphere entity and set the properties - sphere_entity = hydra.Entity("sphere") - sphere_entity.create_entity( - entity_position=math.Vector3(0.0, 0.0, 1.0), - components=["Material"], - parent_id=default_level.id) - sphere_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_brass_polished.azmaterial") - sphere_material_asset_value = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", sphere_material_asset_path, math.Uuid(), False) - sphere_entity.get_set_test(0, "Default Material|Material Asset", sphere_material_asset_value) + TestHelper.enter_game_mode(enter_game_tuple) + TestHelper.wait_for_condition(function=lambda: general.is_in_game_mode(), timeout_in_seconds=timeout_in_seconds) + ScreenshotHelper(general.idle_wait_frames).capture_screenshot_blocking(screenshot_name) + TestHelper.exit_game_mode(exit_game_tuple) + TestHelper.wait_for_condition(function=lambda: not general.is_in_game_mode(), timeout_in_seconds=timeout_in_seconds) - # Work around to add the correct Atom Mesh component - sphere_entity.components.append( - editor.EditorComponentAPIBus( - bus.Broadcast, "AddComponentsOfType", sphere_entity.id, [mesh_type_id] - ).GetValue()[0] - ) + +def create_basic_atom_rendering_scene(): + """ + Sets up a new scene inside the Editor for testing Atom rendering GPU output. + Setup: Deletes all existing entities before creating the scene. + The created scene includes: + 1. "Default Level" entity that holds all of the other entities. + 2. "Grid" entity: Contains a Grid component. + 3. "Global Skylight (IBL)" entity: Contains HDRI Skybox & Global Skylight (IBL) components. + 4. "Ground Plane" entity: Contains Material & Mesh components. + 5. "Directional Light" entity: Contains Directional Light component. + 6. "Sphere" entity: Contains Material & Mesh components. + 7. "Camera" entity: Contains Camera component. + :return: None + """ + import azlmbr.math as math + import azlmbr.paths + + from editor_python_test_tools.asset_utils import Asset + from editor_python_test_tools.editor_entity_utils import EditorEntity + + from Atom.atom_utils.atom_constants import AtomComponentProperties + + DEGREE_RADIAN_FACTOR = 0.0174533 + + # Setup: Deletes all existing entities before creating the scene. + search_filter = azlmbr.entity.SearchFilter() + all_entities = azlmbr.entity.SearchBus(azlmbr.bus.Broadcast, "SearchEntities", search_filter) + azlmbr.editor.ToolsApplicationRequestBus(azlmbr.bus.Broadcast, "DeleteEntities", all_entities) + + # 1. "Default Level" entity that holds all of the other entities. + default_level_entity_name = "Default Level" + default_level_entity = EditorEntity.create_editor_entity_at(math.Vector3(0.0, 0.0, 0.0), default_level_entity_name) + + # 2. "Grid" entity: Contains a Grid component. + grid_entity = EditorEntity.create_editor_entity(AtomComponentProperties.grid(), default_level_entity.id) + grid_component = grid_entity.add_component(AtomComponentProperties.grid()) + secondary_grid_spacing_value = 1.0 + grid_component.set_component_property_value( + AtomComponentProperties.grid('Secondary Grid Spacing'), secondary_grid_spacing_value) + + # 3. "Global Skylight (IBL)" entity: Contains HDRI Skybox & Global Skylight (IBL) components. + global_skylight_entity = EditorEntity.create_editor_entity( + AtomComponentProperties.global_skylight(), default_level_entity.id) + hdri_skybox_component = global_skylight_entity.add_component(AtomComponentProperties.hdri_skybox()) + global_skylight_component = global_skylight_entity.add_component(AtomComponentProperties.global_skylight()) + global_skylight_image_asset_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage") + global_skylight_image_asset = Asset.find_asset_by_path(global_skylight_image_asset_path, False) + hdri_skybox_component.set_component_property_value( + AtomComponentProperties.hdri_skybox('Cubemap Texture'), global_skylight_image_asset.id) + global_skylight_diffuse_image_asset_path = os.path.join( + "LightingPresets", "default_iblskyboxcm_ibldiffuse.exr.streamingimage") + global_skylight_diffuse_image_asset = Asset.find_asset_by_path(global_skylight_diffuse_image_asset_path, False) + global_skylight_component.set_component_property_value( + AtomComponentProperties.global_skylight('Diffuse Image'), global_skylight_diffuse_image_asset.id) + global_skylight_specular_image_asset_path = os.path.join( + "LightingPresets", "default_iblskyboxcm_iblspecular.exr.streamingimage") + global_skylight_specular_image_asset = Asset.find_asset_by_path( + global_skylight_specular_image_asset_path, False) + global_skylight_component.set_component_property_value( + AtomComponentProperties.global_skylight('Specular Image'), global_skylight_specular_image_asset.id) + + # 4. "Ground Plane" entity: Contains Material & Mesh components. + ground_plane_name = "Ground Plane" + ground_plane_entity = EditorEntity.create_editor_entity(ground_plane_name, default_level_entity.id) + ground_plane_material_component = ground_plane_entity.add_component(AtomComponentProperties.material()) + ground_plane_entity.set_local_uniform_scale(32.0) + ground_plane_mesh_component = ground_plane_entity.add_component(AtomComponentProperties.mesh()) + ground_plane_mesh_asset_path = os.path.join("TestData", "Objects", "plane.azmodel") + ground_plane_mesh_asset = Asset.find_asset_by_path(ground_plane_mesh_asset_path, False) + ground_plane_mesh_component.set_component_property_value( + AtomComponentProperties.mesh('Mesh Asset'), ground_plane_mesh_asset.id) + ground_plane_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_chrome.azmaterial") + ground_plane_material_asset = Asset.find_asset_by_path(ground_plane_material_asset_path, False) + ground_plane_material_component.set_component_property_value( + AtomComponentProperties.material('Material Asset'), ground_plane_material_asset.id) + + # 5. "Directional Light" entity: Contains Directional Light component. + directional_light_entity = EditorEntity.create_editor_entity_at( + math.Vector3(0.0, 0.0, 10.0), AtomComponentProperties.directional_light(), default_level_entity.id) + directional_light_entity.add_component(AtomComponentProperties.directional_light()) + directional_light_entity_rotation = math.Vector3(DEGREE_RADIAN_FACTOR * -90.0, 0.0, 0.0) + directional_light_entity.set_local_rotation(directional_light_entity_rotation) + + # 6. "Sphere" entity: Contains Material & Mesh components. + sphere_entity = EditorEntity.create_editor_entity_at( + math.Vector3(0.0, 0.0, 1.0), "Sphere", default_level_entity.id) + sphere_mesh_component = sphere_entity.add_component(AtomComponentProperties.mesh()) sphere_mesh_asset_path = os.path.join("Models", "sphere.azmodel") - sphere_mesh_asset_value = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", sphere_mesh_asset_path, math.Uuid(), False) - sphere_entity.get_set_test(1, "Controller|Configuration|Mesh Asset", sphere_mesh_asset_value) + sphere_mesh_asset = Asset.find_asset_by_path(sphere_mesh_asset_path, False) + sphere_mesh_component.set_component_property_value( + AtomComponentProperties.mesh('Mesh Asset'), sphere_mesh_asset.id) + sphere_material_component = sphere_entity.add_component(AtomComponentProperties.material()) + sphere_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_brass_polished.azmaterial") + sphere_material_asset = Asset.find_asset_by_path(sphere_material_asset_path, False) + sphere_material_component.set_component_property_value( + AtomComponentProperties.material('Material Asset'), sphere_material_asset.id) - # Create camera component and set the properties - camera_entity = hydra.Entity("camera") - camera_entity.create_entity( - entity_position=math.Vector3(5.5, -12.0, 9.0), - components=["Camera"], - parent_id=default_level.id) - rotation = math.Vector3( - degree_radian_factor * -27.0, degree_radian_factor * -12.0, degree_radian_factor * 25.0 - ) - azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", camera_entity.id, rotation) - camera_entity.get_set_test(0, "Controller|Configuration|Field of view", 60.0) - camera.EditorCameraViewRequestBus(azlmbr.bus.Event, "ToggleCameraAsActiveView", camera_entity.id) + # 7. "Camera" entity: Contains Camera component. + camera_entity = EditorEntity.create_editor_entity_at( + math.Vector3(5.5, -12.0, 9.0), AtomComponentProperties.camera(), default_level_entity.id) + camera_component = camera_entity.add_component(AtomComponentProperties.camera()) + camera_entity_rotation = math.Vector3( + DEGREE_RADIAN_FACTOR * -27.0, DEGREE_RADIAN_FACTOR * -12.0, DEGREE_RADIAN_FACTOR * 25.0) + camera_entity.set_local_rotation(camera_entity_rotation) + camera_fov_value = 60.0 + camera_component.set_component_property_value(AtomComponentProperties.camera('Field of view'), camera_fov_value) + azlmbr.camera.EditorCameraViewRequestBus(azlmbr.bus.Event, "ToggleCameraAsActiveView", camera_entity.id) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py index 4884dbff56..e6d6ac7fb1 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/atom_constants.py @@ -19,6 +19,20 @@ LIGHT_TYPES = { } +# Attenuation Radius Mode options for the Light component. +ATTENUATION_RADIUS_MODE = { + 'automatic': 1, + 'explicit': 0, +} + +# Qualiity Level settings for Diffuse Global Illumination level component +GLOBAL_ILLUMINATION_QUALITY = { + 'Low': 0, + 'Medium': 1, + 'High': 2, +} + + class AtomComponentProperties: """ Holds Atom component related constants @@ -42,12 +56,14 @@ class AtomComponentProperties: Bloom component properties. Requires PostFX Layer component. - 'requires' a list of component names as strings required by this component. Use editor_entity_utils EditorEntity.add_components(list) to add this list of requirements.\n + - 'Enable Bloom' Toggle active state of the component True/False :param property: From the last element of the property tree path. Default 'name' for component name string. :return: Full property path OR component name if no property specified. """ properties = { 'name': 'Bloom', 'requires': [AtomComponentProperties.postfx_layer()], + 'Enable Bloom': 'Controller|Configuration|Enable Bloom', } return properties[property] @@ -85,12 +101,14 @@ class AtomComponentProperties: Deferred Fog component properties. Requires PostFX Layer component. - 'requires' a list of component names as strings required by this component. Use editor_entity_utils EditorEntity.add_components(list) to add this list of requirements.\n + - 'Enable Deferred Fog' Toggle active state of the component True/False :param property: From the last element of the property tree path. Default 'name' for component name string. :return: Full property path OR component name if no property specified. """ properties = { 'name': 'Deferred Fog', 'requires': [AtomComponentProperties.postfx_layer()], + 'Enable Deferred Fog': 'Controller|Configuration|Enable Deferred Fog', } return properties[property] @@ -113,7 +131,22 @@ class AtomComponentProperties: return properties[property] @staticmethod - def diffuse_probe(property: str = 'name') -> str: + def diffuse_global_illumination(property: str = 'name') -> str: + """ + Diffuse Global Illumination level component properties. + Controls global settings for Diffuse Probe Grid components. + - 'Quality Level' from atom_constants.py GLOBAL_ILLUMINATION_QUALITY + :param property: From the last element of the property tree path. Default 'name' for component name string. + :return: Full property path OR component name if no property specified. + """ + properties = { + 'name': 'Diffuse Global Illumination', + 'Quality Level': 'Controller|Configuration|Quality Level' + } + return properties[property] + + @staticmethod + def diffuse_probe_grid(property: str = 'name') -> str: """ Diffuse Probe Grid component properties. Requires one of 'shapes'. - 'shapes' a list of supported shapes as component names. @@ -144,12 +177,17 @@ class AtomComponentProperties: @staticmethod def display_mapper(property: str = 'name') -> str: """ - Display Mapper component properties. + Display Mapper level component properties. + - 'Enable LDR color grading LUT' toggles the use of LDR color grading LUT + - 'LDR color Grading LUT' is the Low Definition Range (LDR) color grading for Look-up Textures (LUT) which is + an Asset.id value corresponding to a lighting asset file. :param property: From the last element of the property tree path. Default 'name' for component name string. :return: Full property path OR component name if no property specified. """ properties = { 'name': 'Display Mapper', + 'Enable LDR color grading LUT': 'Controller|Configuration|Enable LDR color grading LUT', + 'LDR color Grading LUT': 'Controller|Configuration|LDR color Grading LUT', } return properties[property] @@ -216,12 +254,14 @@ class AtomComponentProperties: HDR Color Grading component properties. Requires PostFX Layer component. - 'requires' a list of component names as strings required by this component. Use editor_entity_utils EditorEntity.add_components(list) to add this list of requirements.\n + - 'Enable HDR color grading' Toggle active state of the component True/False :param property: From the last element of the property tree path. Default 'name' for component name string. :return: Full property path OR component name if no property specified. """ properties = { 'name': 'HDR Color Grading', 'requires': [AtomComponentProperties.postfx_layer()], + 'Enable HDR color grading': 'Controller|Configuration|Enable HDR color grading', } return properties[property] @@ -243,13 +283,27 @@ class AtomComponentProperties: def light(property: str = 'name') -> str: """ Light component properties. + - 'Attenuation Radius Mode' controls whether the attenuation radius is calculated automatically or explicitly. + - 'Color' the RGB value to set for the color of the light. + - 'Enable shadow' toggle for enabling shadows for the light. + - 'Enable shutters' toggle for enabling shutters for the light. + - 'Inner angle' inner angle value for the shutters (in degrees) + - 'Intensity' the intensity of the light in the set photometric unit (float with no ceiling). - 'Light type' from atom_constants.py LIGHT_TYPES + - 'Outer angle' outer angle value for the shutters (in degrees) :param property: From the last element of the property tree path. Default 'name' for component name string. :return: Full property path OR component name if no property specified. """ properties = { 'name': 'Light', + 'Attenuation Radius Mode': 'Controller|Configuration|Attenuation radius|Mode', + 'Color': 'Controller|Configuration|Color', + 'Enable shadow': 'Controller|Configuration|Shadows|Enable shadow', + 'Enable shutters': 'Controller|Configuration|Shutters|Enable shutters', + 'Inner angle': 'Controller|Configuration|Shutters|Inner angle', + 'Intensity': 'Controller|Configuration|Intensity', 'Light type': 'Controller|Configuration|Light type', + 'Outer angle': 'Controller|Configuration|Shutters|Outer angle', } return properties[property] @@ -259,12 +313,16 @@ class AtomComponentProperties: Look Modification component properties. Requires PostFX Layer component. - 'requires' a list of component names as strings required by this component. Use editor_entity_utils EditorEntity.add_components(list) to add this list of requirements.\n + - 'Enable look modification' Toggle active state of the component True/False + - 'Color Grading LUT' Asset.id for the LUT used for affecting level look. :param property: From the last element of the property tree path. Default 'name' for component name string. :return: Full property path OR component name if no property specified. """ properties = { 'name': 'Look Modification', 'requires': [AtomComponentProperties.postfx_layer()], + 'Enable look modification': 'Controller|Configuration|Enable look modification', + 'Color Grading LUT': 'Controller|Configuration|Color Grading LUT', } return properties[property] @@ -380,7 +438,7 @@ class AtomComponentProperties: 'name': 'PostFX Shape Weight Modifier', 'requires': [AtomComponentProperties.postfx_layer()], 'shapes': ['Axis Aligned Box Shape', 'Box Shape', 'Capsule Shape', 'Compound Shape', 'Cylinder Shape', - 'Disk Shape', 'Polygon Prism Shape', 'Quad Shape', 'Sphere Shape', 'Vegetation Reference Shape'], + 'Disk Shape', 'Polygon Prism Shape', 'Quad Shape', 'Sphere Shape', 'Shape Reference'], } return properties[property] diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponentsLevel_DiffuseGlobalIlluminationAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponentsLevel_DiffuseGlobalIlluminationAdded.py new file mode 100644 index 0000000000..ddcd5c3c46 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponentsLevel_DiffuseGlobalIlluminationAdded.py @@ -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) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponentsLevel_DisplayMapperAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponentsLevel_DisplayMapperAdded.py new file mode 100644 index 0000000000..c050dd76d4 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponentsLevel_DisplayMapperAdded.py @@ -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) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_AddedToEntity.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_AddedToEntity.py deleted file mode 100644 index bbc8463152..0000000000 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_AddedToEntity.py +++ /dev/null @@ -1,238 +0,0 @@ -""" -Copyright (c) Contributors to the Open 3D Engine Project. -For complete copyright and license terms please see the LICENSE at the root of this distribution. - -SPDX-License-Identifier: Apache-2.0 OR MIT -""" - -import os -import sys - -import azlmbr.math as math -import azlmbr.bus as bus -import azlmbr.paths -import azlmbr.asset as asset -import azlmbr.entity as entity -import azlmbr.legacy.general as general -import azlmbr.editor as editor -import azlmbr.render as render - -sys.path.append(os.path.join(azlmbr.paths.projectroot, "Gem", "PythonTests")) - -import editor_python_test_tools.hydra_editor_utils as hydra -from editor_python_test_tools.utils import TestHelper - - -def run(): - """ - Summary: - The below common tests are done for each of the components. - 1) Addition of component to the entity - 2) UNDO/REDO of addition of component - 3) Enter/Exit game mode - 4) Hide/Show entity containing component - 5) Deletion of component - 6) UNDO/REDO of deletion of component - Some additional tests for specific components include - 1) Assigning value to some properties of each component - 2) Verifying if the component is activated only when the required components are added - - Expected Result: - 1) Component can be added to an entity. - 2) The addition of component can be undone and redone. - 3) Game mode can be entered/exited without issue. - 4) Entity with component can be hidden/shown. - 5) Component can be deleted. - 6) The deletion of component can be undone and redone. - 7) Component is activated only when the required components are added - 8) Values can be assigned to the properties of the component - - :return: None - """ - - def create_entity_undo_redo_component_addition(component_name): - new_entity = hydra.Entity(f"{component_name}") - new_entity.create_entity(math.Vector3(512.0, 512.0, 34.0), [component_name]) - general.log(f"{component_name}_test: Component added to the entity: " - f"{hydra.has_components(new_entity.id, [component_name])}") - - # undo component addition - general.undo() - TestHelper.wait_for_condition(lambda: not hydra.has_components(new_entity.id, [component_name]), 2.0) - general.log(f"{component_name}_test: Component removed after UNDO: " - f"{not hydra.has_components(new_entity.id, [component_name])}") - - # redo component addition - general.redo() - TestHelper.wait_for_condition(lambda: hydra.has_components(new_entity.id, [component_name]), 2.0) - general.log(f"{component_name}_test: Component added after REDO: " - f"{hydra.has_components(new_entity.id, [component_name])}") - - return new_entity - - def verify_enter_exit_game_mode(component_name): - general.enter_game_mode() - TestHelper.wait_for_condition(lambda: general.is_in_game_mode(), 2.0) - general.log(f"{component_name}_test: Entered game mode: {general.is_in_game_mode()}") - general.exit_game_mode() - TestHelper.wait_for_condition(lambda: not general.is_in_game_mode(), 2.0) - general.log(f"{component_name}_test: Exit game mode: {not general.is_in_game_mode()}") - - def verify_hide_unhide_entity(component_name, entity_obj): - - def is_entity_hidden(entity_id): - return editor.EditorEntityInfoRequestBus(bus.Event, "IsHidden", entity_id) - - editor.EditorEntityAPIBus(bus.Event, "SetVisibilityState", entity_obj.id, False) - general.idle_wait_frames(1) - general.log(f"{component_name}_test: Entity is hidden: {is_entity_hidden(entity_obj.id)}") - editor.EditorEntityAPIBus(bus.Event, "SetVisibilityState", entity_obj.id, True) - general.idle_wait_frames(1) - general.log(f"{component_name}_test: Entity is shown: {not is_entity_hidden(entity_obj.id)}") - - def verify_deletion_undo_redo(component_name, entity_obj): - editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntityById", entity_obj.id) - TestHelper.wait_for_condition(lambda: not hydra.find_entity_by_name(entity_obj.name), 2.0) - general.log(f"{component_name}_test: Entity deleted: {not hydra.find_entity_by_name(entity_obj.name)}") - - general.undo() - TestHelper.wait_for_condition(lambda: hydra.find_entity_by_name(entity_obj.name) is not None, 2.0) - general.log(f"{component_name}_test: UNDO entity deletion works: " - f"{hydra.find_entity_by_name(entity_obj.name) is not None}") - - general.redo() - TestHelper.wait_for_condition(lambda: not hydra.find_entity_by_name(entity_obj.name), 2.0) - general.log(f"{component_name}_test: REDO entity deletion works: " - f"{not hydra.find_entity_by_name(entity_obj.name)}") - - def verify_required_component_addition(entity_obj, components_to_add, component_name): - - def is_component_enabled(entity_componentid_pair): - return editor.EditorComponentAPIBus(bus.Broadcast, "IsComponentEnabled", entity_componentid_pair) - - general.log( - f"{component_name}_test: Entity disabled initially: " - f"{not is_component_enabled(entity_obj.components[0])}") - for component in components_to_add: - entity_obj.add_component(component) - TestHelper.wait_for_condition(lambda: is_component_enabled(entity_obj.components[0]), 2.0) - general.log( - f"{component_name}_test: Entity enabled after adding " - f"required components: {is_component_enabled(entity_obj.components[0])}" - ) - - def verify_set_property(entity_obj, path, value): - entity_obj.get_set_test(0, path, value) - - # Verify cubemap generation - def verify_cubemap_generation(component_name, entity_obj): - # Initially Check if the component has Reflection Probe component - if not hydra.has_components(entity_obj.id, ["Reflection Probe"]): - raise ValueError(f"Given entity {entity_obj.name} has no Reflection Probe component") - render.EditorReflectionProbeBus(azlmbr.bus.Event, "BakeReflectionProbe", entity_obj.id) - - def get_value(): - hydra.get_component_property_value(entity_obj.components[0], "Cubemap|Baked Cubemap Path") - - TestHelper.wait_for_condition(lambda: get_value() != "", 20.0) - general.log(f"{component_name}_test: Cubemap is generated: {get_value() != ''}") - - # Wait for Editor idle loop before executing Python hydra scripts. - TestHelper.init_idle() - - # Delete all existing entities initially - search_filter = azlmbr.entity.SearchFilter() - all_entities = entity.SearchBus(azlmbr.bus.Broadcast, "SearchEntities", search_filter) - editor.ToolsApplicationRequestBus(bus.Broadcast, "DeleteEntities", all_entities) - - class ComponentTests: - """Test launcher for each component.""" - def __init__(self, component_name, *additional_tests): - self.component_name = component_name - self.additional_tests = additional_tests - self.run_component_tests() - - def run_component_tests(self): - # Run common and additional tests - entity_obj = create_entity_undo_redo_component_addition(self.component_name) - - # Enter/Exit game mode test - verify_enter_exit_game_mode(self.component_name) - - # Any additional tests are executed here - for test in self.additional_tests: - test(entity_obj) - - # Hide/Unhide entity test - verify_hide_unhide_entity(self.component_name, entity_obj) - - # Deletion/Undo/Redo test - verify_deletion_undo_redo(self.component_name, entity_obj) - - # DepthOfField Component - camera_entity = hydra.Entity("camera_entity") - camera_entity.create_entity(math.Vector3(512.0, 512.0, 34.0), ["Camera"]) - depth_of_field = "DepthOfField" - ComponentTests( - depth_of_field, - lambda entity_obj: verify_required_component_addition(entity_obj, ["PostFX Layer"], depth_of_field), - lambda entity_obj: verify_set_property( - entity_obj, "Controller|Configuration|Camera Entity", camera_entity.id)) - - # Decal Component - material_asset_path = os.path.join("AutomatedTesting", "Materials", "basic_grey.material") - material_asset = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", material_asset_path, math.Uuid(), False) - ComponentTests( - "Decal", lambda entity_obj: verify_set_property( - entity_obj, "Controller|Configuration|Material", material_asset)) - - # Directional Light Component - ComponentTests( - "Directional Light", - lambda entity_obj: verify_set_property( - entity_obj, "Controller|Configuration|Shadow|Camera", camera_entity.id)) - - # Exposure Control Component - ComponentTests( - "Exposure Control", lambda entity_obj: verify_required_component_addition( - entity_obj, ["PostFX Layer"], "Exposure Control")) - - # Global Skylight (IBL) Component - diffuse_image_path = os.path.join("LightingPresets", "greenwich_park_02_4k_iblskyboxcm.exr.streamingimage") - diffuse_image_asset = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", diffuse_image_path, math.Uuid(), False) - specular_image_path = os.path.join("LightingPresets", "greenwich_park_02_4k_iblskyboxcm.exr.streamingimage") - specular_image_asset = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", specular_image_path, math.Uuid(), False) - ComponentTests( - "Global Skylight (IBL)", - lambda entity_obj: verify_set_property( - entity_obj, "Controller|Configuration|Diffuse Image", diffuse_image_asset), - lambda entity_obj: verify_set_property( - entity_obj, "Controller|Configuration|Specular Image", specular_image_asset)) - - # Physical Sky Component - ComponentTests("Physical Sky") - - # PostFX Layer Component - ComponentTests("PostFX Layer") - - # PostFX Radius Weight Modifier Component - ComponentTests("PostFX Radius Weight Modifier") - - # Light Component - ComponentTests("Light") - - # Display Mapper Component - ComponentTests("Display Mapper") - - # Reflection Probe Component - reflection_probe = "Reflection Probe" - ComponentTests( - reflection_probe, - lambda entity_obj: verify_required_component_addition(entity_obj, ["Box Shape"], reflection_probe), - lambda entity_obj: verify_cubemap_generation(reflection_probe, entity_obj),) - -if __name__ == "__main__": - run() diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_BloomAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_BloomAdded.py new file mode 100644 index 0000000000..56b22eb20d --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_BloomAdded.py @@ -0,0 +1,189 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + + +class Tests: + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + bloom_creation = ( + "Bloom Entity successfully created", + "Bloom Entity failed to be created") + bloom_component = ( + "Entity has a Bloom component", + "Entity failed to find Bloom component") + bloom_disabled = ( + "Bloom component disabled", + "Bloom component was not disabled") + postfx_layer_component = ( + "Entity has a PostFX Layer component", + "Entity did not have an PostFX Layer component") + bloom_enabled = ( + "Bloom component enabled", + "Bloom component was not enabled") + enable_bloom_parameter_enabled = ( + "Enable Bloom parameter enabled", + "Enable Bloom parameter was not enabled") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") + + +def AtomEditorComponents_Bloom_AddedToEntity(): + """ + Summary: + Tests the Bloom component can be added to an entity and has the expected functionality. + + Test setup: + - Wait for Editor idle loop. + - Open the "Base" level. + + Expected Behavior: + The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components. + Creation and deletion undo/redo should also work. + + Test Steps: + 1) Create an Bloom entity with no components. + 2) Add Bloom component to Bloom entity. + 3) UNDO the entity creation and component addition. + 4) REDO the entity creation and component addition. + 5) Verify Bloom component not enabled. + 6) Add PostFX Layer component since it is required by the Bloom component. + 7) Verify Bloom component is enabled. + 8) Enable the "Enable Bloom" parameter. + 9) Enter/Exit game mode. + 10) Test IsHidden. + 11) Test IsVisible. + 12) Delete Bloom entity. + 13) UNDO deletion. + 14) REDO deletion. + 15) Look for errors. + + :return: None + """ + + import azlmbr.legacy.general as general + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties + + with Tracer() as error_tracer: + # Test setup begins. + # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. + TestHelper.init_idle() + TestHelper.open_level("", "Base") + + # Test steps begin. + # 1. Create an Bloom entity with no components. + bloom_entity = EditorEntity.create_editor_entity(AtomComponentProperties.bloom()) + Report.critical_result(Tests.bloom_creation, bloom_entity.exists()) + + # 2. Add Bloom component to Bloom entity. + bloom_component = bloom_entity.add_component(AtomComponentProperties.bloom()) + Report.critical_result(Tests.bloom_component, bloom_entity.has_component(AtomComponentProperties.bloom())) + + # 3. UNDO the entity creation and component addition. + # -> UNDO component addition. + general.undo() + # -> UNDO naming entity. + general.undo() + # -> UNDO selecting entity. + general.undo() + # -> UNDO entity creation. + general.undo() + general.idle_wait_frames(1) + Report.result(Tests.creation_undo, not bloom_entity.exists()) + + # 4. REDO the entity creation and component addition. + # -> REDO entity creation. + general.redo() + # -> REDO selecting entity. + general.redo() + # -> REDO naming entity. + general.redo() + # -> REDO component addition. + general.redo() + general.idle_wait_frames(1) + Report.result(Tests.creation_redo, bloom_entity.exists()) + + # 5. Verify Bloom component not enabled. + Report.result(Tests.bloom_disabled, not bloom_component.is_enabled()) + + # 6. Add PostFX Layer component since it is required by the Bloom component. + bloom_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.result( + Tests.postfx_layer_component, + bloom_entity.has_component(AtomComponentProperties.postfx_layer())) + + # 7. Verify Bloom component is enabled. + Report.result(Tests.bloom_enabled, bloom_component.is_enabled()) + + # 8. Enable the "Enable Bloom" parameter. + bloom_component.set_component_property_value(AtomComponentProperties.bloom('Enable Bloom'), True) + Report.result( + Tests.enable_bloom_parameter_enabled, + bloom_component.get_component_property_value(AtomComponentProperties.bloom('Enable Bloom')) is True) + + # 9. Enter/Exit game mode. + TestHelper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + TestHelper.exit_game_mode(Tests.exit_game_mode) + + # 10. Test IsHidden. + bloom_entity.set_visibility_state(False) + Report.result(Tests.is_hidden, bloom_entity.is_hidden() is True) + + # 11. Test IsVisible. + bloom_entity.set_visibility_state(True) + general.idle_wait_frames(1) + Report.result(Tests.is_visible, bloom_entity.is_visible() is True) + + # 12. Delete Bloom entity. + bloom_entity.delete() + Report.result(Tests.entity_deleted, not bloom_entity.exists()) + + # 13. UNDO deletion. + general.undo() + Report.result(Tests.deletion_undo, bloom_entity.exists()) + + # 14. REDO deletion. + general.redo() + Report.result(Tests.deletion_redo, not bloom_entity.exists()) + + # 15. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(AtomEditorComponents_Bloom_AddedToEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DecalAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DecalAdded.py index fa02b75fe2..e840cf3cf1 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DecalAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DecalAdded.py @@ -5,25 +5,52 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off class Tests: - camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created") - camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity") - camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component") - creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed") - creation_redo = ("REDO Entity creation success", "REDO Entity creation failed") - decal_creation = ("Decal Entity successfully created", "Decal Entity failed to be created") - decal_component = ("Entity has a Decal component", "Entity failed to find Decal component") - material_property_set = ("Material property set on Decal component", "Couldn't set Material property on Decal component") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - is_visible = ("Entity is visible", "Entity was not visible") - is_hidden = ("Entity is hidden", "Entity was not hidden") - entity_deleted = ("Entity deleted", "Entity was not deleted") - deletion_undo = ("UNDO deletion success", "UNDO deletion failed") - deletion_redo = ("REDO deletion success", "REDO deletion failed") - no_error_occurred = ("No errors detected", "Errors were detected") -# fmt: on + camera_creation = ( + "Camera Entity successfully created", + "Camera Entity failed to be created") + camera_component_added = ( + "Camera component was added to entity", + "Camera component failed to be added to entity") + camera_component_check = ( + "Entity has a Camera component", + "Entity failed to find Camera component") + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + decal_creation = ( + "Decal Entity successfully created", + "Decal Entity failed to be created") + decal_component = ( + "Entity has a Decal component", + "Entity failed to find Decal component") + material_property_set = ( + "Material property set on Decal component", + "Couldn't set Material property on Decal component") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") def AtomEditorComponents_Decal_AddedToEntity(): @@ -51,35 +78,33 @@ def AtomEditorComponents_Decal_AddedToEntity(): 9) Delete Decal entity. 10) UNDO deletion. 11) REDO deletion. - 12) Look for errors. + 12) Look for errors and asserts. :return: None """ import os - import azlmbr.asset as asset - import azlmbr.bus as bus import azlmbr.legacy.general as general - import azlmbr.math as math + from editor_python_test_tools.asset_utils import Asset from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + 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. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Create a Decal entity with no components. - decal_name = "Decal" - decal_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), decal_name) + decal_entity = EditorEntity.create_editor_entity(AtomComponentProperties.decal()) Report.critical_result(Tests.decal_creation, decal_entity.exists()) # 2. Add Decal component to Decal entity. - decal_component = decal_entity.add_component(decal_name) - Report.critical_result(Tests.decal_component, decal_entity.has_component(decal_name)) + decal_component = decal_entity.add_component(AtomComponentProperties.decal()) + Report.critical_result(Tests.decal_component, decal_entity.has_component(AtomComponentProperties.decal())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -106,9 +131,9 @@ def AtomEditorComponents_Decal_AddedToEntity(): Report.result(Tests.creation_redo, decal_entity.exists()) # 5. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) + TestHelper.enter_game_mode(Tests.enter_game_mode) general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + TestHelper.exit_game_mode(Tests.exit_game_mode) # 6. Test IsHidden. decal_entity.set_visibility_state(False) @@ -120,13 +145,11 @@ def AtomEditorComponents_Decal_AddedToEntity(): Report.result(Tests.is_visible, decal_entity.is_visible() is True) # 8. Set Material property on Decal component. - decal_material_property_path = "Controller|Configuration|Material" - decal_material_asset_path = os.path.join("AutomatedTesting", "Materials", "basic_grey.material") - decal_material_asset = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", decal_material_asset_path, math.Uuid(), False) - decal_component.set_component_property_value(decal_material_property_path, decal_material_asset) - get_material_property = decal_component.get_component_property_value(decal_material_property_path) - Report.result(Tests.material_property_set, get_material_property == decal_material_asset) + decal_material_asset_path = os.path.join("materials", "basic_grey.azmaterial") + decal_material_asset = Asset.find_asset_by_path(decal_material_asset_path, False) + decal_component.set_component_property_value(AtomComponentProperties.decal('Material'), decal_material_asset.id) + get_material_property = decal_component.get_component_property_value(AtomComponentProperties.decal('Material')) + Report.result(Tests.material_property_set, get_material_property == decal_material_asset.id) # 9. Delete Decal entity. decal_entity.delete() @@ -141,9 +164,12 @@ def AtomEditorComponents_Decal_AddedToEntity(): general.redo() Report.result(Tests.deletion_redo, not decal_entity.exists()) - # 12. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + # 12. 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__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DeferredFogAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DeferredFogAdded.py new file mode 100644 index 0000000000..71163ece94 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DeferredFogAdded.py @@ -0,0 +1,193 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + + +class Tests: + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + deferred_fog_creation = ( + "Deferred Fog Entity successfully created", + "Deferred Fog Entity failed to be created") + deferred_fog_component = ( + "Entity has a Deferred Fog component", + "Entity failed to find Deferred Fog component") + deferred_fog_disabled = ( + "Deferred Fog component disabled", + "Deferred Fog component was not disabled") + postfx_layer_component = ( + "Entity has a PostFX Layer component", + "Entity did not have an PostFX Layer component") + deferred_fog_enabled = ( + "Deferred Fog component enabled", + "Deferred Fog component was not enabled") + enable_deferred_fog_parameter_enabled = ( + "Enable Deferred Fog parameter enabled", + "Enable Deferred Fog parameter was not enabled") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") + + +def AtomEditorComponents_DeferredFog_AddedToEntity(): + """ + Summary: + Tests the Deferred Fog component can be added to an entity and has the expected functionality. + + Test setup: + - Wait for Editor idle loop. + - Open the "Base" level. + + Expected Behavior: + The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components. + Creation and deletion undo/redo should also work. + + Test Steps: + 1) Create an Deferred Fog entity with no components. + 2) Add Deferred Fog component to Deferred Fog entity. + 3) UNDO the entity creation and component addition. + 4) REDO the entity creation and component addition. + 5) Verify Deferred Fog component not enabled. + 6) Add PostFX Layer component since it is required by the Deferred Fog component. + 7) Verify Deferred Fog component is enabled. + 8) Enable the "Enable Deferred Fog" parameter. + 9) Enter/Exit game mode. + 10) Test IsHidden. + 11) Test IsVisible. + 12) Delete Deferred Fog entity. + 13) UNDO deletion. + 14) REDO deletion. + 15) Look for errors. + + :return: None + """ + + import azlmbr.legacy.general as general + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties + + with Tracer() as error_tracer: + # Test setup begins. + # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. + TestHelper.init_idle() + TestHelper.open_level("", "Base") + + # Test steps begin. + # 1. Create an Deferred Fog entity with no components. + deferred_fog_entity = EditorEntity.create_editor_entity(AtomComponentProperties.deferred_fog()) + Report.critical_result(Tests.deferred_fog_creation, deferred_fog_entity.exists()) + + # 2. Add Deferred Fog component to Deferred Fog entity. + deferred_fog_component = deferred_fog_entity.add_component( + AtomComponentProperties.deferred_fog()) + Report.critical_result( + Tests.deferred_fog_component, + deferred_fog_entity.has_component(AtomComponentProperties.deferred_fog())) + + # 3. UNDO the entity creation and component addition. + # -> UNDO component addition. + general.undo() + # -> UNDO naming entity. + general.undo() + # -> UNDO selecting entity. + general.undo() + # -> UNDO entity creation. + general.undo() + general.idle_wait_frames(1) + Report.result(Tests.creation_undo, not deferred_fog_entity.exists()) + + # 4. REDO the entity creation and component addition. + # -> REDO entity creation. + general.redo() + # -> REDO selecting entity. + general.redo() + # -> REDO naming entity. + general.redo() + # -> REDO component addition. + general.redo() + general.idle_wait_frames(1) + Report.result(Tests.creation_redo, deferred_fog_entity.exists()) + + # 5. Verify Deferred Fog component not enabled. + Report.result(Tests.deferred_fog_disabled, not deferred_fog_component.is_enabled()) + + # 6. Add PostFX Layer component since it is required by the Deferred Fog component. + deferred_fog_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.result( + Tests.postfx_layer_component, + deferred_fog_entity.has_component(AtomComponentProperties.postfx_layer())) + + # 7. Verify Deferred Fog component is enabled. + Report.result(Tests.deferred_fog_enabled, deferred_fog_component.is_enabled()) + + # 8. Enable the "Enable Deferred Fog" parameter. + deferred_fog_component.set_component_property_value( + AtomComponentProperties.deferred_fog('Enable Deferred Fog'), True) + Report.result(Tests.enable_deferred_fog_parameter_enabled, + deferred_fog_component.get_component_property_value( + AtomComponentProperties.deferred_fog('Enable Deferred Fog')) is True) + + # 9. Enter/Exit game mode. + TestHelper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + TestHelper.exit_game_mode(Tests.exit_game_mode) + + # 10. Test IsHidden. + deferred_fog_entity.set_visibility_state(False) + Report.result(Tests.is_hidden, deferred_fog_entity.is_hidden() is True) + + # 11. Test IsVisible. + deferred_fog_entity.set_visibility_state(True) + general.idle_wait_frames(1) + Report.result(Tests.is_visible, deferred_fog_entity.is_visible() is True) + + # 12. Delete Deferred Fog entity. + deferred_fog_entity.delete() + Report.result(Tests.entity_deleted, not deferred_fog_entity.exists()) + + # 13. UNDO deletion. + general.undo() + Report.result(Tests.deletion_undo, deferred_fog_entity.exists()) + + # 14. REDO deletion. + general.redo() + Report.result(Tests.deletion_redo, not deferred_fog_entity.exists()) + + # 15. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(AtomEditorComponents_DeferredFog_AddedToEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DepthOfFieldAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DepthOfFieldAdded.py index 80284902ea..e2c5f9c77d 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DepthOfFieldAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DepthOfFieldAdded.py @@ -5,28 +5,61 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off class Tests: - camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created") - camera_component_added = ("Camera component was added to Camera entity", "Camera component failed to be added to entity") - camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component") - camera_property_set = ("DepthOfField Entity set Camera Entity", "DepthOfField Entity could not set Camera Entity") - creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed") - creation_redo = ("REDO Entity creation success", "REDO Entity creation failed") - depth_of_field_creation = ("DepthOfField Entity successfully created", "DepthOfField Entity failed to be created") - depth_of_field_component = ("Entity has a DepthOfField component", "Entity failed to find DepthOfField component") - depth_of_field_disabled = ("DepthOfField component disabled", "DepthOfField component was not disabled.") - post_fx_component = ("Entity has a Post FX Layer component", "Entity did not have a Post FX Layer component") - depth_of_field_enabled = ("DepthOfField component enabled", "DepthOfField component was not enabled.") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - is_visible = ("Entity is visible", "Entity was not visible") - is_hidden = ("Entity is hidden", "Entity was not hidden") - entity_deleted = ("Entity deleted", "Entity was not deleted") - deletion_undo = ("UNDO deletion success", "UNDO deletion failed") - deletion_redo = ("REDO deletion success", "REDO deletion failed") - no_error_occurred = ("No errors detected", "Errors were detected") -# fmt: on + camera_creation = ( + "Camera Entity successfully created", + "Camera Entity failed to be created") + camera_component_added = ( + "Camera component was added to Camera entity", + "Camera component failed to be added to entity") + camera_component_check = ( + "Entity has a Camera component", + "Entity failed to find Camera component") + camera_property_set = ( + "DepthOfField Entity set Camera Entity", + "DepthOfField Entity could not set Camera Entity") + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + depth_of_field_creation = ( + "DepthOfField Entity successfully created", + "DepthOfField Entity failed to be created") + depth_of_field_component = ( + "Entity has a DepthOfField component", + "Entity failed to find DepthOfField component") + depth_of_field_disabled = ( + "DepthOfField component disabled", + "DepthOfField component was not disabled.") + post_fx_component = ( + "Entity has a Post FX Layer component", + "Entity did not have a Post FX Layer component") + depth_of_field_enabled = ( + "DepthOfField component enabled", + "DepthOfField component was not enabled.") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") def AtomEditorComponents_DepthOfField_AddedToEntity(): @@ -59,33 +92,32 @@ def AtomEditorComponents_DepthOfField_AddedToEntity(): 14) Delete DepthOfField entity. 15) UNDO deletion. 16) REDO deletion. - 17) Look for errors. + 17) Look for errors and asserts. :return: None """ import azlmbr.legacy.general as general - import azlmbr.math as math from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + 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. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Create a DepthOfField entity with no components. - depth_of_field_name = "DepthOfField" - depth_of_field_entity = EditorEntity.create_editor_entity_at( - math.Vector3(512.0, 512.0, 34.0), depth_of_field_name) + depth_of_field_entity = EditorEntity.create_editor_entity(AtomComponentProperties.depth_of_field()) Report.critical_result(Tests.depth_of_field_creation, depth_of_field_entity.exists()) # 2. Add a DepthOfField component to DepthOfField entity. - depth_of_field_component = depth_of_field_entity.add_component(depth_of_field_name) - Report.critical_result(Tests.depth_of_field_component, depth_of_field_entity.has_component(depth_of_field_name)) + depth_of_field_component = depth_of_field_entity.add_component(AtomComponentProperties.depth_of_field()) + Report.critical_result(Tests.depth_of_field_component, + depth_of_field_entity.has_component(AtomComponentProperties.depth_of_field())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -115,17 +147,16 @@ def AtomEditorComponents_DepthOfField_AddedToEntity(): Report.result(Tests.depth_of_field_disabled, not depth_of_field_component.is_enabled()) # 6. Add Post FX Layer component since it is required by the DepthOfField component. - post_fx_layer = "PostFX Layer" - depth_of_field_entity.add_component(post_fx_layer) - Report.result(Tests.post_fx_component, depth_of_field_entity.has_component(post_fx_layer)) + depth_of_field_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.result(Tests.post_fx_component, depth_of_field_entity.has_component(AtomComponentProperties.postfx_layer())) # 7. Verify DepthOfField component is enabled. Report.result(Tests.depth_of_field_enabled, depth_of_field_component.is_enabled()) # 8. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) + TestHelper.enter_game_mode(Tests.enter_game_mode) general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + TestHelper.exit_game_mode(Tests.exit_game_mode) # 9. Test IsHidden. depth_of_field_entity.set_visibility_state(False) @@ -137,19 +168,20 @@ def AtomEditorComponents_DepthOfField_AddedToEntity(): Report.result(Tests.is_visible, depth_of_field_entity.is_visible() is True) # 11. Add Camera entity. - camera_name = "Camera" - camera_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), camera_name) + camera_entity = EditorEntity.create_editor_entity(AtomComponentProperties.camera()) Report.result(Tests.camera_creation, camera_entity.exists()) # 12. Add Camera component to Camera entity. - camera_entity.add_component(camera_name) - Report.result(Tests.camera_component_added, camera_entity.has_component(camera_name)) + camera_entity.add_component(AtomComponentProperties.camera()) + Report.result(Tests.camera_component_added, camera_entity.has_component(AtomComponentProperties.camera())) # 13. Set the DepthOfField components's Camera Entity to the newly created Camera entity. - depth_of_field_camera_property_path = "Controller|Configuration|Camera Entity" - depth_of_field_component.set_component_property_value(depth_of_field_camera_property_path, camera_entity.id) - camera_entity_set = depth_of_field_component.get_component_property_value(depth_of_field_camera_property_path) - Report.result(Tests.camera_property_set, camera_entity.id == camera_entity_set) + depth_of_field_component.set_component_property_value( + AtomComponentProperties.depth_of_field('Camera Entity'), camera_entity.id) + Report.result( + Tests.camera_property_set, + camera_entity.id == depth_of_field_component.get_component_property_value( + AtomComponentProperties.depth_of_field('Camera Entity'))) # 14. Delete DepthOfField entity. depth_of_field_entity.delete() @@ -163,9 +195,12 @@ def AtomEditorComponents_DepthOfField_AddedToEntity(): general.redo() Report.result(Tests.deletion_redo, not depth_of_field_entity.exists()) - # 17. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + # 17. 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__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DiffuseProbeGridAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DiffuseProbeGridAdded.py new file mode 100644 index 0000000000..f42c091057 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DiffuseProbeGridAdded.py @@ -0,0 +1,187 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + + +class Tests: + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + diffuse_probe_grid_creation = ( + "Diffuse Probe Grid Entity successfully created", + "Diffuse Probe Grid Entity failed to be created") + diffuse_probe_grid_component = ( + "Entity has a Diffuse Probe Grid component", + "Entity failed to find Diffuse Probe Grid component") + diffuse_probe_grid_disabled = ( + "Diffuse Probe Grid component disabled", + "Diffuse Probe Grid component was not disabled") + diffuse_probe_grid_enabled = ( + "Diffuse Probe Grid component enabled", + "Diffuse Probe Grid component was not enabled") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") + + +def AtomEditorComponents_DiffuseProbeGrid_AddedToEntity(): + """ + Summary: + Tests the Diffuse Probe Grid component can be added to an entity and has the expected functionality. + + Test setup: + - Wait for Editor idle loop. + - Open the "Base" level. + + Expected Behavior: + The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components. + Creation and deletion undo/redo should also work. + + Test Steps: + 1) Create a Diffuse Probe Grid entity with no components. + 2) Add a Diffuse Probe Grid component to Diffuse Probe Grid entity. + 3) UNDO the entity creation and component addition. + 4) REDO the entity creation and component addition. + 5) Verify Diffuse Probe Grid component not enabled. + 6) Add Shape component since it is required by the Diffuse Probe Grid component. + 7) Verify Diffuse Probe Grid component is enabled. + 8) Enter/Exit game mode. + 9) Test IsHidden. + 10) Test IsVisible. + 11) Delete Diffuse Probe Grid entity. + 12) UNDO deletion. + 13) REDO deletion. + 14) Look for errors. + + :return: None + """ + + import azlmbr.legacy.general as general + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties + + with Tracer() as error_tracer: + # Test setup begins. + # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. + TestHelper.init_idle() + TestHelper.open_level("", "Base") + + # Test steps begin. + # 1. Create a Diffuse Probe Grid entity with no components. + diffuse_probe_grid_entity = EditorEntity.create_editor_entity(AtomComponentProperties.diffuse_probe_grid()) + Report.critical_result(Tests.diffuse_probe_grid_creation, diffuse_probe_grid_entity.exists()) + + # 2. Add a Diffuse Probe Grid component to Diffuse Probe Grid entity. + diffuse_probe_grid_component = diffuse_probe_grid_entity.add_component( + AtomComponentProperties.diffuse_probe_grid()) + Report.critical_result( + Tests.diffuse_probe_grid_component, + diffuse_probe_grid_entity.has_component(AtomComponentProperties.diffuse_probe_grid())) + + # 3. UNDO the entity creation and component addition. + # -> UNDO component addition. + general.undo() + # -> UNDO naming entity. + general.undo() + # -> UNDO selecting entity. + general.undo() + # -> UNDO entity creation. + general.undo() + general.idle_wait_frames(1) + Report.result(Tests.creation_undo, not diffuse_probe_grid_entity.exists()) + + # 4. REDO the entity creation and component addition. + # -> REDO entity creation. + general.redo() + # -> REDO selecting entity. + general.redo() + # -> REDO naming entity. + general.redo() + # -> REDO component addition. + general.redo() + general.idle_wait_frames(1) + Report.result(Tests.creation_redo, diffuse_probe_grid_entity.exists()) + + # 5. Verify Diffuse Probe Grid component not enabled. + Report.result(Tests.diffuse_probe_grid_disabled, not diffuse_probe_grid_component.is_enabled()) + + # 6. Add Shape component since it is required by the Diffuse Probe Grid component. + for shape in AtomComponentProperties.diffuse_probe_grid('shapes'): + diffuse_probe_grid_entity.add_component(shape) + test_shape = ( + f"Entity has a {shape} component", + f"Entity did not have a {shape} component") + Report.result(test_shape, diffuse_probe_grid_entity.has_component(shape)) + + # 7. Check if required shape allows Diffuse Probe Grid to be enabled + Report.result(Tests.diffuse_probe_grid_enabled, diffuse_probe_grid_component.is_enabled()) + + # Undo to remove each added shape except the last one and verify Diffuse Probe Grid is not enabled. + if not (shape == AtomComponentProperties.diffuse_probe_grid('shapes')[-1]): + general.undo() + TestHelper.wait_for_condition(lambda: not diffuse_probe_grid_entity.has_component(shape), 1.0) + Report.result(Tests.diffuse_probe_grid_disabled, not diffuse_probe_grid_component.is_enabled()) + + # 8. 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) + + # 9. Test IsHidden. + diffuse_probe_grid_entity.set_visibility_state(False) + Report.result(Tests.is_hidden, diffuse_probe_grid_entity.is_hidden() is True) + + # 10. Test IsVisible. + diffuse_probe_grid_entity.set_visibility_state(True) + general.idle_wait_frames(1) + Report.result(Tests.is_visible, diffuse_probe_grid_entity.is_visible() is True) + + # 11. Delete Diffuse Probe Grid entity. + diffuse_probe_grid_entity.delete() + Report.result(Tests.entity_deleted, not diffuse_probe_grid_entity.exists()) + + # 12. UNDO deletion. + general.undo() + Report.result(Tests.deletion_undo, diffuse_probe_grid_entity.exists()) + + # 13. REDO deletion. + general.redo() + Report.result(Tests.deletion_redo, not diffuse_probe_grid_entity.exists()) + + # 14. Look for errors or asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(AtomEditorComponents_DiffuseProbeGrid_AddedToEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DirectionalLightAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DirectionalLightAdded.py index 048e132df4..f3ffc0f366 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DirectionalLightAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DirectionalLightAdded.py @@ -5,25 +5,52 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off class Tests: - camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created") - camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity") - camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component") - creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed") - creation_redo = ("REDO Entity creation success", "REDO Entity creation failed") - directional_light_creation = ("Directional Light Entity successfully created", "Directional Light Entity failed to be created") - directional_light_component = ("Entity has a Directional Light component", "Entity failed to find Directional Light component") - shadow_camera_check = ("Directional Light component Shadow camera set", "Directional Light component Shadow camera was not set") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - is_visible = ("Entity is visible", "Entity was not visible") - is_hidden = ("Entity is hidden", "Entity was not hidden") - entity_deleted = ("Entity deleted", "Entity was not deleted") - deletion_undo = ("UNDO deletion success", "UNDO deletion failed") - deletion_redo = ("REDO deletion success", "REDO deletion failed") - no_error_occurred = ("No errors detected", "Errors were detected") -# fmt: on + camera_creation = ( + "Camera Entity successfully created", + "Camera Entity failed to be created") + camera_component_added = ( + "Camera component was added to entity", + "Camera component failed to be added to entity") + camera_component_check = ( + "Entity has a Camera component", + "Entity failed to find Camera component") + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + directional_light_creation = ( + "Directional Light Entity successfully created", + "Directional Light Entity failed to be created") + directional_light_component = ( + "Entity has a Directional Light component", + "Entity failed to find Directional Light component") + shadow_camera_check = ( + "Directional Light component Shadow camera set", + "Directional Light component Shadow camera was not set") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") def AtomEditorComponents_DirectionalLight_AddedToEntity(): @@ -53,34 +80,33 @@ def AtomEditorComponents_DirectionalLight_AddedToEntity(): 11) Delete Directional Light entity. 12) UNDO deletion. 13) REDO deletion. - 14) Look for errors. + 14) Look for errors and asserts. :return: None """ import azlmbr.legacy.general as general - import azlmbr.math as math from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + 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. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Create a Directional Light entity with no components. - directional_light_name = "Directional Light" - directional_light_entity = EditorEntity.create_editor_entity_at( - math.Vector3(512.0, 512.0, 34.0), directional_light_name) + directional_light_entity = EditorEntity.create_editor_entity(AtomComponentProperties.directional_light()) Report.critical_result(Tests.directional_light_creation, directional_light_entity.exists()) # 2. Add Directional Light component to Directional Light entity. - directional_light_component = directional_light_entity.add_component(directional_light_name) + directional_light_component = directional_light_entity.add_component(AtomComponentProperties.directional_light()) Report.critical_result( - Tests.directional_light_component, directional_light_entity.has_component(directional_light_name)) + Tests.directional_light_component, + directional_light_entity.has_component(AtomComponentProperties.directional_light())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -107,9 +133,9 @@ def AtomEditorComponents_DirectionalLight_AddedToEntity(): Report.result(Tests.creation_redo, directional_light_entity.exists()) # 5. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) + TestHelper.enter_game_mode(Tests.enter_game_mode) general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + TestHelper.exit_game_mode(Tests.exit_game_mode) # 6. Test IsHidden. directional_light_entity.set_visibility_state(False) @@ -121,19 +147,20 @@ def AtomEditorComponents_DirectionalLight_AddedToEntity(): Report.result(Tests.is_visible, directional_light_entity.is_visible() is True) # 8. Add Camera entity. - camera_name = "Camera" - camera_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), camera_name) + camera_entity = EditorEntity.create_editor_entity(AtomComponentProperties.camera()) Report.result(Tests.camera_creation, camera_entity.exists()) # 9. Add Camera component to Camera entity. - camera_entity.add_component(camera_name) - Report.result(Tests.camera_component_added, camera_entity.has_component(camera_name)) + camera_entity.add_component(AtomComponentProperties.camera()) + Report.result(Tests.camera_component_added, camera_entity.has_component(AtomComponentProperties.camera())) # 10. Set the Directional Light component property Shadow|Camera to the Camera entity. - shadow_camera_property_path = "Controller|Configuration|Shadow|Camera" - directional_light_component.set_component_property_value(shadow_camera_property_path, camera_entity.id) - shadow_camera_set = directional_light_component.get_component_property_value(shadow_camera_property_path) - Report.result(Tests.shadow_camera_check, camera_entity.id == shadow_camera_set) + directional_light_component.set_component_property_value( + AtomComponentProperties.directional_light('Camera'), camera_entity.id) + Report.result( + Tests.shadow_camera_check, + camera_entity.id == directional_light_component.get_component_property_value( + AtomComponentProperties.directional_light('Camera'))) # 11. Delete DirectionalLight entity. directional_light_entity.delete() @@ -147,9 +174,12 @@ def AtomEditorComponents_DirectionalLight_AddedToEntity(): general.redo() Report.result(Tests.deletion_redo, not directional_light_entity.exists()) - # 14. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + # 14. 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__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DisplayMapperAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DisplayMapperAdded.py index 39d7acf4f4..558d69046e 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DisplayMapperAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_DisplayMapperAdded.py @@ -5,24 +5,47 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off + class Tests: - camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created") - camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity") - camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component") - creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed") - creation_redo = ("REDO Entity creation success", "REDO Entity creation failed") - display_mapper_creation = ("Display Mapper Entity successfully created", "Display Mapper Entity failed to be created") - display_mapper_component = ("Entity has a Display Mapper component", "Entity failed to find Display Mapper component") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - is_visible = ("Entity is visible", "Entity was not visible") - is_hidden = ("Entity is hidden", "Entity was not hidden") - entity_deleted = ("Entity deleted", "Entity was not deleted") - deletion_undo = ("UNDO deletion success", "UNDO deletion failed") - deletion_redo = ("REDO deletion success", "REDO deletion failed") - no_error_occurred = ("No errors detected", "Errors were detected") -# fmt: on + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + display_mapper_creation = ( + "Display Mapper Entity successfully created", + "Display Mapper Entity failed to be created") + display_mapper_component = ( + "Entity has a Display Mapper component", + "Entity failed to find Display Mapper component") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + ldr_color_grading_lut = ( + "LDR color Grading LUT asset set", + "LDR color Grading LUT asset could not be set") + enable_ldr_color_grading_lut = ( + "Enable LDR color grading LUT set", + "Enable LDR color grading LUT could not be set") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") def AtomEditorComponents_DisplayMapper_AddedToEntity(): @@ -43,39 +66,43 @@ def AtomEditorComponents_DisplayMapper_AddedToEntity(): 2) Add Display Mapper component to Display Mapper entity. 3) UNDO the entity creation and component addition. 4) REDO the entity creation and component addition. - 5) Enter/Exit game mode. - 6) Test IsHidden. - 7) Test IsVisible. - 8) Delete Display Mapper entity. - 9) UNDO deletion. - 10) REDO deletion. - 11) Look for errors. + 5) Set LDR color Grading LUT asset. + 6) Set Enable LDR color grading LUT property True + 7) Enter/Exit game mode. + 8) Test IsHidden. + 9) Test IsVisible. + 10) Delete Display Mapper entity. + 11) UNDO deletion. + 12) REDO deletion. + 13) Look for errors and asserts. :return: None """ + import os import azlmbr.legacy.general as general - import azlmbr.math as math + from editor_python_test_tools.asset_utils import Asset from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + 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. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Create a Display Mapper entity with no components. - display_mapper = "Display Mapper" - display_mapper_entity = EditorEntity.create_editor_entity_at( - math.Vector3(512.0, 512.0, 34.0), f"{display_mapper}") + display_mapper_entity = EditorEntity.create_editor_entity(AtomComponentProperties.display_mapper()) Report.critical_result(Tests.display_mapper_creation, display_mapper_entity.exists()) # 2. Add Display Mapper component to Display Mapper entity. - display_mapper_entity.add_component(display_mapper) - Report.critical_result(Tests.display_mapper_component, display_mapper_entity.has_component(display_mapper)) + display_mapper_component = display_mapper_entity.add_component(AtomComponentProperties.display_mapper()) + Report.critical_result( + Tests.display_mapper_component, + display_mapper_entity.has_component(AtomComponentProperties.display_mapper())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -101,35 +128,56 @@ def AtomEditorComponents_DisplayMapper_AddedToEntity(): general.idle_wait_frames(1) Report.result(Tests.creation_redo, display_mapper_entity.exists()) - # 5. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) - general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + # 5. Set LDR color Grading LUT asset. + display_mapper_asset_path = os.path.join("TestData", "test.lightingpreset.azasset") + display_mapper_asset = Asset.find_asset_by_path(display_mapper_asset_path, False) + display_mapper_component.set_component_property_value( + AtomComponentProperties.display_mapper("LDR color Grading LUT"), display_mapper_asset.id) + Report.result( + Tests.ldr_color_grading_lut, + display_mapper_component.get_component_property_value( + AtomComponentProperties.display_mapper("LDR color Grading LUT")) == display_mapper_asset.id) - # 6. Test IsHidden. + # 6. Set Enable LDR color grading LUT property True + display_mapper_component.set_component_property_value( + AtomComponentProperties.display_mapper('Enable LDR color grading LUT'), True) + Report.result( + Tests.enable_ldr_color_grading_lut, + display_mapper_component.get_component_property_value( + AtomComponentProperties.display_mapper('Enable LDR color grading LUT')) is True) + + # 7. Enter/Exit game mode. + TestHelper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + TestHelper.exit_game_mode(Tests.exit_game_mode) + + # 8. Test IsHidden. display_mapper_entity.set_visibility_state(False) Report.result(Tests.is_hidden, display_mapper_entity.is_hidden() is True) - # 7. Test IsVisible. + # 9. Test IsVisible. display_mapper_entity.set_visibility_state(True) general.idle_wait_frames(1) Report.result(Tests.is_visible, display_mapper_entity.is_visible() is True) - # 8. Delete Display Mapper entity. + # 10. Delete Display Mapper entity. display_mapper_entity.delete() Report.result(Tests.entity_deleted, not display_mapper_entity.exists()) - # 9. UNDO deletion. + # 11. UNDO deletion. general.undo() Report.result(Tests.deletion_undo, display_mapper_entity.exists()) - # 10. REDO deletion. + # 12. REDO deletion. general.redo() Report.result(Tests.deletion_redo, not display_mapper_entity.exists()) - # 11. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + # 13. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_EntityReferenceAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_EntityReferenceAdded.py new file mode 100644 index 0000000000..dddcca64fa --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_EntityReferenceAdded.py @@ -0,0 +1,158 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + + +class Tests: + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + entity_reference_creation = ( + "Entity Reference Entity successfully created", + "Entity Reference Entity failed to be created") + entity_reference_component = ( + "Entity has an Entity Reference component", + "Entity failed to find Entity Reference component") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") + + +def AtomEditorComponents_EntityReference_AddedToEntity(): + """ + Summary: + Tests the Entity Reference component can be added to an entity and has the expected functionality. + + Test setup: + - Wait for Editor idle loop. + - Open the "Base" level. + + Expected Behavior: + The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components. + Creation and deletion undo/redo should also work. + + Test Steps: + 1) Create an Entity Reference entity with no components. + 2) Add Entity Reference component to Entity Reference entity. + 3) UNDO the entity creation and component addition. + 4) REDO the entity creation and component addition. + 5) Enter/Exit game mode. + 6) Test IsHidden. + 7) Test IsVisible. + 8) Delete Entity Reference entity. + 9) UNDO deletion. + 10) REDO deletion. + 11) Look for errors. + + :return: None + """ + + import azlmbr.legacy.general as general + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties + + with Tracer() as error_tracer: + # Test setup begins. + # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. + TestHelper.init_idle() + TestHelper.open_level("", "Base") + + # Test steps begin. + # 1. Create an Entity Reference entity with no components. + entity_reference_entity = EditorEntity.create_editor_entity(AtomComponentProperties.entity_reference()) + Report.critical_result(Tests.entity_reference_creation, entity_reference_entity.exists()) + + # 2. Add Entity Reference component to Entity Reference entity. + entity_reference_component = entity_reference_entity.add_component( + AtomComponentProperties.entity_reference()) + Report.critical_result( + Tests.entity_reference_component, + entity_reference_entity.has_component(AtomComponentProperties.entity_reference())) + + # 3. UNDO the entity creation and component addition. + # -> UNDO component addition. + general.undo() + # -> UNDO naming entity. + general.undo() + # -> UNDO selecting entity. + general.undo() + # -> UNDO entity creation. + general.undo() + general.idle_wait_frames(1) + Report.result(Tests.creation_undo, not entity_reference_entity.exists()) + + # 4. REDO the entity creation and component addition. + # -> REDO entity creation. + general.redo() + # -> REDO selecting entity. + general.redo() + # -> REDO naming entity. + general.redo() + # -> REDO component addition. + general.redo() + general.idle_wait_frames(1) + Report.result(Tests.creation_redo, entity_reference_entity.exists()) + + # 5. Enter/Exit game mode. + TestHelper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + TestHelper.exit_game_mode(Tests.exit_game_mode) + + # 6. Test IsHidden. + entity_reference_entity.set_visibility_state(False) + Report.result(Tests.is_hidden, entity_reference_entity.is_hidden() is True) + + # 7. Test IsVisible. + entity_reference_entity.set_visibility_state(True) + general.idle_wait_frames(1) + Report.result(Tests.is_visible, entity_reference_entity.is_visible() is True) + + # 8. Delete Entity Reference entity. + entity_reference_entity.delete() + Report.result(Tests.entity_deleted, not entity_reference_entity.exists()) + + # 9. UNDO deletion. + general.undo() + Report.result(Tests.deletion_undo, entity_reference_entity.exists()) + + # 10. REDO deletion. + general.redo() + Report.result(Tests.deletion_redo, not entity_reference_entity.exists()) + + # 11. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(AtomEditorComponents_EntityReference_AddedToEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ExposureControlAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ExposureControlAdded.py index 23a84435f7..6fa9660539 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ExposureControlAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ExposureControlAdded.py @@ -5,25 +5,58 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off class Tests: - camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created") - camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity") - camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component") - creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed") - creation_redo = ("REDO Entity creation success", "REDO Entity creation failed") - exposure_control_creation = ("ExposureControl Entity successfully created", "ExposureControl Entity failed to be created") - exposure_control_component = ("Entity has a Exposure Control component", "Entity failed to find Exposure Control component") - post_fx_component = ("Entity has a Post FX Layer component", "Entity did not have a Post FX Layer component") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - is_visible = ("Entity is visible", "Entity was not visible") - is_hidden = ("Entity is hidden", "Entity was not hidden") - entity_deleted = ("Entity deleted", "Entity was not deleted") - deletion_undo = ("UNDO deletion success", "UNDO deletion failed") - deletion_redo = ("REDO deletion success", "REDO deletion failed") - no_error_occurred = ("No errors detected", "Errors were detected") -# fmt: on + camera_creation = ( + "Camera Entity successfully created", + "Camera Entity failed to be created") + camera_component_added = ( + "Camera component was added to entity", + "Camera component failed to be added to entity") + camera_component_check = ( + "Entity has a Camera component", + "Entity failed to find Camera component") + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + exposure_control_creation = ( + "ExposureControl Entity successfully created", + "ExposureControl Entity failed to be created") + exposure_control_component = ( + "Entity has a Exposure Control component", + "Entity failed to find Exposure Control component") + exposure_control_disabled = ( + "DepthOfField component disabled", + "DepthOfField component was not disabled.") + post_fx_component = ( + "Entity has a Post FX Layer component", + "Entity did not have a Post FX Layer component") + exposure_control_enabled = ( + "DepthOfField component enabled", + "DepthOfField component was not enabled.") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") def AtomEditorComponents_ExposureControl_AddedToEntity(): @@ -44,41 +77,42 @@ def AtomEditorComponents_ExposureControl_AddedToEntity(): 2) Add Exposure Control component to Exposure Control entity. 3) UNDO the entity creation and component addition. 4) REDO the entity creation and component addition. - 5) Enter/Exit game mode. - 6) Test IsHidden. - 7) Test IsVisible. - 8) Add Post FX Layer component. - 9) Delete Exposure Control entity. - 10) UNDO deletion. - 11) REDO deletion. - 12) Look for errors. + 5) Verify Exposure Control component not enabled. + 6) Add Post FX Layer component since it is required by the Exposure Control component. + 7) Verify Exposure Control component is enabled. + 8) Enter/Exit game mode. + 9) Test IsHidden. + 10) Test IsVisible. + 11) Delete Exposure Control entity. + 12) UNDO deletion. + 13) REDO deletion. + 14) Look for errors and asserts. :return: None """ import azlmbr.legacy.general as general - import azlmbr.math as math from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + 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. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Creation of Exposure Control entity with no components. - exposure_control_name = "Exposure Control" - exposure_control_entity = EditorEntity.create_editor_entity_at( - math.Vector3(512.0, 512.0, 34.0), f"{exposure_control_name}") + exposure_control_entity = EditorEntity.create_editor_entity(AtomComponentProperties.exposure_control()) Report.critical_result(Tests.exposure_control_creation, exposure_control_entity.exists()) # 2. Add Exposure Control component to Exposure Control entity. - exposure_control_entity.add_component(exposure_control_name) + exposure_control_component = exposure_control_entity.add_component(AtomComponentProperties.exposure_control()) Report.critical_result( - Tests.exposure_control_component, exposure_control_entity.has_component(exposure_control_name)) + Tests.exposure_control_component, + exposure_control_entity.has_component(AtomComponentProperties.exposure_control())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -104,40 +138,49 @@ def AtomEditorComponents_ExposureControl_AddedToEntity(): general.idle_wait_frames(1) Report.result(Tests.creation_redo, exposure_control_entity.exists()) - # 5. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) - general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + # 5. Verify Exposure Control component not enabled. + Report.result(Tests.exposure_control_disabled, not exposure_control_component.is_enabled()) - # 6. Test IsHidden. + # 6. Add Post FX Layer component since it is required by the Exposure Control component. + exposure_control_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.result(Tests.post_fx_component, + exposure_control_entity.has_component(AtomComponentProperties.postfx_layer())) + + # 7. Verify Exposure Control component is enabled. + Report.result(Tests.exposure_control_enabled, exposure_control_component.is_enabled()) + + # 8. 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) + + # 9. Test IsHidden. exposure_control_entity.set_visibility_state(False) Report.result(Tests.is_hidden, exposure_control_entity.is_hidden() is True) - # 7. Test IsVisible. + # 10. Test IsVisible. exposure_control_entity.set_visibility_state(True) general.idle_wait_frames(1) Report.result(Tests.is_visible, exposure_control_entity.is_visible() is True) - # 8. Add Post FX Layer component. - post_fx_layer_name = "PostFX Layer" - exposure_control_entity.add_component(post_fx_layer_name) - Report.result(Tests.post_fx_component, exposure_control_entity.has_component(post_fx_layer_name)) - - # 9. Delete ExposureControl entity. + # 11. Delete ExposureControl entity. exposure_control_entity.delete() Report.result(Tests.entity_deleted, not exposure_control_entity.exists()) - # 10. UNDO deletion. + # 12. UNDO deletion. general.undo() Report.result(Tests.deletion_undo, exposure_control_entity.exists()) - # 11. REDO deletion. + # 13. REDO deletion. general.redo() Report.result(Tests.deletion_redo, not exposure_control_entity.exists()) - # 12. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + # 14. 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__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GlobalSkylightIBLAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GlobalSkylightIBLAdded.py index 64bbfb1c4d..7f0c38e289 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GlobalSkylightIBLAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GlobalSkylightIBLAdded.py @@ -5,26 +5,55 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off class Tests: - camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created") - camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity") - camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component") - creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed") - creation_redo = ("REDO Entity creation success", "REDO Entity creation failed") - global_skylight_creation = ("Global Skylight (IBL) Entity successfully created", "Global Skylight (IBL) Entity failed to be created") - global_skylight_component = ("Entity has a Global Skylight (IBL) component", "Entity failed to find Global Skylight (IBL) component") - diffuse_image_set = ("Entity has the Diffuse Image set", "Entity did not the Diffuse Image set") - specular_image_set = ("Entity has the Specular Image set", "Entity did not the Specular Image set") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - is_visible = ("Entity is visible", "Entity was not visible") - is_hidden = ("Entity is hidden", "Entity was not hidden") - entity_deleted = ("Entity deleted", "Entity was not deleted") - deletion_undo = ("UNDO deletion success", "UNDO deletion failed") - deletion_redo = ("REDO deletion success", "REDO deletion failed") - no_error_occurred = ("No errors detected", "Errors were detected") -# fmt: on + camera_creation = ( + "Camera Entity successfully created", + "Camera Entity failed to be created") + camera_component_added = ( + "Camera component was added to entity", + "Camera component failed to be added to entity") + camera_component_check = ( + "Entity has a Camera component", + "Entity failed to find Camera component") + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + global_skylight_creation = ( + "Global Skylight (IBL) Entity successfully created", + "Global Skylight (IBL) Entity failed to be created") + global_skylight_component = ( + "Entity has a Global Skylight (IBL) component", + "Entity failed to find Global Skylight (IBL) component") + diffuse_image_set = ( + "Entity has the Diffuse Image set", + "Entity did not the Diffuse Image set") + specular_image_set = ( + "Entity has the Specular Image set", + "Entity did not the Specular Image set") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") def AtomEditorComponents_GlobalSkylightIBL_AddedToEntity(): @@ -60,29 +89,28 @@ def AtomEditorComponents_GlobalSkylightIBL_AddedToEntity(): import os import azlmbr.legacy.general as general - import azlmbr.math as math from editor_python_test_tools.asset_utils import Asset from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + 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. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Create a Global Skylight (IBL) entity with no components. - global_skylight_name = "Global Skylight (IBL)" - global_skylight_entity = EditorEntity.create_editor_entity_at( - math.Vector3(512.0, 512.0, 34.0), global_skylight_name) + global_skylight_entity = EditorEntity.create_editor_entity(AtomComponentProperties.global_skylight()) Report.critical_result(Tests.global_skylight_creation, global_skylight_entity.exists()) # 2. Add Global Skylight (IBL) component to Global Skylight (IBL) entity. - global_skylight_component = global_skylight_entity.add_component(global_skylight_name) + global_skylight_component = global_skylight_entity.add_component(AtomComponentProperties.global_skylight()) Report.critical_result( - Tests.global_skylight_component, global_skylight_entity.has_component(global_skylight_name)) + Tests.global_skylight_component, + global_skylight_entity.has_component(AtomComponentProperties.global_skylight())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -109,9 +137,9 @@ def AtomEditorComponents_GlobalSkylightIBL_AddedToEntity(): Report.result(Tests.creation_redo, global_skylight_entity.exists()) # 5. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) + TestHelper.enter_game_mode(Tests.enter_game_mode) general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + TestHelper.exit_game_mode(Tests.exit_game_mode) # 6. Test IsHidden. global_skylight_entity.set_visibility_state(False) @@ -126,19 +154,21 @@ def AtomEditorComponents_GlobalSkylightIBL_AddedToEntity(): diffuse_image_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage") diffuse_image_asset = Asset.find_asset_by_path(diffuse_image_path, False) global_skylight_component.set_component_property_value( - global_skylight_diffuse_image_property, diffuse_image_asset.id) - diffuse_image_set = global_skylight_component.get_component_property_value( - global_skylight_diffuse_image_property) - Report.result(Tests.diffuse_image_set, diffuse_image_set == diffuse_image_asset.id) + AtomComponentProperties.global_skylight('Diffuse Image'), diffuse_image_asset.id) + Report.result( + Tests.diffuse_image_set, + diffuse_image_asset.id == global_skylight_component.get_component_property_value( + AtomComponentProperties.global_skylight('Diffuse Image'))) # 9. Set the Specular Image asset on the Global Light (IBL) entity. specular_image_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage") specular_image_asset = Asset.find_asset_by_path(specular_image_path, False) global_skylight_component.set_component_property_value( - global_skylight_specular_image_property, specular_image_asset.id) - specular_image_added = global_skylight_component.get_component_property_value( - global_skylight_specular_image_property) - Report.result(Tests.specular_image_set, specular_image_added == specular_image_asset.id) + AtomComponentProperties.global_skylight('Specular Image'), specular_image_asset.id) + Report.result( + Tests.specular_image_set, + specular_image_asset.id == global_skylight_component.get_component_property_value( + AtomComponentProperties.global_skylight('Specular Image'))) # 10. Delete Global Skylight (IBL) entity. global_skylight_entity.delete() @@ -152,9 +182,12 @@ def AtomEditorComponents_GlobalSkylightIBL_AddedToEntity(): general.redo() Report.result(Tests.deletion_redo, not global_skylight_entity.exists()) - # 13. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + # 13. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") if __name__ == "__main__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GridAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GridAdded.py new file mode 100644 index 0000000000..a77a1f50a4 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_GridAdded.py @@ -0,0 +1,158 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + +class Tests: + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + grid_entity_creation = ( + "Grid Entity successfully created", + "Grid Entity failed to be created") + grid_component_added = ( + "Entity has a Grid component", + "Entity failed to find Grid component") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") + + +def AtomEditorComponents_Grid_AddedToEntity(): + """ + Summary: + Tests the Grid component can be added to an entity and has the expected functionality. + + Test setup: + - Wait for Editor idle loop. + - Open the "Base" level. + + Expected Behavior: + The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components. + Creation and deletion undo/redo should also work. + + Test Steps: + 1) Create a Grid entity with no components. + 2) Add a Grid component to Grid entity. + 3) UNDO the entity creation and component addition. + 4) REDO the entity creation and component addition. + 5) Enter/Exit game mode. + 6) Test IsHidden. + 7) Test IsVisible. + 8) Delete Grid entity. + 9) UNDO deletion. + 10) REDO deletion. + 11) Look for errors. + + :return: None + """ + + import os + + import azlmbr.legacy.general as general + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties + + with Tracer() as error_tracer: + # Test setup begins. + # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. + TestHelper.init_idle() + TestHelper.open_level("", "Base") + + # Test steps begin. + # 1. Create a Grid entity with no components. + grid_entity = EditorEntity.create_editor_entity(AtomComponentProperties.grid()) + Report.critical_result(Tests.grid_entity_creation, grid_entity.exists()) + + # 2. Add a Grid component to Grid entity. + grid_component = grid_entity.add_component(AtomComponentProperties.grid()) + Report.critical_result( + Tests.grid_component_added, + grid_entity.has_component(AtomComponentProperties.grid())) + + # 3. UNDO the entity creation and component addition. + # -> UNDO component addition. + general.undo() + # -> UNDO naming entity. + general.undo() + # -> UNDO selecting entity. + general.undo() + # -> UNDO entity creation. + general.undo() + general.idle_wait_frames(1) + Report.result(Tests.creation_undo, not grid_entity.exists()) + + # 4. REDO the entity creation and component addition. + # -> REDO entity creation. + general.redo() + # -> REDO selecting entity. + general.redo() + # -> REDO naming entity. + general.redo() + # -> REDO component addition. + general.redo() + general.idle_wait_frames(1) + Report.result(Tests.creation_redo, grid_entity.exists()) + + # 5. Enter/Exit game mode. + TestHelper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + TestHelper.exit_game_mode(Tests.exit_game_mode) + + # 6. Test IsHidden. + grid_entity.set_visibility_state(False) + Report.result(Tests.is_hidden, grid_entity.is_hidden() is True) + + # 7. Test IsVisible. + grid_entity.set_visibility_state(True) + general.idle_wait_frames(1) + Report.result(Tests.is_visible, grid_entity.is_visible() is True) + + # 8. Delete Grid entity. + grid_entity.delete() + Report.result(Tests.entity_deleted, not grid_entity.exists()) + + # 9. UNDO deletion. + general.undo() + Report.result(Tests.deletion_undo, grid_entity.exists()) + + # 10. REDO deletion. + general.redo() + Report.result(Tests.deletion_redo, not grid_entity.exists()) + + # 11. Look for errors or asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(AtomEditorComponents_Grid_AddedToEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_HDRColorGradingAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_HDRColorGradingAdded.py new file mode 100644 index 0000000000..4972079fcd --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_HDRColorGradingAdded.py @@ -0,0 +1,192 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + +class Tests: + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + hdr_color_grading_creation = ( + "HDR Color Grading Entity successfully created", + "HDR Color Grading Entity failed to be created") + hdr_color_grading_component = ( + "Entity has an HDR Color Grading component", + "Entity failed to find HDR Color Grading component") + hdr_color_grading_disabled = ( + "HDR Color Grading component disabled", + "HDR Color Grading component was not disabled") + postfx_layer_component = ( + "Entity has a PostFX Layer component", + "Entity did not have an PostFX Layer component") + hdr_color_grading_enabled = ( + "HDR Color Grading component enabled", + "HDR Color Grading component was not enabled") + enable_hdr_color_grading_parameter_enabled = ( + "Enable HDR Color Grading parameter enabled", + "Enable HDR Color Grading parameter was not enabled") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") + + +def AtomEditorComponents_HDRColorGrading_AddedToEntity(): + """ + Summary: + Tests the HDR Color Grading component can be added to an entity and has the expected functionality. + + Test setup: + - Wait for Editor idle loop. + - Open the "Base" level. + + Expected Behavior: + The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components. + Creation and deletion undo/redo should also work. + + Test Steps: + 1) Create an HDR Color Grading entity with no components. + 2) Add HDR Color Grading component to HDR Color Grading entity. + 3) UNDO the entity creation and component addition. + 4) REDO the entity creation and component addition. + 5) Verify HDR Color Grading component not enabled. + 6) Add PostFX Layer component since it is required by the HDR Color Grading component. + 7) Verify HDR Color Grading component is enabled. + 8) Enable the "Enable HDR Color Grading" parameter. + 9) Enter/Exit game mode. + 10) Test IsHidden. + 11) Test IsVisible. + 12) Delete HDR Color Grading entity. + 13) UNDO deletion. + 14) REDO deletion. + 15) Look for errors. + + :return: None + """ + + import azlmbr.legacy.general as general + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties + + with Tracer() as error_tracer: + # Test setup begins. + # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. + TestHelper.init_idle() + TestHelper.open_level("", "Base") + + # Test steps begin. + # 1. Create an HDR Color Grading entity with no components. + hdr_color_grading_entity = EditorEntity.create_editor_entity(AtomComponentProperties.hdr_color_grading()) + Report.critical_result(Tests.hdr_color_grading_creation, hdr_color_grading_entity.exists()) + + # 2. Add HDR Color Grading component to HDR Color Grading entity. + hdr_color_grading_component = hdr_color_grading_entity.add_component( + AtomComponentProperties.hdr_color_grading()) + Report.critical_result( + Tests.hdr_color_grading_component, + hdr_color_grading_entity.has_component(AtomComponentProperties.hdr_color_grading())) + + # 3. UNDO the entity creation and component addition. + # -> UNDO component addition. + general.undo() + # -> UNDO naming entity. + general.undo() + # -> UNDO selecting entity. + general.undo() + # -> UNDO entity creation. + general.undo() + general.idle_wait_frames(1) + Report.result(Tests.creation_undo, not hdr_color_grading_entity.exists()) + + # 4. REDO the entity creation and component addition. + # -> REDO entity creation. + general.redo() + # -> REDO selecting entity. + general.redo() + # -> REDO naming entity. + general.redo() + # -> REDO component addition. + general.redo() + general.idle_wait_frames(1) + Report.result(Tests.creation_redo, hdr_color_grading_entity.exists()) + + # 5. Verify HDR Color Grading component not enabled. + Report.result(Tests.hdr_color_grading_disabled, not hdr_color_grading_component.is_enabled()) + + # 6. Add PostFX Layer component since it is required by the HDR Color Grading component. + hdr_color_grading_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.result( + Tests.postfx_layer_component, + hdr_color_grading_entity.has_component(AtomComponentProperties.postfx_layer())) + + # 7. Verify HDR Color Grading component is enabled. + Report.result(Tests.hdr_color_grading_enabled, hdr_color_grading_component.is_enabled()) + + # 8. Enable the "Enable HDR Color Grading" parameter. + hdr_color_grading_component.set_component_property_value( + AtomComponentProperties.hdr_color_grading('Enable HDR color grading'), True) + Report.result(Tests.enable_hdr_color_grading_parameter_enabled, + hdr_color_grading_component.get_component_property_value( + AtomComponentProperties.hdr_color_grading('Enable HDR color grading')) is True) + + # 9. Enter/Exit game mode. + TestHelper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + TestHelper.exit_game_mode(Tests.exit_game_mode) + + # 10. Test IsHidden. + hdr_color_grading_entity.set_visibility_state(False) + Report.result(Tests.is_hidden, hdr_color_grading_entity.is_hidden() is True) + + # 11. Test IsVisible. + hdr_color_grading_entity.set_visibility_state(True) + general.idle_wait_frames(1) + Report.result(Tests.is_visible, hdr_color_grading_entity.is_visible() is True) + + # 12. Delete HDR Color Grading entity. + hdr_color_grading_entity.delete() + Report.result(Tests.entity_deleted, not hdr_color_grading_entity.exists()) + + # 13. UNDO deletion. + general.undo() + Report.result(Tests.deletion_undo, hdr_color_grading_entity.exists()) + + # 14. REDO deletion. + general.redo() + Report.result(Tests.deletion_redo, not hdr_color_grading_entity.exists()) + + # 15. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(AtomEditorComponents_HDRColorGrading_AddedToEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_HDRiSkyboxAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_HDRiSkyboxAdded.py new file mode 100644 index 0000000000..0f96bc5424 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_HDRiSkyboxAdded.py @@ -0,0 +1,177 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + + +class Tests: + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + hdri_skybox_entity_creation = ( + "HDRi Skybox successfully created", + "HDRi Skybox failed to be created") + hdri_skybox_component = ( + "Entity has an HDRi Skybox component", + "Entity failed to find HDRi Skybox component") + cubemap_property_set = ( + "Cubemap property set on HDRi Skybox component", + "Couldn't set Cubemap property on HDRi Skybox component") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") + + +def AtomEditorComponents_HDRiSkybox_AddedToEntity(): + """ + Summary: + Tests the HDRi Skybox component can be added to an entity and has the expected functionality. + + Test setup: + - Wait for Editor idle loop. + - Open the "Base" level. + + Expected Behavior: + The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components. + Creation and deletion undo/redo should also work. + + Test Steps: + 1) Create an HDRi Skybox with no components. + 2) Add an HDRi Skybox component to HDRi Skybox. + 3) UNDO the entity creation and component addition. + 4) REDO the entity creation and component addition. + 5) Enter/Exit game mode. + 6) Test IsHidden. + 7) Test IsVisible. + 8) Delete HDRi Skybox. + 9) UNDO deletion. + 10) REDO deletion. + 11) Look for errors. + + :return: None + """ + + import os + + import azlmbr.legacy.general as general + + from editor_python_test_tools.asset_utils import Asset + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties + + with Tracer() as error_tracer: + # Test setup begins. + # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. + TestHelper.init_idle() + TestHelper.open_level("", "Base") + + # Test steps begin. + # 1. Create an HDRi Skybox with no components. + hdri_skybox_entity = EditorEntity.create_editor_entity( + AtomComponentProperties.hdri_skybox()) + Report.critical_result(Tests.hdri_skybox_entity_creation, + hdri_skybox_entity.exists()) + + # 2. Add an HDRi Skybox component to HDRi Skybox. + hdri_skybox_component = hdri_skybox_entity.add_component( + AtomComponentProperties.hdri_skybox()) + Report.critical_result( + Tests.hdri_skybox_component, + hdri_skybox_entity.has_component(AtomComponentProperties.hdri_skybox())) + + # 3. UNDO the entity creation and component addition. + # -> UNDO component addition. + general.undo() + # -> UNDO naming entity. + general.undo() + # -> UNDO selecting entity. + general.undo() + # -> UNDO entity creation. + general.undo() + general.idle_wait_frames(1) + Report.result(Tests.creation_undo, not hdri_skybox_entity.exists()) + + # 4. REDO the entity creation and component addition. + # -> REDO entity creation. + general.redo() + # -> REDO selecting entity. + general.redo() + # -> REDO naming entity. + general.redo() + # -> REDO component addition. + general.redo() + general.idle_wait_frames(1) + Report.result(Tests.creation_redo, hdri_skybox_entity.exists()) + + + # 5. Set Cubemap Texture on HDRi Skybox component. + skybox_cubemap_asset_path = os.path.join("LightingPresets", "default_iblskyboxcm.exr.streamingimage") + skybox_cubemap_material_asset = Asset.find_asset_by_path(skybox_cubemap_asset_path, False) + hdri_skybox_component.set_component_property_value( + AtomComponentProperties.hdri_skybox('Cubemap Texture'), skybox_cubemap_material_asset.id) + get_cubemap_property = hdri_skybox_component.get_component_property_value( + AtomComponentProperties.hdri_skybox('Cubemap Texture')) + Report.result(Tests.cubemap_property_set, get_cubemap_property == skybox_cubemap_material_asset.id) + + + # 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. Test IsHidden. + hdri_skybox_entity.set_visibility_state(False) + Report.result(Tests.is_hidden, hdri_skybox_entity.is_hidden() is True) + + # 8. Test IsVisible. + hdri_skybox_entity.set_visibility_state(True) + general.idle_wait_frames(1) + Report.result(Tests.is_visible, hdri_skybox_entity.is_visible() is True) + + # 9. Delete hdri_skybox entity. + hdri_skybox_entity.delete() + Report.result(Tests.entity_deleted, not hdri_skybox_entity.exists()) + + # 10. UNDO deletion. + general.undo() + Report.result(Tests.deletion_undo, hdri_skybox_entity.exists()) + + # 11. REDO deletion. + general.redo() + Report.result(Tests.deletion_redo, not hdri_skybox_entity.exists()) + + # 12. Look for errors or asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(AtomEditorComponents_HDRiSkybox_AddedToEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_LightAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_LightAdded.py index 8b1432f1f7..249671556d 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_LightAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_LightAdded.py @@ -5,24 +5,49 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off class Tests: - camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created") - camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity") - camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component") - creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed") - creation_redo = ("REDO Entity creation success", "REDO Entity creation failed") - light_creation = ("Light Entity successfully created", "Light Entity failed to be created") - light_component = ("Entity has a Light component", "Entity failed to find Light component") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - is_visible = ("Entity is visible", "Entity was not visible") - is_hidden = ("Entity is hidden", "Entity was not hidden") - entity_deleted = ("Entity deleted", "Entity was not deleted") - deletion_undo = ("UNDO deletion success", "UNDO deletion failed") - deletion_redo = ("REDO deletion success", "REDO deletion failed") - no_error_occurred = ("No errors detected", "Errors were detected") -# fmt: on + camera_creation = ( + "Camera Entity successfully created", + "Camera Entity failed to be created") + camera_component_added = ( + "Camera component was added to entity", + "Camera component failed to be added to entity") + camera_component_check = ( + "Entity has a Camera component", + "Entity failed to find Camera component") + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + light_creation = ( + "Light Entity successfully created", + "Light Entity failed to be created") + light_component = ( + "Entity has a Light component", + "Entity failed to find Light component") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") def AtomEditorComponents_Light_AddedToEntity(): @@ -55,26 +80,25 @@ def AtomEditorComponents_Light_AddedToEntity(): """ import azlmbr.legacy.general as general - import azlmbr.math as math from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + 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. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Create a Light entity with no components. - light_name = "Light" - light_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), light_name) + light_entity = EditorEntity.create_editor_entity(AtomComponentProperties.light()) Report.critical_result(Tests.light_creation, light_entity.exists()) # 2. Add Light component to the Light entity. - light_entity.add_component(light_name) - Report.critical_result(Tests.light_component, light_entity.has_component(light_name)) + light_component = light_entity.add_component(AtomComponentProperties.light()) + Report.critical_result(Tests.light_component, light_entity.has_component(AtomComponentProperties.light())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -101,9 +125,9 @@ def AtomEditorComponents_Light_AddedToEntity(): Report.result(Tests.creation_redo, light_entity.exists()) # 5. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) + TestHelper.enter_game_mode(Tests.enter_game_mode) general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + TestHelper.exit_game_mode(Tests.exit_game_mode) # 6. Test IsHidden. light_entity.set_visibility_state(False) @@ -126,9 +150,12 @@ def AtomEditorComponents_Light_AddedToEntity(): general.redo() Report.result(Tests.deletion_redo, not light_entity.exists()) - # 11. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + # 11. Look for errors 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__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_LookModificationAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_LookModificationAdded.py new file mode 100644 index 0000000000..afb8033426 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_LookModificationAdded.py @@ -0,0 +1,211 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + + +class Tests: + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + look_modification_creation = ( + "Look Modification Entity successfully created", + "Look Modification Entity failed to be created") + look_modification_component = ( + "Entity has a Look Modification component", + "Entity failed to find Look Modification component") + look_modification_disabled = ( + "Look Modification component disabled", + "Look Modification component was not disabled") + postfx_layer_component = ( + "Entity has a PostFX Layer component", + "Entity did not have an PostFX Layer component") + look_modification_enabled = ( + "Look Modification component enabled", + "Look Modification component was not enabled") + enable_look_modification_parameter_enabled = ( + "Enable look modification parameter enabled", + "Enable look modification parameter was not enabled") + color_grading_lut_set = ( + "Entity has the Color Grading LUT set", + "Entity did not the Color Grading LUT set") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") + + +def AtomEditorComponents_LookModification_AddedToEntity(): + """ + Summary: + Tests the Look Modification component can be added to an entity and has the expected functionality. + + Test setup: + - Wait for Editor idle loop. + - Open the "Base" level. + + Expected Behavior: + The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components. + Creation and deletion undo/redo should also work. + + Test Steps: + 1) Create an Look Modification entity with no components. + 2) Add Look Modification component to Look Modification entity. + 3) UNDO the entity creation and component addition. + 4) REDO the entity creation and component addition. + 5) Verify Look Modification component not enabled. + 6) Add PostFX Layer component since it is required by the Look Modification component. + 7) Verify Look Modification component is enabled. + 8) Enable the "Enable Look Modification" parameter. + 9) Add LUT asset to the Color Grading LUT parameter. + 9) Enter/Exit game mode. + 10) Test IsHidden. + 11) Test IsVisible. + 12) Delete Look Modification entity. + 13) UNDO deletion. + 14) REDO deletion. + 15) Look for errors. + + :return: None + """ + + import os + + import azlmbr.legacy.general as general + + from editor_python_test_tools.asset_utils import Asset + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties + + with Tracer() as error_tracer: + # Test setup begins. + # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. + TestHelper.init_idle() + TestHelper.open_level("", "Base") + + # Test steps begin. + # 1. Create an Look Modification entity with no components. + look_modification_entity = EditorEntity.create_editor_entity(AtomComponentProperties.look_modification()) + Report.critical_result(Tests.look_modification_creation, look_modification_entity.exists()) + + # 2. Add Look Modification component to Look Modification entity. + look_modification_component = look_modification_entity.add_component( + AtomComponentProperties.look_modification()) + Report.critical_result( + Tests.look_modification_component, + look_modification_entity.has_component(AtomComponentProperties.look_modification())) + + # 3. UNDO the entity creation and component addition. + # -> UNDO component addition. + general.undo() + # -> UNDO naming entity. + general.undo() + # -> UNDO selecting entity. + general.undo() + # -> UNDO entity creation. + general.undo() + general.idle_wait_frames(1) + Report.result(Tests.creation_undo, not look_modification_entity.exists()) + + # 4. REDO the entity creation and component addition. + # -> REDO entity creation. + general.redo() + # -> REDO selecting entity. + general.redo() + # -> REDO naming entity. + general.redo() + # -> REDO component addition. + general.redo() + general.idle_wait_frames(1) + Report.result(Tests.creation_redo, look_modification_entity.exists()) + + # 5. Verify Look Modification component not enabled. + Report.result(Tests.look_modification_disabled, not look_modification_component.is_enabled()) + + # 6. Add PostFX Layer component since it is required by the Look Modification component. + look_modification_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.result( + Tests.postfx_layer_component, + look_modification_entity.has_component(AtomComponentProperties.postfx_layer())) + + # 7. Verify Look Modification component is enabled. + Report.result(Tests.look_modification_enabled, look_modification_component.is_enabled()) + + # 8. Enable the "Enable look modification" parameter. + look_modification_component.set_component_property_value( + AtomComponentProperties.look_modification('Enable look modification'), True) + Report.result(Tests.enable_look_modification_parameter_enabled, + look_modification_component.get_component_property_value( + AtomComponentProperties.look_modification('Enable look modification')) is True) + + # 9. Set the Color Grading LUT asset on the Look Modification entity. + color_grading_lut_path = os.path.join("ColorGrading", "TestData", "Photoshop", "inv-Log2-48nits", + "test_3dl_32_lut.azasset") + color_grading_lut_asset = Asset.find_asset_by_path(color_grading_lut_path, False) + look_modification_component.set_component_property_value( + AtomComponentProperties.look_modification('Color Grading LUT'), color_grading_lut_asset.id) + Report.result( + Tests.color_grading_lut_set, + color_grading_lut_asset.id == look_modification_component.get_component_property_value( + AtomComponentProperties.look_modification('Color Grading LUT'))) + + # 10. 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) + + # 11. Test IsHidden. + look_modification_entity.set_visibility_state(False) + Report.result(Tests.is_hidden, look_modification_entity.is_hidden() is True) + + # 12. Test IsVisible. + look_modification_entity.set_visibility_state(True) + general.idle_wait_frames(1) + Report.result(Tests.is_visible, look_modification_entity.is_visible() is True) + + # 13. Delete Look Modification entity. + look_modification_entity.delete() + Report.result(Tests.entity_deleted, not look_modification_entity.exists()) + + # 14. UNDO deletion. + general.undo() + Report.result(Tests.deletion_undo, look_modification_entity.exists()) + + # 15. REDO deletion. + general.redo() + Report.result(Tests.deletion_redo, not look_modification_entity.exists()) + + # 16. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(AtomEditorComponents_LookModification_AddedToEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MaterialAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MaterialAdded.py index 32cd5471f2..c613bb23e7 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MaterialAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MaterialAdded.py @@ -96,6 +96,7 @@ def AtomEditorComponents_Material_AddedToEntity(): from editor_python_test_tools.editor_entity_utils import EditorEntity from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. @@ -105,15 +106,14 @@ def AtomEditorComponents_Material_AddedToEntity(): # Test steps begin. # 1. Create a Material entity with no components. - material_name = "Material" - material_entity = EditorEntity.create_editor_entity(material_name) + material_entity = EditorEntity.create_editor_entity(AtomComponentProperties.material()) Report.critical_result(Tests.material_creation, material_entity.exists()) # 2. Add a Material component to Material entity. - material_component = material_entity.add_component(material_name) + material_component = material_entity.add_component(AtomComponentProperties.material()) Report.critical_result( Tests.material_component, - material_entity.has_component(material_name)) + material_entity.has_component(AtomComponentProperties.material())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -143,9 +143,8 @@ def AtomEditorComponents_Material_AddedToEntity(): Report.result(Tests.material_disabled, not material_component.is_enabled()) # 6. Add Actor component since it is required by the Material component. - actor_name = "Actor" - material_entity.add_component(actor_name) - Report.result(Tests.actor_component, material_entity.has_component(actor_name)) + material_entity.add_component(AtomComponentProperties.actor()) + Report.result(Tests.actor_component, material_entity.has_component(AtomComponentProperties.actor())) # 7. Verify Material component is enabled. Report.result(Tests.material_enabled, material_component.is_enabled()) @@ -153,15 +152,14 @@ def AtomEditorComponents_Material_AddedToEntity(): # 8. UNDO component addition. general.undo() general.idle_wait_frames(1) - Report.result(Tests.actor_undo, not material_entity.has_component(actor_name)) + Report.result(Tests.actor_undo, not material_entity.has_component(AtomComponentProperties.actor())) # 9. Verify Material component not enabled. Report.result(Tests.material_disabled, not material_component.is_enabled()) # 10. Add Mesh component since it is required by the Material component. - mesh_name = "Mesh" - material_entity.add_component(mesh_name) - Report.result(Tests.mesh_component, material_entity.has_component(mesh_name)) + material_entity.add_component(AtomComponentProperties.mesh()) + Report.result(Tests.mesh_component, material_entity.has_component(AtomComponentProperties.mesh())) # 11. Verify Material component is enabled. Report.result(Tests.material_enabled, material_component.is_enabled()) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MeshAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MeshAdded.py index 82d3b89309..9d56753961 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MeshAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_MeshAdded.py @@ -81,7 +81,7 @@ def AtomEditorComponents_Mesh_AddedToEntity(): 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 as Atom + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. @@ -91,14 +91,14 @@ def AtomEditorComponents_Mesh_AddedToEntity(): # Test steps begin. # 1. Create a Mesh entity with no components. - mesh_entity = EditorEntity.create_editor_entity(Atom.mesh()) + mesh_entity = EditorEntity.create_editor_entity(AtomComponentProperties.mesh()) Report.critical_result(Tests.mesh_entity_creation, mesh_entity.exists()) # 2. Add a Mesh component to Mesh entity. - mesh_component = mesh_entity.add_component(Atom.mesh()) + mesh_component = mesh_entity.add_component(AtomComponentProperties.mesh()) Report.critical_result( Tests.mesh_component_added, - mesh_entity.has_component(Atom.mesh())) + mesh_entity.has_component(AtomComponentProperties.mesh())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -127,9 +127,9 @@ def AtomEditorComponents_Mesh_AddedToEntity(): # 5. Set Mesh component asset property model_path = os.path.join('Objects', 'shaderball', 'shaderball_default_1m.azmodel') model = Asset.find_asset_by_path(model_path) - mesh_component.set_component_property_value(Atom.mesh('Mesh Asset'), model.id) + mesh_component.set_component_property_value(AtomComponentProperties.mesh('Mesh Asset'), model.id) Report.result(Tests.mesh_asset_specified, - mesh_component.get_component_property_value(Atom.mesh('Mesh Asset')) == model.id) + mesh_component.get_component_property_value(AtomComponentProperties.mesh('Mesh Asset')) == model.id) # 6. Enter/Exit game mode. TestHelper.enter_game_mode(Tests.enter_game_mode) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_OcclusionCullingPlaneAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_OcclusionCullingPlaneAdded.py new file mode 100644 index 0000000000..4226ae3dfe --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_OcclusionCullingPlaneAdded.py @@ -0,0 +1,159 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + +class Tests: + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + occlusion_culling_plane_entity_creation = ( + "Occlusion Culling Plane Entity successfully created", + "Occlusion Culling Plane Entity failed to be created") + occlusion_culling_plane_component_added = ( + "Entity has a Occlusion Culling Plane component", + "Entity failed to find Occlusion Culling Plane component") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") + + +def AtomEditorComponents_OcclusionCullingPlane_AddedToEntity(): + """ + Summary: + Tests the occlusion culling plane component can be added to an entity and has the expected functionality. + + Test setup: + - Wait for Editor idle loop. + - Open the "Base" level. + + Expected Behavior: + The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components. + Creation and deletion undo/redo should also work. + + Test Steps: + 1) Create a Occlusion Culling Plane entity with no components. + 2) Add a Occlusion Culling Plane component to Occlusion Culling Plane entity. + 3) UNDO the entity creation and component addition. + 4) REDO the entity creation and component addition. + 5) Enter/Exit game mode. + 6) Test IsHidden. + 7) Test IsVisible. + 8) Delete Occlusion Culling Plane entity. + 9) UNDO deletion. + 10) REDO deletion. + 11) Look for errors. + + :return: None + """ + + import azlmbr.legacy.general as general + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties + + with Tracer() as error_tracer: + # Test setup begins. + # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. + TestHelper.init_idle() + TestHelper.open_level("", "Base") + + # Test steps begin. + # 1. Create a occlusion culling plane entity with no components. + occlusion_culling_plane_entity = EditorEntity.create_editor_entity( + AtomComponentProperties.occlusion_culling_plane()) + Report.critical_result(Tests.occlusion_culling_plane_entity_creation, + occlusion_culling_plane_entity.exists()) + + # 2. Add a occlusion culling plane component to occlusion culling plane entity. + occlusion_culling_plane_component = occlusion_culling_plane_entity.add_component( + AtomComponentProperties.occlusion_culling_plane()) + Report.critical_result( + Tests.occlusion_culling_plane_component_added, + occlusion_culling_plane_entity.has_component(AtomComponentProperties.occlusion_culling_plane())) + + # 3. UNDO the entity creation and component addition. + # -> UNDO component addition. + general.undo() + # -> UNDO naming entity. + general.undo() + # -> UNDO selecting entity. + general.undo() + # -> UNDO entity creation. + general.undo() + general.idle_wait_frames(1) + Report.result(Tests.creation_undo, not occlusion_culling_plane_entity.exists()) + + # 4. REDO the entity creation and component addition. + # -> REDO entity creation. + general.redo() + # -> REDO selecting entity. + general.redo() + # -> REDO naming entity. + general.redo() + # -> REDO component addition. + general.redo() + general.idle_wait_frames(1) + Report.result(Tests.creation_redo, occlusion_culling_plane_entity.exists()) + + # 5. Enter/Exit game mode. + TestHelper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + TestHelper.exit_game_mode(Tests.exit_game_mode) + + # 6. Test IsHidden. + occlusion_culling_plane_entity.set_visibility_state(False) + Report.result(Tests.is_hidden, occlusion_culling_plane_entity.is_hidden() is True) + + # 7. Test IsVisible. + occlusion_culling_plane_entity.set_visibility_state(True) + general.idle_wait_frames(1) + Report.result(Tests.is_visible, occlusion_culling_plane_entity.is_visible() is True) + + # 8. Delete occlusion_culling_plane entity. + occlusion_culling_plane_entity.delete() + Report.result(Tests.entity_deleted, not occlusion_culling_plane_entity.exists()) + + # 9. UNDO deletion. + general.undo() + Report.result(Tests.deletion_undo, occlusion_culling_plane_entity.exists()) + + # 10. REDO deletion. + general.redo() + Report.result(Tests.deletion_redo, not occlusion_culling_plane_entity.exists()) + + # 11. Look for errors or asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(AtomEditorComponents_OcclusionCullingPlane_AddedToEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PhysicalSkyAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PhysicalSkyAdded.py index 04441d5b2c..fa8c626016 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PhysicalSkyAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PhysicalSkyAdded.py @@ -5,24 +5,49 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off class Tests: - camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created") - camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity") - camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component") - creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed") - creation_redo = ("REDO Entity creation success", "REDO Entity creation failed") - physical_sky_creation = ("Physical Sky Entity successfully created", "Physical Sky Entity failed to be created") - physical_sky_component = ("Entity has a Physical Sky component", "Entity failed to find Physical Sky component") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - is_visible = ("Entity is visible", "Entity was not visible") - is_hidden = ("Entity is hidden", "Entity was not hidden") - entity_deleted = ("Entity deleted", "Entity was not deleted") - deletion_undo = ("UNDO deletion success", "UNDO deletion failed") - deletion_redo = ("REDO deletion success", "REDO deletion failed") - no_error_occurred = ("No errors detected", "Errors were detected") -# fmt: on + camera_creation = ( + "Camera Entity successfully created", + "Camera Entity failed to be created") + camera_component_added = ( + "Camera component was added to entity", + "Camera component failed to be added to entity") + camera_component_check = ( + "Entity has a Camera component", + "Entity failed to find Camera component") + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + physical_sky_creation = ( + "Physical Sky Entity successfully created", + "Physical Sky Entity failed to be created") + physical_sky_component = ( + "Entity has a Physical Sky component", + "Entity failed to find Physical Sky component") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") def AtomEditorComponents_PhysicalSky_AddedToEntity(): @@ -49,32 +74,33 @@ def AtomEditorComponents_PhysicalSky_AddedToEntity(): 8) Delete Physical Sky entity. 9) UNDO deletion. 10) REDO deletion. - 11) Look for errors. + 11) Look for errors and asserts. :return: None """ import azlmbr.legacy.general as general - import azlmbr.math as math from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + 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. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Create a Physical Sky entity with no components. - physical_sky_name = "Physical Sky" - physical_sky_entity = EditorEntity.create_editor_entity_at(math.Vector3(512.0, 512.0, 34.0), physical_sky_name) + physical_sky_entity = EditorEntity.create_editor_entity(AtomComponentProperties.physical_sky()) Report.critical_result(Tests.physical_sky_creation, physical_sky_entity.exists()) # 2. Add Physical Sky component to Physical Sky entity. - physical_sky_entity.add_component(physical_sky_name) - Report.critical_result(Tests.physical_sky_component, physical_sky_entity.has_component(physical_sky_name)) + physical_sky_component = physical_sky_entity.add_component(AtomComponentProperties.physical_sky()) + Report.critical_result( + Tests.physical_sky_component, + physical_sky_entity.has_component(AtomComponentProperties.physical_sky())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -101,9 +127,9 @@ def AtomEditorComponents_PhysicalSky_AddedToEntity(): Report.result(Tests.creation_redo, physical_sky_entity.exists()) # 5. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) + TestHelper.enter_game_mode(Tests.enter_game_mode) general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + TestHelper.exit_game_mode(Tests.exit_game_mode) # 6. Test IsHidden. physical_sky_entity.set_visibility_state(False) @@ -126,9 +152,12 @@ def AtomEditorComponents_PhysicalSky_AddedToEntity(): general.redo() Report.result(Tests.deletion_redo, not physical_sky_entity.exists()) - # 11. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + # 11. 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__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXGradientWeightModifierAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXGradientWeightModifierAdded.py index d38e96739d..6e4ae2d9ac 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXGradientWeightModifierAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXGradientWeightModifierAdded.py @@ -86,6 +86,7 @@ def AtomEditorComponents_PostFXGradientWeightModifier_AddedToEntity(): from editor_python_test_tools.editor_entity_utils import EditorEntity from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. @@ -95,15 +96,15 @@ def AtomEditorComponents_PostFXGradientWeightModifier_AddedToEntity(): # Test steps begin. # 1. Create a PostFX Gradient Weight Modifier entity with no components. - postfx_gradient_weight_name = "PostFX Gradient Weight Modifier" - postfx_gradient_weight_entity = EditorEntity.create_editor_entity(postfx_gradient_weight_name) + postfx_gradient_weight_entity = EditorEntity.create_editor_entity(AtomComponentProperties.postfx_gradient()) Report.critical_result(Tests.postfx_gradient_weight_creation, postfx_gradient_weight_entity.exists()) # 2. Add a PostFX Gradient Weight Modifier component to PostFX Gradient Weight Modifier entity. - postfx_gradient_weight_component = postfx_gradient_weight_entity.add_component(postfx_gradient_weight_name) + postfx_gradient_weight_component = postfx_gradient_weight_entity.add_component( + AtomComponentProperties.postfx_gradient()) Report.critical_result( Tests.postfx_gradient_weight_component, - postfx_gradient_weight_entity.has_component(postfx_gradient_weight_name)) + postfx_gradient_weight_entity.has_component(AtomComponentProperties.postfx_gradient())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -133,9 +134,10 @@ def AtomEditorComponents_PostFXGradientWeightModifier_AddedToEntity(): Report.result(Tests.postfx_gradient_weight_disabled, not postfx_gradient_weight_component.is_enabled()) # 6. Add PostFX Layer component since it is required by the PostFX Gradient Weight Modifier component. - postfx_layer_name = "PostFX Layer" - postfx_gradient_weight_entity.add_component(postfx_layer_name) - Report.result(Tests.postfx_layer_component, postfx_gradient_weight_entity.has_component(postfx_layer_name)) + postfx_gradient_weight_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.result( + Tests.postfx_layer_component, + postfx_gradient_weight_entity.has_component(AtomComponentProperties.postfx_layer())) # 7. Verify PostFX Gradient Weight Modifier component is enabled. Report.result(Tests.postfx_gradient_weight_enabled, postfx_gradient_weight_component.is_enabled()) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXLayerAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXLayerAdded.py index 8c2dee416b..efef4c0a43 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXLayerAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXLayerAdded.py @@ -70,12 +70,11 @@ def AtomEditorComponents_postfx_layer_AddedToEntity(): :return: None """ - import os - import azlmbr.legacy.general as general from editor_python_test_tools.editor_entity_utils import EditorEntity from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. @@ -85,13 +84,14 @@ def AtomEditorComponents_postfx_layer_AddedToEntity(): # Test steps begin. # 1. Create a PostFX Layer entity with no components. - postfx_layer_name = "PostFX Layer" - postfx_layer_entity = EditorEntity.create_editor_entity(postfx_layer_name) + postfx_layer_entity = EditorEntity.create_editor_entity(AtomComponentProperties.postfx_layer()) Report.critical_result(Tests.postfx_layer_entity_creation, postfx_layer_entity.exists()) # 2. Add a PostFX Layer component to PostFX Layer entity. - postfx_layer_component = postfx_layer_entity.add_component(postfx_layer_name) - Report.critical_result(Tests.postfx_layer_component_added, postfx_layer_entity.has_component(postfx_layer_name)) + postfx_layer_component = postfx_layer_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.critical_result( + Tests.postfx_layer_component_added, + postfx_layer_entity.has_component(AtomComponentProperties.postfx_layer())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXRadiusWeightModifierAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXRadiusWeightModifierAdded.py index 8914ab9e7e..228ddfcdc5 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXRadiusWeightModifierAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFXRadiusWeightModifierAdded.py @@ -5,24 +5,49 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -# fmt: off class Tests: - camera_creation = ("Camera Entity successfully created", "Camera Entity failed to be created") - camera_component_added = ("Camera component was added to entity", "Camera component failed to be added to entity") - camera_component_check = ("Entity has a Camera component", "Entity failed to find Camera component") - creation_undo = ("UNDO Entity creation success", "UNDO Entity creation failed") - creation_redo = ("REDO Entity creation success", "REDO Entity creation failed") - postfx_radius_weight_creation = ("PostFX Radius Weight Modifier Entity successfully created", "PostFX Radius Weight Modifier Entity failed to be created") - postfx_radius_weight_component = ("Entity has a PostFX Radius Weight Modifier component", "Entity failed to find PostFX Radius Weight Modifier component") - enter_game_mode = ("Entered game mode", "Failed to enter game mode") - exit_game_mode = ("Exited game mode", "Couldn't exit game mode") - is_visible = ("Entity is visible", "Entity was not visible") - is_hidden = ("Entity is hidden", "Entity was not hidden") - entity_deleted = ("Entity deleted", "Entity was not deleted") - deletion_undo = ("UNDO deletion success", "UNDO deletion failed") - deletion_redo = ("REDO deletion success", "REDO deletion failed") - no_error_occurred = ("No errors detected", "Errors were detected") -# fmt: on + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + postfx_radius_weight_creation = ( + "PostFX Radius Weight Modifier Entity successfully created", + "PostFX Radius Weight Modifier Entity failed to be created") + postfx_radius_weight_component = ( + "Entity has a PostFX Radius Weight Modifier component", + "Entity failed to find PostFX Radius Weight Modifier component") + postfx_radius_weight_disabled = ( + "PostFX Radius Weight Modifier component disabled", + "PostFX Radius Weight Modifier component was not disabled.") + postfx_layer_component = ( + "Entity has a PostFX Layer component", + "Entity did not have an PostFX Layer component") + postfx_radius_weight_enabled = ( + "PostFX Radius Weight Modifier component enabled", + "PostFX Radius Weight Modifier component was not enabled.") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") def AtomEditorComponents_PostFXRadiusWeightModifier_AddedToEntity(): @@ -43,40 +68,42 @@ def AtomEditorComponents_PostFXRadiusWeightModifier_AddedToEntity(): 2) Add Post FX Radius Weight Modifier component to Post FX Radius Weight Modifier entity. 3) UNDO the entity creation and component addition. 4) REDO the entity creation and component addition. - 5) Enter/Exit game mode. - 6) Test IsHidden. - 7) Test IsVisible. - 8) Delete PostFX Radius Weight Modifier entity. - 9) UNDO deletion. - 10) REDO deletion. - 11) Look for errors. + 5) Verify PostFX Radius Weight Modifier component not enabled. + 6) Add PostFX Layer component since it is required by the PostFX Radius Weight Modifier component. + 7) Verify PostFX Radius Weight Modifier component is enabled. + 8) Enter/Exit game mode. + 9) Test IsHidden. + 10) Test IsVisible. + 11) Delete PostFX Radius Weight Modifier entity. + 12) UNDO deletion. + 13) REDO deletion. + 14) Look for errors. :return: None """ import azlmbr.legacy.general as general - import azlmbr.math as math from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + 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. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Create a Post FX Radius Weight Modifier entity with no components. - postfx_radius_weight_name = "PostFX Radius Weight Modifier" - postfx_radius_weight_entity = EditorEntity.create_editor_entity_at( - math.Vector3(512.0, 512.0, 34.0), postfx_radius_weight_name) + postfx_radius_weight_entity = EditorEntity.create_editor_entity(AtomComponentProperties.postfx_radius()) Report.critical_result(Tests.postfx_radius_weight_creation, postfx_radius_weight_entity.exists()) # 2. Add Post FX Radius Weight Modifier component to Post FX Radius Weight Modifier entity. - postfx_radius_weight_entity.add_component(postfx_radius_weight_name) + postfx_radius_component = postfx_radius_weight_entity.add_component(AtomComponentProperties.postfx_radius()) Report.critical_result( - Tests.postfx_radius_weight_component, postfx_radius_weight_entity.has_component(postfx_radius_weight_name)) + Tests.postfx_radius_weight_component, + postfx_radius_weight_entity.has_component(AtomComponentProperties.postfx_radius())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -102,35 +129,50 @@ def AtomEditorComponents_PostFXRadiusWeightModifier_AddedToEntity(): general.idle_wait_frames(1) Report.result(Tests.creation_redo, postfx_radius_weight_entity.exists()) - # 5. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) - general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + # 5. Verify PostFX Radius Weight Modifier component not enabled. + Report.result(Tests.postfx_radius_weight_disabled, not postfx_radius_component.is_enabled()) - # 6. Test IsHidden. + # 6. Add PostFX Layer component since it is required by the PostFX Radius Weight Modifier component. + postfx_radius_weight_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.result( + Tests.postfx_layer_component, + postfx_radius_weight_entity.has_component(AtomComponentProperties.postfx_layer())) + + # 7. Verify PostFX Radius Weight Modifier component is enabled. + Report.result(Tests.postfx_radius_weight_enabled, postfx_radius_component.is_enabled()) + + # 8. 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) + + # 9. Test IsHidden. postfx_radius_weight_entity.set_visibility_state(False) Report.result(Tests.is_hidden, postfx_radius_weight_entity.is_hidden() is True) - # 7. Test IsVisible. + # 10. Test IsVisible. postfx_radius_weight_entity.set_visibility_state(True) general.idle_wait_frames(1) Report.result(Tests.is_visible, postfx_radius_weight_entity.is_visible() is True) - # 8. Delete PostFX Radius Weight Modifier entity. + # 11. Delete PostFX Radius Weight Modifier entity. postfx_radius_weight_entity.delete() Report.result(Tests.entity_deleted, not postfx_radius_weight_entity.exists()) - # 9. UNDO deletion. + # 12. UNDO deletion. general.undo() Report.result(Tests.deletion_undo, postfx_radius_weight_entity.exists()) - # 10. REDO deletion. + # 13. REDO deletion. general.redo() Report.result(Tests.deletion_redo, not postfx_radius_weight_entity.exists()) - # 11. Look for errors. - helper.wait_for_condition(lambda: error_tracer.has_errors, 1.0) - Report.result(Tests.no_error_occurred, not error_tracer.has_errors) + # 14. 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__": diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFxShapeWeightModifierAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFxShapeWeightModifierAdded.py index 4c98bcd130..0a37ac6bf7 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFxShapeWeightModifierAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_PostFxShapeWeightModifierAdded.py @@ -92,6 +92,7 @@ def AtomEditorComponents_postfx_shape_weight_AddedToEntity(): from editor_python_test_tools.editor_entity_utils import EditorEntity from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties with Tracer() as error_tracer: # Test setup begins. @@ -101,15 +102,14 @@ def AtomEditorComponents_postfx_shape_weight_AddedToEntity(): # Test steps begin. # 1. Create a PostFx Shape Weight Modifier entity with no components. - postfx_shape_weight_name = "PostFX Shape Weight Modifier" - postfx_shape_weight_entity = EditorEntity.create_editor_entity(postfx_shape_weight_name) + postfx_shape_weight_entity = EditorEntity.create_editor_entity(AtomComponentProperties.postfx_shape()) Report.critical_result(Tests.postfx_shape_weight_creation, postfx_shape_weight_entity.exists()) # 2. Add a PostFx Shape Weight Modifier component to PostFx Shape Weight Modifier entity. - postfx_shape_weight_component = postfx_shape_weight_entity.add_component(postfx_shape_weight_name) + postfx_shape_weight_component = postfx_shape_weight_entity.add_component(AtomComponentProperties.postfx_shape()) Report.critical_result( Tests.postfx_shape_weight_component, - postfx_shape_weight_entity.has_component(postfx_shape_weight_name)) + postfx_shape_weight_entity.has_component(AtomComponentProperties.postfx_shape())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -139,16 +139,16 @@ def AtomEditorComponents_postfx_shape_weight_AddedToEntity(): Report.result(Tests.postfx_shape_weight_disabled, not postfx_shape_weight_component.is_enabled()) # 6. Add PostFX Layer component since it is required by the PostFx Shape Weight Modifier component. - postfx_layer_name = "PostFX Layer" - postfx_shape_weight_entity.add_component(postfx_layer_name) - Report.result(Tests.postfx_layer_component, postfx_shape_weight_entity.has_component(postfx_layer_name)) + postfx_shape_weight_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.result( + Tests.postfx_layer_component, + postfx_shape_weight_entity.has_component(AtomComponentProperties.postfx_layer())) # 7. Verify PostFx Shape Weight Modifier component is NOT enabled since it also requires a shape. Report.result(Tests.postfx_shape_weight_disabled, not postfx_shape_weight_component.is_enabled()) # 8. Add a required shape looping over a list and checking if it enables PostFX Shape Weight Modifier. - for shape in ['Axis Aligned Box Shape', 'Box Shape', 'Capsule Shape', 'Compound Shape', 'Cylinder Shape', - 'Disk Shape', 'Polygon Prism Shape', 'Quad Shape', 'Sphere Shape', 'Vegetation Reference Shape']: + for shape in AtomComponentProperties.postfx_shape('shapes'): postfx_shape_weight_entity.add_component(shape) test_shape = ( f"Entity has a {shape} component", diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ReflectionProbeAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ReflectionProbeAdded.py index 9b13eb2c7e..70adf9143a 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ReflectionProbeAdded.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_ReflectionProbeAdded.py @@ -87,30 +87,28 @@ def AtomEditorComponents_ReflectionProbe_AddedToEntity(): """ import azlmbr.legacy.general as general - import azlmbr.math as math import azlmbr.render as render from editor_python_test_tools.editor_entity_utils import EditorEntity - from editor_python_test_tools.utils import Report, Tracer, TestHelper as helper + 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. - helper.init_idle() - helper.open_level("", "Base") + TestHelper.init_idle() + TestHelper.open_level("", "Base") # Test steps begin. # 1. Create a Reflection Probe entity with no components. - reflection_probe_name = "Reflection Probe" - reflection_probe_entity = EditorEntity.create_editor_entity_at( - math.Vector3(512.0, 512.0, 34.0), reflection_probe_name) + reflection_probe_entity = EditorEntity.create_editor_entity(AtomComponentProperties.reflection_probe()) Report.critical_result(Tests.reflection_probe_creation, reflection_probe_entity.exists()) # 2. Add a Reflection Probe component to Reflection Probe entity. - reflection_probe_component = reflection_probe_entity.add_component(reflection_probe_name) + reflection_probe_component = reflection_probe_entity.add_component(AtomComponentProperties.reflection_probe()) Report.critical_result( Tests.reflection_probe_component, - reflection_probe_entity.has_component(reflection_probe_name)) + reflection_probe_entity.has_component(AtomComponentProperties.reflection_probe())) # 3. UNDO the entity creation and component addition. # -> UNDO component addition. @@ -139,18 +137,27 @@ def AtomEditorComponents_ReflectionProbe_AddedToEntity(): # 5. Verify Reflection Probe component not enabled. Report.result(Tests.reflection_probe_disabled, not reflection_probe_component.is_enabled()) - # 6. Add Box Shape component since it is required by the Reflection Probe component. - box_shape = "Box Shape" - reflection_probe_entity.add_component(box_shape) - Report.result(Tests.box_shape_component, reflection_probe_entity.has_component(box_shape)) + # 6. Add Shape component since it is required by the Reflection Probe component. + for shape in AtomComponentProperties.reflection_probe('shapes'): + reflection_probe_entity.add_component(shape) + test_shape = ( + f"Entity has a {shape} component", + f"Entity did not have a {shape} component") + Report.result(test_shape, reflection_probe_entity.has_component(shape)) - # 7. Verify Reflection Probe component is enabled. - Report.result(Tests.reflection_probe_enabled, reflection_probe_component.is_enabled()) + # 7. Check if required shape allows Reflection Probe to be enabled + Report.result(Tests.reflection_probe_enabled, reflection_probe_component.is_enabled()) + + # Undo to remove each added shape except the last one and verify Reflection Probe is not enabled. + if not (shape == AtomComponentProperties.reflection_probe('shapes')[-1]): + general.undo() + TestHelper.wait_for_condition(lambda: not reflection_probe_entity.has_component(shape), 1.0) + Report.result(Tests.reflection_probe_disabled, not reflection_probe_component.is_enabled()) # 8. Enter/Exit game mode. - helper.enter_game_mode(Tests.enter_game_mode) + TestHelper.enter_game_mode(Tests.enter_game_mode) general.idle_wait_frames(1) - helper.exit_game_mode(Tests.exit_game_mode) + TestHelper.exit_game_mode(Tests.exit_game_mode) # 9. Test IsHidden. reflection_probe_entity.set_visibility_state(False) @@ -165,8 +172,9 @@ def AtomEditorComponents_ReflectionProbe_AddedToEntity(): render.EditorReflectionProbeBus(azlmbr.bus.Event, "BakeReflectionProbe", reflection_probe_entity.id) Report.result( Tests.reflection_map_generated, - helper.wait_for_condition( - lambda: reflection_probe_component.get_component_property_value("Cubemap|Baked Cubemap Path") != "", + TestHelper.wait_for_condition( + lambda: reflection_probe_component.get_component_property_value( + AtomComponentProperties.reflection_probe('Baked Cubemap Path')) != "", 20.0)) # 12. Delete Reflection Probe entity. @@ -182,7 +190,7 @@ def AtomEditorComponents_ReflectionProbe_AddedToEntity(): Report.result(Tests.deletion_redo, not reflection_probe_entity.exists()) # 15. Look for errors or asserts. - helper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + 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: diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_SSAOAdded.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_SSAOAdded.py new file mode 100644 index 0000000000..15f40f8b70 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomEditorComponents_SSAOAdded.py @@ -0,0 +1,182 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + + +class Tests: + creation_undo = ( + "UNDO Entity creation success", + "UNDO Entity creation failed") + creation_redo = ( + "REDO Entity creation success", + "REDO Entity creation failed") + ssao_creation = ( + "SSAO Entity successfully created", + "SSAO Entity failed to be created") + ssao_component = ( + "Entity has a SSAO component", + "Entity failed to find SSAO component") + ssao_disabled = ( + "SSAO component disabled", + "SSAO component was not disabled.") + postfx_layer_component = ( + "Entity has a PostFX Layer component", + "Entity did not have an PostFX Layer component") + ssao_enabled = ( + "SSAO component enabled", + "SSAO component was not enabled.") + enter_game_mode = ( + "Entered game mode", + "Failed to enter game mode") + exit_game_mode = ( + "Exited game mode", + "Couldn't exit game mode") + is_visible = ( + "Entity is visible", + "Entity was not visible") + is_hidden = ( + "Entity is hidden", + "Entity was not hidden") + entity_deleted = ( + "Entity deleted", + "Entity was not deleted") + deletion_undo = ( + "UNDO deletion success", + "UNDO deletion failed") + deletion_redo = ( + "REDO deletion success", + "REDO deletion failed") + + +def AtomEditorComponents_SSAO_AddedToEntity(): + """ + Summary: + Tests the SSAO component can be added to an entity and has the expected functionality. + Screen Space Ambient Occlusion (SSAO) is a PostFX shadow lighting effect. + + Test setup: + - Wait for Editor idle loop. + - Open the "Base" level. + + Expected Behavior: + The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components. + Creation and deletion undo/redo should also work. + + Test Steps: + 1) Create a SSAO entity with no components. + 2) Add SSAO component to SSAO entity. + 3) UNDO the entity creation and component addition. + 4) REDO the entity creation and component addition. + 5) Verify SSAO component not enabled. + 6) Add PostFX Layer component since it is required by the SSAO component. + 7) Verify SSAO component is enabled. + 8) Enter/Exit game mode. + 9) Test IsHidden. + 10) Test IsVisible. + 11) Delete SSAO entity. + 12) UNDO deletion. + 13) REDO deletion. + 14) Look for errors. + + :return: None + """ + + import azlmbr.legacy.general as general + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import Report, Tracer, TestHelper + from Atom.atom_utils.atom_constants import AtomComponentProperties + + with Tracer() as error_tracer: + # Test setup begins. + # Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level. + TestHelper.init_idle() + TestHelper.open_level("", "Base") + + # Test steps begin. + # 1. Create a SSAO entity with no components. + ssao_entity = EditorEntity.create_editor_entity(AtomComponentProperties.ssao()) + Report.critical_result(Tests.ssao_creation, ssao_entity.exists()) + + # 2. Add SSAO component to SSAO entity. + ssao_component = ssao_entity.add_component(AtomComponentProperties.ssao()) + Report.critical_result( + Tests.ssao_component, + ssao_entity.has_component(AtomComponentProperties.ssao())) + ssao_component.get_property_tree() + # 3. UNDO the entity creation and component addition. + # -> UNDO component addition. + general.undo() + # -> UNDO naming entity. + general.undo() + # -> UNDO selecting entity. + general.undo() + # -> UNDO entity creation. + general.undo() + general.idle_wait_frames(1) + Report.result(Tests.creation_undo, not ssao_entity.exists()) + + # 4. REDO the entity creation and component addition. + # -> REDO entity creation. + general.redo() + # -> REDO selecting entity. + general.redo() + # -> REDO naming entity. + general.redo() + # -> REDO component addition. + general.redo() + general.idle_wait_frames(1) + Report.result(Tests.creation_redo, ssao_entity.exists()) + + # 5. Verify SSAO component not enabled. + Report.result(Tests.ssao_disabled, not ssao_component.is_enabled()) + + # 6. Add PostFX Layer component since it is required by the SSAO component. + ssao_entity.add_component(AtomComponentProperties.postfx_layer()) + Report.result( + Tests.postfx_layer_component, + ssao_entity.has_component(AtomComponentProperties.postfx_layer())) + + # 7. Verify SSAO component is enabled. + Report.result(Tests.ssao_enabled, ssao_component.is_enabled()) + + # 8. 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) + + # 9. Test IsHidden. + ssao_entity.set_visibility_state(False) + Report.result(Tests.is_hidden, ssao_entity.is_hidden() is True) + + # 10. Test IsVisible. + ssao_entity.set_visibility_state(True) + general.idle_wait_frames(1) + Report.result(Tests.is_visible, ssao_entity.is_visible() is True) + + # 11. Delete SSAO entity. + ssao_entity.delete() + Report.result(Tests.entity_deleted, not ssao_entity.exists()) + + # 12. UNDO deletion. + general.undo() + Report.result(Tests.deletion_undo, ssao_entity.exists()) + + # 13. REDO deletion. + general.redo() + Report.result(Tests.deletion_redo, not ssao_entity.exists()) + + # 14. Look for errors and asserts. + TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0) + for error_info in error_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in error_tracer.asserts: + Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}") + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(AtomEditorComponents_SSAO_AddedToEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomGPU_AreaLightScreenshotTest.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomGPU_AreaLightScreenshotTest.py new file mode 100644 index 0000000000..a56c72e46c --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomGPU_AreaLightScreenshotTest.py @@ -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) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomGPU_BasicLevelSetup.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomGPU_BasicLevelSetup.py index 127b3e5b5f..b15e5b2131 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomGPU_BasicLevelSetup.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomGPU_BasicLevelSetup.py @@ -80,6 +80,7 @@ def AtomGPU_BasicLevelSetup_SetsUpLevel(): Test setup: - Wait for Editor idle loop. - Open the "Base" level. + - Deletes all existing entities before creating the scene. Expected Behavior: The scene can be setup for a basic level. @@ -115,7 +116,6 @@ def AtomGPU_BasicLevelSetup_SetsUpLevel(): """ import os - from math import isclose import azlmbr.legacy.general as general import azlmbr.math as math @@ -126,21 +126,11 @@ def AtomGPU_BasicLevelSetup_SetsUpLevel(): from editor_python_test_tools.utils import Report, Tracer, TestHelper from Atom.atom_utils.atom_constants import AtomComponentProperties + from Atom.atom_utils.atom_component_helper import initial_viewport_setup from Atom.atom_utils.screenshot_utils import ScreenshotHelper - SCREENSHOT_NAME = "AtomBasicLevelSetup" - SCREEN_WIDTH = 1280 - SCREEN_HEIGHT = 720 DEGREE_RADIAN_FACTOR = 0.0174533 - - def initial_viewport_setup(screen_width, screen_height): - general.set_viewport_size(screen_width, screen_height) - general.update_viewport() - TestHelper.wait_for_condition( - function=lambda: isclose(a=general.get_viewport_size().x, b=SCREEN_WIDTH, rel_tol=0.1) - and isclose(a=general.get_viewport_size().y, b=SCREEN_HEIGHT, rel_tol=0.1), - timeout_in_seconds=4.0 - ) + SCREENSHOT_NAME = "AtomBasicLevelSetup" with Tracer() as error_tracer: # Test setup begins. @@ -148,11 +138,16 @@ def AtomGPU_BasicLevelSetup_SetsUpLevel(): TestHelper.init_idle() TestHelper.open_level("", "Base") + # Setup: Deletes all existing entities before creating the scene. + search_filter = azlmbr.entity.SearchFilter() + all_entities = azlmbr.entity.SearchBus(azlmbr.bus.Broadcast, "SearchEntities", search_filter) + azlmbr.editor.ToolsApplicationRequestBus(azlmbr.bus.Broadcast, "DeleteEntities", all_entities) + # Test steps begin. # 1. Close error windows and display helpers then update the viewport size. TestHelper.close_error_windows() TestHelper.close_display_helpers() - initial_viewport_setup(SCREEN_WIDTH, SCREEN_HEIGHT) + initial_viewport_setup() general.update_viewport() # 2. Create Default Level Entity. diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomGPU_SpotLightScreenshotTest.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomGPU_SpotLightScreenshotTest.py new file mode 100644 index 0000000000..fbfb6e3468 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_AtomGPU_SpotLightScreenshotTest.py @@ -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) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_BasicLevelSetup.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_BasicLevelSetup.py deleted file mode 100644 index 3bf4c46a57..0000000000 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_BasicLevelSetup.py +++ /dev/null @@ -1,220 +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.camera -import azlmbr.entity as entity -import azlmbr.legacy.general as general -import azlmbr.math as math -import azlmbr.paths -import azlmbr.editor as editor - -sys.path.append(os.path.join(azlmbr.paths.projectroot, "Gem", "PythonTests")) - -import editor_python_test_tools.hydra_editor_utils as hydra -from editor_python_test_tools.editor_test_helper import EditorTestHelper -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, & saves the level. - :return: None - """ - 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) - - return True - - # Wait for Editor idle loop before executing Python hydra scripts. - general.idle_enable(True) - - # Open the auto_test level. - new_level_name = "auto_test" # Specified in class TestAllComponentsIndepthTests() - heightmap_resolution = 512 - heightmap_meters_per_pixel = 1 - terrain_texture_resolution = 412 - use_terrain = False - - # Return codes are ECreateLevelResult defined in CryEdit.h - return_code = general.create_level_no_prompt( - new_level_name, heightmap_resolution, heightmap_meters_per_pixel, terrain_texture_resolution, use_terrain) - if return_code == 1: - general.log(f"{new_level_name} level already exists") - elif return_code == 2: - general.log("Failed to create directory") - elif return_code == 3: - general.log("Directory length is too long") - elif return_code != 0: - general.log("Unknown error, failed to create level") - else: - general.log(f"{new_level_name} level created successfully") - - # Basic setup for newly created level. - 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) - ground_plane_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_chrome.azmaterial") - ground_plane_material_asset = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", ground_plane_material_asset_path, math.Uuid(), False) - ground_plane.get_set_test(0, "Default Material|Material Asset", ground_plane_material_asset) - # Work around to add the correct Atom Mesh component - mesh_type_id = azlmbr.globals.property.EditorMeshComponentTypeId - ground_plane.components.append( - editor.EditorComponentAPIBus( - bus.Broadcast, "AddComponentsOfType", ground_plane.id, [mesh_type_id] - ).GetValue()[0] - ) - ground_plane_mesh_asset_path = os.path.join("Objects", "plane.azmodel") - ground_plane_mesh_asset = 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) - - # 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 - ) - sphere_material_asset_path = os.path.join("Materials", "Presets", "PBR", "metal_brass_polished.azmaterial") - sphere_material_asset = asset.AssetCatalogRequestBus( - bus.Broadcast, "GetAssetIdByPath", sphere_material_asset_path, math.Uuid(), False) - sphere.get_set_test(0, "Default Material|Material Asset", sphere_material_asset) - # Work around to add the correct Atom Mesh component - 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) - - # 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) - azlmbr.camera.EditorCameraViewRequestBus(azlmbr.bus.Event, "ToggleCameraAsActiveView", camera_entity.id) - - # Save level, enter game mode, take screenshot, & exit game mode. - general.save_level() - 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() diff --git a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_LightComponent.py b/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_LightComponent.py deleted file mode 100644 index 1c3e6226c1..0000000000 --- a/AutomatedTesting/Gem/PythonTests/Atom/tests/hydra_GPUTest_LightComponent.py +++ /dev/null @@ -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 = "auto_test" -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() diff --git a/AutomatedTesting/Gem/PythonTests/Blast/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Blast/TestSuite_Main.py index dbc4a1a456..a047d09d29 100644 --- a/AutomatedTesting/Gem/PythonTests/Blast/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/Blast/TestSuite_Main.py @@ -23,28 +23,28 @@ from base import TestAutomationBase class TestAutomation(TestAutomationBase): def test_ActorSplitsAfterCollision(self, request, workspace, editor, launcher_platform): from .tests import Blast_ActorSplitsAfterCollision as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ActorSplitsAfterRadialDamage(self, request, workspace, editor, launcher_platform): from .tests import Blast_ActorSplitsAfterRadialDamage as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ActorSplitsAfterCapsuleDamage(self, request, workspace, editor, launcher_platform): from .tests import Blast_ActorSplitsAfterCapsuleDamage as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ActorSplitsAfterImpactSpreadDamage(self, request, workspace, editor, launcher_platform): from .tests import Blast_ActorSplitsAfterImpactSpreadDamage as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ActorSplitsAfterShearDamage(self, request, workspace, editor, launcher_platform): from .tests import Blast_ActorSplitsAfterShearDamage as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ActorSplitsAfterTriangleDamage(self, request, workspace, editor, launcher_platform): from .tests import Blast_ActorSplitsAfterTriangleDamage as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ActorSplitsAfterStressDamage(self, request, workspace, editor, launcher_platform): from .tests import Blast_ActorSplitsAfterStressDamage as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) diff --git a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt index d2b7075e22..800f347359 100644 --- a/AutomatedTesting/Gem/PythonTests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/CMakeLists.txt @@ -62,5 +62,8 @@ add_subdirectory(Terrain) ## AWS ## add_subdirectory(AWS) +## Multiplayer ## +add_subdirectory(Multiplayer) + ## Integration tests for editor testing framework ## add_subdirectory(editor_test_testing) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonBindings/ComponentUpdateListProperty_test.py b/AutomatedTesting/Gem/PythonTests/EditorPythonBindings/ComponentUpdateListProperty_test.py index ed677ea185..a8ca721cca 100755 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonBindings/ComponentUpdateListProperty_test.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonBindings/ComponentUpdateListProperty_test.py @@ -51,5 +51,6 @@ class TestComponentAssetListAutomation(object): editor, "ComponentUpdateListProperty_test_case.py", expected_lines=expected_lines, - cfg_args=[level] + cfg_args=[level], + enable_prefab_system=False, ) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonBindings/ObjectManagerCommands_test_case.py b/AutomatedTesting/Gem/PythonTests/EditorPythonBindings/ObjectManagerCommands_test_case.py index 9a308ab9c1..28b49bb4cb 100755 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonBindings/ObjectManagerCommands_test_case.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonBindings/ObjectManagerCommands_test_case.py @@ -67,30 +67,6 @@ if (editor.EditorToolsApplicationRequestBus(bus.Broadcast, 'GetCurrentLevelName' if(general.get_num_selected() == 0): print("clear_selection works") - general.hide_all_objects() - - if(general.is_object_hidden(objs_list[1])): - print("hide_all_objects works") - - general.unhide_object(objs_list[1]) - - if not(general.is_object_hidden(objs_list[1])): - print("unhide_object works") - - general.hide_object(objs_list[1]) - - if(general.is_object_hidden(objs_list[1])): - print("hide_object works") - - general.unhide_all_objects() - - general.freeze_object(objs_list[1]) - - if(general.is_object_frozen(objs_list[1])): - print("freeze_object works") - - general.unfreeze_object(objs_list[1]) - position = general.get_position(objs_list[1]) px1, py1, pz1 = fetch_vector3_parts(position) general.set_position(objs_list[1], px1 + 10, py1 - 4, pz1 + 3) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/README.txt b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/README.txt index 90afee39bc..bd9740f60f 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/README.txt +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/README.txt @@ -26,9 +26,9 @@ INSTALL It is recommended to set up these these tools with O3DE's CMake build commands. Assuming CMake is already setup on your operating system, below are some sample build commands: cd /path/to/od3e/ - mkdir windows_vs2019 - cd windows_vs2019 - cmake .. -G "Visual Studio 16 2019" -A x64 -T host=x64 -DLY_3RDPARTY_PATH="%3RDPARTYPATH%" -DLY_PROJECTS=AutomatedTesting + mkdir windows + cd windows + cmake .. -G "Visual Studio 16 2019" -DLY_PROJECTS=AutomatedTesting To manually install the project in development mode using your own installed Python interpreter: cd /path/to/od3e/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py index 844f8de903..72530325e0 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/editor_entity_utils.py @@ -25,7 +25,7 @@ class EditorComponent: """ EditorComponent class used to set and get the component property value using path EditorComponent object is returned from either of - EditorEntity.add_component() or Entity.add_components() or EditorEntity.get_component_objects() + EditorEntity.add_component() or Entity.add_components() or EditorEntity.get_components_of_type() which also assigns self.id and self.type_id to the EditorComponent object. """ @@ -94,6 +94,13 @@ class EditorComponent: """ return editor.EditorComponentAPIBus(bus.Broadcast, "IsComponentEnabled", self.id) + def disable_component(self): + """ + Used to disable the component using its id value. + :return: None + """ + editor.EditorComponentAPIBus(bus.Broadcast, "DisableComponents", [self.id]) + @staticmethod def get_type_ids(component_names: list) -> list: """ @@ -107,6 +114,19 @@ class EditorComponent: return type_ids +def convert_to_azvector3(xyz) -> azlmbr.math.Vector3: + """ + Converts a vector3-like element into a azlmbr.math.Vector3 + """ + if isinstance(xyz, Tuple) or isinstance(xyz, List): + assert len(xyz) == 3, ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3") + return math.Vector3(float(xyz[0]), float(xyz[1]), float(xyz[2])) + elif isinstance(xyz, type(math.Vector3())): + return xyz + else: + raise ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3") + + class EditorEntity: """ Entity class is used to create and interact with Editor Entities. @@ -119,13 +139,15 @@ class EditorEntity: def __init__(self, id: azlmbr.entity.EntityId): self.id: azlmbr.entity.EntityId = id + self.components: List[EditorComponent] = [] # Creation functions @classmethod - def find_editor_entity(cls, entity_name: str, must_be_unique : bool = False) -> EditorEntity: + def find_editor_entity(cls, entity_name: str, must_be_unique: bool = False) -> EditorEntity: """ Given Entity name, outputs entity object :param entity_name: Name of entity to find + :param must_be_unique: bool that asserts the entity_name specified is unique when set to True :return: EditorEntity class object """ entities = cls.find_editor_entities([entity_name]) @@ -133,14 +155,14 @@ class EditorEntity: if must_be_unique: assert len(entities) == 1, f"Failure: Multiple entities with name: '{entity_name}' when expected only one" - entity = cls(entities[0]) + entity = entities[0] return entity @classmethod - def find_editor_entities(cls, entity_names: List[str]) -> EditorEntity: + def find_editor_entities(cls, entity_names: List[str]) -> List[EditorEntity]: """ Given Entities names, returns a list of EditorEntity - :param entity_name: Name of entity to find + :param entity_names: List of entity names to find :return: List[EditorEntity] class object """ searchFilter = azlmbr.entity.SearchFilter() @@ -183,15 +205,6 @@ class EditorEntity: :return: EditorEntity class object """ - def convert_to_azvector3(xyz) -> math.Vector3: - if isinstance(xyz, Tuple) or isinstance(xyz, List): - assert len(xyz) == 3, ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3") - return math.Vector3(*xyz) - elif isinstance(xyz, type(math.Vector3())): - return xyz - else: - raise ValueError("vector must be a 3 element list/tuple or azlmbr.math.Vector3") - if parent_id is None: parent_id = azlmbr.entity.EntityId() @@ -206,7 +219,7 @@ class EditorEntity: return entity # Methods - def set_name(self, entity_name: str): + def set_name(self, entity_name: str) -> None: """ Given entity_name, sets name to Entity :param: entity_name: Name of the entity to set @@ -275,7 +288,7 @@ class EditorEntity: ), f"Failure: Could not add component: '{new_comp.get_component_name()}' to entity: '{self.get_name()}'" new_comp.id = add_component_outcome.GetValue()[0] components.append(new_comp) - + self.components.append(new_comp) return components def get_components_of_type(self, component_names: list) -> List[EditorComponent]: @@ -324,7 +337,7 @@ class EditorEntity: self.start_status = status return status - def set_start_status(self, desired_start_status: str): + def set_start_status(self, desired_start_status: str) -> None: """ Set an entity as active/inactive at beginning of runtime or it is editor-only, given its entity id and the start status then return set success @@ -382,18 +395,182 @@ class EditorEntity: """ return editor.EditorEntityInfoRequestBus(bus.Event, "IsVisible", self.id) + # World Transform Functions + def get_world_translation(self) -> azlmbr.math.Vector3: + """ + Gets the world translation of the entity + """ + return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldTranslation", self.id) + + def set_world_translation(self, new_translation) -> None: + """ + Sets the new world translation of the current entity + """ + new_translation = convert_to_azvector3(new_translation) + azlmbr.components.TransformBus(azlmbr.bus.Event, "SetWorldTranslation", self.id, new_translation) + + def get_world_rotation(self) -> azlmbr.math.Quaternion: + """ + Gets the world rotation of the entity + """ + return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldRotation", self.id) + + def set_world_rotation(self, new_rotation): + """ + Sets the new world rotation of the current entity + """ + new_rotation = convert_to_azvector3(new_rotation) + azlmbr.components.TransformBus(azlmbr.bus.Event, "SetWorldRotation", self.id, new_rotation) + + # Local Transform Functions + def get_local_uniform_scale(self) -> float: + """ + Gets the local uniform scale of the entity + """ + return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetLocalUniformScale", self.id) + def set_local_uniform_scale(self, scale_float) -> None: """ - Sets the "SetLocalUniformScale" value on the entity. + Sets the local uniform scale value(relative to the parent) on the entity. :param scale_float: value for "SetLocalUniformScale" to set to. :return: None """ azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalUniformScale", self.id, scale_float) - def set_local_rotation(self, vector3_rotation) -> None: + def get_local_rotation(self) -> azlmbr.math.Quaternion: """ - Sets the "SetLocalRotation" value on the entity. - :param vector3_rotation: The math.Vector3 value to use for rotation on the entity (uses radians). + Gets the local rotation of the entity + """ + return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetLocalRotation", self.id) + + def set_local_rotation(self, new_rotation) -> None: + """ + Sets the set the local rotation(relative to the parent) of the current entity. + :param new_rotation: The math.Vector3 value to use for rotation on the entity (uses radians). :return: None """ - azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", self.id, vector3_rotation) + new_rotation = convert_to_azvector3(new_rotation) + azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalRotation", self.id, new_rotation) + + def get_local_translation(self) -> azlmbr.math.Vector3: + """ + Gets the local translation of the current entity. + :return: The math.Vector3 value of the local translation. + """ + return azlmbr.components.TransformBus(azlmbr.bus.Event, "GetLocalTranslation", self.id) + + def set_local_translation(self, new_translation) -> None: + """ + Sets the local translation(relative to the parent) of the current entity. + :param new_translation: The math.Vector3 value to use for translation on the entity. + :return: None + """ + new_translation = convert_to_azvector3(new_translation) + azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalTranslation", self.id, new_translation) + + # Use this only when prefab system is enabled as it will fail otherwise. + def focus_on_owning_prefab(self) -> None: + """ + Focuses on the owning prefab instance of the given entity. + :param entity: The entity used to fetch the owning prefab to focus on. + """ + + assert self.id.isValid(), "A valid entity id is required to focus on its owning prefab." + focus_prefab_result = azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", self.id) + assert focus_prefab_result.IsSuccess(), f"Prefab operation 'FocusOnOwningPrefab' failed. Error: {focus_prefab_result.GetError()}" + + +class EditorLevelEntity: + """ + EditorLevel class used to add and fetch level components. + Level entity is a special entity that you do not create/destroy independently of larger systems of level creation. + This collects a number of staticmethods that do not rely on entityId since Level entity is found internally by + EditorLevelComponentAPIBus requests. + """ + + @staticmethod + def get_type_ids(component_names: list) -> list: + """ + Used to get type ids of given components list for EntityType Level + :param: component_names: List of components to get type ids + :return: List of type ids of given components. + """ + type_ids = editor.EditorComponentAPIBus( + bus.Broadcast, "FindComponentTypeIdsByEntityType", component_names, azlmbr.entity.EntityType().Level + ) + return type_ids + + @staticmethod + def add_component(component_name: str) -> EditorComponent: + """ + Used to add new component to Level. + :param component_name: String of component name to add. + :return: Component object of newly added component. + """ + component = EditorLevelEntity.add_components([component_name])[0] + return component + + @staticmethod + def add_components(component_names: list) -> List[EditorComponent]: + """ + Used to add multiple components + :param: component_names: List of components to add to level + :return: List of newly added components to the level + """ + components = [] + type_ids = EditorLevelEntity.get_type_ids(component_names) + for type_id in type_ids: + new_comp = EditorComponent() + new_comp.type_id = type_id + add_component_outcome = editor.EditorLevelComponentAPIBus( + bus.Broadcast, "AddComponentsOfType", [type_id] + ) + assert ( + add_component_outcome.IsSuccess() + ), f"Failure: Could not add component: '{new_comp.get_component_name()}' to level" + new_comp.id = add_component_outcome.GetValue()[0] + components.append(new_comp) + return components + + @staticmethod + def get_components_of_type(component_names: list) -> List[EditorComponent]: + """ + Used to get components of type component_name that already exists on the level + :param component_names: List of names of components to check + :return: List of Level Component objects of given component name + """ + component_list = [] + type_ids = EditorLevelEntity.get_type_ids(component_names) + for type_id in type_ids: + component = EditorComponent() + component.type_id = type_id + get_component_of_type_outcome = editor.EditorLevelComponentAPIBus( + bus.Broadcast, "GetComponentOfType", type_id + ) + assert ( + get_component_of_type_outcome.IsSuccess() + ), f"Failure: Level does not have component:'{component.get_component_name()}'" + component.id = get_component_of_type_outcome.GetValue() + component_list.append(component) + + return component_list + + @staticmethod + def has_component(component_name: str) -> bool: + """ + Used to verify if the level has the specified component + :param component_name: Name of component to check for + :return: True, if level has specified component. Else, False + """ + type_ids = EditorLevelEntity.get_type_ids([component_name]) + return editor.EditorLevelComponentAPIBus(bus.Broadcast, "HasComponentOfType", type_ids[0]) + + @staticmethod + def count_components_of_type(component_name: str) -> int: + """ + Used to get a count of the specified level component attached to the level + :param component_name: Name of component to check for + :return: integer count of occurences of level component attached to level or zero if none are present + """ + type_ids = EditorLevelEntity.get_type_ids([component_name]) + return editor.EditorLevelComponentAPIBus(bus.Broadcast, "CountComponentsOfType", type_ids[0]) diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_editor_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_editor_utils.py index 19aa6e9d3c..5e3828ad02 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_editor_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_editor_utils.py @@ -46,6 +46,18 @@ def get_component_type_id(component_name): return component_type_id +def get_level_component_type_id(component_name): + """ + Gets the component_type_id from a given component name + :param component_name: String of component name to search for + :return component type ID + """ + type_ids_list = editor.EditorComponentAPIBus(bus.Broadcast, 'FindComponentTypeIdsByEntityType', [component_name], + entity.EntityType().Level) + component_type_id = type_ids_list[0] + return component_type_id + + def add_level_component(component_name): """ Adds the specified component to the Level Inspector @@ -145,6 +157,20 @@ def get_component_property_value(component, component_propertyPath): print(f'FAILURE: Could not get value from {component_propertyPath}') return None +def set_component_property_value(component, component_propertyPath, value): + """ + Given a component name and component property path, set component property value + :param component: Component object to act on. + :param componentPropertyPath: String of component property. (e.g. 'Settings|Visible') + :param value: new value for the variable being changed in the component + """ + componentPropertyObj = editor.EditorComponentAPIBus(bus.Broadcast, 'SetComponentProperty', component, + component_propertyPath, value) + if componentPropertyObj.IsSuccess(): + print(f'{component_propertyPath} set to {value}') + else: + print(f'FAILURE: Could not set value in {component_propertyPath}') + def get_property_tree(component): """ diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_test_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_test_utils.py index 510d1b1149..ee36a640f0 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_test_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/hydra_test_utils.py @@ -29,7 +29,7 @@ def teardown_editor(editor): def launch_and_validate_results(request, test_directory, editor, editor_script, expected_lines, unexpected_lines=[], halt_on_unexpected=False, run_python="--runpythontest", auto_test_mode=True, null_renderer=True, cfg_args=[], - timeout=300, log_file_name="Editor.log"): + timeout=300, log_file_name="Editor.log", enable_prefab_system=True): """ Runs the Editor with the specified script, and monitors for expected log lines. :param request: Special fixture providing information of the requesting test function. @@ -45,17 +45,22 @@ def launch_and_validate_results(request, test_directory, editor, editor_script, :param cfg_args: Additional arguments for CFG, such as LevelName. :param timeout: Length of time for test to run. Default is 60. :param log_file_name: Name of the log file created by the editor. Defaults to 'Editor.log' + :param enable_prefab_system: Flag to determine whether to use new prefab system or use deprecated slice system. Defaults to True. """ test_case = os.path.join(test_directory, editor_script) request.addfinalizer(lambda: teardown_editor(editor)) logger.debug("Running automated test: {}".format(editor_script)) editor.args.extend(["--skipWelcomeScreenDialog", "--regset=/Amazon/Settings/EnableSourceControl=false", - "--regset=/Amazon/Preferences/EnablePrefabSystem=false", run_python, test_case, + run_python, test_case, f"--pythontestcase={request.node.name}", "--runpythonargs", " ".join(cfg_args)]) if auto_test_mode: editor.args.extend(["--autotest_mode"]) if null_renderer: editor.args.extend(["-rhi=Null"]) + if enable_prefab_system: + editor.args.extend(["--regset=/Amazon/Preferences/EnablePrefabSystem=true"]) + else: + editor.args.extend(["--regset=/Amazon/Preferences/EnablePrefabSystem=false"]) with editor.start(): diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py index 10a6ab1ef4..76c2a42c0a 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/prefab_utils.py @@ -138,6 +138,14 @@ class PrefabInstance: self.container_entity = reparented_container_entity current_instance_prefab.instances.add(self) + def get_direct_child_entities(self): + """ + Returns the entities only contained in the current prefab instance. + This function does not return entities contained in other child instances + """ + return self.container_entity.get_children() + + # This is a helper class which contains some of the useful information about a prefab template. class Prefab: diff --git a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/utils.py b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/utils.py index 1b094b1cfa..f277148bc8 100644 --- a/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/utils.py +++ b/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools/editor_python_test_tools/utils.py @@ -14,7 +14,10 @@ from typing import Callable, Tuple import azlmbr import azlmbr.legacy.general as general +import azlmbr.multiplayer as multiplayer import azlmbr.debug +import ly_test_tools.environment.waiter as waiter +import ly_test_tools.environment.process_utils as process_utils class FailFast(Exception): @@ -31,6 +34,38 @@ class TestHelper: # JIRA: SPEC-2880 # general.idle_wait_frames(1) + @staticmethod + def create_level(level_name: str) -> bool: + """ + :param level_name: The name of the level to be created + :return: True if ECreateLevelResult returns 0, False otherwise with logging to report reason + """ + Report.info(f"Creating level {level_name}") + + # Use these hardcoded values to pass expected values for old terrain system until new create_level API is + # available + heightmap_resolution = 1024 + heightmap_meters_per_pixel = 1 + terrain_texture_resolution = 4096 + use_terrain = False + + result = general.create_level_no_prompt(level_name, heightmap_resolution, heightmap_meters_per_pixel, + terrain_texture_resolution, use_terrain) + + # Result codes are ECreateLevelResult defined in CryEdit.h + if result == 1: + Report.info(f"{level_name} level already exists") + elif result == 2: + Report.info("Failed to create directory") + elif result == 3: + Report.info("Directory length is too long") + elif result != 0: + Report.info("Unknown error, failed to create level") + else: + Report.info(f"{level_name} level created successfully") + + return result == 0 + @staticmethod def open_level(directory : str, level : str): # type: (str, str) -> None @@ -53,8 +88,7 @@ class TestHelper: general.idle_wait_frames(200) @staticmethod - def enter_game_mode(msgtuple_success_fail : Tuple[str, str]): - # type: (tuple) -> None + def enter_game_mode(msgtuple_success_fail: Tuple[str, str]) -> None: """ :param msgtuple_success_fail: The tuple with the expected/unexpected messages for entering game mode. @@ -66,6 +100,55 @@ class TestHelper: TestHelper.wait_for_condition(lambda : general.is_in_game_mode(), 1.0) Report.critical_result(msgtuple_success_fail, general.is_in_game_mode()) + @staticmethod + def multiplayer_enter_game_mode(msgtuple_success_fail: Tuple[str, str], sv_default_player_spawn_asset: str) -> None: + """ + :param msgtuple_success_fail: The tuple with the expected/unexpected messages for entering game mode. + :param sv_default_player_spawn_asset: The path to the network player prefab that will be automatically spawned upon entering gamemode. The engine default is "prefabs/player.network.spawnable" + + :return: None + """ + + # looks for an expected line in a list of tracers lines + # lines: the tracer list of lines to search. options are section_tracer.warnings, section_tracer.errors, section_tracer.asserts, section_tracer.prints + # return: true if the line is found, otherwise false + def find_expected_line(expected_line, lines): + found_lines = [printInfo.message.strip() for printInfo in lines] + return expected_line in found_lines + + def wait_for_critical_expected_line(expected_line, lines, time_out): + TestHelper.wait_for_condition(lambda : find_expected_line(expected_line, lines), time_out) + Report.critical_result(("Found expected line: " + expected_line, "Failed to find expected line: " + expected_line), find_expected_line(expected_line, lines)) + + def wait_for_critical_unexpected_line(unexpected_line, lines, time_out): + TestHelper.wait_for_condition(lambda : find_expected_line(unexpected_line, lines), time_out) + Report.critical_result(("Unexpected line not found: " + unexpected_line, "Unexpected line found: " + unexpected_line), not find_expected_line(unexpected_line, lines)) + + + Report.info("Entering game mode") + if sv_default_player_spawn_asset : + general.set_cvar("sv_defaultPlayerSpawnAsset", sv_default_player_spawn_asset) + + with Tracer() as section_tracer: + # enter game-mode. + # game-mode in multiplayer will also launch ServerLauncher.exe and connect to the editor + multiplayer.PythonEditorFuncs_enter_game_mode() + + # make sure the server launcher binary exists + wait_for_critical_unexpected_line("LaunchEditorServer failed! The ServerLauncher binary is missing!", section_tracer.errors, 0.5) + + # make sure the server launcher is running + waiter.wait_for(lambda: process_utils.process_exists("AutomatedTesting.ServerLauncher", ignore_extensions=True), timeout=5.0, exc=AssertionError("AutomatedTesting.ServerLauncher has NOT launched!"), interval=1.0) + + # make sure the editor connects to the editor-server and sends the level data packet + wait_for_critical_expected_line("Editor is sending the editor-server the level data packet.", section_tracer.prints, 5.0) + + # make sure the editor finally connects to the editor-server network simulation + wait_for_critical_expected_line("Editor-server ready. Editor has successfully connected to the editor-server's network simulation.", section_tracer.prints, 5.0) + + TestHelper.wait_for_condition(lambda : multiplayer.PythonEditorFuncs_is_in_game_mode(), 5.0) + Report.critical_result(msgtuple_success_fail, multiplayer.PythonEditorFuncs_is_in_game_mode()) + @staticmethod def exit_game_mode(msgtuple_success_fail : Tuple[str, str]): # type: (tuple) -> None diff --git a/AutomatedTesting/Gem/PythonTests/Multiplayer/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/Multiplayer/CMakeLists.txt new file mode 100644 index 0000000000..367de4da9a --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Multiplayer/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# 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 +# +# + +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) + ly_add_pytest( + NAME AutomatedTesting::MultiplayerTests_Sandbox + TEST_SUITE sandbox + TEST_SERIAL + PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Sandbox.py + RUNTIME_DEPENDENCIES + Legacy::Editor + AZ::AssetProcessor + AutomatedTesting.Assets + AutomatedTesting.ServerLauncher + COMPONENT + Multiplayer + ) +endif() diff --git a/AutomatedTesting/Gem/PythonTests/Multiplayer/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Multiplayer/TestSuite_Main.py new file mode 100644 index 0000000000..450c760786 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Multiplayer/TestSuite_Main.py @@ -0,0 +1,28 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT + +""" + +# This suite consists of all test cases that are under development and have not been verified yet. +# Once they are verified, please move them to TestSuite_Active.py + +import pytest +import os +import sys + + +sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../automatedtesting_shared') + +from base import TestAutomationBase + +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.parametrize("launcher_platform", ['windows_editor']) +class TestAutomation(TestAutomationBase): + def _run_prefab_test(self, request, workspace, editor, test_module, batch_mode=True, autotest_mode=True): + self._run_test(request, workspace, editor, test_module, + batch_mode=batch_mode, + autotest_mode=autotest_mode) + diff --git a/AutomatedTesting/Gem/PythonTests/Multiplayer/TestSuite_Sandbox.py b/AutomatedTesting/Gem/PythonTests/Multiplayer/TestSuite_Sandbox.py new file mode 100644 index 0000000000..8f50d4d36d --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Multiplayer/TestSuite_Sandbox.py @@ -0,0 +1,34 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT + +""" + +# This suite consists of all test cases that are under development and have not been verified yet. +# Once they are verified, please move them to TestSuite_Active.py + +import pytest +import os +import sys + + +sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../automatedtesting_shared') + +from base import TestAutomationBase + +@pytest.mark.SUITE_sandbox +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +@pytest.mark.parametrize("launcher_platform", ['windows_editor']) +class TestAutomation(TestAutomationBase): + def _run_prefab_test(self, request, workspace, editor, test_module, batch_mode=True, autotest_mode=True): + self._run_test(request, workspace, editor, test_module, + batch_mode=batch_mode, + autotest_mode=autotest_mode) + + ## Seems to be flaky, need to investigate + def test_Multiplayer_AutoComponent_NetworkInput(self, request, workspace, editor, launcher_platform): + from .tests import Multiplayer_AutoComponent_NetworkInput as test_module + self._run_prefab_test(request, workspace, editor, test_module) + diff --git a/AutomatedTesting/Gem/PythonTests/Multiplayer/__init__.py b/AutomatedTesting/Gem/PythonTests/Multiplayer/__init__.py new file mode 100644 index 0000000000..f5193b300e --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Multiplayer/__init__.py @@ -0,0 +1,6 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" diff --git a/AutomatedTesting/Gem/PythonTests/Multiplayer/tests/Multiplayer_AutoComponent_NetworkInput.py b/AutomatedTesting/Gem/PythonTests/Multiplayer/tests/Multiplayer_AutoComponent_NetworkInput.py new file mode 100644 index 0000000000..7b56213313 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Multiplayer/tests/Multiplayer_AutoComponent_NetworkInput.py @@ -0,0 +1,115 @@ +""" +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 +""" + + +# Test Case Title : Check that network input can be created, received by the authority, and processed + + +# fmt: off +class Tests(): + enter_game_mode = ("Entered game mode", "Failed to enter game mode") + exit_game_mode = ("Exited game mode", "Couldn't exit game mode") + find_network_player = ("Found network player", "Couldn't find network player") + found_lines = ("Expected log lines were found", "Expected log lines were not found") + found_unexpected_lines = ("Unexpected log lines were not found", "Unexpected log lines were found") +# fmt: on + + +def Multiplayer_AutoComponent_NetworkInput(): + r""" + Summary: + Runs a test to make sure that network input can be sent from the autonomous player, received by the authority, and processed + + Level Description: + - Dynamic + 1. Although the level is empty, when the server and editor connect the server will spawn and replicate the player network prefab. + a. The player network prefab has a NetworkTestPlayerComponent.AutoComponent and a script canvas attached which will listen for the CreateInput and ProcessInput events. + Print logs occur upon triggering the CreateInput and ProcessInput events along with their values; we are testing to make sure the expected events are values are recieved. + - Static + 1. This is an empty level. All the logic occurs on the Player.network.spawnable (see the above Dynamic description) + + + Expected Outcome: + We should see editor logs stating that network input has been created and processed. + However, if the script receives unexpected values for the Process event we will see print logs for bad data as well. + + :return: + """ + import azlmbr.legacy.general as general + from editor_python_test_tools.utils import Report + from editor_python_test_tools.utils import Tracer + + from editor_python_test_tools.utils import TestHelper as helper + from ly_remote_console.remote_console_commands import RemoteConsole as RemoteConsole + + + def find_expected_line(expected_line): + found_lines = [printInfo.message.strip() for printInfo in section_tracer.prints] + return expected_line in found_lines + + def find_unexpected_line(expected_line): + return not find_expected_line(expected_line) + + unexpected_lines = [ + 'AutoComponent_NetworkInput received bad fwdback!', + 'AutoComponent_NetworkInput received bad leftright!', + + ] + expected_lines = [ + 'AutoComponent_NetworkInput ProcessInput called!', + 'AutoComponent_NetworkInput CreateInput called!', + ] + + expected_lines_server = [ + '(Script) - AutoComponent_NetworkInput ProcessInput called!', + ] + + level_name = "AutoComponent_NetworkInput" + player_prefab_name = "Player" + player_prefab_path = f"levels/multiplayer/{level_name}/{player_prefab_name}.network.spawnable" + + helper.init_idle() + + + # 1) Open Level + helper.open_level("Multiplayer", level_name) + + with Tracer() as section_tracer: + # 2) Enter game mode + helper.multiplayer_enter_game_mode(Tests.enter_game_mode, player_prefab_path.lower()) + + # 3) Make sure the network player was spawned + player_id = general.find_game_entity(player_prefab_name) + Report.critical_result(Tests.find_network_player, player_id.IsValid()) + + # 4) Check the editor logs for expected and unexpected log output + EXPECTEDLINE_WAIT_TIME_SECONDS = 1.0 + for expected_line in expected_lines : + helper.wait_for_condition(lambda: find_expected_line(expected_line), EXPECTEDLINE_WAIT_TIME_SECONDS) + Report.result(Tests.found_lines, find_expected_line(expected_line)) + + general.idle_wait_frames(1) + for unexpected_line in unexpected_lines : + Report.result(Tests.found_unexpected_lines, find_unexpected_line(unexpected_line)) + + # 5) Check the ServerLauncher logs for expected log output + # Since the editor has started a server launcher, the RemoteConsole with the default port=4600 will automatically be able to read the server logs + server_console = RemoteConsole() + server_console.start() + for line in expected_lines_server: + assert server_console.expect_log_line(line, EXPECTEDLINE_WAIT_TIME_SECONDS), f"Expected line not found: {line}" + server_console.stop() + + + # Exit game mode + helper.exit_game_mode(Tests.exit_game_mode) + + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(Multiplayer_AutoComponent_NetworkInput) diff --git a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_InDevelopment.py b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_InDevelopment.py index 9e3a9cb4ff..09d8f5635f 100755 --- a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_InDevelopment.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_InDevelopment.py @@ -26,158 +26,158 @@ class TestAutomation(TestAutomationBase): r"AutomatedTesting\Levels\Physics\Material_LibraryCrudOperationsReflectOnRagdollBones") def test_Material_LibraryCrudOperationsReflectOnRagdollBones(self, request, workspace, editor, launcher_platform): from .tests.material import Material_LibraryCrudOperationsReflectOnRagdollBones as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Material_RagdollBones(self, request, workspace, editor, launcher_platform): from .tests.material import Material_RagdollBones as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @fm.file_revert("c15308221_material_componentsinsyncwithlibrary.physmaterial", r"AutomatedTesting\Levels\Physics\Material_ComponentsInSyncWithLibrary") def test_Material_ComponentsInSyncWithLibrary(self, request, workspace, editor, launcher_platform): from .tests.material import Material_ComponentsInSyncWithLibrary as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) # BUG: LY-107723") def test_ScriptCanvas_SetKinematicTargetTransform(self, request, workspace, editor, launcher_platform): from .tests.script_canvas import ScriptCanvas_SetKinematicTargetTransform as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) # Failing, PhysXTerrain @fm.file_revert("c4925579_material_addmodifydeleteonterrain.physmaterial", r"AutomatedTesting\Levels\Physics\Material_LibraryCrudOperationsReflectOnTerrain") def test_Material_LibraryCrudOperationsReflectOnTerrain(self, request, workspace, editor, launcher_platform): from .tests.material import Material_LibraryCrudOperationsReflectOnTerrain as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) # Failing, PhysXTerrain def test_Terrain_TerrainTexturePainterWorks(self, request, workspace, editor, launcher_platform): from .tests.terrain import Terrain_TerrainTexturePainterWorks as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) # Failing, PhysXTerrain def test_Material_CanBeAssignedToTerrain(self, request, workspace, editor, launcher_platform): from .tests.material import Material_CanBeAssignedToTerrain as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) # Failing, PhysXTerrain def test_Material_DefaultLibraryConsistentOnAllFeatures(self, request, workspace, editor, launcher_platform): from .tests.material import Material_DefaultLibraryConsistentOnAllFeatures as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) # Failing, PhysXTerrain @fm.file_revert("all_ones_1.physmaterial", r"AutomatedTesting\Levels\Physics\Material_DefaultMaterialLibraryChangesWork") @fm.file_override("default.physxconfiguration", "Material_DefaultMaterialLibraryChangesWork.physxconfiguration", "AutomatedTesting") def test_Material_DefaultMaterialLibraryChangesWork(self, request, workspace, editor, launcher_platform): from .tests.material import Material_DefaultMaterialLibraryChangesWork as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Collider_SameCollisionGroupSameLayerCollide(self, request, workspace, editor, launcher_platform): from .tests.collider import Collider_SameCollisionGroupSameLayerCollide as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Ragdoll_OldRagdollSerializationNoErrors(self, request, workspace, editor, launcher_platform): from .tests.ragdoll import Ragdoll_OldRagdollSerializationNoErrors as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @fm.file_override("default.physxconfiguration", "ScriptCanvas_OverlapNode.physxconfiguration") def test_ScriptCanvas_OverlapNode(self, request, workspace, editor, launcher_platform): from .tests.script_canvas import ScriptCanvas_OverlapNode as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Material_StaticFriction(self, request, workspace, editor, launcher_platform): from .tests.material import Material_StaticFriction as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @fm.file_revert("c4888315_material_addmodifydeleteoncollider.physmaterial", r"AutomatedTesting\Levels\Physics\Material_LibraryCrudOperationsReflectOnCollider") def test_Material_LibraryCrudOperationsReflectOnCollider(self, request, workspace, editor, launcher_platform): from .tests.material import Material_LibraryCrudOperationsReflectOnCollider as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @fm.file_revert("c15563573_material_addmodifydeleteoncharactercontroller.physmaterial", r"AutomatedTesting\Levels\Physics\Material_LibraryCrudOperationsReflectOnCharacterController") def test_Material_LibraryCrudOperationsReflectOnCharacterController(self, request, workspace, editor, launcher_platform): from .tests.material import Material_LibraryCrudOperationsReflectOnCharacterController as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @fm.file_revert("c4888315_material_addmodifydeleteoncollider.physmaterial", r"AutomatedTesting\Levels\Physics\Material_LibraryCrudOperationsReflectOnCollider") def test_Material_LibraryCrudOperationsReflectOnCollider(self, request, workspace, editor, launcher_platform): from .tests.material import Material_LibraryCrudOperationsReflectOnCollider as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @fm.file_revert("c15563573_material_addmodifydeleteoncharactercontroller.physmaterial", r"AutomatedTesting\Levels\Physics\Material_LibraryCrudOperationsReflectOnCharacterController") def test_Material_LibraryCrudOperationsReflectOnCharacterController(self, request, workspace, editor, launcher_platform): from .tests.material import Material_LibraryCrudOperationsReflectOnCharacterController as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @fm.file_revert("c4044455_material_librarychangesinstantly.physmaterial", r"AutomatedTesting\Levels\Physics\C4044455_Material_LibraryChangesInstantly") def test_Material_LibraryChangesReflectInstantly(self, request, workspace, editor, launcher_platform): from .tests.material import Material_LibraryChangesReflectInstantly as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @fm.file_revert("Material_LibraryUpdatedAcrossLevels.physmaterial", r"AutomatedTesting\Levels\Physics\Material_LibraryUpdatedAcrossLevels") def test_Material_LibraryUpdatedAcrossLevels(self, request, workspace, editor, launcher_platform): from .tests.material import Material_LibraryUpdatedAcrossLevels as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_RigidBody_LinearDampingAffectsMotion(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_LinearDampingAffectsMotion as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Terrain_CollisionAgainstRigidBody(self, request, workspace, editor, launcher_platform): from .tests.terrain import Terrain_CollisionAgainstRigidBody as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ShapeCollider_CylinderShapeCollides(self, request, workspace, editor, launcher_platform): from .tests.collider import ShapeCollider_CylinderShapeCollides as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Physics_WorldBodyBusWorksOnEditorComponents(self, request, workspace, editor, launcher_platform): from .tests import Physics_WorldBodyBusWorksOnEditorComponents as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Collider_PxMeshErrorIfNoMesh(self, request, workspace, editor, launcher_platform): from .tests.collider import Collider_PxMeshErrorIfNoMesh as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ForceRegion_ImpulsesBoxShapedRigidBody(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_ImpulsesBoxShapedRigidBody as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Terrain_SpawnSecondTerrainComponentWarning(self, request, workspace, editor, launcher_platform): from .tests.terrain import Terrain_SpawnSecondTerrainComponentWarning as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Terrain_AddPhysTerrainComponent(self, request, workspace, editor, launcher_platform): from .tests.terrain import Terrain_AddPhysTerrainComponent as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Terrain_CanAddMultipleTerrainComponents(self, request, workspace, editor, launcher_platform): from .tests.terrain import Terrain_CanAddMultipleTerrainComponents as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Terrain_MultipleTerrainComponentsWarning(self, request, workspace, editor, launcher_platform): from .tests.terrain import Terrain_MultipleTerrainComponentsWarning as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Terrain_MultipleTerrainComponentsWarning(self, request, workspace, editor, launcher_platform): from .tests.terrain import Terrain_MultipleTerrainComponentsWarning as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ForceRegion_HighValuesDirectionAxesWorkWithNoError(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_HighValuesDirectionAxesWorkWithNoError as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Terrain_MultipleResolutionsValid(self, request, workspace, editor, launcher_platform): from .tests.terrain import Terrain_MultipleResolutionsValid as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ForceRegion_SmallMagnitudeDeviationOnLargeForces(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_SmallMagnitudeDeviationOnLargeForces as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) diff --git a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main.py index b64fbb5656..07ae5f6ca0 100644 --- a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main.py @@ -30,33 +30,33 @@ class TestAutomation(TestAutomationBase): def test_RigidBody_EnablingGravityWorksUsingNotificationsPoC(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_EnablingGravityWorksUsingNotificationsPoC as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_LocalSpaceForceOnRigidBodies(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_LocalSpaceForceOnRigidBodies as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config @fm.file_override('physxsystemconfiguration.setreg','Material_DynamicFriction.setreg_override', 'AutomatedTesting/Registry', search_subdirs=True) def test_Material_DynamicFriction(self, request, workspace, editor, launcher_platform): from .tests.material import Material_DynamicFriction as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_Collider_SameCollisionGroupDiffLayersCollide(self, request, workspace, editor, launcher_platform): from .tests.collider import Collider_SameCollisionGroupDiffLayersCollide as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_CharacterController_SwitchLevels(self, request, workspace, editor, launcher_platform): from .tests.character_controller import CharacterController_SwitchLevels as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Ragdoll_AddPhysxRagdollComponentWorks(self, request, workspace, editor, launcher_platform): from .tests.ragdoll import Ragdoll_AddPhysxRagdollComponentWorks as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ScriptCanvas_MultipleRaycastNode(self, request, workspace, editor, launcher_platform): @@ -64,31 +64,43 @@ class TestAutomation(TestAutomationBase): # Fixme: This test previously relied on unexpected lines log reading with is now not supported. # Now the log reading must be done inside the test, preferably with the Tracer() utility # unexpected_lines = ["Assert"] + test_module.Lines.unexpected - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config @fm.file_override('physxsystemconfiguration.setreg','Collider_DiffCollisionGroupDiffCollidingLayersNotCollide.setreg_override', 'AutomatedTesting/Registry', search_subdirs=True) def test_Collider_DiffCollisionGroupDiffCollidingLayersNotCollide(self, request, workspace, editor, launcher_platform): from .tests.collider import Collider_DiffCollisionGroupDiffCollidingLayersNotCollide as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_Joints_HingeLeadFollowerCollide(self, request, workspace, editor, launcher_platform): from .tests.joints import Joints_HingeLeadFollowerCollide as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_Collider_PxMeshConvexMeshCollides(self, request, workspace, editor, launcher_platform): from .tests.collider import Collider_PxMeshConvexMeshCollides as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ShapeCollider_CylinderShapeCollides(self, request, workspace, editor, launcher_platform): from .tests.shape_collider import ShapeCollider_CylinderShapeCollides as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_C15425929_Undo_Redo(self, request, workspace, editor, launcher_platform): from .tests import Physics_UndoRedoWorksOnEntityWithPhysComponents as test_module - self._run_test(request, workspace, editor, test_module) \ No newline at end of file + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) + + @pytest.mark.GROUP_tick + @pytest.mark.xfail(reason="Test still under development.") + def test_Tick_InterpolatedRigidBodyMotionIsSmooth(self, request, workspace, editor, launcher_platform): + from .tests.tick import Tick_InterpolatedRigidBodyMotionIsSmooth as test_module + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) + + @pytest.mark.GROUP_tick + @pytest.mark.xfail(reason="Test still under development.") + def test_Tick_CharacterGameplayComponentMotionIsSmooth(self, request, workspace, editor, launcher_platform): + from .tests.tick import Tick_CharacterGameplayComponentMotionIsSmooth as test_module + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) diff --git a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main_Optimized.py b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main_Optimized.py index 63d3b58249..b25b4340e8 100644 --- a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main_Optimized.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Main_Optimized.py @@ -53,6 +53,24 @@ class EditorSingleTest_WithFileOverrides(EditorSingleTest): for f in original_file_list: fm._restore_file(f, file_list[f]) +@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.") +@pytest.mark.SUITE_main +@pytest.mark.parametrize("launcher_platform", ['windows_editor']) +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +class TestAutomationWithPrefabSystemEnabled(EditorTestSuite): + + @staticmethod + def get_number_parallel_editors(): + return 16 + + class C4982801_PhysXColliderShape_CanBeSelected(EditorSharedTest): + from .tests.collider import Collider_BoxShapeEditing as test_module + + class C4982800_PhysXColliderShape_CanBeSelected(EditorSharedTest): + from .tests.collider import Collider_SphereShapeEditing as test_module + + class C4982802_PhysXColliderShape_CanBeSelected(EditorSharedTest): + from .tests.collider import Collider_CapsuleShapeEditing as test_module @pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.") @pytest.mark.SUITE_main @@ -60,6 +78,8 @@ class EditorSingleTest_WithFileOverrides(EditorSingleTest): @pytest.mark.parametrize("project", ["AutomatedTesting"]) class TestAutomation(EditorTestSuite): + enable_prefab_system = False + @staticmethod def get_number_parallel_editors(): return 16 @@ -286,15 +306,6 @@ class TestAutomation(EditorTestSuite): class C19723164_ShapeCollider_WontCrashEditor(EditorSharedTest): from .tests.shape_collider import ShapeCollider_LargeNumberOfShapeCollidersWontCrashEditor as test_module - class C4982800_PhysXColliderShape_CanBeSelected(EditorSharedTest): - from .tests.collider import Collider_SphereShapeEditting as test_module - - class C4982801_PhysXColliderShape_CanBeSelected(EditorSharedTest): - from .tests.collider import Collider_BoxShapeEditting as test_module - - class C4982802_PhysXColliderShape_CanBeSelected(EditorSharedTest): - from .tests.collider import Collider_CapsuleShapeEditting as test_module - class C12905528_ForceRegion_WithNonTriggerCollider(EditorSharedTest): from .tests.force_region import ForceRegion_WithNonTriggerColliderWarning as test_module # Fixme: expected_lines = ["[Warning] (PhysX Force Region) - Please ensure collider component marked as trigger exists in entity"] diff --git a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Periodic.py b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Periodic.py index e5dd9adbb9..6daf852708 100755 --- a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Periodic.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Periodic.py @@ -31,223 +31,223 @@ class TestAutomation(TestAutomationBase): @revert_physics_config def test_Terrain_NoPhysTerrainComponentNoCollision(self, request, workspace, editor, launcher_platform): from .tests.terrain import Terrain_NoPhysTerrainComponentNoCollision as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_RigidBody_InitialLinearVelocity(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_InitialLinearVelocity as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_RigidBody_StartGravityEnabledWorks(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_StartGravityEnabledWorks as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_RigidBody_KinematicModeWorks(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_KinematicModeWorks as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_LinearDampingForceOnRigidBodies(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_LinearDampingForceOnRigidBodies as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_SimpleDragForceOnRigidBodies(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_SimpleDragForceOnRigidBodies as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_CapsuleShapedForce(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_CapsuleShapedForce as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_ImpulsesCapsuleShapedRigidBody(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_ImpulsesCapsuleShapedRigidBody as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_RigidBody_MomentOfInertiaManualSetting(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_MomentOfInertiaManualSetting as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_RigidBody_COM_ManualSettingWorks(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_COM_ManualSettingWorks as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_RigidBody_AddRigidBodyComponent(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_AddRigidBodyComponent as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_SplineForceOnRigidBodies(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_SplineForceOnRigidBodies as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config @fm.file_override('physxsystemconfiguration.setreg','Material_RestitutionCombine.setreg_override', 'AutomatedTesting/Registry', search_subdirs=True) def test_Material_RestitutionCombine(self, request, workspace, editor, launcher_platform): from .tests.material import Material_RestitutionCombine as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config @fm.file_override('physxsystemconfiguration.setreg','Material_FrictionCombine.setreg_override', 'AutomatedTesting/Registry', search_subdirs=True) def test_Material_FrictionCombine(self, request, workspace, editor, launcher_platform): from .tests.material import Material_FrictionCombine as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_Collider_ColliderPositionOffset(self, request, workspace, editor, launcher_platform): from .tests.collider import Collider_ColliderPositionOffset as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_RigidBody_AngularDampingAffectsRotation(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_AngularDampingAffectsRotation as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_Physics_VerifyColliderRigidBodyMeshAndTerrainWorkTogether(self, request, workspace, editor, launcher_platform): from .tests import Physics_VerifyColliderRigidBodyMeshAndTerrainWorkTogether as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_MultipleForcesInSameComponentCombineForces(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_MultipleForcesInSameComponentCombineForces as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_ImpulsesPxMeshShapedRigidBody(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_ImpulsesPxMeshShapedRigidBody as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ScriptCanvas_TriggerEvents(self, request, workspace, editor, launcher_platform): from .tests.script_canvas import ScriptCanvas_TriggerEvents as test_module # FIXME: expected_lines = test_module.LogLines.expected_lines - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_ZeroPointForceDoesNothing(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_ZeroPointForceDoesNothing as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_ZeroWorldSpaceForceDoesNothing(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_ZeroWorldSpaceForceDoesNothing as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_ZeroLinearDampingDoesNothing(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_ZeroLinearDampingDoesNothing as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_MovingForceRegionChangesNetForce(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_MovingForceRegionChangesNetForce as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ScriptCanvas_CollisionEvents(self, request, workspace, editor, launcher_platform): from .tests.script_canvas import ScriptCanvas_CollisionEvents as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_DirectionHasNoAffectOnTotalForce(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_DirectionHasNoAffectOnTotalForce as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_RigidBody_StartAsleepWorks(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_StartAsleepWorks as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_SliceFileInstantiates(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_SliceFileInstantiates as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_ZeroLocalSpaceForceDoesNothing(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_ZeroLocalSpaceForceDoesNothing as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_ZeroSimpleDragForceDoesNothing(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_ZeroSimpleDragForceDoesNothing as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_RigidBody_COM_ComputingWorks(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_COM_ComputingWorks as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_RigidBody_MassDifferentValuesWorks(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_MassDifferentValuesWorks as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config @fm.file_override('physxsystemconfiguration.setreg','Material_RestitutionCombinePriorityOrder.setreg_override', 'AutomatedTesting/Registry', search_subdirs=True) def test_Material_RestitutionCombinePriorityOrder(self, request, workspace, editor, launcher_platform): from .tests.material import Material_RestitutionCombinePriorityOrder as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_SplineRegionWithModifiedTransform(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_SplineRegionWithModifiedTransform as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ScriptCanvas_ShapeCast(self, request, workspace, editor, launcher_platform): from .tests.script_canvas import ScriptCanvas_ShapeCast as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_RigidBody_InitialAngularVelocity(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_InitialAngularVelocity as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_ZeroSplineForceDoesNothing(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_ZeroSplineForceDoesNothing as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_Physics_DynamicSliceWithPhysNotSpawnsStaticSlice(self, request, workspace, editor, launcher_platform): from .tests import Physics_DynamicSliceWithPhysNotSpawnsStaticSlice as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_PositionOffset(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_PositionOffset as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config @fm.file_override('physxsystemconfiguration.setreg','Material_FrictionCombinePriorityOrder.setreg_override', 'AutomatedTesting/Registry', search_subdirs=True) def test_Material_FrictionCombinePriorityOrder(self, request, workspace, editor, launcher_platform): from .tests.material import Material_FrictionCombinePriorityOrder as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.xfail( reason="Something with the CryRenderer disabling is causing this test to fail now.") @revert_physics_config def test_Ragdoll_LevelSwitchDoesNotCrash(self, request, workspace, editor, launcher_platform): from .tests.ragdoll import Ragdoll_LevelSwitchDoesNotCrash as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_MultipleComponentsCombineForces(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_MultipleComponentsCombineForces as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) # Marking the test as an expected failure due to sporadic failure on Automated Review: LYN-2580 # The test still runs, but a failure of the test doesn't result in the test run failing @@ -258,102 +258,102 @@ class TestAutomation(TestAutomationBase): 'AutomatedTesting/Registry', search_subdirs=True) def test_Material_PerFaceMaterialGetsCorrectMaterial(self, request, workspace, editor, launcher_platform): from .tests.material import Material_PerFaceMaterialGetsCorrectMaterial as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.xfail( reason="This test will sometimes fail as the ball will continue to roll before the timeout is reached.") @revert_physics_config def test_RigidBody_SleepWhenBelowKineticThreshold(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_SleepWhenBelowKineticThreshold as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_RigidBody_COM_NotIncludesTriggerShapes(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_COM_NotIncludesTriggerShapes as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_Material_NoEffectIfNoColliderShape(self, request, workspace, editor, launcher_platform): from .tests.material import Material_NoEffectIfNoColliderShape as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_Collider_TriggerPassThrough(self, request, workspace, editor, launcher_platform): from .tests.collider import Collider_TriggerPassThrough as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_RigidBody_SetGravityWorks(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_SetGravityWorks as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config @fm.file_override('physxsystemconfiguration.setreg','Material_CharacterController.setreg_override', 'AutomatedTesting/Registry', search_subdirs=True) def test_Material_CharacterController(self, request, workspace, editor, launcher_platform): from .tests.material import Material_CharacterController as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_Material_EmptyLibraryUsesDefault(self, request, workspace, editor, launcher_platform): from .tests.material import Material_EmptyLibraryUsesDefault as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_NoQuiverOnHighLinearDampingForce(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_NoQuiverOnHighLinearDampingForce as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_RigidBody_ComputeInertiaWorks(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_ComputeInertiaWorks as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ScriptCanvas_PostPhysicsUpdate(self, request, workspace, editor, launcher_platform): from .tests.script_canvas import ScriptCanvas_PostPhysicsUpdate as test_module # Fixme: unexpected_lines = ["Assert"] + test_module.Lines.unexpected - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config @fm.file_override('physxsystemconfiguration.setreg','Collider_NoneCollisionGroupSameLayerNotCollide.setreg_override', 'AutomatedTesting/Registry', search_subdirs=True) def test_Collider_NoneCollisionGroupSameLayerNotCollide(self, request, workspace, editor, launcher_platform): from .tests.collider import Collider_NoneCollisionGroupSameLayerNotCollide as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config @fm.file_override('physxsystemconfiguration.setreg','Collider_SameCollisionGroupSameCustomLayerCollide.setreg_override', 'AutomatedTesting/Registry', search_subdirs=True) def test_Collider_SameCollisionGroupSameCustomLayerCollide(self, request, workspace, editor, launcher_platform): from .tests.collider import Collider_SameCollisionGroupSameCustomLayerCollide as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config @fm.file_override('physxdefaultsceneconfiguration.setreg','ScriptCanvas_PostUpdateEvent.setreg_override', 'AutomatedTesting/Registry', search_subdirs=True) def test_ScriptCanvas_PostUpdateEvent(self, request, workspace, editor, launcher_platform): from .tests.script_canvas import ScriptCanvas_PostUpdateEvent as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config @fm.file_override('physxsystemconfiguration.setreg','Material_Restitution.setreg_override', 'AutomatedTesting/Registry', search_subdirs=True) def test_Material_Restitution(self, request, workspace, editor, launcher_platform): from .tests.material import Material_Restitution as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config @fm.file_override('physxdefaultsceneconfiguration.setreg', 'ScriptCanvas_PreUpdateEvent.setreg_override', 'AutomatedTesting/Registry', search_subdirs=True) def test_ScriptCanvas_PreUpdateEvent(self, request, workspace, editor, launcher_platform): from .tests.script_canvas import ScriptCanvas_PreUpdateEvent as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_PxMeshShapedForce(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_PxMeshShapedForce as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) # Marking the Test as expected to fail using the xfail decorator due to sporadic failure on Automated Review: SPEC-3146 # The test still runs, but a failure of the test doesn't result in the test run failing @@ -361,173 +361,173 @@ class TestAutomation(TestAutomationBase): @revert_physics_config def test_RigidBody_MaxAngularVelocityWorks(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_MaxAngularVelocityWorks as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_Joints_HingeSoftLimitsConstrained(self, request, workspace, editor, launcher_platform): from .tests.joints import Joints_HingeSoftLimitsConstrained as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_Joints_BallSoftLimitsConstrained(self, request, workspace, editor, launcher_platform): from .tests.joints import Joints_BallSoftLimitsConstrained as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_Joints_BallLeadFollowerCollide(self, request, workspace, editor, launcher_platform): from .tests.joints import Joints_BallLeadFollowerCollide as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config @fm.file_override('physxsystemconfiguration.setreg','Collider_AddingNewGroupWorks.setreg_override', 'AutomatedTesting/Registry', search_subdirs=True) def test_Collider_AddingNewGroupWorks(self, request, workspace, editor, launcher_platform): from .tests.collider import Collider_AddingNewGroupWorks as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ShapeCollider_InactiveWhenNoShapeComponent(self, request, workspace, editor, launcher_platform): from .tests.shape_collider import ShapeCollider_InactiveWhenNoShapeComponent as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_Collider_CheckDefaultShapeSettingIsPxMesh(self, request, workspace, editor, launcher_platform): from .tests.collider import Collider_CheckDefaultShapeSettingIsPxMesh as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ShapeCollider_LargeNumberOfShapeCollidersWontCrashEditor(self, request, workspace, editor, launcher_platform): from .tests.shape_collider import ShapeCollider_LargeNumberOfShapeCollidersWontCrashEditor as test_module + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) + + @revert_physics_config + def test_Collider_SphereShapeEditing(self, request, workspace, editor, launcher_platform): + from .tests.collider import Collider_SphereShapeEditing as test_module self._run_test(request, workspace, editor, test_module) @revert_physics_config - def test_Collider_SphereShapeEditting(self, request, workspace, editor, launcher_platform): - from .tests.collider import Collider_SphereShapeEditting as test_module + def test_Collider_BoxShapeEditing(self, request, workspace, editor, launcher_platform): + from .tests.collider import Collider_BoxShapeEditing as test_module self._run_test(request, workspace, editor, test_module) @revert_physics_config - def test_Collider_BoxShapeEditting(self, request, workspace, editor, launcher_platform): - from .tests.collider import Collider_BoxShapeEditting as test_module - self._run_test(request, workspace, editor, test_module) - - @revert_physics_config - def test_Collider_CapsuleShapeEditting(self, request, workspace, editor, launcher_platform): - from .tests.collider import Collider_CapsuleShapeEditting as test_module + def test_Collider_CapsuleShapeEditing(self, request, workspace, editor, launcher_platform): + from .tests.collider import Collider_CapsuleShapeEditing as test_module self._run_test(request, workspace, editor, test_module) def test_ForceRegion_WithNonTriggerColliderWarning(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_WithNonTriggerColliderWarning as test_module # Fixme: expected_lines = ["[Warning] (PhysX Force Region) - Please ensure collider component marked as trigger exists in entity"] - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ForceRegion_WorldSpaceForceOnRigidBodies(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_WorldSpaceForceOnRigidBodies as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ForceRegion_PointForceOnRigidBodies(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_PointForceOnRigidBodies as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ForceRegion_SphereShapedForce(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_SphereShapedForce as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ForceRegion_RotationalOffset(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_RotationalOffset as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Material_LibraryClearingAssignsDefault(self, request, workspace, editor, launcher_platform): from .tests.material import Material_LibraryClearingAssignsDefault as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Collider_AddColliderComponent(self, request, workspace, editor, launcher_platform): from .tests.collider import Collider_AddColliderComponent as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.xfail( reason="This will fail due to this issue ATOM-15487.") def test_Collider_PxMeshAutoAssignedWhenModifyingRenderMeshComponent(self, request, workspace, editor, launcher_platform): from .tests.collider import Collider_PxMeshAutoAssignedWhenModifyingRenderMeshComponent as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Collider_PxMeshAutoAssignedWhenAddingRenderMeshComponent(self, request, workspace, editor, launcher_platform): from .tests.collider import Collider_PxMeshAutoAssignedWhenAddingRenderMeshComponent as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Collider_MultipleSurfaceSlots(self, request, workspace, editor, launcher_platform): from .tests.collider import Collider_MultipleSurfaceSlots as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Collider_PxMeshNotAutoAssignedWhenNoPhysicsFbx(self, request, workspace, editor, launcher_platform): from .tests.collider import Collider_PxMeshNotAutoAssignedWhenNoPhysicsFbx as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_RigidBody_EnablingGravityWorksPoC(self, request, workspace, editor, launcher_platform): from .tests.rigid_body import RigidBody_EnablingGravityWorksPoC as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config @fm.file_override('physxsystemconfiguration.setreg','Collider_CollisionGroupsWorkflow.setreg_override', 'AutomatedTesting/Registry', search_subdirs=True) def test_Collider_CollisionGroupsWorkflow(self, request, workspace, editor, launcher_platform): from .tests.collider import Collider_CollisionGroupsWorkflow as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_Collider_ColliderRotationOffset(self, request, workspace, editor, launcher_platform): from .tests.collider import Collider_ColliderRotationOffset as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ForceRegion_ParentChildForcesCombineForces(self, request, workspace, editor, launcher_platform): from .tests.force_region import ForceRegion_ParentChildForcesCombineForces as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_ShapeCollider_CanBeAddedWitNoWarnings(self, request, workspace, editor, launcher_platform): from .tests.shape_collider import ShapeCollider_CanBeAddedWitNoWarnings as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_Physics_UndoRedoWorksOnEntityWithPhysComponents(self, request, workspace, editor, launcher_platform): from .tests import Physics_UndoRedoWorksOnEntityWithPhysComponents as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Joints_Fixed2BodiesConstrained(self, request, workspace, editor, launcher_platform): from .tests.joints import Joints_Fixed2BodiesConstrained as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Joints_Hinge2BodiesConstrained(self, request, workspace, editor, launcher_platform): from .tests.joints import Joints_Hinge2BodiesConstrained as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Joints_Ball2BodiesConstrained(self, request, workspace, editor, launcher_platform): from .tests.joints import Joints_Ball2BodiesConstrained as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Joints_FixedBreakable(self, request, workspace, editor, launcher_platform): from .tests.joints import Joints_FixedBreakable as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Joints_HingeBreakable(self, request, workspace, editor, launcher_platform): from .tests.joints import Joints_HingeBreakable as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Joints_BallBreakable(self, request, workspace, editor, launcher_platform): from .tests.joints import Joints_BallBreakable as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Joints_HingeNoLimitsConstrained(self, request, workspace, editor, launcher_platform): from .tests.joints import Joints_HingeNoLimitsConstrained as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Joints_BallNoLimitsConstrained(self, request, workspace, editor, launcher_platform): from .tests.joints import Joints_BallNoLimitsConstrained as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Joints_GlobalFrameConstrained(self, request, workspace, editor, launcher_platform): from .tests.joints import Joints_GlobalFrameConstrained as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @revert_physics_config def test_Material_DefaultLibraryUpdatedAcrossLevels(self, request, workspace, editor, launcher_platform): @@ -537,7 +537,7 @@ class TestAutomation(TestAutomationBase): search_subdirs=True) def levels_before(self, request, workspace, editor, launcher_platform): from .tests.material import Material_DefaultLibraryUpdatedAcrossLevels_before as test_module_0 - self._run_test(request, workspace, editor, test_module_0) + self._run_test(request, workspace, editor, test_module_0, enable_prefab_system=False) # File override replaces the previous physxconfiguration file with another where the only difference is the default material library @fm.file_override("physxsystemconfiguration.setreg", @@ -546,7 +546,7 @@ class TestAutomation(TestAutomationBase): search_subdirs=True) def levels_after(self, request, workspace, editor, launcher_platform): from .tests.material import Material_DefaultLibraryUpdatedAcrossLevels_after as test_module_1 - self._run_test(request, workspace, editor, test_module_1) + self._run_test(request, workspace, editor, test_module_1, enable_prefab_system=False) levels_before(self, request, workspace, editor, launcher_platform) levels_after(self, request, workspace, editor, launcher_platform) \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Sandbox.py b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Sandbox.py index 81fdc747d5..9b104ccfcf 100644 --- a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Sandbox.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Sandbox.py @@ -32,7 +32,7 @@ class TestAutomation(TestAutomationBase): def test_ScriptCanvas_GetCollisionNameReturnsName(self, request, workspace, editor, launcher_platform): from .tests.script_canvas import ScriptCanvas_GetCollisionNameReturnsName as test_module # Fixme: expected_lines=["Layer Name: Right"] - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) ## Seems to be flaky, need to investigate def test_ScriptCanvas_GetCollisionNameReturnsNothingWhenHasToggledLayer(self, request, workspace, editor, launcher_platform): @@ -42,4 +42,4 @@ class TestAutomation(TestAutomationBase): # Fixme: for group in collision_groups: # Fixme: unexpected_lines.append(f"GroupName: {group}") # Fixme: expected_lines=["GroupName: "] - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) diff --git a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Utils.py b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Utils.py index 61c2816f50..3e00b684c0 100755 --- a/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Utils.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/TestSuite_Utils.py @@ -30,11 +30,11 @@ class TestUtils(TestAutomationBase): expected_lines = [] unexpected_lines = ["Assert"] - self._run_test(request, workspace, editor, physmaterial_editor_test_module, expected_lines, unexpected_lines) + self._run_test(request, workspace, editor, physmaterial_editor_test_module, expected_lines, unexpected_lines, enable_prefab_system=False) def test_UtilTest_Tracer_PicksErrorsAndWarnings(self, request, workspace, launcher_platform, editor): from .utils import UtilTest_Tracer_PicksErrorsAndWarnings as testcase_module - self._run_test(request, workspace, editor, testcase_module, [], []) + self._run_test(request, workspace, editor, testcase_module, [], [], enable_prefab_system=False) def test_FileManagement_FindingFiles(self, workspace, launcher_platform): """ @@ -263,4 +263,4 @@ class TestUtils(TestAutomationBase): expected_lines = [] unexpected_lines = ["Assert"] - self._run_test(request, workspace, editor, test_module, expected_lines, unexpected_lines) + self._run_test(request, workspace, editor, test_module, expected_lines, unexpected_lines, enable_prefab_system=False) diff --git a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_BoxShapeEditting.py b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_BoxShapeEditing.py similarity index 97% rename from AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_BoxShapeEditting.py rename to AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_BoxShapeEditing.py index 68ff0b4edc..a6730c8559 100644 --- a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_BoxShapeEditting.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_BoxShapeEditing.py @@ -19,7 +19,7 @@ class Tests(): # fmt: on -def Collider_BoxShapeEditting(): +def Collider_BoxShapeEditing(): """ Summary: Adding PhysX Collider and Shape components to test entity, then attempting to modify the shape's dimensions @@ -73,7 +73,7 @@ def Collider_BoxShapeEditting(): helper.init_idle() # 1) Load the empty level - helper.open_level("Physics", "Base") + helper.open_level("", "Base") # 2) Create the test entity test_entity = Entity.create_editor_entity("Test Entity") @@ -102,4 +102,4 @@ def Collider_BoxShapeEditting(): if __name__ == "__main__": from editor_python_test_tools.utils import Report - Report.start_test(Collider_BoxShapeEditting) + Report.start_test(Collider_BoxShapeEditing) diff --git a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_CapsuleShapeEditting.py b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_CapsuleShapeEditing.py similarity index 97% rename from AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_CapsuleShapeEditting.py rename to AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_CapsuleShapeEditing.py index 7df12c68f0..12435cc54a 100644 --- a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_CapsuleShapeEditting.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_CapsuleShapeEditing.py @@ -19,7 +19,7 @@ class Tests(): # fmt: on -def Collider_CapsuleShapeEditting(): +def Collider_CapsuleShapeEditing(): """ Summary: Adding PhysX Collider and Shape components to test entity, then attempting to modify the shape's dimensions @@ -74,7 +74,7 @@ def Collider_CapsuleShapeEditting(): helper.init_idle() # 1) Load the empty level - helper.open_level("Physics", "Base") + helper.open_level("", "Base") # 2) Create the test entity test_entity = Entity.create_editor_entity("Test Entity") @@ -102,4 +102,4 @@ def Collider_CapsuleShapeEditting(): if __name__ == "__main__": from editor_python_test_tools.utils import Report - Report.start_test(Collider_CapsuleShapeEditting) + Report.start_test(Collider_CapsuleShapeEditing) diff --git a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_SphereShapeEditting.py b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_SphereShapeEditing.py similarity index 96% rename from AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_SphereShapeEditting.py rename to AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_SphereShapeEditing.py index bffd041d92..ef91235411 100644 --- a/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_SphereShapeEditting.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/tests/collider/Collider_SphereShapeEditing.py @@ -19,7 +19,7 @@ class Tests(): # fmt: on -def Collider_SphereShapeEditting(): +def Collider_SphereShapeEditing(): """ Summary: Adding PhysX Collider and Shape components to test entity, then attempting to modify the shape's dimensions @@ -57,7 +57,7 @@ def Collider_SphereShapeEditting(): helper.init_idle() # 1) Load the empty level - helper.open_level("Physics", "Base") + helper.open_level("", "Base") # 2) Create the test entity test_entity = Entity.create_editor_entity("Test Entity") @@ -90,4 +90,4 @@ def Collider_SphereShapeEditting(): if __name__ == "__main__": from editor_python_test_tools.utils import Report - Report.start_test(Collider_SphereShapeEditting) + Report.start_test(Collider_SphereShapeEditing) diff --git a/AutomatedTesting/Gem/PythonTests/Physics/tests/tick/Tick_CharacterGameplayComponentMotionIsSmooth.py b/AutomatedTesting/Gem/PythonTests/Physics/tests/tick/Tick_CharacterGameplayComponentMotionIsSmooth.py new file mode 100644 index 0000000000..fe718d7247 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Physics/tests/tick/Tick_CharacterGameplayComponentMotionIsSmooth.py @@ -0,0 +1,99 @@ +""" +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 + +Test Case Title : Verify that an entity with a character gameplay component moves smoothly. +""" + + +# fmt: off +class Tests(): + create_entity = ("Created test entity", "Failed to create test entity") + character_controller_added = ("Added PhysX Character Controller component", "Failed to add PhysX Character Controller component") + character_gameplay_added = ("Added PhysX Character Gameplay component", "Failed to add PhysX Character Gameplay component") + enter_game_mode = ("Entered game mode", "Failed to enter game mode") + exit_game_mode = ("Exited game mode", "Failed to exit game mode") + character_motion_smooth = ("Character motion passed smoothness threshold", "Failed to meet smoothness threshold for character motion") +# fmt: on + + +def Tick_CharacterGameplayComponentMotionIsSmooth(): + """ + Summary: + Create entity with PhysX Character Controller and PhysX Character Gameplay components. + Verify that the motion of the character controller under gravity is smooth. + + Expected Behavior: + 1) The motion of the character controller under gravity is a smooth curve, rather than an erratic/jittery movement. + + Test Steps: + 1) Load the empty level + 2) Create an entity + 3) Add a PhysX Character Controller Component and PhysX Character Gameplay component + 4) Enter game mode and collect data for the character controller's z co-ordinate and the time values for a series of frames + 5) Check if the motion of the character controller was sufficiently smooth + + :return: None + """ + # imports + import os + import azlmbr.legacy.general as general + import azlmbr.math as math + from editor_python_test_tools.editor_entity_utils import EditorEntity as Entity + from editor_python_test_tools.utils import Report + from editor_python_test_tools.utils import TestHelper as helper + from editor_python_test_tools.asset_utils import Asset + import numpy as np + + # constants + COEFFICIENT_OF_DETERMINATION_THRESHOLD = 1 - 1e-4 # curves with values below this are not considered sufficiently smooth + + helper.init_idle() + # 1) Load the empty level + helper.open_level("", "Base") + + # 2) Create an entity + test_entity = Entity.create_editor_entity("test_entity") + Report.result(Tests.create_entity, test_entity.id.IsValid()) + + azlmbr.components.TransformBus( + azlmbr.bus.Event, "SetWorldTranslation", test_entity.id, math.Vector3(0.0, 0.0, 0.0)) + + # 3) Add character controller and character gameplay components + character_controller_component = test_entity.add_component("PhysX Character Controller") + Report.result(Tests.character_controller_added, test_entity.has_component("PhysX Character Controller")) + character_gameplay_component = test_entity.add_component("PhysX Character Gameplay") + Report.result(Tests.character_gameplay_added, test_entity.has_component("PhysX Character Gameplay")) + + # 4) Enter game mode and collect data for the rigid body's z co-ordinate and the time values for a series of frames + t = [] + z = [] + helper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + game_entity_id = general.find_game_entity("test_entity") + for frame in range(100): + t.append(azlmbr.components.TickRequestBus(azlmbr.bus.Broadcast, "GetTimeAtCurrentTick").GetSeconds()) + z.append(azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", game_entity_id)) + general.idle_wait_frames(1) + helper.exit_game_mode(Tests.exit_game_mode) + + # 5) Test that the z vs t curve is sufficiently smooth (if the interpolation is not working well, the curve will be less smooth) + # normalize the t and z data + t = np.array(t) - np.mean(t) + z = np.array(z) - np.mean(z) + # fit a polynomial to the z vs t curve + fit = np.poly1d(np.polyfit(t, z, 4)) + residual = fit(t) - z + # calculate the coefficient of determination (a measure of how closely the polynomial curve fits the data) + # if the coefficient is very close to 1, then the curve fits the data very well, suggesting that the rigid body motion is smooth + # if the coefficient is significantly less than 1, then the z values vary more erratically relative to the smooth curve, + # indicating that the motion of the rigid body is not smooth + coefficient_of_determination = (1 - np.sum(residual * residual) / np.sum(z * z)) + Report.result(Tests.character_motion_smooth, bool(coefficient_of_determination > COEFFICIENT_OF_DETERMINATION_THRESHOLD)) + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(Tick_CharacterGameplayComponentMotionIsSmooth) diff --git a/AutomatedTesting/Gem/PythonTests/Physics/tests/tick/Tick_InterpolatedRigidBodyMotionIsSmooth.py b/AutomatedTesting/Gem/PythonTests/Physics/tests/tick/Tick_InterpolatedRigidBodyMotionIsSmooth.py new file mode 100644 index 0000000000..19e79355d9 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Physics/tests/tick/Tick_InterpolatedRigidBodyMotionIsSmooth.py @@ -0,0 +1,98 @@ +""" +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 + +Test Case Title : Verify that a rigid body with "Interpolate motion" option selected moves smoothly. +""" + + +# fmt: off +class Tests(): + create_entity = ("Created test entity", "Failed to create test entity") + rigid_body_added = ("Added PhysX Rigid Body component", "Failed to add PhysX Rigid Body component") + enter_game_mode = ("Entered game mode", "Failed to enter game mode") + exit_game_mode = ("Exited game mode", "Failed to exit game mode") + rigid_body_smooth = ("Rigid body motion passed smoothness threshold", "Failed to meet smoothness threshold for rigid body motion") +# fmt: on + + +def Tick_InterpolatedRigidBodyMotionIsSmooth(): + """ + Summary: + Create entity with PhysX Rigid Body component and turn on the Interpolate motion setting. + Verify that the position of the rigid body varies smoothly with time. + + Expected Behavior: + 1) The motion of the rigid body under gravity is a smooth curve, rather than an erratic/jittery movement. + + Test Steps: + 1) Load the empty level + 2) Create an entity + 3) Add rigid body component + 4) Enter game mode and collect data for the rigid body's z co-ordinate and the time values for a series of frames + 5) Check if the motion of the rigid body was sufficiently smooth + + :return: None + """ + # imports + import os + import azlmbr.legacy.general as general + import azlmbr.math as math + from editor_python_test_tools.editor_entity_utils import EditorEntity as Entity + from editor_python_test_tools.utils import Report + from editor_python_test_tools.utils import TestHelper as helper + from editor_python_test_tools.asset_utils import Asset + import numpy as np + + # constants + COEFFICIENT_OF_DETERMINATION_THRESHOLD = 1 - 1e-4 # curves with values below this are not considered sufficiently smooth + + helper.init_idle() + # 1) Load the empty level + helper.open_level("", "Base") + + # 2) Create an entity + test_entity = Entity.create_editor_entity("test_entity") + Report.result(Tests.create_entity, test_entity.id.IsValid()) + + azlmbr.components.TransformBus( + azlmbr.bus.Event, "SetWorldTranslation", test_entity.id, math.Vector3(0.0, 0.0, 0.0)) + + # 3) Add rigid body component + rigid_body_component = test_entity.add_component("PhysX Rigid Body") + rigid_body_component.set_component_property_value("Configuration|Interpolate motion", True) + azlmbr.physics.RigidBodyRequestBus(azlmbr.bus.Event, "SetLinearDamping", test_entity.id, 0.0) + Report.result(Tests.rigid_body_added, test_entity.has_component("PhysX Rigid Body")) + + # 4) Enter game mode and collect data for the rigid body's z co-ordinate and the time values for a series of frames + t = [] + z = [] + helper.enter_game_mode(Tests.enter_game_mode) + general.idle_wait_frames(1) + game_entity_id = general.find_game_entity("test_entity") + for frame in range(100): + t.append(azlmbr.components.TickRequestBus(azlmbr.bus.Broadcast, "GetTimeAtCurrentTick").GetSeconds()) + z.append(azlmbr.components.TransformBus(azlmbr.bus.Event, "GetWorldZ", game_entity_id)) + general.idle_wait_frames(1) + helper.exit_game_mode(Tests.exit_game_mode) + + # 5) Test that the z vs t curve is sufficiently smooth (if the interpolation is not working well, the curve will be less smooth) + # normalize the t and z data + t = np.array(t) - np.mean(t) + z = np.array(z) - np.mean(z) + # fit a polynomial to the z vs t curve + fit = np.poly1d(np.polyfit(t, z, 4)) + residual = fit(t) - z + # calculate the coefficient of determination (a measure of how closely the polynomial curve fits the data) + # if the coefficient is very close to 1, then the curve fits the data very well, suggesting that the rigid body motion is smooth + # if the coefficient is significantly less than 1, then the z values vary more erratically relative to the smooth curve, + # indicating that the motion of the rigid body is not smooth + coefficient_of_determination = (1 - np.sum(residual * residual) / np.sum(z * z)) + Report.result(Tests.rigid_body_smooth, bool(coefficient_of_determination > COEFFICIENT_OF_DETERMINATION_THRESHOLD)) + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(Tick_InterpolatedRigidBodyMotionIsSmooth) diff --git a/AutomatedTesting/Gem/PythonTests/Physics/utils/FileManagement.py b/AutomatedTesting/Gem/PythonTests/Physics/utils/FileManagement.py index 22a41de4ea..03287d0542 100644 --- a/AutomatedTesting/Gem/PythonTests/Physics/utils/FileManagement.py +++ b/AutomatedTesting/Gem/PythonTests/Physics/utils/FileManagement.py @@ -98,38 +98,32 @@ class FileManagement: """ file_map = FileManagement._load_file_map() backup_path = FileManagement.backup_folder_path - backup_file_name = "{}.bak".format(file_name) - backup_file = os.path.join(backup_path, backup_file_name) # If backup directory DNE, make one if not os.path.exists(backup_path): os.mkdir(backup_path) - # If "traditional" backup file exists, delete it (myFile.txt.bak) - if os.path.exists(backup_file): - fs.delete([backup_file], True, False) - # Find my next storage name (myFile_1.txt.bak) - backup_storage_file_name = FileManagement._next_available_name(backup_file_name, file_map) - if backup_storage_file_name is None: + + # Find my next storage name (myFile_1.txt) + backup_file_name = FileManagement._next_available_name(file_name, file_map) + if backup_file_name is None: # If _next_available_name returns None, we have backed up MAX_BACKUPS of files name [file_name] raise Exception( "FileManagement class ran out of backups per name. Max: {}".format(FileManagement.MAX_BACKUPS) ) - backup_storage_file = os.path.join(backup_path, backup_storage_file_name) + + # If this backup file already exists, delete it. + backup_storage_file = "{}.bak".format(os.path.normpath(os.path.join(backup_path, backup_file_name))) if os.path.exists(backup_storage_file): - # This file should not exists, but if it does it's about to get clobbered! - fs.unlock_file(backup_storage_file) - # Create "traditional" backup file (myFile.txt.bak) - fs.create_backup(os.path.join(file_path, file_name), backup_path) - # Copy "traditional" backup file into storage backup (myFile_1.txt.bak) - FileManagement._copy_file(backup_file_name, backup_path, backup_storage_file_name, backup_path) - fs.lock_file(backup_storage_file) - # Delete "traditional" back up file - fs.unlock_file(backup_file) - fs.delete([backup_file], True, False) + fs.delete([backup_storage_file], True, False) + + # Create backup file (myFile_1.txt.bak) + original_file = os.path.normpath(os.path.join(file_path, file_name)) + fs.create_backup(original_file, backup_path, backup_file_name) + # Update file map with new file - file_map[os.path.join(file_path, file_name)] = backup_storage_file_name + file_map[original_file] = backup_file_name FileManagement._save_file_map(file_map) # Unlock original file to get it ready to be edited by the test - fs.unlock_file(os.path.join(file_path, file_name)) + fs.unlock_file(original_file) @staticmethod def _restore_file(file_name, file_path): @@ -143,20 +137,15 @@ class FileManagement: """ file_map = FileManagement._load_file_map() backup_path = FileManagement.backup_folder_path - src_file = os.path.join(file_path, file_name) + src_file = os.path.normpath(os.path.join(file_path, file_name)) if src_file in file_map: - backup_file = os.path.join(backup_path, file_map[src_file]) - if os.path.exists(backup_file): - fs.unlock_file(backup_file) - fs.unlock_file(src_file) - # Make temporary copy of backed up file to restore from - temp_file = "{}.bak".format(file_name) - FileManagement._copy_file(file_map[src_file], backup_path, temp_file, backup_path) - fs.restore_backup(src_file, backup_path) - fs.lock_file(src_file) - # Delete backup file - fs.delete([os.path.join(backup_path, temp_file)], True, False) + backup_file_name = file_map[src_file] + backup_file = "{}.bak".format(os.path.join(backup_path, backup_file_name)) + + fs.unlock_file(src_file) + if fs.restore_backup(src_file, backup_path, backup_file_name): fs.delete([backup_file], True, False) + # Remove from file map del file_map[src_file] FileManagement._save_file_map(file_map) @@ -218,6 +207,7 @@ class FileManagement: src_file_path = os.path.join(src_path, src_file) if os.path.exists(target_file_path): fs.unlock_file(target_file_path) + os.makedirs(target_path, exist_ok=True) shutil.copyfile(src_file_path, target_file_path) @staticmethod diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/Prefab/CMakeLists.txt index 48c24d1ebf..629db72dc7 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/Prefab/CMakeLists.txt @@ -12,7 +12,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) NAME AutomatedTesting::PrefabTests TEST_SUITE main TEST_SERIAL - PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.py + PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main_Optimized.py RUNTIME_DEPENDENCIES Legacy::Editor AZ::AssetProcessor diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py index 5337f0669c..6e1f94d358 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main.py @@ -12,7 +12,6 @@ import pytest import os import sys -from ly_test_tools import LAUNCHERS sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/../automatedtesting_shared') from base import TestAutomationBase @@ -24,34 +23,49 @@ class TestAutomation(TestAutomationBase): def _run_prefab_test(self, request, workspace, editor, test_module, batch_mode=True, autotest_mode=True): self._run_test(request, workspace, editor, test_module, - extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=true"], batch_mode=batch_mode, autotest_mode=autotest_mode) - def test_PrefabLevel_OpensLevelWithEntities(self, request, workspace, editor, launcher_platform): - from .tests import PrefabLevel_OpensLevelWithEntities as test_module + def test_OpenLevel_ContainingTwoEntities(self, request, workspace, editor, launcher_platform): + from Prefab.tests.open_level import OpenLevel_ContainingTwoEntities as test_module self._run_prefab_test(request, workspace, editor, test_module) - def test_PrefabBasicWorkflow_CreatePrefab(self, request, workspace, editor, launcher_platform): - from .tests import PrefabBasicWorkflow_CreatePrefab as test_module + def test_CreatePrefab_WithSingleEntity(self, request, workspace, editor, launcher_platform): + from Prefab.tests.create_prefab import CreatePrefab_WithSingleEntity as test_module self._run_prefab_test(request, workspace, editor, test_module) - def test_PrefabBasicWorkflow_InstantiatePrefab(self, request, workspace, editor, launcher_platform): - from .tests import PrefabBasicWorkflow_InstantiatePrefab as test_module + def test_InstantiatePrefab_ContainingASingleEntity(self, request, workspace, editor, launcher_platform): + from Prefab.tests.instantiate_prefab import InstantiatePrefab_ContainingASingleEntity as test_module self._run_prefab_test(request, workspace, editor, test_module) - def test_PrefabBasicWorkflow_CreateAndDeletePrefab(self, request, workspace, editor, launcher_platform): - from .tests import PrefabBasicWorkflow_CreateAndDeletePrefab as test_module + def test_DeletePrefab_ContainingASingleEntity(self, request, workspace, editor, launcher_platform): + from Prefab.tests.delete_prefab import DeletePrefab_ContainingASingleEntity as test_module self._run_prefab_test(request, workspace, editor, test_module) - def test_PrefabBasicWorkflow_CreateAndReparentPrefab(self, request, workspace, editor, launcher_platform): - from .tests import PrefabBasicWorkflow_CreateAndReparentPrefab as test_module + def test_ReparentPrefab_UnderAnotherPrefab(self, request, workspace, editor, launcher_platform): + from Prefab.tests.reparent_prefab import ReparentPrefab_UnderAnotherPrefab as test_module self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False) - def test_PrefabBasicWorkflow_CreateReparentAndDetachPrefab(self, request, workspace, editor, launcher_platform): - from .tests import PrefabBasicWorkflow_CreateReparentAndDetachPrefab as test_module + def test_DetachPrefab_UnderAnotherPrefab(self, request, workspace, editor, launcher_platform): + from Prefab.tests.detach_prefab import DetachPrefab_UnderAnotherPrefab as test_module self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False) - def test_PrefabBasicWorkflow_CreateAndDuplicatePrefab(self, request, workspace, editor, launcher_platform): - from .tests import PrefabBasicWorkflow_CreateAndDuplicatePrefab as test_module + def test_DuplicatePrefab_ContainingASingleEntity(self, request, workspace, editor, launcher_platform): + from Prefab.tests.duplicate_prefab import DuplicatePrefab_ContainingASingleEntity as test_module self._run_prefab_test(request, workspace, editor, test_module) + + def test_CreatePrefab_UnderAnEntity(self, request, workspace, editor, launcher_platform): + from Prefab.tests.create_prefab import CreatePrefab_UnderAnEntity as test_module + self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False) + + def test_CreatePrefab_UnderAnotherPrefab(self, request, workspace, editor, launcher_platform): + from Prefab.tests.create_prefab import CreatePrefab_UnderAnotherPrefab as test_module + self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False) + + def test_DeleteEntity_UnderAnotherPrefab(self, request, workspace, editor, launcher_platform): + from Prefab.tests.delete_entity import DeleteEntity_UnderAnotherPrefab as test_module + self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False) + + def test_DeleteEntity_UnderLevelPrefab(self, request, workspace, editor, launcher_platform): + from Prefab.tests.delete_entity import DeleteEntity_UnderLevelPrefab as test_module + self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main_Optimized.py b/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main_Optimized.py new file mode 100644 index 0000000000..bf67b2c661 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Prefab/TestSuite_Main_Optimized.py @@ -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 \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/create_prefab/CreatePrefab_UnderAnEntity.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/create_prefab/CreatePrefab_UnderAnEntity.py new file mode 100644 index 0000000000..5033c1da9c --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/create_prefab/CreatePrefab_UnderAnEntity.py @@ -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 +""" + +def CreatePrefab_UnderAnEntity(): + """ + Test description: + - Creates two entities, parent and child. Child entity has Parent entity as its parent. + - Creates a prefab of the child entity. + Test is successful if the new instanced prefab of the child has the parent entity id + """ + + CAR_PREFAB_FILE_NAME = 'car_prefab' + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.prefab_utils import Prefab + + import Prefab.tests.PrefabTestUtils as prefab_test_utils + + prefab_test_utils.open_base_tests_level() + + # Creates a new Entity at the root level + # Asserts if creation didn't succeed + parent_entity = EditorEntity.create_editor_entity_at((100.0, 100.0, 100.0)) + assert parent_entity.id.IsValid(), "Couldn't create parent entity" + + child_entity = EditorEntity.create_editor_entity(parent_id=parent_entity.id) + assert child_entity.id.IsValid(), "Couldn't create child entity" + assert child_entity.get_world_translation().IsClose(parent_entity.get_world_translation()), f"Child entity position{child_entity.get_world_translation().ToString()}" \ + f" is not located at the same position as the parent{parent_entity.get_world_translation().ToString()}" + + # Asserts if prefab creation doesn't succeed + child_prefab, child_instance = Prefab.create_prefab([child_entity], CAR_PREFAB_FILE_NAME) + child_entity_on_child_instance = child_instance.get_direct_child_entities()[0] + assert child_instance.container_entity.get_parent_id().IsValid(), "Newly instanced entity has no parent" + assert child_instance.container_entity.get_parent_id() == parent_entity.id, "Newly instanced entity parent does not match the expected parent" + assert child_instance.container_entity.get_world_translation().IsClose(parent_entity.get_world_translation()), "Newly instanced entity position is not located at the same position as the parent" + # Move the parent position, it should update the child position + parent_entity.set_world_translation((200.0, 200.0, 200.0)) + child_instance_translation = child_instance.container_entity.get_world_translation() + assert child_instance_translation.IsClose(azlmbr.math.Vector3(200.0, 200.0, 200.0)), f"Instance position position{child_instance_translation.ToString()} didn't get updated" \ + f" to the same position as the parent{parent_entity.get_world_translation().ToString()}" + child_translation = child_entity_on_child_instance.get_world_translation() + assert child_translation.IsClose(azlmbr.math.Vector3(200.0, 200.0, 200.0)), f"Entity position{child_translation.ToString()} of the instance didn't get updated" \ + f" to the same position as the parent{parent_entity.get_world_translation().ToString()}" + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(CreatePrefab_UnderAnEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/create_prefab/CreatePrefab_UnderAnotherPrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/create_prefab/CreatePrefab_UnderAnotherPrefab.py new file mode 100644 index 0000000000..429da49434 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/create_prefab/CreatePrefab_UnderAnotherPrefab.py @@ -0,0 +1,57 @@ +""" +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 +""" + +def CreatePrefab_UnderAnotherPrefab(): + """ + Test description: + - Creates an entity with a physx collider + - Creates a prefab "Outer_prefab" and an instance based of that entity + - Creates a prefab "Inner_prefab" inside "Outer_prefab" based the entity contained inside of it + Checks that the entity is correctly handlded by the prefab system checking the name and that it contains the physx collider + """ + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.prefab_utils import Prefab + + import Prefab.tests.PrefabTestUtils as prefab_test_utils + + prefab_test_utils.open_base_tests_level() + + # Creates a new Entity at the root level + # Asserts if creation didn't succeed + entity = EditorEntity.create_editor_entity_at((100.0, 100.0, 100.0), name = "TestEntity") + assert entity.id.IsValid(), "Couldn't create entity" + entity.add_component("PhysX Collider") + assert entity.has_component("PhysX Collider"), "Attempted to add a PhysX Collider but no physx collider collider was found afterwards" + + # Create a prefab based on that entity + outer_prefab, outer_instance = Prefab.create_prefab([entity], "Outer_prefab") + # The test should be now inside the outer prefab instance. + entity = outer_instance.get_direct_child_entities()[0] + # We track if that is the same entity by checking the name and if it still contains the component that we created before + assert entity.get_name() == "TestEntity", f"Entity name inside outer_prefab doesn't match the original name, original:'TestEntity' current:'{entity.get_name()}'" + assert entity.has_component("PhysX Collider"), "Entity name inside outer_prefab doesn't have the collider component it should" + + # Now, create another prefab, based on the entity that is inside outer_prefab + inner_prefab, inner_instance = Prefab.create_prefab([entity], "Inner_prefab") + # The test entity should now be inside the inner prefab instance + entity = inner_instance.get_direct_child_entities()[0] + # We track if that is the same entity by checking the name and if it still contains the component that we created before + assert entity.get_name() == "TestEntity", f"Entity name inside inner_prefab doesn't match the original name, original:'TestEntity' current:'{entity.get_name()}'" + assert entity.has_component("PhysX Collider"), "Entity name inside inner_prefab doesn't have the collider component it should" + + # Verify hierarchy of entities: + # Outer_prefab + # |- Inner_prefab + # | |- TestEntity + assert entity.get_parent_id() == inner_instance.container_entity.id + assert inner_instance.container_entity.get_parent_id() == outer_instance.container_entity.id + + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(CreatePrefab_UnderAnotherPrefab) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreatePrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/create_prefab/CreatePrefab_WithSingleEntity.py similarity index 84% rename from AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreatePrefab.py rename to AutomatedTesting/Gem/PythonTests/Prefab/tests/create_prefab/CreatePrefab_WithSingleEntity.py index cae105a9a9..eb8a262a69 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreatePrefab.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/create_prefab/CreatePrefab_WithSingleEntity.py @@ -5,7 +5,7 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -def PrefabBasicWorkflow_CreatePrefab(): +def CreatePrefab_WithSingleEntity(): CAR_PREFAB_FILE_NAME = 'car_prefab' @@ -13,7 +13,7 @@ def PrefabBasicWorkflow_CreatePrefab(): from editor_python_test_tools.utils import Report from editor_python_test_tools.prefab_utils import Prefab - import PrefabTestUtils as prefab_test_utils + import Prefab.tests.PrefabTestUtils as prefab_test_utils prefab_test_utils.open_base_tests_level() @@ -24,6 +24,7 @@ def PrefabBasicWorkflow_CreatePrefab(): # Creates a prefab from the new entity Prefab.create_prefab(car_prefab_entities, CAR_PREFAB_FILE_NAME) + if __name__ == "__main__": from editor_python_test_tools.utils import Report - Report.start_test(PrefabBasicWorkflow_CreatePrefab) + Report.start_test(CreatePrefab_WithSingleEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/delete_entity/DeleteEntity_UnderAnotherPrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/delete_entity/DeleteEntity_UnderAnotherPrefab.py new file mode 100644 index 0000000000..deaaaedf30 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/delete_entity/DeleteEntity_UnderAnotherPrefab.py @@ -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 +""" + +def DeleteEntity_UnderAnotherPrefab(): + """ + Test description: + - Creates an entity. + - Creates a prefab out of the above entity. + - Focuses on the created prefab and destroys the entity within. + Checks that the entity is correctly destroyed. + """ + + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.prefab_utils import Prefab + + import Prefab.tests.PrefabTestUtils as prefab_test_utils + + prefab_test_utils.open_base_tests_level() + + PREFAB_FILE_NAME = 'some_prefab' + + # Creates a new entity at the root level + entity = EditorEntity.create_editor_entity() + assert entity.id.IsValid(), "Couldn't create entity." + + # Asserts if prefab creation doesn't succeed + child_prefab, child_instance = Prefab.create_prefab([entity], PREFAB_FILE_NAME) + child_entity_ids_inside_prefab = child_instance.get_direct_child_entities() + assert len( + child_entity_ids_inside_prefab) == 1, f"{len(child_entity_ids_inside_prefab)} entities found inside prefab" \ + f" when there should have been just 1 entity" + + child_entity_inside_prefab = child_entity_ids_inside_prefab[0] + child_entity_inside_prefab.focus_on_owning_prefab() + + child_entity_inside_prefab.delete() + + # Wait till prefab propagation finishes before validating entity deletion. + azlmbr.legacy.general.idle_wait_frames(1) + + child_entity_ids_inside_prefab = child_instance.get_direct_child_entities() + assert len( + child_entity_ids_inside_prefab) == 0, f"{len(child_entity_ids_inside_prefab)} entities found inside prefab" \ + f" when there should have been 0 entities" + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(DeleteEntity_UnderAnotherPrefab) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/delete_entity/DeleteEntity_UnderLevelPrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/delete_entity/DeleteEntity_UnderLevelPrefab.py new file mode 100644 index 0000000000..807427718c --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/delete_entity/DeleteEntity_UnderLevelPrefab.py @@ -0,0 +1,37 @@ +""" +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 +""" + +def DeleteEntity_UnderLevelPrefab(): + """ + Test description: + - Creates an entity. + - Destroys the created entity. + Checks that the entity is correctly destroyed. + """ + + from editor_python_test_tools.editor_entity_utils import EditorEntity + import Prefab.tests.PrefabTestUtils as prefab_test_utils + + prefab_test_utils.open_base_tests_level() + + # Creates a new Entity at the root level + # Asserts if creation didn't succeed + entity = EditorEntity.create_editor_entity_at((100.0, 100.0, 100.0), name = "TestEntity") + assert entity.id.IsValid(), "Couldn't create entity" + + level_container_entity = EditorEntity(entity.get_parent_id()) + entity.delete() + + # Wait till prefab propagation finishes before validating entity deletion. + azlmbr.legacy.general.idle_wait_frames(1) + level_container_child_entities_count = len(level_container_entity.get_children_ids()) + assert level_container_child_entities_count == 0, f"The level still has {level_container_child_entities_count}" \ + f" children when it should have 0." + +if __name__ == "__main__": + from editor_python_test_tools.utils import Report + Report.start_test(DeleteEntity_UnderLevelPrefab) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDeletePrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/delete_prefab/DeletePrefab_ContainingASingleEntity.py similarity index 83% rename from AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDeletePrefab.py rename to AutomatedTesting/Gem/PythonTests/Prefab/tests/delete_prefab/DeletePrefab_ContainingASingleEntity.py index bbebd70e04..919168019e 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDeletePrefab.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/delete_prefab/DeletePrefab_ContainingASingleEntity.py @@ -5,14 +5,14 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -def PrefabBasicWorkflow_CreateAndDeletePrefab(): +def DeletePrefab_ContainingASingleEntity(): CAR_PREFAB_FILE_NAME = 'car_prefab' from editor_python_test_tools.editor_entity_utils import EditorEntity from editor_python_test_tools.prefab_utils import Prefab - import PrefabTestUtils as prefab_test_utils + import Prefab.tests.PrefabTestUtils as prefab_test_utils prefab_test_utils.open_base_tests_level() @@ -29,4 +29,4 @@ def PrefabBasicWorkflow_CreateAndDeletePrefab(): if __name__ == "__main__": from editor_python_test_tools.utils import Report - Report.start_test(PrefabBasicWorkflow_CreateAndDeletePrefab) + Report.start_test(DeletePrefab_ContainingASingleEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateReparentAndDetachPrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/detach_prefab/DetachPrefab_UnderAnotherPrefab.py similarity index 84% rename from AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateReparentAndDetachPrefab.py rename to AutomatedTesting/Gem/PythonTests/Prefab/tests/detach_prefab/DetachPrefab_UnderAnotherPrefab.py index bdf77c4bf3..69dbcfc89c 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateReparentAndDetachPrefab.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/detach_prefab/DetachPrefab_UnderAnotherPrefab.py @@ -5,10 +5,10 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -def PrefabBasicWorkflow_CreateReparentAndDetachPrefab(): +def DetachPrefab_UnderAnotherPrefab(): - CAR_PREFAB_FILE_NAME = 'car_prefab' - WHEEL_PREFAB_FILE_NAME = 'wheel_prefab' + CAR_PREFAB_FILE_NAME = 'car_prefab2' + WHEEL_PREFAB_FILE_NAME = 'wheel_prefab2' import editor_python_test_tools.pyside_utils as pyside_utils @@ -18,7 +18,7 @@ def PrefabBasicWorkflow_CreateReparentAndDetachPrefab(): from editor_python_test_tools.editor_entity_utils import EditorEntity from editor_python_test_tools.prefab_utils import Prefab - import PrefabTestUtils as prefab_test_utils + import Prefab.tests.PrefabTestUtils as prefab_test_utils prefab_test_utils.open_base_tests_level() @@ -48,4 +48,4 @@ def PrefabBasicWorkflow_CreateReparentAndDetachPrefab(): if __name__ == "__main__": from editor_python_test_tools.utils import Report - Report.start_test(PrefabBasicWorkflow_CreateReparentAndDetachPrefab) + Report.start_test(DetachPrefab_UnderAnotherPrefab) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDuplicatePrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/duplicate_prefab/DuplicatePrefab_ContainingASingleEntity.py similarity index 83% rename from AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDuplicatePrefab.py rename to AutomatedTesting/Gem/PythonTests/Prefab/tests/duplicate_prefab/DuplicatePrefab_ContainingASingleEntity.py index 2479ae549e..e611303fbb 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndDuplicatePrefab.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/duplicate_prefab/DuplicatePrefab_ContainingASingleEntity.py @@ -5,14 +5,14 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -def PrefabBasicWorkflow_CreateAndDuplicatePrefab(): +def DuplicatePrefab_ContainingASingleEntity(): CAR_PREFAB_FILE_NAME = 'car_prefab' from editor_python_test_tools.editor_entity_utils import EditorEntity from editor_python_test_tools.prefab_utils import Prefab - import PrefabTestUtils as prefab_test_utils + import Prefab.tests.PrefabTestUtils as prefab_test_utils prefab_test_utils.open_base_tests_level() @@ -29,4 +29,4 @@ def PrefabBasicWorkflow_CreateAndDuplicatePrefab(): if __name__ == "__main__": from editor_python_test_tools.utils import Report - Report.start_test(PrefabBasicWorkflow_CreateAndDuplicatePrefab) + Report.start_test(DuplicatePrefab_ContainingASingleEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_InstantiatePrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/instantiate_prefab/InstantiatePrefab_ContainingASingleEntity.py similarity index 85% rename from AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_InstantiatePrefab.py rename to AutomatedTesting/Gem/PythonTests/Prefab/tests/instantiate_prefab/InstantiatePrefab_ContainingASingleEntity.py index a701802cd4..a81608ee8a 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_InstantiatePrefab.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/instantiate_prefab/InstantiatePrefab_ContainingASingleEntity.py @@ -5,7 +5,7 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -def PrefabBasicWorkflow_InstantiatePrefab(): +def InstantiatePrefab_ContainingASingleEntity(): from azlmbr.math import Vector3 @@ -15,7 +15,7 @@ def PrefabBasicWorkflow_InstantiatePrefab(): from editor_python_test_tools.prefab_utils import Prefab - import PrefabTestUtils as prefab_test_utils + import Prefab.tests.PrefabTestUtils as prefab_test_utils prefab_test_utils.open_base_tests_level() @@ -30,4 +30,4 @@ def PrefabBasicWorkflow_InstantiatePrefab(): if __name__ == "__main__": from editor_python_test_tools.utils import Report - Report.start_test(PrefabBasicWorkflow_InstantiatePrefab) + Report.start_test(InstantiatePrefab_ContainingASingleEntity) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabLevel_OpensLevelWithEntities.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/open_level/OpenLevel_ContainingTwoEntities.py similarity index 96% rename from AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabLevel_OpensLevelWithEntities.py rename to AutomatedTesting/Gem/PythonTests/Prefab/tests/open_level/OpenLevel_ContainingTwoEntities.py index 0eb7e86a9e..787d7000d2 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabLevel_OpensLevelWithEntities.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/open_level/OpenLevel_ContainingTwoEntities.py @@ -14,7 +14,7 @@ class Tests(): # fmt:on -def PrefabLevel_OpensLevelWithEntities(): +def OpenLevel_ContainingTwoEntities(): """ Opens the level that contains 2 entities, "EmptyEntity" and "EntityWithPxCollider". This test makes sure that both entities exist after opening the level and that: @@ -70,4 +70,4 @@ def PrefabLevel_OpensLevelWithEntities(): if __name__ == "__main__": from editor_python_test_tools.utils import Report - Report.start_test(PrefabLevel_OpensLevelWithEntities) + Report.start_test(OpenLevel_ContainingTwoEntities) diff --git a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndReparentPrefab.py b/AutomatedTesting/Gem/PythonTests/Prefab/tests/reparent_prefab/ReparentPrefab_UnderAnotherPrefab.py similarity index 89% rename from AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndReparentPrefab.py rename to AutomatedTesting/Gem/PythonTests/Prefab/tests/reparent_prefab/ReparentPrefab_UnderAnotherPrefab.py index 1cbc591c29..2c460a3298 100644 --- a/AutomatedTesting/Gem/PythonTests/Prefab/tests/PrefabBasicWorkflow_CreateAndReparentPrefab.py +++ b/AutomatedTesting/Gem/PythonTests/Prefab/tests/reparent_prefab/ReparentPrefab_UnderAnotherPrefab.py @@ -5,7 +5,7 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT """ -def PrefabBasicWorkflow_CreateAndReparentPrefab(): +def ReparentPrefab_UnderAnotherPrefab(): CAR_PREFAB_FILE_NAME = 'car_prefab' WHEEL_PREFAB_FILE_NAME = 'wheel_prefab' @@ -18,7 +18,7 @@ def PrefabBasicWorkflow_CreateAndReparentPrefab(): from editor_python_test_tools.editor_entity_utils import EditorEntity from editor_python_test_tools.prefab_utils import Prefab - import PrefabTestUtils as prefab_test_utils + import Prefab.tests.PrefabTestUtils as prefab_test_utils prefab_test_utils.open_base_tests_level() @@ -45,4 +45,4 @@ def PrefabBasicWorkflow_CreateAndReparentPrefab(): if __name__ == "__main__": from editor_python_test_tools.utils import Report - Report.start_test(PrefabBasicWorkflow_CreateAndReparentPrefab) + Report.start_test(ReparentPrefab_UnderAnotherPrefab) diff --git a/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainHeightGradientList_AddRemoveGradientWorks.py b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainHeightGradientList_AddRemoveGradientWorks.py new file mode 100644 index 0000000000..0be1f1ca10 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainHeightGradientList_AddRemoveGradientWorks.py @@ -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) \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainMacroMaterialComponent_MacroMaterialActivates.py b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainMacroMaterialComponent_MacroMaterialActivates.py new file mode 100644 index 0000000000..71ff02739a --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainMacroMaterialComponent_MacroMaterialActivates.py @@ -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) \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainSystem_VegetationSpawnsOnTerrainSurfaces.py b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainSystem_VegetationSpawnsOnTerrainSurfaces.py new file mode 100644 index 0000000000..f1769ae3d9 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/TerrainSystem_VegetationSpawnsOnTerrainSurfaces.py @@ -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) \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/Terrain_SupportsPhysics.py b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/Terrain_SupportsPhysics.py index e68b0932c2..390ec6a6b0 100644 --- a/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/Terrain_SupportsPhysics.py +++ b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/Terrain_SupportsPhysics.py @@ -72,7 +72,7 @@ def Terrain_SupportsPhysics(): # 2) Create 2 test entities, one parent at 512.0, 512.0, 50.0 and one child at the default position and add the required components entity1_components_to_add = ["Axis Aligned Box Shape", "Terrain Layer Spawner", "Terrain Height Gradient List", "Terrain Physics Heightfield Collider", "PhysX Heightfield Collider"] - entity2_components_to_add = ["Vegetation Reference Shape", "Gradient Transform Modifier", "FastNoise Gradient"] + entity2_components_to_add = ["Shape Reference", "Gradient Transform Modifier", "FastNoise Gradient"] ball_components_to_add = ["Sphere Shape", "PhysX Collider", "PhysX Rigid Body"] terrain_spawner_entity = hydra.Entity("TestEntity1") terrain_spawner_entity.create_entity(azmath.Vector3(512.0, 512.0, 50.0), entity1_components_to_add) diff --git a/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/Terrain_World_ConfigurationWorks.py b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/Terrain_World_ConfigurationWorks.py new file mode 100644 index 0000000000..bb5dcb3bea --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/Terrain/EditorScripts/Terrain_World_ConfigurationWorks.py @@ -0,0 +1,168 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" + +#fmt: off +class Tests(): + level_components_added = ("Level components added correctly", "Failed to create level components") + create_terrain_spawner_entity = ("Terrain_spawner_entity created successfully", "Failed to create terrain_spawner_entity") + create_height_provider_entity = ("Height_provider_entity created successfully", "Failed to create height_provider_entity") + bounds_max_changed = ("Terrain World Bounds Max changed successfully", "Failed to change Terrain World Bounds Max") + bounds_min_changed = ("Terrain World Bounds Min changed successfully", "Failed to change Terrain World Bounds Min") + height_query_changed = ("Terrain World Height Query Resolution changed successfully", "Failed to change Height Query Resolution") + box_dimensions_changed = ("Aabb dimensions changed successfully", "Failed to change Aabb dimensions") + shape_changed = ("Shape changed successfully", "Failed Shape change") + frequency_changed = ("Frequency changed successfully", "Failed Frequency change") + entity_added = ("Entity added successfully", "Failed Entity add") + terrain_exists = ("Terrain exists at the provided point", "Terrain does not exist at the provided point") + terrain_does_not_exist = ("Terrain does not exist at the provided point", "Terrain exists at the provided point") + values_not_the_same = ("The tested values are not the same", "The tested values are the same") + no_errors_and_warnings_found = ("No errors and warnings found", "Found errors and warnings") +#fmt: on + +def Terrain_World_ConfigurationWorks(): + """ + Summary: + Test the Terrain World configuration changes when parameters are changed in the component + + Test Steps: + Expected Behavior: + The Editor is stable there are no warnings or errors. + + Test Steps: + 1) Start the Tracer to catch any errors and warnings + 2) Load the base level + 3) Load the level components + 4) Create 2 test entities, one parent at 512.0, 512.0, 50.0 and one child at the default position and add the required components + 5) Set the base Terrain World values + 6) Change the Axis Aligned Box Shape dimensions + 7) Set the Shape Reference to terrain_spawner_entity + 8) Set the FastNoise Gradient frequency to 0.01 + 9) Set the Gradient List to height_provider_entity + 10) Disable and Enable the Terrain Gradient List so that it is recognised + 11) Check terrain exists at a known position in the world + 12) Check terrain does not exist at a known position outside the world + 13) Check height value is the expected one when query resolution is changed + """ + from editor_python_test_tools.editor_entity_utils import EditorEntity + from editor_python_test_tools.utils import TestHelper as helper, Report + from editor_python_test_tools.utils import Report, Tracer + import editor_python_test_tools.hydra_editor_utils as hydra + import azlmbr.math as azmath + import azlmbr.legacy.general as general + import azlmbr.bus as bus + import azlmbr.editor as editor + import azlmbr.terrain as terrain + import math + + SET_BOX_X_SIZE = 2048.0 + SET_BOX_Y_SIZE = 2048.0 + SET_BOX_Z_SIZE = 100.0 + CLAMP = 1 + + helper.init_idle() + + # 1) Start the Tracer to catch any errors and warnings + with Tracer() as section_tracer: + # 2) Load the level + helper.open_level("", "Base") + helper.wait_for_condition(lambda: general.get_current_level_name() == "Base", 2.0) + + # 3) Load the level components + terrain_world_component = hydra.add_level_component("Terrain World") + terrain_world_renderer = hydra.add_level_component("Terrain World Renderer") + Report.critical_result(Tests.level_components_added, + terrain_world_component is not None and terrain_world_renderer is not None) + + # 4) Create 2 test entities, one parent at 512.0, 512.0, 50.0 and one child at the default position and add the required components + entity1_components_to_add = ["Axis Aligned Box Shape", "Terrain Layer Spawner", "Terrain Height Gradient List", "Terrain Physics Heightfield Collider"] + entity2_components_to_add = ["Shape Reference", "Gradient Transform Modifier", "FastNoise Gradient"] + terrain_spawner_entity = hydra.Entity("TerrainEntity") + terrain_spawner_entity.create_entity(azmath.Vector3(512.0, 512.0, 50.0), entity1_components_to_add) + Report.result(Tests.create_terrain_spawner_entity, terrain_spawner_entity.id.IsValid()) + height_provider_entity = hydra.Entity("HeightProviderEntity") + height_provider_entity.create_entity(azmath.Vector3(0.0, 0.0, 0.0), entity2_components_to_add,terrain_spawner_entity.id) + Report.result(Tests.create_height_provider_entity, height_provider_entity.id.IsValid()) + + # Give everything a chance to finish initializing. + general.idle_wait_frames(1) + + # 5) Set the base Terrain World values + world_bounds_max = azmath.Vector3(1100.0, 1100.0, 1100.0) + world_bounds_min = azmath.Vector3(10.0, 10.0, 10.0) + height_query_resolution = azmath.Vector2(1.0, 1.0) + hydra.set_component_property_value(terrain_world_component, "Configuration|World Bounds (Max)", world_bounds_max) + hydra.set_component_property_value(terrain_world_component, "Configuration|World Bounds (Min)", world_bounds_min) + hydra.set_component_property_value(terrain_world_component, "Configuration|Height Query Resolution (m)", height_query_resolution) + world_max = hydra.get_component_property_value(terrain_world_component, "Configuration|World Bounds (Max)") + world_min = hydra.get_component_property_value(terrain_world_component, "Configuration|World Bounds (Min)") + world_query = hydra.get_component_property_value(terrain_world_component, "Configuration|Height Query Resolution (m)") + Report.result(Tests.bounds_max_changed, world_max == world_bounds_max) + Report.result(Tests.bounds_min_changed, world_min == world_bounds_min) + Report.result(Tests.height_query_changed, world_query == height_query_resolution) + + # 6) Change the Axis Aligned Box Shape dimensions + box_dimensions = azmath.Vector3(SET_BOX_X_SIZE, SET_BOX_Y_SIZE, SET_BOX_Z_SIZE) + terrain_spawner_entity.get_set_test(0, "Axis Aligned Box Shape|Box Configuration|Dimensions", box_dimensions) + box_shape_dimensions = hydra.get_component_property_value(terrain_spawner_entity.components[0], "Axis Aligned Box Shape|Box Configuration|Dimensions") + Report.result(Tests.box_dimensions_changed, box_dimensions == box_shape_dimensions) + + # 7) Set the Shape Reference to terrain_spawner_entity + height_provider_entity.get_set_test(0, "Configuration|Shape Entity Id", terrain_spawner_entity.id) + entityId = hydra.get_component_property_value(height_provider_entity.components[0], "Configuration|Shape Entity Id") + Report.result(Tests.shape_changed, entityId == terrain_spawner_entity.id) + + # 8) Set the FastNoise Gradient frequency to 0.01 + frequency = 0.01 + height_provider_entity.get_set_test(2, "Configuration|Frequency", frequency) + frequencyVal = hydra.get_component_property_value(height_provider_entity.components[2], "Configuration|Frequency") + Report.result(Tests.frequency_changed, math.isclose(frequency, frequencyVal, abs_tol = 0.00001)) + + # 9) Set the Gradient List to height_provider_entity + propertyTree = hydra.get_property_tree(terrain_spawner_entity.components[2]) + propertyTree.add_container_item("Configuration|Gradient Entities", 0, height_provider_entity.id) + checkID = propertyTree.get_container_item("Configuration|Gradient Entities", 0) + Report.result(Tests.entity_added, checkID.GetValue() == height_provider_entity.id) + + general.idle_wait_frames(1) + + # 10) Disable and Enable the Terrain Gradient List so that it is recognised, EnableComponents performs both actions. + editor.EditorComponentAPIBus(bus.Broadcast, 'EnableComponents', [terrain_spawner_entity.components[2]]) + + # 11) Check terrain exists at a known position in the world + terrainExists = not terrain.TerrainDataRequestBus(bus.Broadcast, 'GetIsHoleFromFloats', 10.0, 10.0, CLAMP) + Report.result(Tests.terrain_exists, terrainExists) + + terrainExists = not terrain.TerrainDataRequestBus(bus.Broadcast, 'GetIsHoleFromFloats', 1100.0, 1100.0, CLAMP) + Report.result(Tests.terrain_exists, terrainExists) + + # 12) Check terrain does not exist at a known position outside the world + terrainDoesNotExist = terrain.TerrainDataRequestBus(bus.Broadcast, 'GetIsHoleFromFloats', 1101.0, 1101.0, CLAMP) + Report.result(Tests.terrain_does_not_exist, terrainDoesNotExist) + + terrainDoesNotExist = terrain.TerrainDataRequestBus(bus.Broadcast, 'GetIsHoleFromFloats', 9.0, 9.0, CLAMP) + Report.result(Tests.terrain_does_not_exist, terrainDoesNotExist) + + # 13) Check height value is the expected one when query resolution is changed + testpoint = terrain.TerrainDataRequestBus(bus.Broadcast, 'GetHeightFromFloats', 10.5, 10.5, CLAMP) + height_query_resolution = azmath.Vector2(0.5, 0.5) + hydra.set_component_property_value(terrain_world_component, "Configuration|Height Query Resolution (m)", height_query_resolution) + general.idle_wait_frames(1) + testpoint2 = terrain.TerrainDataRequestBus(bus.Broadcast, 'GetHeightFromFloats', 10.5, 10.5, CLAMP) + Report.result(Tests.values_not_the_same, not math.isclose(testpoint, testpoint2, abs_tol = 0.000000001)) + + helper.wait_for_condition(lambda: section_tracer.has_errors or section_tracer.has_asserts, 1.0) + for error_info in section_tracer.errors: + Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}") + for assert_info in section_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(Terrain_World_ConfigurationWorks) + diff --git a/AutomatedTesting/Gem/PythonTests/Terrain/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/Terrain/TestSuite_Main.py index c5eec74c08..98c4f8a660 100644 --- a/AutomatedTesting/Gem/PythonTests/Terrain/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/Terrain/TestSuite_Main.py @@ -20,8 +20,22 @@ from ly_test_tools.o3de.editor_test import EditorTestSuite, EditorSharedTest @pytest.mark.parametrize("project", ["AutomatedTesting"]) class TestAutomation(EditorTestSuite): + enable_prefab_system = False + class test_AxisAlignedBoxShape_ConfigurationWorks(EditorSharedTest): from .EditorScripts import TerrainPhysicsCollider_ChangesSizeWithAxisAlignedBoxShapeChanges as test_module class test_Terrain_SupportsPhysics(EditorSharedTest): from .EditorScripts import Terrain_SupportsPhysics as test_module + + class test_TerrainHeightGradientList_AddRemoveGradientWorks(EditorSharedTest): + from .EditorScripts import TerrainHeightGradientList_AddRemoveGradientWorks as test_module + + class test_TerrainSystem_VegetationSpawnsOnTerrainSurfaces(EditorSharedTest): + from .EditorScripts import TerrainSystem_VegetationSpawnsOnTerrainSurfaces as test_module + + class test_TerrainMacroMaterialComponent_MacroMaterialActivates(EditorSharedTest): + from .EditorScripts import TerrainMacroMaterialComponent_MacroMaterialActivates as test_module + + class test_TerrainWorld_ConfigurationWorks(EditorSharedTest): + from .EditorScripts import Terrain_World_ConfigurationWorks as test_module diff --git a/AutomatedTesting/Gem/PythonTests/WhiteBox/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/WhiteBox/TestSuite_Main.py index fa6c81c98a..2fc717e256 100644 --- a/AutomatedTesting/Gem/PythonTests/WhiteBox/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/WhiteBox/TestSuite_Main.py @@ -24,12 +24,12 @@ from base import TestAutomationBase class TestAutomation(TestAutomationBase): def test_WhiteBox_AddComponentToEntity(self, request, workspace, editor, launcher_platform): from .tests import WhiteBox_AddComponentToEntity as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_WhiteBox_SetDefaultShape(self, request, workspace, editor, launcher_platform): from .tests import WhiteBox_SetDefaultShape as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_WhiteBox_SetInvisible(self, request, workspace, editor, launcher_platform): from .tests import WhiteBox_SetInvisible as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_fast_scan_setting_backup_fixture.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_fast_scan_setting_backup_fixture.py index 4a096ca3fe..c224289cf4 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_fast_scan_setting_backup_fixture.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_fast_scan_setting_backup_fixture.py @@ -29,6 +29,9 @@ def ap_fast_scan_setting_backup_fixture(request, workspace) -> PlatformSetting: if workspace.asset_processor_platform == 'mac': pytest.skip("Mac plist file editing not implemented yet") + if workspace.asset_processor_platform == 'linux': + pytest.skip("Linux system settings not implemented yet") + key = fast_scan_key subkey = fast_scan_subkey diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_missing_dependency_fixture.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_missing_dependency_fixture.py index 857d91bb12..7f7f420fd0 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_missing_dependency_fixture.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_missing_dependency_fixture.py @@ -183,23 +183,26 @@ def ap_missing_dependency_fixture(request, workspace, ap_setup_fixture) -> Any: :return: None """ logger.info(f"Searching output for expected dependencies for product {product}") + sorted_expected = sorted(expected_dependencies) # Check dependencies found either in the log or console output for product_name, missing_deps in self.extract_missing_dependencies_from_output(log_output).items(): if product in product_name: + sorted_missing = sorted(missing_deps) # fmt:off - assert sorted(missing_deps) == sorted(expected_dependencies), \ + assert sorted_expected == sorted_missing, \ f"Missing dependencies for '{product_name}' did not match expected. Expected: " \ - f"{expected_dependencies}, Actual: {missing_deps}" + f"{sorted_expected}, Actual: {sorted_missing}" # fmt:on # Check dependencies found in Database for product_name, missing_deps in self.extract_missing_dependencies_from_database(product, platforms).items(): if product.replace("\\", "/") in product_name: + sorted_missing = sorted(missing_deps) # fmt:off - assert sorted(expected_dependencies) == sorted(missing_deps), \ - f"Product '{product_name}' expected missing dependencies: {expected_dependencies}; " \ - f"actual missing dependencies {missing_deps}" + assert sorted_expected == sorted_missing, \ + f"Product '{product_name}' expected missing dependencies: {sorted_expected}; " \ + f"actual missing dependencies {sorted_missing}" # fmt:on def __getitem__(self, item: str) -> object: diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_setup_fixture.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_setup_fixture.py index 244ecfb516..c5b02e4f28 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_setup_fixture.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/ap_setup_fixture.py @@ -4,7 +4,7 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT -A fixture for Setting Up Asset Processor Batch workspace for tests in lmbr_test +A fixture for Setting Up Asset Processor Batch workspace for tests """ # Import builtin libraries diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/asset_processor_fixture.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/asset_processor_fixture.py index eea6cb9224..885b4c2959 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/asset_processor_fixture.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/ap_fixtures/asset_processor_fixture.py @@ -4,7 +4,7 @@ For complete copyright and license terms please see the LICENSE at the root of t SPDX-License-Identifier: Apache-2.0 OR MIT -A fixture for using the Asset Processor in lmbr_test, this will stop the asset processor after every test via +A fixture for using the Asset Processor, this will stop the asset processor after every test via the teardown. Using the fixture at class level will stop the asset processor after the suite completes. Using the fixture at test level will stop asset processor after the test completes. Calling this fixture as a test argument will still run the teardown to stop the Asset Processor. """ @@ -15,6 +15,7 @@ import logging # Import LyTestTools import ly_test_tools.o3de.asset_processor as asset_processor_commands +import ly_test_tools.o3de.asset_processor_utils logger = logging.getLogger(__name__) @@ -36,5 +37,8 @@ def asset_processor(request: pytest.fixture, workspace: pytest.fixture) -> asset ap.stop() request.addfinalizer(teardown) + for n in ly_test_tools.o3de.asset_processor_utils.processList: + assert not ly_test_tools.o3de.asset_processor_utils.check_ap_running(n), f"{n} process did not shutdown correctly." + return ap diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt index 5a5809595e..e37a5ed99b 100644 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt @@ -103,6 +103,19 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) AZ::AssetBundlerBatch ) + ly_add_pytest( + NAME AssetPipelineTests.BundleMode + PATH ${CMAKE_CURRENT_LIST_DIR}/bundle_mode_tests.py + EXCLUDE_TEST_RUN_TARGET_FROM_IDE + TEST_SERIAL + TEST_SUITE periodic + RUNTIME_DEPENDENCIES + AZ::AssetProcessor + AZ::AssetBundlerBatch + Legacy::Editor + AutomatedTesting.Assets + ) + ly_add_pytest( NAME AssetPipelineTests.AssetBuilder PATH ${CMAKE_CURRENT_LIST_DIR}/asset_builder_tests.py diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_tests.py index f3483d9c1f..cc79ed7c27 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/asset_processor_batch_tests.py @@ -8,6 +8,8 @@ General Asset Processor Batch Tests """ # Import builtin libraries +from os import listdir + import pytest import logging import os @@ -329,7 +331,7 @@ class TestsAssetProcessorBatch_AllPlatforms(object): # or an expected behavior has changed. Processing bootstrap.cfg sometimes but not other times should not # cause a failure in this test. num_processed_assets = asset_processor_utils.get_num_processed_assets(output) - assert num_processed_assets >= 8, f'Wrong number of successfully processed assets found in output: '\ + assert num_processed_assets >= 6, f'Wrong number of successfully processed assets found in output: '\ '{num_processed_assets}' missing_assets, _ = asset_processor.compare_assets_with_cache() @@ -585,20 +587,18 @@ class TestsAssetProcessorBatch_AllPlatforms(object): 3. Verify that logs exist for both AP Batch & AP GUI """ asset_processor.create_temp_asset_root() + asset_processor.create_temp_log_root() LOG_PATH = { "batch_log": workspace.paths.ap_batch_log(), - "gui_log": workspace.paths.ap_gui_log(), - "job_logs": workspace.paths.ap_job_logs(), + "gui_log": workspace.paths.ap_gui_log() } class LogTimes: batch_log_start_time = 0 gui_log_start_time = 0 - job_logs_start_time = 0 batch_log_final_time = 0 gui_log_final_time = 0 - job_logs_final_time = 0 @staticmethod def Report(): @@ -607,12 +607,10 @@ class TestsAssetProcessorBatch_AllPlatforms(object): Original Times: Batch: {LogTimes.batch_log_start_time} GUI: {LogTimes.gui_log_start_time} - JobLogs:{LogTimes.job_logs_start_time} Post-Run Times: Batch: {LogTimes.batch_log_final_time} GUI: {LogTimes.gui_log_final_time} - JobLogs:{LogTimes.job_logs_final_time} """ ) @@ -629,23 +627,19 @@ class TestsAssetProcessorBatch_AllPlatforms(object): LogTimes.Report() def check_existence(name, path): - assert os.path.exists(path), f"{name} could not be located after running the AP." + assert os.path.exists(path), f"{name} could not be located {path} after running the AP." # Check if log files previously exist and grab their modification times update_times("_start_time") # Run the Batch process - assert asset_processor.batch_process(), "Batch process failed to successfully terminate" + assert asset_processor.batch_process(create_temp_log=False), "Batch process failed to successfully terminate" - asset_processor.gui_process(quitonidle=True) + asset_processor.gui_process(quitonidle=True, create_temp_log=False) # Check that the Logs directory exists (C1564055) check_existence("Logs Directory", workspace.paths.ap_log_dir()) - # Check that the logs and JobLogs directory have updated modified times (C1564056) - for key in LOG_PATH.keys(): - check_existence(key, LOG_PATH[key]) - update_times("_final_time") for key in LOG_PATH.keys(): @@ -708,6 +702,7 @@ class TestsAssetProcessorBatch_AllPlatforms(object): @pytest.mark.BAT @pytest.mark.assetpipeline + @pytest.mark.skip(reason="need to change assets from .slice files to an asset type that can have nested dependencies") def test_validateNestedPreloadDependency_Found(self, asset_processor, ap_setup_fixture, workspace): """ Tests processing of a nested circular dependency and verifies that Asset Processor will return an error @@ -731,3 +726,11 @@ class TestsAssetProcessorBatch_AllPlatforms(object): assert error_line_found, "The error could not be found in the newest run of the AP Batch log." + @pytest.mark.assetpipeline + def test_AssetProcessor_Log_On_Failure(self, asset_processor, ap_setup_fixture, workspace): + asset_processor.prepare_test_environment(ap_setup_fixture["tests_dir"], "test_AP_Logs") + result, output = asset_processor.batch_process(expect_failure=True, capture_output=True) + assert result == False, f'AssetProcessorBatch should have failed because there is a bad asset, output was {output}' + + jobLogs = listdir(workspace.paths.ap_job_logs() + "/test_AP_Logs") + assert not len(jobLogs) == 0, 'No job logs where output during failure.' diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/cgf_to_delete.cgf b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/cgf_to_delete.cgf deleted file mode 100644 index 47571e39a9..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/cgf_to_delete.cgf +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7932fada1523fad4eb6ad5c13111bb4c00d6b0584ec8641060bf25f306b17e69 -size 84632 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/fbx_to_delete.fbx b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/fbx_to_delete.fbx deleted file mode 100644 index 165bc01a54..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/fbx_to_delete.fbx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bf94b548eccb78432db65077124cadf20de975919b181d712fde081f59edd353 -size 38848 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/file_to_delete.prefab b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/file_to_delete.prefab new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/file_to_delete.prefab @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/file_to_delete.txt b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/file_to_delete.txt new file mode 100644 index 0000000000..3d789f8b83 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1568831/file_to_delete.txt @@ -0,0 +1 @@ +to be deleted \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1571774/test_mesh_robot.cgf b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1571774/test_mesh_robot.cgf deleted file mode 100644 index fcb3513ed0..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1571774/test_mesh_robot.cgf +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1675471085483e0bd252a5bdd4db1b365c66f16ac32a6e6dd70bb1c23df83fa5 -size 24160 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1591338/test_mesh_robot.cgf b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1591338/test_mesh_robot.cgf deleted file mode 100644 index fcb3513ed0..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1591338/test_mesh_robot.cgf +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1675471085483e0bd252a5bdd4db1b365c66f16ac32a6e6dd70bb1c23df83fa5 -size 24160 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1591338/test_mesh_robot.prefab b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1591338/test_mesh_robot.prefab new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/C1591338/test_mesh_robot.prefab @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_AP_Logs/BadAsset.fbx b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_AP_Logs/BadAsset.fbx new file mode 100644 index 0000000000..cf1bbaac8a --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_AP_Logs/BadAsset.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c6b33c6137d6bd8c696f180c30a23089c95c1af398a630b4b13e080bec3254d +size 18220 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_AddSameAssetsDifferentNames_ShouldProcess/Energy_Background_One.png b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_AddSameAssetsDifferentNames_ShouldProcess/Energy_Background_One.png deleted file mode 100644 index e012e887e7..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_AddSameAssetsDifferentNames_ShouldProcess/Energy_Background_One.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:21df6ab62f2572daa6d0710ad6819a728ee751a62170903a6773563d614aa51f -size 15191 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_AddSameAssetsDifferentNames_ShouldProcess/Energy_Background_Two.png b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_AddSameAssetsDifferentNames_ShouldProcess/Energy_Background_Two.png deleted file mode 100644 index e012e887e7..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_AddSameAssetsDifferentNames_ShouldProcess/Energy_Background_Two.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:21df6ab62f2572daa6d0710ad6819a728ee751a62170903a6773563d614aa51f -size 15191 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessAndDeleteCache_APBatchShouldReprocess/Init.bnk b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessAndDeleteCache_APBatchShouldReprocess/Init.bnk deleted file mode 100644 index f525a402af..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessAndDeleteCache_APBatchShouldReprocess/Init.bnk +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f0b4750147acbcc6229a1043ed47ee48e7703a5241f27fc72976e95741144369 -size 2372 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessAndDeleteCache_APBatchShouldReprocess/entity_icon_example_2.png b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessAndDeleteCache_APBatchShouldReprocess/entity_icon_example_2.png deleted file mode 100644 index 47e2d35331..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessAndDeleteCache_APBatchShouldReprocess/entity_icon_example_2.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a036c3763b96079dde8505ad6995f8b717a777c78d7a434ac8de6f1ccb5d8dd1 -size 4327 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessByBothApAndBatch_Md5ShouldMatch/SoundWave_1.png b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessByBothApAndBatch_Md5ShouldMatch/SoundWave_1.png deleted file mode 100644 index b3b8fba4a1..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessByBothApAndBatch_Md5ShouldMatch/SoundWave_1.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cf55a4372d82d70d8cdcc95468367cd4fad7649d3fb99c4d85049977b506885c -size 13429 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessByBothApAndBatch_Md5ShouldMatch/extra_file.prefab b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessByBothApAndBatch_Md5ShouldMatch/extra_file.prefab new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessByBothApAndBatch_Md5ShouldMatch/extra_file.prefab @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessByBothApAndBatch_Md5ShouldMatch/test_cube.fbx b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessByBothApAndBatch_Md5ShouldMatch/test_cube.fbx deleted file mode 100644 index 57fa7b327c..0000000000 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/assets/test_ProcessByBothApAndBatch_Md5ShouldMatch/test_cube.fbx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1821d000b583821fd36ec73f91073d51ae90762225a34a81a74771c36236b5e1 -size 12963 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/bundle_mode_in_editor_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/bundle_mode_in_editor_tests.py new file mode 100644 index 0000000000..33a55d6601 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/bundle_mode_in_editor_tests.py @@ -0,0 +1,20 @@ +""" +Copyright (c) Contributors to the Open 3D Engine Project. +For complete copyright and license terms please see the LICENSE at the root of this distribution. + +SPDX-License-Identifier: Apache-2.0 OR MIT +""" +import azlmbr.bus +import azlmbr.editor +import azlmbr.legacy.general +import sys + +# Print out the passed in bundle_path, so the outer test can verify this was sent in correctly +bundle_path = sys.argv[1] +print('Bundle mode test running with path {}'.format(sys.argv[1])) + +# Turn on bundle mode. This will trigger some printouts that the outer test logic will validate. +azlmbr.legacy.general.set_cvar_integer("sys_report_files_not_found_in_paks", 1) +azlmbr.legacy.general.run_console(f"loadbundles {bundle_path}") + +azlmbr.editor.EditorToolsApplicationRequestBus(azlmbr.bus.Broadcast, 'ExitNoPrompt') diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/bundle_mode_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/bundle_mode_tests.py new file mode 100644 index 0000000000..af92bb1773 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/bundle_mode_tests.py @@ -0,0 +1,93 @@ +""" +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 logging +import sys +import time +pytest.importorskip('ly_test_tools') + +import ly_test_tools.environment.file_system as fs +import ly_test_tools.environment.waiter as waiter +import ly_test_tools.log.log_monitor + +from ..ap_fixtures.asset_processor_fixture import asset_processor as asset_processor +from ..ap_fixtures.bundler_batch_setup_fixture import bundler_batch_setup_fixture as bundler_batch_helper +from ..ap_fixtures.timeout_option_fixture import timeout_option_fixture as timeout + +@pytest.mark.SUITE_periodic +@pytest.mark.parametrize('launcher_platform', ['windows_editor']) +@pytest.mark.parametrize('project', ['AutomatedTesting']) +@pytest.mark.parametrize('level', ['auto_test']) +class TestBundleMode(object): + def test_bundle_mode_with_levels_mounts_bundles_correctly(self, request, editor, level, launcher_platform, + asset_processor, workspace, bundler_batch_helper): + level_pak = os.path.join("levels", level, "level.pak") + + bundles_folder = os.path.join(workspace.paths.project(), "Bundles") + bundle_request_path = os.path.join(bundles_folder, "bundle.pak") + bundle_result_path = os.path.join(bundles_folder, + bundler_batch_helper.platform_file_name( + "bundle.pak", workspace.asset_processor_platform)) + + # Create target 'Bundles' folder if it doesn't exist + if not os.path.exists(bundles_folder): + os.mkdir(bundles_folder) + # Delete target bundle file if it already exists + if os.path.exists(bundle_result_path): + fs.delete([bundle_result_path], True, False) + + # Make asset list file to use in the bundle + bundler_batch_helper.call_assetLists( + addSeed=level_pak, + assetListFile=bundler_batch_helper["asset_info_file_request"], + ) + + # Make bundle in /Bundles + bundler_batch_helper.call_bundles( + assetListFile=bundler_batch_helper["asset_info_file_result"], + outputBundlePath=bundle_request_path, + maxSize="2048", + ) + + # Ensure the bundle was created + assert os.path.exists(bundle_result_path), f"Bundle was not created at location: {bundle_result_path}" + + # The editor flips the slash direction in some of the printouts + bundle_result_path_editor_separator = bundle_result_path.replace('\\', '/') + + expected_lines = [ + # A beginning of test printout can help debug where failures occur, if this line is missing + # then the Editor didn't launch, didn't run the Python test, or didn't pass in the right parameter + f'Bundle mode test running with path {bundles_folder}', + # These printouts happen in response to the loadbundles call, and verify this bundle is actually loaded + f"[CONSOLE] Executing console command 'loadbundles {bundles_folder}'", + f'(BundlingSystem) - Loading bundles from {bundles_folder} of type .pak', + f'(Archive) - Opening archive file {bundle_result_path_editor_separator}', + ] + unexpected_lines = [] + + timeout = 180 + halt_on_unexpected = False + test_directory = os.path.join(os.path.dirname(__file__)) + test_file = os.path.join(test_directory, 'bundle_mode_in_editor_tests.py') + editor.args.extend(['-NullRenderer', '-rhi=Null', "--skipWelcomeScreenDialog", + "--autotest_mode", "--runpythontest", test_file, "--runpythonargs", bundles_folder]) + + with editor.start(launch_ap=True): + editor_log_file = os.path.join(editor.workspace.paths.project_log(), 'Editor.log') + log_monitor = ly_test_tools.log.log_monitor.LogMonitor(editor, editor_log_file) + waiter.wait_for( + lambda: editor.is_alive(), + timeout, + exc=("Log file '{}' was never opened by another process.".format(editor_log_file)), + interval=1) + log_monitor.monitor_log_for_lines(expected_lines, unexpected_lines, halt_on_unexpected, timeout) + + # Delete the bundle created and used in this test + fs.delete([bundle_result_path], True, False) diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/missing_dependency_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/missing_dependency_tests.py index 7159b2e83f..52321fceea 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/missing_dependency_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/missing_dependency_tests.py @@ -120,31 +120,29 @@ class TestsMissingDependencies_WindowsAndMac(object): # Relative path to the txt file with missing dependencies expected_product = f"testassets\\validuuidsnotdependency.txt" - self._asset_processor.add_source_folder_assets(f"{self._workspace.project}\\Objects\\LumberTank") - self._asset_processor.add_source_folder_assets(f"{self._workspace.project}\\Objects\\Characters\\Jack") # Expected missing dependencies expected_dependencies = [ # String Asset # - ('1CB10C43F3245B93A294C602ADEF95F9:[0', '{1CB10C43-F324-5B93-A294-C602ADEF95F9}:0'), + # InvalidAssetIdNoReport.txt + ('E68A85B0-131D-5A82-B2D5-BC58EE4062AE', '{E68A85B0-131D-5A82-B2D5-BC58EE4062AE}:0'), + # InvalidRelativePathsNoReport.txt + ('B3EF12DD306C520EB0A8A6B0D031A195', '{B3EF12DD-306C-520E-B0A8-A6B0D031A195}:0'), + # SelfReferenceUUID.txt ('33bcee02F3225688ABEE534F6058593F', '{33BCEE02-F322-5688-ABEE-534F6058593F}:0'), - ('345E5C660D6254FF8D0F7C8EE66A2249', '{345E5C66-0D62-54FF-8D0F-7C8EE66A2249}:3e8'), - ('345E5C660D6254FF8D0F7C8EE66A2249', '{345E5C66-0D62-54FF-8D0F-7C8EE66A2249}:3ea'), - ('345E5C660D6254FF8D0F7C8EE66A2249', '{345E5C66-0D62-54FF-8D0F-7C8EE66A2249}:3eb'), - ('37108522F50459499CD6C8D47A960CF1', '{37108522-F504-5949-9CD6-C8D47A960CF1}:3e8'), - ('37108522F50459499CD6C8D47A960CF1', '{37108522-F504-5949-9CD6-C8D47A960CF1}:3ea'), - ('37108522F50459499CD6C8D47A960CF1', '{37108522-F504-5949-9CD6-C8D47A960CF1}:3eb'), - ('6BDE282B49C957F7B0714B26579BCA9A', '{6BDE282B-49C9-57F7-B071-4B26579BCA9A}:0'), - ('747D31D71E62553592226173C49CF97E', '{747D31D7-1E62-5535-9222-6173C49CF97E}:1'), - ('747D31D71E62553592226173C49CF97E', '{747D31D7-1E62-5535-9222-6173C49CF97E}:2'), - ('A26C73D1837E5AE59E68F916FA7C3699', '{A26C73D1-837E-5AE5-9E68-F916FA7C3699}:3e8'), - ('A26C73D1837E5AE59E68F916FA7C3699', '{A26C73D1-837E-5AE5-9E68-F916FA7C3699}:3ea'), - ('A26C73D1837E5AE59E68F916FA7C3699', '{A26C73D1-837E-5AE5-9E68-F916FA7C3699}:3eb'), - ('B076CDDC-14DF-50F4-A5E9-7518ABB3E851', '{B076CDDC-14DF-50F4-A5E9-7518ABB3E851}:0'), - ('C67BEA9F-09FF-59AA-A7F0-A52B8F987508', '{C67BEA9F-09FF-59AA-A7F0-A52B8F987508}:3e8'), - ('C67BEA9F-09FF-59AA-A7F0-A52B8F987508', '{C67BEA9F-09FF-59AA-A7F0-A52B8F987508}:3ea'), - ('C67BEA9F-09FF-59AA-A7F0-A52B8F987508', '{C67BEA9F-09FF-59AA-A7F0-A52B8F987508}:3eb'), - ('C67BEA9F-09FF-59AA-A7F0-A52B8F987508', '{C67BEA9F-09FF-59AA-A7F0-A52B8F987508}:3ec'), - ('D92C4661C8985E19BD3597CB2318CFA6:[0', '{D92C4661-C898-5E19-BD35-97CB2318CFA6}:0'), + # SelfReferencePath.txt + ('DD587FBE-16C8-5B98-AE3C-A9F8750B2692', '{DD587FBE-16C8-5B98-AE3C-A9F8750B2692}:0'), + # InvalidUUIDNoReport.txt + ('837412DF-D05F-576D-81AA-ACF360463749', '{837412DF-D05F-576D-81AA-ACF360463749}:0'), + # MaxIteration31Deep.txt + ('3F642A0FDC825696A70A1DA5709744DF', '{3F642A0F-DC82-5696-A70A-1DA5709744DF}:0'), + # OnlyMatchesCorrectLengthUUIDs.txt + ('2545AD8B-1B9B-5F93-859D-D8DC1DC2B480', '{2545AD8B-1B9B-5F93-859D-D8DC1DC2B480}:0'), + # WildcardScanTest1.txt + ('1CB10C43F3245B93A294C602ADEF95F9:[0', '{1CB10C43-F324-5B93-A294-C602ADEF95F9}:0'), + # RelativeProductPathsNotDependencies.txt + ('B772953CA08A5D209491530E87D11504:[0', '{B772953C-A08A-5D20-9491-530E87D11504}:0'), + # WildcardScanTest2.txt + ('D92C4661C8985E19BD3597CB2318CFA6', '{D92C4661-C898-5E19-BD35-97CB2318CFA6}:0'), ] self.do_missing_dependency_test(expected_product, expected_dependencies, "%ValidUUIDsNotDependency.txt") @@ -187,8 +185,11 @@ class TestsMissingDependencies_WindowsAndMac(object): # Expected missing dependencies expected_dependencies = [ # String Asset # - ('2ef92b8D044E5C278E2BB1AC0374A4E7:1003', '{2EF92B8D-044E-5C27-8E2B-B1AC0374A4E7}:3eb'), + # _dev_Red.tif + ('2ef92b8D044E5C278E2BB1AC0374A4E7:1000', '{2EF92B8D-044E-5C27-8E2B-B1AC0374A4E7}:3e8'), + # _dev_Purple.tif ('A2482826-053D-5634-A27B-084B1326AAE5}:[1002', '{A2482826-053D-5634-A27B-084B1326AAE5}:3ea'), + # _dev_White.tif ('D83B36F1-61A6-5001-B191-4D0CE282E236}-1002', '{D83B36F1-61A6-5001-B191-4D0CE282E236}:3ea'), ] @@ -237,11 +238,10 @@ class TestsMissingDependencies_WindowsAndMac(object): expected_dependencies = [ # String Asset # ('TestAssets\\WildcardScanTest1.txt', '{1CB10C43-F324-5B93-A294-C602ADEF95F9}:0'), - ('libs/particles/milestone2PARTICLES.XML', '{6BDE282B-49C9-57F7-B071-4B26579BCA9A}:0'), + ('TESTASSETS/ReportONEmISSINGdEPENDENCY.tXT', '{BE5E2373-245E-59E4-B4C6-7370EEAA2EFD}:0'), ('textures/_dev_Purple.tif', '{A2482826-053D-5634-A27B-084B1326AAE5}:3e8'), ('textures/_dev_Purple.tif', '{A2482826-053D-5634-A27B-084B1326AAE5}:3ea'), - ('textures/_dev_Purple.tif', '{A2482826-053D-5634-A27B-084B1326AAE5}:3eb'), - ('project.json', '{B076CDDC-14DF-50F4-A5E9-7518ABB3E851}:0'), + ('TestAssets/InvalidAssetIdNoReport.txt', '{E68A85B0-131D-5A82-B2D5-BC58EE4062AE}:0'), ('TestAssets/RelativeProductPathsNotDependencies.txt', '{B772953C-A08A-5D20-9491-530E87D11504}:0'), ] @@ -282,29 +282,31 @@ class TestsMissingDependencies_WindowsAndMac(object): 2. Set the expected missing dependencies 3. Execute test """ - - self._asset_processor.add_source_folder_assets(f"Gems\\LyShineExamples\\Assets\\UI\\Fonts\\LyShineExamples") - self._asset_processor.add_scan_folder(f"Gems\\LyShineExamples\\Assets") # Relative path to the txt file with missing dependencies as product paths expected_product = f"testassets\\relativeproductpathsnotdependencies.txt" expected_dependencies = [ # String Asset # - ('materials/floor_tile.mtl', '{0EFF5E4A-F544-5D87-8696-6DDFA62D6063}:0'), - ('materials/am_grass1.mtl', '{1151F14D-38A6-5579-888A-BE3139882E68}:0'), - ('2ef92b8D044E5C278E2BB1AC0374A4E7:1002', '{2EF92B8D-044E-5C27-8E2B-B1AC0374A4E7}:3ea'), - ('textures/milestone2/ama_grey_02.tif.streamingimage', '{3EE80AAD-EB9C-56BD-9E9C-65410578998C}:3e8'), - ('ui/milestone2menu.uicanvas', '{445D9AF3-6CA5-5281-82A9-5C570BCD1DB8}:0'), - ('libs/particles/milestone2particles.xml', '{6BDE282B-49C9-57F7-B071-4B26579BCA9A}:0'), - ('textures/_dev_yellow_light.tif.1002.imagemipchain', '{6C40868F-3FC1-5115-96EA-DD0A9E33DEE4}:3ea'), - ('textures\\\\_dev_tan.tif.streamingimage', '{8F2BCEF5-C8CE-5B80-8103-8C1D694D012C}:3e8'), - ('materials/am_rockground.mtl', '{A1DA3D05-A020-5BB5-A608-C4812B7BD733}:0'), ('textures/_dev_purple.tif.streamingimage', '{A2482826-053D-5634-A27B-084B1326AAE5}:3e8'), - ('A2482826-053D-5634-A27B-084B1326AAE5}:[1002', '{A2482826-053D-5634-A27B-084B1326AAE5}:3ea'), - ('project.json', '{B076CDDC-14DF-50F4-A5E9-7518ABB3E851}:0'), - ('CEAA362B4E505BCEB827CB92EF40A50E', '{CEAA362B-4E50-5BCE-B827-CB92EF40A50E}:1'), - ('CEAA362B4E505BCEB827CB92EF40A50E', '{CEAA362B-4E50-5BCE-B827-CB92EF40A50E}:2'), + ('textures\\_dev_stucco.tif.streamingimage', '{70114D85-D712-5AEB-A816-8FE3A37087AF}:3e8'), + ('textures\\\\_dev_tan.tif.streamingimage', '{8F2BCEF5-C8CE-5B80-8103-8C1D694D012C}:3e8'), ('TEXTURES/_DEV_WHITE.tif.streamingimage', '{D83B36F1-61A6-5001-B191-4D0CE282E236}:3e8'), + ('textures/_dev_yellow_light.tif.1002.imagemipchain', '{6C40868F-3FC1-5115-96EA-DD0A9E33DEE4}:3ea'), + ('textures/_dev_woodland.tif.1002.imagemipchain', '{F3DD193C-5845-569C-A974-AA338B30CF86}:3ea'), ('textures/_dev_woodland.tif.streamingimage', '{F3DD193C-5845-569C-A974-AA338B30CF86}:3e8'), + ('textures/_dev_yellow_light.tif.streamingimage', '{6C40868F-3FC1-5115-96EA-DD0A9E33DEE4}:3e8'), + ('textures/_dev_yellow_med.tif.1002.imagemipchain', '{BB4DFF57-52BD-525B-9628-68232E31802C}:3ea'), + ('textures/lights/flare01.tif.streamingimage', '{D8E49CC4-C743-5F31-A1EC-4AA89163B8F5}:3e8'), + # SelfReferenceUUID.txt + ('33BCEE02-F322-5688-ABEE-534F6058593F', '{33BCEE02-F322-5688-ABEE-534F6058593F}:0'), + ('textures/test_texture_sequence/test_texture_sequence000.png.streamingimage', '{6CC90BEE-0A9F-57A8-9013-7C1D643C0E8E}:3e8'), + # _dev_red.tif.streamingimage + ('2ef92b8D044E5C278E2BB1AC0374A4E7:1002', '{2EF92B8D-044E-5C27-8E2B-B1AC0374A4E7}:3ea'), + # SelfReferenceAssetID.txt + ('785A05D2-483E-5B43-A2B9-92ACDAE6E938', '{785A05D2-483E-5B43-A2B9-92ACDAE6E938}:0'), + ('textures/test_texture_sequence/test_texture_sequence001.png.streamingimage', '{8A8A37DD-01B9-5D70-92E4-925E2C0FE826}:3e8'), + # _dev_purple.tif.1002.imagemipchain + ('A2482826-053D-5634-A27B-084B1326AAE5}:[1002', '{A2482826-053D-5634-A27B-084B1326AAE5}:3ea'), + ('textures/_dev_purple_glass.tif.1002.imagemipchain', '{2FCDD831-77D1-5BE1-A4C8-CA47E4F89F19}:3ea'), ] self.do_missing_dependency_test(expected_product, expected_dependencies, diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/Motion/Jack_Idle_Aim_ZUp.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/Motion/SceneDebug/Jack_Idle_Aim_ZUp.dbgsg similarity index 99% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/Motion/Jack_Idle_Aim_ZUp.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/Motion/SceneDebug/Jack_Idle_Aim_ZUp.dbgsg index 8b0132a448..c8c5c8697b 100644 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/Motion/Jack_Idle_Aim_ZUp.dbgsg +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/Motion/SceneDebug/Jack_Idle_Aim_ZUp.dbgsg @@ -242,7 +242,7 @@ Node Type: BoneData BasisX: < 1.000000, -0.000000, 0.000000> BasisY: < 0.000000, 1.000000, 0.000000> BasisZ: <-0.000000, -0.000000, 1.000000> - Transl: < 0.152547, 0.043345, 0.090955> + Transl: < 0.152547, 0.043345, 0.090954> Node Name: animation Node Path: RootNode.jack_root.Bip01__pelvis.spine1.spine2.animation @@ -544,7 +544,7 @@ Node Type: BoneData Node Name: animation Node Path: RootNode.jack_root.Bip01__pelvis.spine1.spine2.spine3.l_shldr.l_upArm.l_upArmRoll.animation Node Type: AnimationData - KeyFrames: Count 195. Hash: 15529789169672670472 + KeyFrames: Count 195. Hash: 8781707605519483934 TimeStepBetweenFrames: 0.033333 Node Name: transform @@ -710,7 +710,7 @@ Node Type: BoneData BasisX: < 0.514369, 0.855813, 0.054857> BasisY: < 0.088153, 0.010863, -0.996047> BasisZ: <-0.853026, 0.517172, -0.069855> - Transl: <-0.247306, -0.062325, 0.878373> + Transl: <-0.247306, -0.062325, 0.878372> Node Name: animation Node Path: RootNode.jack_root.Bip01__pelvis.spine1.spine2.spine3.r_shldr.r_upArm.r_loArm.r_loArmRoll.animation @@ -857,7 +857,7 @@ Node Type: BoneData BasisX: < 0.329257, 0.944038, -0.019538> BasisY: < 0.465563, -0.180309, -0.866452> BasisZ: <-0.821487, 0.276189, -0.498877> - Transl: <-0.255124, -0.049696, 0.794467> + Transl: <-0.255124, -0.049696, 0.794466> Node Name: animation Node Path: RootNode.jack_root.Bip01__pelvis.spine1.spine2.spine3.l_shldr.l_upArm.l_loArm.l_hand.l_metacarpal.animation @@ -939,7 +939,6 @@ Node Type: AnimationData Node Name: transform Node Path: RootNode.jack_root.Bip01__pelvis.spine1.spine2.spine3.r_shldr.r_upArm.r_loArm.r_hand.r_index1.transform - Node Type: TransformData Matrix: BasisX: < 0.939162, 0.133704, -0.316383> @@ -954,7 +953,7 @@ Node Type: BoneData BasisX: <-0.102387, -0.418082, -0.902621> BasisY: < 0.928150, 0.286271, -0.237880> BasisZ: < 0.357847, -0.862123, 0.358732> - Transl: < 0.187367, 0.698324, 1.467209> + Transl: < 0.187367, 0.698323, 1.467209> Node Name: animation Node Path: RootNode.jack_root.Bip01__pelvis.spine1.spine2.spine3.r_shldr.r_upArm.r_loArm.r_hand.r_mid1.animation @@ -1513,3 +1512,4 @@ Node Type: TransformData BasisY: < 0.000000, 0.229519, -0.973304> BasisZ: < 0.000000, 0.973304, 0.229519> Transl: < 0.000000, -0.023770, 0.000000> + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/Motion/SceneDebug/jack_idle_aim_zup.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/Motion/SceneDebug/jack_idle_aim_zup.dbgsg.xml new file mode 100644 index 0000000000..caaf3810fe --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/Motion/SceneDebug/jack_idle_aim_zup.dbgsg.xml @@ -0,0 +1,3223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshMultipleMaterials/single_mesh_multiple_materials.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshMultipleMaterials/SceneDebug/single_mesh_multiple_materials.dbgsg similarity index 100% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshMultipleMaterials/single_mesh_multiple_materials.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshMultipleMaterials/SceneDebug/single_mesh_multiple_materials.dbgsg diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshMultipleMaterials/SceneDebug/single_mesh_multiple_materials.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshMultipleMaterials/SceneDebug/single_mesh_multiple_materials.dbgsg.xml new file mode 100644 index 0000000000..80b80fd67c --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshMultipleMaterials/SceneDebug/single_mesh_multiple_materials.dbgsg.xml @@ -0,0 +1,849 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshOneMaterial/onemeshonematerial.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshOneMaterial/SceneDebug/onemeshonematerial.dbgsg similarity index 100% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshOneMaterial/onemeshonematerial.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshOneMaterial/SceneDebug/onemeshonematerial.dbgsg diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshOneMaterial/SceneDebug/onemeshonematerial.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshOneMaterial/SceneDebug/onemeshonematerial.dbgsg.xml new file mode 100644 index 0000000000..87cc4fe260 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/OneMeshOneMaterial/SceneDebug/onemeshonematerial.dbgsg.xml @@ -0,0 +1,519 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/SceneDebug/shaderball.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/SceneDebug/shaderball.dbgsg new file mode 100644 index 0000000000..7d070fcea0 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/SceneDebug/shaderball.dbgsg @@ -0,0 +1,3111 @@ +ProductName: shaderball.dbgsg +debugSceneGraphVersion: 1 +shaderball +Node Name: RootNode +Node Path: RootNode +Node Type: RootBoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, 1.000000, 0.000000> + BasisZ: < 0.000000, 0.000000, 1.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: ShaderBall_1m +Node Path: RootNode.ShaderBall_1m +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: transform +Node Path: RootNode.ShaderBall_1m.transform +Node Type: TransformData + Matrix: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: InnerPortion +Node Path: RootNode.ShaderBall_1m.InnerPortion +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: MaterialBase +Node Path: RootNode.ShaderBall_1m.MaterialBase +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: InlayRings +Node Path: RootNode.ShaderBall_1m.InlayRings +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: MainSphere +Node Path: RootNode.ShaderBall_1m.MainSphere +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: HubCap_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1 +Node Type: MeshData + Positions: Count 20476. Hash: 5411361036988924549 + Normals: Count 20476. Hash: 5915154682063029054 + FaceList: Count 8676. Hash: 9142863582186575896 + FaceMaterialIds: Count 8676. Hash: 723360536895379791 + +Node Name: HubCap_2 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: HubCap_1_optimized +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1_optimized +Node Type: MeshData + Positions: Count 4724. Hash: 448283466401665158 + Normals: Count 4724. Hash: 345267294337234954 + FaceList: Count 8676. Hash: 11155608373229651496 + FaceMaterialIds: Count 8676. Hash: 723360536895379791 + +Node Name: InnerSphere_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1 +Node Type: MeshData + Positions: Count 19800. Hash: 17342106809405761922 + Normals: Count 19800. Hash: 602384960091561079 + FaceList: Count 9800. Hash: 15975352410309879244 + FaceMaterialIds: Count 9800. Hash: 2219364576630417284 + +Node Name: InnerSphere_2 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: InnerSphere_1_optimized +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1_optimized +Node Type: MeshData + Positions: Count 5846. Hash: 18229431498904963984 + Normals: Count 5846. Hash: 12980164457801827192 + FaceList: Count 9800. Hash: 2914108932212582430 + FaceMaterialIds: Count 9800. Hash: 2219364576630417284 + +Node Name: InnerCone_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1 +Node Type: MeshData + Positions: Count 192. Hash: 3022774266117638288 + Normals: Count 192. Hash: 12863565187316537150 + FaceList: Count 64. Hash: 1255131899577053537 + FaceMaterialIds: Count 64. Hash: 6312841653578246165 + +Node Name: InnerCone_2 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: InnerCone_1_optimized +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1_optimized +Node Type: MeshData + Positions: Count 65. Hash: 832325255726840940 + Normals: Count 65. Hash: 14121159448916141450 + FaceList: Count 64. Hash: 9660474080562375138 + FaceMaterialIds: Count 64. Hash: 6312841653578246165 + +Node Name: InnerPost_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1 +Node Type: MeshData + Positions: Count 4864. Hash: 7530018531436061848 + Normals: Count 4864. Hash: 534420091635424803 + FaceList: Count 2432. Hash: 4330575616942580147 + FaceMaterialIds: Count 2432. Hash: 12393250111858627709 + +Node Name: InnerPost_2 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: InnerPost_1_optimized +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1_optimized +Node Type: MeshData + Positions: Count 1300. Hash: 15277132209442728123 + Normals: Count 1300. Hash: 1247055552073340932 + FaceList: Count 2432. Hash: 16808521883586752319 + FaceMaterialIds: Count 2432. Hash: 12393250111858627709 + +Node Name: InnerBaseCuff_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1 +Node Type: MeshData + Positions: Count 4096. Hash: 1814004361979755447 + Normals: Count 4096. Hash: 1760582409511017750 + FaceList: Count 2048. Hash: 6642755656211284824 + FaceMaterialIds: Count 2048. Hash: 2795877824940392899 + +Node Name: InnerBaseCuff_2 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: InnerBaseCuff_1_optimized +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1_optimized +Node Type: MeshData + Positions: Count 1122. Hash: 15009434720129565864 + Normals: Count 1122. Hash: 7245598986723209052 + FaceList: Count 2048. Hash: 2887888015166354330 + FaceMaterialIds: Count 2048. Hash: 2795877824940392899 + +Node Name: Inset_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1 +Node Type: MeshData + Positions: Count 1536. Hash: 6247721054948161820 + Normals: Count 1536. Hash: 16362051041722971623 + FaceList: Count 768. Hash: 3142075668387852387 + FaceMaterialIds: Count 768. Hash: 2979441869813298271 + +Node Name: Inset_2 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: Inset_1_optimized +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1_optimized +Node Type: MeshData + Positions: Count 448. Hash: 9520190263881589378 + Normals: Count 448. Hash: 8658432920439039136 + FaceList: Count 768. Hash: 8621713142583851338 + FaceMaterialIds: Count 768. Hash: 2979441869813298271 + +Node Name: BottomCap_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1 +Node Type: MeshData + Positions: Count 960. Hash: 13502046855286963834 + Normals: Count 960. Hash: 7165134632415312413 + FaceList: Count 448. Hash: 11458684524699877690 + FaceMaterialIds: Count 448. Hash: 15444662993354423801 + +Node Name: BottomCap_2 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: BottomCap_1_optimized +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1_optimized +Node Type: MeshData + Positions: Count 257. Hash: 6437758550313952689 + Normals: Count 257. Hash: 9382966961287919705 + FaceList: Count 448. Hash: 16093592375171717669 + FaceMaterialIds: Count 448. Hash: 15444662993354423801 + +Node Name: InnerCushion_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1 +Node Type: MeshData + Positions: Count 54208. Hash: 17548679297809443982 + Normals: Count 54208. Hash: 18279651495381460361 + FaceList: Count 27104. Hash: 12714006452420198903 + FaceMaterialIds: Count 27104. Hash: 12674758055018775830 + +Node Name: InnerCushion_2 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: InnerCushion_1_optimized +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1_optimized +Node Type: MeshData + Positions: Count 13860. Hash: 15965614532178328362 + Normals: Count 13860. Hash: 7297376140184766040 + FaceList: Count 27104. Hash: 2523751700832022798 + FaceMaterialIds: Count 27104. Hash: 12674758055018775830 + +Node Name: OuterBaseCuff_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1 +Node Type: MeshData + Positions: Count 20048. Hash: 3861768905934340307 + Normals: Count 20048. Hash: 12292053423401502106 + FaceList: Count 10024. Hash: 4821138438569111218 + FaceMaterialIds: Count 10024. Hash: 9849297365743356453 + +Node Name: OuterBaseCuff_2 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: OuterBaseCuff_1_optimized +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1_optimized +Node Type: MeshData + Positions: Count 5206. Hash: 5201932106801476439 + Normals: Count 5206. Hash: 15696187265416627056 + FaceList: Count 10024. Hash: 10907381836011223214 + FaceMaterialIds: Count 10024. Hash: 9849297365743356453 + +Node Name: RingLeft_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1 +Node Type: MeshData + Positions: Count 2560. Hash: 10944366330558725569 + Normals: Count 2560. Hash: 11471590896496428199 + FaceList: Count 1280. Hash: 2548580276766813978 + FaceMaterialIds: Count 1280. Hash: 6371267399123018661 + +Node Name: RingLeft_2 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: RingLeft_1_optimized +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1_optimized +Node Type: MeshData + Positions: Count 720. Hash: 14374869925777029719 + Normals: Count 720. Hash: 2252820527750115179 + FaceList: Count 1280. Hash: 17583744445000895264 + FaceMaterialIds: Count 1280. Hash: 6371267399123018661 + +Node Name: RingRight_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1 +Node Type: MeshData + Positions: Count 2560. Hash: 17639843025153488175 + Normals: Count 2560. Hash: 8564843488923790338 + FaceList: Count 1280. Hash: 2548580276766813978 + FaceMaterialIds: Count 1280. Hash: 6371267399123018661 + +Node Name: RingRight_2 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: RingRight_1_optimized +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1_optimized +Node Type: MeshData + Positions: Count 720. Hash: 17033597990859129189 + Normals: Count 720. Hash: 14263134585377771060 + FaceList: Count 1280. Hash: 13824345071081010014 + FaceMaterialIds: Count 1280. Hash: 6371267399123018661 + +Node Name: RightHub_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1 +Node Type: MeshData + Positions: Count 6304. Hash: 11021385291123971383 + Normals: Count 6304. Hash: 10267084099841111837 + FaceList: Count 3152. Hash: 17728162485525521181 + FaceMaterialIds: Count 3152. Hash: 17405713692885844041 + +Node Name: RightHub_2 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: RightHub_1_optimized +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1_optimized +Node Type: MeshData + Positions: Count 1617. Hash: 9820946434895369083 + Normals: Count 1617. Hash: 16951123277321747448 + FaceList: Count 3152. Hash: 15437295155178226041 + FaceMaterialIds: Count 3152. Hash: 17405713692885844041 + +Node Name: LeftHub_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1 +Node Type: MeshData + Positions: Count 6304. Hash: 7564731352768958355 + Normals: Count 6304. Hash: 8133714814490222392 + FaceList: Count 3152. Hash: 17728162485525521181 + FaceMaterialIds: Count 3152. Hash: 17405713692885844041 + +Node Name: LeftHub_2 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: LeftHub_1_optimized +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1_optimized +Node Type: MeshData + Positions: Count 1617. Hash: 8811470420139443913 + Normals: Count 1617. Hash: 11224729257132823435 + FaceList: Count 3152. Hash: 16696390019846851737 + FaceMaterialIds: Count 3152. Hash: 17405713692885844041 + +Node Name: Inside_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1 +Node Type: MeshData + Positions: Count 3520. Hash: 13476777876937219698 + Normals: Count 3520. Hash: 10561277746451021236 + FaceList: Count 1760. Hash: 1345243954764462275 + FaceMaterialIds: Count 1760. Hash: 3100204266221257056 + +Node Name: Inside_2 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: Inside_1_optimized +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1_optimized +Node Type: MeshData + Positions: Count 960. Hash: 1074011803485396393 + Normals: Count 960. Hash: 16318911598614464642 + FaceList: Count 1760. Hash: 3382287220404120115 + FaceMaterialIds: Count 1760. Hash: 3100204266221257056 + +Node Name: MainOuterSphere_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1 +Node Type: MeshData + Positions: Count 18912. Hash: 15956257973657753552 + Normals: Count 18912. Hash: 9328348641512406334 + FaceList: Count 9456. Hash: 9836933646038198686 + FaceMaterialIds: Count 9456. Hash: 13982281543095132650 + +Node Name: MainOuterSphere_2 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_2 +Node Type: BoneData + WorldTransform: + BasisX: < 1.000000, 0.000000, 0.000000> + BasisY: < 0.000000, -0.000000, 1.000000> + BasisZ: < 0.000000, -1.000000, -0.000000> + Transl: < 0.000000, 0.000000, 0.000000> + +Node Name: MainOuterSphere_1_optimized +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1_optimized +Node Type: MeshData + Positions: Count 4891. Hash: 9579089515723090907 + Normals: Count 4891. Hash: 10089610259011330329 + FaceList: Count 9456. Hash: 13541126452398082145 + FaceMaterialIds: Count 9456. Hash: 13982281543095132650 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 20476. Hash: 10688945422788452939 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 20476. Hash: 1387390223232454000 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 20476. Hash: 4424021631256816544 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 20476. Hash: 9768152532901400557 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 20476. Hash: 4890305528292926235 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 20476. Hash: 309820643999247955 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 20476. Hash: 10688945422788452939 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 20476. Hash: 1387390223232454000 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 4724. Hash: 10265340188340999982 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 4724. Hash: 6393506322209434620 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 4724. Hash: 7072046838448900487 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 4724. Hash: 14469479861311642848 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 4724. Hash: 14586570136206675867 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 4724. Hash: 1080930468361041453 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.HubCap.HubCap_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 19800. Hash: 9998270082112342253 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 19800. Hash: 715698668253946311 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 19800. Hash: 9689144294054390217 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 19800. Hash: 13129471596255615133 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 19800. Hash: 12915864712175384367 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 19800. Hash: 704744783983559605 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 19800. Hash: 9998270082112342253 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 19800. Hash: 715698668253946311 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 5846. Hash: 2128276120164603588 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 5846. Hash: 954788723450394678 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 5846. Hash: 14309642535096835998 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 5846. Hash: 2996000735336843208 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 5846. Hash: 6423899825309547347 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 5846. Hash: 6861847030641362531 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InnerPortion.InnerSphere.InnerSphere_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 192. Hash: 10645867109602892598 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 192. Hash: 7257961874201179082 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 192. Hash: 12720324392877726426 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 192. Hash: 7937958557505694755 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 192. Hash: 15065829696130008213 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 192. Hash: 14378088137727336097 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 192. Hash: 10645867109602892598 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 192. Hash: 7257961874201179082 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 65. Hash: 3145658351065228323 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 65. Hash: 13102825703658386866 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 65. Hash: 14651886668877289638 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 65. Hash: 7706988068999921308 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 65. Hash: 1183045102730537867 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 65. Hash: 15200278891890596008 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCone.InnerCone_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 4864. Hash: 5283498994389857134 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 4864. Hash: 4759806696539235318 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 4864. Hash: 1916980755154570809 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 4864. Hash: 3641075817419129841 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 4864. Hash: 1597884606887295389 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 4864. Hash: 12470368568863176335 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 4864. Hash: 5283498994389857134 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 4864. Hash: 4759806696539235318 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 1300. Hash: 519927422840758162 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 1300. Hash: 16690819534142395524 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 1300. Hash: 13697492049473980098 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 1300. Hash: 16069718749273082363 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 1300. Hash: 9503213552298852479 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 1300. Hash: 14920629477034919393 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerPost.InnerPost_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 4096. Hash: 12344372623177285558 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 4096. Hash: 3415525735687273566 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 4096. Hash: 1937219152553164558 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 4096. Hash: 16718347243549159919 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 4096. Hash: 7775661729866538946 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 4096. Hash: 16465645522600859391 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 4096. Hash: 12344372623177285558 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 4096. Hash: 3415525735687273566 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 1122. Hash: 10794215007683911939 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 1122. Hash: 4030215540982392192 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 1122. Hash: 14289595630739500233 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 1122. Hash: 13010448485976282215 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 1122. Hash: 8318401526835048407 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 1122. Hash: 100095329364523248 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerBaseCuff.InnerBaseCuff_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 1536. Hash: 7123339701675171032 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 1536. Hash: 15827204344457762670 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 1536. Hash: 15732245908985477324 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 1536. Hash: 865397990859725823 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 1536. Hash: 2552281640891423471 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 1536. Hash: 17779645716491562667 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 1536. Hash: 7123339701675171032 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 1536. Hash: 15827204344457762670 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 448. Hash: 11533511688039530581 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 448. Hash: 6463578982826635244 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 448. Hash: 10590255987412720146 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 448. Hash: 17744412650281038495 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 448. Hash: 8278337182397882868 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 448. Hash: 8818080862566813062 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.Inset.Inset_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 960. Hash: 12059380739436291361 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 960. Hash: 17894062399627363441 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 960. Hash: 3025207993250150716 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 960. Hash: 909667023812047269 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 960. Hash: 9807712812840041710 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 960. Hash: 15826838159231044203 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 960. Hash: 12059380739436291361 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 960. Hash: 17894062399627363441 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 257. Hash: 8823641736072761245 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 257. Hash: 17988941723121644388 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 257. Hash: 8180735114915510878 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 257. Hash: 6608315278879931556 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 257. Hash: 15606348252975307518 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 257. Hash: 9909736394462106525 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.BottomCap.BottomCap_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 54208. Hash: 3444203649101035485 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 54208. Hash: 937532470362399061 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 54208. Hash: 196567085853229565 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 54208. Hash: 1716600532837684655 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 54208. Hash: 2905235470236164097 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 54208. Hash: 2074363216216487237 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 54208. Hash: 3444203649101035485 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 54208. Hash: 937532470362399061 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 13860. Hash: 263381874971540959 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 13860. Hash: 15191673020616208011 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 13860. Hash: 7105458185902008309 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 13860. Hash: 16259705089531364237 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 13860. Hash: 16022317673583270139 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 13860. Hash: 7251784463761381809 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.InnerCushion.InnerCushion_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 20048. Hash: 10464397683020008867 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 20048. Hash: 11067273583889078226 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 20048. Hash: 15901168792190323178 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 20048. Hash: 552570814640404138 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 20048. Hash: 17726428588184726937 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 20048. Hash: 7508591493894188819 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 20048. Hash: 10464397683020008867 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 20048. Hash: 11067273583889078226 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 5206. Hash: 1074257132915638034 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 5206. Hash: 2243653809093009497 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 5206. Hash: 11559777087289074275 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 5206. Hash: 15450183130901763011 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 5206. Hash: 15359221084106995089 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 5206. Hash: 8652443944286435468 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MaterialBase.OuterBaseCuff.OuterBaseCuff_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 2560. Hash: 14931201357194905697 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 2560. Hash: 5600314145323623005 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 2560. Hash: 11738661055172304644 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 2560. Hash: 320620113692599118 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 2560. Hash: 8466587534284734762 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 2560. Hash: 3318206549561696188 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 2560. Hash: 14931201357194905697 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 2560. Hash: 5600314145323623005 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 720. Hash: 75483450873662317 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 720. Hash: 3793407172213641704 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 720. Hash: 2508676912793167321 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 720. Hash: 2013136453053212946 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 720. Hash: 9302779689053257196 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 720. Hash: 16922723248982534245 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingLeft.RingLeft_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 2560. Hash: 9568588494434360329 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 2560. Hash: 5573555818259644549 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 2560. Hash: 12082144485035076372 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 2560. Hash: 5616878210949329467 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 2560. Hash: 4057154333260499171 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 2560. Hash: 1620460765416970456 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 2560. Hash: 9568588494434360329 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 2560. Hash: 5573555818259644549 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 720. Hash: 3542867785718437760 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 720. Hash: 5575788853295372734 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 720. Hash: 8244955966647049157 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 720. Hash: 17763430282605007327 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 720. Hash: 5442657821959315522 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 720. Hash: 12477592739739533067 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.InlayRings.RingRight.RingRight_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 6304. Hash: 4609122246850169975 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 6304. Hash: 2696528076485355457 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 6304. Hash: 6640465288865380105 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 6304. Hash: 133343330720363387 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 6304. Hash: 790774688340154169 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 6304. Hash: 3046294637431015510 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 6304. Hash: 4609122246850169975 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 6304. Hash: 2696528076485355457 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 1617. Hash: 16761061667647714654 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 1617. Hash: 14954885971875692232 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 1617. Hash: 37061345418264916 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 1617. Hash: 7368059604555723833 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 1617. Hash: 6244533771017459078 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 1617. Hash: 6416015160247779547 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.RightHub.RightHub_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 6304. Hash: 2524875548439506384 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 6304. Hash: 89810986084680009 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 6304. Hash: 1044733215619569246 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 6304. Hash: 15252409165383740719 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 6304. Hash: 3184716392697283856 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 6304. Hash: 4969210758291089995 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 6304. Hash: 2524875548439506384 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 6304. Hash: 89810986084680009 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 1617. Hash: 8467946356718878053 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 1617. Hash: 2373603727160338558 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 1617. Hash: 1521191693628786862 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 1617. Hash: 7175852234718691900 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 1617. Hash: 12283058591680528758 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 1617. Hash: 14516337485055158228 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.LeftHub.LeftHub_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 3520. Hash: 7734180808251274182 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 3520. Hash: 13560118186140352568 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 3520. Hash: 5538036360908204376 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 3520. Hash: 470358662493460341 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 3520. Hash: 13801426056203770982 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 3520. Hash: 8463107387658894201 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 3520. Hash: 7734180808251274182 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 3520. Hash: 13560118186140352568 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 960. Hash: 2691029117997309291 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 960. Hash: 10093724573967674240 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 960. Hash: 1221219959437752888 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 960. Hash: 1294720383009806722 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 960. Hash: 10294793677923893113 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 960. Hash: 6108415656799664788 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.Inside.Inside_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1.Tiled +Node Type: MeshVertexUVData + UVs: Count 18912. Hash: 4120783891454032649 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 18912. Hash: 9011003754405408275 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 18912. Hash: 12406712159692783345 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 18912. Hash: 7868083933985729169 + GenerationMethod: 1 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 18912. Hash: 1990603474898794477 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 18912. Hash: 4812378464029296668 + GenerationMethod: 1 + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_2.Tiled +Node Type: MeshVertexUVData + UVs: Count 18912. Hash: 4120783891454032649 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_2.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 18912. Hash: 9011003754405408275 + UVCustomName: Unwrapped + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_2.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + +Node Name: Tiled +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1_optimized.Tiled +Node Type: MeshVertexUVData + UVs: Count 4891. Hash: 15498889522919365505 + UVCustomName: Tiled + +Node Name: Unwrapped +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1_optimized.Unwrapped +Node Type: MeshVertexUVData + UVs: Count 4891. Hash: 15832520573612498718 + UVCustomName: Unwrapped + +Node Name: TangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1_optimized.TangentSet_0 +Node Type: MeshVertexTangentData + Tangents: Count 4891. Hash: 6697133368486369688 + GenerationMethod: 1 + SetIndex: 0 + +Node Name: TangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1_optimized.TangentSet_1 +Node Type: MeshVertexTangentData + Tangents: Count 4891. Hash: 18420832496569008358 + GenerationMethod: 1 + SetIndex: 1 + +Node Name: BitangentSet_0 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1_optimized.BitangentSet_0 +Node Type: MeshVertexBitangentData + Bitangents: Count 4891. Hash: 2267250201584370787 + GenerationMethod: 1 + +Node Name: BitangentSet_1 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1_optimized.BitangentSet_1 +Node Type: MeshVertexBitangentData + Bitangents: Count 4891. Hash: 7889928104085840247 + GenerationMethod: 1 + +Node Name: blinn1 +Node Path: RootNode.ShaderBall_1m.MainSphere.MainOuterSphere.MainOuterSphere_1_optimized.blinn1 +Node Type: MaterialData + MaterialName: blinn1 + UniqueId: 2076548245838624187 + IsNoDraw: false + DiffuseColor: < 0.800000, 0.800000, 0.800000> + SpecularColor: < 0.500000, 0.500000, 0.500000> + EmissiveColor: < 0.000000, 0.000000, 0.000000> + Opacity: 1.000000 + Shininess: 6.311791 + UseColorMap: Not set + BaseColor: Not set + UseMetallicMap: Not set + MetallicFactor: Not set + UseRoughnessMap: Not set + RoughnessFactor: Not set + UseEmissiveMap: Not set + EmissiveIntensity: Not set + UseAOMap: Not set + DiffuseTexture: ShaderBall/_dev_shaderball_00_basecolor.png + SpecularTexture: + BumpTexture: + NormalTexture: + MetallicTexture: + RoughnessTexture: + AmbientOcclusionTexture: + EmissiveTexture: + BaseColorTexture: ShaderBall/_dev_shaderball_00_basecolor.png + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/SceneDebug/shaderball.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/SceneDebug/shaderball.dbgsg.xml new file mode 100644 index 0000000000..acff9e5f02 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/SceneDebug/shaderball.dbgsg.xml @@ -0,0 +1,9491 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/_dev_shaderball_00_basecolor.png b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/_dev_shaderball_00_basecolor.png new file mode 100644 index 0000000000..415ca3e521 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/_dev_shaderball_00_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93a7e033d9fb0fcac221647322bde03716643d789390f79078c4fcc37ecfd005 +size 68327 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/shaderball.fbx b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/shaderball.fbx new file mode 100644 index 0000000000..caf6dcbe8f --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/ShaderBall/shaderball.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e63a55a35c749a16a03e10a1f53a48bd426c61db80151de080235b14cf6b70d +size 2479344 diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingLOD/lodtest.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingLOD/SceneDebug/lodtest.dbgsg similarity index 100% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingLOD/lodtest.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingLOD/SceneDebug/lodtest.dbgsg diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingLOD/SceneDebug/lodtest.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingLOD/SceneDebug/lodtest.dbgsg.xml new file mode 100644 index 0000000000..d1af4198b5 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingLOD/SceneDebug/lodtest.dbgsg.xml @@ -0,0 +1,2007 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingPhysics/physicstest.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingPhysics/SceneDebug/physicstest.dbgsg similarity index 100% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingPhysics/physicstest.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingPhysics/SceneDebug/physicstest.dbgsg diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingPhysics/SceneDebug/physicstest.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingPhysics/SceneDebug/physicstest.dbgsg.xml new file mode 100644 index 0000000000..b22eb14fc6 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/SoftNamingPhysics/SceneDebug/physicstest.dbgsg.xml @@ -0,0 +1,817 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshLinkedMaterials/multiple_mesh_linked_materials.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshLinkedMaterials/SceneDebug/multiple_mesh_linked_materials.dbgsg similarity index 100% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshLinkedMaterials/multiple_mesh_linked_materials.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshLinkedMaterials/SceneDebug/multiple_mesh_linked_materials.dbgsg diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshLinkedMaterials/SceneDebug/multiple_mesh_linked_materials.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshLinkedMaterials/SceneDebug/multiple_mesh_linked_materials.dbgsg.xml new file mode 100644 index 0000000000..3eaf018fb6 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshLinkedMaterials/SceneDebug/multiple_mesh_linked_materials.dbgsg.xml @@ -0,0 +1,1345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshOneMaterial/multiple_mesh_one_material.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshOneMaterial/SceneDebug/multiple_mesh_one_material.dbgsg similarity index 100% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshOneMaterial/multiple_mesh_one_material.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshOneMaterial/SceneDebug/multiple_mesh_one_material.dbgsg diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshOneMaterial/SceneDebug/multiple_mesh_one_material.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshOneMaterial/SceneDebug/multiple_mesh_one_material.dbgsg.xml new file mode 100644 index 0000000000..39ac33f654 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshOneMaterial/SceneDebug/multiple_mesh_one_material.dbgsg.xml @@ -0,0 +1,1015 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/multiple_mesh_multiple_material.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material.dbgsg similarity index 100% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/multiple_mesh_multiple_material.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material.dbgsg diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material.dbgsg.xml new file mode 100644 index 0000000000..c41c1414a4 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material.dbgsg.xml @@ -0,0 +1,1015 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/multiple_mesh_multiple_material_override.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material_override.dbgsg similarity index 100% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/multiple_mesh_multiple_material_override.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material_override.dbgsg diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material_override.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material_override.dbgsg.xml new file mode 100644 index 0000000000..01167ec0ee --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/TwoMeshTwoMaterial/SceneDebug/multiple_mesh_multiple_material_override.dbgsg.xml @@ -0,0 +1,817 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/VertexColor/vertexcolor.dbgsg b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/VertexColor/SceneDebug/vertexcolor.dbgsg similarity index 100% rename from AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/VertexColor/vertexcolor.dbgsg rename to AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/VertexColor/SceneDebug/vertexcolor.dbgsg diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/VertexColor/SceneDebug/vertexcolor.dbgsg.xml b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/VertexColor/SceneDebug/vertexcolor.dbgsg.xml new file mode 100644 index 0000000000..f5caa70d63 --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/assets/VertexColor/SceneDebug/vertexcolor.dbgsg.xml @@ -0,0 +1,576 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/fbx_tests.py b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/fbx_tests.py index 71f200074c..c28dfa64f2 100755 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/fbx_tests.py +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/fbx_tests/fbx_tests.py @@ -21,8 +21,8 @@ from ly_test_tools.o3de.asset_processor import ASSET_PROCESSOR_PLATFORM_MAP from ..ap_fixtures.asset_processor_fixture import asset_processor as asset_processor from ..ap_fixtures.ap_setup_fixture import ap_setup_fixture as ap_setup_fixture from ..ap_fixtures.ap_config_backup_fixture import ap_config_backup_fixture as ap_config_backup_fixture -from ..ap_fixtures.ap_config_default_platform_fixture import ap_config_default_platform_fixture as ap_config_default_platform_fixture - +from ..ap_fixtures.ap_config_default_platform_fixture \ + import ap_config_default_platform_fixture as ap_config_default_platform_fixture # Import LyShared import ly_test_tools.o3de.pipeline_utils as utils @@ -33,6 +33,7 @@ logger = logging.getLogger(__name__) # Helper: variables we will use for parameter values in the test: targetProjects = ["AutomatedTesting"] + @pytest.fixture @pytest.mark.SUITE_sandbox def local_resources(request, workspace, ap_setup_fixture): @@ -54,25 +55,30 @@ class BlackboxAssetTest: blackbox_fbx_tests = [ pytest.param( BlackboxAssetTest( - test_name= "OneMeshOneMaterial_RunAP_SuccessWithMatchingProducts", - asset_folder= "OneMeshOneMaterial", + test_name="OneMeshOneMaterial_RunAP_SuccessWithMatchingProducts", + asset_folder="OneMeshOneMaterial", scene_debug_file="onemeshonematerial.dbgsg", - assets = [ + assets=[ asset_db_utils.DBSourceAsset( - source_file_name = "OneMeshOneMaterial.fbx", - uuid = b"8a9164adb84859be893e18aa819438e1", - jobs = [ + source_file_name="OneMeshOneMaterial.fbx", + uuid=b"8a9164adb84859be893e18aa819438e1", + jobs=[ asset_db_utils.DBJob( - job_key= "Scene compilation", + job_key="Scene compilation", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", status=4, error_count=0, warning_count=1, - products = [ + products=[ asset_db_utils.DBProduct( product_name='onemeshonematerial/onemeshonematerial.dbgsg', sub_id=1918494907, - asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b') + asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'), + asset_db_utils.DBProduct( + product_name='onemeshonematerial/onemeshonematerial.dbgsg.xml', + sub_id=556355570, + asset_type=b'51f376140d774f369ac67ed70a0ac868' + ) ] ), ] @@ -86,25 +92,30 @@ blackbox_fbx_tests = [ BlackboxAssetTest( # Verifies that the soft naming convention feature with level of detail meshes works. # https://docs.aws.amazon.com/lumberyard/latest/userguide/char-fbx-importer-soft-naming.html - test_name= "SoftNamingLOD_RunAP_SuccessWithMatchingProducts", - asset_folder= "SoftNamingLOD", + test_name="SoftNamingLOD_RunAP_SuccessWithMatchingProducts", + asset_folder="SoftNamingLOD", scene_debug_file="lodtest.dbgsg", - assets = [ + assets=[ asset_db_utils.DBSourceAsset( - source_file_name = "lodtest.fbx", - uuid = b"44c8627fe2c25aae91fe3ff9547be3b9", - jobs = [ + source_file_name="lodtest.fbx", + uuid=b"44c8627fe2c25aae91fe3ff9547be3b9", + jobs=[ asset_db_utils.DBJob( - job_key= "Scene compilation", + job_key="Scene compilation", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", status=4, error_count=0, warning_count=22, - products = [ + products=[ asset_db_utils.DBProduct( product_name='softnaminglod/lodtest.dbgsg', sub_id=-632012261, - asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b') + asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'), + asset_db_utils.DBProduct( + product_name='softnaminglod/lodtest.dbgsg.xml', + sub_id=-2036095434, + asset_type=b'51f376140d774f369ac67ed70a0ac868' + ) ] ), ] @@ -118,31 +129,36 @@ blackbox_fbx_tests = [ BlackboxAssetTest( # Verifies that the soft naming convention feature with physics proxies works. # https://docs.aws.amazon.com/lumberyard/latest/userguide/char-fbx-importer-soft-naming.html - test_name= "SoftNamingPhysics_RunAP_SuccessWithMatchingProducts", - asset_folder= "SoftNamingPhysics", + test_name="SoftNamingPhysics_RunAP_SuccessWithMatchingProducts", + asset_folder="SoftNamingPhysics", scene_debug_file="physicstest.dbgsg", - assets = [ + assets=[ asset_db_utils.DBSourceAsset( - source_file_name = "physicstest.fbx", - uuid = b"df957b7918cf5b029806c73f630fa1c8", - jobs = [ + source_file_name="physicstest.fbx", + uuid=b"df957b7918cf5b029806c73f630fa1c8", + jobs=[ asset_db_utils.DBJob( - job_key= "Scene compilation", + job_key="Scene compilation", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", status=4, error_count=0, warning_count=14, - products = [ + products=[ asset_db_utils.DBProduct( product_name='softnamingphysics/physicstest.dbgsg', sub_id=-740411732, asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b' ), + asset_db_utils.DBProduct( + product_name='softnamingphysics/physicstest.dbgsg.xml', + sub_id=330338417, + asset_type=b'51f376140d774f369ac67ed70a0ac868' + ), asset_db_utils.DBProduct( product_name="softnamingphysics/physicstest.pxmesh", sub_id=640975857, asset_type=b"7a2871b95eab4de0a901b0d2c6920ddb" - ), + ) ] ), ] @@ -152,25 +168,29 @@ blackbox_fbx_tests = [ ), pytest.param( BlackboxAssetTest( - test_name= "MultipleMeshOneMaterial_RunAP_SuccessWithMatchingProducts", - asset_folder= "TwoMeshOneMaterial", + test_name="MultipleMeshOneMaterial_RunAP_SuccessWithMatchingProducts", + asset_folder="TwoMeshOneMaterial", scene_debug_file="multiple_mesh_one_material.dbgsg", - assets = [ + assets=[ asset_db_utils.DBSourceAsset( - source_file_name = "multiple_mesh_one_material.fbx", - uuid = b"597618fd497659a1b197a015fe47aa95", - jobs = [ + source_file_name="multiple_mesh_one_material.fbx", + uuid=b"597618fd497659a1b197a015fe47aa95", + jobs=[ asset_db_utils.DBJob( - job_key= "Scene compilation", + job_key="Scene compilation", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", status=4, error_count=0, warning_count=2, - products = [ + products=[ asset_db_utils.DBProduct( product_name='twomeshonematerial/multiple_mesh_one_material.dbgsg', sub_id=2077268018, - asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b') + asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'), + asset_db_utils.DBProduct( + product_name='twomeshonematerial/multiple_mesh_one_material.dbgsg.xml', + sub_id=1321067730, + asset_type=b'51f376140d774f369ac67ed70a0ac868') ] ), ] @@ -183,26 +203,31 @@ blackbox_fbx_tests = [ pytest.param( BlackboxAssetTest( # Verifies whether multiple meshes can share linked materials - test_name= "MultipleMeshLinkedMaterials_RunAP_SuccessWithMatchingProducts", - asset_folder= "TwoMeshLinkedMaterials", - scene_debug_file= "multiple_mesh_linked_materials.dbgsg", - assets = [ + test_name="MultipleMeshLinkedMaterials_RunAP_SuccessWithMatchingProducts", + asset_folder="TwoMeshLinkedMaterials", + scene_debug_file="multiple_mesh_linked_materials.dbgsg", + assets=[ asset_db_utils.DBSourceAsset( - source_file_name = "multiple_mesh_linked_materials.fbx", - uuid = b"25d8301c2eef5dc7bded310db8ea608d", - jobs = [ + source_file_name="multiple_mesh_linked_materials.fbx", + uuid=b"25d8301c2eef5dc7bded310db8ea608d", + jobs=[ asset_db_utils.DBJob( - job_key= "Scene compilation", - platform= "pc", + job_key="Scene compilation", + platform="pc", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", status=4, error_count=0, warning_count=2, - products= [ + products=[ asset_db_utils.DBProduct( product_name='twomeshlinkedmaterials/multiple_mesh_linked_materials.dbgsg', sub_id=-1898461950, asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b' + ), + asset_db_utils.DBProduct( + product_name='twomeshlinkedmaterials/multiple_mesh_linked_materials.dbgsg.xml', + sub_id=-772341513, + asset_type=b'51f376140d774f369ac67ed70a0ac868' ) ] ), @@ -216,26 +241,31 @@ blackbox_fbx_tests = [ pytest.param( BlackboxAssetTest( # Verifies a mesh with multiple materials - test_name= "SingleMeshMultipleMaterials_RunAP_SuccessWithMatchingProducts", - asset_folder= "OneMeshMultipleMaterials", + test_name="SingleMeshMultipleMaterials_RunAP_SuccessWithMatchingProducts", + asset_folder="OneMeshMultipleMaterials", scene_debug_file="single_mesh_multiple_materials.dbgsg", - assets = [ + assets=[ asset_db_utils.DBSourceAsset( - source_file_name = "single_mesh_multiple_materials.fbx", - uuid = b"f08fd585dfa35881b4bf86637da5e858", - jobs = [ + source_file_name="single_mesh_multiple_materials.fbx", + uuid=b"f08fd585dfa35881b4bf86637da5e858", + jobs=[ asset_db_utils.DBJob( - job_key= "Scene compilation", - platform= "pc", + job_key="Scene compilation", + platform="pc", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", status=4, error_count=0, warning_count=1, - products = [ + products=[ asset_db_utils.DBProduct( product_name='onemeshmultiplematerials/single_mesh_multiple_materials.dbgsg', sub_id=-262822238, - asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b') + asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'), + asset_db_utils.DBProduct( + product_name='onemeshmultiplematerials/single_mesh_multiple_materials.dbgsg.xml', + sub_id=1462358160, + asset_type=b'51f376140d774f369ac67ed70a0ac868' + ) ] ), ] @@ -265,7 +295,12 @@ blackbox_fbx_tests = [ asset_db_utils.DBProduct( product_name='vertexcolor/vertexcolor.dbgsg', sub_id=-1543877170, - asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b') + asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'), + asset_db_utils.DBProduct( + product_name='vertexcolor/vertexcolor.dbgsg.xml', + sub_id=1743516586, + asset_type=b'51f376140d774f369ac67ed70a0ac868' + ) ] ), ] @@ -277,25 +312,30 @@ blackbox_fbx_tests = [ ), pytest.param( BlackboxAssetTest( - test_name= "MotionTest_RunAP_SuccessWithMatchingProducts", - asset_folder= "Motion", + test_name="MotionTest_RunAP_SuccessWithMatchingProducts", + asset_folder="Motion", scene_debug_file="Jack_Idle_Aim_ZUp.dbgsg", - assets = [ + assets=[ asset_db_utils.DBSourceAsset( - source_file_name = "Jack_Idle_Aim_ZUp.fbx", - uuid = b"eda904ae0e145f8b973d57fc5809918b", - jobs = [ + source_file_name="Jack_Idle_Aim_ZUp.fbx", + uuid=b"eda904ae0e145f8b973d57fc5809918b", + jobs=[ asset_db_utils.DBJob( - job_key= "Scene compilation", + job_key="Scene compilation", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", status=4, error_count=0, warning_count=0, - products = [ + products=[ asset_db_utils.DBProduct( product_name='motion/jack_idle_aim_zup.dbgsg', sub_id=-517610290, asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'), + asset_db_utils.DBProduct( + product_name='motion/jack_idle_aim_zup.dbgsg.xml', + sub_id=-817863914, + asset_type=b'51f376140d774f369ac67ed70a0ac868' + ), asset_db_utils.DBProduct( product_name='motion/jack_idle_aim_zup.motion', sub_id=186392073, @@ -307,33 +347,69 @@ blackbox_fbx_tests = [ ] ), ), + pytest.param( + BlackboxAssetTest( + test_name="ShaderBall_RunAP_SuccessWithMatchingProducts", + asset_folder="ShaderBall", + scene_debug_file="shaderball.dbgsg", + assets=[ + asset_db_utils.DBSourceAsset( + source_file_name="shaderball.fbx", + uuid=b"48181ba8038e5193997540fc8dffb06d", + jobs=[ + asset_db_utils.DBJob( + job_key="Scene compilation", + builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", + status=4, + error_count=0, + warning_count=30, + products=[ + asset_db_utils.DBProduct( + product_name='shaderball/shaderball.dbgsg', + sub_id=-1607815784, + asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'), + asset_db_utils.DBProduct( + product_name='shaderball/shaderball.dbgsg.xml', + sub_id=-1153118555, + asset_type=b'51f376140d774f369ac67ed70a0ac868'), + ] + ), + ] + ) + ] + ), + ), ] - blackbox_fbx_special_tests = [ pytest.param( BlackboxAssetTest( - test_name= "MultipleMeshMultipleMaterial_MultipleAssetInfo_RunAP_SuccessWithMatchingProducts", - asset_folder= "TwoMeshTwoMaterial", - override_asset_folder = "OverrideAssetInfoForTwoMeshTwoMaterial", + test_name="MultipleMeshMultipleMaterial_MultipleAssetInfo_RunAP_SuccessWithMatchingProducts", + asset_folder="TwoMeshTwoMaterial", + override_asset_folder="OverrideAssetInfoForTwoMeshTwoMaterial", scene_debug_file="multiple_mesh_multiple_material.dbgsg", override_scene_debug_file="multiple_mesh_multiple_material_override.dbgsg", - assets = [ + assets=[ asset_db_utils.DBSourceAsset( - source_file_name = "multiple_mesh_multiple_material.fbx", - uuid = b"b5915fb874af5c8a866ccabbddb57595", - jobs = [ + source_file_name="multiple_mesh_multiple_material.fbx", + uuid=b"b5915fb874af5c8a866ccabbddb57595", + jobs=[ asset_db_utils.DBJob( job_key="Scene compilation", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", status=4, error_count=0, warning_count=2, - products = [ + products=[ asset_db_utils.DBProduct( product_name='twomeshtwomaterial/multiple_mesh_multiple_material.dbgsg', sub_id=896980093, - asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b') + asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'), + asset_db_utils.DBProduct( + product_name='twomeshtwomaterial/multiple_mesh_multiple_material.dbgsg.xml', + sub_id=-1556988544, + asset_type=b'51f376140d774f369ac67ed70a0ac868' + ) ] ), ] @@ -341,20 +417,25 @@ blackbox_fbx_special_tests = [ ], override_assets=[ asset_db_utils.DBSourceAsset( - source_file_name = "multiple_mesh_multiple_material.fbx", - uuid = b"b5915fb874af5c8a866ccabbddb57595", - jobs = [ + source_file_name="multiple_mesh_multiple_material.fbx", + uuid=b"b5915fb874af5c8a866ccabbddb57595", + jobs=[ asset_db_utils.DBJob( - job_key= "Scene compilation", + job_key="Scene compilation", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", status=4, error_count=0, warning_count=2, - products = [ + products=[ asset_db_utils.DBProduct( product_name='twomeshtwomaterial/multiple_mesh_multiple_material.dbgsg', sub_id=896980093, - asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b') + asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'), + asset_db_utils.DBProduct( + product_name='twomeshtwomaterial/multiple_mesh_multiple_material.dbgsg.xml', + sub_id=-1556988544, + asset_type=b'51f376140d774f369ac67ed70a0ac868' + ) ] ), ] @@ -378,29 +459,26 @@ class TestsFBX_AllPlatforms(object): @pytest.mark.BAT @pytest.mark.SUITE_sandbox @pytest.mark.parametrize("blackbox_param", blackbox_fbx_tests) - def test_FBXBlackboxTest_SourceFiles_Processed_ResultInExpectedProducts(self, workspace, - ap_setup_fixture, asset_processor, project, - blackbox_param): + def test_FBXBlackboxTest_SourceFiles_Processed_ResultInExpectedProducts(self, workspace, ap_setup_fixture, + asset_processor, project, blackbox_param): """ - Please see run_fbx_test(...) for details + Please see run_fbx_test(...) for details Test Steps: 1. Determine if blackbox is set to none 2. Run FBX Test + """ if blackbox_param == None: return - self.run_fbx_test(workspace, ap_setup_fixture, - asset_processor, project, blackbox_param) + self.run_fbx_test(workspace, ap_setup_fixture, asset_processor, project, blackbox_param) @pytest.mark.BAT @pytest.mark.SUITE_sandbox @pytest.mark.parametrize("blackbox_param", blackbox_fbx_special_tests) - def test_FBXBlackboxTest_AssetInfoModified_AssetReprocessed_ResultInExpectedProducts(self, - workspace, ap_setup_fixture, - asset_processor, project, - blackbox_param): + def test_FBXBlackboxTest_AssetInfoModified_AssetReprocessed_ResultInExpectedProducts( + self, workspace, ap_setup_fixture, asset_processor, project, blackbox_param): """ Please see run_fbx_test(...) for details @@ -429,8 +507,21 @@ class TestsFBX_AllPlatforms(object): product.product_name = job.platform + "/" \ + product.product_name + def compare_scene_debug_file(self, asset_processor, expected_file_path, actual_file_path): + debug_graph_path = os.path.join(asset_processor.project_test_cache_folder(), actual_file_path) + expected_debug_graph_path = os.path.join(asset_processor.project_test_source_folder(), "SceneDebug", expected_file_path) + + logger.info(f"Parsing scene graph: {debug_graph_path}") + with open(debug_graph_path, "r") as scene_file: + actual_lines = scene_file.readlines() + + logger.info(f"Parsing scene graph: {expected_debug_graph_path}") + with open(expected_debug_graph_path, "r") as scene_file: + expected_lines = scene_file.readlines() + + assert utils.compare_lists(actual_lines, expected_lines), "Scene mismatch" def run_fbx_test(self, workspace, ap_setup_fixture, asset_processor, - project, blackbox_params: BlackboxAssetTest, overrideAsset = False): + project, blackbox_params: BlackboxAssetTest, overrideAsset=False): """ These tests work by having the test case ingest the test data and determine the run pattern. Tests will process scene settings files and will additionally do a verification against a provided debug file @@ -469,32 +560,27 @@ class TestsFBX_AllPlatforms(object): expected_product_list.append(expected_product.product_name) missing_assets, _ = utils.compare_assets_with_cache(expected_product_list, - asset_processor.project_test_cache_folder()) + asset_processor.project_test_cache_folder()) - assert not missing_assets, f'The following assets were expected to be in, but not found in cache: {str(missing_assets)}' + assert not missing_assets, \ + f'The following assets were expected to be in, but not found in cache: {str(missing_assets)}' # Load the asset database. db_path = os.path.join(asset_processor.temp_asset_root(), "Cache", "assetdb.sqlite") cache_root = os.path.dirname(os.path.join(asset_processor.temp_asset_root(), "Cache", - ASSET_PROCESSOR_PLATFORM_MAP[workspace.asset_processor_platform])) + ASSET_PROCESSOR_PLATFORM_MAP[workspace.asset_processor_platform])) if blackbox_params.scene_debug_file: - scene_debug_file = blackbox_params.override_scene_debug_file if overrideAsset\ + scene_debug_file = blackbox_params.override_scene_debug_file if overrideAsset \ else blackbox_params.scene_debug_file - debug_graph_path = os.path.join(asset_processor.project_test_cache_folder(), blackbox_params.scene_debug_file) - expected_debug_graph_path = os.path.join(asset_processor.project_test_source_folder(), scene_debug_file) + self.compare_scene_debug_file(asset_processor, scene_debug_file, blackbox_params.scene_debug_file) - logger.info(f"Parsing scene graph: {debug_graph_path}") - with open(debug_graph_path, "r") as scene_file: - actual_lines = scene_file.readlines() - - logger.info(f"Parsing scene graph: {expected_debug_graph_path}") - with open(expected_debug_graph_path, "r") as scene_file: - expected_lines = scene_file.readlines() - - assert utils.compare_lists(actual_lines, expected_lines), "Scene mismatch" + # Run again for the .dbgsg.xml file + self.compare_scene_debug_file(asset_processor, + scene_debug_file + ".xml", + blackbox_params.scene_debug_file + ".xml") # Check that each given source asset resulted in the expected jobs and products. self.populateAssetInfo(workspace, project, assetsToValidate) diff --git a/AutomatedTesting/Gem/PythonTests/automatedtesting_shared/base.py b/AutomatedTesting/Gem/PythonTests/automatedtesting_shared/base.py index cbb6102a44..88188aa6c0 100755 --- a/AutomatedTesting/Gem/PythonTests/automatedtesting_shared/base.py +++ b/AutomatedTesting/Gem/PythonTests/automatedtesting_shared/base.py @@ -8,7 +8,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT import os import logging -import subprocess +import sys import pytest import time @@ -52,7 +52,7 @@ class TestAutomationBase: cls._kill_ly_processes() def _run_test(self, request, workspace, editor, testcase_module, extra_cmdline_args=[], batch_mode=True, - autotest_mode=True, use_null_renderer=True): + autotest_mode=True, use_null_renderer=True, enable_prefab_system=True): test_starttime = time.time() self.logger = logging.getLogger(__name__) errors = [] @@ -97,6 +97,11 @@ class TestAutomationBase: pycmd += ["-BatchMode"] if autotest_mode: pycmd += ["-autotest_mode"] + if enable_prefab_system: + pycmd += ["--regset=/Amazon/Preferences/EnablePrefabSystem=true"] + else: + pycmd += ["--regset=/Amazon/Preferences/EnablePrefabSystem=false"] + pycmd += extra_cmdline_args editor.args.extend(pycmd) # args are added to the WinLauncher start command editor.start(backupFiles = False, launch_ap = False) @@ -123,7 +128,8 @@ class TestAutomationBase: errors.append(TestRunError("FAILED TEST", error_str)) if return_code and return_code != TestAutomationBase.TEST_FAIL_RETCODE: # Crashed crash_info = "-- No crash log available --" - crash_log = os.path.join(workspace.paths.project_log(), 'error.log') + crash_log = workspace.paths.crash_log() + try: waiter.wait_for(lambda: os.path.exists(crash_log), timeout=TestAutomationBase.WAIT_FOR_CRASH_LOG) except AssertionError: @@ -165,7 +171,7 @@ class TestAutomationBase: for line in f.readlines(): error_str += f"|{log_basename}| {line}" except Exception as ex: - error_str += "-- No log available --" + error_str += f"-- No log available ({ex})--" pytest.fail(error_str) diff --git a/AutomatedTesting/Gem/PythonTests/editor/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/editor/CMakeLists.txt index bf42579970..1fc71da972 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/editor/CMakeLists.txt @@ -7,60 +7,6 @@ # if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_FOUNDATION_TEST_SUPPORTED) - ly_add_pytest( - NAME AutomatedTesting::EditorTests_Main - TEST_SUITE main - TEST_SERIAL - PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.py - PYTEST_MARKS "not REQUIRES_gpu" - RUNTIME_DEPENDENCIES - Legacy::Editor - AZ::AssetProcessor - AutomatedTesting.Assets - COMPONENT - Editor - ) - - ly_add_pytest( - NAME AutomatedTesting::EditorTests_Main_GPU - TEST_SUITE main - TEST_SERIAL - TEST_REQUIRES gpu - PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.py - PYTEST_MARKS "REQUIRES_gpu" - RUNTIME_DEPENDENCIES - Legacy::Editor - AZ::AssetProcessor - AutomatedTesting.Assets - COMPONENT - Editor - ) - - ly_add_pytest( - NAME AutomatedTesting::EditorTests_Periodic - TEST_SUITE periodic - TEST_SERIAL - PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Periodic.py - RUNTIME_DEPENDENCIES - Legacy::Editor - AZ::AssetProcessor - AutomatedTesting.Assets - COMPONENT - Editor - ) - - ly_add_pytest( - NAME AutomatedTesting::EditorTests_Sandbox - TEST_SUITE sandbox - TEST_SERIAL - PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Sandbox.py - RUNTIME_DEPENDENCIES - Legacy::Editor - AZ::AssetProcessor - AutomatedTesting.Assets - COMPONENT - Editor - ) ly_add_pytest( NAME AutomatedTesting::EditorTests_Main_Optimized diff --git a/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/Docking_BasicDockedTools.py b/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/Docking_BasicDockedTools.py index 2a91e7a374..6683fc952a 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/Docking_BasicDockedTools.py +++ b/AutomatedTesting/Gem/PythonTests/editor/EditorScripts/Docking_BasicDockedTools.py @@ -140,13 +140,13 @@ def Docking_BasicDockedTools(): # 2.5,6) Send a console command. console_line_edit = console.findChild(QtWidgets.QLineEdit, "lineEdit") - console_line_edit.setText("t_Scale 2") + console_line_edit.setText("t_simulationTickScale 2") QtTest.QTest.keyClick(console_line_edit, QtCore.Qt.Key_Enter) - general.get_cvar("t_Scale") - Report.result(Tests.docked_console_works, general.get_cvar("t_Scale") == "2") + general.get_cvar("t_simulationTickScale") + Report.result(Tests.docked_console_works, general.get_cvar("t_simulationTickScale") == "2") # Reset the altered cvar - console_line_edit.setText("t_Scale 1") + console_line_edit.setText("t_simulationTickScale 1") QtTest.QTest.keyClick(console_line_edit, QtCore.Qt.Key_Enter) run_test() diff --git a/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Main.py index 49069569eb..c9e91687e0 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Main.py @@ -33,14 +33,14 @@ class TestAutomation(TestAutomationBase): def test_BasicEditorWorkflows_LevelEntityComponentCRUD(self, request, workspace, editor, launcher_platform, remove_test_level): from .EditorScripts import BasicEditorWorkflows_LevelEntityComponentCRUD as test_module - self._run_test(request, workspace, editor, test_module, batch_mode=False, autotest_mode=False) + self._run_test(request, workspace, editor, test_module, batch_mode=False, autotest_mode=False, enable_prefab_system=False) @pytest.mark.REQUIRES_gpu def test_BasicEditorWorkflows_GPU_LevelEntityComponentCRUD(self, request, workspace, editor, launcher_platform, remove_test_level): from .EditorScripts import BasicEditorWorkflows_LevelEntityComponentCRUD as test_module self._run_test(request, workspace, editor, test_module, batch_mode=False, autotest_mode=False, - use_null_renderer=False) + use_null_renderer=False, enable_prefab_system=False) def test_EntityOutlienr_EntityOrdering(self, request, workspace, editor, launcher_platform): from .EditorScripts import EntityOutliner_EntityOrdering as test_module @@ -51,5 +51,4 @@ class TestAutomation(TestAutomationBase): test_module, batch_mode=False, autotest_mode=True, - extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=true"] ) diff --git a/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Main_Optimized.py b/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Main_Optimized.py index 820e4bd2aa..d87fd8625b 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Main_Optimized.py +++ b/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Main_Optimized.py @@ -21,6 +21,8 @@ class TestAutomationNoAutoTestMode(EditorTestSuite): # Disable -autotest_mode and -BatchMode. Tests cannot run in -BatchMode due to UI interactions, and these tests # interact with modal dialogs global_extra_cmdline_args = [] + + enable_prefab_system = False class test_BasicEditorWorkflows_LevelEntityComponentCRUD(EditorSingleTest): # Custom teardown to remove slice asset created during test @@ -47,7 +49,6 @@ class TestAutomationNoAutoTestMode(EditorTestSuite): from .EditorScripts import AssetPicker_UI_UX as test_module -@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.") @pytest.mark.SUITE_main @pytest.mark.parametrize("launcher_platform", ['windows_editor']) @pytest.mark.parametrize("project", ["AutomatedTesting"]) @@ -56,6 +57,8 @@ class TestAutomationAutoTestMode(EditorTestSuite): # Enable only -autotest_mode for these tests. Tests cannot run in -BatchMode due to UI interactions global_extra_cmdline_args = ["-autotest_mode"] + enable_prefab_system = False + class test_AssetBrowser_TreeNavigation(EditorSharedTest): from .EditorScripts import AssetBrowser_TreeNavigation as test_module diff --git a/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Periodic.py b/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Periodic.py index f131a1c8bc..1bd1d7f987 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Periodic.py +++ b/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Periodic.py @@ -32,29 +32,29 @@ class TestAutomation(TestAutomationBase): def test_AssetBrowser_TreeNavigation(self, request, workspace, editor, launcher_platform): from .EditorScripts import AssetBrowser_TreeNavigation as test_module - self._run_test(request, workspace, editor, test_module, batch_mode=False) + self._run_test(request, workspace, editor, test_module, batch_mode=False, enable_prefab_system=False) def test_AssetBrowser_SearchFiltering(self, request, workspace, editor, launcher_platform): from .EditorScripts import AssetBrowser_SearchFiltering as test_module - self._run_test(request, workspace, editor, test_module, batch_mode=False) + self._run_test(request, workspace, editor, test_module, batch_mode=False, enable_prefab_system=False) def test_AssetPicker_UI_UX(self, request, workspace, editor, launcher_platform): from .EditorScripts import AssetPicker_UI_UX as test_module - self._run_test(request, workspace, editor, test_module, autotest_mode=False, batch_mode=False) + self._run_test(request, workspace, editor, test_module, autotest_mode=False, batch_mode=False, enable_prefab_system=False) def test_ComponentCRUD_Add_Delete_Components(self, request, workspace, editor, launcher_platform): from .EditorScripts import ComponentCRUD_Add_Delete_Components as test_module - self._run_test(request, workspace, editor, test_module, batch_mode=False) + self._run_test(request, workspace, editor, test_module, batch_mode=False, enable_prefab_system=False) def test_InputBindings_Add_Remove_Input_Events(self, request, workspace, editor, launcher_platform): from .EditorScripts import InputBindings_Add_Remove_Input_Events as test_module - self._run_test(request, workspace, editor, test_module, batch_mode=False, autotest_mode=False) + self._run_test(request, workspace, editor, test_module, batch_mode=False, autotest_mode=False, enable_prefab_system=False) def test_Menus_ViewMenuOptions_Work(self, request, workspace, editor, launcher_platform): from .EditorScripts import Menus_ViewMenuOptions as test_module - self._run_test(request, workspace, editor, test_module, batch_mode=False) + self._run_test(request, workspace, editor, test_module, batch_mode=False, enable_prefab_system=False) @pytest.mark.skip(reason="Times out due to dialogs failing to dismiss: LYN-4208") def test_Menus_FileMenuOptions_Work(self, request, workspace, editor, launcher_platform): from .EditorScripts import Menus_FileMenuOptions as test_module - self._run_test(request, workspace, editor, test_module, batch_mode=False) + self._run_test(request, workspace, editor, test_module, batch_mode=False, enable_prefab_system=False) diff --git a/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Sandbox.py b/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Sandbox.py index 98a6620d9c..8a56a2dbfd 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Sandbox.py +++ b/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Sandbox.py @@ -20,8 +20,8 @@ class TestAutomation(TestAutomationBase): def test_Menus_EditMenuOptions_Work(self, request, workspace, editor, launcher_platform): from .EditorScripts import Menus_EditMenuOptions as test_module - self._run_test(request, workspace, editor, test_module, batch_mode=False) + self._run_test(request, workspace, editor, test_module, batch_mode=False, enable_prefab_system=False) def test_Docking_BasicDockedTools(self, request, workspace, editor, launcher_platform): from .EditorScripts import Docking_BasicDockedTools as test_module - self._run_test(request, workspace, editor, test_module, batch_mode=False) + self._run_test(request, workspace, editor, test_module, batch_mode=False, enable_prefab_system=False) diff --git a/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Sandbox_Optimized.py b/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Sandbox_Optimized.py index 4a472095ae..ce0d5e43e9 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Sandbox_Optimized.py +++ b/AutomatedTesting/Gem/PythonTests/editor/TestSuite_Sandbox_Optimized.py @@ -19,6 +19,8 @@ class TestAutomationAutoTestMode(EditorTestSuite): # Enable only -autotest_mode for these tests. Tests cannot run in -BatchMode due to UI interactions global_extra_cmdline_args = ["-autotest_mode"] + enable_prefab_system = False + class test_Docking_BasicDockedTools(EditorSharedTest): from .EditorScripts import Docking_BasicDockedTools as test_module diff --git a/AutomatedTesting/Gem/PythonTests/editor_test_testing/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/editor_test_testing/TestSuite_Main.py index 15ba6690a1..50b89ab138 100644 --- a/AutomatedTesting/Gem/PythonTests/editor_test_testing/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/editor_test_testing/TestSuite_Main.py @@ -15,6 +15,7 @@ import sys import importlib import re +import ly_test_tools from ly_test_tools import LAUNCHERS sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -25,8 +26,15 @@ import ly_test_tools.environment.process_utils as process_utils import argparse, sys -@pytest.mark.SUITE_main -@pytest.mark.parametrize("launcher_platform", ['windows_editor']) +def get_editor_launcher_platform(): + if ly_test_tools.WINDOWS: + return "windows_editor" + elif ly_test_tools.LINUX: + return "linux_editor" + else: + return None + +@pytest.mark.parametrize("launcher_platform", [get_editor_launcher_platform()]) @pytest.mark.parametrize("project", ["AutomatedTesting"]) class TestEditorTest: @@ -69,7 +77,7 @@ class TestEditorTest: from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorTestSuite @pytest.mark.SUITE_main - @pytest.mark.parametrize("launcher_platform", ['windows_editor']) + @pytest.mark.parametrize("launcher_platform", [{get_editor_launcher_platform()}]) @pytest.mark.parametrize("project", ["AutomatedTesting"]) class TestAutomation(EditorTestSuite): class test_single(EditorSingleTest): @@ -123,7 +131,7 @@ class TestEditorTest: from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorTestSuite @pytest.mark.SUITE_main - @pytest.mark.parametrize("launcher_platform", ['windows_editor']) + @pytest.mark.parametrize("launcher_platform", [{get_editor_launcher_platform()}]) @pytest.mark.parametrize("project", ["AutomatedTesting"]) class TestAutomation(EditorTestSuite): {module_class_code} diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/largeworlds/CMakeLists.txt index c2123d683c..ba607ede32 100644 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/CMakeLists.txt @@ -10,45 +10,29 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_ ## DynVeg ## - ly_add_pytest( - NAME AutomatedTesting::DynamicVegetationTests_Main - TEST_SERIAL - TEST_SUITE main - PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg/TestSuite_Main.py - RUNTIME_DEPENDENCIES - AZ::AssetProcessor - Legacy::Editor - AutomatedTesting.GameLauncher - AutomatedTesting.Assets - COMPONENT - LargeWorlds - ) - - - ly_add_pytest( - NAME AutomatedTesting::DynamicVegetationTests_Periodic - TEST_SERIAL - TEST_SUITE periodic - PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg/TestSuite_Periodic.py - RUNTIME_DEPENDENCIES - AZ::AssetProcessor - Legacy::Editor - AutomatedTesting.Assets - AutomatedTesting.GameLauncher - COMPONENT - LargeWorlds - ) - ly_add_pytest( NAME AutomatedTesting::DynamicVegetationTests_Main_Optimized TEST_SERIAL TEST_SUITE main PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg/TestSuite_Main_Optimized.py RUNTIME_DEPENDENCIES - AZ::AssetProcessor - Legacy::Editor - AutomatedTesting.Assets - AutomatedTesting.GameLauncher + AZ::AssetProcessor + Legacy::Editor + AutomatedTesting.Assets + AutomatedTesting.GameLauncher + COMPONENT + LargeWorlds + ) + ly_add_pytest( + NAME AutomatedTesting::DynamicVegetationTests_Periodic_Optimized + TEST_SERIAL + TEST_SUITE periodic + PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg/TestSuite_Periodic_Optimized.py + RUNTIME_DEPENDENCIES + AZ::AssetProcessor + Legacy::Editor + AutomatedTesting.Assets + AutomatedTesting.GameLauncher COMPONENT LargeWorlds ) diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynVegUtils_TempPrefabCreationWorks.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynVegUtils_TempPrefabCreationWorks.py new file mode 100644 index 0000000000..d016473d5e --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynVegUtils_TempPrefabCreationWorks.py @@ -0,0 +1,85 @@ +""" +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 +""" + + +def DynVegUtils_TempPrefabCreationWorks(): + """ + Summary: + An existing level is opened. Each Prefab setup to be spawned by Dynamic Vegetation tests is created in memory and + validated against existing test slice components/mesh assignments. + + Expected Behavior: + Temporary prefabs contain the expected components/assets. + + Test Steps: + 1) Open an existing level + 2) Create each of the necessary temporary Mesh prefabs, and validate the component/mesh setups + 3) Create the necessary temporary PhysX Collider, and validate the component setup + 4) Report errors/asserts + + :return: None + """ + + import os + + import azlmbr.asset as asset + import azlmbr.bus as bus + import azlmbr.math as math + + from Prefab.tests import PrefabTestUtils as prefab_test_utils + from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg + from editor_python_test_tools.utils import Report, Tracer + from editor_python_test_tools.utils import TestHelper as helper + from editor_python_test_tools.prefab_utils import PrefabInstance + + with Tracer() as error_tracer: + # Create dictionary for prefab filenames and paths to create using helper function + mesh_prefabs = { + "PinkFlower": os.path.join("assets", "objects", "foliage", "grass_flower_pink.azmodel"), + "PurpleFlower": os.path.join("assets", "objects", "foliage", "grass_flower_purple.azmodel"), + "1m_Cube": os.path.join("objects", "_primitives", "_box_1x1.azmodel"), + "CedarTree": os.path.join("assets", "objects", "foliage", "cedar.azmodel"), + "Bush": os.path.join("assets", "objects", "foliage", "bush_privet_01.azmodel"), + } + + # 1) Open an existing simple level + prefab_test_utils.open_base_tests_level() + + # 2) Create each of the Mesh asset prefabs and validate that the prefab created successfully + for prefab_filename, asset_path in mesh_prefabs.items(): + mesh_prefab_created = ( + f"Temporary mesh prefab: {prefab_filename} created successfully", + f"Failed to create temporary mesh prefab: {prefab_filename}" + ) + prefab = dynveg.create_temp_mesh_prefab(asset_path, prefab_filename) + Report.result(mesh_prefab_created, helper.wait_for_condition(lambda: + PrefabInstance.is_valid(prefab[1]), 3.0)) + + # 3) Create temp PhysX Collider prefab and validate that the prefab created successfully + physx_prefab_filename = "CedarTree_Collision" + physx_collider_prefab_created = ( + f"Temporary mesh prefab: {physx_prefab_filename} created successfully", + f"Failed to create temporary mesh prefab: {physx_prefab_filename}" + ) + test_physx_mesh_asset_id = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", os.path.join( + "assets", "objects", "foliage", "cedar.pxmesh"), math.Uuid(), False) + dynveg.create_temp_physx_mesh_collider(test_physx_mesh_asset_id, physx_prefab_filename) + Report.result(physx_collider_prefab_created, helper.wait_for_condition(lambda: + PrefabInstance.is_valid(prefab[1]), 3.0)) + + # 4) Report errors/asserts + helper.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(DynVegUtils_TempPrefabCreationWorks) diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_Embedded_E2E.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_Embedded_E2E.py index 84c661873c..fa45e057e2 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_Embedded_E2E.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_Embedded_E2E.py @@ -72,9 +72,9 @@ def DynamicSliceInstanceSpawner_Embedded_E2E(): # 1) Create a new, temporary level lvl_name = "tmp_level" helper.init_idle() - level_created = general.create_level_no_prompt(lvl_name, 1024, 1, 4096, False) + level_created = helper.create_level(lvl_name) general.idle_wait(1.0) - Report.critical_result(Tests.level_created, level_created == 0) + Report.critical_result(Tests.level_created, level_created) general.set_current_view_position(512.0, 480.0, 38.0) # 2) Create a new entity with required vegetation area components and Script Canvas component for launcher test diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_External_E2E.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_External_E2E.py index de2554034f..2353095849 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_External_E2E.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/DynamicSliceInstanceSpawner_External_E2E.py @@ -73,9 +73,9 @@ def DynamicSliceInstanceSpawner_External_E2E(): # 1) Create a new, temporary level lvl_name = "tmp_level" helper.init_idle() - level_created = general.create_level_no_prompt(lvl_name, 1024, 1, 4096, False) + level_created = helper.create_level(lvl_name) general.idle_wait(1.0) - Report.critical_result(Tests.level_created, level_created == 0) + Report.critical_result(Tests.level_created, level_created) general.set_current_view_position(512.0, 480.0, 38.0) # 2) Create a new entity with required vegetation area components and switch the Vegetation Asset List Source diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerBlender_E2E_Editor.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerBlender_E2E_Editor.py index bf6501f469..130d56937b 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerBlender_E2E_Editor.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerBlender_E2E_Editor.py @@ -76,9 +76,9 @@ def LayerBlender_E2E_Editor(): # 1) Create a new, temporary level lvl_name = "tmp_level" helper.init_idle() - level_created = general.create_level_no_prompt(lvl_name, 1024, 1, 4096, False) + level_created = helper.create_level(lvl_name) general.idle_wait(1.0) - Report.critical_result(Tests.level_created, level_created == 0) + Report.critical_result(Tests.level_created, level_created) general.set_current_view_position(500.49, 498.69, 46.66) general.set_current_view_rotation(-42.05, 0.00, -36.33) diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InheritBehaviorFlag.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InheritBehaviorFlag.py index 649c7d0776..1153ae2657 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InheritBehaviorFlag.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InheritBehaviorFlag.py @@ -78,7 +78,7 @@ def LayerSpawner_InheritBehaviorFlag(): # Create Vegetation area and assign a valid asset veg_1 = hydra.Entity("veg_1") veg_1.create_entity( - position, ["Vegetation Layer Spawner", "Vegetation Reference Shape", "Vegetation Asset List"] + position, ["Vegetation Layer Spawner", "Shape Reference", "Vegetation Asset List"] ) set_dynamic_slice_asset(veg_1, 2, os.path.join("Slices", "PinkFlower.dynamicslice")) veg_1.get_set_test(1, "Configuration|Shape Entity Id", blender_entity.id) @@ -86,7 +86,7 @@ def LayerSpawner_InheritBehaviorFlag(): # Create second vegetation area and assign a valid asset veg_2 = hydra.Entity("veg_2") veg_2.create_entity( - position, ["Vegetation Layer Spawner", "Vegetation Reference Shape", "Vegetation Asset List"] + position, ["Vegetation Layer Spawner", "Shape Reference", "Vegetation Asset List"] ) set_dynamic_slice_asset(veg_2, 2, os.path.join("Slices", "PurpleFlower.dynamicslice")) veg_2.get_set_test(1, "Configuration|Shape Entity Id", blender_entity.id) diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InstancesPlantInAllSupportedShapes.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InstancesPlantInAllSupportedShapes.py index 0da200d87a..42604cd2da 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InstancesPlantInAllSupportedShapes.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/EditorScripts/LayerSpawner_InstancesPlantInAllSupportedShapes.py @@ -9,7 +9,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT def LayerSpawner_InstancesPlantInAllSupportedShapes(): """ Summary: - The level is loaded and vegetation area is created. Then the Vegetation Reference Shape + The level is loaded and vegetation area is created. Then the Shape Reference component of vegetation area is pinned with entities of different shape components to check if the vegetation plants in different shaped areas. @@ -67,7 +67,7 @@ def LayerSpawner_InstancesPlantInAllSupportedShapes(): 10.0, 10.0, 10.0, asset_path) vegetation.remove_component("Box Shape") - vegetation.add_component("Vegetation Reference Shape") + vegetation.add_component("Shape Reference") # Create surface for planting on dynveg.create_surface_entity("Surface Entity", entity_position, 60.0, 60.0, 1.0) diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Main.py index 4c02c887ef..06c9c5f615 100644 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Main.py @@ -20,8 +20,8 @@ class TestAutomation(TestAutomationBase): def test_DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks(self, request, workspace, editor, launcher_platform): from .EditorScripts import DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_EmptyInstanceSpawner_EmptySpawnerWorks(self, request, workspace, editor, launcher_platform): from .EditorScripts import EmptyInstanceSpawner_EmptySpawnerWorks as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Main_Optimized.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Main_Optimized.py index ded2dda4e9..673e40e397 100644 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Main_Optimized.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Main_Optimized.py @@ -12,12 +12,24 @@ import ly_test_tools.environment.file_system as file_system from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite -@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.") @pytest.mark.SUITE_main @pytest.mark.parametrize("launcher_platform", ['windows_editor']) @pytest.mark.parametrize("project", ["AutomatedTesting"]) class TestAutomation(EditorTestSuite): + enable_prefab_system = False + + # Helpers for test asset cleanup + def cleanup_test_level(self, workspace): + file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")], + True, True) + + def cleanup_test_slices(self, workspace): + file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "slices", + "TestSlice_1.slice")], True, True) + file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "slices", + "TestSlice_2.slice")], True, True) + class test_DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks(EditorParallelTest): from .EditorScripts import DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks as test_module @@ -36,10 +48,7 @@ class TestAutomation(EditorTestSuite): class test_SpawnerSlices_SliceCreationAndVisibilityToggleWorks(EditorSingleTest): # Custom teardown to remove slice asset created during test def teardown(self, request, workspace, editor, editor_test_results, launcher_platform): - file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "slices", - "TestSlice_1.slice")], True, True) - file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "slices", - "TestSlice_2.slice")], True, True) + TestAutomation.cleanup_test_slices(self, workspace) from .EditorScripts import SpawnerSlices_SliceCreationAndVisibilityToggleWorks as test_module class test_AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea(EditorParallelTest): @@ -150,23 +159,29 @@ class TestAutomation(EditorTestSuite): class test_DynamicSliceInstanceSpawner_Embedded_E2E_Editor(EditorSingleTest): from .EditorScripts import DynamicSliceInstanceSpawner_Embedded_E2E as test_module - # Custom teardown to remove test level created during test + # Custom setup/teardown to remove test level created during test + def setup(self, request, workspace, editor, editor_test_results, launcher_platform): + TestAutomation.cleanup_test_level(self, workspace) + def teardown(self, request, workspace, editor, editor_test_results, launcher_platform): - file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")], - True, True) + TestAutomation.cleanup_test_level(self, workspace) class test_DynamicSliceInstanceSpawner_External_E2E_Editor(EditorSingleTest): from .EditorScripts import DynamicSliceInstanceSpawner_External_E2E as test_module - # Custom teardown to remove test level created during test + # Custom setup/teardown to remove test level created during test + def setup(self, request, workspace, editor, editor_test_results, launcher_platform): + TestAutomation.cleanup_test_level(self, workspace) + def teardown(self, request, workspace, editor, editor_test_results, launcher_platform): - file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")], - True, True) - + TestAutomation.cleanup_test_level(self, workspace) + class test_LayerBlender_E2E_Editor(EditorSingleTest): from .EditorScripts import LayerBlender_E2E_Editor as test_module - # Custom teardown to remove test level created during test + # Custom setup/teardown to remove test level created during test + def setup(self, request, workspace, editor, editor_test_results, launcher_platform): + TestAutomation.cleanup_test_level(self, workspace) + def teardown(self, request, workspace, editor, editor_test_results, launcher_platform): - file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")], - True, True) + TestAutomation.cleanup_test_level(self, workspace) diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Periodic.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Periodic.py index 2780c0f471..d0d570950b 100644 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Periodic.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Periodic.py @@ -52,158 +52,158 @@ class TestAutomation(TestAutomationBase): def test_AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude(self, request, workspace, editor, launcher_platform): from .EditorScripts import AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude(self, request, workspace, editor, launcher_platform): from .EditorScripts import AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_AltitudeFilter_FilterStageToggle(self, request, workspace, editor, launcher_platform): from .EditorScripts import AltitudeFilter_FilterStageToggle as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_SpawnerSlices_SliceCreationAndVisibilityToggleWorks(self, request, workspace, editor, remove_test_slice, launcher_platform): from .EditorScripts import SpawnerSlices_SliceCreationAndVisibilityToggleWorks as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea(self, request, workspace, editor, launcher_platform): from .EditorScripts import AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_AssetWeightSelector_InstancesExpressBasedOnWeight(self, request, workspace, editor, launcher_platform): from .EditorScripts import AssetWeightSelector_InstancesExpressBasedOnWeight as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/4155") def test_DistanceBetweenFilter_InstancesPlantAtSpecifiedRadius(self, request, workspace, editor, launcher_platform): from .EditorScripts import DistanceBetweenFilter_InstancesPlantAtSpecifiedRadius as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/4155") def test_DistanceBetweenFilterOverrides_InstancesPlantAtSpecifiedRadius(self, request, workspace, editor, launcher_platform): from .EditorScripts import DistanceBetweenFilterOverrides_InstancesPlantAtSpecifiedRadius as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_SurfaceDataRefreshes_RemainsStable(self, request, workspace, editor, launcher_platform): from .EditorScripts import SurfaceDataRefreshes_RemainsStable as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_VegetationInstances_DespawnWhenOutOfRange(self, request, workspace, editor, launcher_platform): from .EditorScripts import VegetationInstances_DespawnWhenOutOfRange as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_InstanceSpawnerPriority_LayerAndSubPriority_HigherValuesPlantOverLower(self, request, workspace, editor, launcher_platform): from .EditorScripts import InstanceSpawnerPriority_LayerAndSubPriority as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LayerBlocker_InstancesBlockedInConfiguredArea(self, request, workspace, editor, launcher_platform): from .EditorScripts import LayerBlocker_InstancesBlockedInConfiguredArea as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LayerSpawner_InheritBehaviorFlag(self, request, workspace, editor, launcher_platform): from .EditorScripts import LayerSpawner_InheritBehaviorFlag as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LayerSpawner_InstancesPlantInAllSupportedShapes(self, request, workspace, editor, launcher_platform): from .EditorScripts import LayerSpawner_InstancesPlantInAllSupportedShapes as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LayerSpawner_FilterStageToggle(self, request, workspace, editor, launcher_platform): from .EditorScripts import LayerSpawner_FilterStageToggle as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/2038") def test_LayerSpawner_InstancesRefreshUsingCorrectViewportCamera(self, request, workspace, editor, launcher_platform): from .EditorScripts import LayerSpawner_InstancesRefreshUsingCorrectViewportCamera as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_MeshBlocker_InstancesBlockedByMesh(self, request, workspace, editor, launcher_platform): from .EditorScripts import MeshBlocker_InstancesBlockedByMesh as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_MeshBlocker_InstancesBlockedByMeshHeightTuning(self, request, workspace, editor, launcher_platform): from .EditorScripts import MeshBlocker_InstancesBlockedByMeshHeightTuning as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_MeshSurfaceTagEmitter_DependentOnMeshComponent(self, request, workspace, editor, launcher_platform): from .EditorScripts import MeshSurfaceTagEmitter_DependentOnMeshComponent as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_MeshSurfaceTagEmitter_SurfaceTagsAddRemoveSuccessfully(self, request, workspace, editor, launcher_platform): from .EditorScripts import MeshSurfaceTagEmitter_SurfaceTagsAddRemoveSuccessfully as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_PhysXColliderSurfaceTagEmitter_E2E_Editor(self, request, workspace, editor, launcher_platform): from .EditorScripts import PhysXColliderSurfaceTagEmitter_E2E_Editor as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_PositionModifier_ComponentAndOverrides_InstancesPlantAtSpecifiedOffsets(self, request, workspace, editor, launcher_platform): from .EditorScripts import PositionModifier_ComponentAndOverrides_InstancesPlantAtSpecifiedOffsets as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_PositionModifier_AutoSnapToSurfaceWorks(self, request, workspace, editor, launcher_platform): from .EditorScripts import PositionModifier_AutoSnapToSurfaceWorks as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_RotationModifier_InstancesRotateWithinRange(self, request, workspace, editor, launcher_platform): from .EditorScripts import RotationModifier_InstancesRotateWithinRange as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_RotationModifierOverrides_InstancesRotateWithinRange(self, request, workspace, editor, launcher_platform): from .EditorScripts import RotationModifierOverrides_InstancesRotateWithinRange as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ScaleModifier_InstancesProperlyScale(self, request, workspace, editor, launcher_platform): from .EditorScripts import ScaleModifier_InstancesProperlyScale as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ScaleModifierOverrides_InstancesProperlyScale(self, request, workspace, editor, launcher_platform): from .EditorScripts import ScaleModifierOverrides_InstancesProperlyScale as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ShapeIntersectionFilter_InstancesPlantInAssignedShape(self, request, workspace, editor, launcher_platform): from .EditorScripts import ShapeIntersectionFilter_InstancesPlantInAssignedShape as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ShapeIntersectionFilter_FilterStageToggle(self, request, workspace, editor, launcher_platform): from .EditorScripts import ShapeIntersectionFilter_FilterStageToggle as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_SlopeAlignmentModifier_InstanceSurfaceAlignment(self, request, workspace, editor, launcher_platform): from .EditorScripts import SlopeAlignmentModifier_InstanceSurfaceAlignment as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_SlopeAlignmentModifierOverrides_InstanceSurfaceAlignment(self, request, workspace, editor, launcher_platform): from .EditorScripts import SlopeAlignmentModifierOverrides_InstanceSurfaceAlignment as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_SurfaceMaskFilter_BasicSurfaceTagCreation(self, request, workspace, editor, launcher_platform): from .EditorScripts import SurfaceMaskFilter_BasicSurfaceTagCreation as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_SurfaceMaskFilter_ExclusiveSurfaceTags_Function(self, request, workspace, editor, launcher_platform): from .EditorScripts import SurfaceMaskFilter_ExclusionList as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_SurfaceMaskFilter_InclusiveSurfaceTags_Function(self, request, workspace, editor, launcher_platform): from .EditorScripts import SurfaceMaskFilter_InclusionList as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_SurfaceMaskFilterOverrides_MultipleDescriptorOverridesPlantAsExpected(self, request, workspace, editor, launcher_platform): from .EditorScripts import SurfaceMaskFilterOverrides_MultipleDescriptorOverridesPlantAsExpected as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_SystemSettings_SectorPointDensity(self, request, workspace, editor, launcher_platform): from .EditorScripts import SystemSettings_SectorPointDensity as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_SystemSettings_SectorSize(self, request, workspace, editor, launcher_platform): from .EditorScripts import SystemSettings_SectorSize as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_SlopeFilter_ComponentAndOverrides_InstancesPlantOnValidSlopes(self, request, workspace, editor, launcher_platform): from .EditorScripts import SlopeFilter_ComponentAndOverrides_InstancesPlantOnValidSlope as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.SUITE_periodic @@ -219,7 +219,7 @@ class TestAutomationE2E(TestAutomationBase): file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True) from .EditorScripts import DynamicSliceInstanceSpawner_Embedded_E2E as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.parametrize("launcher_platform", ['windows']) def test_DynamicSliceInstanceSpawner_Embedded_E2E_Launcher(self, workspace, launcher, level, @@ -240,7 +240,7 @@ class TestAutomationE2E(TestAutomationBase): file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True) from .EditorScripts import DynamicSliceInstanceSpawner_External_E2E as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.parametrize("launcher_platform", ['windows']) def test_DynamicSliceInstanceSpawner_External_E2E_Launcher(self, workspace, launcher, level, @@ -261,7 +261,7 @@ class TestAutomationE2E(TestAutomationBase): file_system.delete([os.path.join(workspace.paths.engine_root(), project, "Levels", level)], True, True) from .EditorScripts import LayerBlender_E2E_Editor as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.parametrize("launcher_platform", ['windows']) @pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/4170") diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Periodic_Optimized.py b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Periodic_Optimized.py new file mode 100644 index 0000000000..f87d6567ef --- /dev/null +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/dyn_veg/TestSuite_Periodic_Optimized.py @@ -0,0 +1,23 @@ +""" +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 EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite + + +@pytest.mark.SUITE_main +@pytest.mark.parametrize("launcher_platform", ['windows_editor']) +@pytest.mark.parametrize("project", ["AutomatedTesting"]) +class TestAutomation(EditorTestSuite): + + global_extra_cmdline_args = ["-BatchMode", "-autotest_mode"] + + class test_DynVegUtils_TempPrefabCreationWorks(EditorSharedTest): + from .EditorScripts import DynVegUtils_TempPrefabCreationWorks as test_module diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/TestSuite_Periodic.py b/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/TestSuite_Periodic.py index 21eecf642c..3ad27eb973 100644 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/TestSuite_Periodic.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/TestSuite_Periodic.py @@ -20,52 +20,52 @@ class TestAutomation(TestAutomationBase): def test_GradientGenerators_Incompatibilities(self, request, workspace, editor, launcher_platform): from .EditorScripts import GradientGenerators_Incompatibilities as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_GradientModifiers_Incompatibilities(self, request, workspace, editor, launcher_platform): from .EditorScripts import GradientModifiers_Incompatibilities as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_GradientPreviewSettings_DefaultPinnedEntityIsSelf(self, request, workspace, editor, launcher_platform): from .EditorScripts import GradientPreviewSettings_DefaultPinnedEntityIsSelf as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_GradientPreviewSettings_ClearingPinnedEntitySetsPreviewToOrigin(self, request, workspace, editor, launcher_platform): from .EditorScripts import GradientPreviewSettings_ClearingPinnedEntitySetsPreviewToOrigin as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_GradientSampling_GradientReferencesAddRemoveSuccessfully(self, request, workspace, editor, launcher_platform): from .EditorScripts import GradientSampling_GradientReferencesAddRemoveSuccessfully as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_GradientSurfaceTagEmitter_ComponentDependencies(self, request, workspace, editor, launcher_platform): from .EditorScripts import GradientSurfaceTagEmitter_ComponentDependencies as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_GradientSurfaceTagEmitter_SurfaceTagsAddRemoveSuccessfully(self, request, workspace, editor, launcher_platform): from .EditorScripts import GradientSurfaceTagEmitter_SurfaceTagsAddRemoveSuccessfully as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_GradientTransform_RequiresShape(self, request, workspace, editor, launcher_platform): from .EditorScripts import GradientTransform_RequiresShape as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_GradientTransform_FrequencyZoomCanBeSetBeyondSliderRange(self, request, workspace, editor, launcher_platform): from .EditorScripts import GradientTransform_FrequencyZoomCanBeSetBeyondSliderRange as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_GradientTransform_ComponentIncompatibleWithSpawners(self, request, workspace, editor, launcher_platform): from .EditorScripts import GradientTransform_ComponentIncompatibleWithSpawners as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_GradientTransform_ComponentIncompatibleWithExpectedGradients(self, request, workspace, editor, launcher_platform): from .EditorScripts import GradientTransform_ComponentIncompatibleWithExpectedGradients as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ImageGradient_RequiresShape(self, request, workspace, editor, launcher_platform): from .EditorScripts import ImageGradient_RequiresShape as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ImageGradient_ProcessedImageAssignedSuccessfully(self, request, workspace, editor, launcher_platform): from .EditorScripts import ImageGradient_ProcessedImageAssignedSuccessfully as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/TestSuite_Periodic_Optimized.py b/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/TestSuite_Periodic_Optimized.py index 514504d324..6ac0658b4d 100644 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/TestSuite_Periodic_Optimized.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/gradient_signal/TestSuite_Periodic_Optimized.py @@ -15,6 +15,8 @@ from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, E @pytest.mark.parametrize("project", ["AutomatedTesting"]) class TestAutomation(EditorTestSuite): + enable_prefab_system = False + class test_GradientGenerators_Incompatibilities(EditorSharedTest): from .EditorScripts import GradientGenerators_Incompatibilities as test_module diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/AreaNodes_DependentComponentsAdded.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/AreaNodes_DependentComponentsAdded.py index 9703423901..c69ce77041 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/AreaNodes_DependentComponentsAdded.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/AreaNodes_DependentComponentsAdded.py @@ -96,7 +96,7 @@ def AreaNodes_DependentComponentsAdded(): 'SpawnerAreaNode': [ 'Vegetation Layer Spawner', 'Vegetation Asset List', - 'Vegetation Reference Shape' + 'Shape Reference' ], 'MeshBlockerAreaNode': [ 'Vegetation Layer Blocker (Mesh)', @@ -104,7 +104,7 @@ def AreaNodes_DependentComponentsAdded(): ], 'BlockerAreaNode': [ 'Vegetation Layer Blocker', - 'Vegetation Reference Shape' + 'Shape Reference' ] } diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/Edit_DisabledNodeDuplication.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/Edit_DisabledNodeDuplication.py index 417e093567..4dfc227c53 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/Edit_DisabledNodeDuplication.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/Edit_DisabledNodeDuplication.py @@ -82,7 +82,7 @@ def Edit_DisabledNodeDuplication(): nodes = { 'SpawnerAreaNode': 'Vegetation Asset List', 'MeshBlockerAreaNode': 'Mesh', - 'BlockerAreaNode': 'Vegetation Reference Shape', + 'BlockerAreaNode': 'Shape Reference', 'FastNoiseGradientNode': 'Gradient Transform Modifier', 'ImageGradientNode': 'Gradient Transform Modifier', 'PerlinNoiseGradientNode': 'Gradient Transform Modifier', diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientNodes_DependentComponentsAdded.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientNodes_DependentComponentsAdded.py index c04f9f05f6..63df9a6fcb 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientNodes_DependentComponentsAdded.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/EditorScripts/GradientNodes_DependentComponentsAdded.py @@ -104,7 +104,7 @@ def GradientNodes_DependentComponentsAdded(): # we will be checking for commonComponents = [ 'Gradient Transform Modifier', - 'Vegetation Reference Shape' + 'Shape Reference' ] componentNames = [] for name in gradients: @@ -114,7 +114,7 @@ def GradientNodes_DependentComponentsAdded(): # Create nodes for the gradients that have additional required dependencies and check if # the Entity created by adding the node has the appropriate Component and required - # Gradient Transform Modifier and Vegetation Reference Shape components added automatically to it + # Gradient Transform Modifier and Shape Reference components added automatically to it newGraph = graph.GraphManagerRequestBus(bus.Broadcast, 'GetGraph', newGraphId) x = 10.0 y = 10.0 diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Main.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Main.py index af4855a546..dedc9c70c0 100644 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Main.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Main.py @@ -22,8 +22,8 @@ class TestAutomation(TestAutomationBase): def test_LandscapeCanvas_SlotConnections_UpdateComponentReferences(self, request, workspace, editor, launcher_platform): from .EditorScripts import SlotConnections_UpdateComponentReferences as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_GradientMixer_NodeConstruction(self, request, workspace, editor, launcher_platform): from .EditorScripts import GradientMixer_NodeConstruction as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Main_Optimized.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Main_Optimized.py index 1c3652cae9..402df133cb 100644 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Main_Optimized.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Main_Optimized.py @@ -18,6 +18,8 @@ from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, E @pytest.mark.parametrize("project", ["AutomatedTesting"]) class TestAutomation(EditorTestSuite): + enable_prefab_system = False + class test_LandscapeCanvas_SlotConnections_UpdateComponentReferences(EditorSharedTest): from .EditorScripts import SlotConnections_UpdateComponentReferences as test_module diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Periodic.py b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Periodic.py index ef8b3e492b..a719b1a7c0 100644 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Periodic.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/landscape_canvas/TestSuite_Periodic.py @@ -33,89 +33,89 @@ class TestAutomation(TestAutomationBase): def test_LandscapeCanvas_AreaNodes_DependentComponentsAdded(self, request, workspace, editor, launcher_platform): from .EditorScripts import AreaNodes_DependentComponentsAdded as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_AreaNodes_EntityCreatedOnNodeAdd(self, request, workspace, editor, launcher_platform): from .EditorScripts import AreaNodes_EntityCreatedOnNodeAdd as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_AreaNodes_EntityRemovedOnNodeDelete(self, request, workspace, editor, launcher_platform): from .EditorScripts import AreaNodes_EntityRemovedOnNodeDelete as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_LayerExtenderNodes_ComponentEntitySync(self, request, workspace, editor, launcher_platform): from .EditorScripts import LayerExtenderNodes_ComponentEntitySync as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_Edit_DisabledNodeDuplication(self, request, workspace, editor, launcher_platform): from .EditorScripts import Edit_DisabledNodeDuplication as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_Edit_UndoNodeDelete_SliceEntity(self, request, workspace, editor, launcher_platform): from .EditorScripts import Edit_UndoNodeDelete_SliceEntity as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_NewGraph_CreatedSuccessfully(self, request, workspace, editor, launcher_platform): from .EditorScripts import NewGraph_CreatedSuccessfully as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_Component_AddedRemoved(self, request, workspace, editor, launcher_platform): from .EditorScripts import Component_AddedRemoved as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_GraphClosed_OnLevelChange(self, request, workspace, editor, launcher_platform): from .EditorScripts import GraphClosed_OnLevelChange as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.xfail(reason="https://github.com/o3de/o3de/issues/2201") def test_LandscapeCanvas_GraphClosed_OnEntityDelete(self, request, workspace, editor, launcher_platform): from .EditorScripts import GraphClosed_OnEntityDelete as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_GraphClosed_TabbedGraphClosesIndependently(self, request, workspace, editor, launcher_platform): from .EditorScripts import GraphClosed_TabbedGraph as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_Slice_CreateInstantiate(self, request, workspace, editor, remove_test_slice, launcher_platform): from .EditorScripts import Slice_CreateInstantiate as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_GradientModifierNodes_EntityCreatedOnNodeAdd(self, request, workspace, editor, launcher_platform): from .EditorScripts import GradientModifierNodes_EntityCreatedOnNodeAdd as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_GradientModifierNodes_EntityRemovedOnNodeDelete(self, request, workspace, editor, launcher_platform): from .EditorScripts import GradientModifierNodes_EntityRemovedOnNodeDelete as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_GradientNodes_DependentComponentsAdded(self, request, workspace, editor, launcher_platform): from .EditorScripts import GradientNodes_DependentComponentsAdded as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_GradientNodes_EntityCreatedOnNodeAdd(self, request, workspace, editor, launcher_platform): from .EditorScripts import GradientNodes_EntityCreatedOnNodeAdd as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_GradientNodes_EntityRemovedOnNodeDelete(self, request, workspace, editor, launcher_platform): from .EditorScripts import GradientNodes_EntityRemovedOnNodeDelete as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_GraphUpdates_UpdateComponents(self, request, workspace, editor, launcher_platform): from .EditorScripts import GraphUpdates_UpdateComponents as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_ComponentUpdates_UpdateGraph(self, request, workspace, editor, launcher_platform): from .EditorScripts import ComponentUpdates_UpdateGraph as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_LayerBlender_NodeConstruction(self, request, workspace, editor, launcher_platform): from .EditorScripts import LayerBlender_NodeConstruction as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_ShapeNodes_EntityCreatedOnNodeAdd(self, request, workspace, editor, launcher_platform): from .EditorScripts import ShapeNodes_EntityCreatedOnNodeAdd as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_LandscapeCanvas_ShapeNodes_EntityRemovedOnNodeDelete(self, request, workspace, editor, launcher_platform): from .EditorScripts import ShapeNodes_EntityRemovedOnNodeDelete as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/large_worlds_utils/editor_dynveg_test_helper.py b/AutomatedTesting/Gem/PythonTests/largeworlds/large_worlds_utils/editor_dynveg_test_helper.py index 957536fffb..d7d5842518 100755 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/large_worlds_utils/editor_dynveg_test_helper.py +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/large_worlds_utils/editor_dynveg_test_helper.py @@ -19,6 +19,45 @@ import azlmbr.paths sys.path.append(os.path.join(azlmbr.paths.projectroot, 'Gem', 'PythonTests')) import editor_python_test_tools.hydra_editor_utils as hydra +from editor_python_test_tools.editor_entity_utils import EditorEntity +from editor_python_test_tools.prefab_utils import Prefab + + +def create_temp_mesh_prefab(mesh_asset_path, prefab_filename): + # Create initial entity + root = EditorEntity.create_editor_entity(name=prefab_filename) + assert root.exists(), "Failed to create entity" + # Add mesh component + mesh_component = root.add_component("Mesh") + assert root.has_component("Mesh") and mesh_component.is_enabled(), "Failed to add/activate Mesh component" + # Assign the specified mesh asset + mesh_asset = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", mesh_asset_path, math.Uuid(), False) + mesh_component.set_component_property_value("Controller|Configuration|Mesh Asset", mesh_asset) + assert mesh_component.get_component_property_value("Controller|Configuration|Mesh Asset") == mesh_asset, \ + "Failed to set Mesh asset" + # Create and return the temporary/in-memory prefab + temp_prefab = Prefab.create_prefab([root], prefab_filename) + return temp_prefab + + +def create_temp_physx_mesh_collider(physx_mesh_id, prefab_filename): + # Create initial entity + root = EditorEntity.create_editor_entity(name=prefab_filename) + assert root.exists(), "Failed to create entity" + # Add PhysX Collider component + collider_component = root.add_component("PhysX Collider") + assert root.has_component("PhysX Collider") and collider_component.is_enabled(), \ + "Failed to add/activate PhysX Collider component" + # Set the Collider's Shape Configuration field to PhysicsAsset, and assign the specified PhysX Mesh asset + collider_component.set_component_property_value("Shape Configuration|Shape", 7) + assert collider_component.get_component_property_value("Shape Configuration|Shape") == 7, \ + "Failed to set Collider Shape to PhysicsAsset" + collider_component.set_component_property_value("Shape Configuration|Asset|PhysX Mesh", physx_mesh_id) + assert collider_component.get_component_property_value("Shape Configuration|Asset|PhysX Mesh") == physx_mesh_id, \ + "Failed to assign PhysX Mesh asset" + # Create and return the temporary/in-memory prefab + temp_prefab = Prefab.create_prefab([root], prefab_filename) + return temp_prefab def create_surface_entity(name, center_point, box_size_x, box_size_y, box_size_z): diff --git a/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Periodic.py b/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Periodic.py index b2001e6825..27af63ddbc 100755 --- a/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Periodic.py +++ b/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Periodic.py @@ -27,15 +27,15 @@ TEST_DIRECTORY = os.path.dirname(__file__) class TestAutomation(TestAutomationBase): def test_Pane_HappyPath_OpenCloseSuccessfully(self, request, workspace, editor, launcher_platform): from . import Pane_HappyPath_OpenCloseSuccessfully as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Pane_HappyPath_DocksProperly(self, request, workspace, editor, launcher_platform): from . import Pane_HappyPath_DocksProperly as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Pane_HappyPath_ResizesProperly(self, request, workspace, editor, launcher_platform): from . import Pane_HappyPath_ResizesProperly as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.xfail(reason="Test fails to find expected lines, it needs to be fixed.") @pytest.mark.parametrize("level", ["tmp_level"]) @@ -45,7 +45,7 @@ class TestAutomation(TestAutomationBase): request.addfinalizer(teardown) file_system.delete([os.path.join(workspace.paths.project(), "Levels", level)], True, True) from . import ScriptCanvas_TwoComponents_InteractSuccessfully as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.xfail(reason="Test fails to find expected lines, it needs to be fixed.") @pytest.mark.parametrize("level", ["tmp_level"]) @@ -55,15 +55,15 @@ class TestAutomation(TestAutomationBase): request.addfinalizer(teardown) file_system.delete([os.path.join(workspace.paths.project(), "Levels", level)], True, True) from . import ScriptCanvas_ChangingAssets_ComponentStable as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Graph_HappyPath_ZoomInZoomOut(self, request, workspace, editor, launcher_platform): from . import Graph_HappyPath_ZoomInZoomOut as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_NodePalette_HappyPath_CanSelectNode(self, request, workspace, editor, launcher_platform): from . import NodePalette_HappyPath_CanSelectNode as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.xfail(reason="Test fails to find expected lines, it needs to be fixed.") @pytest.mark.parametrize("level", ["tmp_level"]) @@ -73,11 +73,11 @@ class TestAutomation(TestAutomationBase): request.addfinalizer(teardown) file_system.delete([os.path.join(workspace.paths.project(), "Levels", level)], True, True) from . import ScriptCanvasComponent_OnEntityActivatedDeactivated_PrintMessage as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_NodePalette_HappyPath_ClearSelection(self, request, workspace, editor, launcher_platform, project): from . import NodePalette_HappyPath_ClearSelection as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.xfail(reason="Test fails to find expected lines, it needs to be fixed.") @pytest.mark.parametrize("level", ["tmp_level"]) @@ -87,7 +87,7 @@ class TestAutomation(TestAutomationBase): request.addfinalizer(teardown) file_system.delete([os.path.join(workspace.paths.project(), "Levels", level)], True, True) from . import ScriptCanvas_TwoEntities_UseSimultaneously as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ScriptEvent_HappyPath_CreatedWithoutError(self, request, workspace, editor, launcher_platform, project): def teardown(): @@ -99,19 +99,19 @@ class TestAutomation(TestAutomationBase): [os.path.join(workspace.paths.project(), "ScriptCanvas", "test_file.scriptevent")], True, True ) from . import ScriptEvent_HappyPath_CreatedWithoutError as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ScriptCanvasTools_Toggle_OpenCloseSuccess(self, request, workspace, editor, launcher_platform): from . import ScriptCanvasTools_Toggle_OpenCloseSuccess as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_NodeInspector_HappyPath_VariableRenames(self, request, workspace, editor, launcher_platform, project): from . import NodeInspector_HappyPath_VariableRenames as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Debugger_HappyPath_TargetMultipleGraphs(self, request, workspace, editor, launcher_platform, project): from . import Debugger_HappyPath_TargetMultipleGraphs as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.parametrize("level", ["tmp_level"]) def test_Debugger_HappyPath_TargetMultipleEntities(self, request, workspace, editor, launcher_platform, project, level): @@ -120,16 +120,16 @@ class TestAutomation(TestAutomationBase): request.addfinalizer(teardown) file_system.delete([os.path.join(workspace.paths.project(), "Levels", level)], True, True) from . import Debugger_HappyPath_TargetMultipleEntities as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.xfail(reason="Test fails to find expected lines, it needs to be fixed.") def test_EditMenu_Default_UndoRedo(self, request, workspace, editor, launcher_platform, project): from . import EditMenu_Default_UndoRedo as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Pane_Undocked_ClosesSuccessfully(self, request, workspace, editor, launcher_platform): from . import Pane_Undocked_ClosesSuccessfully as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.parametrize("level", ["tmp_level"]) def test_Entity_HappyPath_AddScriptCanvasComponent(self, request, workspace, editor, launcher_platform, project, level): @@ -138,11 +138,11 @@ class TestAutomation(TestAutomationBase): request.addfinalizer(teardown) file_system.delete([os.path.join(workspace.paths.project(), "Levels", level)], True, True) from . import Entity_HappyPath_AddScriptCanvasComponent as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Pane_Default_RetainOnSCRestart(self, request, workspace, editor, launcher_platform): from . import Pane_Default_RetainOnSCRestart as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.xfail(reason="Test fails to find expected lines, it needs to be fixed.") @pytest.mark.parametrize("level", ["tmp_level"]) @@ -152,7 +152,7 @@ class TestAutomation(TestAutomationBase): request.addfinalizer(teardown) file_system.delete([os.path.join(workspace.paths.project(), "Levels", level)], True, True) from . import ScriptEvents_HappyPath_SendReceiveAcrossMultiple as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.xfail(reason="Test fails to find expected lines, it needs to be fixed.") @pytest.mark.parametrize("level", ["tmp_level"]) @@ -162,7 +162,7 @@ class TestAutomation(TestAutomationBase): request.addfinalizer(teardown) file_system.delete([os.path.join(workspace.paths.project(), "Levels", level)], True, True) from . import ScriptEvents_Default_SendReceiveSuccessfully as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.xfail(reason="Test fails to find expected lines, it needs to be fixed.") @pytest.mark.parametrize("level", ["tmp_level"]) @@ -172,24 +172,24 @@ class TestAutomation(TestAutomationBase): request.addfinalizer(teardown) file_system.delete([os.path.join(workspace.paths.project(), "Levels", level)], True, True) from . import ScriptEvents_ReturnSetType_Successfully as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_NodeCategory_ExpandOnClick(self, request, workspace, editor, launcher_platform): from . import NodeCategory_ExpandOnClick as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_NodePalette_SearchText_Deletion(self, request, workspace, editor, launcher_platform): from . import NodePalette_SearchText_Deletion as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) @pytest.mark.xfail(reason="Test fails to find expected lines, it needs to be fixed.") def test_VariableManager_UnpinVariableType_Works(self, request, workspace, editor, launcher_platform): from . import VariableManager_UnpinVariableType_Works as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_Node_HappyPath_DuplicateNode(self, request, workspace, editor, launcher_platform): from . import Node_HappyPath_DuplicateNode as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) def test_ScriptEvent_AddRemoveParameter_ActionsSuccessful(self, request, workspace, editor, launcher_platform): def teardown(): @@ -201,7 +201,7 @@ class TestAutomation(TestAutomationBase): [os.path.join(workspace.paths.project(), "ScriptCanvas", "test_file.scriptevent")], True, True ) from . import ScriptEvent_AddRemoveParameter_ActionsSuccessful as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) # NOTE: We had to use hydra_test_utils.py, as TestAutomationBase run_test method # fails because of pyside_utils import @@ -220,7 +220,14 @@ class TestScriptCanvasTests(object): "File->Open action working as expected: True", ] hydra.launch_and_validate_results( - request, TEST_DIRECTORY, editor, "FileMenu_Default_NewAndOpen.py", expected_lines, auto_test_mode=False, timeout=60, + request, + TEST_DIRECTORY, + editor, + "FileMenu_Default_NewAndOpen.py", + expected_lines, + auto_test_mode=False, + timeout=60, + enable_prefab_system=False, ) @pytest.mark.xfail(reason="Test fails to find expected lines, it needs to be fixed.") @@ -239,6 +246,7 @@ class TestScriptCanvasTests(object): expected_lines, auto_test_mode=False, timeout=60, + enable_prefab_system=False, ) def test_GraphClose_Default_SavePrompt(self, request, editor, launcher_platform): @@ -255,6 +263,7 @@ class TestScriptCanvasTests(object): expected_lines, auto_test_mode=False, timeout=60, + enable_prefab_system=False, ) def test_VariableManager_Default_CreateDeleteVars(self, request, editor, launcher_platform): @@ -269,6 +278,7 @@ class TestScriptCanvasTests(object): expected_lines, auto_test_mode=False, timeout=60, + enable_prefab_system=False, ) @pytest.mark.parametrize( @@ -304,6 +314,7 @@ class TestScriptCanvasTests(object): cfg_args=[config.get('cfg_args')], auto_test_mode=False, timeout=60, + enable_prefab_system=False, ) @pytest.mark.xfail(reason="Test fails to find expected lines, it needs to be fixed.") @@ -332,6 +343,7 @@ class TestScriptCanvasTests(object): expected_lines, auto_test_mode=False, timeout=60, + enable_prefab_system=False, ) @pytest.mark.xfail(reason="Test fails to find expected lines, it needs to be fixed.") @@ -359,5 +371,6 @@ class TestScriptCanvasTests(object): expected_lines, auto_test_mode=False, timeout=60, + enable_prefab_system=False, ) \ No newline at end of file diff --git a/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Sandbox.py b/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Sandbox.py index 91b01d8d08..071ae2286c 100644 --- a/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Sandbox.py +++ b/AutomatedTesting/Gem/PythonTests/scripting/TestSuite_Sandbox.py @@ -23,4 +23,4 @@ class TestAutomation(TestAutomationBase): def test_Opening_Closing_Pane(self, request, workspace, editor, launcher_platform): from . import Opening_Closing_Pane as test_module - self._run_test(request, workspace, editor, test_module) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AzTestRunner_Works.py b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AzTestRunner_Works.py index b269c9e131..df755d5d11 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AzTestRunner_Works.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_AzTestRunner_Works.py @@ -13,18 +13,26 @@ import os import pytest import subprocess +import ly_test_tools + @pytest.mark.SUITE_smoke class TestCLIToolAzTestRunnerWorks(object): - def test_CLITool_AzTestRunner_Works(self, build_directory): + def test_CLITool_AzTestRunner_ListSelfTests(self, build_directory): file_path = os.path.join(build_directory, "AzTestRunner") help_message = "OKAY Symbol found: AzRunUnitTests" - # Launch AzTestRunner + + if ly_test_tools.WINDOWS: + target_lib = "AzTestRunner.Tests" + else: + target_lib = "libAzTestRunner.Tests" + + # Launch AzTestRunner, load self-tests, print test names output = subprocess.run( - [file_path, "AzTestRunner.Tests", "AzRunUnitTests", "--gtest_list_tests"], capture_output=True, timeout=10 + [file_path, target_lib, "AzRunUnitTests", "--gtest_list_tests"], capture_output=True, timeout=10 ) assert ( len(output.stderr) == 0 and output.returncode == 0 ), f"Error occurred while launching {file_path}: {output.stderr}" # Verify help message - assert help_message in str(output.stdout), f"Help Message: {help_message} is not present" + assert help_message in str(output.stdout), f"Help Message: '{help_message}' unexpectedly not present" diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_SerializeContextTools_Works.py b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_SerializeContextTools_Works.py index a3f6b8b09f..917ff17f82 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_SerializeContextTools_Works.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_CLITool_SerializeContextTools_Works.py @@ -13,7 +13,10 @@ import os import pytest import subprocess +import ly_test_tools + +@pytest.mark.skipif(not ly_test_tools.WINDOWS, reason="Only succeeds on windows https://github.com/o3de/o3de/issues/5539") @pytest.mark.SUITE_smoke class TestCLIToolSerializeContextToolsWorks(object): def test_CLITool_SerializeContextTools_Works(self, build_directory): diff --git a/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels_Works.py b/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels_Works.py index 72e548615c..5caf7744c4 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels_Works.py +++ b/AutomatedTesting/Gem/PythonTests/smoke/test_Editor_NewExistingLevels_Works.py @@ -11,10 +11,13 @@ Test should run in both gpu and non gpu import pytest import os from automatedtesting_shared.base import TestAutomationBase + +import ly_test_tools import ly_test_tools.environment.file_system as file_system @pytest.mark.SUITE_smoke +@pytest.mark.skipif(not ly_test_tools.WINDOWS, reason="Only succeeds on windows https://github.com/o3de/o3de/issues/5539") @pytest.mark.parametrize("launcher_platform", ["windows_editor"]) @pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.parametrize("level", ["temp_level"]) @@ -28,4 +31,4 @@ class TestAutomation(TestAutomationBase): from . import Editor_NewExistingLevels_Works as test_module - self._run_test(request, workspace, editor, test_module, extra_cmdline_args=["--regset=/Amazon/Preferences/EnablePrefabSystem=false"]) + self._run_test(request, workspace, editor, test_module, enable_prefab_system=False) diff --git a/AutomatedTesting/Gem/Sponza/.gitignore b/AutomatedTesting/Gem/Sponza/.gitignore new file mode 100644 index 0000000000..8bbb0be455 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/.gitignore @@ -0,0 +1,4 @@ +/.maya_data/* +/.mayaSwatches/* +*.swatch +[Uu]ser_env.bat \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/.src/objects/sponza.ma b/AutomatedTesting/Gem/Sponza/.src/objects/sponza.ma new file mode 100644 index 0000000000..1c137a50b7 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/.src/objects/sponza.ma @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57848334af0220b7348a8f2583080acf1d9c139a78c9fb1a93a7d2bce61f3c40 +size 41335413 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Prefabs/test_sponza_material_conversion.prefab b/AutomatedTesting/Gem/Sponza/Assets/Prefabs/test_sponza_material_conversion.prefab new file mode 100644 index 0000000000..92874574e4 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Prefabs/test_sponza_material_conversion.prefab @@ -0,0 +1,1240 @@ +{ + "Source": "Prefabs/test_sponza_material_conversion.prefab", + "ContainerEntity": { + "Id": "ContainerEntity", + "Name": "test_sponza_material_conversion", + "Components": { + "Component_[11355906858588942318]": { + "$type": "EditorEntitySortComponent", + "Id": 11355906858588942318 + }, + "Component_[12303631836799763574]": { + "$type": "EditorEntityIconComponent", + "Id": 12303631836799763574 + }, + "Component_[13884330903538620487]": { + "$type": "SelectionComponent", + "Id": 13884330903538620487 + }, + "Component_[14017626015546393905]": { + "$type": "EditorInspectorComponent", + "Id": 14017626015546393905 + }, + "Component_[15706249274315432595]": { + "$type": "EditorLockComponent", + "Id": 15706249274315432595 + }, + "Component_[17662098699702294917]": { + "$type": "EditorPendingCompositionComponent", + "Id": 17662098699702294917 + }, + "Component_[1984406083399463185]": { + "$type": "EditorOnlyEntityComponent", + "Id": 1984406083399463185 + }, + "Component_[3645983967515381372]": { + "$type": "EditorPrefabComponent", + "Id": 3645983967515381372 + }, + "Component_[7000715958539023355]": { + "$type": "EditorVisibilityComponent", + "Id": 7000715958539023355 + }, + "Component_[7182760741886065388]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7182760741886065388, + "Parent Entity": "", + "Cached World Transform Parent": "" + }, + "Component_[7314978375961307600]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7314978375961307600 + } + }, + "IsDependencyReady": true + }, + "Entities": { + "Entity_[1220355829629]": { + "Id": "Entity_[1220355829629]", + "Name": "GiTestProbe", + "Components": { + "Component_[12581383605035992405]": { + "$type": "SelectionComponent", + "Id": 12581383605035992405 + }, + "Component_[14182862666898786767]": { + "$type": "AZ::Render::EditorReflectionProbeComponent", + "Id": 14182862666898786767, + "Controller": { + "Configuration": { + "OuterHeight": 8.0, + "OuterLength": 8.0, + "OuterWidth": 8.0, + "InnerHeight": 8.0, + "InnerLength": 8.0, + "InnerWidth": 8.0, + "BakedCubeMapRelativePath": "ReflectionProbes/GiTestProbe__0564394E-2435-488E-A30C-E999F79FB049__iblspecularcm256.dds", + "BakedCubeMapAsset": { + "assetId": { + "guid": "{B05482A4-7D4D-5C6D-96DD-54CB9A227307}", + "subId": 2000 + }, + "loadBehavior": "PreLoad", + "assetHint": "reflectionprobes/gitestprobe__0564394e-2435-488e-a30c-e999f79fb049__iblspecularcm256.dds.streamingimage" + }, + "EntityId": 12080580926711778904 + } + }, + "bakedCubeMapRelativePath": "ReflectionProbes/GiTestProbe__0564394E-2435-488E-A30C-E999F79FB049__iblspecularcm256.dds" + }, + "Component_[16557111097824744403]": { + "$type": "EditorBoxShapeComponent", + "Id": 16557111097824744403, + "BoxShape": { + "Configuration": { + "Dimensions": [ + 8.0, + 8.0, + 8.0 + ] + } + } + }, + "Component_[16623349159368925155]": { + "$type": "EditorPendingCompositionComponent", + "Id": 16623349159368925155 + }, + "Component_[17588593493138070858]": { + "$type": "EditorVisibilityComponent", + "Id": 17588593493138070858 + }, + "Component_[18332655128892032441]": { + "$type": "EditorOnlyEntityComponent", + "Id": 18332655128892032441 + }, + "Component_[344586797989012598]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 344586797989012598, + "Parent Entity": "Entity_[1250420600701]", + "Transform Data": { + "Translate": [ + 0.0, + 0.0, + 4.0 + ] + }, + "Cached World Transform": { + "Translation": [ + 0.0, + 0.0, + 4.099999904632568 + ] + }, + "Cached World Transform Parent": "Entity_[1250420600701]" + }, + "Component_[5703653759245493769]": { + "$type": "EditorLockComponent", + "Id": 5703653759245493769 + }, + "Component_[7030857297912025340]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7030857297912025340, + "DisabledComponents": [ + { + "$type": "AZ::Render::EditorDiffuseProbeGridComponent", + "Id": 1614505811629141904, + "Controller": { + "Configuration": { + "ProbeSpacing": [ + 0.5, + 0.5, + 0.5 + ], + "Extents": [ + 8.0, + 8.0, + 8.0 + ] + } + }, + "probeSpacingX": 0.5, + "probeSpacingY": 0.5, + "probeSpacingZ": 0.5 + } + ] + }, + "Component_[7984183259827618750]": { + "$type": "EditorEntitySortComponent", + "Id": 7984183259827618750 + }, + "Component_[827806435204448771]": { + "$type": "EditorEntityIconComponent", + "Id": 827806435204448771 + }, + "Component_[8673278437899912468]": { + "$type": "EditorInspectorComponent", + "Id": 8673278437899912468 + } + }, + "IsDependencyReady": true + }, + "Entity_[1224650796925]": { + "Id": "Entity_[1224650796925]", + "Name": "Camera", + "Components": { + "Component_[10395754987446042279]": { + "$type": "AZ::Render::EditorPostFxLayerComponent", + "Id": 10395754987446042279 + }, + "Component_[11895140916889160460]": { + "$type": "EditorEntityIconComponent", + "Id": 11895140916889160460 + }, + "Component_[16880285896855930892]": { + "$type": "{CA11DA46-29FF-4083-B5F6-E02C3A8C3A3D} EditorCameraComponent", + "Id": 16880285896855930892, + "Controller": { + "Configuration": { + "Field of View": 55.0, + "EditorEntityId": 8929576024571800510 + } + } + }, + "Component_[17187464423780271193]": { + "$type": "EditorLockComponent", + "Id": 17187464423780271193 + }, + "Component_[17495696818315413311]": { + "$type": "EditorEntitySortComponent", + "Id": 17495696818315413311 + }, + "Component_[1798550073623453489]": { + "$type": "AZ::Render::EditorExposureControlComponent", + "Id": 1798550073623453489, + "Controller": { + "Configuration": { + "ExposureControlType": 1 + } + } + }, + "Component_[18086214374043522055]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 18086214374043522055, + "Parent Entity": "Entity_[1250420600701]", + "Transform Data": { + "Translate": [ + 1.7387686967849732, + 1.752368450164795, + 5.225453853607178 + ], + "Rotate": [ + 34.95081329345703, + -29.21880340576172, + 145.06878662109376 + ] + }, + "Cached World Transform": { + "Translation": [ + 1.7387685775756837, + 1.7523683309555054, + 5.325453281402588 + ], + "Rotation": [ + -0.14265206456184388, + -0.3503122329711914, + 0.8573471903800964, + 0.3491239547729492 + ] + }, + "Cached World Transform Parent": "Entity_[1250420600701]" + }, + "Component_[18387556550380114975]": { + "$type": "SelectionComponent", + "Id": 18387556550380114975 + }, + "Component_[2654521436129313160]": { + "$type": "EditorVisibilityComponent", + "Id": 2654521436129313160 + }, + "Component_[5265045084611556958]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 5265045084611556958 + }, + "Component_[7169798125182238623]": { + "$type": "EditorPendingCompositionComponent", + "Id": 7169798125182238623 + }, + "Component_[8866210352157164042]": { + "$type": "EditorInspectorComponent", + "Id": 8866210352157164042 + }, + "Component_[9129253381063760879]": { + "$type": "EditorOnlyEntityComponent", + "Id": 9129253381063760879 + } + }, + "IsDependencyReady": true + }, + "Entity_[1228945764221]": { + "Id": "Entity_[1228945764221]", + "Name": "Global Sky", + "Components": { + "Component_[11231930600558681245]": { + "$type": "AZ::Render::EditorHDRiSkyboxComponent", + "Id": 11231930600558681245, + "Controller": { + "Configuration": { + "CubemapAsset": { + "assetId": { + "guid": "{874DA395-146F-5198-90AD-532F18954407}", + "subId": 1000 + }, + "assetHint": "testdata/white_latlong_iblskyboxcm.exr.streamingimage" + } + } + } + }, + "Component_[11980494120202836095]": { + "$type": "SelectionComponent", + "Id": 11980494120202836095 + }, + "Component_[1428633914413949476]": { + "$type": "EditorLockComponent", + "Id": 1428633914413949476 + }, + "Component_[14936200426671614999]": { + "$type": "AZ::Render::EditorImageBasedLightComponent", + "Id": 14936200426671614999, + "Controller": { + "Configuration": { + "diffuseImageAsset": { + "assetId": { + "guid": "{874DA395-146F-5198-90AD-532F18954407}", + "subId": 3000 + }, + "assetHint": "testdata/white_latlong_iblskyboxcm_ibldiffuse.exr.streamingimage" + }, + "specularImageAsset": { + "assetId": { + "guid": "{874DA395-146F-5198-90AD-532F18954407}", + "subId": 2000 + }, + "assetHint": "testdata/white_latlong_iblskyboxcm_iblspecular.exr.streamingimage" + } + } + } + }, + "Component_[14994774102579326069]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 14994774102579326069 + }, + "Component_[15417479889044493340]": { + "$type": "EditorPendingCompositionComponent", + "Id": 15417479889044493340 + }, + "Component_[15826613364991382688]": { + "$type": "EditorEntitySortComponent", + "Id": 15826613364991382688 + }, + "Component_[1665003113283562343]": { + "$type": "EditorOnlyEntityComponent", + "Id": 1665003113283562343 + }, + "Component_[3704934735944502280]": { + "$type": "EditorEntityIconComponent", + "Id": 3704934735944502280 + }, + "Component_[5698542331457326479]": { + "$type": "EditorVisibilityComponent", + "Id": 5698542331457326479 + }, + "Component_[6644513399057217122]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 6644513399057217122, + "Parent Entity": "Entity_[1250420600701]", + "Transform Data": { + "Translate": [ + 0.0, + 0.0, + -0.10000000149011612 + ] + }, + "Cached World Transform Parent": "Entity_[1250420600701]" + }, + "Component_[931091830724002070]": { + "$type": "EditorInspectorComponent", + "Id": 931091830724002070 + } + }, + "IsDependencyReady": true + }, + "Entity_[1233240731517]": { + "Id": "Entity_[1233240731517]", + "Name": "Shader Ball", + "Components": { + "Component_[10789351944715265527]": { + "$type": "EditorOnlyEntityComponent", + "Id": 10789351944715265527 + }, + "Component_[12037033284781049225]": { + "$type": "EditorEntitySortComponent", + "Id": 12037033284781049225 + }, + "Component_[13759153306105970079]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13759153306105970079 + }, + "Component_[14135560884830586279]": { + "$type": "EditorInspectorComponent", + "Id": 14135560884830586279 + }, + "Component_[16247165675903986673]": { + "$type": "EditorVisibilityComponent", + "Id": 16247165675903986673 + }, + "Component_[18082433625958885247]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 18082433625958885247 + }, + "Component_[6472623349872972660]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 6472623349872972660, + "Parent Entity": "Entity_[1250420600701]", + "Transform Data": { + "Rotate": [ + 0.0, + 0.10000000149011612, + 180.0 + ] + }, + "Cached World Transform": { + "Translation": [ + 0.0, + 0.0, + 0.10000000149011612 + ], + "Rotation": [ + 0.0008726645028218627, + 0.0, + 0.9999996423721314, + 0.0 + ] + }, + "Cached World Transform Parent": "Entity_[1250420600701]" + }, + "Component_[6495255223970673916]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 6495255223970673916, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{FD340C30-755C-5911-92A3-19A3F7A77931}", + "subId": 281415304 + }, + "assetHint": "objects/shaderball/shaderball_default_1m.azmodel" + } + } + } + }, + "Component_[8056625192494070973]": { + "$type": "SelectionComponent", + "Id": 8056625192494070973 + }, + "Component_[8550141614185782969]": { + "$type": "EditorEntityIconComponent", + "Id": 8550141614185782969 + }, + "Component_[9439770997198325425]": { + "$type": "EditorLockComponent", + "Id": 9439770997198325425 + } + }, + "IsDependencyReady": true + }, + "Entity_[1237535698813]": { + "Id": "Entity_[1237535698813]", + "Name": "Sun", + "Components": { + "Component_[10440557478882592717]": { + "$type": "SelectionComponent", + "Id": 10440557478882592717 + }, + "Component_[13620450453324765907]": { + "$type": "EditorLockComponent", + "Id": 13620450453324765907 + }, + "Component_[2134313378593666258]": { + "$type": "EditorInspectorComponent", + "Id": 2134313378593666258 + }, + "Component_[234010807770404186]": { + "$type": "EditorVisibilityComponent", + "Id": 234010807770404186 + }, + "Component_[2970359110423865725]": { + "$type": "EditorEntityIconComponent", + "Id": 2970359110423865725 + }, + "Component_[3722854130373041803]": { + "$type": "EditorOnlyEntityComponent", + "Id": 3722854130373041803 + }, + "Component_[5992533738676323195]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 5992533738676323195 + }, + "Component_[7378860763541895402]": { + "$type": "AZ::Render::EditorDirectionalLightComponent", + "Id": 7378860763541895402, + "Controller": { + "Configuration": { + "Intensity": 0.0, + "CameraEntityId": "", + "ShadowFilterMethod": 1 + } + } + }, + "Component_[7892834440890947578]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7892834440890947578, + "Parent Entity": "Entity_[1250420600701]", + "Transform Data": { + "Translate": [ + 0.0, + 0.0, + 13.38704299926758 + ], + "Rotate": [ + -76.1310043334961, + -0.8469989895820618, + -15.8100004196167 + ] + }, + "Cached World Transform": { + "Translation": [ + 0.0, + 0.0, + 13.487043380737305 + ], + "Rotation": [ + -0.609886109828949, + -0.09055805951356888, + -0.10376212745904924, + 0.7804304361343384 + ] + }, + "Cached World Transform Parent": "Entity_[1250420600701]" + }, + "Component_[8599729549570828259]": { + "$type": "EditorEntitySortComponent", + "Id": 8599729549570828259 + }, + "Component_[952797371922080273]": { + "$type": "EditorPendingCompositionComponent", + "Id": 952797371922080273 + } + }, + "IsDependencyReady": true + }, + "Entity_[1241830666109]": { + "Id": "Entity_[1241830666109]", + "Name": "Ground", + "Components": { + "Component_[11701138785793981042]": { + "$type": "SelectionComponent", + "Id": 11701138785793981042 + }, + "Component_[12260880513256986252]": { + "$type": "EditorEntityIconComponent", + "Id": 12260880513256986252 + }, + "Component_[13711420870643673468]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 13711420870643673468 + }, + "Component_[138002849734991713]": { + "$type": "EditorOnlyEntityComponent", + "Id": 138002849734991713 + }, + "Component_[16578565737331764849]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 16578565737331764849, + "Parent Entity": "Entity_[1250420600701]", + "Transform Data": { + "Translate": [ + 0.0, + 0.0, + -0.10000000149011612 + ] + }, + "Cached World Transform Parent": "Entity_[1250420600701]" + }, + "Component_[16919232076966545697]": { + "$type": "EditorInspectorComponent", + "Id": 16919232076966545697 + }, + "Component_[5182430712893438093]": { + "$type": "EditorMaterialComponent", + "Id": 5182430712893438093, + "materialSlots": [ + { + "id": { + "materialAssetId": { + "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}", + "subId": 803645540 + } + } + } + ], + "materialSlotsByLod": [ + [ + { + "id": { + "lodIndex": 0, + "materialAssetId": { + "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}", + "subId": 803645540 + } + } + } + ] + ] + }, + "Component_[5675108321710651991]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 5675108321710651991, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}", + "subId": 277889906 + }, + "assetHint": "objects/groudplane/groundplane_512x512m.azmodel" + } + } + } + }, + "Component_[5681893399601237518]": { + "$type": "EditorEntitySortComponent", + "Id": 5681893399601237518 + }, + "Component_[592692962543397545]": { + "$type": "EditorPendingCompositionComponent", + "Id": 592692962543397545 + }, + "Component_[7090012899106946164]": { + "$type": "EditorLockComponent", + "Id": 7090012899106946164 + }, + "Component_[9410832619875640998]": { + "$type": "EditorVisibilityComponent", + "Id": 9410832619875640998 + } + }, + "IsDependencyReady": true + }, + "Entity_[1246125633405]": { + "Id": "Entity_[1246125633405]", + "Name": "Grid", + "Components": { + "Component_[11443347433215807130]": { + "$type": "EditorEntityIconComponent", + "Id": 11443347433215807130 + }, + "Component_[11779275529534764488]": { + "$type": "SelectionComponent", + "Id": 11779275529534764488 + }, + "Component_[14249419413039427459]": { + "$type": "EditorInspectorComponent", + "Id": 14249419413039427459 + }, + "Component_[15448581635946161318]": { + "$type": "AZ::Render::EditorGridComponent", + "Id": 15448581635946161318, + "Controller": { + "Configuration": { + "primarySpacing": 4.0, + "primaryColor": [ + 0.501960813999176, + 0.501960813999176, + 0.501960813999176 + ], + "secondarySpacing": 0.5, + "secondaryColor": [ + 0.250980406999588, + 0.250980406999588, + 0.250980406999588 + ] + } + } + }, + "Component_[1843303322527297409]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 1843303322527297409 + }, + "Component_[380249072065273654]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 380249072065273654, + "Parent Entity": "Entity_[1250420600701]", + "Transform Data": { + "Translate": [ + 0.0, + 0.0, + -0.10000000149011612 + ] + }, + "Cached World Transform Parent": "Entity_[1250420600701]" + }, + "Component_[7476660583684339787]": { + "$type": "EditorPendingCompositionComponent", + "Id": 7476660583684339787 + }, + "Component_[7557626501215118375]": { + "$type": "EditorEntitySortComponent", + "Id": 7557626501215118375 + }, + "Component_[7984048488947365511]": { + "$type": "EditorVisibilityComponent", + "Id": 7984048488947365511 + }, + "Component_[8118181039276487398]": { + "$type": "EditorOnlyEntityComponent", + "Id": 8118181039276487398 + }, + "Component_[9189909764215270515]": { + "$type": "EditorLockComponent", + "Id": 9189909764215270515 + } + }, + "IsDependencyReady": true + }, + "Entity_[1250420600701]": { + "Id": "Entity_[1250420600701]", + "Name": "test_sponza_material_conversion", + "Components": { + "Component_[11342679732910125733]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 11342679732910125733, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 273579101 + }, + "assetHint": "testdata/test_sponza_material_conversion.azmodel" + } + } + } + }, + "Component_[11482250315251448254]": { + "$type": "EditorEntityIconComponent", + "Id": 11482250315251448254 + }, + "Component_[12520362418832404237]": { + "$type": "EditorVisibilityComponent", + "Id": 12520362418832404237 + }, + "Component_[13922696236864086628]": { + "$type": "EditorInspectorComponent", + "Id": 13922696236864086628, + "ComponentOrderEntryArray": [ + { + "ComponentId": 9795135998007712828 + }, + { + "ComponentId": 11342679732910125733, + "SortIndex": 1 + }, + { + "ComponentId": 14504829236779989058, + "SortIndex": 2 + } + ] + }, + "Component_[14504829236779989058]": { + "$type": "EditorMaterialComponent", + "Id": 14504829236779989058, + "Controller": { + "Configuration": { + "materials": [ + { + "Key": { + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 40852248 + } + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{1D8B391C-DEB3-549E-9A30-14A6989B1B50}" + }, + "assetHint": "testdata/test_sponza_material_conversion_mat_floor.azmaterial" + } + } + }, + { + "Key": { + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 842740826 + } + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{F3C97FF0-8FC7-5207-9E97-21D680962068}" + } + } + } + }, + { + "Key": { + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 1262202891 + } + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{AF16DFCF-E1E9-57B9-8F01-A49AAC1962AE}" + } + } + } + }, + { + "Key": { + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 2438226774 + } + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{4310E0FE-532D-5436-A946-49B2E14B0375}" + } + } + } + }, + { + "Key": { + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 2816279700 + } + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{6339FEBB-4293-5CEF-809A-2BE072AC94D6}" + }, + "assetHint": "testdata/test_sponza_material_conversion_mat_bricks.azmaterial" + } + } + }, + { + "Key": { + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 3157644117 + } + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{3EF8BEDB-8DE0-5550-A994-D542086622B7}" + }, + "assetHint": "testdata/test_sponza_material_conversion_mat_arch.azmaterial" + }, + "PropertyOverrides": { + "general.applySpecularAA": { + "$type": "bool", + "Value": true + } + } + } + }, + { + "Key": { + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 3208782114 + } + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{CCA05CBE-2606-59A3-91A9-E46F97980A38}" + } + } + } + }, + { + "Key": { + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 3591631827 + } + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{B45DCFD4-6B12-5212-AB92-B64E064155CE}" + } + } + } + }, + { + "Key": { + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 3888291602 + } + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{E6A159AA-989D-5534-8550-1E002148AC84}" + }, + "assetHint": "testdata/test_sponza_material_conversion_mat_roof.azmaterial" + } + } + } + ] + } + }, + "materialSlots": [ + { + "id": { + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 842740826 + } + }, + "materialAsset": { + "assetId": { + "guid": "{F3C97FF0-8FC7-5207-9E97-21D680962068}" + } + } + }, + { + "id": { + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 3208782114 + } + }, + "materialAsset": { + "assetId": { + "guid": "{CCA05CBE-2606-59A3-91A9-E46F97980A38}" + } + } + }, + { + "id": { + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 3157644117 + } + }, + "materialAsset": { + "assetId": { + "guid": "{3EF8BEDB-8DE0-5550-A994-D542086622B7}" + }, + "assetHint": "testdata/test_sponza_material_conversion_mat_arch.azmaterial" + } + }, + { + "id": { + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 2816279700 + } + }, + "materialAsset": { + "assetId": { + "guid": "{6339FEBB-4293-5CEF-809A-2BE072AC94D6}" + }, + "assetHint": "testdata/test_sponza_material_conversion_mat_bricks.azmaterial" + } + }, + { + "id": { + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 40852248 + } + }, + "materialAsset": { + "assetId": { + "guid": "{1D8B391C-DEB3-549E-9A30-14A6989B1B50}" + }, + "assetHint": "testdata/test_sponza_material_conversion_mat_floor.azmaterial" + } + }, + { + "id": { + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 3888291602 + } + }, + "materialAsset": { + "assetId": { + "guid": "{E6A159AA-989D-5534-8550-1E002148AC84}" + }, + "assetHint": "testdata/test_sponza_material_conversion_mat_roof.azmaterial" + } + }, + { + "id": { + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 2438226774 + } + }, + "materialAsset": { + "assetId": { + "guid": "{4310E0FE-532D-5436-A946-49B2E14B0375}" + } + } + }, + { + "id": { + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 1262202891 + } + }, + "materialAsset": { + "assetId": { + "guid": "{AF16DFCF-E1E9-57B9-8F01-A49AAC1962AE}" + } + } + }, + { + "id": { + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 3591631827 + } + }, + "materialAsset": { + "assetId": { + "guid": "{B45DCFD4-6B12-5212-AB92-B64E064155CE}" + } + } + } + ], + "materialSlotsByLod": [ + [ + { + "id": { + "lodIndex": 0, + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 842740826 + } + }, + "materialAsset": { + "assetId": { + "guid": "{F3C97FF0-8FC7-5207-9E97-21D680962068}" + } + } + }, + { + "id": { + "lodIndex": 0, + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 3208782114 + } + }, + "materialAsset": { + "assetId": { + "guid": "{CCA05CBE-2606-59A3-91A9-E46F97980A38}" + } + } + }, + { + "id": { + "lodIndex": 0, + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 3157644117 + } + }, + "materialAsset": { + "assetId": { + "guid": "{3EF8BEDB-8DE0-5550-A994-D542086622B7}" + }, + "assetHint": "testdata/test_sponza_material_conversion_mat_arch.azmaterial" + } + }, + { + "id": { + "lodIndex": 0, + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 2816279700 + } + }, + "materialAsset": { + "assetId": { + "guid": "{6339FEBB-4293-5CEF-809A-2BE072AC94D6}" + }, + "assetHint": "testdata/test_sponza_material_conversion_mat_bricks.azmaterial" + } + }, + { + "id": { + "lodIndex": 0, + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 40852248 + } + }, + "materialAsset": { + "assetId": { + "guid": "{1D8B391C-DEB3-549E-9A30-14A6989B1B50}" + }, + "assetHint": "testdata/test_sponza_material_conversion_mat_floor.azmaterial" + } + }, + { + "id": { + "lodIndex": 0, + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 3888291602 + } + }, + "materialAsset": { + "assetId": { + "guid": "{E6A159AA-989D-5534-8550-1E002148AC84}" + }, + "assetHint": "testdata/test_sponza_material_conversion_mat_roof.azmaterial" + } + }, + { + "id": { + "lodIndex": 0, + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 2438226774 + } + }, + "materialAsset": { + "assetId": { + "guid": "{4310E0FE-532D-5436-A946-49B2E14B0375}" + } + } + }, + { + "id": { + "lodIndex": 0, + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 1262202891 + } + }, + "materialAsset": { + "assetId": { + "guid": "{AF16DFCF-E1E9-57B9-8F01-A49AAC1962AE}" + } + } + }, + { + "id": { + "lodIndex": 0, + "materialAssetId": { + "guid": "{7D2F89D6-2634-5EE6-A122-638377C0CB21}", + "subId": 3591631827 + } + }, + "materialAsset": { + "assetId": { + "guid": "{B45DCFD4-6B12-5212-AB92-B64E064155CE}" + } + } + } + ] + ] + }, + "Component_[16137637180608547307]": { + "$type": "EditorPendingCompositionComponent", + "Id": 16137637180608547307 + }, + "Component_[17072211258387308642]": { + "$type": "EditorOnlyEntityComponent", + "Id": 17072211258387308642 + }, + "Component_[2587501640342227295]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 2587501640342227295 + }, + "Component_[4820742733748380832]": { + "$type": "SelectionComponent", + "Id": 4820742733748380832 + }, + "Component_[5027382790057670521]": { + "$type": "EditorLockComponent", + "Id": 5027382790057670521 + }, + "Component_[7651519254420083868]": { + "$type": "EditorEntitySortComponent", + "Id": 7651519254420083868, + "ChildEntityOrderEntryArray": [ + { + "EntityId": "Entity_[1246125633405]" + }, + { + "EntityId": "Entity_[1228945764221]", + "SortIndex": 1 + }, + { + "EntityId": "Entity_[1220355829629]", + "SortIndex": 2 + }, + { + "EntityId": "Entity_[1224650796925]", + "SortIndex": 3 + }, + { + "EntityId": "Entity_[1233240731517]", + "SortIndex": 4 + }, + { + "EntityId": "Entity_[1237535698813]", + "SortIndex": 5 + }, + { + "EntityId": "Entity_[1241830666109]", + "SortIndex": 6 + } + ] + }, + "Component_[9795135998007712828]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 9795135998007712828, + "Parent Entity": "ContainerEntity", + "Cached World Transform": { + "Translation": [ + 0.0, + 0.0, + 0.10000000149011612 + ] + }, + "Cached World Transform Parent": "ContainerEntity" + } + }, + "IsDependencyReady": true + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion.fbx b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion.fbx new file mode 100644 index 0000000000..638828984a --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b936b72b5b45b52c188bf9f930d6066af65f0d79ff8abf5dc08927d97ac465e +size 55264 diff --git a/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_black.material b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_black.material new file mode 100644 index 0000000000..cc2c9e785b --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_black.material @@ -0,0 +1,35 @@ +{ + "description": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "parentMaterial": "", + "propertyLayoutVersion": 3, + "properties": { + "baseColor": { + "color": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + "emissive": { + "color": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + "irradiance": { + "color": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + "opacity": { + "factor": 1.0 + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_green.material b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_green.material new file mode 100644 index 0000000000..a4bfb73d12 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_green.material @@ -0,0 +1,35 @@ +{ + "description": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "parentMaterial": "", + "propertyLayoutVersion": 3, + "properties": { + "baseColor": { + "color": [ + 0.0, + 1.0, + 0.0, + 1.0 + ] + }, + "emissive": { + "color": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + "irradiance": { + "color": [ + 0.0, + 1.0, + 0.0, + 1.0 + ] + }, + "opacity": { + "factor": 1.0 + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_mat_arch.material b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_mat_arch.material new file mode 100644 index 0000000000..fe9c54bc02 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_mat_arch.material @@ -0,0 +1,49 @@ +{ + "description": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "parentMaterial": "", + "propertyLayoutVersion": 3, + "properties": { + "baseColor": { + "color": [ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1.0 + ], + "textureMap": "Textures/arch_1k_basecolor.png" + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 1.0, + 0.885053813457489, + 0.801281750202179, + 1.0 + ] + }, + "metallic": { + "textureMap": "Textures/arch_1k_metallic.png" + }, + "normal": { + "textureMap": "Textures/arch_1k_normal.jpg" + }, + "occlusion": { + "diffuseTextureMap": "Textures/arch_1k_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "parallax": { + "factor": 0.050999999046325687, + "pdo": true, + "quality": "High", + "useTexture": false + }, + "roughness": { + "textureMap": "Textures/arch_1k_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_mat_bricks.material b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_mat_bricks.material new file mode 100644 index 0000000000..a19afa33e2 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_mat_bricks.material @@ -0,0 +1,54 @@ +{ + "description": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "parentMaterial": "", + "propertyLayoutVersion": 3, + "properties": { + "baseColor": { + "color": [ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1.0 + ], + "textureMap": "Textures/bricks_1k_basecolor.png" + }, + "clearCoat": { + "factor": 0.5, + "normalMap": "Textures/bricks_1k_normal.jpg", + "roughness": 0.5 + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 1.0, + 0.9703211784362793, + 0.9703211784362793, + 1.0 + ] + }, + "metallic": { + "textureMap": "Textures/bricks_1k_metallic.png" + }, + "normal": { + "textureMap": "Textures/bricks_1k_normal.jpg" + }, + "occlusion": { + "diffuseTextureMap": "Textures/bricks_1k_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "parallax": { + "algorithm": "ContactRefinement", + "factor": 0.03500000014901161, + "quality": "Medium", + "useTexture": false + }, + "roughness": { + "textureMap": "Textures/bricks_1k_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_mat_floor.material b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_mat_floor.material new file mode 100644 index 0000000000..0c1208d8fb --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_mat_floor.material @@ -0,0 +1,51 @@ +{ + "description": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "parentMaterial": "", + "propertyLayoutVersion": 3, + "properties": { + "baseColor": { + "color": [ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1.0 + ], + "textureMap": "Textures/floor_1k_basecolor.png" + }, + "clearCoat": { + "enable": true, + "influenceMap": "Textures/floor_1k_ao.png", + "normalMap": "Textures/floor_1k_normal.png", + "roughness": 0.25 + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 1.0, + 0.9404135346412659, + 0.8688944578170776, + 1.0 + ] + }, + "normal": { + "textureMap": "Textures/floor_1k_normal.png" + }, + "occlusion": { + "diffuseTextureMap": "Textures/floor_1k_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "parallax": { + "factor": 0.012000000104308129, + "pdo": true, + "useTexture": false + }, + "roughness": { + "textureMap": "Textures/floor_1k_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_mat_roof.material b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_mat_roof.material new file mode 100644 index 0000000000..6aad4d644a --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_mat_roof.material @@ -0,0 +1,44 @@ +{ + "description": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "parentMaterial": "", + "propertyLayoutVersion": 3, + "properties": { + "baseColor": { + "color": [ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1.0 + ], + "textureBlendMode": "Lerp", + "textureMap": "Textures/roof_1k_basecolor.png" + }, + "general": { + "applySpecularAA": true + }, + "metallic": { + "useTexture": false + }, + "normal": { + "factor": 0.5, + "flipY": true, + "textureMap": "Textures/roof_1k_normal.jpg" + }, + "occlusion": { + "diffuseTextureMap": "Textures/roof_1k_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "parallax": { + "algorithm": "ContactRefinement", + "factor": 0.019999999552965165, + "quality": "Medium", + "useTexture": false + }, + "roughness": { + "textureMap": "Textures/roof_1k_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_phong5.material b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_phong5.material new file mode 100644 index 0000000000..302589dc85 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_phong5.material @@ -0,0 +1,35 @@ +{ + "description": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "parentMaterial": "", + "propertyLayoutVersion": 3, + "properties": { + "baseColor": { + "color": [ + 0.0, + 0.0, + 1.0, + 1.0 + ] + }, + "emissive": { + "color": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + "irradiance": { + "color": [ + 0.0, + 0.0, + 1.0, + 1.0 + ] + }, + "opacity": { + "factor": 1.0 + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_red.material b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_red.material new file mode 100644 index 0000000000..5217a4e4be --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_red.material @@ -0,0 +1,35 @@ +{ + "description": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "parentMaterial": "", + "propertyLayoutVersion": 3, + "properties": { + "baseColor": { + "color": [ + 0.800000011920929, + 0.0, + 0.0, + 1.0 + ] + }, + "emissive": { + "color": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + "irradiance": { + "color": [ + 1.0, + 0.0, + 0.0, + 1.0 + ] + }, + "opacity": { + "factor": 1.0 + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_white.material b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_white.material new file mode 100644 index 0000000000..dba44f7b49 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/TestData/Test_Sponza_Material_Conversion_white.material @@ -0,0 +1,19 @@ +{ + "description": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "parentMaterial": "", + "propertyLayoutVersion": 3, + "properties": { + "emissive": { + "color": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + "opacity": { + "factor": 1.0 + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/TestData/white_latlong_iblskyboxcm.exr b/AutomatedTesting/Gem/Sponza/Assets/TestData/white_latlong_iblskyboxcm.exr new file mode 100644 index 0000000000..56d66de7fc --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/TestData/white_latlong_iblskyboxcm.exr @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9c14dc81887be7647d9afa9e5481634eded0b1d0285b59bb76bb2a91326ca8a +size 3369 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/arch_1k_ao.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/arch_1k_ao.png new file mode 100644 index 0000000000..8bc54f1e55 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/arch_1k_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f21e251c50a657956ba8b401f102b8a3237a0c9c78eab0427332eb3ad8f5952 +size 281259 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/arch_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/arch_1k_basecolor.png new file mode 100644 index 0000000000..c8c21bbfea --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/arch_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b37ec99d6f8d26b8313315d801b9c378456ed9ec31cc2e0566c17ea3287b4fa +size 1643629 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/arch_1k_height.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/arch_1k_height.png new file mode 100644 index 0000000000..425f2f4a74 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/arch_1k_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57657925d24ceb83881e614b3056b82dfc7197a3eebbb707a47f6721249d47c3 +size 108869 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/arch_1k_normal.jpg b/AutomatedTesting/Gem/Sponza/Assets/Textures/arch_1k_normal.jpg new file mode 100644 index 0000000000..511d4412f8 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/arch_1k_normal.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc5a747fef8c4d830b72e250645925f41125ea75c18b6fb094cafe7b19850ca5 +size 70950 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/arch_1k_roughness.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/arch_1k_roughness.png new file mode 100644 index 0000000000..eb9ddee5f2 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/arch_1k_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8438ee2f06842f54e493fe1eda34b6ca39e6db19d2e0f21342f0f4e52fc3f1e3 +size 689118 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/background_1k_ao.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/background_1k_ao.png new file mode 100644 index 0000000000..6ca9f441b1 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/background_1k_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e59f003d7c8d26e7ec8460acec442f31cf7a06eb76008989c1ae6fccd8e2f15 +size 537441 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/background_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/background_1k_basecolor.png new file mode 100644 index 0000000000..a1684d404f --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/background_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1c69c97ffc402d63130b680af75128b431504c4dedcbd15a5fbb41c142317fe +size 1305835 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/background_1k_height.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/background_1k_height.png new file mode 100644 index 0000000000..e03e062c98 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/background_1k_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6650f449128dee04f513961b9d65d81105f2bd4fa580f4dbd9d6fb7b0af932bc +size 275302 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/background_1k_metallic.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/background_1k_metallic.png new file mode 100644 index 0000000000..c7bceabff2 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/background_1k_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f482b7b57d180c6df76b750d05547151731fc6c61d2b9674f3b9daa5ab0ac225 +size 440844 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/background_1k_normal.jpg b/AutomatedTesting/Gem/Sponza/Assets/Textures/background_1k_normal.jpg new file mode 100644 index 0000000000..29f27d2bd5 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/background_1k_normal.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:955f60ebbe10948e9d47cb90e0e0359c4ee26bd8e76d95292a673a2adb68f410 +size 82102 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/background_1k_roughness.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/background_1k_roughness.png new file mode 100644 index 0000000000..689da83af7 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/background_1k_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5d0f40ab7b5153a44b87ee0c83c65ddc69cf845d16c8c9c3c2c043f85c1f04f +size 542782 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/bricks_1k_ao.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/bricks_1k_ao.png new file mode 100644 index 0000000000..4b608a456f --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/bricks_1k_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3d169b73a2852e404eaa113e91d32b66e0d45a1b55598cc58f64e16c9565bf9 +size 729137 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/bricks_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/bricks_1k_basecolor.png new file mode 100644 index 0000000000..cf78cde324 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/bricks_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f34b38f6e205b6bffec72ddfd1305a66d0146d61bcc2ef901fd146a0ec2708de +size 1934899 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/bricks_1k_height.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/bricks_1k_height.png new file mode 100644 index 0000000000..b4f639e01d --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/bricks_1k_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9745b60c53a4b9fa33a6a3713b9b06ec7679883524533de44628cfe0d26ae038 +size 377230 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/bricks_1k_normal.jpg b/AutomatedTesting/Gem/Sponza/Assets/Textures/bricks_1k_normal.jpg new file mode 100644 index 0000000000..e097988246 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/bricks_1k_normal.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03eba2d92ed70e04d5278152e945cf3a170750b71a04fd0f835fd6030b731720 +size 166767 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/bricks_1k_roughness.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/bricks_1k_roughness.png new file mode 100644 index 0000000000..9573d6ca3e --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/bricks_1k_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e35858a269d42405702102e7f7972a6330c3eaacfe637148c676e14d5c901c29 +size 784675 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/ceiling_1k_ao.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/ceiling_1k_ao.png new file mode 100644 index 0000000000..fd87eefdbd --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/ceiling_1k_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:22b80bfc1226fb587b230c51e9dac7fce6e295a4f0674326e96dc1232f40548b +size 901463 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/ceiling_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/ceiling_1k_basecolor.png new file mode 100644 index 0000000000..161272e604 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/ceiling_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6743770a3428721a42a25a331fdb1e9895f71848c8215377e690b7cf9b7397f +size 1756093 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/ceiling_1k_height.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/ceiling_1k_height.png new file mode 100644 index 0000000000..36b0f61834 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/ceiling_1k_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:756105faa27e722d6cc5915f33f8bcf9fd7afc022c41276a8c0e3b1f122303a3 +size 501917 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/ceiling_1k_normal.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/ceiling_1k_normal.png new file mode 100644 index 0000000000..870d38abe9 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/ceiling_1k_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08fa78ce8e7840189d22442ed46863b4eaf5a4a99c0655690a2c47b1b0ebd9ce +size 1399902 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/ceiling_1k_roughness.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/ceiling_1k_roughness.png new file mode 100644 index 0000000000..c5ce447017 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/ceiling_1k_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c24b269cd413b86da8cdd44bb639f3b95cfd1a8c53a3a941cb5a22a1042f85d +size 689214 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/chain_alpha.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/chain_alpha.png new file mode 100644 index 0000000000..13f145b733 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/chain_alpha.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:30a1308326a5b0288147d0fb92230ebd1cef107180e38f7d5795bdc13a62e368 +size 3389 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/chain_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/chain_basecolor.png new file mode 100644 index 0000000000..8c6384ed12 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/chain_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:51c661aa2ee3fb4b4735005a71e734eb25e433ef73ded5131b110e4df2e94acf +size 384316 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/chain_normal.jpg b/AutomatedTesting/Gem/Sponza/Assets/Textures/chain_normal.jpg new file mode 100644 index 0000000000..4a5575731f --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/chain_normal.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4619ec32e2de143db42639e0a4e0dfb174eb85057eb20f9e0d0af59e40977d9a +size 11229 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/columnA_1k_ao.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnA_1k_ao.png new file mode 100644 index 0000000000..ec340ea1a0 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnA_1k_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a505d441c79f3ab674fd5a6b879a430970400fb9f56b379dde00900da296a47e +size 599094 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/columnA_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnA_1k_basecolor.png new file mode 100644 index 0000000000..b6f499c922 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnA_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:552608851205bde18e2bc2f7cbf475ccefbb182c7bfcb9430fa931da22363c80 +size 1836310 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/columnA_1k_height.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnA_1k_height.png new file mode 100644 index 0000000000..704638918a --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnA_1k_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79f1c3430a193ee78c72b84a461612ffd79aa23374949af13a200ec9a301ba2d +size 274678 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/columnA_1k_normal.jpg b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnA_1k_normal.jpg new file mode 100644 index 0000000000..dc90933767 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnA_1k_normal.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d280d5676ccfd6e4b6efdc53e828cc6823f44339a87ec9cee8a79d989ad46175 +size 127305 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/columnA_1k_roughness.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnA_1k_roughness.png new file mode 100644 index 0000000000..a7221fc001 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnA_1k_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c4fb7c8012891c7af4ff371c8e6a8ef85ef1160a5b93012b964158ab48b8e4a +size 760602 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/columnB_1k_ao.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnB_1k_ao.png new file mode 100644 index 0000000000..9cf3966fba --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnB_1k_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9fe904a58b248937adde2b48ba320da5139700d03e3183fbadfaaa0ac8bb6f5 +size 651508 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/columnB_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnB_1k_basecolor.png new file mode 100644 index 0000000000..8315166b41 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnB_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37d461594dca7eb3da8f6f135511d38efc7c1f333afb98d286fb36a0034f3100 +size 2158620 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/columnB_1k_height.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnB_1k_height.png new file mode 100644 index 0000000000..30bd2b0585 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnB_1k_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8e95e08eed20fb4d44a6461f38f27f21fd3ae3f024550afed95ecf3bc30929b1 +size 297329 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/columnB_1k_normal.jpg b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnB_1k_normal.jpg new file mode 100644 index 0000000000..9299417a78 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnB_1k_normal.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b00b684b53854ec24e86441e59220e6c49948429bd6fb7edab7058a11e8f92e +size 139998 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/columnB_1k_roughness.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnB_1k_roughness.png new file mode 100644 index 0000000000..4f4c674c6e --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnB_1k_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a6d428bf17ecee32d47c3feab27567a882b97dfa733dcf6ca8bf1b0bd0a025a +size 908698 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/columnC_1k_ao.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnC_1k_ao.png new file mode 100644 index 0000000000..763aaedc50 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnC_1k_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ab2c93badd7b72f45990c3859d6ddc7f2436a4aa5e93c0de65a139ea99120a97 +size 628789 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/columnC_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnC_1k_basecolor.png new file mode 100644 index 0000000000..f8f34d9232 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnC_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97ec8bd597b0805a8a36bcd90ccdba2ae9430ed3a8631b0f7f87796fb2ceefc5 +size 2128105 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/columnC_1k_height.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnC_1k_height.png new file mode 100644 index 0000000000..46a96abc24 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnC_1k_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8bbbc46f0f1ccfefec202b1e26ecbd418d3283037746bd63a892d1a930fb36d +size 282637 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/columnC_1k_normal.jpg b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnC_1k_normal.jpg new file mode 100644 index 0000000000..270b9d12d1 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnC_1k_normal.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44db84371fb81e1712b5f6aa9777fcfe76728dc25a51f0423e07c294b062c724 +size 137528 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/columnC_1k_roughness.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnC_1k_roughness.png new file mode 100644 index 0000000000..8682bae62c --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/columnC_1k_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c07d4a1af570a6b3817e97c92daa17f2593ea2b88e0e167ed43f7869b77971ab +size 881064 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/curtainBlue_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/curtainBlue_1k_basecolor.png new file mode 100644 index 0000000000..b6b394e860 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/curtainBlue_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4310237ce83738635b3880ccd896f78ea2c70bebadd620cb6ffaaccc78ba7cbf +size 9231896 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/curtainGreen_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/curtainGreen_1k_basecolor.png new file mode 100644 index 0000000000..59e47b4e75 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/curtainGreen_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e093cf9538bb40615d32bec6fa9c60d4308bc017c5ab02a7c388310159bc87fe +size 8342440 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/curtainRed_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/curtainRed_1k_basecolor.png new file mode 100644 index 0000000000..dc708deba0 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/curtainRed_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:465efab4da8d2b6698c83844d2fbe662e38a92f278aa27d356bbc1bca1e23f8b +size 8879938 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/curtain_ao.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/curtain_ao.png new file mode 100644 index 0000000000..fcce123429 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/curtain_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b4faebdc849479a6b5d10db4cac8b6e9823bf447aeb4b45945f6acd43333def8 +size 4557863 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/curtain_height.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/curtain_height.png new file mode 100644 index 0000000000..c983ec6ca8 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/curtain_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:064fbdba14e1ca04e438f4ec85d3f9a0fe3203620ee2a014113ec99454fbee65 +size 2709058 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/curtain_metallic.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/curtain_metallic.png new file mode 100644 index 0000000000..cf63b48d04 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/curtain_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c8159b1ad888143ec3cbf5e2535f87abbe238a1474619696355d11bc4e4bb49 +size 812014 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/curtain_normal.jpg b/AutomatedTesting/Gem/Sponza/Assets/Textures/curtain_normal.jpg new file mode 100644 index 0000000000..d677f3650b --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/curtain_normal.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0da92f21f46502a7c58c1a7ce7280354b4b41e553a5bf181d9962cacf8ed2191 +size 2327816 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/curtain_roughness.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/curtain_roughness.png new file mode 100644 index 0000000000..60e2328213 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/curtain_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a701afeb01e19efdff72393370c86ee148f6d81c1551bcbd211937df503be9b +size 3694284 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/details_1k_ao.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/details_1k_ao.png new file mode 100644 index 0000000000..ed4d761fdb --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/details_1k_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:368262bb832ab61b4649aadffe4158b64d46b70985e99106300fa0c7a7989c35 +size 666377 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/details_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/details_1k_basecolor.png new file mode 100644 index 0000000000..090f5eaef8 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/details_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2021b64706bc328959d521f056dcb9b9acf3062a5f9ad35da9e93b2351dcab97 +size 1371185 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/details_1k_height.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/details_1k_height.png new file mode 100644 index 0000000000..8cfe37da45 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/details_1k_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fdbb251cf483ffccc6cb1b344eba1b3262fa8087c37a36aa843f0c0e9d222f8f +size 403018 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/details_1k_metallic.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/details_1k_metallic.png new file mode 100644 index 0000000000..fe1ced8718 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/details_1k_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b67d6bea4c9fbc8412944b261a0c0f2dc1dc2e4103a6742bfb57d3c2d36429c +size 499028 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/details_1k_normal.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/details_1k_normal.png new file mode 100644 index 0000000000..be8a9932e9 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/details_1k_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae4c4d95bb92dfe25f5fba8564780c10ee663268360fccf1aa707b6c7c6a079c +size 1121073 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/details_1k_roughness.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/details_1k_roughness.png new file mode 100644 index 0000000000..dc633ad668 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/details_1k_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5429087af4e8c33d19b2f13bd194c983bcdbe0c43165bea3dc0711874c2e50b9 +size 523398 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/fabricBlue_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/fabricBlue_1k_basecolor.png new file mode 100644 index 0000000000..54373a9f98 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/fabricBlue_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a1b6588cfdaa69b671ba879aa56712e88d6fcd74c160a751440441c9f18c727b +size 2102967 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/fabricGreen_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/fabricGreen_1k_basecolor.png new file mode 100644 index 0000000000..c77a44eca9 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/fabricGreen_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09c5ff45b66e5a65746c9bef6cbad8a897f58552b896ca6692e5498d91da71ab +size 2166239 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/fabricPurple_2k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/fabricPurple_2k_basecolor.png new file mode 100644 index 0000000000..c6ef949040 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/fabricPurple_2k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:abd3b68e5c2982ef558593017bd4c7a6e3f23681827aa8b8e19dbe21564493e2 +size 1158454 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/fabricRed_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/fabricRed_1k_basecolor.png new file mode 100644 index 0000000000..ab603245df --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/fabricRed_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27ee68e81cb549f65e437debaf97f8eaa57a0f6975a15adee7f395a8bb6f0028 +size 2211896 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/fabric_ao.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/fabric_ao.png new file mode 100644 index 0000000000..f4ae398a33 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/fabric_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3ddbc3833531aba81f9e16fcc1e9c88af1c8b329a8e8a4dfb6a9df08913085a +size 977058 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/fabric_height.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/fabric_height.png new file mode 100644 index 0000000000..f891a127a4 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/fabric_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f44f224b5a162f56ad0e8b931f781eed5fdbfd5a8b9b143aedb5ab69f5c3ca29 +size 564113 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/fabric_metallic.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/fabric_metallic.png new file mode 100644 index 0000000000..7de5d2d8ce --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/fabric_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c456fec0df8f7c72680b855a2c8dd183014429e93bce68a4b2c38059a765c5b6 +size 126769 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/fabric_normal.jpg b/AutomatedTesting/Gem/Sponza/Assets/Textures/fabric_normal.jpg new file mode 100644 index 0000000000..928bd38b87 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/fabric_normal.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee04cb3dc6a718510f662ae06e04fe8fe52230472f4a504a4a513ab3ab344790 +size 479191 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/fabric_roughness.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/fabric_roughness.png new file mode 100644 index 0000000000..63fe4a8b2f --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/fabric_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4097ae87d2d55653c44efa6c607da9513f35057a2556f2f41cd412e1ef447dc4 +size 755476 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/flagpole_1k_ao.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/flagpole_1k_ao.png new file mode 100644 index 0000000000..ee5613ebcb --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/flagpole_1k_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ce7d28833be640b1b4809040580b25bc5ed0caa10f9fcb8b7f28c5b952102d5 +size 689711 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/flagpole_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/flagpole_1k_basecolor.png new file mode 100644 index 0000000000..431210e5ee --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/flagpole_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd515352a030a1a8cf766dfbf9ff78ce27608349e7b661e48d5ec0b59c6d5a93 +size 1376341 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/flagpole_1k_height.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/flagpole_1k_height.png new file mode 100644 index 0000000000..2dd9284a27 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/flagpole_1k_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:883499e06b12d8f25d3381954d56c34aefc9346a972c3a6a21ec5bb34029160c +size 427516 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/flagpole_1k_metallic.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/flagpole_1k_metallic.png new file mode 100644 index 0000000000..4ed2320ab8 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/flagpole_1k_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5291109e90b65a1581ea05046d01c6918c10c87805790af915d62e57acd2d29a +size 1148004 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/flagpole_1k_normal.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/flagpole_1k_normal.png new file mode 100644 index 0000000000..4913bf9352 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/flagpole_1k_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40b175859bf806237bee5ea72efc935b2dd28e3f6198d0604d5e474aea3725c7 +size 1215323 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/flagpole_1k_roughness.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/flagpole_1k_roughness.png new file mode 100644 index 0000000000..f7ea0b06f0 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/flagpole_1k_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:24fdf0849349745e7abdd0c42baa82ec2cc2d5b5a9c1a683bbf677a156251316 +size 1316334 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/floor_1k_ao.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/floor_1k_ao.png new file mode 100644 index 0000000000..2a3e9ac3db --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/floor_1k_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:04fba251cdae222d9f82b6cf6d1e8135ee1e066911967489f8c07ba20d076b14 +size 436348 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/floor_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/floor_1k_basecolor.png new file mode 100644 index 0000000000..3a79998f5b --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/floor_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80d91120f7213e835e342c150386e468865c5b68994915e9febf54c5cc83cf5a +size 1883340 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/floor_1k_height.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/floor_1k_height.png new file mode 100644 index 0000000000..f4fa9f2604 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/floor_1k_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf6fd8259710d106daab4c459172ed3e98f29640d655b238a462cd36039be40a +size 543386 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/floor_1k_metallic.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/floor_1k_metallic.png new file mode 100644 index 0000000000..fdb6071b7c --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/floor_1k_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89ef6e57efdf8c232b771a4ff988799f9f4ae5aefe2149578c9fa661b8f66cd9 +size 1062809 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/floor_1k_normal.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/floor_1k_normal.png new file mode 100644 index 0000000000..2e24080b5c --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/floor_1k_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d1dd751aaed6dce2085e1a886be1d72845dd68df34d33cc8401618b03aed991 +size 1509938 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/floor_1k_roughness.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/floor_1k_roughness.png new file mode 100644 index 0000000000..083cda1634 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/floor_1k_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb0a952c7976e646a5c14b222e23a8fde2510425567e12c73c6f6dc957108d0b +size 1326209 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/lion_1k_ao.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/lion_1k_ao.png new file mode 100644 index 0000000000..9cc32ad4b4 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/lion_1k_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:686bb3c1274e5f48f9b390a67a6ce1e28b127546e1ae7a0d5d01152a2a7c537b +size 519499 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/lion_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/lion_1k_basecolor.png new file mode 100644 index 0000000000..64092a39d1 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/lion_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c919b25301c084a2f4f03e5e80142d1ff72747bfb9cacb5332cdf80c4f62109 +size 1691028 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/lion_1k_height.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/lion_1k_height.png new file mode 100644 index 0000000000..ad711b9144 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/lion_1k_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aedb902b3256c3741fc4fe176608b8b033a22b52c689bc87fe4796f1720d9d54 +size 605847 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/lion_1k_metallic.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/lion_1k_metallic.png new file mode 100644 index 0000000000..7617fd3cae --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/lion_1k_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f2564801b00ffa0ef05ca18ae2222abc257c2b940720b0a73c1c95bccf61c73 +size 1008929 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/lion_1k_normal.jpg b/AutomatedTesting/Gem/Sponza/Assets/Textures/lion_1k_normal.jpg new file mode 100644 index 0000000000..648c37d670 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/lion_1k_normal.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ac327e8140639b4ef77a22f0ab1ecbc9738f84d6f0513b85a0fa2c18991ce5a +size 117602 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/lion_1k_roughness.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/lion_1k_roughness.png new file mode 100644 index 0000000000..faafa83d28 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/lion_1k_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dee59798e56c34d7c014c6e608ba0090dea0a160c44a57b4c61ca587c69950d0 +size 1336005 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/roof_1k_ao.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/roof_1k_ao.png new file mode 100644 index 0000000000..58ec3d1bae --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/roof_1k_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:112bf756fab862ecf71c47dabbf601330e406ed9486500566a5fb012774af7a1 +size 845332 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/roof_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/roof_1k_basecolor.png new file mode 100644 index 0000000000..dd93a2c546 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/roof_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31c065bfd67e0b0ea459d81f134e9c1545f7601764c84544099d8c8f800f583c +size 2403959 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/roof_1k_height.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/roof_1k_height.png new file mode 100644 index 0000000000..b9b65fc1d2 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/roof_1k_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77830ec6672f68124d974657537abce461519f9a0ae705bd79d60ebcf18f4f99 +size 765576 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/roof_1k_metallic.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/roof_1k_metallic.png new file mode 100644 index 0000000000..a82a51c778 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/roof_1k_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc4d854ae199fe8285c51b115005de50f35ddd5553c72867ff6a29e65454fc57 +size 1451908 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/roof_1k_normal.jpg b/AutomatedTesting/Gem/Sponza/Assets/Textures/roof_1k_normal.jpg new file mode 100644 index 0000000000..ead40b8bd9 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/roof_1k_normal.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68b7477af3006f68f882ff0160f85b4e4b880737281b80f82de36a317c177f58 +size 557594 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/roof_1k_roughness.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/roof_1k_roughness.png new file mode 100644 index 0000000000..b666c149ed --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/roof_1k_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b8af2651ce3a8a57244e174231b8ae954dff3bf7a70c024a8acfa9371768eac +size 1820103 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/thorn_alpha.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/thorn_alpha.png new file mode 100644 index 0000000000..c6c678849f --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/thorn_alpha.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1128834ae704583af703df8c63d1671f846d4a89927f90033573b9d8cd495bbd +size 76001 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/thorn_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/thorn_basecolor.png new file mode 100644 index 0000000000..dad1c18f0c --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/thorn_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae57e9e7fc478ee9ad28821af35c3266174795eb10293eb598353efe72c85cc7 +size 430161 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/thorn_height.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/thorn_height.png new file mode 100644 index 0000000000..156e5e6d44 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/thorn_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f6727b858621faaa5930a974e7d60aa66cf3f694234ccfec4e80aeed033b2f1 +size 149055 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/thorn_metallic.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/thorn_metallic.png new file mode 100644 index 0000000000..8b6340bcdd --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/thorn_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c67dadbce8c39c43e12137a48b662e2f71def0a1bcdc197a6ba87ecc391c0334 +size 244023 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/thorn_normal.jpg b/AutomatedTesting/Gem/Sponza/Assets/Textures/thorn_normal.jpg new file mode 100644 index 0000000000..e928f2bb36 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/thorn_normal.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3e0f3396a78712d283247a9c0fc539a671fc8ec01c60f98d48d9a129cfad58f +size 10762 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/thorn_roughness.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/thorn_roughness.png new file mode 100644 index 0000000000..8f319021a4 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/thorn_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00295c69a981a7b0af074267062693ae9c5f698d295b96f884729fb9af109385 +size 295295 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseHanging_1k_ao.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseHanging_1k_ao.png new file mode 100644 index 0000000000..42330f43fc --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseHanging_1k_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:342c4b0811833ae0826b7c10e4987f43ff10df38ee7df1719e540f35d2d3499e +size 345240 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseHanging_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseHanging_1k_basecolor.png new file mode 100644 index 0000000000..d6fbff7d0e --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseHanging_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b5fb7c0472b9643cfd619d9e6c5527899492e7746147a2d975406d25374be78 +size 1314027 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseHanging_1k_height.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseHanging_1k_height.png new file mode 100644 index 0000000000..ac9285bca7 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseHanging_1k_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:caaa5ec46d6059ae3b245e9f7eadf886c4290c89958a13583dc4bc9293495cb0 +size 398025 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseHanging_1k_metallic.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseHanging_1k_metallic.png new file mode 100644 index 0000000000..ede79d9158 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseHanging_1k_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62832e5e067086c97c46fee31baab1cb6dd2c807af685574a90c0afa8088ee43 +size 1401314 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseHanging_1k_normal.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseHanging_1k_normal.png new file mode 100644 index 0000000000..4645b54d64 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseHanging_1k_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f5d23737cc43a2e445dffd4221f8236e61dc553184c2a63b9d5c71d066d84573 +size 1147257 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseHanging_1k_roughness.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseHanging_1k_roughness.png new file mode 100644 index 0000000000..235780d752 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseHanging_1k_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:72ff5ec3b6a469d86f34e93d9fb143fe4846588e44f0e851e509be88745a25aa +size 1365617 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vasePlant_1k_alpha.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/vasePlant_1k_alpha.png new file mode 100644 index 0000000000..8556f67471 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vasePlant_1k_alpha.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85ee26dcfdf8cb3ddcd378e92fd52e313ee23a58f7cc5a37556d96556986ba0b +size 62658 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vasePlant_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/vasePlant_1k_basecolor.png new file mode 100644 index 0000000000..5f6cd0377f --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vasePlant_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05d7a544ccf6b04dcfb37411927a79e8f4b2e6224b09651f92885604d825dc9c +size 860074 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseRound_1k_ao.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseRound_1k_ao.png new file mode 100644 index 0000000000..c865df4c29 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseRound_1k_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a63c38ed593010cd495256c1b2b5f2d39659e0d4e9c699dd97a2d0a5582a65d7 +size 255623 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseRound_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseRound_1k_basecolor.png new file mode 100644 index 0000000000..942b1a859a --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseRound_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f438726b2dd4107d85b2542476243bc6977362a4865a5c21916d186fdf0eb5f4 +size 1846782 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseRound_1k_height.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseRound_1k_height.png new file mode 100644 index 0000000000..d0aaaa46c1 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseRound_1k_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ddea2c560e2c86d58722b3728e0018c41b20fb64d543bd5241bc7de482bfcae +size 584961 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseRound_1k_metallic.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseRound_1k_metallic.png new file mode 100644 index 0000000000..b040d20cd6 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseRound_1k_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12e3d690707f3fad9c26c402329ba1c1c7e751b6c38e11d965a1cfd8f67f61c3 +size 1143341 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseRound_1k_normal.jpg b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseRound_1k_normal.jpg new file mode 100644 index 0000000000..866c9e0907 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseRound_1k_normal.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cd9f31741ebd52c9ddcc65b3d33b875b39af16379fcaed35fcb662a12c382cf +size 81207 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseRound_1k_roughness.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseRound_1k_roughness.png new file mode 100644 index 0000000000..05e201d968 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vaseRound_1k_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f7a398b8c249c20d199d53f203df79826a63d1f56d62c28403ce9e52065fb49f +size 1397051 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vase_1k_ao.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/vase_1k_ao.png new file mode 100644 index 0000000000..53e7968cbf --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vase_1k_ao.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3740f6f1cac7251ee455c24498e0026da033d0c4994960a99cad4c78f0edcccb +size 639510 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vase_1k_basecolor.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/vase_1k_basecolor.png new file mode 100644 index 0000000000..76c61bfe2e --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vase_1k_basecolor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b6b3d8fe5c470adbff0f9d9063c7efa1921491693a34b1fb383a9a60d7d7a9c +size 1838562 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vase_1k_height.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/vase_1k_height.png new file mode 100644 index 0000000000..50a9cab822 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vase_1k_height.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc63680f8772effdad47e0a04def2bd8b6ec1dae8e8e7bfce2a11409a0c767a8 +size 572074 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vase_1k_metallic.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/vase_1k_metallic.png new file mode 100644 index 0000000000..d847f3d4a3 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vase_1k_metallic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d90cac110f7f129df7bd37d9c5e735be8f253be9e3798c3e2932ba351b8d7b5 +size 1044158 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vase_1k_normal.jpg b/AutomatedTesting/Gem/Sponza/Assets/Textures/vase_1k_normal.jpg new file mode 100644 index 0000000000..559222c38c --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vase_1k_normal.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:387a9873c1ec3ebda366c19b94ca0c553f8015f7b48dd4adf8a53d3d83f3ad3d +size 217939 diff --git a/AutomatedTesting/Gem/Sponza/Assets/Textures/vase_1k_roughness.png b/AutomatedTesting/Gem/Sponza/Assets/Textures/vase_1k_roughness.png new file mode 100644 index 0000000000..999fdab25e --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/Textures/vase_1k_roughness.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e5b7e0a1b62aa2b3f270e2fd4be4acb6a9b5c1c45f6eb83fa177c777e9f14b8 +size 1317066 diff --git a/AutomatedTesting/Gem/Sponza/Assets/license.txt b/AutomatedTesting/Gem/Sponza/Assets/license.txt new file mode 100644 index 0000000000..e303d8c767 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/license.txt @@ -0,0 +1,8 @@ +The content in this gem "O3DE\Gems\AtomContent\Sponza" is ported +from the original source, and modified for the O3DE Engine and Atom Renderer. + +The original "Crytek Sponza" scene data can be downloaded from the +"McGuire Computer Graphics Archive": https://casual-effects.com/data/ + +The original content is under the "CC BY 3.0" License: +https://creativecommons.org/licenses/by/3.0/ \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/lightBlocker.fbx b/AutomatedTesting/Gem/Sponza/Assets/objects/lightBlocker.fbx new file mode 100644 index 0000000000..8064e80546 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/lightBlocker.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4284aa2655fefad45723d899824b99a732f8b1d5b231bb8b26734cae251e15d0 +size 23216 diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/lightBlocker_lambert1.material b/AutomatedTesting/Gem/Sponza/Assets/objects/lightBlocker_lambert1.material new file mode 100644 index 0000000000..c8e9f1f8f7 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/lightBlocker_lambert1.material @@ -0,0 +1,19 @@ +{ + "description": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "parentMaterial": "", + "propertyLayoutVersion": 3, + "properties": { + "emissive": { + "color": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + "opacity": { + "factor": 1.0 + } + } +} diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sphere.fbx b/AutomatedTesting/Gem/Sponza/Assets/objects/sphere.fbx new file mode 100644 index 0000000000..734dbb2843 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sphere.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67a3f676de88eb967df30d526a810e1c569a611904ff1a85628b66e75c4181f0 +size 47168 diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sphereTwo.fbx b/AutomatedTesting/Gem/Sponza/Assets/objects/sphereTwo.fbx new file mode 100644 index 0000000000..99c96e832a --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sphereTwo.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3abce1aadb4f505bf2eebbf8256598f9dab4cd5e9239e9a1523a70b4302912d +size 47184 diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza.fbx b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza.fbx new file mode 100644 index 0000000000..a38f51750a --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a8e686bd64cda37e8b27adcb691a846e62bcea6491547d53334ef4ed5424493 +size 21247456 diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_arch.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_arch.material new file mode 100644 index 0000000000..6518091265 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_arch.material @@ -0,0 +1,40 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/arch_1k_basecolor.png" + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 0.2663614749908447, + 0.2383916974067688, + 0.18117037415504456, + 1.0 + ] + }, + "normal": { + "textureMap": "../Textures/arch_1k_normal.jpg" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/arch_1k_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "parallax": { + "factor": 0.050999999046325684, + "pdo": true, + "quality": "High", + "useTexture": false + }, + "roughness": { + "textureMap": "../Textures/arch_1k_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_background.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_background.material new file mode 100644 index 0000000000..2fd7ed39cc --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_background.material @@ -0,0 +1,45 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/background_1k_basecolor.png" + }, + "clearCoat": { + "factor": 0.5, + "normalMap": "../Textures/background_1k_normal.jpg", + "roughness": 0.4000000059604645 + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 0.19806210696697235, + 0.1746547669172287, + 0.16513313353061676, + 1.0 + ] + }, + "normal": { + "textureMap": "../Textures/background_1k_normal.jpg" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/background_1k_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "parallax": { + "factor": 0.03099999949336052, + "pdo": true, + "quality": "High", + "useTexture": false + }, + "roughness": { + "textureMap": "../Textures/background_1k_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_bricks.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_bricks.material new file mode 100644 index 0000000000..52fdf9e3a2 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_bricks.material @@ -0,0 +1,45 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/bricks_1k_basecolor.png" + }, + "clearCoat": { + "factor": 0.5, + "normalMap": "../Textures/bricks_1k_normal.jpg", + "roughness": 0.5 + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 0.27467766404151917, + 0.27467766404151917, + 0.270496666431427, + 1.0 + ] + }, + "normal": { + "textureMap": "../Textures/bricks_1k_normal.jpg" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/bricks_1k_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "parallax": { + "algorithm": "ContactRefinement", + "factor": 0.03500000014901161, + "quality": "Medium", + "useTexture": false + }, + "roughness": { + "textureMap": "../Textures/bricks_1k_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_ceiling.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_ceiling.material new file mode 100644 index 0000000000..10f5a01a8e --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_ceiling.material @@ -0,0 +1,36 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/ceiling_1k_basecolor.png" + }, + "emissive": { + "color": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + "irradiance": { + "color": [ + 0.29176774621009827, + 0.27888914942741394, + 0.2501564025878906, + 1.0 + ] + }, + "normal": { + "textureMap": "../Textures/ceiling_1k_normal.png" + }, + "opacity": { + "factor": 1.0 + }, + "roughness": { + "textureMap": "../Textures/ceiling_1k_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_chain.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_chain.material new file mode 100644 index 0000000000..caf03bd3ce --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_chain.material @@ -0,0 +1,35 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureBlendMode": "Lerp", + "textureMap": "../Textures/chain_basecolor.png" + }, + "emissive": { + "color": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + "general": { + "doubleSided": true + }, + "metallic": { + "factor": 0.8899999856948853 + }, + "normal": { + "textureMap": "../Textures/chain_normal.jpg" + }, + "opacity": { + "alphaSource": "Split", + "factor": 1.0, + "mode": "Cutout", + "textureMap": "../Textures/chain_alpha.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_columna.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_columna.material new file mode 100644 index 0000000000..15c3fec349 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_columna.material @@ -0,0 +1,45 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/columnA_1k_basecolor.png" + }, + "clearCoat": { + "factor": 0.5, + "normalMap": "../Textures/columnA_1k_normal.jpg", + "roughness": 0.30000001192092896 + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 1.0, + 0.8964369893074036, + 0.8264744281768799, + 1.0 + ] + }, + "normal": { + "textureMap": "../Textures/columnA_1k_normal.jpg" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/columnA_1k_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "parallax": { + "factor": 0.017000000923871994, + "pdo": true, + "quality": "High", + "useTexture": false + }, + "roughness": { + "textureMap": "../Textures/columnA_1k_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_columnb.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_columnb.material new file mode 100644 index 0000000000..e13f96b2bb --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_columnb.material @@ -0,0 +1,45 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/columnB_1k_basecolor.png" + }, + "clearCoat": { + "factor": 0.5, + "normalMap": "../Textures/columnB_1k_normal.jpg", + "roughness": 0.30000001192092896 + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 0.41788357496261597, + 0.40723279118537903, + 0.4286869466304779, + 1.0 + ] + }, + "normal": { + "textureMap": "../Textures/columnB_1k_normal.jpg" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/columnB_1k_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "parallax": { + "factor": 0.020999999716877937, + "pdo": true, + "quality": "High", + "useTexture": false + }, + "roughness": { + "textureMap": "../Textures/columnB_1k_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_columnc.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_columnc.material new file mode 100644 index 0000000000..479692848a --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_columnc.material @@ -0,0 +1,45 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/columnC_1k_basecolor.png" + }, + "clearCoat": { + "factor": 0.5, + "normalMap": "../Textures/columnC_1k_normal.jpg", + "roughness": 0.30000001192092896 + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 0.32314029335975647, + 0.29176774621009827, + 0.24228274822235107, + 1.0 + ] + }, + "normal": { + "textureMap": "../Textures/columnC_1k_normal.jpg" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/columnC_1k_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "parallax": { + "factor": 0.014000000432133675, + "pdo": true, + "quality": "High", + "useTexture": false + }, + "roughness": { + "textureMap": "../Textures/columnC_1k_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_curtainblue.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_curtainblue.material new file mode 100644 index 0000000000..50fd968956 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_curtainblue.material @@ -0,0 +1,52 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "color": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "textureMap": "../Textures/curtainBlue_1k_basecolor.png" + }, + "emissive": { + "color": [ + 1.0, + 1.0, + 1.0, + 1.0 + ] + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 0.0, + 0.14901961386203766, + 1.0, + 1.0 + ] + }, + "metallic": { + "textureMap": "../Textures/curtain_metallic.png" + }, + "normal": { + "factor": 0.5, + "textureMap": "../Textures/curtain_normal.jpg" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/curtain_ao.png" + }, + "roughness": { + "textureMap": "../Textures/curtain_roughness.png" + }, + "specularF0": { + "enableMultiScatterCompensation": true + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_curtaingreen.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_curtaingreen.material new file mode 100644 index 0000000000..6e70a42d24 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_curtaingreen.material @@ -0,0 +1,38 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/curtainGreen_1k_basecolor.png" + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 0.0, + 0.15294118225574493, + 0.0, + 1.0 + ] + }, + "metallic": { + "textureMap": "../Textures/curtain_metallic.png" + }, + "normal": { + "factor": 0.5, + "textureMap": "../Textures/curtain_normal.jpg" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/curtain_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "roughness": { + "textureMap": "../Textures/curtain_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_curtainred.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_curtainred.material new file mode 100644 index 0000000000..8233633310 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_curtainred.material @@ -0,0 +1,43 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/curtainRed_1k_basecolor.png" + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 0.41960784792900085, + 0.003921568859368563, + 0.003921568859368563, + 1.0 + ] + }, + "metallic": { + "textureMap": "../Textures/curtain_metallic.png" + }, + "normal": { + "textureMap": "../Textures/curtain_normal.jpg" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/curtain_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "roughness": { + "textureMap": "../Textures/curtain_roughness.png" + }, + "uv": { + "center": [ + 16.0, + 0.0 + ] + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_details.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_details.material new file mode 100644 index 0000000000..b66d8fa679 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_details.material @@ -0,0 +1,39 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/details_1k_basecolor.png" + }, + "clearCoat": { + "factor": 0.5, + "normalMap": "../Textures/details_1k_normal.png", + "roughness": 0.25 + }, + "general": { + "applySpecularAA": true + }, + "metallic": { + "textureMap": "../Textures/details_1k_metallic.png" + }, + "normal": { + "textureMap": "../Textures/details_1k_normal.png" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/details_1k_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "parallax": { + "factor": 0.02500000037252903, + "pdo": true, + "useTexture": false + }, + "roughness": { + "textureMap": "../Textures/details_1k_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_fabricblue.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_fabricblue.material new file mode 100644 index 0000000000..19ce3a6839 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_fabricblue.material @@ -0,0 +1,39 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/fabricBlue_1k_basecolor.png" + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 0.0, + 0.15049973130226135, + 1.0, + 1.0 + ], + "factor": 0.30000001192092896 + }, + "metallic": { + "textureMap": "../Textures/fabric_metallic.png" + }, + "normal": { + "factor": 0.5, + "textureMap": "../Textures/fabric_normal.jpg" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/fabric_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "roughness": { + "textureMap": "../Textures/fabric_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_fabricgreen.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_fabricgreen.material new file mode 100644 index 0000000000..94b8270fef --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_fabricgreen.material @@ -0,0 +1,39 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/fabricGreen_1k_basecolor.png" + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 0.0, + 0.15292592346668243, + 0.0012207217514514923, + 1.0 + ], + "factor": 0.30000001192092896 + }, + "metallic": { + "textureMap": "../Textures/fabric_metallic.png" + }, + "normal": { + "factor": 0.5, + "textureMap": "../Textures/fabric_normal.jpg" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/fabric_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "roughness": { + "textureMap": "../Textures/fabric_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_fabricred.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_fabricred.material new file mode 100644 index 0000000000..7bd2ecddd0 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_fabricred.material @@ -0,0 +1,39 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/fabricRed_1k_basecolor.png" + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 0.42040130496025085, + 0.004654001910239458, + 0.0037232013419270515, + 1.0 + ], + "factor": 0.30000001192092896 + }, + "metallic": { + "textureMap": "../Textures/fabric_metallic.png" + }, + "normal": { + "factor": 0.5, + "textureMap": "../Textures/fabric_normal.jpg" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/fabric_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "roughness": { + "textureMap": "../Textures/fabric_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_flagpole.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_flagpole.material new file mode 100644 index 0000000000..aca6c05d29 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_flagpole.material @@ -0,0 +1,46 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/flagpole_1k_basecolor.png" + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 1.0, + 0.6520485281944275, + 0.7122911214828491, + 1.0 + ] + }, + "metallic": { + "textureMap": "../Textures/flagpole_1k_metallic.png" + }, + "normal": { + "textureMap": "../Textures/flagpole_1k_normal.png" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/flagpole_1k_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "parallax": { + "factor": 0.014000000432133675, + "pdo": true, + "quality": "High", + "useTexture": false + }, + "roughness": { + "textureMap": "../Textures/flagpole_1k_roughness.png" + }, + "specularF0": { + "enableMultiScatterCompensation": true + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_floor.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_floor.material new file mode 100644 index 0000000000..b75143326d --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_floor.material @@ -0,0 +1,44 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/floor_1k_basecolor.png" + }, + "clearCoat": { + "influenceMap": "../Textures/floor_1k_ao.png", + "normalMap": "../Textures/floor_1k_normal.png", + "roughness": 0.25 + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 1.0, + 0.9404135346412659, + 0.8688944578170776, + 1.0 + ] + }, + "normal": { + "textureMap": "../Textures/floor_1k_normal.png" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/floor_1k_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "parallax": { + "factor": 0.012000000104308128, + "pdo": true, + "useTexture": false + }, + "roughness": { + "textureMap": "../Textures/floor_1k_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_leaf.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_leaf.material new file mode 100644 index 0000000000..51638d6d94 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_leaf.material @@ -0,0 +1,43 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/thorn_basecolor.png" + }, + "clearCoat": { + "factor": 0.05000000074505806, + "normalMap": "../Textures/thorn_normal.jpg", + "roughness": 0.10000000149011612 + }, + "general": { + "applySpecularAA": true, + "doubleSided": true + }, + "irradiance": { + "color": [ + 0.46506446599960327, + 1.0, + 0.3944609761238098, + 1.0 + ] + }, + "normal": { + "textureMap": "../Textures/thorn_normal.jpg" + }, + "opacity": { + "alphaSource": "Split", + "factor": 0.20000000298023224, + "mode": "Cutout", + "textureMap": "../Textures/thorn_alpha.png" + }, + "parallax": { + "useTexture": false + }, + "roughness": { + "textureMap": "../Textures/thorn_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_lion.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_lion.material new file mode 100644 index 0000000000..eee41e9c13 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_lion.material @@ -0,0 +1,36 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/lion_1k_basecolor.png" + }, + "emissive": { + "color": [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + "irradiance": { + "color": [ + 0.5583428740501404, + 0.496940553188324, + 0.4125429093837738, + 1.0 + ] + }, + "normal": { + "textureMap": "../Textures/lion_1k_normal.jpg" + }, + "opacity": { + "factor": 1.0 + }, + "roughness": { + "textureMap": "../Textures/lion_1k_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_roof.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_roof.material new file mode 100644 index 0000000000..fec7bb1e34 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_roof.material @@ -0,0 +1,47 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureBlendMode": "Lerp", + "textureMap": "../Textures/roof_1k_basecolor.png" + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 0.29613184928894043, + 0.3324483036994934, + 0.45078203082084656, + 1.0 + ] + }, + "metallic": { + "textureMap": "../Textures/roof_1k_metallic.png", + "useTexture": false + }, + "normal": { + "factor": 0.5, + "flipY": true, + "textureMap": "../Textures/roof_1k_normal.jpg" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/roof_1k_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "parallax": { + "algorithm": "ContactRefinement", + "factor": 0.019999999552965164, + "quality": "Medium", + "useTexture": false + }, + "roughness": { + "textureMap": "../Textures/roof_1k_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_vase.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_vase.material new file mode 100644 index 0000000000..da7a9de3a8 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_vase.material @@ -0,0 +1,46 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/vase_1k_basecolor.png" + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 1.0, + 0.8713664412498474, + 0.6021667718887329, + 1.0 + ] + }, + "metallic": { + "textureMap": "../Textures/vase_1k_metallic.png" + }, + "normal": { + "textureMap": "../Textures/vase_1k_normal.jpg" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/vase_1k_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "parallax": { + "factor": 0.027000000700354576, + "pdo": true, + "quality": "High", + "useTexture": false + }, + "roughness": { + "textureMap": "../Textures/vase_1k_roughness.png" + }, + "specularF0": { + "enableMultiScatterCompensation": true + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_vasehanging.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_vasehanging.material new file mode 100644 index 0000000000..bda7aad38c --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_vasehanging.material @@ -0,0 +1,43 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/vaseHanging_1k_basecolor.png" + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 0.765606164932251, + 1.0, + 0.7052567601203918, + 1.0 + ] + }, + "metallic": { + "textureMap": "../Textures/vaseHanging_1k_metallic.png" + }, + "normal": { + "textureMap": "../Textures/vaseHanging_1k_normal.png" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/vaseHanging_1k_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "parallax": { + "factor": 0.04600000008940697, + "pdo": true, + "quality": "High", + "useTexture": false + }, + "roughness": { + "textureMap": "../Textures/vaseHanging_1k_roughness.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_vaseplant.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_vaseplant.material new file mode 100644 index 0000000000..5546daa0e0 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_vaseplant.material @@ -0,0 +1,36 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "color": [ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1.0 + ], + "textureBlendMode": "Lerp", + "textureMap": "../Textures/vasePlant_1k_basecolor.png" + }, + "general": { + "applySpecularAA": true, + "doubleSided": true + }, + "irradiance": { + "color": [ + 0.09086747467517853, + 0.4111391007900238, + 0.0474097803235054, + 1.0 + ] + }, + "opacity": { + "alphaSource": "Split", + "factor": 0.23999999463558197, + "mode": "Cutout", + "textureMap": "../Textures/vasePlant_1k_alpha.png" + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_vaseround.material b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_vaseround.material new file mode 100644 index 0000000000..268e3ab613 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/objects/sponza_mat_vaseround.material @@ -0,0 +1,49 @@ +{ + "description": "", + "parentMaterial": "", + "materialType": "Materials/Types/StandardPBR.materialtype", + "materialTypeVersion": 4, + "properties": { + "baseColor": { + "textureMap": "../Textures/vaseRound_1k_basecolor.png" + }, + "clearCoat": { + "factor": 0.5, + "influenceMap": "../Textures/vaseRound_1k_ao.png", + "normalMap": "../Textures/vaseRound_1k_normal.jpg", + "roughness": 0.25 + }, + "general": { + "applySpecularAA": true + }, + "irradiance": { + "color": [ + 0.46933698654174805, + 0.3824063539505005, + 0.47861447930336, + 1.0 + ] + }, + "normal": { + "textureMap": "../Textures/vaseRound_1k_normal.jpg" + }, + "occlusion": { + "diffuseTextureMap": "../Textures/vaseRound_1k_ao.png" + }, + "opacity": { + "factor": 1.0 + }, + "parallax": { + "factor": 0.019999999552965164, + "pdo": true, + "quality": "High", + "useTexture": false + }, + "roughness": { + "textureMap": "../Textures/vaseRound_1k_roughness.png" + }, + "specularF0": { + "enableMultiScatterCompensation": true + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Assets/slices/lightingGRP.slice b/AutomatedTesting/Gem/Sponza/Assets/slices/lightingGRP.slice new file mode 100644 index 0000000000..97f3b3bb55 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Assets/slices/lightingGRP.slice @@ -0,0 +1,962 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Code/Tools/Standalone/Platform/Linux/profiler_linux_files.cmake b/AutomatedTesting/Gem/Sponza/CMakeLists.txt similarity index 50% rename from Code/Tools/Standalone/Platform/Linux/profiler_linux_files.cmake rename to AutomatedTesting/Gem/Sponza/CMakeLists.txt index 35ab1449e2..2d625a71b6 100644 --- a/Code/Tools/Standalone/Platform/Linux/profiler_linux_files.cmake +++ b/AutomatedTesting/Gem/Sponza/CMakeLists.txt @@ -5,8 +5,7 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT # # - -set(FILES - ../Common/Unimplemented/Source/StandaloneApplication_Unimplemented.cpp - ../Common/Unimplemented/Source/Driller/EvenTrace/EventTraceDataAggregator_Unimplemented.cpp -) +# This will export its "SourcePaths" to the generated "cmake_dependencies..assetbuilder.setreg" +if(PAL_TRAIT_BUILD_HOST_TOOLS) + ly_create_alias(NAME AtomContent_Sponza.Builders NAMESPACE Gem) +endif() \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Registry/AssetProcessorPlatformConfig.setreg b/AutomatedTesting/Gem/Sponza/Registry/AssetProcessorPlatformConfig.setreg new file mode 100644 index 0000000000..206a0a2a87 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Registry/AssetProcessorPlatformConfig.setreg @@ -0,0 +1,14 @@ +{ + "Amazon": { + "AssetProcessor": { + "Settings": { + // ------------------------------------------------------------------------------ + // Sample Gems, Block source folders + // ------------------------------------------------------------------------------ + "Exclude Work In Progress Folders": { + "pattern": "(^|.+/).[Ss]rc(/.*)?$" + } + } + } + } +} diff --git a/AutomatedTesting/Gem/Sponza/Tools/Launch_Cmd.bat b/AutomatedTesting/Gem/Sponza/Tools/Launch_Cmd.bat new file mode 100644 index 0000000000..0b94be5bea --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Tools/Launch_Cmd.bat @@ -0,0 +1,43 @@ +@echo off +:: Keep changes local +SETLOCAL enableDelayedExpansion + +REM +REM Copyright (c) Contributors to the Open 3D Engine Project +REM +REM SPDX-License-Identifier: Apache-2.0 OR MIT +REM For complete copyright and license terms please see the LICENSE at the root of this distribution. +REM +REM + +:: Set up and start a O3DE CMD prompt +:: Sets up the current (DCC) Project_Env, +:: Puts you in the CMD within the dev environment + +:: Set up window +TITLE O3DE DCC Scripting Interface Cmd +:: Use obvious color to prevent confusion (Grey with Yellow Text) +COLOR 8E + +%~d0 +cd %~dp0 +PUSHD %~dp0 + +CALL %~dp0\Project_Env.bat + +echo. +echo _____________________________________________________________________ +echo. +echo ~ O3DE %O3DE_PROJECT% Asset Gem CMD ... +echo _____________________________________________________________________ +echo. + +:: Create command prompt with environment +CALL %windir%\system32\cmd.exe + +ENDLOCAL + +:: Return to starting directory +POPD + +:END_OF_FILE \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/Tools/Launch_Maya.bat b/AutomatedTesting/Gem/Sponza/Tools/Launch_Maya.bat new file mode 100644 index 0000000000..0af25905a0 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Tools/Launch_Maya.bat @@ -0,0 +1,66 @@ +@echo off + +REM +REM Copyright (c) Contributors to the Open 3D Engine Project. +REM For complete copyright and license terms please see the LICENSE at the root of this distribution. +REM +REM SPDX-License-Identifier: Apache-2.0 OR MIT +REM +REM + +%~d0 +cd %~dp0 +PUSHD %~dp0 + +echo ________________________________ +echo ~ calling PROJ_Env.bat + +:: Keep changes local +SETLOCAL enableDelayedExpansion + +:: PY version Major +IF "%DCCSI_PY_VERSION_MAJOR%"=="" (set DCCSI_PY_VERSION_MAJOR=2) +echo DCCSI_PY_VERSION_MAJOR = %DCCSI_PY_VERSION_MAJOR% + +:: PY version Major +IF "%DCCSI_PY_VERSION_MINOR%"=="" (set DCCSI_PY_VERSION_MINOR=7) +echo DCCSI_PY_VERSION_MINOR = %DCCSI_PY_VERSION_MINOR% + +:: Maya Version +IF "%DCCSI_MAYA_VERSION%"=="" (set DCCSI_MAYA_VERSION=2020) +echo DCCSI_MAYA_VERSION = %DCCSI_MAYA_VERSION% + +:: if a local customEnv.bat exists, run it +IF EXIST "%~dp0Project_Env.bat" CALL %~dp0Project_Env.bat + +echo ________________________________ +echo Launching Maya %DCCSI_MAYA_VERSION% for O3DE: %O3DE_PROJECT%... + +:::: Set Maya native project acess to this project +::set MAYA_PROJECT=%LY_PROJECT% +::echo MAYA_PROJECT = %MAYA_PROJECT% + +:: DX11 Viewport +Set MAYA_VP2_DEVICE_OVERRIDE = VirtualDeviceDx11 + +:: Default to the right version of Maya if we can detect it... and launch +echo MAYA_BIN_PATH = %MAYA_BIN_PATH% + +IF EXIST "%MAYA_BIN_PATH%\Maya.exe" ( + start "" "%MAYA_BIN_PATH%\Maya.exe" %* +) ELSE ( + Where maya.exe 2> NUL + IF ERRORLEVEL 1 ( + echo Maya.exe could not be found + pause + ) ELSE ( + start "" Maya.exe %* + ) +) + +:: Return to starting directory +POPD + +:END_OF_FILE + +exit /b 0 diff --git a/AutomatedTesting/Gem/Sponza/Tools/Project_Env.bat b/AutomatedTesting/Gem/Sponza/Tools/Project_Env.bat new file mode 100644 index 0000000000..ddf934d206 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Tools/Project_Env.bat @@ -0,0 +1,107 @@ +@echo off + +REM +REM Copyright (c) Contributors to the Open 3D Engine Project +REM +REM SPDX-License-Identifier: Apache-2.0 OR MIT +REM For complete copyright and license terms please see the LICENSE at the root of this distribution. +REM +REM + +:: Sets up environment for O3DE DCC tools and code access + +:: Set up window +TITLE O3DE Asset Gem +:: Use obvious color to prevent confusion (Grey with Yellow Text) +COLOR 8E + +:: Skip initialization if already completed +IF "%O3DE_PROJ_ENV_INIT%"=="1" GOTO :END_OF_FILE + +:: Store current dir +%~d0 +cd %~dp0 +PUSHD %~dp0 + +:: Put you project env vars and overrides in this file + +:: chanhe the relative path up to dev +set ABS_PATH=%~dp0 + +:: project name as a str tag +IF "%O3DE_PROJECT%"=="" ( + for %%I in ("%~dp0.") do for %%J in ("%%~dpI.") do set O3DE_PROJECT=%%~nxJ + ) + +echo. +echo _____________________________________________________________________ +echo. +echo ~ Setting up O3DE %O3DE_PROJECT% Environment ... +echo _____________________________________________________________________ +echo. +echo O3DE_PROJECT = %O3DE_PROJECT% + +:: if the user has set up a custom env call it +:: this should allow the user to locally +:: set env hooks like O3DE_DEV or O3DE_PROJECT_PATH +IF EXIST "%~dp0User_Env.bat" CALL %~dp0User_Env.bat +echo O3DE_DEV = %O3DE_DEV% + +:: Constant Vars (Global) +:: global debug flag (propogates) +:: The intent here is to set and globally enter a debug mode +IF "%DCCSI_GDEBUG%"=="" (set DCCSI_GDEBUG=false) +echo DCCSI_GDEBUG = %DCCSI_GDEBUG% +:: initiates earliest debugger connection +:: we support attaching to WingIDE... PyCharm and VScode in the future +IF "%DCCSI_DEV_MODE%"=="" (set DCCSI_DEV_MODE=false) +echo DCCSI_DEV_MODE = %DCCSI_DEV_MODE% +:: sets debugger, options: WING, PYCHARM +IF "%DCCSI_GDEBUGGER%"=="" (set DCCSI_GDEBUGGER=WING) +echo DCCSI_GDEBUGGER = %DCCSI_GDEBUGGER% +:: Default level logger will handle +:: Override this to control the setting +:: CRITICAL:50 +:: ERROR:40 +:: WARNING:30 +:: INFO:20 +:: DEBUG:10 +:: NOTSET:0 +IF "%DCCSI_LOGLEVEL%"=="" (set DCCSI_LOGLEVEL=20) +echo DCCSI_LOGLEVEL = %DCCSI_LOGLEVEL% + +:: Override the default maya version +IF "%DCCSI_MAYA_VERSION%"=="" (set DCCSI_MAYA_VERSION=2020) +echo DCCSI_MAYA_VERSION = %DCCSI_MAYA_VERSION% + +:: O3DE_PROJECT_PATH is ideally treated as a full path in the env launchers +:: do to changes in o3de, external engine/project/gem folder structures, etc. +IF "%O3DE_PROJECT_PATH%"=="" ( + for %%i in ("%~dp0..") do set "O3DE_PROJECT_PATH=%%~fi" + ) +echo O3DE_PROJECT_PATH = %O3DE_PROJECT_PATH% + +:: Change to root O3DE dev dir +IF "%O3DE_DEV%"=="" echo ~ You must set O3DE_DEV in a User_Env.bat to match your local engine repo! +IF "%O3DE_DEV%"=="" echo ~ Using default O3DE_DEV=C:\Depot\o3de-engine +IF "%O3DE_DEV%"=="" (set O3DE_DEV=C:\Depot\o3de-engine) +echo O3DE_DEV = %O3DE_DEV% + +CALL %O3DE_DEV%\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\Tools\Dev\Windows\Env_Maya.bat + +:: Restore original directory +popd + +:: Change to root dir +CD /D %ABS_PATH% + +::ENDLOCAL + +:: Set flag so we don't initialize dccsi environment twice +SET O3DE_PROJ_ENV_INIT=1 +GOTO END_OF_FILE + +:: Return to starting directory +POPD + +:END_OF_FILE diff --git a/AutomatedTesting/Gem/Sponza/Tools/User_Env.bat.template b/AutomatedTesting/Gem/Sponza/Tools/User_Env.bat.template new file mode 100644 index 0000000000..d108f30a5b --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/Tools/User_Env.bat.template @@ -0,0 +1,42 @@ +@echo off + +REM +REM Copyright (c) Contributors to the Open 3D Engine Project +REM +REM SPDX-License-Identifier: Apache-2.0 OR MIT +REM For complete copyright and license terms please see the LICENSE at the root of this distribution. +REM +REM + +:: copy this file, rename to User_Env.bat (remove .template) +:: use this file to override any local properties that differ from base + +:: Skip initialization if already completed +IF "%O3DE_USER_ENV_INIT%"=="1" GOTO :END_OF_FILE + +:: Store current dir +%~d0 +cd %~dp0 +PUSHD %~dp0 + +SET O3DE_DEV=C:\Depot\o3de-engine +::SET OCIO_APPS=C:\Depot\o3de-engine\Tools\ColorGrading\ocio\build\src\apps +SET TAG_LY_BUILD_PATH=build +SET DCCSI_GDEBUG=True +SET DCCSI_DEV_MODE=True + +set DCCSI_MAYA_VERSION=2020 + +:: set the your user name here for windows path +SET TAG_USERNAME=NOT_SET +SET DCCSI_PY_REV=rev1 +SET DCCSI_PY_PLATFORM=windows + +:: Set flag so we don't initialize dccsi environment twice +SET O3DE_USER_ENV_INIT=1 +GOTO END_OF_FILE + +:: Return to starting directory +POPD + +:END_OF_FILE \ No newline at end of file diff --git a/AutomatedTesting/Gem/Sponza/gem.json b/AutomatedTesting/Gem/Sponza/gem.json new file mode 100644 index 0000000000..68749cd5f4 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/gem.json @@ -0,0 +1,17 @@ +{ + "gem_name": "Sponza", + "display_name": "Sponza", + "license": "Apache-2.0 Or MIT", + "license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt", + "origin": "Open 3D Engine - o3de.org", + "type": "Asset", + "summary": "A standard test scene for Global Illumination (forked from crytek sponza scene)", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "Assets" + ], + "requirements": "", + "dependencies": [] +} diff --git a/AutomatedTesting/Gem/Sponza/workspace.mel b/AutomatedTesting/Gem/Sponza/workspace.mel new file mode 100644 index 0000000000..b082b73ef7 --- /dev/null +++ b/AutomatedTesting/Gem/Sponza/workspace.mel @@ -0,0 +1,89 @@ +//Maya 2020 Project Definition + +workspace -fr "fluidCache" ""; +workspace -fr "JT_ATF" ""; +workspace -fr "images" ".maya_data/Images"; +workspace -fr "offlineEdit" ".maya_data/scenes/edits"; +workspace -fr "STEP_ATF Export" ""; +workspace -fr "furShadowMap" ""; +workspace -fr "SVG" ""; +workspace -fr "scripts" "ArtSource/Maya/Scripts"; +workspace -fr "DAE_FBX" ""; +workspace -fr "shaders" "ArtSource/Maya/Shaders"; +workspace -fr "NX_ATF" ""; +workspace -fr "CATIAV5_ATF Export" ""; +workspace -fr "furFiles" ""; +workspace -fr "OBJ" ".maya_data/obj"; +workspace -fr "PARASOLID_ATF Export" ""; +workspace -fr "FBX export" "Assets/Objects"; +workspace -fr "furEqualMap" ""; +workspace -fr "textures" "Assets/textures"; +workspace -fr "BIF" ""; +workspace -fr "lights" ".maya_data/renderData/shaders"; +workspace -fr "DAE_FBX export" ""; +workspace -fr "aliasWire" ".maya_data/data"; +workspace -fr "CATIAV5_ATF" ""; +workspace -fr "SAT_ATF Export" ""; +workspace -fr "movie" ".maya_data/movies"; +workspace -fr "ASS Export" ""; +workspace -fr "mayaAscii" ""; +workspace -fr "move" ".maya_data"; +workspace -fr "autoSave" ".maya_data/autoSave"; +workspace -fr "NX_ATF Export" ""; +workspace -fr "sound" ".maya_data/sound"; +workspace -fr "mayaBinary" ""; +workspace -fr "timeEditor" ""; +workspace -fr "RIBexport" ".maya_data/data"; +workspace -fr "DWG_ATF" ""; +workspace -fr "mentalray" ".maya_data/renderData/mentalray"; +workspace -fr "Arnold-USD" ""; +workspace -fr "JT_ATF Export" ""; +workspace -fr "iprImages" ".maya_data/renderData/iprImages"; +workspace -fr "FBX" "Assets/Objects"; +workspace -fr "renderData" ".maya_data/renderData"; +workspace -fr "CATIAV4_ATF" ""; +workspace -fr "fileCache" ""; +workspace -fr "Fbx" "Assets/Objects"; +workspace -fr "eps" ""; +workspace -fr "IGESexport" ".maya_data/data"; +workspace -fr "3dPaintTextures" ".maya_data/3dPaintTextures"; +workspace -fr "DXF_ATF Export" ""; +workspace -fr "mel" ".maya_data/mel"; +workspace -fr "translatorData" ""; +workspace -fr "IGES" ".maya_data/data"; +workspace -fr "particles" ".maya_data/particles"; +workspace -fr "DXFexport" ".maya_data/data"; +workspace -fr "DXF_ATF" ""; +workspace -fr "scene" "Assets/Objects"; +workspace -fr "renderScenes" ".maya_data/renderScenes"; +workspace -fr "SAT_ATF" ""; +workspace -fr "PROE_ATF" ""; +workspace -fr "WIRE_ATF Export" ""; +workspace -fr "sourceImages" "ArtSource/Images"; +workspace -fr "RIB" ".maya_data/data"; +workspace -fr "furImages" ""; +workspace -fr "clips" ".maya_data/clips"; +workspace -fr "Adobe(R) Illustrator(R)" ".maya_data/data"; +workspace -fr "animExport" ".maya_data/data"; +workspace -fr "mentalRay" ".maya_data/mentalRay"; +workspace -fr "STEP_ATF" ""; +workspace -fr "DWG_ATF Export" ""; +workspace -fr "depth" ".maya_data/renderData/depth"; +workspace -fr "sceneAssembly" ""; +workspace -fr "IGES_ATF Export" ""; +workspace -fr "teClipExports" ""; +workspace -fr "IGES_ATF" ""; +workspace -fr "PARASOLID_ATF" ""; +workspace -fr "ASS" ""; +workspace -fr "Substance" ".maya_data/data"; +workspace -fr "audio" ".maya_data/sound"; +workspace -fr "EPS" ".maya_data/data"; +workspace -fr "Alembic" "Assets/Objects"; +workspace -fr "diskCache" ".maya_data/cache"; +workspace -fr "illustrator" ""; +workspace -fr "WIRE_ATF" ""; +workspace -fr "templates" "ArtSource/SceneTemplates"; +workspace -fr "animImport" ".maya_data/data"; +workspace -fr "OBJexport" "Assets/Objects"; +workspace -fr "furAttrMap" ""; +workspace -fr "DXF" ".maya_data/data"; diff --git a/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/AutoComponent_NetworkInput.prefab b/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/AutoComponent_NetworkInput.prefab new file mode 100644 index 0000000000..78c88144fd --- /dev/null +++ b/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/AutoComponent_NetworkInput.prefab @@ -0,0 +1,525 @@ +{ + "ContainerEntity": { + "Id": "Entity_[1146574390643]", + "Name": "Level", + "Components": { + "Component_[10641544592923449938]": { + "$type": "EditorInspectorComponent", + "Id": 10641544592923449938 + }, + "Component_[12039882709170782873]": { + "$type": "EditorOnlyEntityComponent", + "Id": 12039882709170782873 + }, + "Component_[12265484671603697631]": { + "$type": "EditorPendingCompositionComponent", + "Id": 12265484671603697631 + }, + "Component_[14126657869720434043]": { + "$type": "EditorEntitySortComponent", + "Id": 14126657869720434043 + }, + "Component_[15230859088967841193]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 15230859088967841193, + "Parent Entity": "" + }, + "Component_[16239496886950819870]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 16239496886950819870 + }, + "Component_[5688118765544765547]": { + "$type": "EditorEntityIconComponent", + "Id": 5688118765544765547 + }, + "Component_[6545738857812235305]": { + "$type": "SelectionComponent", + "Id": 6545738857812235305 + }, + "Component_[7247035804068349658]": { + "$type": "EditorPrefabComponent", + "Id": 7247035804068349658 + }, + "Component_[9307224322037797205]": { + "$type": "EditorLockComponent", + "Id": 9307224322037797205 + }, + "Component_[9562516168917670048]": { + "$type": "EditorVisibilityComponent", + "Id": 9562516168917670048 + } + } + }, + "Entities": { + "Entity_[1155164325235]": { + "Id": "Entity_[1155164325235]", + "Name": "Sun", + "Components": { + "Component_[10440557478882592717]": { + "$type": "SelectionComponent", + "Id": 10440557478882592717 + }, + "Component_[13620450453324765907]": { + "$type": "EditorLockComponent", + "Id": 13620450453324765907 + }, + "Component_[2134313378593666258]": { + "$type": "EditorInspectorComponent", + "Id": 2134313378593666258 + }, + "Component_[234010807770404186]": { + "$type": "EditorVisibilityComponent", + "Id": 234010807770404186 + }, + "Component_[2970359110423865725]": { + "$type": "EditorEntityIconComponent", + "Id": 2970359110423865725 + }, + "Component_[3722854130373041803]": { + "$type": "EditorOnlyEntityComponent", + "Id": 3722854130373041803 + }, + "Component_[5992533738676323195]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 5992533738676323195 + }, + "Component_[7378860763541895402]": { + "$type": "AZ::Render::EditorDirectionalLightComponent", + "Id": 7378860763541895402, + "Controller": { + "Configuration": { + "Intensity": 1.0, + "CameraEntityId": "", + "ShadowFilterMethod": 1 + } + } + }, + "Component_[7892834440890947578]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7892834440890947578, + "Parent Entity": "Entity_[1176639161715]", + "Transform Data": { + "Translate": [ + 0.0, + 0.0, + 13.487043380737305 + ], + "Rotate": [ + -76.13099670410156, + -0.847000002861023, + -15.8100004196167 + ] + } + }, + "Component_[8599729549570828259]": { + "$type": "EditorEntitySortComponent", + "Id": 8599729549570828259 + }, + "Component_[952797371922080273]": { + "$type": "EditorPendingCompositionComponent", + "Id": 952797371922080273 + } + } + }, + "Entity_[1159459292531]": { + "Id": "Entity_[1159459292531]", + "Name": "Ground", + "Components": { + "Component_[11701138785793981042]": { + "$type": "SelectionComponent", + "Id": 11701138785793981042 + }, + "Component_[12260880513256986252]": { + "$type": "EditorEntityIconComponent", + "Id": 12260880513256986252 + }, + "Component_[13711420870643673468]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 13711420870643673468 + }, + "Component_[138002849734991713]": { + "$type": "EditorOnlyEntityComponent", + "Id": 138002849734991713 + }, + "Component_[16578565737331764849]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 16578565737331764849, + "Parent Entity": "Entity_[1176639161715]" + }, + "Component_[16919232076966545697]": { + "$type": "EditorInspectorComponent", + "Id": 16919232076966545697 + }, + "Component_[5182430712893438093]": { + "$type": "EditorMaterialComponent", + "Id": 5182430712893438093, + "materialSlots": [ + { + "id": { + "materialSlotStableId": 803645540 + } + }, + { + "id": { + "materialSlotStableId": 803645540 + } + } + ], + "materialSlotsByLod": [ + [ + { + "id": { + "lodIndex": 0, + "materialSlotStableId": 803645540 + } + } + ], + [ + { + "id": { + "lodIndex": 0, + "materialSlotStableId": 803645540 + } + } + ] + ] + }, + "Component_[5675108321710651991]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 5675108321710651991, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}", + "subId": 277889906 + }, + "assetHint": "objects/groudplane/groundplane_512x512m.azmodel" + } + } + } + }, + "Component_[5681893399601237518]": { + "$type": "EditorEntitySortComponent", + "Id": 5681893399601237518 + }, + "Component_[592692962543397545]": { + "$type": "EditorPendingCompositionComponent", + "Id": 592692962543397545 + }, + "Component_[7090012899106946164]": { + "$type": "EditorLockComponent", + "Id": 7090012899106946164 + }, + "Component_[9410832619875640998]": { + "$type": "EditorVisibilityComponent", + "Id": 9410832619875640998 + } + } + }, + "Entity_[1163754259827]": { + "Id": "Entity_[1163754259827]", + "Name": "Camera", + "Components": { + "Component_[11895140916889160460]": { + "$type": "EditorEntityIconComponent", + "Id": 11895140916889160460 + }, + "Component_[16880285896855930892]": { + "$type": "{CA11DA46-29FF-4083-B5F6-E02C3A8C3A3D} EditorCameraComponent", + "Id": 16880285896855930892, + "Controller": { + "Configuration": { + "Field of View": 55.0, + "EditorEntityId": 12554887233631987164 + } + } + }, + "Component_[17187464423780271193]": { + "$type": "EditorLockComponent", + "Id": 17187464423780271193 + }, + "Component_[17495696818315413311]": { + "$type": "EditorEntitySortComponent", + "Id": 17495696818315413311 + }, + "Component_[18086214374043522055]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 18086214374043522055, + "Parent Entity": "Entity_[1176639161715]", + "Transform Data": { + "Translate": [ + -2.3000001907348633, + -3.9368600845336914, + 1.0 + ], + "Rotate": [ + -2.050307512283325, + 1.9552897214889526, + -43.623355865478516 + ] + } + }, + "Component_[18387556550380114975]": { + "$type": "SelectionComponent", + "Id": 18387556550380114975 + }, + "Component_[2654521436129313160]": { + "$type": "EditorVisibilityComponent", + "Id": 2654521436129313160 + }, + "Component_[5265045084611556958]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 5265045084611556958 + }, + "Component_[7169798125182238623]": { + "$type": "EditorPendingCompositionComponent", + "Id": 7169798125182238623 + }, + "Component_[7255796294953281766]": { + "$type": "GenericComponentWrapper", + "Id": 7255796294953281766, + "m_template": { + "$type": "FlyCameraInputComponent" + } + }, + "Component_[8866210352157164042]": { + "$type": "EditorInspectorComponent", + "Id": 8866210352157164042 + }, + "Component_[9129253381063760879]": { + "$type": "EditorOnlyEntityComponent", + "Id": 9129253381063760879 + } + } + }, + "Entity_[1168049227123]": { + "Id": "Entity_[1168049227123]", + "Name": "Grid", + "Components": { + "Component_[11443347433215807130]": { + "$type": "EditorEntityIconComponent", + "Id": 11443347433215807130 + }, + "Component_[11779275529534764488]": { + "$type": "SelectionComponent", + "Id": 11779275529534764488 + }, + "Component_[14249419413039427459]": { + "$type": "EditorInspectorComponent", + "Id": 14249419413039427459 + }, + "Component_[15448581635946161318]": { + "$type": "AZ::Render::EditorGridComponent", + "Id": 15448581635946161318, + "Controller": { + "Configuration": { + "primarySpacing": 4.0, + "primaryColor": [ + 0.501960813999176, + 0.501960813999176, + 0.501960813999176 + ], + "secondarySpacing": 0.5, + "secondaryColor": [ + 0.250980406999588, + 0.250980406999588, + 0.250980406999588 + ] + } + } + }, + "Component_[1843303322527297409]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 1843303322527297409 + }, + "Component_[380249072065273654]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 380249072065273654, + "Parent Entity": "Entity_[1176639161715]" + }, + "Component_[7476660583684339787]": { + "$type": "EditorPendingCompositionComponent", + "Id": 7476660583684339787 + }, + "Component_[7557626501215118375]": { + "$type": "EditorEntitySortComponent", + "Id": 7557626501215118375 + }, + "Component_[7984048488947365511]": { + "$type": "EditorVisibilityComponent", + "Id": 7984048488947365511 + }, + "Component_[8118181039276487398]": { + "$type": "EditorOnlyEntityComponent", + "Id": 8118181039276487398 + }, + "Component_[9189909764215270515]": { + "$type": "EditorLockComponent", + "Id": 9189909764215270515 + } + } + }, + "Entity_[1176639161715]": { + "Id": "Entity_[1176639161715]", + "Name": "Atom Default Environment", + "Components": { + "Component_[10757302973393310045]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 10757302973393310045, + "Parent Entity": "Entity_[1146574390643]" + }, + "Component_[14505817420424255464]": { + "$type": "EditorInspectorComponent", + "Id": 14505817420424255464, + "ComponentOrderEntryArray": [ + { + "ComponentId": 10757302973393310045 + } + ] + }, + "Component_[14988041764659020032]": { + "$type": "EditorLockComponent", + "Id": 14988041764659020032 + }, + "Component_[15808690248755038124]": { + "$type": "SelectionComponent", + "Id": 15808690248755038124 + }, + "Component_[15900837685796817138]": { + "$type": "EditorVisibilityComponent", + "Id": 15900837685796817138 + }, + "Component_[3298767348226484884]": { + "$type": "EditorOnlyEntityComponent", + "Id": 3298767348226484884 + }, + "Component_[4076975109609220594]": { + "$type": "EditorPendingCompositionComponent", + "Id": 4076975109609220594 + }, + "Component_[5679760548946028854]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 5679760548946028854 + }, + "Component_[5855590796136709437]": { + "$type": "EditorEntitySortComponent", + "Id": 5855590796136709437, + "ChildEntityOrderEntryArray": [ + { + "EntityId": "Entity_[1155164325235]" + }, + { + "EntityId": "Entity_[1180934129011]", + "SortIndex": 1 + }, + { + "EntityId": "", + "SortIndex": 2 + }, + { + "EntityId": "Entity_[1168049227123]", + "SortIndex": 3 + }, + { + "EntityId": "Entity_[1163754259827]", + "SortIndex": 4 + }, + { + "EntityId": "Entity_[1159459292531]", + "SortIndex": 5 + } + ] + }, + "Component_[9277695270015777859]": { + "$type": "EditorEntityIconComponent", + "Id": 9277695270015777859 + } + } + }, + "Entity_[1180934129011]": { + "Id": "Entity_[1180934129011]", + "Name": "Global Sky", + "Components": { + "Component_[11231930600558681245]": { + "$type": "AZ::Render::EditorHDRiSkyboxComponent", + "Id": 11231930600558681245, + "Controller": { + "Configuration": { + "CubemapAsset": { + "assetId": { + "guid": "{215E47FD-D181-5832-B1AB-91673ABF6399}", + "subId": 1000 + }, + "assetHint": "lightingpresets/highcontrast/goegap_4k_skyboxcm.exr.streamingimage" + } + } + } + }, + "Component_[11980494120202836095]": { + "$type": "SelectionComponent", + "Id": 11980494120202836095 + }, + "Component_[1428633914413949476]": { + "$type": "EditorLockComponent", + "Id": 1428633914413949476 + }, + "Component_[14936200426671614999]": { + "$type": "AZ::Render::EditorImageBasedLightComponent", + "Id": 14936200426671614999, + "Controller": { + "Configuration": { + "diffuseImageAsset": { + "assetId": { + "guid": "{3FD09945-D0F2-55C8-B9AF-B2FD421FE3BE}", + "subId": 3000 + }, + "assetHint": "lightingpresets/highcontrast/goegap_4k_iblglobalcm_ibldiffuse.exr.streamingimage" + }, + "specularImageAsset": { + "assetId": { + "guid": "{3FD09945-D0F2-55C8-B9AF-B2FD421FE3BE}", + "subId": 2000 + }, + "assetHint": "lightingpresets/highcontrast/goegap_4k_iblglobalcm_iblspecular.exr.streamingimage" + } + } + } + }, + "Component_[14994774102579326069]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 14994774102579326069 + }, + "Component_[15417479889044493340]": { + "$type": "EditorPendingCompositionComponent", + "Id": 15417479889044493340 + }, + "Component_[15826613364991382688]": { + "$type": "EditorEntitySortComponent", + "Id": 15826613364991382688 + }, + "Component_[1665003113283562343]": { + "$type": "EditorOnlyEntityComponent", + "Id": 1665003113283562343 + }, + "Component_[3704934735944502280]": { + "$type": "EditorEntityIconComponent", + "Id": 3704934735944502280 + }, + "Component_[5698542331457326479]": { + "$type": "EditorVisibilityComponent", + "Id": 5698542331457326479 + }, + "Component_[6644513399057217122]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 6644513399057217122, + "Parent Entity": "Entity_[1176639161715]" + }, + "Component_[931091830724002070]": { + "$type": "EditorInspectorComponent", + "Id": 931091830724002070 + } + } + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/AutoComponent_NetworkInput.scriptcanvas b/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/AutoComponent_NetworkInput.scriptcanvas new file mode 100644 index 0000000000..2f8a434108 --- /dev/null +++ b/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/AutoComponent_NetworkInput.scriptcanvas @@ -0,0 +1,1827 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "ScriptCanvasData", + "ClassData": { + "m_scriptCanvas": { + "Id": { + "id": 20239954977260 + }, + "Name": "AutoComponent_NetworkInput", + "Components": { + "Component_[14059414856740480991]": { + "$type": "EditorGraphVariableManagerComponent", + "Id": 14059414856740480991, + "m_variableData": { + "m_nameVariableMap": [ + { + "Key": { + "m_id": "{720D213F-AA7A-48B0-ABBB-A3756C528CF9}" + }, + "Value": { + "Datum": { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.25 + }, + "VariableId": { + "m_id": "{720D213F-AA7A-48B0-ABBB-A3756C528CF9}" + }, + "VariableName": "leftright" + } + }, + { + "Key": { + "m_id": "{B2E338E9-2ACB-42FC-B0BE-EDA14A8A86AD}" + }, + "Value": { + "Datum": { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 1.0 + }, + "VariableId": { + "m_id": "{B2E338E9-2ACB-42FC-B0BE-EDA14A8A86AD}" + }, + "VariableName": "fwdback" + } + }, + { + "Key": { + "m_id": "{D843B13F-B3C7-4619-8092-7164EB1D7888}" + }, + "Value": { + "Datum": { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 20.0 + }, + "VariableId": { + "m_id": "{D843B13F-B3C7-4619-8092-7164EB1D7888}" + }, + "VariableName": "VELOCITY" + } + } + ] + } + }, + "Component_[642525765775040231]": { + "$type": "{4D755CA9-AB92-462C-B24F-0B3376F19967} Graph", + "Id": 642525765775040231, + "m_graphData": { + "m_nodes": [ + { + "Id": { + "id": 20265724781036 + }, + "Name": "SC-Node(NotEqualTo)", + "Components": { + "Component_[12036973200504716538]": { + "$type": "NotEqualTo", + "Id": 12036973200504716538, + "Slots": [ + { + "id": { + "m_id": "{6D0EF497-7C52-432B-9301-7BAB26F4F5A5}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result", + "DisplayDataType": { + "m_type": 0 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{AE7E453C-15DE-47D2-954A-05C3895B7AC6}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "Signal to perform the evaluation when desired.", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{AC364E17-A9A1-42DB-A29D-7B2D666E4287}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "True", + "toolTip": "Signaled if the result of the operation is true.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{0754AC37-61A6-42A7-B4EC-D35D18D27960}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "False", + "toolTip": "Signaled if the result of the operation is false.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{57EBC15B-452E-49B3-8BD3-4FBBB07F1F14}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Value A", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 3545012108 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{21864AEE-7954-4F68-82C6-573C175724EF}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Value B", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 3545012108 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{720D213F-AA7A-48B0-ABBB-A3756C528CF9}" + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Value A" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Value B" + } + ] + } + } + }, + { + "Id": { + "id": 20257134846444 + }, + "Name": "SC-Node(NotEqualTo)", + "Components": { + "Component_[12036973200504716538]": { + "$type": "NotEqualTo", + "Id": 12036973200504716538, + "Slots": [ + { + "id": { + "m_id": "{6D0EF497-7C52-432B-9301-7BAB26F4F5A5}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result", + "DisplayDataType": { + "m_type": 0 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{AE7E453C-15DE-47D2-954A-05C3895B7AC6}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "Signal to perform the evaluation when desired.", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{AC364E17-A9A1-42DB-A29D-7B2D666E4287}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "True", + "toolTip": "Signaled if the result of the operation is true.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{0754AC37-61A6-42A7-B4EC-D35D18D27960}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "False", + "toolTip": "Signaled if the result of the operation is false.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{57EBC15B-452E-49B3-8BD3-4FBBB07F1F14}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Value A", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 3545012108 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{21864AEE-7954-4F68-82C6-573C175724EF}" + }, + "DynamicTypeOverride": 3, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Value B", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DynamicGroup": { + "Value": 3545012108 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{B2E338E9-2ACB-42FC-B0BE-EDA14A8A86AD}" + } + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Value A" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "Value B" + } + ] + } + } + }, + { + "Id": { + "id": 20278609682924 + }, + "Name": "SC-Node(Print)", + "Components": { + "Component_[12996217042650310160]": { + "$type": "Print", + "Id": 12996217042650310160, + "Slots": [ + { + "id": { + "m_id": "{D994D58A-DBF1-4929-B779-F0D2CBAD2F0D}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "Input signal", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{233C15BC-0186-4A7F-B272-6B2C020DE57A}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "m_format": "AutoComponent_NetworkInput ProcessInput called!", + "m_unresolvedString": [ + "AutoComponent_NetworkInput ProcessInput called!" + ] + } + } + }, + { + "Id": { + "id": 20244249944556 + }, + "Name": "SC-Node(CreateFromValues)", + "Components": { + "Component_[16858161274259468878]": { + "$type": "{E42861BD-1956-45AE-8DD7-CCFC1E3E5ACF} Method", + "Id": 16858161274259468878, + "Slots": [ + { + "id": { + "m_id": "{7CF3B6C0-FE59-4E16-ABC6-E7CC8AFA8EF3}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "fwdBack", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{B2E338E9-2ACB-42FC-B0BE-EDA14A8A86AD}" + } + }, + { + "id": { + "m_id": "{02473A8A-A0F0-4E73-8999-0B16D17E8E2A}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "leftRight", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1, + "IsReference": true, + "VariableReference": { + "m_id": "{720D213F-AA7A-48B0-ABBB-A3756C528CF9}" + } + }, + { + "id": { + "m_id": "{514CDDAA-290F-4758-B28F-4003E719E635}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{E3A94716-C880-4D6D-ADD6-34D019161ED1}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{90E52F81-54E0-4C63-9881-B661FB5D87D1}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result: NetworkTestPlayerComponentNetworkInput", + "DisplayDataType": { + "m_type": 4, + "m_azType": "{12A1776B-61F6-4E5F-356A-AD718A62051F}" + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "fwdBack" + }, + { + "scriptCanvasType": { + "m_type": 3 + }, + "isNullPointer": false, + "$type": "double", + "value": 0.0, + "label": "leftRight" + } + ], + "methodType": 2, + "methodName": "CreateFromValues", + "className": "NetworkTestPlayerComponentNetworkInput", + "resultSlotIDs": [ + {} + ], + "inputSlots": [ + { + "m_id": "{7CF3B6C0-FE59-4E16-ABC6-E7CC8AFA8EF3}" + }, + { + "m_id": "{02473A8A-A0F0-4E73-8999-0B16D17E8E2A}" + } + ], + "prettyClassName": "NetworkTestPlayerComponentNetworkInput" + } + } + }, + { + "Id": { + "id": 20252839879148 + }, + "Name": "EBusEventHandler", + "Components": { + "Component_[1734984895901771768]": { + "$type": "EBusEventHandler", + "Id": 1734984895901771768, + "Slots": [ + { + "id": { + "m_id": "{41899533-66B3-423F-8BE2-A624A1FB6FCB}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Connect", + "toolTip": "Connect this event handler to the specified entity.", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{65C09819-1B00-4BA8-B6E0-8DD6D8B44A36}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Disconnect", + "toolTip": "Disconnect this event handler.", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{B7437693-34D0-4BDE-8516-2CADB9F509BF}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "OnConnected", + "toolTip": "Signaled when a connection has taken place.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{BCE36ED4-0899-4403-826A-FE89BE033A0A}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "OnDisconnected", + "toolTip": "Signaled when this event handler is disconnected.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{796993F4-685D-4A85-A0CA-032E3466FB57}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "OnFailure", + "toolTip": "Signaled when it is not possible to connect this handler.", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{680E9668-87B4-4FF2-AA24-EB8F11352A49}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Source", + "toolTip": "ID used to connect on a specific Event address (Type: EntityId)", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{ADF9B366-8324-4C1E-B601-C059DA70FDDE}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Result: NetworkTestPlayerComponentNetworkInput", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{3301102C-AD42-48A0-8764-D2D4D31E0C75}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{B831AC60-7641-4B74-9829-26A3576B4766}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "ExecutionSlot:CreateInput", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + }, + "IsLatent": true + }, + { + "id": { + "m_id": "{8F69FA2E-28D8-4DF1-A4B5-AEF3985095C5}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "NetworkTestPlayerComponentNetworkInput", + "DisplayDataType": { + "m_type": 4, + "m_azType": "{12A1776B-61F6-4E5F-356A-AD718A62051F}" + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{BE3E3F84-E91C-4F6C-B148-E13BF01B5FBC}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Number", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{4C8F2908-12B0-4C35-8468-31D3D4DF36AA}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "ExecutionSlot:ProcessInput", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + }, + "IsLatent": true + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 1 + }, + "isNullPointer": false, + "$type": "EntityId", + "value": { + "id": 2901262558 + }, + "label": "Source" + }, + { + "scriptCanvasType": { + "m_type": 4, + "m_azType": "{12A1776B-61F6-4E5F-356A-AD718A62051F}" + }, + "isNullPointer": false, + "$type": "NetworkTestPlayerComponentNetworkInput", + "label": "Result: NetworkTestPlayerComponentNetworkInput" + } + ], + "m_eventMap": [ + { + "Key": { + "Value": 78438309 + }, + "Value": { + "m_eventName": "CreateInput", + "m_eventId": { + "Value": 78438309 + }, + "m_eventSlotId": { + "m_id": "{B831AC60-7641-4B74-9829-26A3576B4766}" + }, + "m_resultSlotId": { + "m_id": "{ADF9B366-8324-4C1E-B601-C059DA70FDDE}" + }, + "m_parameterSlotIds": [ + { + "m_id": "{3301102C-AD42-48A0-8764-D2D4D31E0C75}" + } + ], + "m_numExpectedArguments": 1 + } + }, + { + "Key": { + "Value": 1793364217 + }, + "Value": { + "m_eventName": "ProcessInput", + "m_eventId": { + "Value": 1793364217 + }, + "m_eventSlotId": { + "m_id": "{4C8F2908-12B0-4C35-8468-31D3D4DF36AA}" + }, + "m_parameterSlotIds": [ + { + "m_id": "{8F69FA2E-28D8-4DF1-A4B5-AEF3985095C5}" + }, + { + "m_id": "{BE3E3F84-E91C-4F6C-B148-E13BF01B5FBC}" + } + ], + "m_numExpectedArguments": 2 + } + } + ], + "m_ebusName": "NetworkTestPlayerComponentBusHandler", + "m_busId": { + "Value": 3690077280 + } + } + } + }, + { + "Id": { + "id": 20248544911852 + }, + "Name": "SC-Node(ExtractProperty)", + "Components": { + "Component_[2395597035843331661]": { + "$type": "ExtractProperty", + "Id": 2395597035843331661, + "Slots": [ + { + "id": { + "m_id": "{C80C50EE-F216-4F44-B107-6B35354AFD52}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "When signaled assigns property values using the supplied source input", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{C69C098D-D667-4DC7-85E5-AFD119727D94}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "toolTip": "Signaled after all property haves have been pushed to the output slots", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{D387C800-352B-4B01-8765-4F4B40DF45CB}" + }, + "DynamicTypeOverride": 1, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Source", + "toolTip": "The value on which to extract properties from.", + "DisplayDataType": { + "m_type": 4, + "m_azType": "{12A1776B-61F6-4E5F-356A-AD718A62051F}" + }, + "Descriptor": { + "ConnectionType": 1, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{0C03D491-DE25-46C2-BF09-14769FA49FDB}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "FwdBack: Number", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + }, + { + "id": { + "m_id": "{4C13F9EF-60BF-4AD1-8FA9-66F46455411C}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "LeftRight: Number", + "DisplayDataType": { + "m_type": 3 + }, + "Descriptor": { + "ConnectionType": 2, + "SlotType": 2 + }, + "DataType": 1 + } + ], + "Datums": [ + { + "scriptCanvasType": { + "m_type": 4, + "m_azType": "{12A1776B-61F6-4E5F-356A-AD718A62051F}" + }, + "isNullPointer": false, + "$type": "NetworkTestPlayerComponentNetworkInput", + "label": "Source" + } + ], + "m_dataType": { + "m_type": 4, + "m_azType": "{12A1776B-61F6-4E5F-356A-AD718A62051F}" + }, + "m_propertyAccounts": [ + { + "m_propertySlotId": { + "m_id": "{0C03D491-DE25-46C2-BF09-14769FA49FDB}" + }, + "m_propertyType": { + "m_type": 3 + }, + "m_propertyName": "FwdBack" + }, + { + "m_propertySlotId": { + "m_id": "{4C13F9EF-60BF-4AD1-8FA9-66F46455411C}" + }, + "m_propertyType": { + "m_type": 3 + }, + "m_propertyName": "LeftRight" + } + ] + } + } + }, + { + "Id": { + "id": 20270019748332 + }, + "Name": "SC-Node(Print)", + "Components": { + "Component_[7568030783460446634]": { + "$type": "Print", + "Id": 7568030783460446634, + "Slots": [ + { + "id": { + "m_id": "{733AA75D-022C-45E9-9D0F-3EF9A1633ADC}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "Input signal", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{2E77B0FC-DE56-4D78-9A59-152B7A0666F3}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "m_format": "AutoComponent_NetworkInput received bad fwdback!", + "m_unresolvedString": [ + "AutoComponent_NetworkInput received bad fwdback!" + ] + } + } + }, + { + "Id": { + "id": 20274314715628 + }, + "Name": "SC-Node(Print)", + "Components": { + "Component_[7568030783460446634]": { + "$type": "Print", + "Id": 7568030783460446634, + "Slots": [ + { + "id": { + "m_id": "{733AA75D-022C-45E9-9D0F-3EF9A1633ADC}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "Input signal", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{2E77B0FC-DE56-4D78-9A59-152B7A0666F3}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "m_format": "AutoComponent_NetworkInput received bad leftright!", + "m_unresolvedString": [ + "AutoComponent_NetworkInput received bad leftright!" + ] + } + } + }, + { + "Id": { + "id": 20261429813740 + }, + "Name": "SC-Node(Print)", + "Components": { + "Component_[8131385522131771125]": { + "$type": "Print", + "Id": 8131385522131771125, + "Slots": [ + { + "id": { + "m_id": "{2B6DB3BC-AA87-4280-B4C3-42C1EE17CBA3}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "In", + "toolTip": "Input signal", + "Descriptor": { + "ConnectionType": 1, + "SlotType": 1 + } + }, + { + "id": { + "m_id": "{68F1C47B-3127-4CBA-AC9B-5B9B736C70AD}" + }, + "contracts": [ + { + "$type": "SlotTypeContract" + } + ], + "slotName": "Out", + "Descriptor": { + "ConnectionType": 2, + "SlotType": 1 + } + } + ], + "m_format": "AutoComponent_NetworkInput CreateInput called!", + "m_unresolvedString": [ + "AutoComponent_NetworkInput CreateInput called!" + ] + } + } + } + ], + "m_connections": [ + { + "Id": { + "id": 20282904650220 + }, + "Name": "srcEndpoint=(NetworkTestPlayerComponentBusHandler Handler: ExecutionSlot:CreateInput), destEndpoint=(Print: In)", + "Components": { + "Component_[3586317167340048684]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 3586317167340048684, + "sourceEndpoint": { + "nodeId": { + "id": 20252839879148 + }, + "slotId": { + "m_id": "{B831AC60-7641-4B74-9829-26A3576B4766}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20261429813740 + }, + "slotId": { + "m_id": "{2B6DB3BC-AA87-4280-B4C3-42C1EE17CBA3}" + } + } + } + } + }, + { + "Id": { + "id": 20287199617516 + }, + "Name": "srcEndpoint=(NetworkTestPlayerComponentBusHandler Handler: ExecutionSlot:CreateInput), destEndpoint=(CreateFromValues: In)", + "Components": { + "Component_[15956251897822268937]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 15956251897822268937, + "sourceEndpoint": { + "nodeId": { + "id": 20252839879148 + }, + "slotId": { + "m_id": "{B831AC60-7641-4B74-9829-26A3576B4766}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20244249944556 + }, + "slotId": { + "m_id": "{514CDDAA-290F-4758-B28F-4003E719E635}" + } + } + } + } + }, + { + "Id": { + "id": 20291494584812 + }, + "Name": "srcEndpoint=(CreateFromValues: Result: NetworkTestPlayerComponentNetworkInput), destEndpoint=(NetworkTestPlayerComponentBusHandler Handler: Result: NetworkTestPlayerComponentNetworkInput)", + "Components": { + "Component_[3864080489501353126]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 3864080489501353126, + "sourceEndpoint": { + "nodeId": { + "id": 20244249944556 + }, + "slotId": { + "m_id": "{90E52F81-54E0-4C63-9881-B661FB5D87D1}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20252839879148 + }, + "slotId": { + "m_id": "{ADF9B366-8324-4C1E-B601-C059DA70FDDE}" + } + } + } + } + }, + { + "Id": { + "id": 20295789552108 + }, + "Name": "srcEndpoint=(NetworkTestPlayerComponentBusHandler Handler: ExecutionSlot:ProcessInput), destEndpoint=(Print: In)", + "Components": { + "Component_[8628095809445337119]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 8628095809445337119, + "sourceEndpoint": { + "nodeId": { + "id": 20252839879148 + }, + "slotId": { + "m_id": "{4C8F2908-12B0-4C35-8468-31D3D4DF36AA}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20278609682924 + }, + "slotId": { + "m_id": "{D994D58A-DBF1-4929-B779-F0D2CBAD2F0D}" + } + } + } + } + }, + { + "Id": { + "id": 20300084519404 + }, + "Name": "srcEndpoint=(NetworkTestPlayerComponentBusHandler Handler: ExecutionSlot:ProcessInput), destEndpoint=(Extract Properties: In)", + "Components": { + "Component_[10621112306443381493]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 10621112306443381493, + "sourceEndpoint": { + "nodeId": { + "id": 20252839879148 + }, + "slotId": { + "m_id": "{4C8F2908-12B0-4C35-8468-31D3D4DF36AA}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20248544911852 + }, + "slotId": { + "m_id": "{C80C50EE-F216-4F44-B107-6B35354AFD52}" + } + } + } + } + }, + { + "Id": { + "id": 20304379486700 + }, + "Name": "srcEndpoint=(NetworkTestPlayerComponentBusHandler Handler: NetworkTestPlayerComponentNetworkInput), destEndpoint=(Extract Properties: Source)", + "Components": { + "Component_[14013500888143163469]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 14013500888143163469, + "sourceEndpoint": { + "nodeId": { + "id": 20252839879148 + }, + "slotId": { + "m_id": "{8F69FA2E-28D8-4DF1-A4B5-AEF3985095C5}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20248544911852 + }, + "slotId": { + "m_id": "{D387C800-352B-4B01-8765-4F4B40DF45CB}" + } + } + } + } + }, + { + "Id": { + "id": 20308674453996 + }, + "Name": "srcEndpoint=(Extract Properties: Out), destEndpoint=(Not Equal To (!=): In)", + "Components": { + "Component_[14597948098713219792]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 14597948098713219792, + "sourceEndpoint": { + "nodeId": { + "id": 20248544911852 + }, + "slotId": { + "m_id": "{C69C098D-D667-4DC7-85E5-AFD119727D94}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20257134846444 + }, + "slotId": { + "m_id": "{AE7E453C-15DE-47D2-954A-05C3895B7AC6}" + } + } + } + } + }, + { + "Id": { + "id": 20312969421292 + }, + "Name": "srcEndpoint=(Extract Properties: FwdBack: Number), destEndpoint=(Not Equal To (!=): Value A)", + "Components": { + "Component_[14915522756837814768]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 14915522756837814768, + "sourceEndpoint": { + "nodeId": { + "id": 20248544911852 + }, + "slotId": { + "m_id": "{0C03D491-DE25-46C2-BF09-14769FA49FDB}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20257134846444 + }, + "slotId": { + "m_id": "{57EBC15B-452E-49B3-8BD3-4FBBB07F1F14}" + } + } + } + } + }, + { + "Id": { + "id": 20317264388588 + }, + "Name": "srcEndpoint=(Extract Properties: Out), destEndpoint=(Not Equal To (!=): In)", + "Components": { + "Component_[6510282773353837676]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 6510282773353837676, + "sourceEndpoint": { + "nodeId": { + "id": 20248544911852 + }, + "slotId": { + "m_id": "{C69C098D-D667-4DC7-85E5-AFD119727D94}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20265724781036 + }, + "slotId": { + "m_id": "{AE7E453C-15DE-47D2-954A-05C3895B7AC6}" + } + } + } + } + }, + { + "Id": { + "id": 20321559355884 + }, + "Name": "srcEndpoint=(Extract Properties: LeftRight: Number), destEndpoint=(Not Equal To (!=): Value A)", + "Components": { + "Component_[16150645152204311425]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 16150645152204311425, + "sourceEndpoint": { + "nodeId": { + "id": 20248544911852 + }, + "slotId": { + "m_id": "{4C13F9EF-60BF-4AD1-8FA9-66F46455411C}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20265724781036 + }, + "slotId": { + "m_id": "{57EBC15B-452E-49B3-8BD3-4FBBB07F1F14}" + } + } + } + } + }, + { + "Id": { + "id": 20325854323180 + }, + "Name": "srcEndpoint=(Not Equal To (!=): True), destEndpoint=(Print: In)", + "Components": { + "Component_[3322355580364572639]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 3322355580364572639, + "sourceEndpoint": { + "nodeId": { + "id": 20257134846444 + }, + "slotId": { + "m_id": "{AC364E17-A9A1-42DB-A29D-7B2D666E4287}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20270019748332 + }, + "slotId": { + "m_id": "{733AA75D-022C-45E9-9D0F-3EF9A1633ADC}" + } + } + } + } + }, + { + "Id": { + "id": 20330149290476 + }, + "Name": "srcEndpoint=(Not Equal To (!=): True), destEndpoint=(Print: In)", + "Components": { + "Component_[1975626970668030308]": { + "$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection", + "Id": 1975626970668030308, + "sourceEndpoint": { + "nodeId": { + "id": 20265724781036 + }, + "slotId": { + "m_id": "{AC364E17-A9A1-42DB-A29D-7B2D666E4287}" + } + }, + "targetEndpoint": { + "nodeId": { + "id": 20274314715628 + }, + "slotId": { + "m_id": "{733AA75D-022C-45E9-9D0F-3EF9A1633ADC}" + } + } + } + } + } + ] + }, + "m_assetType": "{3E2AC8CD-713F-453E-967F-29517F331784}", + "versionData": { + "_grammarVersion": 1, + "_runtimeVersion": 1, + "_fileVersion": 1 + }, + "m_variableCounter": 2, + "GraphCanvasData": [ + { + "Key": { + "id": 20239954977260 + }, + "Value": { + "ComponentData": { + "{5F84B500-8C45-40D1-8EFC-A5306B241444}": { + "$type": "SceneComponentSaveData", + "ViewParams": { + "Scale": 1.0097068678919363, + "AnchorX": 1086.4539794921875, + "AnchorY": 198.07728576660156 + } + } + } + } + }, + { + "Key": { + "id": 20244249944556 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MethodNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 740.0, + 100.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData", + "SubStyle": ".method" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{7A7C96CB-4B5A-48DD-A0AD-0094A113549B}" + } + } + } + }, + { + "Key": { + "id": 20248544911852 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "DefaultNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 740.0, + 320.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{ED8F0B6C-0811-4FB7-AEE8-B71DB87FABF0}" + } + } + } + }, + { + "Key": { + "id": 20252839879148 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 160.0, + 100.0 + ] + }, + "{9E81C95F-89C0-4476-8E82-63CCC4E52E04}": { + "$type": "EBusHandlerNodeDescriptorSaveData", + "EventIds": [ + { + "Value": 78438309 + }, + { + "Value": 1793364217 + } + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{BB97BA3E-F2AB-4078-9BB3-2A0F31DC771C}" + } + } + } + }, + { + "Key": { + "id": 20257134846444 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1040.0, + 320.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{71AB4748-41F4-4FEA-874C-F54037236F31}" + } + } + } + }, + { + "Key": { + "id": 20261429813740 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "StringNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 740.0, + -100.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{26E363EE-F35A-4096-88EF-DF907A809894}" + } + } + } + }, + { + "Key": { + "id": 20265724781036 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "MathNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1040.0, + 520.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{F64E211C-8DDD-4223-9235-8DB802A017CB}" + } + } + } + }, + { + "Key": { + "id": 20270019748332 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "StringNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1480.0, + 320.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{9161B9FA-8493-479D-BE35-10D92644D5C0}" + } + } + } + }, + { + "Key": { + "id": 20274314715628 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "StringNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 1480.0, + 520.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{1AF129A6-751C-4FC0-805E-65AC2D4B6D3D}" + } + } + } + }, + { + "Key": { + "id": 20278609682924 + }, + "Value": { + "ComponentData": { + "{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": { + "$type": "NodeSaveData" + }, + "{328FF15C-C302-458F-A43D-E1794DE0904E}": { + "$type": "GeneralNodeTitleComponentSaveData", + "PaletteOverride": "StringNodeTitlePalette" + }, + "{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": { + "$type": "GeometrySaveData", + "Position": [ + 740.0, + 740.0 + ] + }, + "{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": { + "$type": "StylingComponentSaveData" + }, + "{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": { + "$type": "PersistentIdComponentSaveData", + "PersistentId": "{F7407B9B-C302-4B50-BFB0-D1208DF1D5AC}" + } + } + } + } + ], + "StatisticsHelper": { + "InstanceCounter": [ + { + "Key": 5842116704436214676, + "Value": 1 + }, + { + "Key": 5842116706017748280, + "Value": 1 + }, + { + "Key": 7441100700879769985, + "Value": 2 + }, + { + "Key": 10684225535275896474, + "Value": 4 + }, + { + "Key": 10715014621082578046, + "Value": 1 + }, + { + "Key": 14285852892804039565, + "Value": 1 + } + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/Player.prefab b/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/Player.prefab new file mode 100644 index 0000000000..72fce2d291 --- /dev/null +++ b/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/Player.prefab @@ -0,0 +1,196 @@ +{ + "ContainerEntity": { + "Id": "ContainerEntity", + "Name": "Player", + "Components": { + "Component_[10591405285626521927]": { + "$type": "EditorLockComponent", + "Id": 10591405285626521927 + }, + "Component_[10962884071806037909]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 10962884071806037909, + "Parent Entity": "" + }, + "Component_[14883697413991420474]": { + "$type": "EditorOnlyEntityComponent", + "Id": 14883697413991420474 + }, + "Component_[1497622121956209837]": { + "$type": "EditorVisibilityComponent", + "Id": 1497622121956209837 + }, + "Component_[16429314387772079347]": { + "$type": "EditorEntityIconComponent", + "Id": 16429314387772079347 + }, + "Component_[16665294301093657382]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 16665294301093657382 + }, + "Component_[1706666252612720326]": { + "$type": "EditorInspectorComponent", + "Id": 1706666252612720326 + }, + "Component_[4216896820422195198]": { + "$type": "EditorPendingCompositionComponent", + "Id": 4216896820422195198 + }, + "Component_[4540089401187370610]": { + "$type": "EditorPrefabComponent", + "Id": 4540089401187370610 + }, + "Component_[6378576046601184103]": { + "$type": "EditorEntitySortComponent", + "Id": 6378576046601184103 + }, + "Component_[7745420981568587180]": { + "$type": "SelectionComponent", + "Id": 7745420981568587180 + } + } + }, + "Entities": { + "Entity_[1028733630164]": { + "Id": "Entity_[1028733630164]", + "Name": "Player", + "Components": { + "Component_[12294726333564087591]": { + "$type": "SelectionComponent", + "Id": 12294726333564087591 + }, + "Component_[13587084088242540786]": { + "$type": "EditorInspectorComponent", + "Id": 13587084088242540786, + "ComponentOrderEntryArray": [ + { + "ComponentId": 6819443882832501114 + }, + { + "ComponentId": 5577505593558922067, + "SortIndex": 1 + }, + { + "ComponentId": 2069554278758260821, + "SortIndex": 2 + }, + { + "ComponentId": 16508969730014660362, + "SortIndex": 3 + }, + { + "ComponentId": 8125406152674415588, + "SortIndex": 4 + }, + { + "ComponentId": 4337571454344109612, + "SortIndex": 5 + }, + { + "ComponentId": 16457408099527309065, + "SortIndex": 6 + } + ] + }, + "Component_[14335168881008289852]": { + "$type": "EditorEntitySortComponent", + "Id": 14335168881008289852 + }, + "Component_[16308902899170829847]": { + "$type": "EditorVisibilityComponent", + "Id": 16308902899170829847 + }, + "Component_[16457408099527309065]": { + "$type": "GenericComponentWrapper", + "Id": 16457408099527309065, + "m_template": { + "$type": "Multiplayer::NetworkTransformComponent" + } + }, + "Component_[16508969730014660362]": { + "$type": "GenericComponentWrapper", + "Id": 16508969730014660362, + "m_template": { + "$type": "AutomatedTesting::NetworkTestPlayerComponent" + } + }, + "Component_[16541569566865026527]": { + "$type": "EditorOnlyEntityComponent", + "Id": 16541569566865026527 + }, + "Component_[2002761223483048905]": { + "$type": "EditorPendingCompositionComponent", + "Id": 2002761223483048905 + }, + "Component_[2069554278758260821]": { + "$type": "EditorScriptCanvasComponent", + "Id": 2069554278758260821, + "m_name": "AutoComponent_NetworkInput", + "m_assetHolder": { + "m_asset": { + "assetId": { + "guid": "{D079F53D-CCAA-5C98-8E0C-B485B7821747}" + }, + "assetHint": "levels/multiplayer/autocomponent_networkinput/autocomponent_networkinput.scriptcanvas" + } + }, + "runtimeDataIsValid": true, + "runtimeDataOverrides": { + "source": { + "assetId": { + "guid": "{D079F53D-CCAA-5C98-8E0C-B485B7821747}" + }, + "assetHint": "levels/multiplayer/autocomponent_networkinput/autocomponent_networkinput.scriptcanvas" + } + } + }, + "Component_[4337571454344109612]": { + "$type": "GenericComponentWrapper", + "Id": 4337571454344109612, + "m_template": { + "$type": "NetBindComponent" + } + }, + "Component_[477591477979440744]": { + "$type": "EditorLockComponent", + "Id": 477591477979440744 + }, + "Component_[5577505593558922067]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 5577505593558922067, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{6DE0E9A8-A1C7-5D0F-9407-4E627C1F223C}", + "subId": 284780167 + }, + "assetHint": "models/sphere.azmodel" + } + } + } + }, + "Component_[5828214869455694702]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 5828214869455694702 + }, + "Component_[6819443882832501114]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 6819443882832501114, + "Parent Entity": "ContainerEntity" + }, + "Component_[8125406152674415588]": { + "$type": "GenericComponentWrapper", + "Id": 8125406152674415588, + "m_template": { + "$type": "Multiplayer::LocalPredictionPlayerInputComponent" + } + }, + "Component_[8838623765985560328]": { + "$type": "EditorEntityIconComponent", + "Id": 8838623765985560328 + } + } + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/tags.txt b/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/tags.txt new file mode 100644 index 0000000000..0d6c1880e7 --- /dev/null +++ b/AutomatedTesting/Levels/Multiplayer/AutoComponent_NetworkInput/tags.txt @@ -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 diff --git a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/LevelData/Environment.xml b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/LevelData/Environment.xml deleted file mode 100644 index 4ba36f66ae..0000000000 --- a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/LevelData/Environment.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/LevelData/TerrainTexture.xml b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/LevelData/TerrainTexture.xml deleted file mode 100644 index f43df05b22..0000000000 --- a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/LevelData/TerrainTexture.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/LevelData/TimeOfDay.xml b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/LevelData/TimeOfDay.xml deleted file mode 100644 index 6ea168cc6b..0000000000 --- a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/LevelData/TimeOfDay.xml +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/LevelData/VegetationMap.dat b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/LevelData/VegetationMap.dat deleted file mode 100644 index dce5631cd0..0000000000 --- a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/LevelData/VegetationMap.dat +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0e6a5435c928079b27796f6b202bbc2623e7e454244ddc099a3cadf33b7cb9e9 -size 63 diff --git a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/NvCloth_AddClothSimulationToActor.ly b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/NvCloth_AddClothSimulationToActor.ly deleted file mode 100644 index 23397bf18d..0000000000 --- a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/NvCloth_AddClothSimulationToActor.ly +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b59cbe84cb77090d723d120597f9d11817aa67a267a7f495f8b012fdd8a9dd86 -size 5536 diff --git a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/NvCloth_AddClothSimulationToActor.prefab b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/NvCloth_AddClothSimulationToActor.prefab new file mode 100644 index 0000000000..de23da874f --- /dev/null +++ b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/NvCloth_AddClothSimulationToActor.prefab @@ -0,0 +1,387 @@ +{ + "ContainerEntity": { + "Id": "Entity_[1146574390643]", + "Name": "Level", + "Components": { + "Component_[10641544592923449938]": { + "$type": "EditorInspectorComponent", + "Id": 10641544592923449938 + }, + "Component_[12039882709170782873]": { + "$type": "EditorOnlyEntityComponent", + "Id": 12039882709170782873 + }, + "Component_[12265484671603697631]": { + "$type": "EditorPendingCompositionComponent", + "Id": 12265484671603697631 + }, + "Component_[14126657869720434043]": { + "$type": "EditorEntitySortComponent", + "Id": 14126657869720434043, + "Child Entity Order": [ + "Entity_[1176639161715]", + "Instance_[1015201222663]/ContainerEntity" + ] + }, + "Component_[15230859088967841193]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 15230859088967841193, + "Parent Entity": "" + }, + "Component_[16239496886950819870]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 16239496886950819870 + }, + "Component_[5688118765544765547]": { + "$type": "EditorEntityIconComponent", + "Id": 5688118765544765547 + }, + "Component_[6545738857812235305]": { + "$type": "SelectionComponent", + "Id": 6545738857812235305 + }, + "Component_[7247035804068349658]": { + "$type": "EditorPrefabComponent", + "Id": 7247035804068349658 + }, + "Component_[9307224322037797205]": { + "$type": "EditorLockComponent", + "Id": 9307224322037797205 + }, + "Component_[9562516168917670048]": { + "$type": "EditorVisibilityComponent", + "Id": 9562516168917670048 + } + } + }, + "Entities": { + "Entity_[1155164325235]": { + "Id": "Entity_[1155164325235]", + "Name": "Sun", + "Components": { + "Component_[10440557478882592717]": { + "$type": "SelectionComponent", + "Id": 10440557478882592717 + }, + "Component_[13620450453324765907]": { + "$type": "EditorLockComponent", + "Id": 13620450453324765907 + }, + "Component_[2134313378593666258]": { + "$type": "EditorInspectorComponent", + "Id": 2134313378593666258 + }, + "Component_[234010807770404186]": { + "$type": "EditorVisibilityComponent", + "Id": 234010807770404186 + }, + "Component_[2970359110423865725]": { + "$type": "EditorEntityIconComponent", + "Id": 2970359110423865725 + }, + "Component_[3722854130373041803]": { + "$type": "EditorOnlyEntityComponent", + "Id": 3722854130373041803 + }, + "Component_[5992533738676323195]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 5992533738676323195 + }, + "Component_[7378860763541895402]": { + "$type": "AZ::Render::EditorDirectionalLightComponent", + "Id": 7378860763541895402, + "Controller": { + "Configuration": { + "Intensity": 1.0, + "CameraEntityId": "", + "ShadowFilterMethod": 1 + } + } + }, + "Component_[7892834440890947578]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7892834440890947578, + "Parent Entity": "Entity_[1176639161715]", + "Transform Data": { + "Translate": [ + 0.0, + 0.0, + 13.487043380737305 + ], + "Rotate": [ + -76.13099670410156, + -0.847000002861023, + -15.8100004196167 + ] + } + }, + "Component_[8599729549570828259]": { + "$type": "EditorEntitySortComponent", + "Id": 8599729549570828259 + }, + "Component_[952797371922080273]": { + "$type": "EditorPendingCompositionComponent", + "Id": 952797371922080273 + } + } + }, + "Entity_[1163754259827]": { + "Id": "Entity_[1163754259827]", + "Name": "Camera", + "Components": { + "Component_[11895140916889160460]": { + "$type": "EditorEntityIconComponent", + "Id": 11895140916889160460 + }, + "Component_[16880285896855930892]": { + "$type": "{CA11DA46-29FF-4083-B5F6-E02C3A8C3A3D} EditorCameraComponent", + "Id": 16880285896855930892, + "Controller": { + "Configuration": { + "Field of View": 55.0, + "EditorEntityId": 8929576024571800510 + } + } + }, + "Component_[17187464423780271193]": { + "$type": "EditorLockComponent", + "Id": 17187464423780271193 + }, + "Component_[17495696818315413311]": { + "$type": "EditorEntitySortComponent", + "Id": 17495696818315413311 + }, + "Component_[18086214374043522055]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 18086214374043522055, + "Parent Entity": "Entity_[1176639161715]", + "Transform Data": { + "Translate": [ + 1.511600136756897, + -1.3604341745376587, + 1.412430763244629 + ], + "Rotate": [ + -7.442450523376465, + -6.665996551513672, + 41.62498092651367 + ] + } + }, + "Component_[18387556550380114975]": { + "$type": "SelectionComponent", + "Id": 18387556550380114975 + }, + "Component_[2654521436129313160]": { + "$type": "EditorVisibilityComponent", + "Id": 2654521436129313160 + }, + "Component_[5265045084611556958]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 5265045084611556958 + }, + "Component_[7169798125182238623]": { + "$type": "EditorPendingCompositionComponent", + "Id": 7169798125182238623 + }, + "Component_[7255796294953281766]": { + "$type": "GenericComponentWrapper", + "Id": 7255796294953281766, + "m_template": { + "$type": "FlyCameraInputComponent" + } + }, + "Component_[8866210352157164042]": { + "$type": "EditorInspectorComponent", + "Id": 8866210352157164042 + }, + "Component_[9129253381063760879]": { + "$type": "EditorOnlyEntityComponent", + "Id": 9129253381063760879 + } + } + }, + "Entity_[1176639161715]": { + "Id": "Entity_[1176639161715]", + "Name": "Atom Default Environment", + "Components": { + "Component_[10757302973393310045]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 10757302973393310045, + "Parent Entity": "Entity_[1146574390643]" + }, + "Component_[14505817420424255464]": { + "$type": "EditorInspectorComponent", + "Id": 14505817420424255464, + "ComponentOrderEntryArray": [ + { + "ComponentId": 10757302973393310045 + } + ] + }, + "Component_[14988041764659020032]": { + "$type": "EditorLockComponent", + "Id": 14988041764659020032 + }, + "Component_[15808690248755038124]": { + "$type": "SelectionComponent", + "Id": 15808690248755038124 + }, + "Component_[15900837685796817138]": { + "$type": "EditorVisibilityComponent", + "Id": 15900837685796817138 + }, + "Component_[3298767348226484884]": { + "$type": "EditorOnlyEntityComponent", + "Id": 3298767348226484884 + }, + "Component_[4076975109609220594]": { + "$type": "EditorPendingCompositionComponent", + "Id": 4076975109609220594 + }, + "Component_[5679760548946028854]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 5679760548946028854 + }, + "Component_[5855590796136709437]": { + "$type": "EditorEntitySortComponent", + "Id": 5855590796136709437, + "Child Entity Order": [ + "Entity_[1155164325235]", + "Entity_[1180934129011]", + "Entity_[1163754259827]" + ] + }, + "Component_[9277695270015777859]": { + "$type": "EditorEntityIconComponent", + "Id": 9277695270015777859 + } + } + }, + "Entity_[1180934129011]": { + "Id": "Entity_[1180934129011]", + "Name": "Global Sky", + "Components": { + "Component_[11231930600558681245]": { + "$type": "AZ::Render::EditorHDRiSkyboxComponent", + "Id": 11231930600558681245, + "Controller": { + "Configuration": { + "CubemapAsset": { + "assetId": { + "guid": "{215E47FD-D181-5832-B1AB-91673ABF6399}", + "subId": 1000 + }, + "assetHint": "lightingpresets/highcontrast/goegap_4k_skyboxcm.exr.streamingimage" + } + } + } + }, + "Component_[11980494120202836095]": { + "$type": "SelectionComponent", + "Id": 11980494120202836095 + }, + "Component_[1428633914413949476]": { + "$type": "EditorLockComponent", + "Id": 1428633914413949476 + }, + "Component_[14936200426671614999]": { + "$type": "AZ::Render::EditorImageBasedLightComponent", + "Id": 14936200426671614999, + "Controller": { + "Configuration": { + "diffuseImageAsset": { + "assetId": { + "guid": "{3FD09945-D0F2-55C8-B9AF-B2FD421FE3BE}", + "subId": 3000 + }, + "assetHint": "lightingpresets/highcontrast/goegap_4k_iblglobalcm_ibldiffuse.exr.streamingimage" + }, + "specularImageAsset": { + "assetId": { + "guid": "{3FD09945-D0F2-55C8-B9AF-B2FD421FE3BE}", + "subId": 2000 + }, + "assetHint": "lightingpresets/highcontrast/goegap_4k_iblglobalcm_iblspecular.exr.streamingimage" + } + } + } + }, + "Component_[14994774102579326069]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 14994774102579326069 + }, + "Component_[15417479889044493340]": { + "$type": "EditorPendingCompositionComponent", + "Id": 15417479889044493340 + }, + "Component_[15826613364991382688]": { + "$type": "EditorEntitySortComponent", + "Id": 15826613364991382688 + }, + "Component_[1665003113283562343]": { + "$type": "EditorOnlyEntityComponent", + "Id": 1665003113283562343 + }, + "Component_[3704934735944502280]": { + "$type": "EditorEntityIconComponent", + "Id": 3704934735944502280 + }, + "Component_[5698542331457326479]": { + "$type": "EditorVisibilityComponent", + "Id": 5698542331457326479 + }, + "Component_[6644513399057217122]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 6644513399057217122, + "Parent Entity": "Entity_[1176639161715]" + }, + "Component_[931091830724002070]": { + "$type": "EditorInspectorComponent", + "Id": 931091830724002070 + } + } + } + }, + "Instances": { + "Instance_[1015201222663]": { + "Source": "prefabs/Cloth/Chicken_Actor.prefab", + "Patches": [ + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Parent Entity", + "value": "../Entity_[1146574390643]" + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Translate/0", + "value": 0.31251561641693115 + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Translate/1", + "value": -0.006644248962402344 + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Translate/2", + "value": 0.3271239995956421 + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Rotate/2", + "value": 179.72178649902344 + }, + { + "op": "add", + "path": "/ContainerEntity/Components/Component_[7874177159288365422]/Child Entity Order/0", + "value": "Entity_[303447173544404]" + }, + { + "op": "remove", + "path": "/LinkId" + } + ] + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/TerrainTexture.pak b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/TerrainTexture.pak deleted file mode 100644 index fe3604a050..0000000000 --- a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/TerrainTexture.pak +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8739c76e681f900923b900c9df0ef75cf421d39cabb54650c4b9ad19b6a76d85 -size 22 diff --git a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/filelist.xml b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/filelist.xml deleted file mode 100644 index 20952a47ce..0000000000 --- a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/filelist.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/level.pak b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/level.pak deleted file mode 100644 index 4259131c4f..0000000000 --- a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToActor/level.pak +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:46051f4116003e1a2d13855bea92a1b15501166b1379a11d02c4d2239ccd2530 -size 3648 diff --git a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/LevelData/Environment.xml b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/LevelData/Environment.xml deleted file mode 100644 index 4ba36f66ae..0000000000 --- a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/LevelData/Environment.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/LevelData/TerrainTexture.xml b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/LevelData/TerrainTexture.xml deleted file mode 100644 index f43df05b22..0000000000 --- a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/LevelData/TerrainTexture.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/LevelData/TimeOfDay.xml b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/LevelData/TimeOfDay.xml deleted file mode 100644 index 6ea168cc6b..0000000000 --- a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/LevelData/TimeOfDay.xml +++ /dev/null @@ -1,356 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/LevelData/VegetationMap.dat b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/LevelData/VegetationMap.dat deleted file mode 100644 index dce5631cd0..0000000000 --- a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/LevelData/VegetationMap.dat +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0e6a5435c928079b27796f6b202bbc2623e7e454244ddc099a3cadf33b7cb9e9 -size 63 diff --git a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/NvCloth_AddClothSimulationToMesh.ly b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/NvCloth_AddClothSimulationToMesh.ly deleted file mode 100644 index 861626993e..0000000000 --- a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/NvCloth_AddClothSimulationToMesh.ly +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bedd2adc60f244a8595e64619069046d5036dd762f61b5393f9b759d69281362 -size 5276 diff --git a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/NvCloth_AddClothSimulationToMesh.prefab b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/NvCloth_AddClothSimulationToMesh.prefab new file mode 100644 index 0000000000..93d0d1e707 --- /dev/null +++ b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/NvCloth_AddClothSimulationToMesh.prefab @@ -0,0 +1,522 @@ +{ + "ContainerEntity": { + "Id": "Entity_[1146574390643]", + "Name": "Level", + "Components": { + "Component_[10641544592923449938]": { + "$type": "EditorInspectorComponent", + "Id": 10641544592923449938 + }, + "Component_[12039882709170782873]": { + "$type": "EditorOnlyEntityComponent", + "Id": 12039882709170782873 + }, + "Component_[12265484671603697631]": { + "$type": "EditorPendingCompositionComponent", + "Id": 12265484671603697631 + }, + "Component_[14126657869720434043]": { + "$type": "EditorEntitySortComponent", + "Id": 14126657869720434043, + "Child Entity Order": [ + "Entity_[1176639161715]", + "Instance_[811083782986]/ContainerEntity", + "Instance_[503204883039]/ContainerEntity", + "Instance_[563334425183]/ContainerEntity", + "Instance_[640643836511]/ContainerEntity", + "Instance_[735133117023]/ContainerEntity" + ] + }, + "Component_[15230859088967841193]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 15230859088967841193, + "Parent Entity": "" + }, + "Component_[16239496886950819870]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 16239496886950819870 + }, + "Component_[5688118765544765547]": { + "$type": "EditorEntityIconComponent", + "Id": 5688118765544765547 + }, + "Component_[6545738857812235305]": { + "$type": "SelectionComponent", + "Id": 6545738857812235305 + }, + "Component_[7247035804068349658]": { + "$type": "EditorPrefabComponent", + "Id": 7247035804068349658 + }, + "Component_[9307224322037797205]": { + "$type": "EditorLockComponent", + "Id": 9307224322037797205 + }, + "Component_[9562516168917670048]": { + "$type": "EditorVisibilityComponent", + "Id": 9562516168917670048 + } + } + }, + "Entities": { + "Entity_[1155164325235]": { + "Id": "Entity_[1155164325235]", + "Name": "Sun", + "Components": { + "Component_[10440557478882592717]": { + "$type": "SelectionComponent", + "Id": 10440557478882592717 + }, + "Component_[13620450453324765907]": { + "$type": "EditorLockComponent", + "Id": 13620450453324765907 + }, + "Component_[2134313378593666258]": { + "$type": "EditorInspectorComponent", + "Id": 2134313378593666258 + }, + "Component_[234010807770404186]": { + "$type": "EditorVisibilityComponent", + "Id": 234010807770404186 + }, + "Component_[2970359110423865725]": { + "$type": "EditorEntityIconComponent", + "Id": 2970359110423865725 + }, + "Component_[3722854130373041803]": { + "$type": "EditorOnlyEntityComponent", + "Id": 3722854130373041803 + }, + "Component_[5992533738676323195]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 5992533738676323195 + }, + "Component_[7378860763541895402]": { + "$type": "AZ::Render::EditorDirectionalLightComponent", + "Id": 7378860763541895402, + "Controller": { + "Configuration": { + "Intensity": 1.0, + "CameraEntityId": "", + "ShadowFilterMethod": 1 + } + } + }, + "Component_[7892834440890947578]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7892834440890947578, + "Parent Entity": "Entity_[1176639161715]", + "Transform Data": { + "Translate": [ + 0.0, + 0.0, + 13.487043380737305 + ], + "Rotate": [ + -76.13099670410156, + -0.847000002861023, + -15.8100004196167 + ] + } + }, + "Component_[8599729549570828259]": { + "$type": "EditorEntitySortComponent", + "Id": 8599729549570828259 + }, + "Component_[952797371922080273]": { + "$type": "EditorPendingCompositionComponent", + "Id": 952797371922080273 + } + } + }, + "Entity_[1163754259827]": { + "Id": "Entity_[1163754259827]", + "Name": "Camera", + "Components": { + "Component_[11895140916889160460]": { + "$type": "EditorEntityIconComponent", + "Id": 11895140916889160460 + }, + "Component_[16880285896855930892]": { + "$type": "{CA11DA46-29FF-4083-B5F6-E02C3A8C3A3D} EditorCameraComponent", + "Id": 16880285896855930892, + "Controller": { + "Configuration": { + "Field of View": 55.0, + "EditorEntityId": 8929576024571800510 + } + } + }, + "Component_[17187464423780271193]": { + "$type": "EditorLockComponent", + "Id": 17187464423780271193 + }, + "Component_[17495696818315413311]": { + "$type": "EditorEntitySortComponent", + "Id": 17495696818315413311 + }, + "Component_[18086214374043522055]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 18086214374043522055, + "Parent Entity": "Entity_[1176639161715]", + "Transform Data": { + "Translate": [ + 3.288902521133423, + -3.0976791381835938, + 1.595407247543335 + ], + "Rotate": [ + -6.36656379699707, + -6.542551040649414, + 45.600582122802734 + ] + } + }, + "Component_[18387556550380114975]": { + "$type": "SelectionComponent", + "Id": 18387556550380114975 + }, + "Component_[2654521436129313160]": { + "$type": "EditorVisibilityComponent", + "Id": 2654521436129313160 + }, + "Component_[5265045084611556958]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 5265045084611556958 + }, + "Component_[7169798125182238623]": { + "$type": "EditorPendingCompositionComponent", + "Id": 7169798125182238623 + }, + "Component_[7255796294953281766]": { + "$type": "GenericComponentWrapper", + "Id": 7255796294953281766, + "m_template": { + "$type": "FlyCameraInputComponent" + } + }, + "Component_[8866210352157164042]": { + "$type": "EditorInspectorComponent", + "Id": 8866210352157164042 + }, + "Component_[9129253381063760879]": { + "$type": "EditorOnlyEntityComponent", + "Id": 9129253381063760879 + } + } + }, + "Entity_[1176639161715]": { + "Id": "Entity_[1176639161715]", + "Name": "Atom Default Environment", + "Components": { + "Component_[10757302973393310045]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 10757302973393310045, + "Parent Entity": "Entity_[1146574390643]" + }, + "Component_[14505817420424255464]": { + "$type": "EditorInspectorComponent", + "Id": 14505817420424255464, + "ComponentOrderEntryArray": [ + { + "ComponentId": 10757302973393310045 + } + ] + }, + "Component_[14988041764659020032]": { + "$type": "EditorLockComponent", + "Id": 14988041764659020032 + }, + "Component_[15808690248755038124]": { + "$type": "SelectionComponent", + "Id": 15808690248755038124 + }, + "Component_[15900837685796817138]": { + "$type": "EditorVisibilityComponent", + "Id": 15900837685796817138 + }, + "Component_[3298767348226484884]": { + "$type": "EditorOnlyEntityComponent", + "Id": 3298767348226484884 + }, + "Component_[4076975109609220594]": { + "$type": "EditorPendingCompositionComponent", + "Id": 4076975109609220594 + }, + "Component_[5679760548946028854]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 5679760548946028854 + }, + "Component_[5855590796136709437]": { + "$type": "EditorEntitySortComponent", + "Id": 5855590796136709437, + "Child Entity Order": [ + "Entity_[1155164325235]", + "Entity_[1180934129011]", + "Entity_[1163754259827]" + ] + }, + "Component_[9277695270015777859]": { + "$type": "EditorEntityIconComponent", + "Id": 9277695270015777859 + } + } + }, + "Entity_[1180934129011]": { + "Id": "Entity_[1180934129011]", + "Name": "Global Sky", + "Components": { + "Component_[11231930600558681245]": { + "$type": "AZ::Render::EditorHDRiSkyboxComponent", + "Id": 11231930600558681245, + "Controller": { + "Configuration": { + "CubemapAsset": { + "assetId": { + "guid": "{215E47FD-D181-5832-B1AB-91673ABF6399}", + "subId": 1000 + }, + "assetHint": "lightingpresets/highcontrast/goegap_4k_skyboxcm.exr.streamingimage" + } + } + } + }, + "Component_[11980494120202836095]": { + "$type": "SelectionComponent", + "Id": 11980494120202836095 + }, + "Component_[1428633914413949476]": { + "$type": "EditorLockComponent", + "Id": 1428633914413949476 + }, + "Component_[14936200426671614999]": { + "$type": "AZ::Render::EditorImageBasedLightComponent", + "Id": 14936200426671614999, + "Controller": { + "Configuration": { + "diffuseImageAsset": { + "assetId": { + "guid": "{3FD09945-D0F2-55C8-B9AF-B2FD421FE3BE}", + "subId": 3000 + }, + "assetHint": "lightingpresets/highcontrast/goegap_4k_iblglobalcm_ibldiffuse.exr.streamingimage" + }, + "specularImageAsset": { + "assetId": { + "guid": "{3FD09945-D0F2-55C8-B9AF-B2FD421FE3BE}", + "subId": 2000 + }, + "assetHint": "lightingpresets/highcontrast/goegap_4k_iblglobalcm_iblspecular.exr.streamingimage" + } + } + } + }, + "Component_[14994774102579326069]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 14994774102579326069 + }, + "Component_[15417479889044493340]": { + "$type": "EditorPendingCompositionComponent", + "Id": 15417479889044493340 + }, + "Component_[15826613364991382688]": { + "$type": "EditorEntitySortComponent", + "Id": 15826613364991382688 + }, + "Component_[1665003113283562343]": { + "$type": "EditorOnlyEntityComponent", + "Id": 1665003113283562343 + }, + "Component_[3704934735944502280]": { + "$type": "EditorEntityIconComponent", + "Id": 3704934735944502280 + }, + "Component_[5698542331457326479]": { + "$type": "EditorVisibilityComponent", + "Id": 5698542331457326479 + }, + "Component_[6644513399057217122]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 6644513399057217122, + "Parent Entity": "Entity_[1176639161715]" + }, + "Component_[931091830724002070]": { + "$type": "EditorInspectorComponent", + "Id": 931091830724002070 + } + } + } + }, + "Instances": { + "Instance_[503204883039]": { + "Source": "prefabs/Cloth/cloth_blinds_broken.prefab", + "Patches": [ + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Parent Entity", + "value": "../Entity_[1146574390643]" + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Translate/0", + "value": -3.9779629707336426 + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Translate/1", + "value": -0.7587795257568359 + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Translate/2", + "value": 1.0752365589141846 + }, + { + "op": "add", + "path": "/ContainerEntity/Components/Component_[7874177159288365422]/Child Entity Order/0", + "value": "Entity_[303326914460116]" + }, + { + "op": "remove", + "path": "/LinkId" + } + ] + }, + "Instance_[563334425183]": { + "Source": "prefabs/Cloth/cloth_locked_corners_four.prefab", + "Patches": [ + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Parent Entity", + "value": "../Entity_[1146574390643]" + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Translate/0", + "value": -4.94368839263916 + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Translate/1", + "value": 0.805694580078125 + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Translate/2", + "value": 2.2616283893585205 + }, + { + "op": "add", + "path": "/ContainerEntity/Components/Component_[7874177159288365422]/Child Entity Order/0", + "value": "Entity_[303417108773332]" + }, + { + "op": "remove", + "path": "/LinkId" + } + ] + }, + "Instance_[640643836511]": { + "Source": "prefabs/Cloth/cloth_locked_corners_two.prefab", + "Patches": [ + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Parent Entity", + "value": "../Entity_[1146574390643]" + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Translate/0", + "value": -2.416099786758423 + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Translate/1", + "value": 2.790005683898926 + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Translate/2", + "value": 2.1456499099731445 + }, + { + "op": "add", + "path": "/ContainerEntity/Components/Component_[7874177159288365422]/Child Entity Order/0", + "value": "Entity_[303387044002260]" + }, + { + "op": "remove", + "path": "/LinkId" + } + ] + }, + "Instance_[735133117023]": { + "Source": "prefabs/Cloth/cloth_locked_edge.prefab", + "Patches": [ + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Parent Entity", + "value": "../Entity_[1146574390643]" + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Translate/0", + "value": -1.9405040740966797 + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Translate/1", + "value": -0.8027572631835938 + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Translate/2", + "value": 0.2776646614074707 + }, + { + "op": "add", + "path": "/ContainerEntity/Components/Component_[7874177159288365422]/Child Entity Order/0", + "value": "Entity_[303356979231188]" + }, + { + "op": "remove", + "path": "/LinkId" + } + ] + }, + "Instance_[811083782986]": { + "Source": "prefabs/Cloth/cloth_blinds.prefab", + "Patches": [ + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Parent Entity", + "value": "../Entity_[1146574390643]" + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Translate/0", + "value": 0.6851601600646973 + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Translate/1", + "value": 0.1960926055908203 + }, + { + "op": "replace", + "path": "/ContainerEntity/Components/Component_[4272963378099646759]/Transform Data/Translate/2", + "value": 0.3339226245880127 + }, + { + "op": "add", + "path": "/ContainerEntity/Components/Component_[7874177159288365422]/Child Entity Order/0", + "value": "Entity_[303275374852564]" + }, + { + "op": "remove", + "path": "/LinkId" + } + ] + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/TerrainTexture.pak b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/TerrainTexture.pak deleted file mode 100644 index fe3604a050..0000000000 --- a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/TerrainTexture.pak +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8739c76e681f900923b900c9df0ef75cf421d39cabb54650c4b9ad19b6a76d85 -size 22 diff --git a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/filelist.xml b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/filelist.xml deleted file mode 100644 index d3492ca7b6..0000000000 --- a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/filelist.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/level.pak b/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/level.pak deleted file mode 100644 index 7fa23ea67f..0000000000 --- a/AutomatedTesting/Levels/NvCloth/NvCloth_AddClothSimulationToMesh/level.pak +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:81fc98854424d55e594a3983da53d2f5a4d7a7cf60e52e12366e4800d1d3f080 -size 38559 diff --git a/AutomatedTesting/Levels/Sponza/Sponza.prefab b/AutomatedTesting/Levels/Sponza/Sponza.prefab new file mode 100644 index 0000000000..d755eb9774 --- /dev/null +++ b/AutomatedTesting/Levels/Sponza/Sponza.prefab @@ -0,0 +1,1489 @@ +{ + "ContainerEntity": { + "Id": "Entity_[406217483857]", + "Name": "Level", + "Components": { + "Component_[10588931505759123943]": { + "$type": "EditorVisibilityComponent", + "Id": 10588931505759123943 + }, + "Component_[135321489898029517]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 135321489898029517 + }, + "Component_[14858507413812498857]": { + "$type": "EditorEntityIconComponent", + "Id": 14858507413812498857 + }, + "Component_[15178816766766638692]": { + "$type": "EditorInspectorComponent", + "Id": 15178816766766638692 + }, + "Component_[17951702430591084334]": { + "$type": "EditorPendingCompositionComponent", + "Id": 17951702430591084334 + }, + "Component_[2563280132145207190]": { + "$type": "EditorLockComponent", + "Id": 2563280132145207190 + }, + "Component_[2822556265044965634]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2822556265044965634 + }, + "Component_[446057404408737487]": { + "$type": "EditorPrefabComponent", + "Id": 446057404408737487 + }, + "Component_[5683088399314452447]": { + "$type": "SelectionComponent", + "Id": 5683088399314452447 + }, + "Component_[5801776107759571453]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 5801776107759571453, + "Parent Entity": "" + }, + "Component_[9426929613394548724]": { + "$type": "EditorEntitySortComponent", + "Id": 9426929613394548724, + "ChildEntityOrderEntryArray": [ + { + "EntityId": "Entity_[1081216208506]" + }, + { + "EntityId": "Entity_[935187320442]", + "SortIndex": 1 + }, + { + "EntityId": "Entity_[935187320442]", + "SortIndex": 2 + }, + { + "EntityId": "Entity_[935187320442]", + "SortIndex": 3 + }, + { + "EntityId": "Entity_[935187320442]", + "SortIndex": 4 + }, + { + "EntityId": "Entity_[935187320442]", + "SortIndex": 5 + } + ] + } + } + }, + "Entities": { + "Entity_[1081216208506]": { + "Id": "Entity_[1081216208506]", + "Name": "LIGHTING", + "Components": { + "Component_[10807484710436476353]": { + "$type": "EditorInspectorComponent", + "Id": 10807484710436476353 + }, + "Component_[12535994014200063229]": { + "$type": "EditorVisibilityComponent", + "Id": 12535994014200063229 + }, + "Component_[12567916482013107382]": { + "$type": "SelectionComponent", + "Id": 12567916482013107382 + }, + "Component_[15060801524064219165]": { + "$type": "EditorOnlyEntityComponent", + "Id": 15060801524064219165 + }, + "Component_[1597296709936823974]": { + "$type": "EditorEntityIconComponent", + "Id": 1597296709936823974 + }, + "Component_[17535943354307721937]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 17535943354307721937 + }, + "Component_[3144604952797148227]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 3144604952797148227, + "Parent Entity": "Entity_[406217483857]", + "Transform Data": { + "Translate": [ + -0.7899999022483826, + -10.387897491455078, + 8.160000801086426 + ] + } + }, + "Component_[3567054724563794528]": { + "$type": "EditorPendingCompositionComponent", + "Id": 3567054724563794528 + }, + "Component_[8097342669405778862]": { + "$type": "EditorEntitySortComponent", + "Id": 8097342669405778862, + "ChildEntityOrderEntryArray": [ + { + "EntityId": "Entity_[1613792153210]" + }, + { + "EntityId": "Entity_[1119870914170]", + "SortIndex": 1 + }, + { + "EntityId": "Entity_[1115575946874]", + "SortIndex": 2 + }, + { + "EntityId": "Entity_[1111280979578]", + "SortIndex": 3 + }, + { + "EntityId": "Entity_[1085511175802]", + "SortIndex": 4 + }, + { + "EntityId": "Entity_[1102691044986]", + "SortIndex": 5 + }, + { + "EntityId": "Entity_[1098396077690]", + "SortIndex": 6 + }, + { + "EntityId": "Entity_[1094101110394]", + "SortIndex": 7 + }, + { + "EntityId": "Entity_[1085511175802]", + "SortIndex": 8 + }, + { + "EntityId": "Entity_[1106986012282]", + "SortIndex": 8 + }, + { + "EntityId": "Entity_[1085511175802]", + "SortIndex": 7 + }, + { + "EntityId": "Entity_[1089806143098]", + "SortIndex": 6 + }, + { + "EntityId": "Entity_[1085511175802]", + "SortIndex": 5 + }, + { + "EntityId": "Entity_[1085511175802]", + "SortIndex": 4 + }, + { + "EntityId": "Entity_[1085511175802]", + "SortIndex": 3 + }, + { + "EntityId": "Entity_[1085511175802]", + "SortIndex": 2 + }, + { + "EntityId": "Entity_[1085511175802]", + "SortIndex": 1 + }, + { + "EntityId": "Entity_[1085511175802]", + "SortIndex": 17 + } + ] + }, + "Component_[9064866490989218404]": { + "$type": "EditorLockComponent", + "Id": 9064866490989218404 + } + } + }, + "Entity_[1085511175802]": { + "Id": "Entity_[1085511175802]", + "Name": "ReflectionProbe_UpperLevel", + "Components": { + "Component_[1038637709631818218]": { + "$type": "EditorEntitySortComponent", + "Id": 1038637709631818218 + }, + "Component_[13993242205578133374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 13993242205578133374 + }, + "Component_[14164886745537962894]": { + "$type": "EditorPendingCompositionComponent", + "Id": 14164886745537962894 + }, + "Component_[15336954334699691810]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 15336954334699691810 + }, + "Component_[17468041844277100837]": { + "$type": "EditorVisibilityComponent", + "Id": 17468041844277100837 + }, + "Component_[18107599364473376156]": { + "$type": "AZ::Render::EditorReflectionProbeComponent", + "Id": 18107599364473376156, + "Controller": { + "Configuration": { + "OuterHeight": 3.0, + "OuterLength": 3.0, + "OuterWidth": 1.0, + "InnerHeight": 2.75, + "InnerLength": 2.75, + "InnerWidth": 0.75, + "BakedCubemapQualityLevel": 3, + "BakedCubeMapRelativePath": "ReflectionProbes/ReflectionProbe_UpperLevel__4DABA7BF-9367-4D95-AA5F-A9BF6BA0F7BE__iblspecularcm512.dds", + "BakedCubeMapAsset": { + "assetId": { + "guid": "{7854FC84-FDCE-5C02-AC9C-DBD6894F52F4}", + "subId": 2000 + }, + "assetHint": "reflectionprobes/reflectionprobe_upperlevel__4daba7bf-9367-4d95-aa5f-a9bf6ba0f7be__iblspecularcm512.dds.streamingimage" + }, + "EntityId": 14568425243222190542, + "ShowVisualization": false + } + }, + "bakedCubeMapQualityLevel": 3, + "bakedCubeMapRelativePath": "ReflectionProbes/ReflectionProbe_UpperLevel__4DABA7BF-9367-4D95-AA5F-A9BF6BA0F7BE__iblspecularcm512.dds" + }, + "Component_[320343109587065620]": { + "$type": "EditorInspectorComponent", + "Id": 320343109587065620 + }, + "Component_[3267511667303906499]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 3267511667303906499, + "Parent Entity": "Entity_[1081216208506]", + "Transform Data": { + "Translate": [ + -13.363113403320313, + 10.723356246948242, + -1.8831419944763184 + ] + } + }, + "Component_[4634887848675078464]": { + "$type": "EditorLockComponent", + "Id": 4634887848675078464 + }, + "Component_[4783209159709094238]": { + "$type": "SelectionComponent", + "Id": 4783209159709094238 + }, + "Component_[728315922784530894]": { + "$type": "EditorEntityIconComponent", + "Id": 728315922784530894 + }, + "Component_[8999082608038997636]": { + "$type": "EditorAxisAlignedBoxShapeComponent", + "Id": 8999082608038997636, + "DisplayFilled": false, + "AxisAlignedBoxShape": { + "Configuration": { + "IsFilled": false, + "Dimensions": [ + 1.0, + 3.0, + 3.0 + ] + } + } + } + } + }, + "Entity_[1089806143098]": { + "Id": "Entity_[1089806143098]", + "Name": "ReflectionProbe_Lion", + "Components": { + "Component_[14298922893146648725]": { + "$type": "EditorAxisAlignedBoxShapeComponent", + "Id": 14298922893146648725, + "Visible": false, + "DisplayFilled": false, + "AxisAlignedBoxShape": { + "Configuration": { + "IsFilled": false, + "Dimensions": [ + 3.0, + 4.0, + 4.0 + ] + } + } + }, + "Component_[1455674266703651422]": { + "$type": "EditorEntitySortComponent", + "Id": 1455674266703651422 + }, + "Component_[14636684755738306181]": { + "$type": "AZ::Render::EditorReflectionProbeComponent", + "Id": 14636684755738306181, + "Controller": { + "Configuration": { + "OuterHeight": 4.0, + "OuterLength": 4.0, + "OuterWidth": 3.0, + "InnerHeight": 3.0, + "InnerLength": 3.0, + "InnerWidth": 2.0, + "BakedCubemapQualityLevel": 3, + "BakedCubeMapRelativePath": "ReflectionProbes/ReflectionProbe_Lion__CA20BB89-1587-4410-80BA-8150CB0EF47B__iblspecularcm512.dds", + "BakedCubeMapAsset": { + "assetId": { + "guid": "{AC2EF073-63B8-53C2-8178-9B80D2D7E56D}", + "subId": 2000 + }, + "assetHint": "reflectionprobes/reflectionprobe_lion__ca20bb89-1587-4410-80ba-8150cb0ef47b__iblspecularcm512.dds.streamingimage" + }, + "EntityId": 15188201575897363858, + "ShowVisualization": false + } + }, + "bakedCubeMapQualityLevel": 3, + "bakedCubeMapRelativePath": "ReflectionProbes/ReflectionProbe_Lion__CA20BB89-1587-4410-80BA-8150CB0EF47B__iblspecularcm512.dds" + }, + "Component_[1558055592965824024]": { + "$type": "EditorInspectorComponent", + "Id": 1558055592965824024 + }, + "Component_[161549568296074325]": { + "$type": "EditorOnlyEntityComponent", + "Id": 161549568296074325 + }, + "Component_[16573539057585083382]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 16573539057585083382 + }, + "Component_[17523994278098812420]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 17523994278098812420, + "Parent Entity": "Entity_[1081216208506]", + "Transform Data": { + "Translate": [ + -13.342336654663086, + 10.449432373046875, + -6.156347274780273 + ] + } + }, + "Component_[1895148655574754151]": { + "$type": "EditorVisibilityComponent", + "Id": 1895148655574754151 + }, + "Component_[2659020665645761255]": { + "$type": "EditorPendingCompositionComponent", + "Id": 2659020665645761255 + }, + "Component_[3707403743603935171]": { + "$type": "EditorLockComponent", + "Id": 3707403743603935171 + }, + "Component_[5008186835003860730]": { + "$type": "EditorEntityIconComponent", + "Id": 5008186835003860730 + }, + "Component_[7632822173429043662]": { + "$type": "SelectionComponent", + "Id": 7632822173429043662 + } + } + }, + "Entity_[1094101110394]": { + "Id": "Entity_[1094101110394]", + "Name": "ReflectionProbe_Scene", + "Components": { + "Component_[1084328120591698803]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 1084328120591698803, + "Parent Entity": "Entity_[1081216208506]", + "Transform Data": { + "Translate": [ + 0.0, + 10.77357292175293, + -2.114013671875 + ] + } + }, + "Component_[11927919033815991882]": { + "$type": "EditorOnlyEntityComponent", + "Id": 11927919033815991882 + }, + "Component_[12922039245522655341]": { + "$type": "EditorPendingCompositionComponent", + "Id": 12922039245522655341 + }, + "Component_[13830787916768787880]": { + "$type": "EditorEntityIconComponent", + "Id": 13830787916768787880 + }, + "Component_[13864356435863998063]": { + "$type": "EditorEntitySortComponent", + "Id": 13864356435863998063 + }, + "Component_[14618987283145709626]": { + "$type": "EditorLockComponent", + "Id": 14618987283145709626 + }, + "Component_[14690766657349179909]": { + "$type": "AZ::Render::EditorReflectionProbeComponent", + "Id": 14690766657349179909, + "Controller": { + "Configuration": { + "OuterHeight": 17.0, + "OuterLength": 25.0, + "OuterWidth": 35.0, + "BakedCubeMapRelativePath": "ReflectionProbes/ReflectionProbe_Scene__D245F488-152E-4A3A-898B-FBCF71E4A87A__iblspecularcm256.dds", + "BakedCubeMapAsset": { + "assetId": { + "guid": "{E94B628B-9E61-5DD4-8AFD-200DD7540CAD}", + "subId": 2000 + }, + "assetHint": "reflectionprobes/reflectionprobe_scene__d245f488-152e-4a3a-898b-fbcf71e4a87a__iblspecularcm256.dds.streamingimage" + }, + "EntityId": 18057698412353009297, + "ShowVisualization": false + } + }, + "bakedCubeMapRelativePath": "ReflectionProbes/ReflectionProbe_Scene__D245F488-152E-4A3A-898B-FBCF71E4A87A__iblspecularcm256.dds" + }, + "Component_[16801122362190390827]": { + "$type": "EditorVisibilityComponent", + "Id": 16801122362190390827 + }, + "Component_[1897972008443666285]": { + "$type": "SelectionComponent", + "Id": 1897972008443666285 + }, + "Component_[2961811751273549376]": { + "$type": "EditorBoxShapeComponent", + "Id": 2961811751273549376, + "DisplayFilled": false, + "BoxShape": { + "Configuration": { + "IsFilled": false, + "Dimensions": [ + 35.0, + 25.0, + 17.0 + ] + } + } + }, + "Component_[879709407962202982]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 879709407962202982 + }, + "Component_[9324834207126061548]": { + "$type": "EditorInspectorComponent", + "Id": 9324834207126061548 + } + } + }, + "Entity_[1098396077690]": { + "Id": "Entity_[1098396077690]", + "Name": "PointLight_02", + "Components": { + "Component_[10534705633537717631]": { + "$type": "EditorEntityIconComponent", + "Id": 10534705633537717631 + }, + "Component_[10552323305936140565]": { + "$type": "EditorInspectorComponent", + "Id": 10552323305936140565 + }, + "Component_[13058374277331580766]": { + "$type": "SelectionComponent", + "Id": 13058374277331580766 + }, + "Component_[14949512479506290054]": { + "$type": "EditorPendingCompositionComponent", + "Id": 14949512479506290054 + }, + "Component_[1548752089356963562]": { + "$type": "AZ::Render::EditorAreaLightComponent", + "Id": 1548752089356963562, + "Controller": { + "Configuration": { + "LightType": 1, + "IntensityMode": 4, + "Intensity": 15.359000205993652, + "AttenuationRadius": 72.0053482055664, + "Enable Shadow": true + } + } + }, + "Component_[15792288291507192351]": { + "$type": "EditorLockComponent", + "Id": 15792288291507192351 + }, + "Component_[17205110032543434616]": { + "$type": "EditorOnlyEntityComponent", + "Id": 17205110032543434616 + }, + "Component_[2030794600614672812]": { + "$type": "EditorVisibilityComponent", + "Id": 2030794600614672812 + }, + "Component_[2978368743586258871]": { + "$type": "EditorEntitySortComponent", + "Id": 2978368743586258871 + }, + "Component_[4291094216513321822]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 4291094216513321822, + "Parent Entity": "Entity_[1081216208506]", + "Transform Data": { + "Translate": [ + 2.581982135772705, + 10.840080261230469, + -0.9913702011108398 + ] + } + }, + "Component_[4611947863173505]": { + "$type": "EditorSphereShapeComponent", + "Id": 4611947863173505, + "ShapeColor": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "SphereShape": { + "Configuration": { + "Radius": 0.05000000074505806 + } + } + }, + "Component_[7416869202661318055]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7416869202661318055 + } + } + }, + "Entity_[1102691044986]": { + "Id": "Entity_[1102691044986]", + "Name": "PointLight_01", + "Components": { + "Component_[11365983295692083092]": { + "$type": "EditorPendingCompositionComponent", + "Id": 11365983295692083092 + }, + "Component_[11475917125854123708]": { + "$type": "EditorEntitySortComponent", + "Id": 11475917125854123708 + }, + "Component_[14430205535050778968]": { + "$type": "EditorOnlyEntityComponent", + "Id": 14430205535050778968 + }, + "Component_[1466324423527856074]": { + "$type": "EditorEntityIconComponent", + "Id": 1466324423527856074 + }, + "Component_[15554484541906022976]": { + "$type": "EditorVisibilityComponent", + "Id": 15554484541906022976 + }, + "Component_[15619369680404016454]": { + "$type": "EditorLockComponent", + "Id": 15619369680404016454 + }, + "Component_[2289960019728880406]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 2289960019728880406 + }, + "Component_[2537457439377723385]": { + "$type": "SelectionComponent", + "Id": 2537457439377723385 + }, + "Component_[3297556825839117286]": { + "$type": "EditorSphereShapeComponent", + "Id": 3297556825839117286, + "ShapeColor": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "SphereShape": { + "Configuration": { + "Radius": 0.05000000074505806 + } + } + }, + "Component_[6893246775558579859]": { + "$type": "EditorInspectorComponent", + "Id": 6893246775558579859 + }, + "Component_[776705866357873394]": { + "$type": "AZ::Render::EditorAreaLightComponent", + "Id": 776705866357873394, + "Controller": { + "Configuration": { + "LightType": 1, + "IntensityMode": 4, + "Intensity": 15.84000015258789, + "AttenuationRadius": 85.0672607421875 + } + } + }, + "Component_[9324553387194905409]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 9324553387194905409, + "Parent Entity": "Entity_[1081216208506]", + "Transform Data": { + "Translate": [ + -2.2817187309265137, + 10.581533432006836, + 7.5597944259643555 + ] + } + } + } + }, + "Entity_[1106986012282]": { + "Id": "Entity_[1106986012282]", + "Name": "EnvironmentLight", + "Components": { + "Component_[10422300770865106323]": { + "$type": "EditorVisibilityComponent", + "Id": 10422300770865106323 + }, + "Component_[15319816472229733542]": { + "$type": "EditorPendingCompositionComponent", + "Id": 15319816472229733542 + }, + "Component_[16299889256965403184]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 16299889256965403184, + "Parent Entity": "Entity_[1081216208506]" + }, + "Component_[16493827350222098538]": { + "$type": "AZ::Render::EditorHDRiSkyboxComponent", + "Id": 16493827350222098538, + "Controller": { + "Configuration": { + "CubemapAsset": { + "assetId": { + "guid": "{B78C84E9-45BE-5A50-8898-177B33B8DA84}", + "subId": 2000 + }, + "assetHint": "envhdri/photo_studio_01_4k_iblskyboxcm_iblspecular.exr.streamingimage" + } + } + } + }, + "Component_[16719262120090148450]": { + "$type": "EditorEntityIconComponent", + "Id": 16719262120090148450 + }, + "Component_[16793670898160667741]": { + "$type": "EditorInspectorComponent", + "Id": 16793670898160667741 + }, + "Component_[18257477315946306250]": { + "$type": "AZ::Render::EditorImageBasedLightComponent", + "Id": 18257477315946306250, + "Controller": { + "Configuration": { + "diffuseImageAsset": { + "assetId": { + "guid": "{B78C84E9-45BE-5A50-8898-177B33B8DA84}", + "subId": 3000 + }, + "assetHint": "envhdri/photo_studio_01_4k_iblskyboxcm_ibldiffuse.exr.streamingimage" + }, + "specularImageAsset": { + "assetId": { + "guid": "{B78C84E9-45BE-5A50-8898-177B33B8DA84}", + "subId": 2000 + }, + "assetHint": "envhdri/photo_studio_01_4k_iblskyboxcm_iblspecular.exr.streamingimage" + } + } + } + }, + "Component_[4042063241506989985]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 4042063241506989985 + }, + "Component_[4043099982625130775]": { + "$type": "EditorOnlyEntityComponent", + "Id": 4043099982625130775 + }, + "Component_[5787212845085960464]": { + "$type": "SelectionComponent", + "Id": 5787212845085960464 + }, + "Component_[7736270313311953442]": { + "$type": "EditorLockComponent", + "Id": 7736270313311953442 + }, + "Component_[9830936955971178632]": { + "$type": "EditorEntitySortComponent", + "Id": 9830936955971178632 + } + } + }, + "Entity_[1111280979578]": { + "Id": "Entity_[1111280979578]", + "Name": "DirectionalLight_01", + "Components": { + "Component_[10555827406016705179]": { + "$type": "AZ::Render::EditorDirectionalLightComponent", + "Id": 10555827406016705179, + "Controller": { + "Configuration": { + "Intensity": 2.700000047683716, + "CameraEntityId": "", + "ShadowmapSize": "Size2048", + "ShadowFilterMethod": 3 + } + } + }, + "Component_[10958643723292926203]": { + "$type": "EditorEntitySortComponent", + "Id": 10958643723292926203 + }, + "Component_[11446130315244380400]": { + "$type": "EditorInspectorComponent", + "Id": 11446130315244380400 + }, + "Component_[13236945310915796893]": { + "$type": "EditorVisibilityComponent", + "Id": 13236945310915796893 + }, + "Component_[1489806191674568397]": { + "$type": "EditorPendingCompositionComponent", + "Id": 1489806191674568397 + }, + "Component_[5250190904313425540]": { + "$type": "EditorOnlyEntityComponent", + "Id": 5250190904313425540 + }, + "Component_[6777228833367576785]": { + "$type": "EditorEntityIconComponent", + "Id": 6777228833367576785 + }, + "Component_[7346763132239097919]": { + "$type": "SelectionComponent", + "Id": 7346763132239097919 + }, + "Component_[7453645446520457790]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7453645446520457790, + "Parent Entity": "Entity_[1081216208506]", + "Transform Data": { + "Translate": [ + 2.5331368446350098, + 10.560922622680664, + -5.415566921234131 + ], + "Rotate": [ + -103.25949096679688, + -1.1917028427124023, + 9.752097129821777 + ] + } + }, + "Component_[7712714857312986803]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7712714857312986803 + }, + "Component_[9180065208679807523]": { + "$type": "EditorLockComponent", + "Id": 9180065208679807523 + } + } + }, + "Entity_[1115575946874]": { + "Id": "Entity_[1115575946874]", + "Name": "DiffuseGI_02", + "Components": { + "Component_[10817704949085277229]": { + "$type": "EditorLockComponent", + "Id": 10817704949085277229 + }, + "Component_[11636460237576493708]": { + "$type": "EditorEntityIconComponent", + "Id": 11636460237576493708 + }, + "Component_[16774608348754201693]": { + "$type": "EditorEntitySortComponent", + "Id": 16774608348754201693 + }, + "Component_[17994425379406278407]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 17994425379406278407, + "Parent Entity": "Entity_[1081216208506]", + "Transform Data": { + "Translate": [ + 8.965616226196289, + 10.854515075683594, + -1.710240364074707 + ] + } + }, + "Component_[3002179259651230640]": { + "$type": "AZ::Render::EditorDiffuseProbeGridComponent", + "Id": 3002179259651230640, + "Controller": { + "Configuration": { + "Extents": [ + 21.0, + 23.0, + 16.0 + ], + "BakedIrradianceTextureRelativePath": "DiffuseProbeGrids/DiffuseGI_02_A78EEAF4-7CB2-4635-AA6F-2ED677328706_Irradiance_lutrgba16.dds", + "BakedDistanceTextureRelativePath": "DiffuseProbeGrids/DiffuseGI_02_84D0F8A4-AD4F-4FD1-BA26-4803EAD88FE2_Distance_lutrg32f.dds", + "BakedRelocationTextureRelativePath": "DiffuseProbeGrids/DiffuseGI_02_9DC8C208-5327-4F0F-B1DF-C98F7F81F07D_Relocation_lutrgba16f.dds", + "BakedClassificationTextureRelativePath": "DiffuseProbeGrids/DiffuseGI_02_222F1F65-BF31-4E0D-9957-14AE73194A37_Classification_lutr32f.dds", + "BakedIrradianceTextureAsset": { + "assetId": { + "guid": "{5B66D8B5-E4AE-5571-A551-1D21333519A0}", + "subId": 1000 + }, + "assetHint": "diffuseprobegrids/diffusegi_02_a78eeaf4-7cb2-4635-aa6f-2ed677328706_irradiance_lutrgba16.dds.streamingimage" + }, + "BakedDistanceTextureAsset": { + "assetId": { + "guid": "{B75364AF-D0AC-5E8C-A3B0-EBAFB063D6C3}", + "subId": 1000 + }, + "assetHint": "diffuseprobegrids/diffusegi_02_84d0f8a4-ad4f-4fd1-ba26-4803ead88fe2_distance_lutrg32f.dds.streamingimage" + }, + "BakedRelocationTextureAsset": { + "assetId": { + "guid": "{CB803781-B18A-51CE-AF8C-DF24FC48160B}", + "subId": 1000 + }, + "assetHint": "diffuseprobegrids/diffusegi_02_9dc8c208-5327-4f0f-b1df-c98f7f81f07d_relocation_lutrgba16f.dds.streamingimage" + }, + "BakedClassificationTextureAsset": { + "assetId": { + "guid": "{D4B4F7E6-3B39-58BE-B8E9-7D057EF9A1F9}", + "subId": 1000 + }, + "assetHint": "diffuseprobegrids/diffusegi_02_222f1f65-bf31-4e0d-9957-14ae73194a37_classification_lutr32f.dds.streamingimage" + } + } + } + }, + "Component_[4937167128415130244]": { + "$type": "EditorVisibilityComponent", + "Id": 4937167128415130244 + }, + "Component_[544440683292345740]": { + "$type": "EditorAxisAlignedBoxShapeComponent", + "Id": 544440683292345740, + "DisplayFilled": false, + "AxisAlignedBoxShape": { + "Configuration": { + "IsFilled": false, + "Dimensions": [ + 21.0, + 23.0, + 16.0 + ] + } + } + }, + "Component_[578950453122763358]": { + "$type": "EditorInspectorComponent", + "Id": 578950453122763358 + }, + "Component_[6950664630097923667]": { + "$type": "EditorOnlyEntityComponent", + "Id": 6950664630097923667 + }, + "Component_[7122880078686807527]": { + "$type": "SelectionComponent", + "Id": 7122880078686807527 + }, + "Component_[800320972776069167]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 800320972776069167 + }, + "Component_[8383621918310482787]": { + "$type": "EditorPendingCompositionComponent", + "Id": 8383621918310482787 + } + } + }, + "Entity_[1119870914170]": { + "Id": "Entity_[1119870914170]", + "Name": "DiffuseGI_01", + "Components": { + "Component_[12209861770686976090]": { + "$type": "EditorEntityIconComponent", + "Id": 12209861770686976090 + }, + "Component_[15353835329112294192]": { + "$type": "AZ::Render::EditorDiffuseProbeGridComponent", + "Id": 15353835329112294192, + "Controller": { + "Configuration": { + "Extents": [ + 21.0, + 23.0, + 16.0 + ], + "BakedIrradianceTextureRelativePath": "DiffuseProbeGrids/DiffuseGI_01_1B66C428-1D7C-4A53-BC9B-6F23E420FEC0_Irradiance_lutrgba16.dds", + "BakedDistanceTextureRelativePath": "DiffuseProbeGrids/DiffuseGI_01_5A2C9A6B-F914-4D9E-8098-2AD411B69F87_Distance_lutrg32f.dds", + "BakedRelocationTextureRelativePath": "DiffuseProbeGrids/DiffuseGI_01_52D75B1C-90BE-40A8-A874-3376F6B39299_Relocation_lutrgba16f.dds", + "BakedClassificationTextureRelativePath": "DiffuseProbeGrids/DiffuseGI_01_A483018F-78ED-4814-986F-F925B0AA5EF8_Classification_lutr32f.dds", + "BakedIrradianceTextureAsset": { + "assetId": { + "guid": "{458A2A6C-6DEA-5D69-99B5-49A4F227C0A8}", + "subId": 1000 + }, + "assetHint": "diffuseprobegrids/diffusegi_01_1b66c428-1d7c-4a53-bc9b-6f23e420fec0_irradiance_lutrgba16.dds.streamingimage" + }, + "BakedDistanceTextureAsset": { + "assetId": { + "guid": "{9CB9A440-9BB1-56CA-B988-35FCF667F993}", + "subId": 1000 + }, + "assetHint": "diffuseprobegrids/diffusegi_01_5a2c9a6b-f914-4d9e-8098-2ad411b69f87_distance_lutrg32f.dds.streamingimage" + }, + "BakedRelocationTextureAsset": { + "assetId": { + "guid": "{DD6EB8D0-B68D-5AB8-B781-8E702F57C07D}", + "subId": 1000 + }, + "assetHint": "diffuseprobegrids/diffusegi_01_52d75b1c-90be-40a8-a874-3376f6b39299_relocation_lutrgba16f.dds.streamingimage" + }, + "BakedClassificationTextureAsset": { + "assetId": { + "guid": "{17D2178C-D102-5B11-B54C-A5EB233B570F}", + "subId": 1000 + }, + "assetHint": "diffuseprobegrids/diffusegi_01_a483018f-78ed-4814-986f-f925b0aa5ef8_classification_lutr32f.dds.streamingimage" + } + } + } + }, + "Component_[15878232015065363455]": { + "$type": "EditorAxisAlignedBoxShapeComponent", + "Id": 15878232015065363455, + "DisplayFilled": false, + "AxisAlignedBoxShape": { + "Configuration": { + "IsFilled": false, + "Dimensions": [ + 21.0, + 23.0, + 16.0 + ] + } + } + }, + "Component_[17684272660747914090]": { + "$type": "EditorVisibilityComponent", + "Id": 17684272660747914090 + }, + "Component_[6724702399933727508]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 6724702399933727508, + "Parent Entity": "Entity_[1081216208506]", + "Transform Data": { + "Translate": [ + -9.077760696411133, + 10.860031127929688, + -1.7297496795654297 + ] + } + }, + "Component_[6821316397695137380]": { + "$type": "EditorInspectorComponent", + "Id": 6821316397695137380 + }, + "Component_[7220674190610508127]": { + "$type": "SelectionComponent", + "Id": 7220674190610508127 + }, + "Component_[7255528083818713568]": { + "$type": "EditorPendingCompositionComponent", + "Id": 7255528083818713568 + }, + "Component_[7481484881559898788]": { + "$type": "EditorOnlyEntityComponent", + "Id": 7481484881559898788 + }, + "Component_[7524464908826669595]": { + "$type": "EditorLockComponent", + "Id": 7524464908826669595 + }, + "Component_[8325378114504743847]": { + "$type": "EditorEntitySortComponent", + "Id": 8325378114504743847 + }, + "Component_[997706187713882733]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 997706187713882733 + } + } + }, + "Entity_[1613792153210]": { + "Id": "Entity_[1613792153210]", + "Name": "Bloom", + "Components": { + "Component_[10907847005341580620]": { + "$type": "EditorEntitySortComponent", + "Id": 10907847005341580620 + }, + "Component_[12758150889799563454]": { + "$type": "SelectionComponent", + "Id": 12758150889799563454 + }, + "Component_[13247037123089245326]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 13247037123089245326 + }, + "Component_[14642483781584474817]": { + "$type": "EditorLockComponent", + "Id": 14642483781584474817 + }, + "Component_[17298230148657754532]": { + "$type": "AZ::Render::EditorBloomComponent", + "Id": 17298230148657754532, + "Controller": { + "Configuration": { + "Enabled": true, + "Intensity": 0.20000000298023224 + } + } + }, + "Component_[18357464390083641697]": { + "$type": "EditorEntityIconComponent", + "Id": 18357464390083641697 + }, + "Component_[2912267744881101414]": { + "$type": "EditorPendingCompositionComponent", + "Id": 2912267744881101414 + }, + "Component_[3110194465577429301]": { + "$type": "EditorInspectorComponent", + "Id": 3110194465577429301 + }, + "Component_[5583330531036127211]": { + "$type": "EditorVisibilityComponent", + "Id": 5583330531036127211 + }, + "Component_[6528482372370936011]": { + "$type": "EditorOnlyEntityComponent", + "Id": 6528482372370936011 + }, + "Component_[6944819497695081730]": { + "$type": "AZ::Render::EditorPostFxLayerComponent", + "Id": 6944819497695081730 + }, + "Component_[8401224278912231908]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 8401224278912231908, + "Parent Entity": "Entity_[1081216208506]", + "Transform Data": { + "Translate": [ + -9.137216567993164, + 8.613965034484863, + -6.547377586364746 + ] + } + } + } + }, + "Entity_[935187320442]": { + "Id": "Entity_[935187320442]", + "Name": "GEO_Sponza", + "Components": { + "Component_[10397152383962473889]": { + "$type": "EditorEntityIconComponent", + "Id": 10397152383962473889 + }, + "Component_[10403905564538530551]": { + "$type": "EditorEntitySortComponent", + "Id": 10403905564538530551 + }, + "Component_[1212647033037288333]": { + "$type": "SelectionComponent", + "Id": 1212647033037288333 + }, + "Component_[13446466918421510411]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13446466918421510411 + }, + "Component_[14976154143417033372]": { + "$type": "EditorMaterialComponent", + "Id": 14976154143417033372, + "Controller": { + "Configuration": { + "materials": [ + { + "Key": { + "materialSlotStableId": 333178618 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{BE4DBB1D-16BA-5D82-9865-58904BAFD500}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_columna.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 338114910 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{CE74A12B-9D17-5C84-8118-9F071E61AF82}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_curtaingreen.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 775278732 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{31A90D34-8E36-5A69-AF65-CEC3776F4BD4}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_fabricblue.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 856448979 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{7EDE4FB5-7629-5EA7-942B-DF4404F7F1BF}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_lion.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 919776125 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{BA39AE42-222C-5965-868C-1A1D7ACD3B8B}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_arch.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 1207315403 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{0B8E109B-F295-544E-A360-193D435E9CE7}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_bricks.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 1802255958 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{31EDB842-C01F-53C3-BA5B-ABE47AE4202E}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_roof.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 2017475302 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{96768574-B7E8-5AB1-A1D6-41A1FA817749}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_vaseround.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 2070928957 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{5E3B1B14-DD87-56A9-AF68-58E17A98DAA4}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_vase.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 2207162385 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{5E8EF668-45AD-5ADA-99CB-37F0BD155282}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_vaseplant.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 2270338210 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{7D57D2FA-CC79-5A05-B89D-A821A69E06AA}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_fabricgreen.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 2309916823 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{66472C16-905D-58D3-A2FF-189D0D81A72C}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_fabricred.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 2590157792 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{B064CDF8-E63A-50FE-82C7-F73863C6BAE7}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_floor.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 2958866002 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{C4EF81AF-6924-51F9-B653-374FDEEF2DFF}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_background.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 3018304250 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{348EBEE6-9480-5284-8427-F3503D835C7A}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_details.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 3091600944 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{1D82B774-8649-53B8-89B8-38392145D0B3}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_columnb.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 3123792718 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{9ADCD89D-834D-5894-9829-81A938408C56}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_ceiling.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 3332811907 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{6DC9F913-16C6-58D4-9E62-7C1BD2F17BAA}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_flagpole.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 3597278613 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{CC0ED9DC-03DD-5BC3-97C7-30FCC53AB6D3}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_curtainred.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 3682637945 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{0E6CFBCB-8A02-5BF8-A7BF-F367D729E343}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_curtainblue.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 3761550797 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{8889EE6F-29FD-56D6-806A-77ED795F25E1}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_vasehanging.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 3856935901 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{B44690C5-30A3-5B6B-8785-E51111965AFC}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_columnc.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 3861299630 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{674F363F-C927-5977-8215-4B7E9D3D7E5A}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_leaf.azmaterial" + } + } + }, + { + "Key": { + "materialSlotStableId": 4043076162 + }, + "Value": { + "MaterialAsset": { + "assetId": { + "guid": "{73D2E54D-AA36-5EE3-818D-A627A014FE15}" + }, + "assetHint": "gem/sponza/assets/objects/sponza_mat_chain.azmaterial" + } + } + } + ] + } + } + }, + "Component_[17881751166570014482]": { + "$type": "EditorVisibilityComponent", + "Id": 17881751166570014482 + }, + "Component_[1890254654486049633]": { + "$type": "EditorLockComponent", + "Id": 1890254654486049633 + }, + "Component_[2265788496878584641]": { + "$type": "EditorInspectorComponent", + "Id": 2265788496878584641 + }, + "Component_[4234266794358802507]": { + "$type": "EditorOnlyEntityComponent", + "Id": 4234266794358802507 + }, + "Component_[7538889219751513244]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 7538889219751513244, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{0E91A4B4-9A13-56A1-8007-4F9594DFB8FC}", + "subId": 282066894 + }, + "assetHint": "gem/sponza/assets/objects/sponza.azmodel" + } + } + } + }, + "Component_[8795821630604550348]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 8795821630604550348 + }, + "Component_[972437945687475257]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 972437945687475257, + "Parent Entity": "Entity_[406217483857]" + } + } + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Levels/Sponza/tags.txt b/AutomatedTesting/Levels/Sponza/tags.txt new file mode 100644 index 0000000000..0d6c1880e7 --- /dev/null +++ b/AutomatedTesting/Levels/Sponza/tags.txt @@ -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 diff --git a/AutomatedTesting/Levels/macbeth_shaderballs/macbeth_shaderballs.prefab b/AutomatedTesting/Levels/macbeth_shaderballs/macbeth_shaderballs.prefab new file mode 100644 index 0000000000..22504f168a --- /dev/null +++ b/AutomatedTesting/Levels/macbeth_shaderballs/macbeth_shaderballs.prefab @@ -0,0 +1,3401 @@ +{ + "ContainerEntity": { + "Id": "ContainerEntity", + "Name": "macbeth_shaderballs", + "Components": { + "Component_[10182366347512475253]": { + "$type": "EditorPrefabComponent", + "Id": 10182366347512475253 + }, + "Component_[12917798267488243668]": { + "$type": "EditorPendingCompositionComponent", + "Id": 12917798267488243668 + }, + "Component_[3261249813163778338]": { + "$type": "EditorOnlyEntityComponent", + "Id": 3261249813163778338 + }, + "Component_[3837204912784440039]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 3837204912784440039 + }, + "Component_[4272963378099646759]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 4272963378099646759, + "Parent Entity": "" + }, + "Component_[4848458548047175816]": { + "$type": "EditorVisibilityComponent", + "Id": 4848458548047175816 + }, + "Component_[5787060997243919943]": { + "$type": "EditorInspectorComponent", + "Id": 5787060997243919943 + }, + "Component_[7804170251266531779]": { + "$type": "EditorLockComponent", + "Id": 7804170251266531779 + }, + "Component_[7874177159288365422]": { + "$type": "EditorEntitySortComponent", + "Id": 7874177159288365422 + }, + "Component_[8018146290632383969]": { + "$type": "EditorEntityIconComponent", + "Id": 8018146290632383969 + }, + "Component_[8452360690590857075]": { + "$type": "SelectionComponent", + "Id": 8452360690590857075 + } + } + }, + "Entities": { + "Entity_[471076350497]": { + "Id": "Entity_[471076350497]", + "Name": "WorldOrigin", + "Components": { + "Component_[10118378636607282023]": { + "$type": "AZ::Render::EditorImageBasedLightComponent", + "Id": 10118378636607282023, + "Controller": { + "Configuration": { + "diffuseImageAsset": { + "assetId": { + "guid": "{10853039-DC8A-558A-B27E-4433A6386731}", + "subId": 3000 + }, + "assetHint": "lightingpresets/lowcontrast/blouberg_sunrise_1_4k_iblskyboxcm_ibldiffuse.exr.streamingimage" + }, + "specularImageAsset": { + "assetId": { + "guid": "{10853039-DC8A-558A-B27E-4433A6386731}", + "subId": 2000 + }, + "assetHint": "lightingpresets/lowcontrast/blouberg_sunrise_1_4k_iblskyboxcm_iblspecular.exr.streamingimage" + }, + "exposure": 1.0 + } + } + }, + "Component_[10390989140659450689]": { + "$type": "EditorInspectorComponent", + "Id": 10390989140659450689, + "ComponentOrderEntryArray": [ + { + "ComponentId": 6066687697346848609 + }, + { + "ComponentId": 1538992203183232042, + "SortIndex": 1 + }, + { + "ComponentId": 10118378636607282023, + "SortIndex": 2 + } + ] + }, + "Component_[1122756123782465575]": { + "$type": "EditorLockComponent", + "Id": 1122756123782465575 + }, + "Component_[1411541685315998773]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 1411541685315998773 + }, + "Component_[1538992203183232042]": { + "$type": "AZ::Render::EditorGridComponent", + "Id": 1538992203183232042 + }, + "Component_[16871442125196328877]": { + "$type": "EditorEntitySortComponent", + "Id": 16871442125196328877, + "ChildEntityOrderEntryArray": [ + { + "EntityId": "Entity_[604220336673]" + }, + { + "EntityId": "Entity_[599925369377]", + "SortIndex": 1 + }, + { + "EntityId": "Entity_[475371317793]", + "SortIndex": 2 + }, + { + "EntityId": "Entity_[509731056161]", + "SortIndex": 3 + }, + { + "EntityId": "Entity_[505436088865]", + "SortIndex": 4 + }, + { + "EntityId": "Entity_[539795827233]", + "SortIndex": 5 + }, + { + "EntityId": "Entity_[569860598305]", + "SortIndex": 6 + } + ] + }, + "Component_[18389136819207633744]": { + "$type": "SelectionComponent", + "Id": 18389136819207633744 + }, + "Component_[2967708543517171475]": { + "$type": "EditorEntityIconComponent", + "Id": 2967708543517171475 + }, + "Component_[6066687697346848609]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 6066687697346848609, + "Parent Entity": "ContainerEntity" + }, + "Component_[7035058231756199033]": { + "$type": "EditorVisibilityComponent", + "Id": 7035058231756199033 + }, + "Component_[7861798362721154905]": { + "$type": "EditorOnlyEntityComponent", + "Id": 7861798362721154905 + }, + "Component_[8535986786667781968]": { + "$type": "EditorPendingCompositionComponent", + "Id": 8535986786667781968 + } + } + }, + "Entity_[475371317793]": { + "Id": "Entity_[475371317793]", + "Name": "00_Illuminant", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{29C7358C-9899-56DF-8F99-F654C7138DB8}" + }, + "assetHint": "materials/presets/macbeth/00_illuminant.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[471076350497]", + "Transform Data": { + "Translate": [ + -0.020035700872540474, + 10.880657196044922, + 1.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[479666285089]": { + "Id": "Entity_[479666285089]", + "Name": "09_moderate_red", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{FD3D09E1-9B20-5761-87A2-388ADD3C966A}" + }, + "assetHint": "materials/presets/macbeth/09_moderate_red.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[505436088865]", + "Transform Data": { + "Translate": [ + -2.113382339477539, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[483961252385]": { + "Id": "Entity_[483961252385]", + "Name": "08_purplish_blue", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{0478869F-5E19-5A5C-AA22-0D31972E83B7}" + }, + "assetHint": "materials/presets/macbeth/08_purplish_blue.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[505436088865]", + "Transform Data": { + "Translate": [ + -6.113382339477539, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[488256219681]": { + "Id": "Entity_[488256219681]", + "Name": "07_orange", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{3E414822-FF6A-5A79-BF1A-66F4C48C381D}" + }, + "assetHint": "materials/presets/macbeth/07_orange.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[505436088865]", + "Transform Data": { + "Translate": [ + -10.113382339477539, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[492551186977]": { + "Id": "Entity_[492551186977]", + "Name": "10_purple", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{6A0A0CBE-FE95-5732-B2A9-442ABAC6B3AA}" + }, + "assetHint": "materials/presets/macbeth/10_purple.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[505436088865]", + "Transform Data": { + "Translate": [ + 1.8866175413131714, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[496846154273]": { + "Id": "Entity_[496846154273]", + "Name": "11_yellowish_green", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{8D382D9F-D56E-523E-8372-C372B002B81D}" + }, + "assetHint": "materials/presets/macbeth/11_yellow_green.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[505436088865]", + "Transform Data": { + "Translate": [ + 5.886617660522461, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[501141121569]": { + "Id": "Entity_[501141121569]", + "Name": "12_orange_yellow", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{7C8D9C96-8D79-5AA5-9A1D-DE68760127D7}" + }, + "assetHint": "materials/presets/macbeth/12_orange_yellow.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[505436088865]", + "Transform Data": { + "Translate": [ + 9.886617660522461, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[505436088865]": { + "Id": "Entity_[505436088865]", + "Name": "Row", + "Components": { + "Component_[10247332857034196288]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 10247332857034196288 + }, + "Component_[1050259146293298025]": { + "$type": "EditorOnlyEntityComponent", + "Id": 1050259146293298025 + }, + "Component_[10963468433108777551]": { + "$type": "EditorInspectorComponent", + "Id": 10963468433108777551, + "ComponentOrderEntryArray": [ + { + "ComponentId": 5648156935684358836 + } + ] + }, + "Component_[11044618010943237536]": { + "$type": "EditorEntityIconComponent", + "Id": 11044618010943237536 + }, + "Component_[11056805018150955063]": { + "$type": "EditorEntitySortComponent", + "Id": 11056805018150955063, + "ChildEntityOrderEntryArray": [ + { + "EntityId": "Entity_[488256219681]" + }, + { + "EntityId": "Entity_[483961252385]", + "SortIndex": 1 + }, + { + "EntityId": "Entity_[479666285089]", + "SortIndex": 2 + }, + { + "EntityId": "Entity_[492551186977]", + "SortIndex": 3 + }, + { + "EntityId": "Entity_[496846154273]", + "SortIndex": 4 + }, + { + "EntityId": "Entity_[501141121569]", + "SortIndex": 5 + } + ] + }, + "Component_[11466054095979053511]": { + "$type": "EditorPendingCompositionComponent", + "Id": 11466054095979053511 + }, + "Component_[1364058654406679998]": { + "$type": "SelectionComponent", + "Id": 1364058654406679998 + }, + "Component_[1550934027474222562]": { + "$type": "EditorVisibilityComponent", + "Id": 1550934027474222562 + }, + "Component_[15938036103959223730]": { + "$type": "EditorLockComponent", + "Id": 15938036103959223730 + }, + "Component_[5648156935684358836]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 5648156935684358836, + "Parent Entity": "Entity_[471076350497]", + "Transform Data": { + "Translate": [ + 0.0, + 2.0, + 1.0 + ] + } + } + } + }, + "Entity_[509731056161]": { + "Id": "Entity_[509731056161]", + "Name": "Row", + "Components": { + "Component_[10247332857034196288]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 10247332857034196288 + }, + "Component_[1050259146293298025]": { + "$type": "EditorOnlyEntityComponent", + "Id": 1050259146293298025 + }, + "Component_[10963468433108777551]": { + "$type": "EditorInspectorComponent", + "Id": 10963468433108777551, + "ComponentOrderEntryArray": [ + { + "ComponentId": 5648156935684358836 + } + ] + }, + "Component_[11044618010943237536]": { + "$type": "EditorEntityIconComponent", + "Id": 11044618010943237536 + }, + "Component_[11056805018150955063]": { + "$type": "EditorEntitySortComponent", + "Id": 11056805018150955063, + "ChildEntityOrderEntryArray": [ + { + "EntityId": "Entity_[522615958049]" + }, + { + "EntityId": "Entity_[518320990753]", + "SortIndex": 1 + }, + { + "EntityId": "Entity_[514026023457]", + "SortIndex": 2 + }, + { + "EntityId": "Entity_[526910925345]", + "SortIndex": 3 + }, + { + "EntityId": "Entity_[531205892641]", + "SortIndex": 4 + }, + { + "EntityId": "Entity_[535500859937]", + "SortIndex": 5 + } + ] + }, + "Component_[11466054095979053511]": { + "$type": "EditorPendingCompositionComponent", + "Id": 11466054095979053511 + }, + "Component_[1364058654406679998]": { + "$type": "SelectionComponent", + "Id": 1364058654406679998 + }, + "Component_[1550934027474222562]": { + "$type": "EditorVisibilityComponent", + "Id": 1550934027474222562 + }, + "Component_[15938036103959223730]": { + "$type": "EditorLockComponent", + "Id": 15938036103959223730 + }, + "Component_[5648156935684358836]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 5648156935684358836, + "Parent Entity": "Entity_[471076350497]", + "Transform Data": { + "Translate": [ + 0.0, + 6.0, + 1.0 + ] + } + } + } + }, + "Entity_[514026023457]": { + "Id": "Entity_[514026023457]", + "Name": "03_blue_sky", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{65DF9715-8D50-5852-BDDF-345BF9A36AAF}" + }, + "assetHint": "materials/presets/macbeth/03_blue_sky.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[509731056161]", + "Transform Data": { + "Translate": [ + -2.113382339477539, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[518320990753]": { + "Id": "Entity_[518320990753]", + "Name": "02_light_skin", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{0B0603C9-E7C3-5166-98EC-F8B3A4D469FB}" + }, + "assetHint": "materials/presets/macbeth/02_light_skin.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[509731056161]", + "Transform Data": { + "Translate": [ + -6.113382339477539, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[522615958049]": { + "Id": "Entity_[522615958049]", + "Name": "01_dark_skin", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{73B6CE55-0766-51FD-8D9C-92C60862D270}" + }, + "assetHint": "materials/presets/macbeth/01_dark_skin.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[509731056161]", + "Transform Data": { + "Translate": [ + -10.113382339477539, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[526910925345]": { + "Id": "Entity_[526910925345]", + "Name": "04_foliage", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{C11C560D-F984-5836-928A-45CF96179862}" + }, + "assetHint": "materials/presets/macbeth/04_foliage.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[509731056161]", + "Transform Data": { + "Translate": [ + 1.8866175413131714, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[531205892641]": { + "Id": "Entity_[531205892641]", + "Name": "05_blue_flower", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{3326A6D9-FEA4-5CDE-AE4A-8BD28DF3A7CA}" + }, + "assetHint": "materials/presets/macbeth/05_blue_flower.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[509731056161]", + "Transform Data": { + "Translate": [ + 5.886617660522461, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[535500859937]": { + "Id": "Entity_[535500859937]", + "Name": "06_bluish_green", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{BD7A8B80-242E-50CC-900F-9001945C5A0C}" + }, + "assetHint": "materials/presets/macbeth/06_bluish_green.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[509731056161]", + "Transform Data": { + "Translate": [ + 9.886617660522461, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[539795827233]": { + "Id": "Entity_[539795827233]", + "Name": "Row", + "Components": { + "Component_[10247332857034196288]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 10247332857034196288 + }, + "Component_[1050259146293298025]": { + "$type": "EditorOnlyEntityComponent", + "Id": 1050259146293298025 + }, + "Component_[10963468433108777551]": { + "$type": "EditorInspectorComponent", + "Id": 10963468433108777551, + "ComponentOrderEntryArray": [ + { + "ComponentId": 5648156935684358836 + } + ] + }, + "Component_[11044618010943237536]": { + "$type": "EditorEntityIconComponent", + "Id": 11044618010943237536 + }, + "Component_[11056805018150955063]": { + "$type": "EditorEntitySortComponent", + "Id": 11056805018150955063, + "ChildEntityOrderEntryArray": [ + { + "EntityId": "Entity_[552680729121]" + }, + { + "EntityId": "Entity_[548385761825]", + "SortIndex": 1 + }, + { + "EntityId": "Entity_[544090794529]", + "SortIndex": 2 + }, + { + "EntityId": "Entity_[556975696417]", + "SortIndex": 3 + }, + { + "EntityId": "Entity_[561270663713]", + "SortIndex": 4 + }, + { + "EntityId": "Entity_[565565631009]", + "SortIndex": 5 + } + ] + }, + "Component_[11466054095979053511]": { + "$type": "EditorPendingCompositionComponent", + "Id": 11466054095979053511 + }, + "Component_[1364058654406679998]": { + "$type": "SelectionComponent", + "Id": 1364058654406679998 + }, + "Component_[1550934027474222562]": { + "$type": "EditorVisibilityComponent", + "Id": 1550934027474222562 + }, + "Component_[15938036103959223730]": { + "$type": "EditorLockComponent", + "Id": 15938036103959223730 + }, + "Component_[5648156935684358836]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 5648156935684358836, + "Parent Entity": "Entity_[471076350497]", + "Transform Data": { + "Translate": [ + 0.0, + -2.0, + 1.0 + ] + } + } + } + }, + "Entity_[544090794529]": { + "Id": "Entity_[544090794529]", + "Name": "15_red", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{9C47066E-BD8F-5C1B-B935-933296BBE312}" + }, + "assetHint": "materials/presets/macbeth/15_red.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[539795827233]", + "Transform Data": { + "Translate": [ + -2.113382339477539, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[548385761825]": { + "Id": "Entity_[548385761825]", + "Name": "14_green", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{82346ED7-D369-5EF0-A7E0-70C1082EE073}" + }, + "assetHint": "materials/presets/macbeth/14_green.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[539795827233]", + "Transform Data": { + "Translate": [ + -6.113382339477539, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[552680729121]": { + "Id": "Entity_[552680729121]", + "Name": "13_blue", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{B8972ADB-DBA9-5807-9742-2B14453FDD96}" + }, + "assetHint": "materials/presets/macbeth/13_blue.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[539795827233]", + "Transform Data": { + "Translate": [ + -10.113382339477539, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[556975696417]": { + "Id": "Entity_[556975696417]", + "Name": "16_yellow", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{099BB2A1-F76E-5B77-BCFD-B0A6249F0EA3}" + }, + "assetHint": "materials/presets/macbeth/16_yellow.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[539795827233]", + "Transform Data": { + "Translate": [ + 1.8866175413131714, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[561270663713]": { + "Id": "Entity_[561270663713]", + "Name": "17_magenta", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{2A83451E-0FE6-508E-BAA2-6142AAA53C42}" + }, + "assetHint": "materials/presets/macbeth/17_magenta.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[539795827233]", + "Transform Data": { + "Translate": [ + 5.886617660522461, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[565565631009]": { + "Id": "Entity_[565565631009]", + "Name": "18_cyan", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{6949B983-05D6-50A4-9D43-A6CDAB2BF3F5}" + }, + "assetHint": "materials/presets/macbeth/18_cyan.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[539795827233]", + "Transform Data": { + "Translate": [ + 9.886617660522461, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[569860598305]": { + "Id": "Entity_[569860598305]", + "Name": "Row", + "Components": { + "Component_[10247332857034196288]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 10247332857034196288 + }, + "Component_[1050259146293298025]": { + "$type": "EditorOnlyEntityComponent", + "Id": 1050259146293298025 + }, + "Component_[10963468433108777551]": { + "$type": "EditorInspectorComponent", + "Id": 10963468433108777551, + "ComponentOrderEntryArray": [ + { + "ComponentId": 5648156935684358836 + } + ] + }, + "Component_[11044618010943237536]": { + "$type": "EditorEntityIconComponent", + "Id": 11044618010943237536 + }, + "Component_[11056805018150955063]": { + "$type": "EditorEntitySortComponent", + "Id": 11056805018150955063, + "ChildEntityOrderEntryArray": [ + { + "EntityId": "Entity_[582745500193]" + }, + { + "EntityId": "Entity_[578450532897]", + "SortIndex": 1 + }, + { + "EntityId": "Entity_[574155565601]", + "SortIndex": 2 + }, + { + "EntityId": "Entity_[587040467489]", + "SortIndex": 3 + }, + { + "EntityId": "Entity_[591335434785]", + "SortIndex": 4 + }, + { + "EntityId": "Entity_[595630402081]", + "SortIndex": 5 + } + ] + }, + "Component_[11466054095979053511]": { + "$type": "EditorPendingCompositionComponent", + "Id": 11466054095979053511 + }, + "Component_[1364058654406679998]": { + "$type": "SelectionComponent", + "Id": 1364058654406679998 + }, + "Component_[1550934027474222562]": { + "$type": "EditorVisibilityComponent", + "Id": 1550934027474222562 + }, + "Component_[15938036103959223730]": { + "$type": "EditorLockComponent", + "Id": 15938036103959223730 + }, + "Component_[5648156935684358836]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 5648156935684358836, + "Parent Entity": "Entity_[471076350497]", + "Transform Data": { + "Translate": [ + 0.0, + -6.0, + 1.0 + ] + } + } + } + }, + "Entity_[574155565601]": { + "Id": "Entity_[574155565601]", + "Name": "21_neutral_6.5", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{ADAA8BF6-1580-5684-A7F5-4B0150117375}" + }, + "assetHint": "materials/presets/macbeth/21_neutral_6-5_0-44d.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[569860598305]", + "Transform Data": { + "Translate": [ + -2.113382339477539, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[578450532897]": { + "Id": "Entity_[578450532897]", + "Name": "20_neutral_8", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{A9BAEC06-A3F6-53E9-9E3E-61E12048FC75}" + }, + "assetHint": "materials/presets/macbeth/20_neutral_8-0_0-23d.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[569860598305]", + "Transform Data": { + "Translate": [ + -6.113382339477539, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[582745500193]": { + "Id": "Entity_[582745500193]", + "Name": "19_white_9.5", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{94E3052F-2B5A-5C28-912A-C0FDC00F5CD3}" + }, + "assetHint": "materials/presets/macbeth/19_white_9-5_0-05d.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[569860598305]", + "Transform Data": { + "Translate": [ + -10.113382339477539, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[587040467489]": { + "Id": "Entity_[587040467489]", + "Name": "22_neutral_5", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{1E45E15B-8035-5775-B796-A77654CDB094}" + }, + "assetHint": "materials/presets/macbeth/22_neutral_5-0_0-70d.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[569860598305]", + "Transform Data": { + "Translate": [ + 1.8866175413131714, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[591335434785]": { + "Id": "Entity_[591335434785]", + "Name": "23_neutral_3.5", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{23C26041-7155-5FE2-8E12-FACFD52DA006}" + }, + "assetHint": "materials/presets/macbeth/23_neutral_3-5_1-05d.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[569860598305]", + "Transform Data": { + "Translate": [ + 5.886617660522461, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[595630402081]": { + "Id": "Entity_[595630402081]", + "Name": "24_black_2", + "Components": { + "Component_[12222961627447331506]": { + "$type": "EditorMaterialComponent", + "Id": 12222961627447331506, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{1D83625A-4016-58F0-A94A-13B92B19F5B5}" + }, + "assetHint": "materials/presets/macbeth/24_black_2-0_1-50d.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[12780007764330464223]": { + "$type": "EditorVisibilityComponent", + "Id": 12780007764330464223 + }, + "Component_[12904863407657276829]": { + "$type": "EditorInspectorComponent", + "Id": 12904863407657276829, + "ComponentOrderEntryArray": [ + { + "ComponentId": 7205597372613518510 + }, + { + "ComponentId": 8564054653851438099, + "SortIndex": 1 + }, + { + "ComponentId": 12222961627447331506, + "SortIndex": 2 + } + ] + }, + "Component_[13729618014821386240]": { + "$type": "EditorPendingCompositionComponent", + "Id": 13729618014821386240 + }, + "Component_[14429836600052599894]": { + "$type": "EditorEntityIconComponent", + "Id": 14429836600052599894 + }, + "Component_[14808014799413383215]": { + "$type": "EditorEntitySortComponent", + "Id": 14808014799413383215 + }, + "Component_[17252932649882883756]": { + "$type": "SelectionComponent", + "Id": 17252932649882883756 + }, + "Component_[2229055145450914672]": { + "$type": "EditorLockComponent", + "Id": 2229055145450914672 + }, + "Component_[2249882080644631374]": { + "$type": "EditorOnlyEntityComponent", + "Id": 2249882080644631374 + }, + "Component_[7205597372613518510]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 7205597372613518510, + "Parent Entity": "Entity_[569860598305]", + "Transform Data": { + "Translate": [ + 9.886617660522461, + -9.999999974752427e-7, + 0.0 + ], + "Rotate": [ + 0.0, + 0.0, + 180.00001525878906 + ] + } + }, + "Component_[7918371639409185899]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 7918371639409185899 + }, + "Component_[8564054653851438099]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 8564054653851438099, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{D0F73AAF-52B7-507C-B045-DBE2FE2D4403}", + "subId": 268677693 + }, + "assetHint": "objects/shaderball_simple/shaberball_simple_1m.azmodel" + }, + "LodOverride": 255 + } + } + } + } + }, + "Entity_[599925369377]": { + "Id": "Entity_[599925369377]", + "Name": "MacBeth_Chart", + "Components": { + "Component_[10911367092756441312]": { + "$type": "EditorLockComponent", + "Id": 10911367092756441312 + }, + "Component_[11487615730470734577]": { + "$type": "EditorEntitySortComponent", + "Id": 11487615730470734577 + }, + "Component_[1380862607750834390]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 1380862607750834390 + }, + "Component_[17376808010180534107]": { + "$type": "EditorOnlyEntityComponent", + "Id": 17376808010180534107 + }, + "Component_[18051852481298910543]": { + "$type": "EditorPendingCompositionComponent", + "Id": 18051852481298910543 + }, + "Component_[2468310869499941539]": { + "$type": "EditorVisibilityComponent", + "Id": 2468310869499941539 + }, + "Component_[3104847651593575388]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 3104847651593575388, + "Parent Entity": "Entity_[471076350497]", + "Transform Data": { + "Translate": [ + 0.0, + 0.0, + 1.0 + ], + "Scale": [ + 24.748918533325195, + 24.748918533325195, + 24.748918533325195 + ], + "UniformScale": 24.748918533325195 + } + }, + "Component_[4039743767801786212]": { + "$type": "AZ::Render::EditorMeshComponent", + "Id": 4039743767801786212, + "Controller": { + "Configuration": { + "ModelAsset": { + "assetId": { + "guid": "{767B3209-EDF7-503A-BF3D-6A69DAABC966}", + "subId": 285003870 + }, + "assetHint": "materialeditor/viewportmodels/plane_1x1.azmodel" + }, + "LodOverride": 255 + } + } + }, + "Component_[4350883917310195183]": { + "$type": "EditorMaterialComponent", + "Id": 4350883917310195183, + "Controller": { + "Configuration": { + "materials": { + "{}": { + "MaterialAsset": { + "assetId": { + "guid": "{6BCA78B0-98F0-5843-A0D9-2FD6AB5B8B95}" + }, + "assetHint": "materials/presets/macbeth/macbeth_lab_16bit_2014_srgb.azmaterial" + } + } + } + } + }, + "materialSlotsByLodEnabled": true + }, + "Component_[5382697958657080154]": { + "$type": "EditorInspectorComponent", + "Id": 5382697958657080154, + "ComponentOrderEntryArray": [ + { + "ComponentId": 3104847651593575388 + }, + { + "ComponentId": 4039743767801786212, + "SortIndex": 1 + }, + { + "ComponentId": 4350883917310195183, + "SortIndex": 2 + } + ] + }, + "Component_[5944774294236360498]": { + "$type": "SelectionComponent", + "Id": 5944774294236360498 + }, + "Component_[7918181081161287223]": { + "$type": "EditorEntityIconComponent", + "Id": 7918181081161287223 + } + } + }, + "Entity_[604220336673]": { + "Id": "Entity_[604220336673]", + "Name": "Camera1", + "Components": { + "Component_[10875630838724467144]": { + "$type": "{CA11DA46-29FF-4083-B5F6-E02C3A8C3A3D} EditorCameraComponent", + "Id": 10875630838724467144, + "Controller": { + "Configuration": { + "EditorEntityId": 604220336673 + } + } + }, + "Component_[11853636775353879324]": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 11853636775353879324, + "Parent Entity": "Entity_[471076350497]", + "Transform Data": { + "Translate": [ + -0.088332898914814, + -14.735246658325195, + 12.247514724731445 + ], + "Rotate": [ + -34.60991287231445, + 0.19504709541797638, + -0.282683789730072 + ] + } + }, + "Component_[14115131108729471373]": { + "$type": "EditorEntitySortComponent", + "Id": 14115131108729471373 + }, + "Component_[14490537709933782275]": { + "$type": "SelectionComponent", + "Id": 14490537709933782275 + }, + "Component_[15389860813854215395]": { + "$type": "EditorDisabledCompositionComponent", + "Id": 15389860813854215395 + }, + "Component_[16956210187152487952]": { + "$type": "EditorVisibilityComponent", + "Id": 16956210187152487952 + }, + "Component_[3120168445836073859]": { + "$type": "EditorInspectorComponent", + "Id": 3120168445836073859, + "ComponentOrderEntryArray": [ + { + "ComponentId": 11853636775353879324 + }, + { + "ComponentId": 6418726603140010485, + "SortIndex": 1 + }, + { + "ComponentId": 6573470892650938647, + "SortIndex": 2 + }, + { + "ComponentId": 10875630838724467144, + "SortIndex": 3 + }, + { + "ComponentId": 9127356411199949930, + "SortIndex": 4 + } + ] + }, + "Component_[397791896240265054]": { + "$type": "EditorEntityIconComponent", + "Id": 397791896240265054 + }, + "Component_[6418726603140010485]": { + "$type": "AZ::Render::EditorExposureControlComponent", + "Id": 6418726603140010485, + "Controller": { + "Configuration": { + "ExposureControlType": 1, + "EyeAdaptationExposureMin": -10.0, + "EyeAdaptationExposureMax": 10.0 + } + } + }, + "Component_[6572845495569063152]": { + "$type": "EditorOnlyEntityComponent", + "Id": 6572845495569063152 + }, + "Component_[6573470892650938647]": { + "$type": "AZ::Render::EditorPostFxLayerComponent", + "Id": 6573470892650938647 + }, + "Component_[7175586201406734874]": { + "$type": "EditorLockComponent", + "Id": 7175586201406734874 + }, + "Component_[7393764569438584638]": { + "$type": "EditorPendingCompositionComponent", + "Id": 7393764569438584638 + }, + "Component_[9127356411199949930]": { + "$type": "GenericComponentWrapper", + "Id": 9127356411199949930, + "m_template": { + "$type": "FlyCameraInputComponent" + } + } + } + } + } +} \ No newline at end of file diff --git a/AutomatedTesting/Levels/macbeth_shaderballs/tags.txt b/AutomatedTesting/Levels/macbeth_shaderballs/tags.txt new file mode 100644 index 0000000000..0d6c1880e7 --- /dev/null +++ b/AutomatedTesting/Levels/macbeth_shaderballs/tags.txt @@ -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 diff --git a/AutomatedTesting/Objects/sphere_5lods.fbx b/AutomatedTesting/Objects/sphere_5lods.fbx new file mode 100644 index 0000000000..965738c933 --- /dev/null +++ b/AutomatedTesting/Objects/sphere_5lods.fbx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7e169277bca473325281d5fe043cffc9196bd3ef46f6bffbea6e0b5e3b7194a1 +size 62700 diff --git a/AutomatedTesting/Objects/sphere_5lods.fbx.assetinfo b/AutomatedTesting/Objects/sphere_5lods.fbx.assetinfo new file mode 100644 index 0000000000..d46cbf322a --- /dev/null +++ b/AutomatedTesting/Objects/sphere_5lods.fbx.assetinfo @@ -0,0 +1,8 @@ +{ + "values": [ + { + "$type": "ScriptProcessorRule", + "scriptFilename": "Editor/Scripts/auto_lod.py" + } + ] +} diff --git a/AutomatedTesting/Passes/MainPipeline.pass b/AutomatedTesting/Passes/MainPipeline.pass index aa9f3757c4..c34c556983 100644 --- a/AutomatedTesting/Passes/MainPipeline.pass +++ b/AutomatedTesting/Passes/MainPipeline.pass @@ -205,6 +205,99 @@ } ] }, + + { + // NOTE: HairParentPass does not write into Depth MSAA from Opaque Pass. If new passes downstream + // of HairParentPass will need to use Depth MSAA, HairParentPass will need to be updated to use Depth MSAA + // instead of regular Depth as DepthStencil. Specifically, HairResolvePPLL.pass and the associated + // .azsl file will need to be updated. + "Name": "HairParentPass", + // Note: The following two lines represent the choice of rendering pipeline for the hair. + // You can either choose to use PPLL or ShortCut and accordingly change the flag + // 'm_usePPLLRenderTechnique' in the class 'HairFeatureProcessor.cpp' +// "TemplateName": "HairParentPassTemplate", + "TemplateName": "HairParentShortCutPassTemplate", + "Enabled": true, + "Connections": [ + // Critical to keep DepthLinear as input - used to set the size of the Head PPLL image buffer. + // If DepthLinear is not available - connect to another viewport (non MSAA) image. + { + "LocalSlot": "DepthLinearInput", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "DepthLinear" + } + }, + { + "LocalSlot": "Depth", + "AttachmentRef": { + "Pass": "DepthPrePass", + "Attachment": "Depth" + } + }, + { + "LocalSlot": "RenderTargetInputOutput", + "AttachmentRef": { + "Pass": "OpaquePass", + "Attachment": "Output" + } + }, + { + "LocalSlot": "RenderTargetInputOnly", + "AttachmentRef": { + "Pass": "OpaquePass", + "Attachment": "Output" + } + }, + + // Shadows resources + { + "LocalSlot": "DirectionalShadowmap", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "DirectionalShadowmap" + } + }, + { + "LocalSlot": "DirectionalESM", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "DirectionalESM" + } + }, + { + "LocalSlot": "ProjectedShadowmap", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "ProjectedShadowmap" + } + }, + { + "LocalSlot": "ProjectedESM", + "AttachmentRef": { + "Pass": "ShadowPass", + "Attachment": "ProjectedESM" + } + }, + + // Lighting Resources + { + "LocalSlot": "TileLightData", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "TileLightData" + } + }, + { + "LocalSlot": "LightListRemapped", + "AttachmentRef": { + "Pass": "LightCullingPass", + "Attachment": "LightListRemapped" + } + } + ] + }, + { "Name": "TransparentPass", "TemplateName": "TransparentParentTemplate", @@ -254,22 +347,22 @@ { "LocalSlot": "InputLinearDepth", "AttachmentRef": { - "Pass": "DepthPrePass", + "Pass": "HairParentPass", "Attachment": "DepthLinear" } }, { "LocalSlot": "DepthStencil", "AttachmentRef": { - "Pass": "DepthPrePass", + "Pass": "HairParentPass", "Attachment": "Depth" } }, { "LocalSlot": "InputOutput", "AttachmentRef": { - "Pass": "OpaquePass", - "Attachment": "Output" + "Pass": "HairParentPass", + "Attachment": "RenderTargetInputOutput" } } ] @@ -282,22 +375,22 @@ { "LocalSlot": "InputLinearDepth", "AttachmentRef": { - "Pass": "DepthPrePass", + "Pass": "HairParentPass", "Attachment": "DepthLinear" } }, { "LocalSlot": "InputDepthStencil", "AttachmentRef": { - "Pass": "DepthPrePass", + "Pass": "HairParentPass", "Attachment": "Depth" } }, { "LocalSlot": "RenderTargetInputOutput", "AttachmentRef": { - "Pass": "TransparentPass", - "Attachment": "InputOutput" + "Pass": "HairParentPass", + "Attachment": "RenderTargetInputOutput" } } ], @@ -337,7 +430,7 @@ { "LocalSlot": "Depth", "AttachmentRef": { - "Pass": "DepthPrePass", + "Pass": "HairParentPass", "Attachment": "Depth" } }, @@ -372,7 +465,7 @@ { "LocalSlot": "DepthInputOutput", "AttachmentRef": { - "Pass": "DepthPrePass", + "Pass": "HairParentPass", "Attachment": "Depth" } } @@ -431,7 +524,7 @@ { "LocalSlot": "DepthInputOutput", "AttachmentRef": { - "Pass": "DepthPrePass", + "Pass": "HairParentPass", "Attachment": "Depth" } } @@ -451,7 +544,7 @@ { "LocalSlot": "DepthInputOutput", "AttachmentRef": { - "Pass": "DepthPrePass", + "Pass": "HairParentPass", "Attachment": "Depth" } } diff --git a/AutomatedTesting/ReflectionProbes/ReflectionProbe_Lion__CA20BB89-1587-4410-80BA-8150CB0EF47B__iblspecularcm512.dds b/AutomatedTesting/ReflectionProbes/ReflectionProbe_Lion__CA20BB89-1587-4410-80BA-8150CB0EF47B__iblspecularcm512.dds new file mode 100644 index 0000000000..86dea1906c --- /dev/null +++ b/AutomatedTesting/ReflectionProbes/ReflectionProbe_Lion__CA20BB89-1587-4410-80BA-8150CB0EF47B__iblspecularcm512.dds @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d65e361a150417b56fa88ac2171bbef6512f526a15695fdc52a446a7f891c593 +size 50331796 diff --git a/AutomatedTesting/ReflectionProbes/ReflectionProbe_Scene__D245F488-152E-4A3A-898B-FBCF71E4A87A__iblspecularcm256.dds b/AutomatedTesting/ReflectionProbes/ReflectionProbe_Scene__D245F488-152E-4A3A-898B-FBCF71E4A87A__iblspecularcm256.dds new file mode 100644 index 0000000000..536270c531 --- /dev/null +++ b/AutomatedTesting/ReflectionProbes/ReflectionProbe_Scene__D245F488-152E-4A3A-898B-FBCF71E4A87A__iblspecularcm256.dds @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c3fbf2f491048ed05ff1ea15728afd2fbc059880d0e61efb8c557cf97ebaae4 +size 50331796 diff --git a/AutomatedTesting/ReflectionProbes/ReflectionProbe_UpperLevel__4DABA7BF-9367-4D95-AA5F-A9BF6BA0F7BE__iblspecularcm512.dds b/AutomatedTesting/ReflectionProbes/ReflectionProbe_UpperLevel__4DABA7BF-9367-4D95-AA5F-A9BF6BA0F7BE__iblspecularcm512.dds new file mode 100644 index 0000000000..bb00476697 --- /dev/null +++ b/AutomatedTesting/ReflectionProbes/ReflectionProbe_UpperLevel__4DABA7BF-9367-4D95-AA5F-A9BF6BA0F7BE__iblspecularcm512.dds @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f142022323102e4225afc2f9b44b6631683691d180fdf4586bce34f1fd13a76 +size 50331796 diff --git a/AutomatedTesting/Registry/editorpreferences.setreg b/AutomatedTesting/Registry/editorpreferences.setreg index b338d6acad..e76e04f9ba 100644 --- a/AutomatedTesting/Registry/editorpreferences.setreg +++ b/AutomatedTesting/Registry/editorpreferences.setreg @@ -1,7 +1,7 @@ { "Amazon": { "Preferences": { - "EnablePrefabSystem": false + "EnablePrefabSystem": true } } } \ No newline at end of file diff --git a/AutomatedTesting/TestAssets/RelativeProductPathsNotDependencies.txt b/AutomatedTesting/TestAssets/RelativeProductPathsNotDependencies.txt index d3975ced7c..8084defe84 100644 --- a/AutomatedTesting/TestAssets/RelativeProductPathsNotDependencies.txt +++ b/AutomatedTesting/TestAssets/RelativeProductPathsNotDependencies.txt @@ -2,24 +2,24 @@ These tests are mostly done with files that have a different extension between s The source scan is done first, and will catch files in the source path. Product path searching is resolved using "endsWith" logic. textures/_dev_purple.tif.streamingimage -Back slashes, and project name in the path -pc/textures/_dev_stucco.tif.streamingimage +Back slashes +textures\_dev_stucco.tif.streamingimage Double back slashes textures\\_dev_tan.tif.streamingimage Casing doesn't match TEXTURES/_DEV_WHITE.tif.streamingimage Some files have multiple extensions, this verifies that won't trip up the scanner. textures/_dev_yellow_light.tif.1002.imagemipchain -Path inline textures/milestone2/ama_grey_02.tif.streamingimage test +Path inline textures/_dev_woodland.tif.1002.imagemipchain test Path after=textures/_dev_woodland.tif.streamingimage equal sign Multiple paths on one line -Multiple materials/am_grass1.mtl paths materials/am_rockground.mtl on one line -Path before a UUID -Path materials/floor_tile.mtl before B92667DC-9F5B-5D72-A29D-99219DD9B691 a UUID -Path before an asset ID -Path ui/milestone2menu.uicanvas before an 2ef92b8D044E5C278E2BB1AC0374A4E7:1002 asset ID -Path after a UUID -Path after CEAA362B4E505BCEB827CB92EF40A50E a project.json UUID -Path after an asset ID -Path after {A2482826-053D-5634-A27B-084B1326AAE5}:[1002] an libs/particles/milestone2particles.xml asset ID +Multiple textures/_dev_yellow_light.tif.streamingimage paths textures/_dev_yellow_med.tif.1002.imagemipchain on one line +Path before a UUID for SelfReferenceUUID text file +Path textures/lights/flare01.tif.streamingimage before 33BCEE02-F322-5688-ABEE-534F6058593F a UUID +Path before an asset ID for _dev_red image +Path textures/test_texture_sequence/test_texture_sequence000.png.streamingimage before an 2ef92b8D044E5C278E2BB1AC0374A4E7:1002 asset ID +Path after a UUID for SelfReferenceAssetID text file +Path after 785A05D2-483E-5B43-A2B9-92ACDAE6E938 a textures/test_texture_sequence/test_texture_sequence001.png.streamingimage UUID +Path after an asset ID for _dev_purple image file +Path after {A2482826-053D-5634-A27B-084B1326AAE5}:[1002] an textures/_dev_purple_glass.tif.1002.imagemipchain asset ID diff --git a/AutomatedTesting/TestAssets/RelativeSourcePathsNotDependencies.txt b/AutomatedTesting/TestAssets/RelativeSourcePathsNotDependencies.txt index a5ae046b11..df3bcc896e 100644 --- a/AutomatedTesting/TestAssets/RelativeSourcePathsNotDependencies.txt +++ b/AutomatedTesting/TestAssets/RelativeSourcePathsNotDependencies.txt @@ -3,6 +3,6 @@ TestAssets/RelativeProductPathsNotDependencies.txt Back slashes TestAssets\WildcardScanTest1.txt Casing doesn't match -libs/particles/milestone2PARTICLES.XML -Path inline project.json test +TESTASSETS/ReportONEmISSINGdEPENDENCY.tXT +Path inline TestAssets/InvalidAssetIdNoReport.txt test Path after=textures/_dev_Purple.tif equal sign diff --git a/AutomatedTesting/TestAssets/ValidAssetIdNotDependency.txt b/AutomatedTesting/TestAssets/ValidAssetIdNotDependency.txt index 4cda459232..0b9df7c99d 100644 --- a/AutomatedTesting/TestAssets/ValidAssetIdNotDependency.txt +++ b/AutomatedTesting/TestAssets/ValidAssetIdNotDependency.txt @@ -1,5 +1,7 @@ +File extensions are separated from file names, so the missing dependency scanner doesn't find when scanning, and only finds the asset IDs in this file. + /textures /_dev_Purple . tif, the product ID is for one of the mips. {A2482826-053D-5634-A27B-084B1326AAE5}:[1002] _dev_Red . tif, another mip, different formatting. -2ef92b8D044E5C278E2BB1AC0374A4E7:1003 +2ef92b8D044E5C278E2BB1AC0374A4E7:1000 _dev_White.tif, {D83B36F1-61A6-5001-B191-4D0CE282E236}-1002 asset ID inline. diff --git a/AutomatedTesting/TestAssets/ValidUUIDsNotDependency.txt b/AutomatedTesting/TestAssets/ValidUUIDsNotDependency.txt index 436d6fb628..91c518ef2f 100644 --- a/AutomatedTesting/TestAssets/ValidUUIDsNotDependency.txt +++ b/AutomatedTesting/TestAssets/ValidUUIDsNotDependency.txt @@ -1,18 +1,19 @@ Paths are broken up to avoid having them show up as relative path results. +All references are to other text files in this folder, extensions are omitted to make sure only UUID scanning finds these references. -This is the UUID for Materials / Default / AM_UV_v1_1K_source . png -C67BEA9F-09FF-59AA-A7F0-A52B8F987508 -This is the UUID for libs / particles / milestone2particles . xml. This tests UUIDs without separators. -6BDE282B49C957F7B0714B26579BCA9A -This is the UUID for SelfReferenceUUID.txt. This tests UUIDs with mixed casing. +This is the UUID for InvalidAssetIdNoReport +E68A85B0-131D-5A82-B2D5-BC58EE4062AE +This is the UUID for InvalidRelativePathsNoReport. This tests UUIDs without separators. +B3EF12DD306C520EB0A8A6B0D031A195 +This is the UUID for SelfReferenceUUID. This tests UUIDs with mixed casing. 33bcee02F3225688ABEE534F6058593F -This is a UUID mid-line B076CDDC-14DF-50F4-A5E9-7518ABB3E851, for project . json +This is a UUID mid-line DD587FBE-16C8-5B98-AE3C-A9F8750B2692, for SelfReferencePath -Two UUIDs on the same line -Two UUIDs 345E5C660D6254FF8D0F7C8EE66A2249 mixed on A26C73D1837E5AE59E68F916FA7C3699 the same line +InvalidUUIDNoReport and MaxIteration31Deep +Two UUIDs 837412DF-D05F-576D-81AA-ACF360463749 mixed on 3F642A0FDC825696A70A1DA5709744DF the same line Test UUIDs and Asset IDs mixed on the same line. Relative paths are handled in the relative path tests. -UUID: slices / MuzzleFlash . slice, AssetID: TestsAssets / WildcardScanTest1 . txt -This 747D31D71E62553592226173C49CF97E uuid is on the line with 1CB10C43F3245B93A294C602ADEF95F9:[0] a valid asset ID -UUID: Objects / Lumbertank_turret . cgf, AssetID: TestsAssets / WildcardScanTest2 . txt -This D92C4661C8985E19BD3597CB2318CFA6:[0] uuid is on the line with 37108522F50459499CD6C8D47A960CF1 a valid asset ID +OnlyMatchesCorrectLengthUUIDs and WildcardScanTest1 +This 2545AD8B-1B9B-5F93-859D-D8DC1DC2B480 uuid is on the line with 1CB10C43F3245B93A294C602ADEF95F9:[0] a valid asset ID +RelativeProductPathsNotDependencies and WildcardScanTest2 +This B772953CA08A5D209491530E87D11504:[0] uuid is on the line with D92C4661C8985E19BD3597CB2318CFA6 a valid asset ID diff --git a/Code/Editor/2DViewport.h b/Code/Editor/2DViewport.h index 68823acdb7..e89f4aed34 100644 --- a/Code/Editor/2DViewport.h +++ b/Code/Editor/2DViewport.h @@ -62,7 +62,6 @@ public: // ovverided from CViewport. float GetScreenScaleFactor(const Vec3& worldPoint) const override; - float GetScreenScaleFactor([[maybe_unused]] const CCamera& camera, [[maybe_unused]] const Vec3& object_position) override { return 1; } //Eric@conffx // Overrided from CViewport. void OnDragSelectRectangle(const QRect &rect, bool bNormalizeRect = false) override; diff --git a/Code/Editor/AboutDialog.ui b/Code/Editor/AboutDialog.ui index a36d65e35b..09a7c18841 100644 --- a/Code/Editor/AboutDialog.ui +++ b/Code/Editor/AboutDialog.ui @@ -125,7 +125,7 @@ - Stable 21.11 + development Qt::AutoText diff --git a/Code/Editor/Animation/AnimationBipedBoneNames.cpp b/Code/Editor/Animation/AnimationBipedBoneNames.cpp index d9f1b845ca..72502fe61d 100644 --- a/Code/Editor/Animation/AnimationBipedBoneNames.cpp +++ b/Code/Editor/Animation/AnimationBipedBoneNames.cpp @@ -10,24 +10,21 @@ #include "AnimationBipedBoneNames.h" -namespace EditorAnimationBones +namespace EditorAnimationBones::Biped { - namespace Biped - { - const char* Pelvis = "Bip01 Pelvis"; - const char* Head = "Bip01 Head"; - const char* Weapon = "weapon_bone"; + const char* Pelvis = "Bip01 Pelvis"; + const char* Head = "Bip01 Head"; + const char* Weapon = "weapon_bone"; - const char* LeftEye = "eye_bone_left"; - const char* RightEye = "eye_bone_right"; + const char* LeftEye = "eye_bone_left"; + const char* RightEye = "eye_bone_right"; - const char* Spine[5] = { "Bip01 Spine", "Bip01 Spine1", "Bip01 Spine2", "Bip01 Spine3", "Bip01 Spine4" }; - const char* Neck[2] = { "Bip01 Neck", "Bip01 Neck1" }; + const char* Spine[5] = { "Bip01 Spine", "Bip01 Spine1", "Bip01 Spine2", "Bip01 Spine3", "Bip01 Spine4" }; + const char* Neck[2] = { "Bip01 Neck", "Bip01 Neck1" }; - const char* LeftHeel = "Bip01 L Heel"; - const char* LeftToe[2] = { "Bip01 L Toe0", "Bip01 L Toe1" }; + const char* LeftHeel = "Bip01 L Heel"; + const char* LeftToe[2] = { "Bip01 L Toe0", "Bip01 L Toe1" }; - const char* RightHeel = "Bip01 R Heel"; - const char* RightToe[2] = { "Bip01 R Toe0", "Bip01 R Toe1" }; - } -} + const char* RightHeel = "Bip01 R Heel"; + const char* RightToe[2] = { "Bip01 R Toe0", "Bip01 R Toe1" }; +} // namespace EditorAnimationBones::Biped diff --git a/Code/Editor/AnimationContext.cpp b/Code/Editor/AnimationContext.cpp index fce1150878..51dd8deefc 100644 --- a/Code/Editor/AnimationContext.cpp +++ b/Code/Editor/AnimationContext.cpp @@ -21,6 +21,8 @@ #include "Include/IObjectManager.h" #include "Objects/EntityObject.h" +#include + ////////////////////////////////////////////////////////////////////////// // Movie Callback. ////////////////////////////////////////////////////////////////////////// @@ -499,25 +501,24 @@ void CAnimationContext::Update() return; } - ITimer* pTimer = GetIEditor()->GetSystem()->GetITimer(); + const AZ::TimeUs frameDeltaTimeUs = AZ::GetSimulationTickDeltaTimeUs(); + const float frameDeltaTime = AZ::TimeUsToSeconds(frameDeltaTimeUs); if (!m_bAutoRecording) { AnimateActiveSequence(); - float dt = pTimer->GetFrameTime(); - m_currTime += dt * m_fTimeScale; + m_currTime += frameDeltaTime * m_fTimeScale; if (!m_recording) { - GetIEditor()->GetMovieSystem()->PreUpdate(dt); - GetIEditor()->GetMovieSystem()->PostUpdate(dt); + GetIEditor()->GetMovieSystem()->PreUpdate(frameDeltaTime); + GetIEditor()->GetMovieSystem()->PostUpdate(frameDeltaTime); } } else { - float dt = pTimer->GetFrameTime(); - m_fRecordingCurrTime += dt * m_fTimeScale; + m_fRecordingCurrTime += frameDeltaTime * m_fTimeScale; if (fabs(m_fRecordingCurrTime - m_currTime) > m_fRecordingTimeStep) { m_currTime += m_fRecordingTimeStep; @@ -644,7 +645,9 @@ void CAnimationContext::OnPostRender() { SAnimContext ac; ac.dt = 0; - ac.fps = GetIEditor()->GetSystem()->GetITimer()->GetFrameRate(); + const AZ::TimeUs frameDeltaTimeUs = AZ::GetSimulationTickDeltaTimeUs(); + const float frameDeltaTime = AZ::TimeUsToSeconds(frameDeltaTimeUs); + ac.fps = 1.0f / frameDeltaTime; ac.time = m_currTime; ac.singleFrame = true; ac.forcePlay = true; @@ -797,7 +800,9 @@ void CAnimationContext::AnimateActiveSequence() SAnimContext ac; ac.dt = 0; - ac.fps = GetIEditor()->GetSystem()->GetITimer()->GetFrameRate(); + const AZ::TimeUs frameDeltaTimeUs = AZ::GetSimulationTickDeltaTimeUs(); + const float frameDeltaTime = AZ::TimeUsToSeconds(frameDeltaTimeUs); + ac.fps = 1.0f / frameDeltaTime; ac.time = m_currTime; ac.singleFrame = true; ac.forcePlay = true; diff --git a/Code/Editor/AssetImporter/AssetImporterManager/AssetImporterManager.cpp b/Code/Editor/AssetImporter/AssetImporterManager/AssetImporterManager.cpp index b721b8759e..baa287e47f 100644 --- a/Code/Editor/AssetImporter/AssetImporterManager/AssetImporterManager.cpp +++ b/Code/Editor/AssetImporter/AssetImporterManager/AssetImporterManager.cpp @@ -140,7 +140,7 @@ bool AssetImporterManager::OnBrowseFiles() bool encounteredCrate = false; QStringList invalidFiles; - for (QString path : fileDialog.selectedFiles()) + for (const QString& path : fileDialog.selectedFiles()) { QString fileName = GetFileName(path); QFileInfo info(path); diff --git a/Code/Editor/BaseLibrary.cpp b/Code/Editor/BaseLibrary.cpp index 9f26630c2c..15742bd254 100644 --- a/Code/Editor/BaseLibrary.cpp +++ b/Code/Editor/BaseLibrary.cpp @@ -14,66 +14,6 @@ #include "Include/IBaseLibraryManager.h" #include #include -#include "Undo/IUndoObject.h" - -////////////////////////////////////////////////////////////////////////// -// Undo functionality for libraries. -////////////////////////////////////////////////////////////////////////// - -class CUndoBaseLibrary - : public IUndoObject -{ -public: - CUndoBaseLibrary(CBaseLibrary* pLib, const QString& description, const QString& selectedItem = QString()) - : m_pLib(pLib) - , m_description(description) - , m_redo(nullptr) - , m_selectedItem(selectedItem) - { - assert(m_pLib); - - m_undo = GetIEditor()->GetSystem()->CreateXmlNode("Undo"); - m_pLib->Serialize(m_undo, false); - } - - QString GetEditorObjectName() override - { - return m_selectedItem; - } - -protected: - int GetSize() override { return sizeof(CUndoBaseLibrary); } - QString GetDescription() override { return m_description; }; - - void Undo(bool bUndo) override - { - if (bUndo) - { - m_redo = GetIEditor()->GetSystem()->CreateXmlNode("Redo"); - m_pLib->Serialize(m_redo, false); - } - m_pLib->Serialize(m_undo, true); - m_pLib->SetModified(); - GetIEditor()->Notify(eNotify_OnDataBaseUpdate); - } - - void Redo() override - { - m_pLib->Serialize(m_redo, true); - m_pLib->SetModified(); - GetIEditor()->Notify(eNotify_OnDataBaseUpdate); - } - -private: - QString m_description; - QString m_selectedItem; - _smart_ptr m_pLib; - XmlNodeRef m_undo; - XmlNodeRef m_redo; -}; - - - ////////////////////////////////////////////////////////////////////////// // CBaseLibrary implementation. diff --git a/Code/Editor/BaseLibraryItem.cpp b/Code/Editor/BaseLibraryItem.cpp index b1fba91fad..e3788bc3e4 100644 --- a/Code/Editor/BaseLibraryItem.cpp +++ b/Code/Editor/BaseLibraryItem.cpp @@ -29,7 +29,6 @@ public: assert(libMgr); m_itemPath = libItem->GetFullName(); - m_description = "Lib item changed: " + m_itemPath; //serialize the lib item to undo m_undoCtx.node = GetIEditor()->GetSystem()->CreateXmlNode("Undo"); @@ -45,13 +44,8 @@ public: m_size = sizeof(CUndoBaseLibraryItem); m_size += static_cast(xmlStr.GetAllocatedMemory()); m_size += m_itemPath.length(); - m_size += m_description.length(); } - QString GetEditorObjectName() override - { - return m_itemPath; - } protected: int GetSize() override @@ -59,11 +53,6 @@ protected: return m_size; } - QString GetDescription() override - { - return m_description; - } - void Undo(bool bUndo) override { //find the libItem @@ -111,7 +100,6 @@ protected: } private: - QString m_description; QString m_itemPath; IDataBaseItem::SerializeContext m_undoCtx; //saved before operation IDataBaseItem::SerializeContext m_redoCtx; //saved after operation so used for redo diff --git a/Code/Editor/BaseLibraryManager.cpp b/Code/Editor/BaseLibraryManager.cpp index 7346ffaf8c..0e51a5238c 100644 --- a/Code/Editor/BaseLibraryManager.cpp +++ b/Code/Editor/BaseLibraryManager.cpp @@ -17,120 +17,6 @@ #include "ErrorReport.h" #include "Undo/IUndoObject.h" - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Undo functionality for Managers, including add library, remove library, and rename library -- Vera, Confetti -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -class CUndoBaseLibraryManager - : public IUndoObject -{ -public: - CUndoBaseLibraryManager(CBaseLibraryManager* pMngr, const QString& description, const QString& modifiedManager = nullptr) - : m_pMngr(pMngr) - , m_description(description) - , m_editorObject(modifiedManager) - { - assert(m_pMngr); - SerializeTo(m_undos); - } - - QString GetEditorObjectName() override - { - return m_editorObject; - } - -protected: - int GetSize() override { return sizeof(CUndoBaseLibraryManager); } - QString GetDescription() override { return m_description; }; - - void Undo(bool bUndo) override - { - if (bUndo) - { - SerializeTo(m_redos); - } - m_pMngr->ClearAll(); - UnserializeFrom(m_undos); - GetIEditor()->Notify(eNotify_OnDataBaseUpdate); - } - - void Redo() override - { - m_pMngr->ClearAll(); - UnserializeFrom(m_redos); - GetIEditor()->Notify(eNotify_OnDataBaseUpdate); - } - -private: - struct LibUndoNode - : public _i_reference_target_t - { - LibUndoNode() - { - node = nullptr; - fileName = ""; - } - XmlNodeRef node; - QString fileName; - }; - - static const char* const LIBRARY_TAG; - static const char* const LEVEL_LIBRARY_TAG; - - void SerializeTo(std::vector<_smart_ptr >& undos) // Save Library Undo - { - undos.clear(); - for (int i = 0; i < m_pMngr->GetLibraryCount(); i++) - { - IDataBaseLibrary* library = m_pMngr->GetLibrary(i); - - const char* tag = library->IsLevelLibrary() ? LEVEL_LIBRARY_TAG : LIBRARY_TAG; - XmlNodeRef node = GetIEditor()->GetSystem()->CreateXmlNode(tag); - QString file = library->GetFilename().isEmpty() ? library->GetFilename() : library->GetName(); - library->Serialize(node, false); - if (node && !file.isEmpty()) - { - _smart_ptr undo = new LibUndoNode(); - undo->fileName = file; - undo->node = node; - undos.push_back(undo); - } - } - } - - void UnserializeFrom(std::vector<_smart_ptr >& undos) // Load Library Undo - { - for (int i = 0; i < undos.size(); i++) - { - _smart_ptr undo = undos[i]; - if (undo->node && !undo->fileName.isEmpty()) - { - //AddLibrary adds a .xml to the end of the library path, this will remove the extra for compatibility - undo->fileName.replace(m_pMngr->GetLibsPath().toLower(), ""); - undo->fileName.replace(".xml", ""); - - const bool isLevelLibrary = (strcmp(undo->node->getTag(), LEVEL_LIBRARY_TAG) == 0); - - IDataBaseLibrary* library = m_pMngr->AddLibrary(undo->fileName, isLevelLibrary); - library->Serialize(undo->node, true); - } - } - } - - - QString m_description; - QString m_editorObject; - CBaseLibraryManager* m_pMngr; - std::vector<_smart_ptr > m_undos; - std::vector<_smart_ptr > m_redos; -}; - -const char* const CUndoBaseLibraryManager::LIBRARY_TAG = "UndoLibrary"; -const char* const CUndoBaseLibraryManager::LEVEL_LIBRARY_TAG = "UndoLevelLibrary"; - - - ////////////////////////////////////////////////////////////////////////// // CBaseLibraryManager implementation. ////////////////////////////////////////////////////////////////////////// @@ -606,11 +492,9 @@ void CBaseLibraryManager::RegisterItem(CBaseLibraryItem* pItem, REFGUID newGuid) if (m_bUniqGuidMap) { - bool bNewItem = true; REFGUID oldGuid = pItem->GetGUID(); if (!GuidUtil::IsEmpty(oldGuid)) { - bNewItem = false; m_itemsGuidMap.erase(oldGuid); } if (GuidUtil::IsEmpty(newGuid)) diff --git a/Code/Editor/CMakeLists.txt b/Code/Editor/CMakeLists.txt index e55498203c..3358b49dce 100644 --- a/Code/Editor/CMakeLists.txt +++ b/Code/Editor/CMakeLists.txt @@ -249,38 +249,9 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) RUNTIME_DEPENDENCIES Gem::LmbrCentral ) + ly_add_googletest( NAME Legacy::EditorLib.Tests ) - ly_add_target( - NAME EditorLib.Camera.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} - NAMESPACE Legacy - FILES_CMAKE - Lib/Tests/Camera/editor_lib_camera_test_files.cmake - INCLUDE_DIRECTORIES - PRIVATE - . - BUILD_DEPENDENCIES - PRIVATE - AZ::AzCore - AZ::AzTest - AZ::AzToolsFramework - AZ::AzTestShared - Legacy::EditorLib - Gem::Camera.Editor - Gem::AtomToolsFramework.Static - RUNTIME_DEPENDENCIES - Legacy::EditorLib - ) - - ly_add_source_properties( - SOURCES Lib/Tests/Camera/test_EditorCamera.cpp - PROPERTY COMPILE_DEFINITIONS - VALUES CAMERA_EDITOR_MODULE="$" - ) - - ly_add_googletest( - NAME Legacy::EditorLib.Camera.Tests - ) endif() diff --git a/Code/Editor/CVarMenu.h b/Code/Editor/CVarMenu.h index efcc8e8caf..5195bd99d7 100644 --- a/Code/Editor/CVarMenu.h +++ b/Code/Editor/CVarMenu.h @@ -16,9 +16,12 @@ #include #include +struct ICVar; + class CVarMenu : public QMenu { + Q_OBJECT public: // CVar that can be toggled on and off struct CVarToggle diff --git a/Code/Editor/ConfigGroup.cpp b/Code/Editor/ConfigGroup.cpp index 74fe6f7b5c..42236e43dd 100644 --- a/Code/Editor/ConfigGroup.cpp +++ b/Code/Editor/ConfigGroup.cpp @@ -19,10 +19,9 @@ namespace Config CConfigGroup::~CConfigGroup() { - for (TConfigVariables::const_iterator it = m_vars.begin(); - it != m_vars.end(); ++it) + for (IConfigVar* var : m_vars) { - delete (*it); + delete var; } } @@ -31,17 +30,15 @@ namespace Config m_vars.push_back(var); } - uint32 CConfigGroup::GetVarCount() + AZ::u32 CConfigGroup::GetVarCount() { - return static_cast(m_vars.size()); + return aznumeric_cast(m_vars.size()); } IConfigVar* CConfigGroup::GetVar(const char* szName) { - for (TConfigVariables::const_iterator it = m_vars.begin(); - it != m_vars.end(); ++it) + for (IConfigVar* var : m_vars) { - IConfigVar* var = (*it); if (0 == _stricmp(szName, var->GetName().c_str())) { return var; @@ -53,20 +50,19 @@ namespace Config const IConfigVar* CConfigGroup::GetVar(const char* szName) const { - for (TConfigVariables::const_iterator it = m_vars.begin(); - it != m_vars.end(); ++it) + for (const IConfigVar* var : m_vars) { - IConfigVar* var = (*it); if (0 == _stricmp(szName, var->GetName().c_str())) { return var; } + } return nullptr; } - IConfigVar* CConfigGroup::GetVar(uint index) + IConfigVar* CConfigGroup::GetVar(AZ::u32 index) { if (index < m_vars.size()) { @@ -76,7 +72,7 @@ namespace Config return nullptr; } - const IConfigVar* CConfigGroup::GetVar(uint index) const + const IConfigVar* CConfigGroup::GetVar(AZ::u32 index) const { if (index < m_vars.size()) { @@ -89,114 +85,110 @@ namespace Config void CConfigGroup::SaveToXML(XmlNodeRef node) { // save only values that don't have default values - for (TConfigVariables::const_iterator it = m_vars.begin(); - it != m_vars.end(); ++it) + for (const IConfigVar* var : m_vars) { - IConfigVar* var = (*it); - if (!var->IsFlagSet(IConfigVar::eFlag_DoNotSave)) + if (var->IsFlagSet(IConfigVar::eFlag_DoNotSave) || var->IsDefault()) { - if (!var->IsDefault()) - { - const char* szName = var->GetName().c_str(); + continue; + } - switch (var->GetType()) - { - case IConfigVar::eType_BOOL: - { - bool currentValue = false; - var->Get(¤tValue); - node->setAttr(szName, currentValue); - break; - } + const char* szName = var->GetName().c_str(); - case IConfigVar::eType_INT: - { - int currentValue = 0; - var->Get(¤tValue); - node->setAttr(szName, currentValue); - break; - } + switch (var->GetType()) + { + case IConfigVar::eType_BOOL: + { + bool currentValue = false; + var->Get(¤tValue); + node->setAttr(szName, currentValue); + break; + } - case IConfigVar::eType_FLOAT: - { - float currentValue = 0; - var->Get(¤tValue); - node->setAttr(szName, currentValue); - break; - } + case IConfigVar::eType_INT: + { + int currentValue = 0; + var->Get(¤tValue); + node->setAttr(szName, currentValue); + break; + } - case IConfigVar::eType_STRING: - { - AZStd::string currentValue; - var->Get(¤tValue); - node->setAttr(szName, currentValue.c_str()); - break; - } - } - } + case IConfigVar::eType_FLOAT: + { + float currentValue = 0; + var->Get(¤tValue); + node->setAttr(szName, currentValue); + break; + } + + case IConfigVar::eType_STRING: + { + AZStd::string currentValue; + var->Get(¤tValue); + node->setAttr(szName, currentValue.c_str()); + break; + } } } } void CConfigGroup::LoadFromXML(XmlNodeRef node) { - // save only values that don't have default values - for (TConfigVariables::const_iterator it = m_vars.begin(); - it != m_vars.end(); ++it) + // load values that are save-able + for (IConfigVar* var : m_vars) { - IConfigVar* var = (*it); - if (!var->IsFlagSet(IConfigVar::eFlag_DoNotSave)) + if (var->IsFlagSet(IConfigVar::eFlag_DoNotSave)) { - const char* szName = var->GetName().c_str(); + continue; + } + const char* szName = var->GetName().c_str(); - switch (var->GetType()) + switch (var->GetType()) + { + case IConfigVar::eType_BOOL: + { + bool currentValue = false; + var->GetDefault(¤tValue); + if (node->getAttr(szName, currentValue)) { - case IConfigVar::eType_BOOL: - { - bool currentValue = false; - var->GetDefault(¤tValue); - if (node->getAttr(szName, currentValue)) - { - var->Set(¤tValue); - } - break; + var->Set(¤tValue); } + break; + } - case IConfigVar::eType_INT: + case IConfigVar::eType_INT: + { + int currentValue = 0; + var->GetDefault(¤tValue); + if (node->getAttr(szName, currentValue)) { - int currentValue = 0; - var->GetDefault(¤tValue); - if (node->getAttr(szName, currentValue)) - { - var->Set(¤tValue); - } - break; + var->Set(¤tValue); } + break; + } - case IConfigVar::eType_FLOAT: + case IConfigVar::eType_FLOAT: + { + float currentValue = 0; + var->GetDefault(¤tValue); + if (node->getAttr(szName, currentValue)) { - float currentValue = 0; - var->GetDefault(¤tValue); - if (node->getAttr(szName, currentValue)) - { - var->Set(¤tValue); - } - break; + var->Set(¤tValue); } + break; + } - case IConfigVar::eType_STRING: + case IConfigVar::eType_STRING: + { + AZStd::string currentValue; + var->GetDefault(¤tValue); + QString readValue(currentValue.c_str()); + if (node->getAttr(szName, readValue)) { - AZStd::string currentValue; - var->GetDefault(¤tValue); - QString readValue(currentValue.c_str()); - if (node->getAttr(szName, readValue)) - { - currentValue = readValue.toUtf8().data(); - var->Set(¤tValue); - } - break; - } + currentValue = readValue.toUtf8().data(); + var->Set(¤tValue); } + break; + } } } } diff --git a/Code/Editor/ConfigGroup.h b/Code/Editor/ConfigGroup.h index 769a29ba8a..004725e32c 100644 --- a/Code/Editor/ConfigGroup.h +++ b/Code/Editor/ConfigGroup.h @@ -8,8 +8,12 @@ #pragma once -#ifndef CRYINCLUDE_EDITOR_CONFIGGROUP_H -#define CRYINCLUDE_EDITOR_CONFIGGROUP_H +#include +#include +#include + +struct ICVar; +class XmlNodeRef; namespace Config { @@ -32,7 +36,7 @@ namespace Config eFlag_DoNotSave = 1 << 2, }; - IConfigVar(const char* szName, const char* szDescription, EType varType, uint8 flags) + IConfigVar(const char* szName, const char* szDescription, EType varType, AZ::u8 flags) : m_name(szName) , m_description(szDescription) , m_type(varType) @@ -42,22 +46,22 @@ namespace Config virtual ~IConfigVar() = default; - ILINE EType GetType() const + AZ_FORCE_INLINE EType GetType() const { return m_type; } - ILINE const AZStd::string& GetName() const + AZ_FORCE_INLINE const AZStd::string& GetName() const { return m_name; } - ILINE const AZStd::string& GetDescription() const + AZ_FORCE_INLINE const AZStd::string& GetDescription() const { return m_description; } - ILINE bool IsFlagSet(EFlags flag) const + AZ_FORCE_INLINE bool IsFlagSet(EFlags flag) const { return 0 != (m_flags & flag); } @@ -68,73 +72,28 @@ namespace Config virtual void GetDefault(void* outPtr) const = 0; virtual void Reset() = 0; - static EType TranslateType(const bool&) { return eType_BOOL; } - static EType TranslateType(const int&) { return eType_INT; } - static EType TranslateType(const float&) { return eType_FLOAT; } - static EType TranslateType(const AZStd::string&) { return eType_STRING; } + static constexpr EType TranslateType(const bool&) { return eType_BOOL; } + static constexpr EType TranslateType(const int&) { return eType_INT; } + static constexpr EType TranslateType(const float&) { return eType_FLOAT; } + static constexpr EType TranslateType(const AZStd::string&) { return eType_STRING; } protected: EType m_type; - uint8 m_flags; + AZ::u8 m_flags; AZStd::string m_name; AZStd::string m_description; void* m_ptr; ICVar* m_pCVar; }; - // Typed wrapper for config variable - template - class TConfigVar - : public IConfigVar - { - private: - T m_default; - - public: - TConfigVar(const char* szName, const char* szDescription, uint8 flags, T& ptr, const T& defaultValue) - : IConfigVar(szName, szDescription, IConfigVar::TranslateType(ptr), flags) - , m_default(defaultValue) - { - m_ptr = &ptr; - - // reset to default value on initializations - ptr = defaultValue; - } - - virtual void Get(void* outPtr) const - { - *reinterpret_cast(outPtr) = *reinterpret_cast(m_ptr); - } - - virtual void Set(const void* ptr) - { - *reinterpret_cast(m_ptr) = *reinterpret_cast(ptr); - } - - virtual void Reset() - { - *reinterpret_cast(m_ptr) = m_default; - } - - virtual void GetDefault(void* outPtr) const - { - *reinterpret_cast(outPtr) = m_default; - } - - virtual bool IsDefault() const - { - return *reinterpret_cast(m_ptr) == m_default; - } - }; - // Group of configuration variables with optional mapping to CVars class CConfigGroup { private: - typedef std::vector TConfigVariables; + using TConfigVariables = AZStd::vector ; TConfigVariables m_vars; - typedef std::vector TConsoleVariables; + using TConsoleVariables = AZStd::vector; TConsoleVariables m_consoleVars; public: @@ -142,20 +101,13 @@ namespace Config virtual ~CConfigGroup(); void AddVar(IConfigVar* var); - uint32 GetVarCount(); + AZ::u32 GetVarCount(); IConfigVar* GetVar(const char* szName); - IConfigVar* GetVar(uint index); + IConfigVar* GetVar(AZ::u32 index); const IConfigVar* GetVar(const char* szName) const; - const IConfigVar* GetVar(uint index) const; + const IConfigVar* GetVar(AZ::u32 index) const; void SaveToXML(XmlNodeRef node); void LoadFromXML(XmlNodeRef node); - - template - void AddVar(const char* szName, const char* szDescription, T& var, const T& defaultValue, uint8 flags = 0) - { - AddVar(new TConfigVar(szName, szDescription, flags, var, defaultValue)); - } }; }; -#endif // CRYINCLUDE_EDITOR_CONFIGGROUP_H diff --git a/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h b/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h index 5ec24b679d..849e44cedd 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h +++ b/Code/Editor/Controls/ReflectedPropertyControl/PropertyMiscCtrl.h @@ -6,8 +6,6 @@ * */ -#ifndef CRYINCLUDE_EDITOR_UTILS_PROPERTYMISCCTRL_H -#define CRYINCLUDE_EDITOR_UTILS_PROPERTYMISCCTRL_H #pragma once #if !defined(Q_MOC_RUN) @@ -53,6 +51,7 @@ private: class UserPopupWidgetHandler : public QObject, public AzToolsFramework::PropertyHandler < CReflectedVarUser, UserPropertyEditor> { + Q_OBJECT public: AZ_CLASS_ALLOCATOR(UserPopupWidgetHandler, AZ::SystemAllocator, 0); bool IsDefaultHandler() const override { return false; } @@ -67,6 +66,7 @@ public: class FloatCurveHandler : public QObject, public AzToolsFramework::PropertyHandler < CReflectedVarSpline, CSplineCtrl> { + Q_OBJECT public: AZ_CLASS_ALLOCATOR(FloatCurveHandler, AZ::SystemAllocator, 0); bool IsDefaultHandler() const override { return false; } @@ -80,5 +80,3 @@ public: void OnSplineChange(CSplineCtrl*); }; - -#endif // CRYINCLUDE_EDITOR_UTILS_PROPERTYMISCCTRL_H diff --git a/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp b/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp index c5ccc599d6..d26e978ae8 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp +++ b/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.cpp @@ -58,17 +58,9 @@ private: void OnClicked() override { QString tempValue(""); - QString ext(""); - if (m_path.isEmpty() == false) + if (!m_path.isEmpty() && !Path::GetExt(m_path).isEmpty()) { - if (Path::GetExt(m_path) == "") - { - tempValue = ""; - } - else - { - tempValue = m_path; - } + tempValue = m_path; } AssetSelectionModel selection; diff --git a/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.h b/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.h index fa30eba034..087ee9f1db 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.h +++ b/Code/Editor/Controls/ReflectedPropertyControl/PropertyResourceCtrl.h @@ -99,6 +99,7 @@ class FileResourceSelectorWidgetHandler : QObject , public AzToolsFramework::PropertyHandler < CReflectedVarResource, FileResourceSelectorWidget > { + Q_OBJECT public: AZ_CLASS_ALLOCATOR(FileResourceSelectorWidgetHandler, AZ::SystemAllocator, 0); diff --git a/Code/Editor/Controls/ReflectedPropertyControl/ReflectedPropertyCtrl.cpp b/Code/Editor/Controls/ReflectedPropertyControl/ReflectedPropertyCtrl.cpp index 1151137bfc..c5bf6a4d81 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/ReflectedPropertyCtrl.cpp +++ b/Code/Editor/Controls/ReflectedPropertyControl/ReflectedPropertyCtrl.cpp @@ -671,7 +671,7 @@ AzToolsFramework::PropertyRowWidget* ReflectedPropertyControl::FindPropertyRowWi return nullptr; } const AzToolsFramework::ReflectedPropertyEditor::WidgetList& widgets = m_editor->GetWidgets(); - for (auto instance : widgets) + for (const auto& instance : widgets) { if (instance.second->label() == item->GetPropertyName()) { diff --git a/Code/Editor/Controls/ReflectedPropertyControl/ReflectedVarWrapper.cpp b/Code/Editor/Controls/ReflectedPropertyControl/ReflectedVarWrapper.cpp index b3f1b35461..2320938f08 100644 --- a/Code/Editor/Controls/ReflectedPropertyControl/ReflectedVarWrapper.cpp +++ b/Code/Editor/Controls/ReflectedPropertyControl/ReflectedVarWrapper.cpp @@ -446,41 +446,54 @@ void ReflectedVarUserAdapter::SetVariable(IVariable *pVariable) m_reflectedVar.reset(new CReflectedVarUser( pVariable->GetHumanName().toUtf8().data())); } -void ReflectedVarUserAdapter::SyncReflectedVarToIVar(IVariable *pVariable) +void ReflectedVarUserAdapter::SyncReflectedVarToIVar(IVariable* pVariable) { QString value; pVariable->Get(value); m_reflectedVar->m_value = value.toUtf8().data(); - //extract the list of custom items from the IVariable user data - IVariable::IGetCustomItems* pGetCustomItems = static_cast (pVariable->GetUserData().value()); - if (pGetCustomItems != nullptr) - { - std::vector items; - QString dlgTitle; - // call the user supplied callback to fill-in items and get dialog title - bool bShowIt = pGetCustomItems->GetItems(pVariable, items, dlgTitle); - if (bShowIt) // if func didn't veto, show the dialog - { - m_reflectedVar->m_enableEdit = true; - m_reflectedVar->m_useTree = pGetCustomItems->UseTree(); - m_reflectedVar->m_treeSeparator = pGetCustomItems->GetTreeSeparator(); - m_reflectedVar->m_dialogTitle = dlgTitle.toUtf8().data(); - m_reflectedVar->m_itemNames.resize(items.size()); - m_reflectedVar->m_itemDescriptions.resize(items.size()); - - QByteArray ba; - int i = -1; - std::generate(m_reflectedVar->m_itemNames.begin(), m_reflectedVar->m_itemNames.end(), [&items, &i, &ba]() { ++i; ba = items[i].name.toUtf8(); return ba.data(); }); - i = -1; - std::generate(m_reflectedVar->m_itemDescriptions.begin(), m_reflectedVar->m_itemDescriptions.end(), [&items, &i, &ba]() { ++i; ba = items[i].desc.toUtf8(); return ba.data(); }); - - } - } - else + // extract the list of custom items from the IVariable user data + IVariable::IGetCustomItems* pGetCustomItems = static_cast(pVariable->GetUserData().value()); + if (pGetCustomItems == nullptr) { m_reflectedVar->m_enableEdit = false; + return; } + + std::vector items; + QString dlgTitle; + // call the user supplied callback to fill-in items and get dialog title + bool bShowIt = pGetCustomItems->GetItems(pVariable, items, dlgTitle); + if (!bShowIt) // if func vetoed it, don't show the dialog + { + return; + } + m_reflectedVar->m_enableEdit = true; + m_reflectedVar->m_useTree = pGetCustomItems->UseTree(); + m_reflectedVar->m_treeSeparator = pGetCustomItems->GetTreeSeparator(); + m_reflectedVar->m_dialogTitle = dlgTitle.toUtf8().data(); + m_reflectedVar->m_itemNames.resize(items.size()); + m_reflectedVar->m_itemDescriptions.resize(items.size()); + + QByteArray ba; + int i = -1; + AZStd::generate( + m_reflectedVar->m_itemNames.begin(), m_reflectedVar->m_itemNames.end(), + [&items, &i, &ba]() + { + ++i; + ba = items[i].name.toUtf8(); + return ba.data(); + }); + i = -1; + AZStd::generate( + m_reflectedVar->m_itemDescriptions.begin(), m_reflectedVar->m_itemDescriptions.end(), + [&items, &i, &ba]() + { + ++i; + ba = items[i].desc.toUtf8(); + return ba.data(); + }); } void ReflectedVarUserAdapter::SyncIVarToReflectedVar(IVariable *pVariable) diff --git a/Code/Editor/Controls/SplineCtrlEx.cpp b/Code/Editor/Controls/SplineCtrlEx.cpp index 1dc2ff19e1..bff75af169 100644 --- a/Code/Editor/Controls/SplineCtrlEx.cpp +++ b/Code/Editor/Controls/SplineCtrlEx.cpp @@ -82,7 +82,6 @@ protected: } int GetSize() override { return sizeof(*this); } - QString GetDescription() override { return "UndoSplineCtrlEx"; }; void Undo(bool bUndo) override { diff --git a/Code/Editor/Controls/TimelineCtrl.cpp b/Code/Editor/Controls/TimelineCtrl.cpp index aa30c326ac..3890b46b2b 100644 --- a/Code/Editor/Controls/TimelineCtrl.cpp +++ b/Code/Editor/Controls/TimelineCtrl.cpp @@ -126,7 +126,6 @@ void TimelineWidget::DrawTicks(QPainter* painter) const QPen pOldPen = painter->pen(); const QPen ltgray(QColor(110, 110, 110)); - const QPen black(palette().color(QPalette::Normal, QPalette::Text)); const QPen redpen(QColor(255, 0, 255)); // Draw time ticks every tick step seconds. @@ -598,7 +597,6 @@ void TimelineWidget::DrawSecondTicks(QPainter* painter) { const QPen ltgray(QColor(110, 110, 110)); const QPen black(palette().color(QPalette::Normal, QPalette::Text)); - const QPen redpen(QColor(255, 0, 255)); for (int gx = m_grid.firstGridLine.x(); gx < m_grid.firstGridLine.x() + m_grid.numGridLines.x() + 1; gx++) { diff --git a/Code/Editor/CryEdit.cpp b/Code/Editor/CryEdit.cpp index dffe222f42..3890a9e59a 100644 --- a/Code/Editor/CryEdit.cpp +++ b/Code/Editor/CryEdit.cpp @@ -33,6 +33,7 @@ AZ_POP_DISABLE_WARNING #include #include #include +#include #include // Aws Native SDK @@ -80,7 +81,6 @@ AZ_POP_DISABLE_WARNING #include // CryCommon -#include #include // Editor @@ -546,7 +546,6 @@ public: { "BatchMode", m_bConsoleMode }, { "NullRenderer", m_bNullRenderer }, { "devmode", m_bDeveloperMode }, - { "VTUNE", dummy }, { "runpython", m_bRunPythonScript }, { "runpythontest", m_bRunPythonTestScript }, { "version", m_bShowVersionInfo }, @@ -2811,14 +2810,11 @@ void CCryEditApp::OpenProjectManager(const AZStd::string& screen) { // provide the current project path for in case we want to update the project AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath(); -#if !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS - const char* argumentQuoteString = R"(")"; -#else - const char* argumentQuoteString = R"(\")"; -#endif - const AZStd::string commandLineOptions = AZStd::string::format(R"( --screen %s --project-path %s%s%s)", - screen.c_str(), - argumentQuoteString, projectPath.c_str(), argumentQuoteString); + + const AZStd::vector commandLineOptions { + "--screen", screen, + "--project-path", AZStd::string::format(R"("%s")", projectPath.c_str()) }; + bool launchSuccess = AzFramework::ProjectManager::LaunchProjectManager(commandLineOptions); if (!launchSuccess) { @@ -3818,7 +3814,8 @@ void CCryEditApp::OnOpenQuickAccessBar() } QRect geo = m_pQuickAccessBar->geometry(); - geo.moveCenter(MainWindow::instance()->geometry().center()); + auto mainWindow = MainWindow::instance(); + geo.moveCenter(mainWindow->mapToGlobal(mainWindow->geometry().center())); m_pQuickAccessBar->setGeometry(geo); m_pQuickAccessBar->setVisible(true); m_pQuickAccessBar->setFocus(); @@ -3830,7 +3827,7 @@ void CCryEditApp::SetEditorWindowTitle(QString sTitleStr, QString sPreTitleStr, { if (sTitleStr.isEmpty()) { - sTitleStr = QObject::tr("O3DE Editor [Stable 21.11]"); + sTitleStr = QObject::tr("O3DE Editor [Developer Preview]"); } if (!sPreTitleStr.isEmpty()) @@ -4017,7 +4014,7 @@ void CCryEditApp::OnError(AzFramework::AssetSystem::AssetSystemErrors error) break; } - CryMessageBox(errorMessage.c_str(), "Error", MB_OK | MB_ICONERROR | MB_SETFOREGROUND); + QMessageBox::critical(nullptr,"Error",errorMessage.c_str()); } void CCryEditApp::OnOpenProceduralMaterialEditor() @@ -4132,7 +4129,15 @@ extern "C" int AZ_DLL_EXPORT CryEditMain(int argc, char* argv[]) AzQtComponents::Utilities::HandleDpiAwareness(AzQtComponents::Utilities::SystemDpiAware); Editor::EditorQtApplication* app = Editor::EditorQtApplication::newInstance(argc, argv); - if (app->arguments().contains("-autotest_mode")) + QStringList qArgs = app->arguments(); + const bool is_automated_test = AZStd::any_of(qArgs.begin(), qArgs.end(), + [](const QString& elem) + { + return elem.endsWith("autotest_mode") || elem.endsWith("runpythontest"); + } + ); + + if (is_automated_test) { // Nullroute all stdout to null for automated tests, this way we make sure // that the test result output is not polluted with unrelated output data. diff --git a/Code/Editor/CryEdit.h b/Code/Editor/CryEdit.h index dd597dcc55..8c514170ae 100644 --- a/Code/Editor/CryEdit.h +++ b/Code/Editor/CryEdit.h @@ -432,6 +432,7 @@ public: class CCrySingleDocTemplate : public QObject { + Q_OBJECT private: explicit CCrySingleDocTemplate(const QMetaObject* pDocClass) : QObject() diff --git a/Code/Editor/CryEditDoc.cpp b/Code/Editor/CryEditDoc.cpp index 4944c3bff5..3bcba4d5e0 100644 --- a/Code/Editor/CryEditDoc.cpp +++ b/Code/Editor/CryEditDoc.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -50,7 +51,6 @@ #include "GameExporter.h" #include "MainWindow.h" #include "LevelFileDialog.h" -#include "StatObjBus.h" #include "Undo/Undo.h" #include @@ -60,15 +60,6 @@ #include #include // for LmbrCentral::EditorLightComponentRequestBus -//#define PROFILE_LOADING_WITH_VTUNE - -// profilers api. -//#include "pure.h" -#ifdef PROFILE_LOADING_WITH_VTUNE -#include "C:\Program Files\Intel\Vtune\Analyzer\Include\VTuneApi.h" -#pragma comment(lib,"C:\\Program Files\\Intel\\Vtune\\Analyzer\\Lib\\VTuneApi.lib") -#endif - static const char* kAutoBackupFolder = "_autobackup"; static const char* kHoldFolder = "$tmp_hold"; // conform to the ignored file types $tmp[0-9]*_ regex static const char* kSaveBackupFolder = "_savebackup"; @@ -254,9 +245,6 @@ void CCryEditDoc::DeleteContents() EBUS_EVENT(AzToolsFramework::EditorEntityContextRequestBus, ResetEditorContext); - // [LY-90904] move this to the EditorVegetationManager component - InstanceStatObjEventBus::Broadcast(&InstanceStatObjEventBus::Events::ReleaseData); - ////////////////////////////////////////////////////////////////////////// // Clear all undo info. ////////////////////////////////////////////////////////////////////////// @@ -316,8 +304,6 @@ void CCryEditDoc::Save(TDocMultiArchive& arrXmlAr) // Fog settings /////////////////////////////////////////////////////// SerializeFogSettings((*arrXmlAr[DMAS_GENERAL])); - - SerializeNameSelection((*arrXmlAr[DMAS_GENERAL])); } } AfterSave(); @@ -408,9 +394,6 @@ void CCryEditDoc::Load(TDocMultiArchive& arrXmlAr, const QString& szFilename) int t0 = GetTickCount(); -#ifdef PROFILE_LOADING_WITH_VTUNE - VTResume(); -#endif // Load level-specific audio data. AZStd::string levelFileName{ fileName.toUtf8().constData() }; AZStd::to_lower(levelFileName.begin(), levelFileName.end()); @@ -466,12 +449,6 @@ void CCryEditDoc::Load(TDocMultiArchive& arrXmlAr, const QString& szFilename) } } - if (!isPrefabEnabled) - { - // Name Selection groups - SerializeNameSelection((*arrXmlAr[DMAS_GENERAL])); - } - { CAutoLogTime logtime("Post Load"); @@ -484,10 +461,6 @@ void CCryEditDoc::Load(TDocMultiArchive& arrXmlAr, const QString& szFilename) CSurfaceTypeValidator().Validate(); -#ifdef PROFILE_LOADING_WITH_VTUNE - VTPause(); -#endif - LogLoadTime(GetTickCount() - t0); // Loaded with success, remove event from log file GetIEditor()->GetSettingsManager()->UnregisterEvent(loadEvent); @@ -610,16 +583,6 @@ void CCryEditDoc::SerializeFogSettings(CXmlArchive& xmlAr) } } -void CCryEditDoc::SerializeNameSelection(CXmlArchive& xmlAr) -{ - IObjectManager* pObjManager = GetIEditor()->GetObjectManager(); - - if (pObjManager) - { - pObjManager->SerializeNameSelection(xmlAr.root, xmlAr.bLoading); - } -} - void CCryEditDoc::SetModifiedModules(EModifiedModule eModifiedModule, bool boSet) { if (!boSet) @@ -765,7 +728,9 @@ bool CCryEditDoc::OnOpenDocument(const QString& lpszPathName) bool CCryEditDoc::BeforeOpenDocument(const QString& lpszPathName, TOpenDocContext& context) { - CTimeValue loading_start_time = gEnv->pTimer->GetAsyncTime(); + const AZ::TimeMs timeMs = AZ::GetRealElapsedTimeMs(); + const double timeSec = AZ::TimeMsToSecondsDouble(timeMs); + const CTimeValue loading_start_time(timeSec); bool usePrefabSystemForLevels = false; AzFramework::ApplicationRequests::Bus::BroadcastResult( @@ -806,7 +771,7 @@ bool CCryEditDoc::BeforeOpenDocument(const QString& lpszPathName, TOpenDocContex bool CCryEditDoc::DoOpenDocument(TOpenDocContext& context) { - CTimeValue& loading_start_time = context.loading_start_time; + const CTimeValue& loading_start_time = context.loading_start_time; bool isPrefabEnabled = false; AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); @@ -876,7 +841,9 @@ bool CCryEditDoc::DoOpenDocument(TOpenDocContext& context) StartStreamingLoad(); - CTimeValue loading_end_time = gEnv->pTimer->GetAsyncTime(); + const AZ::TimeMs timeMs = AZ::GetRealElapsedTimeMs(); + const double timeSec = AZ::TimeMsToSecondsDouble(timeMs); + const CTimeValue loading_end_time(timeSec); CLogFile::FormatLine("-----------------------------------------------------------"); CLogFile::FormatLine("Successfully opened document %s", context.absoluteLevelPath.toUtf8().data()); @@ -1139,7 +1106,7 @@ bool CCryEditDoc::SaveLevel(const QString& filename) const QString oldLevelPattern = QDir(oldLevelFolder).absoluteFilePath("*.*"); const QString oldLevelName = Path::GetFile(GetLevelPathName()); const QString oldLevelXml = Path::ReplaceExtension(oldLevelName, "xml"); - AZ::IO::ArchiveFileIterator findHandle = pIPak->FindFirst(oldLevelPattern.toUtf8().data(), AZ::IO::IArchive::eFileSearchType_AllowOnDiskAndInZips); + AZ::IO::ArchiveFileIterator findHandle = pIPak->FindFirst(oldLevelPattern.toUtf8().data(), AZ::IO::FileSearchLocation::Any); if (findHandle) { do diff --git a/Code/Editor/CryEditDoc.h b/Code/Editor/CryEditDoc.h index e64bdb1308..f7e97d308e 100644 --- a/Code/Editor/CryEditDoc.h +++ b/Code/Editor/CryEditDoc.h @@ -24,7 +24,6 @@ #include #endif -class CClouds; struct LightingSettings; struct IVariable; struct ICVar; @@ -124,7 +123,6 @@ public: // Create from serialization only const char* GetTemporaryLevelName() const; void DeleteTemporaryLevel(); - CClouds* GetClouds() { return m_pClouds; } void SetWaterColor(const QColor& col) { m_waterColor = col; } QColor GetWaterColor() const { return m_waterColor; } XmlNodeRef& GetFogTemplate() { return m_fogTemplate; } @@ -163,7 +161,6 @@ protected: bool LoadEntitiesFromSlice(const QString& sliceFile); void SerializeFogSettings(CXmlArchive& xmlAr); virtual void SerializeViewSettings(CXmlArchive& xmlAr); - void SerializeNameSelection(CXmlArchive& xmlAr); void LogLoadTime(int time) const; struct TSaveDocContext @@ -195,7 +192,6 @@ protected: QColor m_waterColor = QColor(0, 0, 255); XmlNodeRef m_fogTemplate; XmlNodeRef m_environmentTemplate; - CClouds* m_pClouds; std::list m_listeners; bool m_bDocumentReady = false; ICVar* doc_validate_surface_types = nullptr; diff --git a/Code/Editor/CustomResolutionDlg.h b/Code/Editor/CustomResolutionDlg.h index 5dd9acaae8..e1b8035c65 100644 --- a/Code/Editor/CustomResolutionDlg.h +++ b/Code/Editor/CustomResolutionDlg.h @@ -12,8 +12,6 @@ // Notice : Refer to ViewportTitleDlg.cpp for a use case. -#ifndef CRYINCLUDE_EDITOR_CUSTOMRESOLUTIONDLG_H -#define CRYINCLUDE_EDITOR_CUSTOMRESOLUTIONDLG_H #pragma once #if !defined(Q_MOC_RUN) @@ -28,6 +26,7 @@ namespace Ui class CCustomResolutionDlg : public QDialog { + Q_OBJECT public: CCustomResolutionDlg(int w, int h, QWidget* pParent = nullptr); ~CCustomResolutionDlg(); @@ -42,5 +41,3 @@ protected: QScopedPointer m_ui; }; - -#endif // CRYINCLUDE_EDITOR_CUSTOMRESOLUTIONDLG_H diff --git a/Code/Editor/CustomizeKeyboardDialog.cpp b/Code/Editor/CustomizeKeyboardDialog.cpp index ce09f8d878..280d6c5323 100644 --- a/Code/Editor/CustomizeKeyboardDialog.cpp +++ b/Code/Editor/CustomizeKeyboardDialog.cpp @@ -211,9 +211,9 @@ public: void Reset(QAction& action) { - emit beginResetModel(); + beginResetModel(); m_action = &action; - emit endResetModel(); + endResetModel(); } private: @@ -266,7 +266,7 @@ QStringList CustomizeKeyboardDialog::BuildModels(QWidget* parent) categories.append(category); QMenu* menu = menuAction->menu(); - m_menuActions[category] = GetAllActionsForMenu(menu, QStringLiteral("")); + m_menuActions[category] = GetAllActionsForMenu(menu, QString()); } return categories; diff --git a/Code/Editor/Dialogs/PythonScriptsDialog.cpp b/Code/Editor/Dialogs/PythonScriptsDialog.cpp index 35047947ac..06b1acdbc8 100644 --- a/Code/Editor/Dialogs/PythonScriptsDialog.cpp +++ b/Code/Editor/Dialogs/PythonScriptsDialog.cpp @@ -40,10 +40,10 @@ AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING namespace { // File name extension for python files - const QString s_kPythonFileNameSpec = "*.py"; + const QString s_kPythonFileNameSpec("*.py"); // Tree root element name - const QString s_kRootElementName = "Python Scripts"; + const QString s_kRootElementName("Python Scripts"); } ////////////////////////////////////////////////////////////////////////// diff --git a/Code/Editor/EditorDefs.h b/Code/Editor/EditorDefs.h index 4115e8433a..97c03b2b45 100644 --- a/Code/Editor/EditorDefs.h +++ b/Code/Editor/EditorDefs.h @@ -105,7 +105,6 @@ #include #include #include -#include #include #include diff --git a/Code/Editor/EditorEnvironment.cpp b/Code/Editor/EditorEnvironment.cpp index 463fec8d08..2d675275ec 100644 --- a/Code/Editor/EditorEnvironment.cpp +++ b/Code/Editor/EditorEnvironment.cpp @@ -17,7 +17,7 @@ void SetEditorEnvironment(SSystemGlobalEnvironment* pEnv) void AttachEditorAZEnvironment(AZ::EnvironmentInstance azEnv) { - AZ::Environment::Attach(azEnv, true); + AZ::Environment::Attach(azEnv); } void DetachEditorAZEnvironment() diff --git a/Code/Editor/EditorModularViewportCameraComposer.cpp b/Code/Editor/EditorModularViewportCameraComposer.cpp index f145adf72f..3f66468584 100644 --- a/Code/Editor/EditorModularViewportCameraComposer.cpp +++ b/Code/Editor/EditorModularViewportCameraComposer.cpp @@ -356,7 +356,8 @@ namespace SandboxEditor AZ::TransformBus::EventResult(worldFromLocal, viewEntityId, &AZ::TransformBus::Events::GetWorldTM); AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( - m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::StartTrackingTransform, worldFromLocal); + m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::StartTrackingTransform, + worldFromLocal); } else { @@ -367,8 +368,10 @@ namespace SandboxEditor void EditorModularViewportCameraComposer::OnTick(const float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) { - const float delta = [duration = &ed_cameraDefaultOrbitFadeDuration, deltaTime] { - if (*duration == 0.0f) { + const float delta = [duration = &ed_cameraDefaultOrbitFadeDuration, deltaTime] + { + if (*duration == 0.0f) + { return 1.0f; } return deltaTime / *duration; diff --git a/Code/Editor/EditorPreferencesPageViewportManipulator.cpp b/Code/Editor/EditorPreferencesPageViewportManipulator.cpp index ea00d6a7f0..32e0e5b573 100644 --- a/Code/Editor/EditorPreferencesPageViewportManipulator.cpp +++ b/Code/Editor/EditorPreferencesPageViewportManipulator.cpp @@ -10,6 +10,8 @@ #include "EditorPreferencesPageViewportManipulator.h" +#include + // Editor #include "EditorViewportSettings.h" #include "Settings.h" @@ -19,7 +21,17 @@ void CEditorPreferencesPage_ViewportManipulator::Reflect(AZ::SerializeContext& s serialize.Class() ->Version(1) ->Field("LineBoundWidth", &Manipulators::m_manipulatorLineBoundWidth) - ->Field("CircleBoundWidth", &Manipulators::m_manipulatorCircleBoundWidth); + ->Field("CircleBoundWidth", &Manipulators::m_manipulatorCircleBoundWidth) + ->Field("LinearManipulatorAxisLength", &Manipulators::m_linearManipulatorAxisLength) + ->Field("PlanarManipulatorAxisLength", &Manipulators::m_planarManipulatorAxisLength) + ->Field("SurfaceManipulatorRadius", &Manipulators::m_surfaceManipulatorRadius) + ->Field("SurfaceManipulatorOpacity", &Manipulators::m_surfaceManipulatorOpacity) + ->Field("LinearManipulatorConeLength", &Manipulators::m_linearManipulatorConeLength) + ->Field("LinearManipulatorConeRadius", &Manipulators::m_linearManipulatorConeRadius) + ->Field("ScaleManipulatorBoxHalfExtent", &Manipulators::m_scaleManipulatorBoxHalfExtent) + ->Field("RotationManipulatorRadius", &Manipulators::m_rotationManipulatorRadius) + ->Field("ManipulatorViewBaseScale", &Manipulators::m_manipulatorViewBaseScale) + ->Field("FlipManipulatorAxesTowardsView", &Manipulators::m_flipManipulatorAxesTowardsView); serialize.Class()->Version(2)->Field( "Manipulators", &CEditorPreferencesPage_ViewportManipulator::m_manipulators); @@ -36,7 +48,55 @@ void CEditorPreferencesPage_ViewportManipulator::Reflect(AZ::SerializeContext& s AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_manipulatorCircleBoundWidth, "Circle Bound Width", "Manipulator Circle Bound Width") ->Attribute(AZ::Edit::Attributes::Min, 0.001f) - ->Attribute(AZ::Edit::Attributes::Max, 2.0f); + ->Attribute(AZ::Edit::Attributes::Max, 2.0f) + ->DataElement( + AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_linearManipulatorAxisLength, "Linear Manipulator Axis Length", + "Length of default Linear Manipulator (for Translation and Scale Manipulators)") + ->Attribute(AZ::Edit::Attributes::Min, 0.1f) + ->Attribute(AZ::Edit::Attributes::Max, 5.0f) + ->DataElement( + AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_planarManipulatorAxisLength, "Planar Manipulator Axis Length", + "Length of default Planar Manipulator (for Translation Manipulators)") + ->Attribute(AZ::Edit::Attributes::Min, 0.1f) + ->Attribute(AZ::Edit::Attributes::Max, 5.0f) + ->DataElement( + AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_surfaceManipulatorRadius, "Surface Manipulator Radius", + "Radius of default Surface Manipulator (for Translation Manipulators)") + ->Attribute(AZ::Edit::Attributes::Min, 0.05f) + ->Attribute(AZ::Edit::Attributes::Max, 1.0f) + ->DataElement( + AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_surfaceManipulatorOpacity, "Surface Manipulator Opacity", + "Opacity of default Surface Manipulator (for Translation Manipulators)") + ->Attribute(AZ::Edit::Attributes::Min, 0.01f) + ->Attribute(AZ::Edit::Attributes::Max, 1.0f) + ->DataElement( + AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_linearManipulatorConeLength, "Linear Manipulator Cone Length", + "Length of cone for default Linear Manipulator (for Translation Manipulators)") + ->Attribute(AZ::Edit::Attributes::Min, 0.05f) + ->Attribute(AZ::Edit::Attributes::Max, 1.0f) + ->DataElement( + AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_linearManipulatorConeRadius, "Linear Manipulator Cone Radius", + "Radius of cone for default Linear Manipulator (for Translation Manipulators)") + ->Attribute(AZ::Edit::Attributes::Min, 0.05f) + ->Attribute(AZ::Edit::Attributes::Max, 0.5f) + ->DataElement( + AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_scaleManipulatorBoxHalfExtent, "Scale Manipulator Box Half Extent", + "Half extent of box for default Scale Manipulator") + ->Attribute(AZ::Edit::Attributes::Min, 0.05f) + ->Attribute(AZ::Edit::Attributes::Max, 1.0f) + ->DataElement( + AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_rotationManipulatorRadius, "Rotation Manipulator Radius", + "Radius of default Angular Manipulators (for Rotation Manipulators)") + ->Attribute(AZ::Edit::Attributes::Min, 0.5f) + ->Attribute(AZ::Edit::Attributes::Max, 5.0f) + ->DataElement( + AZ::Edit::UIHandlers::SpinBox, &Manipulators::m_manipulatorViewBaseScale, "Manipulator View Base Scale", + "The base scale to apply to all Manipulator Views (default is 1.0)") + ->Attribute(AZ::Edit::Attributes::Min, 0.5f) + ->Attribute(AZ::Edit::Attributes::Max, 2.0f) + ->DataElement( + AZ::Edit::UIHandlers::CheckBox, &Manipulators::m_flipManipulatorAxesTowardsView, "Flip Manipulator Axes Towards View", + "Determines whether Planar and Linear Manipulators should switch to face the view (camera) in the Editor"); editContext ->Class("Manipulator Viewport Preferences", "Manipulator Viewport Preferences") @@ -82,10 +142,32 @@ void CEditorPreferencesPage_ViewportManipulator::OnApply() { SandboxEditor::SetManipulatorLineBoundWidth(m_manipulators.m_manipulatorLineBoundWidth); SandboxEditor::SetManipulatorCircleBoundWidth(m_manipulators.m_manipulatorCircleBoundWidth); + + AzToolsFramework::SetLinearManipulatorAxisLength(m_manipulators.m_linearManipulatorAxisLength); + AzToolsFramework::SetPlanarManipulatorAxisLength(m_manipulators.m_planarManipulatorAxisLength); + AzToolsFramework::SetSurfaceManipulatorRadius(m_manipulators.m_surfaceManipulatorRadius); + AzToolsFramework::SetSurfaceManipulatorOpacity(m_manipulators.m_surfaceManipulatorOpacity); + AzToolsFramework::SetLinearManipulatorConeLength(m_manipulators.m_linearManipulatorConeLength); + AzToolsFramework::SetLinearManipulatorConeRadius(m_manipulators.m_linearManipulatorConeRadius); + AzToolsFramework::SetScaleManipulatorBoxHalfExtent(m_manipulators.m_scaleManipulatorBoxHalfExtent); + AzToolsFramework::SetRotationManipulatorRadius(m_manipulators.m_rotationManipulatorRadius); + AzToolsFramework::SetFlipManipulatorAxesTowardsView(m_manipulators.m_flipManipulatorAxesTowardsView); + AzToolsFramework::SetManipulatorViewBaseScale(m_manipulators.m_manipulatorViewBaseScale); } void CEditorPreferencesPage_ViewportManipulator::InitializeSettings() { m_manipulators.m_manipulatorLineBoundWidth = SandboxEditor::ManipulatorLineBoundWidth(); m_manipulators.m_manipulatorCircleBoundWidth = SandboxEditor::ManipulatorCircleBoundWidth(); + + m_manipulators.m_linearManipulatorAxisLength = AzToolsFramework::LinearManipulatorAxisLength(); + m_manipulators.m_planarManipulatorAxisLength = AzToolsFramework::PlanarManipulatorAxisLength(); + m_manipulators.m_surfaceManipulatorRadius = AzToolsFramework::SurfaceManipulatorRadius(); + m_manipulators.m_surfaceManipulatorOpacity = AzToolsFramework::SurfaceManipulatorOpacity(); + m_manipulators.m_linearManipulatorConeLength = AzToolsFramework::LinearManipulatorConeLength(); + m_manipulators.m_linearManipulatorConeRadius = AzToolsFramework::LinearManipulatorConeRadius(); + m_manipulators.m_scaleManipulatorBoxHalfExtent = AzToolsFramework::ScaleManipulatorBoxHalfExtent(); + m_manipulators.m_rotationManipulatorRadius = AzToolsFramework::RotationManipulatorRadius(); + m_manipulators.m_flipManipulatorAxesTowardsView = AzToolsFramework::FlipManipulatorAxesTowardsView(); + m_manipulators.m_manipulatorViewBaseScale = AzToolsFramework::ManipulatorViewBaseScale(); } diff --git a/Code/Editor/EditorPreferencesPageViewportManipulator.h b/Code/Editor/EditorPreferencesPageViewportManipulator.h index 93db6a7035..eb76cec2c5 100644 --- a/Code/Editor/EditorPreferencesPageViewportManipulator.h +++ b/Code/Editor/EditorPreferencesPageViewportManipulator.h @@ -41,6 +41,16 @@ private: float m_manipulatorLineBoundWidth = 0.0f; float m_manipulatorCircleBoundWidth = 0.0f; + float m_linearManipulatorAxisLength = 0.0f; + float m_planarManipulatorAxisLength = 0.0f; + float m_surfaceManipulatorRadius = 0.0f; + float m_surfaceManipulatorOpacity = 0.0f; + float m_linearManipulatorConeLength = 0.0f; + float m_linearManipulatorConeRadius = 0.0f; + float m_scaleManipulatorBoxHalfExtent = 0.0f; + float m_rotationManipulatorRadius = 0.0f; + float m_manipulatorViewBaseScale = 0.0f; + bool m_flipManipulatorAxesTowardsView = false; }; Manipulators m_manipulators; diff --git a/Code/Editor/EditorViewportSettings.cpp b/Code/Editor/EditorViewportSettings.cpp index ae188c7d98..e06b9696e1 100644 --- a/Code/Editor/EditorViewportSettings.cpp +++ b/Code/Editor/EditorViewportSettings.cpp @@ -12,6 +12,7 @@ #include #include #include +#include namespace SandboxEditor { @@ -57,31 +58,6 @@ namespace SandboxEditor constexpr AZStd::string_view CameraDefaultStartingPositionY = "/Amazon/Preferences/Editor/Camera/DefaultStartingPosition/y"; constexpr AZStd::string_view CameraDefaultStartingPositionZ = "/Amazon/Preferences/Editor/Camera/DefaultStartingPosition/z"; - template - void SetRegistry(const AZStd::string_view setting, T&& value) - { - if (auto* registry = AZ::SettingsRegistry::Get()) - { - registry->Set(setting, AZStd::forward(value)); - } - } - - template - AZStd::remove_cvref_t GetRegistry(const AZStd::string_view setting, T&& defaultValue) - { - AZStd::remove_cvref_t value = AZStd::forward(defaultValue); - if (const auto* registry = AZ::SettingsRegistry::Get()) - { - T potentialValue; - if (registry->Get(potentialValue, setting)) - { - value = AZStd::move(potentialValue); - } - } - - return value; - } - struct EditorViewportSettingsCallbacksImpl : public EditorViewportSettingsCallbacks { EditorViewportSettingsCallbacksImpl() @@ -118,399 +94,409 @@ namespace SandboxEditor AZ::Vector3 CameraDefaultEditorPosition() { return AZ::Vector3( - aznumeric_cast(GetRegistry(CameraDefaultStartingPositionX, 0.0)), - aznumeric_cast(GetRegistry(CameraDefaultStartingPositionY, -10.0)), - aznumeric_cast(GetRegistry(CameraDefaultStartingPositionZ, 4.0))); + aznumeric_cast(AzToolsFramework::GetRegistry(CameraDefaultStartingPositionX, 0.0)), + aznumeric_cast(AzToolsFramework::GetRegistry(CameraDefaultStartingPositionY, -10.0)), + aznumeric_cast(AzToolsFramework::GetRegistry(CameraDefaultStartingPositionZ, 4.0))); } void SetCameraDefaultEditorPosition(const AZ::Vector3& defaultCameraPosition) { - SetRegistry(CameraDefaultStartingPositionX, defaultCameraPosition.GetX()); - SetRegistry(CameraDefaultStartingPositionY, defaultCameraPosition.GetY()); - SetRegistry(CameraDefaultStartingPositionZ, defaultCameraPosition.GetZ()); + AzToolsFramework::SetRegistry(CameraDefaultStartingPositionX, defaultCameraPosition.GetX()); + AzToolsFramework::SetRegistry(CameraDefaultStartingPositionY, defaultCameraPosition.GetY()); + AzToolsFramework::SetRegistry(CameraDefaultStartingPositionZ, defaultCameraPosition.GetZ()); } AZ::u64 MaxItemsShownInAssetBrowserSearch() { - return GetRegistry(AssetBrowserMaxItemsShownInSearchSetting, aznumeric_cast(50)); + return AzToolsFramework::GetRegistry(AssetBrowserMaxItemsShownInSearchSetting, aznumeric_cast(50)); } void SetMaxItemsShownInAssetBrowserSearch(const AZ::u64 numberOfItemsShown) { - SetRegistry(AssetBrowserMaxItemsShownInSearchSetting, numberOfItemsShown); + AzToolsFramework::SetRegistry(AssetBrowserMaxItemsShownInSearchSetting, numberOfItemsShown); } bool GridSnappingEnabled() { - return GetRegistry(GridSnappingSetting, false); + return AzToolsFramework::GetRegistry(GridSnappingSetting, false); } void SetGridSnapping(const bool enabled) { - SetRegistry(GridSnappingSetting, enabled); + AzToolsFramework::SetRegistry(GridSnappingSetting, enabled); } float GridSnappingSize() { - return aznumeric_cast(GetRegistry(GridSizeSetting, 0.1)); + return aznumeric_cast(AzToolsFramework::GetRegistry(GridSizeSetting, 0.1)); } void SetGridSnappingSize(const float size) { - SetRegistry(GridSizeSetting, size); + AzToolsFramework::SetRegistry(GridSizeSetting, size); } bool AngleSnappingEnabled() { - return GetRegistry(AngleSnappingSetting, false); + return AzToolsFramework::GetRegistry(AngleSnappingSetting, false); } void SetAngleSnapping(const bool enabled) { - SetRegistry(AngleSnappingSetting, enabled); + AzToolsFramework::SetRegistry(AngleSnappingSetting, enabled); } float AngleSnappingSize() { - return aznumeric_cast(GetRegistry(AngleSizeSetting, 5.0)); + return aznumeric_cast(AzToolsFramework::GetRegistry(AngleSizeSetting, 5.0)); } void SetAngleSnappingSize(const float size) { - SetRegistry(AngleSizeSetting, size); + AzToolsFramework::SetRegistry(AngleSizeSetting, size); } bool ShowingGrid() { - return GetRegistry(ShowGridSetting, false); + return AzToolsFramework::GetRegistry(ShowGridSetting, false); } void SetShowingGrid(const bool showing) { - SetRegistry(ShowGridSetting, showing); + AzToolsFramework::SetRegistry(ShowGridSetting, showing); } bool StickySelectEnabled() { - return GetRegistry(StickySelectSetting, false); + return AzToolsFramework::GetRegistry(StickySelectSetting, false); } void SetStickySelectEnabled(const bool enabled) { - SetRegistry(StickySelectSetting, enabled); + AzToolsFramework::SetRegistry(StickySelectSetting, enabled); } float ManipulatorLineBoundWidth() { - return aznumeric_cast(GetRegistry(ManipulatorLineBoundWidthSetting, 0.1)); + return aznumeric_cast(AzToolsFramework::GetRegistry(ManipulatorLineBoundWidthSetting, 0.1)); } void SetManipulatorLineBoundWidth(const float lineBoundWidth) { - SetRegistry(ManipulatorLineBoundWidthSetting, lineBoundWidth); + AzToolsFramework::SetRegistry(ManipulatorLineBoundWidthSetting, lineBoundWidth); } float ManipulatorCircleBoundWidth() { - return aznumeric_cast(GetRegistry(ManipulatorCircleBoundWidthSetting, 0.1)); + return aznumeric_cast(AzToolsFramework::GetRegistry(ManipulatorCircleBoundWidthSetting, 0.1)); } void SetManipulatorCircleBoundWidth(const float circleBoundWidth) { - SetRegistry(ManipulatorCircleBoundWidthSetting, circleBoundWidth); + AzToolsFramework::SetRegistry(ManipulatorCircleBoundWidthSetting, circleBoundWidth); } float CameraTranslateSpeed() { - return aznumeric_cast(GetRegistry(CameraTranslateSpeedSetting, 10.0)); + return aznumeric_cast(AzToolsFramework::GetRegistry(CameraTranslateSpeedSetting, 10.0)); } void SetCameraTranslateSpeed(const float speed) { - SetRegistry(CameraTranslateSpeedSetting, speed); + AzToolsFramework::SetRegistry(CameraTranslateSpeedSetting, speed); } float CameraBoostMultiplier() { - return aznumeric_cast(GetRegistry(CameraBoostMultiplierSetting, 3.0)); + return aznumeric_cast(AzToolsFramework::GetRegistry(CameraBoostMultiplierSetting, 3.0)); } void SetCameraBoostMultiplier(const float multiplier) { - SetRegistry(CameraBoostMultiplierSetting, multiplier); + AzToolsFramework::SetRegistry(CameraBoostMultiplierSetting, multiplier); } float CameraRotateSpeed() { - return aznumeric_cast(GetRegistry(CameraRotateSpeedSetting, 0.005)); + return aznumeric_cast(AzToolsFramework::GetRegistry(CameraRotateSpeedSetting, 0.005)); } void SetCameraRotateSpeed(const float speed) { - SetRegistry(CameraRotateSpeedSetting, speed); + AzToolsFramework::SetRegistry(CameraRotateSpeedSetting, speed); } float CameraScrollSpeed() { - return aznumeric_cast(GetRegistry(CameraScrollSpeedSetting, 0.02)); + return aznumeric_cast(AzToolsFramework::GetRegistry(CameraScrollSpeedSetting, 0.02)); } void SetCameraScrollSpeed(const float speed) { - SetRegistry(CameraScrollSpeedSetting, speed); + AzToolsFramework::SetRegistry(CameraScrollSpeedSetting, speed); } float CameraDollyMotionSpeed() { - return aznumeric_cast(GetRegistry(CameraDollyMotionSpeedSetting, 0.01)); + return aznumeric_cast(AzToolsFramework::GetRegistry(CameraDollyMotionSpeedSetting, 0.01)); } void SetCameraDollyMotionSpeed(const float speed) { - SetRegistry(CameraDollyMotionSpeedSetting, speed); + AzToolsFramework::SetRegistry(CameraDollyMotionSpeedSetting, speed); } bool CameraOrbitYawRotationInverted() { - return GetRegistry(CameraOrbitYawRotationInvertedSetting, false); + return AzToolsFramework::GetRegistry(CameraOrbitYawRotationInvertedSetting, false); } void SetCameraOrbitYawRotationInverted(const bool inverted) { - SetRegistry(CameraOrbitYawRotationInvertedSetting, inverted); + AzToolsFramework::SetRegistry(CameraOrbitYawRotationInvertedSetting, inverted); } bool CameraPanInvertedX() { - return GetRegistry(CameraPanInvertedXSetting, true); + return AzToolsFramework::GetRegistry(CameraPanInvertedXSetting, true); } void SetCameraPanInvertedX(const bool inverted) { - SetRegistry(CameraPanInvertedXSetting, inverted); + AzToolsFramework::SetRegistry(CameraPanInvertedXSetting, inverted); } bool CameraPanInvertedY() { - return GetRegistry(CameraPanInvertedYSetting, true); + return AzToolsFramework::GetRegistry(CameraPanInvertedYSetting, true); } void SetCameraPanInvertedY(const bool inverted) { - SetRegistry(CameraPanInvertedYSetting, inverted); + AzToolsFramework::SetRegistry(CameraPanInvertedYSetting, inverted); } float CameraPanSpeed() { - return aznumeric_cast(GetRegistry(CameraPanSpeedSetting, 0.01)); + return aznumeric_cast(AzToolsFramework::GetRegistry(CameraPanSpeedSetting, 0.01)); } void SetCameraPanSpeed(float speed) { - SetRegistry(CameraPanSpeedSetting, speed); + AzToolsFramework::SetRegistry(CameraPanSpeedSetting, speed); } float CameraRotateSmoothness() { - return aznumeric_cast(GetRegistry(CameraRotateSmoothnessSetting, 5.0)); + return aznumeric_cast(AzToolsFramework::GetRegistry(CameraRotateSmoothnessSetting, 5.0)); } void SetCameraRotateSmoothness(const float smoothness) { - SetRegistry(CameraRotateSmoothnessSetting, smoothness); + AzToolsFramework::SetRegistry(CameraRotateSmoothnessSetting, smoothness); } float CameraTranslateSmoothness() { - return aznumeric_cast(GetRegistry(CameraTranslateSmoothnessSetting, 5.0)); + return aznumeric_cast(AzToolsFramework::GetRegistry(CameraTranslateSmoothnessSetting, 5.0)); } void SetCameraTranslateSmoothness(const float smoothness) { - SetRegistry(CameraTranslateSmoothnessSetting, smoothness); + AzToolsFramework::SetRegistry(CameraTranslateSmoothnessSetting, smoothness); } bool CameraRotateSmoothingEnabled() { - return GetRegistry(CameraRotateSmoothingSetting, true); + return AzToolsFramework::GetRegistry(CameraRotateSmoothingSetting, true); } void SetCameraRotateSmoothingEnabled(const bool enabled) { - SetRegistry(CameraRotateSmoothingSetting, enabled); + AzToolsFramework::SetRegistry(CameraRotateSmoothingSetting, enabled); } bool CameraTranslateSmoothingEnabled() { - return GetRegistry(CameraTranslateSmoothingSetting, true); + return AzToolsFramework::GetRegistry(CameraTranslateSmoothingSetting, true); } void SetCameraTranslateSmoothingEnabled(const bool enabled) { - SetRegistry(CameraTranslateSmoothingSetting, enabled); + AzToolsFramework::SetRegistry(CameraTranslateSmoothingSetting, enabled); } bool CameraCaptureCursorForLook() { - return GetRegistry(CameraCaptureCursorLookSetting, true); + return AzToolsFramework::GetRegistry(CameraCaptureCursorLookSetting, true); } void SetCameraCaptureCursorForLook(const bool capture) { - SetRegistry(CameraCaptureCursorLookSetting, capture); + AzToolsFramework::SetRegistry(CameraCaptureCursorLookSetting, capture); } float CameraDefaultOrbitDistance() { - return aznumeric_cast(GetRegistry(CameraDefaultOrbitDistanceSetting, 20.0)); + return aznumeric_cast(AzToolsFramework::GetRegistry(CameraDefaultOrbitDistanceSetting, 20.0)); } void SetCameraDefaultOrbitDistance(const float distance) { - SetRegistry(CameraDefaultOrbitDistanceSetting, distance); + AzToolsFramework::SetRegistry(CameraDefaultOrbitDistanceSetting, distance); } AzFramework::InputChannelId CameraTranslateForwardChannelId() { return AzFramework::InputChannelId( - GetRegistry(CameraTranslateForwardIdSetting, AZStd::string("keyboard_key_alphanumeric_W")).c_str()); + AzToolsFramework::GetRegistry(CameraTranslateForwardIdSetting, AZStd::string("keyboard_key_alphanumeric_W")).c_str()); } void SetCameraTranslateForwardChannelId(AZStd::string_view cameraTranslateForwardId) { - SetRegistry(CameraTranslateForwardIdSetting, cameraTranslateForwardId); + AzToolsFramework::SetRegistry(CameraTranslateForwardIdSetting, cameraTranslateForwardId); } AzFramework::InputChannelId CameraTranslateBackwardChannelId() { return AzFramework::InputChannelId( - GetRegistry(CameraTranslateBackwardIdSetting, AZStd::string("keyboard_key_alphanumeric_S")).c_str()); + AzToolsFramework::GetRegistry(CameraTranslateBackwardIdSetting, AZStd::string("keyboard_key_alphanumeric_S")).c_str()); } void SetCameraTranslateBackwardChannelId(AZStd::string_view cameraTranslateBackwardId) { - SetRegistry(CameraTranslateBackwardIdSetting, cameraTranslateBackwardId); + AzToolsFramework::SetRegistry(CameraTranslateBackwardIdSetting, cameraTranslateBackwardId); } AzFramework::InputChannelId CameraTranslateLeftChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraTranslateLeftIdSetting, AZStd::string("keyboard_key_alphanumeric_A")).c_str()); + return AzFramework::InputChannelId( + AzToolsFramework::GetRegistry(CameraTranslateLeftIdSetting, AZStd::string("keyboard_key_alphanumeric_A")).c_str()); } void SetCameraTranslateLeftChannelId(AZStd::string_view cameraTranslateLeftId) { - SetRegistry(CameraTranslateLeftIdSetting, cameraTranslateLeftId); + AzToolsFramework::SetRegistry(CameraTranslateLeftIdSetting, cameraTranslateLeftId); } AzFramework::InputChannelId CameraTranslateRightChannelId() { return AzFramework::InputChannelId( - GetRegistry(CameraTranslateRightIdSetting, AZStd::string("keyboard_key_alphanumeric_D")).c_str()); + AzToolsFramework::GetRegistry(CameraTranslateRightIdSetting, AZStd::string("keyboard_key_alphanumeric_D")).c_str()); } void SetCameraTranslateRightChannelId(AZStd::string_view cameraTranslateRightId) { - SetRegistry(CameraTranslateRightIdSetting, cameraTranslateRightId); + AzToolsFramework::SetRegistry(CameraTranslateRightIdSetting, cameraTranslateRightId); } AzFramework::InputChannelId CameraTranslateUpChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraTranslateUpIdSetting, AZStd::string("keyboard_key_alphanumeric_E")).c_str()); + return AzFramework::InputChannelId( + AzToolsFramework::GetRegistry(CameraTranslateUpIdSetting, AZStd::string("keyboard_key_alphanumeric_E")).c_str()); } void SetCameraTranslateUpChannelId(AZStd::string_view cameraTranslateUpId) { - SetRegistry(CameraTranslateUpIdSetting, cameraTranslateUpId); + AzToolsFramework::SetRegistry(CameraTranslateUpIdSetting, cameraTranslateUpId); } AzFramework::InputChannelId CameraTranslateDownChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraTranslateDownIdSetting, AZStd::string("keyboard_key_alphanumeric_Q")).c_str()); + return AzFramework::InputChannelId( + AzToolsFramework::GetRegistry(CameraTranslateDownIdSetting, AZStd::string("keyboard_key_alphanumeric_Q")).c_str()); } void SetCameraTranslateDownChannelId(AZStd::string_view cameraTranslateDownId) { - SetRegistry(CameraTranslateDownIdSetting, cameraTranslateDownId); + AzToolsFramework::SetRegistry(CameraTranslateDownIdSetting, cameraTranslateDownId); } AzFramework::InputChannelId CameraTranslateBoostChannelId() { return AzFramework::InputChannelId( - GetRegistry(CameraTranslateBoostIdSetting, AZStd::string("keyboard_key_modifier_shift_l")).c_str()); + AzToolsFramework::GetRegistry(CameraTranslateBoostIdSetting, AZStd::string("keyboard_key_modifier_shift_l")).c_str()); } void SetCameraTranslateBoostChannelId(AZStd::string_view cameraTranslateBoostId) { - SetRegistry(CameraTranslateBoostIdSetting, cameraTranslateBoostId); + AzToolsFramework::SetRegistry(CameraTranslateBoostIdSetting, cameraTranslateBoostId); } AzFramework::InputChannelId CameraOrbitChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraOrbitIdSetting, AZStd::string("keyboard_key_modifier_alt_l")).c_str()); + return AzFramework::InputChannelId( + AzToolsFramework::GetRegistry(CameraOrbitIdSetting, AZStd::string("keyboard_key_modifier_alt_l")).c_str()); } void SetCameraOrbitChannelId(AZStd::string_view cameraOrbitId) { - SetRegistry(CameraOrbitIdSetting, cameraOrbitId); + AzToolsFramework::SetRegistry(CameraOrbitIdSetting, cameraOrbitId); } AzFramework::InputChannelId CameraFreeLookChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraFreeLookIdSetting, AZStd::string("mouse_button_right")).c_str()); + return AzFramework::InputChannelId( + AzToolsFramework::GetRegistry(CameraFreeLookIdSetting, AZStd::string("mouse_button_right")).c_str()); } void SetCameraFreeLookChannelId(AZStd::string_view cameraFreeLookId) { - SetRegistry(CameraFreeLookIdSetting, cameraFreeLookId); + AzToolsFramework::SetRegistry(CameraFreeLookIdSetting, cameraFreeLookId); } AzFramework::InputChannelId CameraFreePanChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraFreePanIdSetting, AZStd::string("mouse_button_middle")).c_str()); + return AzFramework::InputChannelId( + AzToolsFramework::GetRegistry(CameraFreePanIdSetting, AZStd::string("mouse_button_middle")).c_str()); } void SetCameraFreePanChannelId(AZStd::string_view cameraFreePanId) { - SetRegistry(CameraFreePanIdSetting, cameraFreePanId); + AzToolsFramework::SetRegistry(CameraFreePanIdSetting, cameraFreePanId); } AzFramework::InputChannelId CameraOrbitLookChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraOrbitLookIdSetting, AZStd::string("mouse_button_left")).c_str()); + return AzFramework::InputChannelId( + AzToolsFramework::GetRegistry(CameraOrbitLookIdSetting, AZStd::string("mouse_button_left")).c_str()); } void SetCameraOrbitLookChannelId(AZStd::string_view cameraOrbitLookId) { - SetRegistry(CameraOrbitLookIdSetting, cameraOrbitLookId); + AzToolsFramework::SetRegistry(CameraOrbitLookIdSetting, cameraOrbitLookId); } AzFramework::InputChannelId CameraOrbitDollyChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraOrbitDollyIdSetting, AZStd::string("mouse_button_right")).c_str()); + return AzFramework::InputChannelId( + AzToolsFramework::GetRegistry(CameraOrbitDollyIdSetting, AZStd::string("mouse_button_right")).c_str()); } void SetCameraOrbitDollyChannelId(AZStd::string_view cameraOrbitDollyId) { - SetRegistry(CameraOrbitDollyIdSetting, cameraOrbitDollyId); + AzToolsFramework::SetRegistry(CameraOrbitDollyIdSetting, cameraOrbitDollyId); } AzFramework::InputChannelId CameraOrbitPanChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraOrbitPanIdSetting, AZStd::string("mouse_button_middle")).c_str()); + return AzFramework::InputChannelId( + AzToolsFramework::GetRegistry(CameraOrbitPanIdSetting, AZStd::string("mouse_button_middle")).c_str()); } void SetCameraOrbitPanChannelId(AZStd::string_view cameraOrbitPanId) { - SetRegistry(CameraOrbitPanIdSetting, cameraOrbitPanId); + AzToolsFramework::SetRegistry(CameraOrbitPanIdSetting, cameraOrbitPanId); } AzFramework::InputChannelId CameraFocusChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraFocusIdSetting, AZStd::string("keyboard_key_alphanumeric_X")).c_str()); + return AzFramework::InputChannelId( + AzToolsFramework::GetRegistry(CameraFocusIdSetting, AZStd::string("keyboard_key_alphanumeric_X")).c_str()); } void SetCameraFocusChannelId(AZStd::string_view cameraFocusId) { - SetRegistry(CameraFocusIdSetting, cameraFocusId); + AzToolsFramework::SetRegistry(CameraFocusIdSetting, cameraFocusId); } } // namespace SandboxEditor diff --git a/Code/Editor/EditorViewportWidget.cpp b/Code/Editor/EditorViewportWidget.cpp index 8f94afd514..7323da9e95 100644 --- a/Code/Editor/EditorViewportWidget.cpp +++ b/Code/Editor/EditorViewportWidget.cpp @@ -53,7 +53,6 @@ #include // CryCommon -#include #include // AzFramework @@ -98,9 +97,6 @@ #include -#include -#include - AZ_CVAR( bool, ed_visibility_logTiming, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Output the timing of the new IVisibilitySystem query"); @@ -476,7 +472,7 @@ void EditorViewportWidget::Update() { auto start = std::chrono::steady_clock::now(); - m_entityVisibilityQuery.UpdateVisibility(GetCameraState()); + m_entityVisibilityQuery.UpdateVisibility(m_renderViewport->GetCameraState()); if (ed_visibility_logTiming) { @@ -552,22 +548,6 @@ void EditorViewportWidget::OnEditorNotifyEvent(EEditorNotifyEvent event) // this should only occur for the main viewport and no others. ShowCursor(); - // If the user has selected game mode, enable outputting to any attached HMD and properly size the context - // to the resolution specified by the VR device. - if (gSettings.bEnableGameModeVR) - { - const AZ::VR::HMDDeviceInfo* deviceInfo = nullptr; - EBUS_EVENT_RESULT(deviceInfo, AZ::VR::HMDDeviceRequestBus, GetDeviceInfo); - AZ_Warning("Render Viewport", deviceInfo, "No VR device detected"); - - if (deviceInfo) - { - // Note: This may also need to adjust the viewport size - SetActiveWindow(); - SetFocus(); - SetSelected(true); - } - } SetCurrentCursor(STD_CURSOR_GAME); if (ShouldPreviewFullscreen()) @@ -734,7 +714,7 @@ void EditorViewportWidget::RenderAll() m_debugDisplay->DepthTestOff(); m_manipulatorManager->DrawManipulators( - *m_debugDisplay, GetCameraState(), + *m_debugDisplay, m_renderViewport->GetCameraState(), BuildMouseInteractionInternal( AztfVi::MouseButtons(AztfVi::TranslateMouseButtons(QGuiApplication::mouseButtons())), keyboardModifiers, BuildMousePick(WidgetToViewport(mapFromGlobal(QCursor::pos()))))); @@ -898,31 +878,11 @@ void EditorViewportWidget::OnMenuSelectCurrentCamera() } } -AzFramework::CameraState EditorViewportWidget::GetCameraState() -{ - return m_renderViewport->GetCameraState(); -} - -AZ::Vector3 EditorViewportWidget::PickTerrain(const AzFramework::ScreenPoint& point) -{ - return LYVec3ToAZVec3(ViewToWorld(AzToolsFramework::ViewportInteraction::QPointFromScreenPoint(point), nullptr, true)); -} - -float EditorViewportWidget::TerrainHeight(const AZ::Vector2& position) -{ - return GetIEditor()->GetTerrainElevation(position.GetX(), position.GetY()); -} - void EditorViewportWidget::FindVisibleEntities(AZStd::vector& visibleEntitiesOut) { visibleEntitiesOut.assign(m_entityVisibilityQuery.Begin(), m_entityVisibilityQuery.End()); } -AzFramework::ScreenPoint EditorViewportWidget::ViewportWorldToScreen(const AZ::Vector3& worldPosition) -{ - return m_renderViewport->ViewportWorldToScreen(worldPosition); -} - QWidget* EditorViewportWidget::GetWidgetForViewportContextMenu() { return this; @@ -1759,13 +1719,6 @@ float EditorViewportWidget::GetScreenScaleFactor([[maybe_unused]] const Vec3& wo AZ_Error("CryLegacy", false, "EditorViewportWidget::GetScreenScaleFactor not implemented"); return 1.f; } -////////////////////////////////////////////////////////////////////////// -float EditorViewportWidget::GetScreenScaleFactor(const CCamera& camera, const Vec3& object_position) -{ - Vec3 camPos = camera.GetPosition(); - float dist = camPos.GetDistance(object_position); - return dist; -} ////////////////////////////////////////////////////////////////////////// bool EditorViewportWidget::CheckRespondToInput() const @@ -1786,7 +1739,6 @@ bool EditorViewportWidget::CheckRespondToInput() const ////////////////////////////////////////////////////////////////////////// bool EditorViewportWidget::HitTest(const QPoint& point, HitContext& hitInfo) { - hitInfo.camera = nullptr; hitInfo.pExcludedObject = GetCameraObject(); return QtViewport::HitTest(point, hitInfo); } @@ -2160,7 +2112,7 @@ bool EditorViewportWidget::GetActiveCameraState(AzFramework::CameraState& camera { if (m_pPrimaryViewport == this) { - cameraState = GetCameraState(); + cameraState = m_renderViewport->GetCameraState(); return true; } @@ -2491,12 +2443,6 @@ bool EditorViewportWidget::ShouldPreviewFullscreen() const return false; } - // Not supported in VR - if (gSettings.bEnableGameModeVR) - { - return false; - } - // If level not loaded, don't preview in fullscreen (preview shouldn't work at all without a level, but it does) if (auto ge = GetIEditor()->GetGameEngine()) { diff --git a/Code/Editor/EditorViewportWidget.h b/Code/Editor/EditorViewportWidget.h index 7d8daba8c5..3d52b2416d 100644 --- a/Code/Editor/EditorViewportWidget.h +++ b/Code/Editor/EditorViewportWidget.h @@ -10,7 +10,6 @@ #pragma once #if !defined(Q_MOC_RUN) -#include #include @@ -171,7 +170,6 @@ private: void ViewToWorldRay(const QPoint& vp, Vec3& raySrc, Vec3& rayDir) const override; Vec3 ViewToWorldNormal(const QPoint& vp, bool onlyTerrain, bool bTestRenderMesh = false) override; float GetScreenScaleFactor(const Vec3& worldPoint) const override; - float GetScreenScaleFactor(const CCamera& camera, const Vec3& object_position) override; float GetAspectRatio() const override; bool HitTest(const QPoint& point, HitContext& hitInfo) override; bool IsBoundsVisible(const AABB& box) const override; @@ -207,8 +205,6 @@ private: void* GetSystemCursorConstraintWindow() const override; // AzToolsFramework::MainEditorViewportInteractionRequestBus overrides ... - AZ::Vector3 PickTerrain(const AzFramework::ScreenPoint& point) override; - float TerrainHeight(const AZ::Vector2& position) override; bool ShowingWorldSpace() override; QWidget* GetWidgetForViewportContextMenu() override; @@ -295,9 +291,6 @@ private: // This switches the active camera to the next one in the list of (default, all custom cams). void CycleCamera(); - AzFramework::CameraState GetCameraState(); - AzFramework::ScreenPoint ViewportWorldToScreen(const AZ::Vector3& worldPosition); - QPoint WidgetToViewport(const QPoint& point) const; QPoint ViewportToWidget(const QPoint& point) const; QSize WidgetToViewport(const QSize& size) const; diff --git a/Code/Editor/ErrorDialog.cpp b/Code/Editor/ErrorDialog.cpp index 0a45b2bf89..e43df75947 100644 --- a/Code/Editor/ErrorDialog.cpp +++ b/Code/Editor/ErrorDialog.cpp @@ -25,9 +25,9 @@ namespace SandboxEditor connect(m_ui->okButton, &QPushButton::clicked, this, &ErrorDialog::OnOK); connect( m_ui->messages, - SIGNAL(itemSelectionChanged()), + &QTreeWidget::itemSelectionChanged, this, - SLOT(MessageSelectionChanged())); + &ErrorDialog::MessageSelectionChanged); } ErrorDialog::~ErrorDialog() diff --git a/Code/Editor/Export/ExportManager.cpp b/Code/Editor/Export/ExportManager.cpp index 5527aa6ce6..b27b7e0387 100644 --- a/Code/Editor/Export/ExportManager.cpp +++ b/Code/Editor/Export/ExportManager.cpp @@ -35,20 +35,8 @@ #include "Resource.h" #include "Plugins/ComponentEntityEditorPlugin/Objects/ComponentEntityObject.h" -#include -#include - namespace { - inline Export::Vector3D Vec3ToVector3D(const Vec3& vec) - { - Export::Vector3D ret; - ret.x = vec.x; - ret.y = vec.y; - ret.z = vec.z; - return ret; - } - const float kTangentDelta = 0.01f; const float kAspectRatio = 1.777778f; const int kReserveCount = 7; // x,y,z,rot_x,rot_y,rot_z,fov @@ -106,22 +94,22 @@ void Export::CData::Clear() // CExportManager CExportManager::CExportManager() : m_isPrecaching(false) - , m_pBaseObj(nullptr) - , m_FBXBakedExportFPS(0.0f) , m_fScale(100.0f) + , m_bAnimationExport(false) + , m_pBaseObj(nullptr) , // this scale is used by CryEngine RC - m_bAnimationExport(false) + m_FBXBakedExportFPS(0.0f) , m_bExportLocalCoords(false) + , m_bExportOnlyPrimaryCamera(false) , m_numberOfExportFrames(0) , m_pivotEntityObject(nullptr) , m_bBakedKeysSequenceExport(true) , m_animTimeExportPrimarySequenceCurrentTime(0.0f) , m_animKeyTimeExport(true) , m_soundKeyTimeExport(true) - , m_bExportOnlyPrimaryCamera(false) { - RegisterExporter(new COBJExporter()); - RegisterExporter(new COCMExporter()); + CExportManager::RegisterExporter(new COBJExporter()); + CExportManager::RegisterExporter(new COCMExporter()); } @@ -313,203 +301,6 @@ void CExportManager::AddEntityAnimationData(AZ::EntityId entityId) ProcessEntityAnimationTrack(entityId, pObj, AnimParamType::Rotation); } - -void CExportManager::AddMesh(Export::CObject* pObj, const IIndexedMesh* pIndMesh, Matrix34A* pTm) -{ - if (m_isPrecaching || !pObj) - { - return; - } - - pObj->m_MeshHash = reinterpret_cast(pIndMesh); - IIndexedMesh::SMeshDescription meshDesc; - pIndMesh->GetMeshDescription(meshDesc); - - // if we have subset of meshes we need to duplicate vertices, - // keep transformation of submesh, - // and store new offset for indices - int newOffsetIndex = pObj->GetVertexCount(); - - if (meshDesc.m_nVertCount) - { - pObj->m_vertices.reserve(meshDesc.m_nVertCount + newOffsetIndex); - pObj->m_normals.reserve(meshDesc.m_nVertCount + newOffsetIndex); - } - - for (int v = 0; v < meshDesc.m_nVertCount; ++v) - { - Vec3 n = meshDesc.m_pNorms[v].GetN(); - Vec3 tmp = (meshDesc.m_pVerts ? meshDesc.m_pVerts[v] : meshDesc.m_pVertsF16[v].ToVec3()); - if (pTm) - { - tmp = pTm->TransformPoint(tmp); - } - - pObj->m_vertices.push_back(Vec3ToVector3D(tmp * m_fScale)); - pObj->m_normals.push_back(Vec3ToVector3D(n)); - } - - if (meshDesc.m_nCoorCount) - { - pObj->m_texCoords.reserve(meshDesc.m_nCoorCount + newOffsetIndex); - } - - for (int v = 0; v < meshDesc.m_nCoorCount; ++v) - { - Vec2 uv = meshDesc.m_pTexCoord[v].GetUV(); - uv.y = 1.0f - uv.y; - pObj->m_texCoords.push_back({uv.x,uv.y}); - } - - if (pIndMesh->GetSubSetCount() && !(pIndMesh->GetSubSetCount() == 1 && pIndMesh->GetSubSet(0).nNumIndices == 0)) - { - for (int i = 0; i < pIndMesh->GetSubSetCount(); ++i) - { - Export::CMesh* pMesh = new Export::CMesh(); - - const SMeshSubset& sms = pIndMesh->GetSubSet(i); - const vtx_idx* pIndices = &meshDesc.m_pIndices[sms.nFirstIndexId]; - int nTris = sms.nNumIndices / 3; - pMesh->m_faces.reserve(nTris); - for (int f = 0; f < nTris; ++f) - { - Export::Face face; - face.idx[0] = *(pIndices++) + newOffsetIndex; - face.idx[1] = *(pIndices++) + newOffsetIndex; - face.idx[2] = *(pIndices++) + newOffsetIndex; - pMesh->m_faces.push_back(face); - } - - pObj->m_meshes.push_back(pMesh); - } - } - else - { - Export::CMesh* pMesh = new Export::CMesh(); - if (meshDesc.m_nFaceCount == 0 && meshDesc.m_nIndexCount != 0 && meshDesc.m_pIndices != nullptr) - { - const vtx_idx* pIndices = &meshDesc.m_pIndices[0]; - int nTris = meshDesc.m_nIndexCount / 3; - pMesh->m_faces.reserve(nTris); - for (int f = 0; f < nTris; ++f) - { - Export::Face face; - face.idx[0] = *(pIndices++) + newOffsetIndex; - face.idx[1] = *(pIndices++) + newOffsetIndex; - face.idx[2] = *(pIndices++) + newOffsetIndex; - pMesh->m_faces.push_back(face); - } - } - else - { - pMesh->m_faces.reserve(meshDesc.m_nFaceCount); - for (int f = 0; f < meshDesc.m_nFaceCount; ++f) - { - Export::Face face; - face.idx[0] = meshDesc.m_pFaces[f].v[0]; - face.idx[1] = meshDesc.m_pFaces[f].v[1]; - face.idx[2] = meshDesc.m_pFaces[f].v[2]; - pMesh->m_faces.push_back(face); - } - } - - pObj->m_meshes.push_back(pMesh); - } -} - - -bool CExportManager::AddStatObj(Export::CObject* pObj, IStatObj* pStatObj, Matrix34A* pTm) -{ - IIndexedMesh* pIndMesh = nullptr; - - if (pStatObj->GetSubObjectCount()) - { - for (int i = 0; i < pStatObj->GetSubObjectCount(); i++) - { - IStatObj::SSubObject* pSubObj = pStatObj->GetSubObject(i); - if (pSubObj && pSubObj->nType == STATIC_SUB_OBJECT_MESH && pSubObj->pStatObj) - { - pIndMesh = nullptr; - if (m_isOccluder) - { - if (pSubObj->pStatObj->GetLodObject(2)) - { - pIndMesh = pSubObj->pStatObj->GetLodObject(2)->GetIndexedMesh(true); - } - if (!pIndMesh && pSubObj->pStatObj->GetLodObject(1)) - { - pIndMesh = pSubObj->pStatObj->GetLodObject(1)->GetIndexedMesh(true); - } - } - if (!pIndMesh) - { - pIndMesh = pSubObj->pStatObj->GetIndexedMesh(true); - } - if (pIndMesh) - { - AddMesh(pObj, pIndMesh, pTm); - } - } - } - } - - if (!pIndMesh) - { - if (m_isOccluder) - { - if (pStatObj->GetLodObject(2)) - { - pIndMesh = pStatObj->GetLodObject(2)->GetIndexedMesh(true); - } - if (!pIndMesh && pStatObj->GetLodObject(1)) - { - pIndMesh = pStatObj->GetLodObject(1)->GetIndexedMesh(true); - } - } - if (!pIndMesh) - { - pIndMesh = pStatObj->GetIndexedMesh(true); - } - if (pIndMesh) - { - AddMesh(pObj, pIndMesh, pTm); - } - } - - return true; -} - -bool CExportManager::AddMeshes(Export::CObject* pObj) -{ - if (m_pBaseObj->GetType() == OBJTYPE_AZENTITY) - { - CEntityObject* pEntityObject = (CEntityObject*)m_pBaseObj; - IRenderNode* pEngineNode = pEntityObject->GetEngineNode(); - - if (pEngineNode) - { - if (!m_isPrecaching) - { - for (int i = 0; i < pEngineNode->GetSlotCount(); ++i) - { - Matrix34A tm; - IStatObj* pStatObj = pEngineNode->GetEntityStatObj(i, 0, &tm); - if (pStatObj) - { - Matrix34A objTM = m_pBaseObj->GetWorldTM(); - objTM.Invert(); - tm = objTM * tm; - AddStatObj(pObj, pStatObj, &tm); - } - } - } - } - } - - return true; -} - - bool CExportManager::AddObject(CBaseObject* pBaseObj) { if (m_isOccluder) @@ -531,7 +322,6 @@ bool CExportManager::AddObject(CBaseObject* pBaseObj) if (m_isPrecaching) { - AddMeshes(nullptr); return true; } @@ -542,7 +332,6 @@ bool CExportManager::AddObject(CBaseObject* pBaseObj) m_objectMap[pBaseObj] = int(m_data.m_objects.size() - 1); - AddMeshes(pObj); m_pBaseObj = nullptr; return true; @@ -1227,15 +1016,6 @@ bool CExportManager::ImportFromFile(const char* filename) return bRet; } -bool CExportManager::ExportSingleStatObj(IStatObj* pStatObj, const char* filename) -{ - Export::CObject* pObj = new Export::CObject(Path::GetFileName(filename).toUtf8().data()); - AddStatObj(pObj, pStatObj); - m_data.m_objects.push_back(pObj); - ExportToFile(filename, true); - return true; -} - void CExportManager::SaveNodeKeysTimeToXML() { CTrackViewSequence* pSequence = GetIEditor()->GetAnimation()->GetSequence(); diff --git a/Code/Editor/Export/ExportManager.h b/Code/Editor/Export/ExportManager.h index 14318be957..6908bcf0f7 100644 --- a/Code/Editor/Export/ExportManager.h +++ b/Code/Editor/Export/ExportManager.h @@ -139,18 +139,11 @@ public: bool ImportFromFile(const char* filename); const Export::CData& GetData() const {return m_data; }; - //! Exports the stat obj to the obj file specified - //! returns true if succeeded, otherwise false - bool ExportSingleStatObj(IStatObj* pStatObj, const char* filename) override; - void SetBakedKeysSequenceExport(bool bBaked){m_bBakedKeysSequenceExport = bBaked; }; void SaveNodeKeysTimeToXML(); private: - void AddMesh(Export::CObject* pObj, const IIndexedMesh* pIndMesh, Matrix34A* pTm = nullptr); - bool AddStatObj(Export::CObject* pObj, IStatObj* pStatObj, Matrix34A* pTm = nullptr); - bool AddMeshes(Export::CObject* pObj); bool AddObject(CBaseObject* pBaseObj); void SolveHierarchy(); diff --git a/Code/Editor/GameEngine.cpp b/Code/Editor/GameEngine.cpp index 5f3545e593..e82832fc21 100644 --- a/Code/Editor/GameEngine.cpp +++ b/Code/Editor/GameEngine.cpp @@ -35,7 +35,6 @@ // CryCommon #include -#include #include // Editor @@ -49,9 +48,6 @@ #include "Include/IObjectManager.h" #include "ActionManager.h" -// Including this too early will result in a linker error -#include - // Implementation of System Callback structure. struct SSystemUserCallback : public ISystemUserCallback @@ -160,20 +156,20 @@ struct SSystemUserCallback } } - int ShowMessage(const char* text, const char* caption, unsigned int uType) override + void ShowMessage(const char* text, const char* caption, unsigned int uType) override { if (CCryEditApp::instance()->IsInAutotestMode()) { - return IDOK; + return; } const UINT kMessageBoxButtonMask = 0x000f; if (!GetIEditor()->IsInGameMode() && (uType == 0 || uType == MB_OK || !(uType & kMessageBoxButtonMask))) { static_cast(GetIEditor())->AddErrorMessage(text, caption); - return IDOK; + return; } - return CryMessageBox(text, caption, uType); + CryMessageBox(text, caption, uType); } void OnSplashScreenDone() @@ -242,8 +238,7 @@ private: AZ_PUSH_DISABLE_WARNING(4273, "-Wunknown-warning-option") CGameEngine::CGameEngine() - : m_gameDll(nullptr) - , m_bIgnoreUpdates(false) + : m_bIgnoreUpdates(false) , m_ePendingGameMode(ePGM_NotPending) , m_modalWindowDismisser(nullptr) AZ_POP_DISABLE_WARNING @@ -253,7 +248,7 @@ AZ_POP_DISABLE_WARNING m_bInGameMode = false; m_bSimulationMode = false; m_bSyncPlayerPosition = true; - m_hSystemHandle = nullptr; + m_hSystemHandle.reset(nullptr); m_bJustCreated = false; m_levelName = "Untitled"; m_levelExtension = EditorUtils::LevelFile::GetDefaultFileExtension(); @@ -268,18 +263,10 @@ AZ_POP_DISABLE_WARNING GetIEditor()->UnregisterNotifyListener(this); m_pISystem->GetIMovieSystem()->SetCallback(nullptr); - if (m_gameDll) - { - CryFreeLibrary(m_gameDll); - } - delete m_pISystem; m_pISystem = nullptr; - if (m_hSystemHandle) - { - CryFreeLibrary(m_hSystemHandle); - } + m_hSystemHandle.reset(nullptr); delete m_pSystemUserCallback; } @@ -347,18 +334,19 @@ AZ::Outcome CGameEngine::Init( HWND hwndForInputSystem) { m_pSystemUserCallback = new SSystemUserCallback(logo); - m_hSystemHandle = CryLoadLibraryDefName("CrySystem"); - if (!m_hSystemHandle) + constexpr const char* crySystemLibraryName = AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX "CrySystem" AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION; + + m_hSystemHandle = AZ::DynamicModuleHandle::Create(crySystemLibraryName); + if (!m_hSystemHandle->Load(true)) { - auto errorMessage = AZStd::string::format("%s Loading Failed", CryLibraryDefName("CrySystem")); + auto errorMessage = AZStd::string::format("%s Loading Failed", crySystemLibraryName); Error(errorMessage.c_str()); return AZ::Failure(errorMessage); } PFNCREATESYSTEMINTERFACE pfnCreateSystemInterface = - (PFNCREATESYSTEMINTERFACE)CryGetProcAddress(m_hSystemHandle, "CreateSystemInterface"); - + m_hSystemHandle->GetFunction("CreateSystemInterface"); SSystemInitParams sip; @@ -606,13 +594,6 @@ void CGameEngine::SwitchToInEditor() // Enable accelerators. GetIEditor()->EnableAcceleratos(true); - - // reset UI system - if (gEnv->pLyShine) - { - gEnv->pLyShine->Reset(); - } - // [Anton] - order changed, see comments for CGameEngine::SetSimulationMode //! Send event to switch out of game. GetIEditor()->GetObjectManager()->SendEvent(EVENT_OUTOFGAME); @@ -833,7 +814,7 @@ void CGameEngine::Update() if (gEnv->pSystem) { gEnv->pSystem->UpdatePreTickBus(); - componentApplication->Tick(gEnv->pTimer->GetFrameTime(ITimer::ETIMER_GAME)); + componentApplication->Tick(); gEnv->pSystem->UpdatePostTickBus(); } @@ -849,7 +830,7 @@ void CGameEngine::Update() unsigned int updateFlags = ESYSUPDATE_EDITOR; GetIEditor()->GetAnimation()->Update(); GetIEditor()->GetSystem()->UpdatePreTickBus(updateFlags); - componentApplication->Tick(gEnv->pTimer->GetFrameTime(ITimer::ETIMER_GAME)); + componentApplication->Tick(); GetIEditor()->GetSystem()->UpdatePostTickBus(updateFlags); } } diff --git a/Code/Editor/GameEngine.h b/Code/Editor/GameEngine.h index 4d183cc38e..5a77fc2fb1 100644 --- a/Code/Editor/GameEngine.h +++ b/Code/Editor/GameEngine.h @@ -8,17 +8,11 @@ // Description : The game engine for editor - - -#ifndef CRYINCLUDE_EDITOR_GAMEENGINE_H -#define CRYINCLUDE_EDITOR_GAMEENGINE_H - #pragma once #if !defined(Q_MOC_RUN) #include #include "LogFile.h" -#include "CryListenerSet.h" #include "Util/ModalWindowDismisser.h" #endif @@ -28,6 +22,8 @@ struct IInitializeUIInfo; #include #include +#include + class ThreadedOnErrorHandler : public QObject { Q_OBJECT @@ -124,11 +120,6 @@ public: return s_pakModifyMutex; } - inline HMODULE GetGameModule() const - { - return m_gameDll; - } - private: void SetGameMode(bool inGame); void SwitchToInGame(); @@ -150,8 +141,7 @@ private: AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING Matrix34 m_playerViewTM; struct SSystemUserCallback* m_pSystemUserCallback; - HMODULE m_hSystemHandle; - HMODULE m_gameDll; + AZStd::unique_ptr m_hSystemHandle; enum EPendingGameMode { ePGM_NotPending, @@ -163,5 +153,3 @@ private: AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING }; - -#endif // CRYINCLUDE_EDITOR_GAMEENGINE_H diff --git a/Code/Editor/GameExporter.cpp b/Code/Editor/GameExporter.cpp index ae1095b03a..a768861b90 100644 --- a/Code/Editor/GameExporter.cpp +++ b/Code/Editor/GameExporter.cpp @@ -146,13 +146,11 @@ bool CGameExporter::Export(unsigned int flags, [[maybe_unused]] EEndian eExportE exportSuccessful = false; } - if (exportSuccessful) + if (exportSuccessful && m_bAutoExportMode) { - if (m_bAutoExportMode) - { - // Remove read-only flags. - CrySetFileAttributes(m_levelPak.m_sPath.toUtf8().data(), FILE_ATTRIBUTE_NORMAL); - } + // Remove read-only flags. + auto perms = QFile::permissions(m_levelPak.m_sPath) | QFile::Permission::WriteOwner; + QFile::setPermissions(m_levelPak.m_sPath, perms); } ////////////////////////////////////////////////////////////////////////// diff --git a/Code/Editor/GotoPositionDlg.cpp b/Code/Editor/GotoPositionDlg.cpp index 84d149de58..37f55f19ea 100644 --- a/Code/Editor/GotoPositionDlg.cpp +++ b/Code/Editor/GotoPositionDlg.cpp @@ -6,7 +6,6 @@ * */ - #include "GotoPositionDlg.h" #include "EditorDefs.h" @@ -25,6 +24,17 @@ AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING #include AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING +void GotoPositionPitchConstraints::DeterminePitchRange(const AngleRangeConfigureFn& configurePitchRangeFn) const +{ + const auto [pitchMinRadians, pitchMaxRadians] = AzFramework::CameraPitchMinMaxRadians(); + configurePitchRangeFn(AZ::RadToDeg(pitchMinRadians), AZ::RadToDeg(pitchMaxRadians)); +} + +float GotoPositionPitchConstraints::PitchClampedRadians(float pitchDegrees) const +{ + return AzFramework::ClampPitchRotation(AZ::DegToRad(pitchDegrees)); +} + GotoPositionDialog::GotoPositionDialog(QWidget* parent) : QDialog(parent) , m_ui(new Ui::GotoPositionDialog) @@ -55,20 +65,23 @@ void GotoPositionDialog::OnInitDialog() const auto yawDegrees = AZ::RadToDeg(cameraRotation.GetZ()); // position - m_ui->m_dymX->setRange(-64000.0, 64000.0); + const double CameraPositionExtent = 64000.0; + m_ui->m_dymX->setRange(-CameraPositionExtent, CameraPositionExtent); m_ui->m_dymX->setValue(cameraTranslation.GetX()); - - m_ui->m_dymY->setRange(-64000.0, 64000.0); + m_ui->m_dymY->setRange(-CameraPositionExtent, CameraPositionExtent); m_ui->m_dymY->setValue(cameraTranslation.GetY()); - - m_ui->m_dymZ->setRange(-64000.0, 64000.0); + m_ui->m_dymZ->setRange(-CameraPositionExtent, CameraPositionExtent); m_ui->m_dymZ->setValue(cameraTranslation.GetZ()); // rotation - m_ui->m_dymAnglePitch->setRange(-180.0, 180.0); + m_gotoPositionPitchConstraints.DeterminePitchRange( + [this](const float minPitchDegrees, const float maxPitchDegrees) + { + m_ui->m_dymAnglePitch->setRange(minPitchDegrees, maxPitchDegrees); + }); m_ui->m_dymAnglePitch->setValue(pitchDegrees); - m_ui->m_dymAngleYaw->setRange(-180.0, 180.0); + m_ui->m_dymAngleYaw->setRange(-360, 360); m_ui->m_dymAngleYaw->setValue(yawDegrees); // ensure the goto button is highlighted correctly. @@ -108,12 +121,13 @@ void GotoPositionDialog::OnUpdateNumbers() void GotoPositionDialog::accept() { - SandboxEditor::InterpolateDefaultViewportCameraToTransform( - AZ::Vector3( - aznumeric_cast(m_ui->m_dymX->value()), aznumeric_cast(m_ui->m_dymY->value()), - aznumeric_cast(m_ui->m_dymZ->value())), - AZ::DegToRad(aznumeric_cast(m_ui->m_dymAnglePitch->value())), - AZ::DegToRad(aznumeric_cast(m_ui->m_dymAngleYaw->value()))); + const auto position = AZ::Vector3( + aznumeric_cast(m_ui->m_dymX->value()), aznumeric_cast(m_ui->m_dymY->value()), + aznumeric_cast(m_ui->m_dymZ->value())); + const auto pitchRadians = m_gotoPositionPitchConstraints.PitchClampedRadians(aznumeric_cast(m_ui->m_dymAnglePitch->value())); + const auto yawRadians = AZ::DegToRad(aznumeric_cast(m_ui->m_dymAngleYaw->value())); + + SandboxEditor::InterpolateDefaultViewportCameraToTransform(position, pitchRadians, yawRadians); QDialog::accept(); } diff --git a/Code/Editor/GotoPositionDlg.h b/Code/Editor/GotoPositionDlg.h index ef46b9cbc8..5b627fcebb 100644 --- a/Code/Editor/GotoPositionDlg.h +++ b/Code/Editor/GotoPositionDlg.h @@ -6,21 +6,33 @@ * */ - #pragma once #if !defined(Q_MOC_RUN) #include #endif +#include + +#include + namespace Ui { class GotoPositionDialog; } +//! Utility to deal with ensuring camera pitch values are in the expected range. +struct GotoPositionPitchConstraints +{ + using AngleRangeConfigureFn = AZStd::function; + //! Notify a callback with the min and max camera pitch constraints (no tolerance included). + SANDBOX_API void DeterminePitchRange(const AngleRangeConfigureFn& configurePitchRangeFn) const; + //! Returns the clamped pitch value (including tolerance with range extents). + SANDBOX_API float PitchClampedRadians(float pitchDegrees) const; +}; + //! GotoPositionDialog for setting camera position and rotation. -class GotoPositionDialog - : public QDialog +class GotoPositionDialog : public QDialog { Q_OBJECT @@ -39,5 +51,6 @@ public: QString m_transform; private: + GotoPositionPitchConstraints m_gotoPositionPitchConstraints; QScopedPointer m_ui; }; diff --git a/Code/Editor/IEditor.h b/Code/Editor/IEditor.h index 7c49819d83..48c8392f32 100644 --- a/Code/Editor/IEditor.h +++ b/Code/Editor/IEditor.h @@ -508,8 +508,6 @@ struct IEditor virtual CBaseObject* NewObject(const char* typeName, const char* fileName = "", const char* name = "", float x = 0.0f, float y = 0.0f, float z = 0.0f, bool modifyDoc = true) = 0; //! Delete object virtual void DeleteObject(CBaseObject* obj) = 0; - //! Clone object - virtual CBaseObject* CloneObject(CBaseObject* obj) = 0; //! Get current selection group virtual CSelectionGroup* GetSelection() = 0; virtual CBaseObject* GetSelectedObject() = 0; diff --git a/Code/Editor/IEditorImpl.cpp b/Code/Editor/IEditorImpl.cpp index 66c64d5bef..e5df6be58e 100644 --- a/Code/Editor/IEditorImpl.cpp +++ b/Code/Editor/IEditorImpl.cpp @@ -397,11 +397,6 @@ void CEditorImpl::Update() // Make sure this is not called recursively m_bUpdates = false; - //@FIXME: Restore this latter. - //if (GetGameEngine() && GetGameEngine()->IsLevelLoaded()) - { - m_pObjectManager->Update(); - } if (IsInPreviewMode()) { SetModifiedFlag(false); @@ -687,13 +682,6 @@ void CEditorImpl::DeleteObject(CBaseObject* obj) GetObjectManager()->DeleteObject(obj); } -CBaseObject* CEditorImpl::CloneObject(CBaseObject* obj) -{ - SetModifiedFlag(); - GetIEditor()->SetModifiedModule(eModifiedBrushes); - return GetObjectManager()->CloneObject(obj); -} - CBaseObject* CEditorImpl::GetSelectedObject() { if (m_pObjectManager->GetSelection()->GetCount() != 1) diff --git a/Code/Editor/IEditorImpl.h b/Code/Editor/IEditorImpl.h index 26701edec2..4976c0c1ca 100644 --- a/Code/Editor/IEditorImpl.h +++ b/Code/Editor/IEditorImpl.h @@ -141,7 +141,6 @@ public: const SGizmoParameters& GetGlobalGizmoParameters() override; CBaseObject* NewObject(const char* typeName, const char* fileName = "", const char* name = "", float x = 0.0f, float y = 0.0f, float z = 0.0f, bool modifyDoc = true) override; void DeleteObject(CBaseObject* obj) override; - CBaseObject* CloneObject(CBaseObject* obj) override; IObjectManager* GetObjectManager() override; // This will return a null pointer if CrySystem is not loaded before // Global Sandbox Settings are loaded from the registry before CrySystem diff --git a/Code/Editor/IconManager.cpp b/Code/Editor/IconManager.cpp index 820213bbd9..a9d31aef7b 100644 --- a/Code/Editor/IconManager.cpp +++ b/Code/Editor/IconManager.cpp @@ -21,8 +21,6 @@ #include "Util/Image.h" #include "Util/ImageUtil.h" -#include - #define HELPER_MATERIAL "Objects/Helper" namespace @@ -38,7 +36,6 @@ namespace CIconManager::CIconManager() { ZeroStruct(m_icons); - ZeroStruct(m_objects); } ////////////////////////////////////////////////////////////////////////// @@ -61,13 +58,7 @@ void CIconManager::Done() void CIconManager::Reset() { // Do not unload objects. but clears them. - int i; - for (i = 0; i < sizeof(m_objects) / sizeof(m_objects[0]); i++) - { - delete m_objects[i]; - m_objects[i] = nullptr; - } - for (i = 0; i < eIcon_COUNT; i++) + for (int i = 0; i < eIcon_COUNT; i++) { m_icons[i] = 0; } @@ -110,12 +101,6 @@ int CIconManager::GetIconTexture(EIcon icon) return m_icons[icon]; } -////////////////////////////////////////////////////////////////////////// -IStatObj* CIconManager::GetObject(EStatObject) -{ - return nullptr; -} - ////////////////////////////////////////////////////////////////////////// QImage* CIconManager::GetIconBitmap(const char* filename, bool& bHaveAlpha, uint32 effects /*=0*/) { diff --git a/Code/Editor/IconManager.h b/Code/Editor/IconManager.h index 7183f036ed..7e9be63665 100644 --- a/Code/Editor/IconManager.h +++ b/Code/Editor/IconManager.h @@ -8,11 +8,6 @@ // Description : Manages Textures used by Icon. - - -#ifndef CRYINCLUDE_EDITOR_ICONMANAGER_H -#define CRYINCLUDE_EDITOR_ICONMANAGER_H - #pragma once #include "Include/IIconManager.h" // for IIconManager @@ -31,7 +26,7 @@ class CIconManager public: // Construction CIconManager(); - ~CIconManager(); + ~CIconManager() override; void Init(); void Done(); @@ -41,8 +36,6 @@ public: // Operations virtual int GetIconTexture(EIcon icon); - - virtual IStatObj* GetObject(EStatObject object); virtual int GetIconTexture(const char* iconName); ////////////////////////////////////////////////////////////////////////// @@ -61,7 +54,6 @@ public: private: StdMap m_textures; - IStatObj* m_objects[eStatObject_COUNT]; int m_icons[eIcon_COUNT]; ////////////////////////////////////////////////////////////////////////// @@ -70,5 +62,3 @@ private: typedef std::map IconsMap; IconsMap m_iconBitmapsMap; }; - -#endif // CRYINCLUDE_EDITOR_ICONMANAGER_H diff --git a/Code/Editor/Include/HitContext.h b/Code/Editor/Include/HitContext.h index 186124555f..ceff0adb22 100644 --- a/Code/Editor/Include/HitContext.h +++ b/Code/Editor/Include/HitContext.h @@ -19,7 +19,6 @@ class CBaseObject; struct IDisplayViewport; class CDeepSelection; struct AABB; -class CCamera; #include #include @@ -68,8 +67,6 @@ struct HitContext QRect rect; //! Optional limiting bounding box for hit testing. AABB* bounds; - //! Optional camera for culling perspective viewports. - CCamera* camera; //! Testing performed in 2D viewport. bool b2DViewport; @@ -120,7 +117,6 @@ struct HitContext rect = QRect(); b2DViewport = false; view = 0; - camera = 0; point2d = QPoint(); axis = 0; distanceTolerance = 0; diff --git a/Code/Editor/Include/IDisplayViewport.h b/Code/Editor/Include/IDisplayViewport.h index 637d19e39b..2dab2802c7 100644 --- a/Code/Editor/Include/IDisplayViewport.h +++ b/Code/Editor/Include/IDisplayViewport.h @@ -6,15 +6,11 @@ * */ - -#ifndef CRYINCLUDE_EDITOR_INCLUDE_IDISPLAYVIEWPORT_H -#define CRYINCLUDE_EDITOR_INCLUDE_IDISPLAYVIEWPORT_H #pragma once struct DisplayContext; class CBaseObjectsCache; class QPoint; -class CCamera; struct AABB; class CViewport; @@ -23,8 +19,6 @@ struct IDisplayViewport { virtual void Update() = 0; virtual float GetScreenScaleFactor(const Vec3& position) const = 0; - virtual float GetScreenScaleFactor(const CCamera& camera, const Vec3& object_position) = 0; - virtual bool HitTestLine(const Vec3& lineP1, const Vec3& lineP2, const QPoint& hitpoint, int pixelRadius, float* pToCameraDistance = 0) const = 0; /** * Gets the distance of the point on screen to the line defined by the two points, converted to screenspace. @@ -50,12 +44,9 @@ struct IDisplayViewport virtual Vec3 WorldToView3D(const Vec3& worldPoint, int flags = 0) const = 0; virtual Vec3 ViewToWorld(const QPoint& vp, bool* collideWithTerrain = nullptr, bool onlyTerrain = false, bool bSkipVegetation = false, bool bTestRenderMesh = false, bool* collideWithObject = nullptr) const = 0; virtual void ViewToWorldRay(const QPoint& vp, Vec3& raySrc, Vec3& rayDir) const = 0; - virtual float GetGridStep() const = 0; virtual void setRay(QPoint& vp, Vec3& raySrc, Vec3& rayDir) = 0; - virtual void setHitcontext(QPoint& vp, Vec3& raySrc, Vec3& rayDir) = 0; virtual float GetAspectRatio() const = 0; - virtual const ::Plane* GetConstructionPlane() const = 0; virtual bool IsBoundsVisible(const AABB& box) const = 0; @@ -64,5 +55,3 @@ struct IDisplayViewport virtual CViewport *asCViewport() { return nullptr; } }; - -#endif // CRYINCLUDE_EDITOR_INCLUDE_IDISPLAYVIEWPORT_H diff --git a/Code/Editor/Include/IExportManager.h b/Code/Editor/Include/IExportManager.h index ea802134b6..ce79e52e15 100644 --- a/Code/Editor/Include/IExportManager.h +++ b/Code/Editor/Include/IExportManager.h @@ -8,14 +8,9 @@ // Description : Export geometry interfaces - - -#ifndef CRYINCLUDE_EDITOR_INCLUDE_IEXPORTMANAGER_H -#define CRYINCLUDE_EDITOR_INCLUDE_IEXPORTMANAGER_H #pragma once #define EXP_NAMESIZE 32 -struct IStatObj; enum class AnimParamType; namespace Export @@ -178,18 +173,10 @@ struct IExporter virtual void Release() = 0; }; - - // IExportManager: interface to export manager struct IExportManager { //! Register exporter //! return true if succeed, otherwise false virtual bool RegisterExporter(IExporter* pExporter) = 0; - - virtual bool ExportSingleStatObj(IStatObj* pStatObj, const char* filename) = 0; }; - - - -#endif // CRYINCLUDE_EDITOR_INCLUDE_IEXPORTMANAGER_H diff --git a/Code/Editor/Include/IIconManager.h b/Code/Editor/Include/IIconManager.h index 4925e47ae8..2407ea31bd 100644 --- a/Code/Editor/Include/IIconManager.h +++ b/Code/Editor/Include/IIconManager.h @@ -6,12 +6,8 @@ * */ - -#ifndef CRYINCLUDE_EDITOR_INCLUDE_IICONMANAGER_H -#define CRYINCLUDE_EDITOR_INCLUDE_IICONMANAGER_H #pragma once -struct IStatObj; struct IMaterial; class CBitmap; @@ -56,12 +52,9 @@ enum EIconEffect struct IIconManager { virtual ~IIconManager() = default; - virtual IStatObj* GetObject(EStatObject object) = 0; virtual int GetIconTexture(EIcon icon) = 0; virtual int GetIconTexture(const char* iconName) = 0; virtual QImage* GetIconBitmap(const char* filename, bool& haveAlpha, uint32 effects = 0) = 0; // Register an Icon for the specific command virtual void RegisterCommandIcon([[maybe_unused]] const char* filename, [[maybe_unused]] int nCommandId) {} }; - -#endif // CRYINCLUDE_EDITOR_INCLUDE_IICONMANAGER_H diff --git a/Code/Editor/Include/IObjectManager.h b/Code/Editor/Include/IObjectManager.h index 0a7bc8dcca..b612bcb80c 100644 --- a/Code/Editor/Include/IObjectManager.h +++ b/Code/Editor/Include/IObjectManager.h @@ -5,10 +5,6 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ - - -#ifndef CRYINCLUDE_EDITOR_INCLUDE_IOBJECTMANAGER_H -#define CRYINCLUDE_EDITOR_INCLUDE_IOBJECTMANAGER_H #pragma once #include @@ -29,32 +25,15 @@ class CObjectArchive; class CViewport; struct HitContext; enum class ImageRotationDegrees; -struct IStatObj; class CBaseObject; class XmlNodeRef; #include "ObjectEvent.h" -enum SerializeFlags -{ - SERIALIZE_ALL = 0, - SERIALIZE_ONLY_SHARED = 1, - SERIALIZE_ONLY_NOTSHARED = 2, -}; - ////////////////////////////////////////////////////////////////////////// typedef std::vector CBaseObjectsArray; typedef std::pair< bool(CALLBACK*)(CBaseObject const&, void*), void* > BaseObjectFilterFunctor; -struct IObjectSelectCallback -{ - //! Called when object is selected. - //! Return true if selection should proceed, or false to abort object selection. - virtual bool OnSelectObject(CBaseObject* obj) = 0; - //! Return true if object can be selected. - virtual bool CanSelectObject(CBaseObject* obj) = 0; -}; - ////////////////////////////////////////////////////////////////////////// // // Interface to access editor objects scene graph. @@ -78,10 +57,6 @@ public: virtual void DeleteObject(CBaseObject* obj) = 0; virtual void DeleteSelection(CSelectionGroup* pSelection) = 0; virtual void DeleteAllObjects() = 0; - virtual CBaseObject* CloneObject(CBaseObject* obj) = 0; - - virtual void BeginEditParams(CBaseObject* obj, int flags) = 0; - virtual void EndEditParams(int flags = 0) = 0; //! Get number of objects manager by ObjectManager (not contain sub objects of groups). virtual int GetObjectCount() const = 0; @@ -90,27 +65,9 @@ public: //! @param layer if 0 get objects for all layers, or layer to get objects from. virtual void GetObjects(CBaseObjectsArray& objects) const = 0; - //! Get array of objects that pass the filter. - //! @param filter The filter functor, return true if you want to get the certain obj, return false if want to skip it. - virtual void GetObjects(CBaseObjectsArray& objects, BaseObjectFilterFunctor const& filter) const = 0; - //! Display objects on specified display context. virtual void Display(DisplayContext& dc) = 0; - //! Called when selecting without selection helpers - this is needed since - //! the visible object cache is normally not updated when not displaying helpers. - virtual void ForceUpdateVisibleObjectCache(DisplayContext& dc) = 0; - - //! Check intersection with objects. - //! Find intersection with nearest to ray origin object hit by ray. - //! If distance tollerance is specified certain relaxation applied on collision test. - //! @return true if hit any object, and fills hitInfo structure. - virtual bool HitTest(HitContext& hitInfo) = 0; - - //! Check intersection with an object. - //! @return true if hit, and fills hitInfo structure. - virtual bool HitTestObject(CBaseObject* obj, HitContext& hc) = 0; - //! Gets a radius to be used for hit tests on the axis helpers, like the transform gizmo. //! @return the axis helper hit radius. virtual int GetAxisHelperHitRadius() const = 0; @@ -137,59 +94,18 @@ public: //! Find objects which intersect with a given AABB. virtual void FindObjectsInAABB(const AABB& aabb, std::vector& result) const = 0; - ////////////////////////////////////////////////////////////////////////// - // Operations on objects. - ////////////////////////////////////////////////////////////////////////// - //! Makes object visible or invisible. - virtual void HideObject(CBaseObject* obj, bool hide) = 0; - //! Shows the last hidden object based on hidden ID - virtual void ShowLastHiddenObject() = 0; - //! Freeze object, making it unselectable. - virtual void FreezeObject(CBaseObject* obj, bool freeze) = 0; - //! Unhide all hidden objects. - virtual void UnhideAll() = 0; - //! Unfreeze all frozen objects. - virtual void UnfreezeAll() = 0; - ////////////////////////////////////////////////////////////////////////// // Object Selection. ////////////////////////////////////////////////////////////////////////// virtual bool SelectObject(CBaseObject* obj, bool bUseMask = true) = 0; virtual void UnselectObject(CBaseObject* obj) = 0; - //! Select objects within specified distance from given position. - //! Return number of selected objects. - virtual int SelectObjects(const AABB& box, bool bUnselect = false) = 0; - - virtual void SelectEntities(std::set& s) = 0; - - virtual int MoveObjects(const AABB& box, const Vec3& offset, ImageRotationDegrees rotation, bool bIsCopy = false) = 0; - - //! Selects/Unselects all objects within 2d rectangle in given viewport. - virtual void SelectObjectsInRect(CViewport* view, const QRect& rect, bool bSelect) = 0; - virtual void FindObjectsInRect(CViewport* view, const QRect& rect, std::vector& guids) = 0; - //! Clear default selection set. //! @Return number of objects removed from selection. virtual int ClearSelection() = 0; - //! Deselect all current selected objects and selects object that were unselected. - //! @Return number of selected objects. - virtual int InvertSelection() = 0; - //! Get current selection. virtual CSelectionGroup* GetSelection() const = 0; - //! Get named selection. - virtual CSelectionGroup* GetSelection(const QString& name) const = 0; - // Get selection group names - virtual void GetNameSelectionStrings(QStringList& names) = 0; - //! Change name of current selection group. - //! And store it in list. - virtual void NameSelection(const QString& name) = 0; - //! Set one of name selections as current selection. - virtual void SetSelection(const QString& name) = 0; - //! Removes one of named selections. - virtual void RemoveSelection(const QString& name) = 0; //! Delete all objects in current selection group. virtual void DeleteSelection() = 0; @@ -198,54 +114,11 @@ public: virtual QString GenerateUniqueObjectName(const QString& typeName) = 0; //! Register object name in object manager, needed for generating uniq names. virtual void RegisterObjectName(const QString& name) = 0; - //! Enable/Disable generating of unique object names (Enabled by default). - //! Return previous value. - virtual bool EnableUniqObjectNames(bool bEnable) = 0; //! Find object class by name. virtual CObjectClassDesc* FindClass(const QString& className) = 0; - virtual void GetClassCategories(QStringList& categories) = 0; - virtual void GetClassCategoryToolClassNamePairs(std::vector< std::pair >& categoryToolClassNamePairs) = 0; - virtual void GetClassTypes(const QString& category, QStringList& types) = 0; - - //! Export objects to xml. - //! When onlyShared is true ony objects with shared flags exported, overwise only not shared object exported. - virtual void Export(const QString& levelPath, XmlNodeRef& rootNode, bool onlyShared) = 0; - //! Export only entities to xml. - virtual void ExportEntities(XmlNodeRef& rootNode) = 0; - - //! Serialize Objects in manager to specified XML Node. - //! @param flags Can be one of SerializeFlags. - virtual void Serialize(XmlNodeRef& rootNode, bool bLoading, int flags = SERIALIZE_ALL) = 0; - virtual void SerializeNameSelection(XmlNodeRef& rootNode, bool bLoading) = 0; - - //! Load objects from object archive. - //! @param bSelect if set newly loaded object will be selected. - virtual void LoadObjects(CObjectArchive& ar, bool bSelect) = 0; virtual void ChangeObjectId(REFGUID oldId, REFGUID newId) = 0; - virtual bool IsDuplicateObjectName(const QString& newName) const = 0; - virtual void ShowDuplicationMsgWarning(CBaseObject* obj, const QString& newName, bool bShowMsgBox) const = 0; - virtual void ChangeObjectName(CBaseObject* obj, const QString& newName) = 0; - - //! while loading PreFabs we need to force this IDs - //! to force always the same IDs, on each load. - //! needed for RAM-maps assignments - virtual uint32 ForceID() const = 0; - virtual void ForceID(uint32 FID) = 0; - - //! Convert object of one type to object of another type. - //! Original object is deleted. - virtual bool ConvertToType(CBaseObject* pObject, const QString& typeName) = 0; - - //! Set new selection callback. - //! @return previous selection callback. - virtual IObjectSelectCallback* SetSelectCallback(IObjectSelectCallback* callback) = 0; - - // Enables/Disables creating of game objects. - virtual void SetCreateGameObject(bool enable) = 0; - //! Return true if objects loaded from xml should immidiatly create game objects associated with them. - virtual bool IsCreateGameObjects() const = 0; virtual IGizmoManager* GetGizmoManager() = 0; @@ -253,34 +126,9 @@ public: //! Invalidate visibily settings of objects. virtual void InvalidateVisibleList() = 0; - ////////////////////////////////////////////////////////////////////////// - // ObjectManager notification Callbacks. - ////////////////////////////////////////////////////////////////////////// - virtual void AddObjectEventListener(EventListener* listener) = 0; - virtual void RemoveObjectEventListener(EventListener* listener) = 0; - - ////////////////////////////////////////////////////////////////////////// - // Used to indicate starting and ending of objects loading. - ////////////////////////////////////////////////////////////////////////// - virtual void StartObjectsLoading(int numObjects) = 0; - virtual void EndObjectsLoading() = 0; - ////////////////////////////////////////////////////////////////////////// // Gathers all resources used by all objects. virtual void GatherUsedResources(CUsedResources& resources) = 0; virtual bool IsLightClass(CBaseObject* pObject) = 0; - - virtual void FindAndRenameProperty2(const char* property2Name, const QString& oldValue, const QString& newValue) = 0; - virtual void FindAndRenameProperty2If(const char* property2Name, const QString& oldValue, const QString& newValue, const char* otherProperty2Name, const QString& otherValue) = 0; - - virtual bool IsReloading() const = 0; - - // Set bSkipUpdate to true if you want to skip update objects on the idle loop. - virtual void SetSkipUpdate(bool bSkipUpdate) = 0; - - virtual void SetExportingLevel(bool bExporting) = 0; - virtual bool IsExportingLevelInprogress() const = 0; }; - -#endif // CRYINCLUDE_EDITOR_INCLUDE_IOBJECTMANAGER_H diff --git a/Code/Editor/Include/ObjectEvent.h b/Code/Editor/Include/ObjectEvent.h index e59bca6111..a117a8f8f1 100644 --- a/Code/Editor/Include/ObjectEvent.h +++ b/Code/Editor/Include/ObjectEvent.h @@ -14,7 +14,6 @@ //! Standart objects types. enum ObjectType { - OBJTYPE_DUMMY = 1 << 20, OBJTYPE_AZENTITY = 1 << 21, }; diff --git a/Code/Editor/KeyboardCustomizationSettings.cpp b/Code/Editor/KeyboardCustomizationSettings.cpp index c4f9133f66..81d9375850 100644 --- a/Code/Editor/KeyboardCustomizationSettings.cpp +++ b/Code/Editor/KeyboardCustomizationSettings.cpp @@ -240,7 +240,7 @@ QJsonObject KeyboardCustomizationSettings::ExportGroup() void KeyboardCustomizationSettings::ImportFromFile(QWidget* parent) { - QString fileName = QFileDialog::getOpenFileName(parent, QObject::tr("Export Keyboard Shortcuts"), QStringLiteral(""), QObject::tr("Keyboard Settings (*.keys)")); + QString fileName = QFileDialog::getOpenFileName(parent, QObject::tr("Export Keyboard Shortcuts"), QString(), QObject::tr("Keyboard Settings (*.keys)")); if (fileName.isEmpty()) { return; diff --git a/Code/Editor/LayoutWnd.cpp b/Code/Editor/LayoutWnd.cpp index aa2a008844..ea125ca3d4 100644 --- a/Code/Editor/LayoutWnd.cpp +++ b/Code/Editor/LayoutWnd.cpp @@ -487,7 +487,6 @@ bool CLayoutWnd::LoadConfig() CreateLayout((EViewLayout)layout, false); - bool bRebindViewports = false; if (m_splitWnd) { const QString str = settings.value("Viewports").toString(); @@ -498,14 +497,12 @@ bool CLayoutWnd::LoadConfig() { break; } - bRebindViewports = true; if (!resToken.isEmpty()) { m_viewType[nIndex] = resToken; } nIndex++; } - ; } BindViewports(); diff --git a/Code/Editor/Lib/Tests/Camera/test_EditorCamera.cpp b/Code/Editor/Lib/Tests/Camera/test_EditorCamera.cpp index 76e23466f7..0dcda3be33 100644 --- a/Code/Editor/Lib/Tests/Camera/test_EditorCamera.cpp +++ b/Code/Editor/Lib/Tests/Camera/test_EditorCamera.cpp @@ -15,28 +15,19 @@ #include #include +#include + namespace UnitTest { - class EditorCameraTestEnvironment : public AZ::Test::GemTestEnvironment - { - // AZ::Test::GemTestEnvironment overrides ... - void AddGemsAndComponents() override; - }; - - void EditorCameraTestEnvironment::AddGemsAndComponents() - { - AddDynamicModulePaths({ CAMERA_EDITOR_MODULE }); - AddComponentDescriptors({ AzToolsFramework::Components::TransformComponent::CreateDescriptor() }); - } - class EditorCameraFixture : public ::testing::Test { public: + AZ::ComponentApplication* m_application = nullptr; AtomToolsFramework::ModularCameraViewportContext* m_cameraViewportContextView = nullptr; AZStd::unique_ptr m_editorModularViewportCameraComposer; - AZStd::unique_ptr m_editorLibHandle; AzFramework::ViewportControllerListPtr m_controllerList; - AZStd::unique_ptr m_entity; + AZ::Entity* m_entity = nullptr; + AZ::ComponentDescriptor* m_transformComponent = nullptr; static inline constexpr AzFramework::ViewportId TestViewportId = 2345; static inline constexpr float HalfInterpolateToTransformDuration = @@ -44,18 +35,19 @@ namespace UnitTest void SetUp() override { - m_editorLibHandle = AZ::DynamicModuleHandle::Create("EditorLib"); - [[maybe_unused]] const bool loaded = m_editorLibHandle->Load(true); - AZ_Assert(loaded, "EditorLib could not be loaded"); + m_application = aznew AZ::ComponentApplication; + AZ::ComponentApplication::Descriptor appDesc; + m_entity = m_application->Create(appDesc); + m_transformComponent = AzToolsFramework::Components::TransformComponent::CreateDescriptor(); + m_application->RegisterComponentDescriptor(m_transformComponent); - m_controllerList = AZStd::make_shared(); - m_controllerList->RegisterViewportContext(TestViewportId); - - m_entity = AZStd::make_unique(); m_entity->Init(); m_entity->CreateComponent(); m_entity->Activate(); + m_controllerList = AZStd::make_shared(); + m_controllerList->RegisterViewportContext(TestViewportId); + m_editorModularViewportCameraComposer = AZStd::make_unique(TestViewportId); auto controller = m_editorModularViewportCameraComposer->CreateModularViewportCameraController(); @@ -74,8 +66,17 @@ namespace UnitTest { m_editorModularViewportCameraComposer.reset(); m_cameraViewportContextView = nullptr; - m_entity.reset(); - m_editorLibHandle = {}; + + if (m_application) + { + m_application->UnregisterComponentDescriptor(m_transformComponent); + delete m_transformComponent; + m_transformComponent = nullptr; + + m_application->Destroy(); + delete m_application; + m_application = nullptr; + } } }; @@ -258,16 +259,35 @@ namespace UnitTest EXPECT_THAT(interpolating, ::testing::IsFalse()); EXPECT_THAT(nextInterpolationBegan, ::testing::IsTrue()); } + + TEST(GotoPositionPitchConstraints, GoToPositionPitchIsSetToPlusOrMinusNinetyDegrees) + { + float minPitch = 0.0f; + float maxPitch = 0.0f; + + GotoPositionPitchConstraints m_gotoPositionContraints; + m_gotoPositionContraints.DeterminePitchRange( + [&minPitch, &maxPitch](const float minPitchDegrees, const float maxPitchDegrees) + { + minPitch = minPitchDegrees; + maxPitch = maxPitchDegrees; + }); + + using ::testing::FloatNear; + EXPECT_THAT(minPitch, FloatNear(-90.0f, AZ::Constants::FloatEpsilon)); + EXPECT_THAT(maxPitch, FloatNear(90.0f, AZ::Constants::FloatEpsilon)); + } + + TEST(GotoPositionPitchConstraints, GoToPositionPitchClampsFinalPitchValueWithTolerance) + { + const auto [expectedMinPitchRadians, expectedMaxPitchRadians] = AzFramework::CameraPitchMinMaxRadiansWithTolerance(); + + GotoPositionPitchConstraints m_gotoPositionContraints; + const float minClampedPitchRadians = m_gotoPositionContraints.PitchClampedRadians(-90.0f); + const float maxClampedPitchRadians = m_gotoPositionContraints.PitchClampedRadians(90.0f); + + using ::testing::FloatNear; + EXPECT_THAT(minClampedPitchRadians, FloatNear(expectedMinPitchRadians, AZ::Constants::FloatEpsilon)); + EXPECT_THAT(maxClampedPitchRadians, FloatNear(expectedMaxPitchRadians, AZ::Constants::FloatEpsilon)); + } } // namespace UnitTest - -// required to support running integration tests with the Camera Gem -AZTEST_EXPORT int AZ_UNIT_TEST_HOOK_NAME(int argc, char** argv) -{ - ::testing::InitGoogleMock(&argc, argv); - AZ::Test::printUnusedParametersWarning(argc, argv); - AZ::Test::addTestEnvironments({ new UnitTest::EditorCameraTestEnvironment() }); - int result = RUN_ALL_TESTS(); - return result; -} - -IMPLEMENT_TEST_EXECUTABLE_MAIN(); diff --git a/Code/Editor/Lib/Tests/test_ClickableLabel.cpp b/Code/Editor/Lib/Tests/test_ClickableLabel.cpp index 905e91b64c..a676714992 100644 --- a/Code/Editor/Lib/Tests/test_ClickableLabel.cpp +++ b/Code/Editor/Lib/Tests/test_ClickableLabel.cpp @@ -19,7 +19,7 @@ using namespace ::testing; namespace UnitTest { class TestingClickableLabel - : public testing::Test + : public ScopedAllocatorSetupFixture { public: ClickableLabel m_clickableLabel; diff --git a/Code/Editor/Lib/Tests/test_CryEditDocPythonBindings.cpp b/Code/Editor/Lib/Tests/test_CryEditDocPythonBindings.cpp index f4e029cb7a..5bab9cb24b 100644 --- a/Code/Editor/Lib/Tests/test_CryEditDocPythonBindings.cpp +++ b/Code/Editor/Lib/Tests/test_CryEditDocPythonBindings.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -22,7 +23,7 @@ namespace CryEditDocPythonBindingsUnitTests { class CryEditDocPythonBindingsFixture - : public testing::Test + : public ::UnitTest::ScopedAllocatorSetupFixture { public: AzToolsFramework::ToolsApplication m_app; @@ -30,7 +31,6 @@ namespace CryEditDocPythonBindingsUnitTests void SetUp() override { AzFramework::Application::Descriptor appDesc; - appDesc.m_enableDrilling = false; m_app.Start(appDesc); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is diff --git a/Code/Editor/Lib/Tests/test_CryEditPythonBindings.cpp b/Code/Editor/Lib/Tests/test_CryEditPythonBindings.cpp index 30afa07304..5246fcdee5 100644 --- a/Code/Editor/Lib/Tests/test_CryEditPythonBindings.cpp +++ b/Code/Editor/Lib/Tests/test_CryEditPythonBindings.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -24,7 +25,7 @@ namespace CryEditPythonBindingsUnitTests { class CryEditPythonBindingsFixture - : public testing::Test + : public ::UnitTest::ScopedAllocatorSetupFixture { public: AzToolsFramework::ToolsApplication m_app; @@ -32,7 +33,6 @@ namespace CryEditPythonBindingsUnitTests void SetUp() override { AzFramework::Application::Descriptor appDesc; - appDesc.m_enableDrilling = false; m_app.Start(appDesc); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is diff --git a/Code/Editor/Lib/Tests/test_DisplaySettingsPythonBindings.cpp b/Code/Editor/Lib/Tests/test_DisplaySettingsPythonBindings.cpp index e6af0bdeff..1fd70cb88c 100644 --- a/Code/Editor/Lib/Tests/test_DisplaySettingsPythonBindings.cpp +++ b/Code/Editor/Lib/Tests/test_DisplaySettingsPythonBindings.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -22,7 +23,7 @@ namespace DisplaySettingsPythonBindingsUnitTests { class DisplaySettingsPythonBindingsFixture - : public testing::Test + : public ::UnitTest::ScopedAllocatorSetupFixture { public: AzToolsFramework::ToolsApplication m_app; @@ -30,7 +31,6 @@ namespace DisplaySettingsPythonBindingsUnitTests void SetUp() override { AzFramework::Application::Descriptor appDesc; - appDesc.m_enableDrilling = false; m_app.Start(appDesc); m_app.RegisterComponentDescriptor(AzToolsFramework::DisplaySettingsPythonFuncsHandler::CreateDescriptor()); @@ -52,7 +52,7 @@ namespace DisplaySettingsPythonBindingsUnitTests } class DisplaySettingsComponentFixture - : public testing::Test + : public ::UnitTest::ScopedAllocatorSetupFixture { public: AzToolsFramework::ToolsApplication m_app; @@ -60,7 +60,6 @@ namespace DisplaySettingsPythonBindingsUnitTests void SetUp() override { AzFramework::Application::Descriptor appDesc; - appDesc.m_enableDrilling = false; m_app.Start(appDesc); m_app.RegisterComponentDescriptor(AzToolsFramework::DisplaySettingsComponent::CreateDescriptor()); diff --git a/Code/Editor/Lib/Tests/test_EditorPythonBindings.cpp b/Code/Editor/Lib/Tests/test_EditorPythonBindings.cpp index 1006c339ee..49b65f7ffc 100644 --- a/Code/Editor/Lib/Tests/test_EditorPythonBindings.cpp +++ b/Code/Editor/Lib/Tests/test_EditorPythonBindings.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -25,7 +26,6 @@ #include #include #include -#include #include #include "IEditorMock.h" @@ -80,7 +80,7 @@ namespace EditorPythonBindingsUnitTests }; class EditorPythonBindingsFixture - : public testing::Test + : public ::UnitTest::ScopedAllocatorSetupFixture { public: AzToolsFramework::ToolsApplication m_app; @@ -88,7 +88,6 @@ namespace EditorPythonBindingsUnitTests void SetUp() override { AzFramework::Application::Descriptor appDesc; - appDesc.m_enableDrilling = false; m_app.Start(appDesc); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is diff --git a/Code/Editor/Lib/Tests/test_EditorUtils.cpp b/Code/Editor/Lib/Tests/test_EditorUtils.cpp index 3556757ae3..c0515a77ad 100644 --- a/Code/Editor/Lib/Tests/test_EditorUtils.cpp +++ b/Code/Editor/Lib/Tests/test_EditorUtils.cpp @@ -11,6 +11,7 @@ #include #include #include +#include namespace EditorUtilsTest { @@ -39,7 +40,7 @@ namespace EditorUtilsTest class TestWarningAbsorber - : public testing::Test + : public ::UnitTest::ScopedAllocatorSetupFixture { }; diff --git a/Code/Editor/Lib/Tests/test_Main.cpp b/Code/Editor/Lib/Tests/test_Main.cpp index 6250c540db..91369ee8b6 100644 --- a/Code/Editor/Lib/Tests/test_Main.cpp +++ b/Code/Editor/Lib/Tests/test_Main.cpp @@ -9,12 +9,13 @@ #include "EditorDefs.h" #include #include +#include #include #include class EditorLibTestEnvironment - : public AZ::Test::ITestEnvironment + : public ::UnitTest::TraceBusHook { public: ~EditorLibTestEnvironment() override = default; @@ -22,16 +23,20 @@ public: protected: void SetupEnvironment() override { + ::UnitTest::TraceBusHook::SetupEnvironment(); + AZ::Environment::Create(nullptr); - AttachEditorAZEnvironment(AZ::Environment::GetInstance()); AZ::AllocatorInstance::Create(); + AttachEditorAZEnvironment(AZ::Environment::GetInstance()); } void TeardownEnvironment() override { - AZ::AllocatorInstance::Destroy(); DetachEditorAZEnvironment(); + AZ::AllocatorInstance::Destroy(); AZ::Environment::Destroy(); + + ::UnitTest::TraceBusHook::TeardownEnvironment(); } }; diff --git a/Code/Editor/Lib/Tests/test_MainWindowPythonBindings.cpp b/Code/Editor/Lib/Tests/test_MainWindowPythonBindings.cpp index 3963ae990f..e3268704bc 100644 --- a/Code/Editor/Lib/Tests/test_MainWindowPythonBindings.cpp +++ b/Code/Editor/Lib/Tests/test_MainWindowPythonBindings.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -22,7 +23,7 @@ namespace MainWindowPythonBindingsUnitTests { class MainWindowPythonBindingsFixture - : public testing::Test + : public ::UnitTest::ScopedAllocatorSetupFixture { public: AzToolsFramework::ToolsApplication m_app; @@ -30,7 +31,6 @@ namespace MainWindowPythonBindingsUnitTests void SetUp() override { AzFramework::Application::Descriptor appDesc; - appDesc.m_enableDrilling = false; m_app.Start(appDesc); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is diff --git a/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp b/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp index 656440f16e..67d89ad967 100644 --- a/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp +++ b/Code/Editor/Lib/Tests/test_ModularViewportCameraController.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/Code/Editor/Lib/Tests/test_ObjectManagerPythonBindings.cpp b/Code/Editor/Lib/Tests/test_ObjectManagerPythonBindings.cpp index 39a89670ef..6766b6a1fa 100644 --- a/Code/Editor/Lib/Tests/test_ObjectManagerPythonBindings.cpp +++ b/Code/Editor/Lib/Tests/test_ObjectManagerPythonBindings.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -22,7 +23,7 @@ namespace ObjectManagerPythonBindingsUnitTests { class ObjectManagerPythonBindingsFixture - : public testing::Test + : public ::UnitTest::ScopedAllocatorSetupFixture { public: AzToolsFramework::ToolsApplication m_app; @@ -30,7 +31,6 @@ namespace ObjectManagerPythonBindingsUnitTests void SetUp() override { AzFramework::Application::Descriptor appDesc; - appDesc.m_enableDrilling = false; m_app.Start(appDesc); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is @@ -63,16 +63,6 @@ namespace ObjectManagerPythonBindingsUnitTests EXPECT_TRUE(behaviorContext->m_methods.find("get_selection_center") != behaviorContext->m_methods.end()); EXPECT_TRUE(behaviorContext->m_methods.find("get_selection_aabb") != behaviorContext->m_methods.end()); - EXPECT_TRUE(behaviorContext->m_methods.find("hide_object") != behaviorContext->m_methods.end()); - EXPECT_TRUE(behaviorContext->m_methods.find("is_object_hidden") != behaviorContext->m_methods.end()); - EXPECT_TRUE(behaviorContext->m_methods.find("unhide_object") != behaviorContext->m_methods.end()); - EXPECT_TRUE(behaviorContext->m_methods.find("hide_all_objects") != behaviorContext->m_methods.end()); - EXPECT_TRUE(behaviorContext->m_methods.find("unhide_all_objects") != behaviorContext->m_methods.end()); - - EXPECT_TRUE(behaviorContext->m_methods.find("freeze_object") != behaviorContext->m_methods.end()); - EXPECT_TRUE(behaviorContext->m_methods.find("is_object_frozen") != behaviorContext->m_methods.end()); - EXPECT_TRUE(behaviorContext->m_methods.find("unfreeze_object") != behaviorContext->m_methods.end()); - EXPECT_TRUE(behaviorContext->m_methods.find("delete_object") != behaviorContext->m_methods.end()); EXPECT_TRUE(behaviorContext->m_methods.find("delete_selected") != behaviorContext->m_methods.end()); diff --git a/Code/Editor/Lib/Tests/test_TerrainHoleToolPythonBindings.cpp b/Code/Editor/Lib/Tests/test_TerrainHoleToolPythonBindings.cpp index e163004d75..fc292b1f52 100644 --- a/Code/Editor/Lib/Tests/test_TerrainHoleToolPythonBindings.cpp +++ b/Code/Editor/Lib/Tests/test_TerrainHoleToolPythonBindings.cpp @@ -23,7 +23,7 @@ namespace TerrainFuncsUnitTests { class TerrainHoleToolPythonBindingsFixture - : public testing::Test + : public UnitTest::ScopedAllocatorSetupFixture { public: AzToolsFramework::ToolsApplication m_app; @@ -31,7 +31,6 @@ namespace TerrainFuncsUnitTests void SetUp() override { AzFramework::Application::Descriptor appDesc; - appDesc.m_enableDrilling = false; m_app.Start(appDesc); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is diff --git a/Code/Editor/Lib/Tests/test_TerrainLayerPythonBindings.cpp b/Code/Editor/Lib/Tests/test_TerrainLayerPythonBindings.cpp index ad7baa44a5..3296572ed2 100644 --- a/Code/Editor/Lib/Tests/test_TerrainLayerPythonBindings.cpp +++ b/Code/Editor/Lib/Tests/test_TerrainLayerPythonBindings.cpp @@ -23,7 +23,7 @@ namespace TerrainFuncsUnitTests { class TerrainLayerPythonBindingsFixture - : public testing::Test + : public UnitTest::ScopedAllocatorSetupFixture { public: AzToolsFramework::ToolsApplication m_app; @@ -31,7 +31,6 @@ namespace TerrainFuncsUnitTests void SetUp() override { AzFramework::Application::Descriptor appDesc; - appDesc.m_enableDrilling = false; m_app.Start(appDesc); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is diff --git a/Code/Editor/Lib/Tests/test_TerrainModifyPythonBindings.cpp b/Code/Editor/Lib/Tests/test_TerrainModifyPythonBindings.cpp index 166e4c0be5..37afbe5f37 100644 --- a/Code/Editor/Lib/Tests/test_TerrainModifyPythonBindings.cpp +++ b/Code/Editor/Lib/Tests/test_TerrainModifyPythonBindings.cpp @@ -22,7 +22,7 @@ namespace TerrainModifyPythonBindingsUnitTests { class TerrainModifyPythonBindingsFixture - : public testing::Test + : public UnitTest::ScopedAllocatorSetupFixture { public: AzToolsFramework::ToolsApplication m_app; @@ -30,7 +30,6 @@ namespace TerrainModifyPythonBindingsUnitTests void SetUp() override { AzFramework::Application::Descriptor appDesc; - appDesc.m_enableDrilling = false; m_app.Start(appDesc); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is diff --git a/Code/Editor/Lib/Tests/test_TerrainPainterPythonBindings.cpp b/Code/Editor/Lib/Tests/test_TerrainPainterPythonBindings.cpp index 7a2cf786cd..3281da8078 100644 --- a/Code/Editor/Lib/Tests/test_TerrainPainterPythonBindings.cpp +++ b/Code/Editor/Lib/Tests/test_TerrainPainterPythonBindings.cpp @@ -23,7 +23,7 @@ namespace TerrainFuncsUnitTests { class TerrainPainterPythonBindingsFixture - : public testing::Test + : public UnitTest::ScopedAllocatorSetupFixture { public: AzToolsFramework::ToolsApplication m_app; @@ -31,7 +31,6 @@ namespace TerrainFuncsUnitTests void SetUp() override { AzFramework::Application::Descriptor appDesc; - appDesc.m_enableDrilling = false; m_app.Start(appDesc); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is diff --git a/Code/Editor/Lib/Tests/test_TerrainPythonBindings.cpp b/Code/Editor/Lib/Tests/test_TerrainPythonBindings.cpp index 6a0df6b62a..786158afb3 100644 --- a/Code/Editor/Lib/Tests/test_TerrainPythonBindings.cpp +++ b/Code/Editor/Lib/Tests/test_TerrainPythonBindings.cpp @@ -23,7 +23,7 @@ namespace TerrainFuncsUnitTests { class TerrainPythonBindingsFixture - : public testing::Test + : public UnitTest::ScopedAllocatorSetupFixture { public: AzToolsFramework::ToolsApplication m_app; @@ -31,7 +31,6 @@ namespace TerrainFuncsUnitTests void SetUp() override { AzFramework::Application::Descriptor appDesc; - appDesc.m_enableDrilling = false; m_app.Start(appDesc); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is diff --git a/Code/Editor/Lib/Tests/test_TerrainTexturePythonBindings.cpp b/Code/Editor/Lib/Tests/test_TerrainTexturePythonBindings.cpp index 23d30c0b82..7bd81772e1 100644 --- a/Code/Editor/Lib/Tests/test_TerrainTexturePythonBindings.cpp +++ b/Code/Editor/Lib/Tests/test_TerrainTexturePythonBindings.cpp @@ -23,7 +23,7 @@ namespace TerrainFuncsUnitTests { class TerrainTexturePythonBindingsFixture - : public testing::Test + : public UnitTest::ScopedAllocatorSetupFixture { public: AzToolsFramework::ToolsApplication m_app; @@ -31,7 +31,6 @@ namespace TerrainFuncsUnitTests void SetUp() override { AzFramework::Application::Descriptor appDesc; - appDesc.m_enableDrilling = false; m_app.Start(appDesc); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is diff --git a/Code/Editor/Lib/Tests/test_TrackViewPythonBindings.cpp b/Code/Editor/Lib/Tests/test_TrackViewPythonBindings.cpp index 191596f4ae..558bdaaabc 100644 --- a/Code/Editor/Lib/Tests/test_TrackViewPythonBindings.cpp +++ b/Code/Editor/Lib/Tests/test_TrackViewPythonBindings.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -22,7 +23,7 @@ namespace TrackViewPythonBindingsUnitTests { class TrackViewPythonBindingsFixture - : public testing::Test + : public UnitTest::ScopedAllocatorSetupFixture { public: AzToolsFramework::ToolsApplication m_app; @@ -30,7 +31,6 @@ namespace TrackViewPythonBindingsUnitTests void SetUp() override { AzFramework::Application::Descriptor appDesc; - appDesc.m_enableDrilling = false; m_app.Start(appDesc); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is @@ -82,7 +82,7 @@ namespace TrackViewPythonBindingsUnitTests } class TrackViewComponentFixture - : public testing::Test + : public UnitTest::ScopedAllocatorSetupFixture { public: AzToolsFramework::ToolsApplication m_app; @@ -90,10 +90,12 @@ namespace TrackViewPythonBindingsUnitTests void SetUp() override { AzFramework::Application::Descriptor appDesc; - appDesc.m_enableDrilling = false; m_app.Start(appDesc); m_app.RegisterComponentDescriptor(AzToolsFramework::TrackViewComponent::CreateDescriptor()); + + // Disable saving global user settings to prevent failure due to detecting file updates + AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize); } void TearDown() override diff --git a/Code/Editor/Lib/Tests/test_ViewPanePythonBindings.cpp b/Code/Editor/Lib/Tests/test_ViewPanePythonBindings.cpp index 88ce6d5767..047cea3aad 100644 --- a/Code/Editor/Lib/Tests/test_ViewPanePythonBindings.cpp +++ b/Code/Editor/Lib/Tests/test_ViewPanePythonBindings.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -22,7 +23,7 @@ namespace ViewPaneFuncsUnitTests { class ViewPanePythonBindingsFixture - : public testing::Test + : public ::UnitTest::ScopedAllocatorSetupFixture { public: AzToolsFramework::ToolsApplication m_app; @@ -30,7 +31,6 @@ namespace ViewPaneFuncsUnitTests void SetUp() override { AzFramework::Application::Descriptor appDesc; - appDesc.m_enableDrilling = false; m_app.Start(appDesc); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is diff --git a/Code/Editor/Lib/Tests/test_ViewportManipulatorController.cpp b/Code/Editor/Lib/Tests/test_ViewportManipulatorController.cpp index 81dcae9129..473ddd81f7 100644 --- a/Code/Editor/Lib/Tests/test_ViewportManipulatorController.cpp +++ b/Code/Editor/Lib/Tests/test_ViewportManipulatorController.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include #include diff --git a/Code/Editor/Lib/Tests/test_ViewportTitleDlgPythonBindings.cpp b/Code/Editor/Lib/Tests/test_ViewportTitleDlgPythonBindings.cpp index c3d6be1fdd..c3f7fe35f5 100644 --- a/Code/Editor/Lib/Tests/test_ViewportTitleDlgPythonBindings.cpp +++ b/Code/Editor/Lib/Tests/test_ViewportTitleDlgPythonBindings.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -22,7 +23,7 @@ namespace ViewportTitleDlgFuncsUnitTests { class ViewportTitleDlgPythonBindingsFixture - : public testing::Test + : public UnitTest::ScopedAllocatorSetupFixture { public: AzToolsFramework::ToolsApplication m_app; @@ -30,7 +31,6 @@ namespace ViewportTitleDlgFuncsUnitTests void SetUp() override { AzFramework::Application::Descriptor appDesc; - appDesc.m_enableDrilling = false; m_app.Start(appDesc); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is diff --git a/Code/Editor/LyViewPaneNames.h b/Code/Editor/LyViewPaneNames.h index 1761235f4b..5b32b66741 100644 --- a/Code/Editor/LyViewPaneNames.h +++ b/Code/Editor/LyViewPaneNames.h @@ -44,7 +44,7 @@ namespace LyViewPane static const char* const SubstanceEditor = "Substance Editor"; static const char* const VegetationEditor = "Vegetation Editor"; static const char* const LandscapeCanvas = "Landscape Canvas"; - static const char* const AnimationEditor = "EMotion FX Animation Editor (PREVIEW)"; + static const char* const AnimationEditor = "EMotion FX Animation Editor"; static const char* const PhysXConfigurationEditor = "PhysX Configuration (PREVIEW)"; static const char* const SliceRelationships = "Slice Relationship View (PREVIEW)"; diff --git a/Code/Editor/MainStatusBar.cpp b/Code/Editor/MainStatusBar.cpp index f955035bd8..65502eaba5 100644 --- a/Code/Editor/MainStatusBar.cpp +++ b/Code/Editor/MainStatusBar.cpp @@ -27,7 +27,6 @@ // Editor #include "MainStatusBarItems.h" -#include "CryLibrary.h" #include "ProcessInfo.h" @@ -419,7 +418,7 @@ void MemoryStatusItem::updateStatus() GeneralStatusItem::GeneralStatusItem(QString name, MainStatusBar* parent) : StatusBarItem(name, parent) { - connect(parent, SIGNAL(messageChanged(QString)), this, SLOT(update())); + connect(parent, &MainStatusBar::messageChanged, this, [this](const QString&) { update(); }); } QString GeneralStatusItem::CurrentText() const diff --git a/Code/Editor/MainWindow.cpp b/Code/Editor/MainWindow.cpp index cc5a4b6b3d..38e433c3a3 100644 --- a/Code/Editor/MainWindow.cpp +++ b/Code/Editor/MainWindow.cpp @@ -576,7 +576,6 @@ void MainWindow::closeEvent(QCloseEvent* event) } // Close all edit panels. GetIEditor()->ClearSelection(); - GetIEditor()->GetObjectManager()->EndEditParams(); // force clean up of all deferred deletes, so that we don't have any issues with windows from plugins not being deleted yet qApp->sendPostedEvents(nullptr, QEvent::DeferredDelete); @@ -1050,7 +1049,7 @@ void MainWindow::InitActions() if (emfxEnabled.value) { QAction* action = am->AddAction(ID_OPEN_EMOTIONFX_EDITOR, tr("Animation Editor")) - .SetToolTip(tr("Open Animation Editor (PREVIEW)")) + .SetToolTip(tr("Open Animation Editor")) .SetIcon(QIcon(":/EMotionFX/EMFX_icon_32x32.png")); QObject::connect(action, &QAction::triggered, this, []() { QtViewPaneManager::instance()->OpenPane(LyViewPane::AnimationEditor); diff --git a/Code/Editor/NewLevelDialog.cpp b/Code/Editor/NewLevelDialog.cpp index 29445cef2b..c773acdb6f 100644 --- a/Code/Editor/NewLevelDialog.cpp +++ b/Code/Editor/NewLevelDialog.cpp @@ -100,9 +100,10 @@ CNewLevelDialog::CNewLevelDialog(QWidget* pParent /*=nullptr*/) m_level = ""; // First of all, keyboard focus is related to widget tab order, and the default tab order is based on the order in which // widgets are constructed. Therefore, creating more widgets changes the keyboard focus. That is why setFocus() is called last. - // Secondly, using singleShot() allows setFocus() slot of the QLineEdit instance to be invoked right after the event system - // is ready to do so. Therefore, it is better to use singleShot() than directly call setFocus(). - QTimer::singleShot(0, ui->LEVEL, SLOT(OnStartup())); + // in OnStartup() + // Secondly, using singleShot() allows OnStartup() slot of the QLineEdit instance to be invoked right after the event system + // is ready to do so. Therefore, it is better to use singleShot() than directly call OnStartup(). + QTimer::singleShot(0, this, &CNewLevelDialog::OnStartup); ReloadLevelFolder(); } diff --git a/Code/Editor/Objects/AxisGizmo.cpp b/Code/Editor/Objects/AxisGizmo.cpp index a603b2615d..7215853854 100644 --- a/Code/Editor/Objects/AxisGizmo.cpp +++ b/Code/Editor/Objects/AxisGizmo.cpp @@ -329,7 +329,6 @@ bool CAxisGizmo::MouseCallback(CViewport* view, EMouseEvent event, QPoint& point hc.b2DViewport = view->GetType() != ET_ViewportCamera; hc.point2d = point; view->ViewToWorldRay(point, hc.raySrc, hc.rayDir); - bool bHit = false; if (HitTest(hc)) { switch (hc.manipulatorMode) @@ -344,7 +343,6 @@ bool CAxisGizmo::MouseCallback(CViewport* view, EMouseEvent event, QPoint& point view->SetCurrentCursor(STD_CURSOR_SCALE); break; } - bHit = true; } } } diff --git a/Code/Editor/Objects/BaseObject.cpp b/Code/Editor/Objects/BaseObject.cpp index 4ae01faddf..86840789dd 100644 --- a/Code/Editor/Objects/BaseObject.cpp +++ b/Code/Editor/Objects/BaseObject.cpp @@ -33,8 +33,6 @@ #include "ViewManager.h" #include "IEditorImpl.h" #include "GameEngine.h" -#include -#include // To use the Andrew's algorithm in order to make convex hull from the points, this header is needed. #include "Util/GeometryUtil.h" @@ -53,18 +51,16 @@ class CUndoBaseObject : public IUndoObject { public: - CUndoBaseObject(CBaseObject* pObj, const char* undoDescription); + CUndoBaseObject(CBaseObject* pObj); protected: int GetSize() override { return sizeof(*this); } - QString GetDescription() override { return m_undoDescription; }; QString GetObjectName() override; void Undo(bool bUndo) override; void Redo() override; protected: - QString m_undoDescription; GUID m_guid; XmlNodeRef m_undo; XmlNodeRef m_redo; @@ -77,11 +73,10 @@ class CUndoBaseObjectMinimal : public IUndoObject { public: - CUndoBaseObjectMinimal(CBaseObject* obj, const char* undoDescription, int flags); + CUndoBaseObjectMinimal(CBaseObject* obj, int flags); protected: int GetSize() override { return sizeof(*this); } - QString GetDescription() override { return m_undoDescription; }; QString GetObjectName() override; void Undo(bool bUndo) override; @@ -101,7 +96,6 @@ private: void SetTransformsFromState(CBaseObject* pObject, const StateStruct& state, bool bUndo); GUID m_guid; - QString m_undoDescription; StateStruct m_undoState; StateStruct m_redoState; }; @@ -167,7 +161,6 @@ private: } int GetSize() override { return sizeof(CUndoAttachBaseObject); } - QString GetDescription() override { return "Attachment Changed"; } GUID m_attachedObjectGUID; GUID m_parentObjectGUID; @@ -176,11 +169,10 @@ private: }; ////////////////////////////////////////////////////////////////////////// -CUndoBaseObject::CUndoBaseObject(CBaseObject* obj, const char* undoDescription) +CUndoBaseObject::CUndoBaseObject(CBaseObject* obj) { // Stores the current state of this object. assert(obj != 0); - m_undoDescription = undoDescription; m_guid = obj->GetId(); m_redo = nullptr; @@ -254,11 +246,10 @@ void CUndoBaseObject::Redo() } ////////////////////////////////////////////////////////////////////////// -CUndoBaseObjectMinimal::CUndoBaseObjectMinimal(CBaseObject* pObj, const char* undoDescription, [[maybe_unused]] int flags) +CUndoBaseObjectMinimal::CUndoBaseObjectMinimal(CBaseObject* pObj, [[maybe_unused]] int flags) { // Stores the current state of this object. assert(pObj != nullptr); - m_undoDescription = undoDescription; m_guid = pObj->GetId(); ZeroStruct(m_redoState); @@ -287,7 +278,7 @@ QString CUndoBaseObjectMinimal::GetObjectName() void CUndoBaseObjectMinimal::Undo(bool bUndo) { CBaseObject* pObject = GetIEditor()->GetObjectManager()->FindObject(m_guid); - if (!pObject || pObject->GetType() == OBJTYPE_DUMMY) + if (!pObject) { return; } @@ -316,7 +307,7 @@ void CUndoBaseObjectMinimal::Undo(bool bUndo) void CUndoBaseObjectMinimal::Redo() { CBaseObject* pObject = GetIEditor()->GetObjectManager()->FindObject(m_guid); - if (!pObject || pObject->GetType() == OBJTYPE_DUMMY) + if (!pObject) { return; } @@ -382,7 +373,6 @@ CBaseObject::CBaseObject() , m_rotate(IDENTITY) , m_scale(1, 1, 1) , m_guid(GUID_NULL) - , m_floorNumber(-1) , m_flags(0) , m_nTextureIcon(0) , m_color(QColor(255, 255, 255)) @@ -398,11 +388,9 @@ CBaseObject::CBaseObject() , m_bMatrixInWorldSpace(false) , m_bMatrixValid(false) , m_bWorldBoxValid(false) - , m_nMaterialLayersMask(0) , m_nMinSpec(0) , m_vDrawIconPos(0, 0, 0) , m_nIconFlags(0) - , m_hideOrder(CBaseObject::s_invalidHiddenID) { m_worldBounds.min.Set(0, 0, 0); m_worldBounds.max.Set(0, 0, 0); @@ -431,7 +419,6 @@ bool CBaseObject::Init([[maybe_unused]] IEditor* ie, CBaseObject* prev, [[maybe_ SetLocalTM(prev->GetPos(), prev->GetRotation(), prev->GetScale()); SetArea(prev->GetArea()); SetColor(prev->GetColor()); - m_nMaterialLayersMask = prev->m_nMaterialLayersMask; SetMinSpec(prev->GetMinSpec(), false); // Copy all basic variables. @@ -488,7 +475,7 @@ void CBaseObject::SetName(const QString& name) return; } - StoreUndo("Name"); + StoreUndo(); // Notification is expensive and not required if this is during construction. bool notify = (!m_name.isEmpty()); @@ -500,7 +487,6 @@ void CBaseObject::SetName(const QString& name) if (notify) { NotifyListeners(ON_RENAME); - static_cast(GetIEditor()->GetObjectManager())->NotifyObjectListeners(this, ON_RENAME); } } @@ -529,47 +515,6 @@ const QString& CBaseObject::GetName() const return m_name; } -////////////////////////////////////////////////////////////////////////// -QString CBaseObject::GetWarningsText() const -{ - QString warnings; - - if (gSettings.viewports.bShowScaleWarnings) - { - const EScaleWarningLevel scaleWarningLevel = GetScaleWarningLevel(); - if (scaleWarningLevel == eScaleWarningLevel_Rescaled) - { - warnings += "\\n Warning: Object Scale is not 100%."; - } - else if (scaleWarningLevel == eScaleWarningLevel_RescaledNonUniform) - { - warnings += "\\n Warning: Object has non-uniform scale."; - } - } - - if (gSettings.viewports.bShowRotationWarnings) - { - const ERotationWarningLevel rotationWarningLevel = GetRotationWarningLevel(); - - if (rotationWarningLevel == eRotationWarningLevel_Rotated) - { - warnings += "\\n Warning: Object is rotated."; - } - else if (rotationWarningLevel == eRotationWarningLevel_RotatedNonRectangular) - { - warnings += "\\n Warning: Object is rotated non-orthogonally."; - } - } - - return warnings; -} - -////////////////////////////////////////////////////////////////////////// -bool CBaseObject::IsSameClass(CBaseObject* obj) -{ - return GetClassDesc() == obj->GetClassDesc(); -} - ////////////////////////////////////////////////////////////////////////// bool CBaseObject::SetPos(const Vec3& pos, int flags) { @@ -615,7 +560,7 @@ bool CBaseObject::SetPos(const Vec3& pos, int flags) ////////////////////////////////////////////////////////////////////////// if (!bPositionDelegated && (flags & eObjectUpdateFlags_RestoreUndo) == 0 && (flags & eObjectUpdateFlags_Animated) == 0) { - StoreUndo("Position", true, flags); + StoreUndo(true, flags); } if (!bPositionDelegated) @@ -659,7 +604,7 @@ bool CBaseObject::SetRotation(const Quat& rotate, int flags) if (!bRotationDelegated && (flags & eObjectUpdateFlags_RestoreUndo) == 0 && (flags & eObjectUpdateFlags_Animated) == 0) { - StoreUndo("Rotate", true, flags); + StoreUndo(true, flags); } if (!bRotationDelegated) @@ -704,7 +649,7 @@ bool CBaseObject::SetScale(const Vec3& scale, int flags) if (!bScaleDelegated && (flags & eObjectUpdateFlags_RestoreUndo) == 0 && (flags & eObjectUpdateFlags_Animated) == 0) { - StoreUndo("Scale", true, flags); + StoreUndo(true, flags); } if (!bScaleDelegated) @@ -763,7 +708,7 @@ void CBaseObject::ChangeColor(const QColor& color) return; } - StoreUndo("Color", true); + StoreUndo(true); SetColor(color); SetModified(false); @@ -783,7 +728,7 @@ void CBaseObject::SetArea(float area) return; } - StoreUndo("Area", true); + StoreUndo(true); m_flattenArea = area; SetModified(false); @@ -953,14 +898,8 @@ void CBaseObject::DrawTextureIcon(DisplayContext& dc, [[maybe_unused]] const Vec } ////////////////////////////////////////////////////////////////////////// -void CBaseObject::DrawWarningIcons(DisplayContext& dc, const Vec3& pos) +void CBaseObject::DrawWarningIcons(DisplayContext& dc, const Vec3&) { - // Don't draw warning icons if they are beyond draw distance - if ((dc.camera->GetPosition() - pos).GetLength() > gSettings.viewports.fWarningIconsDrawDistance) - { - return; - } - if (gSettings.viewports.bShowIcons || gSettings.viewports.bShowSizeBasedIcons) { const int warningIconSizeX = OBJECT_TEXTURE_ICON_SIZEX / 2; @@ -1010,11 +949,8 @@ void CBaseObject::DrawLabel(DisplayContext& dc, const Vec3& pos, const QColor& l labelColor = QColor(0, 0, 0); } - float camDist = dc.camera->GetPosition().GetDistance(pos); - float maxDist = dc.settings->GetLabelsDistance(); - if (camDist < dc.settings->GetLabelsDistance() || (dc.flags & DISPLAY_SELECTION_HELPERS)) + if (dc.flags & DISPLAY_SELECTION_HELPERS) { - float range = maxDist / 2.0f; Vec3 c(static_cast(labelColor.redF()), static_cast(labelColor.greenF()), static_cast(labelColor.redF())); if (IsSelected()) { @@ -1032,10 +968,6 @@ void CBaseObject::DrawLabel(DisplayContext& dc, const Vec3& pos, const QColor& l col[1] = c.y; col[2] = c.z; } - else if (camDist > range) - { - col[3] = col[3] * (1.0f - (camDist - range) / range); - } dc.SetColor(col[0], col[1], col[2], col[3] * alpha); dc.DrawTextLabel(pos, size, GetName().toUtf8().data()); @@ -1196,74 +1128,6 @@ bool CBaseObject::CanBeDrawn(const DisplayContext& dc, bool& outDisplaySelection return bResult; } -////////////////////////////////////////////////////////////////////////// -bool CBaseObject::IsInCameraView(const CCamera& camera) -{ - AABB bbox; - GetBoundBox(bbox); - return (camera.IsAABBVisible_F(AABB(bbox.min, bbox.max))); -} - -////////////////////////////////////////////////////////////////////////// -float CBaseObject::GetCameraVisRatio(const CCamera& camera) -{ - AABB bbox; - GetBoundBox(bbox); - - static const float defaultVisRatio = 1000.0f; - - const float objectHeightSq = max(1.0f, (bbox.max - bbox.min).GetLengthSquared()); - const float camdistSq = (bbox.min - camera.GetPosition()).GetLengthSquared(); - float visRatio = defaultVisRatio; - if (camdistSq > FLT_EPSILON) - { - visRatio = objectHeightSq / camdistSq; - } - - return visRatio; -} - -////////////////////////////////////////////////////////////////////////// -int CBaseObject::MouseCreateCallback(CViewport* view, EMouseEvent event, QPoint& point, int flags) -{ - AZ_PROFILE_FUNCTION(Editor); - - if (event == eMouseMove || event == eMouseLDown) - { - Vec3 pos; - if (GetIEditor()->GetAxisConstrains() != AXIS_TERRAIN) - { - pos = view->MapViewToCP(point); - } - else - { - // Snap to terrain. - bool hitTerrain; - pos = view->ViewToWorld(point, &hitTerrain); - if (hitTerrain) - { - pos.z = GetIEditor()->GetTerrainElevation(pos.x, pos.y) + 1.0f; - } - pos = view->SnapToGrid(pos); - } - SetPos(pos); - - if (event == eMouseLDown) - { - return MOUSECREATE_OK; - } - } - - if (event == eMouseWheel) - { - float angle = 1; - Quat rot = GetRotation(); - rot.SetRotationXYZ(Ang3(0.f, 0.f, rot.GetRotZ() + DEG2RAD(flags > 0 ? angle * (-1) : angle))); - SetRotation(rot); - } - return MOUSECREATE_CONTINUE; -} - ////////////////////////////////////////////////////////////////////////// void CBaseObject::OnEvent(ObjectEvent event) { @@ -1277,18 +1141,13 @@ void CBaseObject::OnEvent(ObjectEvent event) ////////////////////////////////////////////////////////////////////////// -void CBaseObject::SetShared([[maybe_unused]] bool bShared) -{ -} - -////////////////////////////////////////////////////////////////////////// -void CBaseObject::SetHidden(bool bHidden, uint64 hiddenID, bool bAnimated) +void CBaseObject::SetHidden(bool bHidden, bool bAnimated) { if (CheckFlags(OBJFLAG_HIDDEN) != bHidden) { if (!bAnimated) { - StoreUndo("Hide Object"); + StoreUndo(); } if (bHidden) @@ -1300,7 +1159,6 @@ void CBaseObject::SetHidden(bool bHidden, uint64 hiddenID, bool bAnimated) ClearFlags(OBJFLAG_HIDDEN); } - m_hideOrder = hiddenID; UpdateVisibility(!IsHidden()); } } @@ -1310,7 +1168,7 @@ void CBaseObject::SetFrozen(bool bFrozen) { if (CheckFlags(OBJFLAG_FROZEN) != bFrozen) { - StoreUndo("Freeze Object"); + StoreUndo(); if (bFrozen) { SetFlags(OBJFLAG_FROZEN); @@ -1396,14 +1254,6 @@ void CBaseObject::Serialize(CObjectArchive& ar) if (ar.bLoading) { // Loading. - if (ar.ShouldResetInternalMembers()) - { - m_flags = 0; - m_flattenArea = 0.0f; - m_nMinSpec = 0; - m_scale.Set(1.0f, 1.0f, 1.0f); - } - int flags = 0; int oldFlags = m_flags; @@ -1443,7 +1293,6 @@ void CBaseObject::Serialize(CObjectArchive& ar) xmlNode->getAttr("LookAt", lookatId); xmlNode->getAttr("Material", mtlName); xmlNode->getAttr("MinSpec", nMinSpec); - xmlNode->getAttr("FloorNumber", m_floorNumber); if (nMinSpec <= CONFIG_VERYHIGH_SPEC) // Ignore invalid values. { @@ -1507,18 +1356,6 @@ void CBaseObject::Serialize(CObjectArchive& ar) SetModified(false); ////////////////////////////////////////////////////////////////////////// - - if (ar.bUndo) - { - // If we are selected update UI Panel. - xmlNode->getAttr("HideOrder", m_hideOrder); - } - - // We reseted the min spec and deserialized it so set it internally - if (ar.ShouldResetInternalMembers()) - { - SetMinSpec(m_nMinSpec); - } } else { @@ -1530,7 +1367,6 @@ void CBaseObject::Serialize(CObjectArchive& ar) xmlNode->setAttr("Id", m_guid); xmlNode->setAttr("Name", GetName().toUtf8().data()); - xmlNode->setAttr("HideOrder", m_hideOrder); if (m_parent) { @@ -1547,8 +1383,6 @@ void CBaseObject::Serialize(CObjectArchive& ar) xmlNode->setAttr("Pos", GetPos()); } - xmlNode->setAttr("FloorNumber", m_floorNumber); - xmlNode->setAttr("Rotate", m_rotate); if (!IsEquivalent(GetScale(), Vec3(1, 1, 1), 0)) @@ -1641,13 +1475,8 @@ CBaseObject* CBaseObject::FindObject(REFGUID id) const } ////////////////////////////////////////////////////////////////////////// -void CBaseObject::StoreUndo(const char* UndoDescription, bool minimal, int flags) +void CBaseObject::StoreUndo(bool minimal, int flags) { - if (m_objType == OBJTYPE_DUMMY) - { - return; - } - // Don't use Sandbox undo for AZ entities, except for the move & scale tools, which rely on it. const bool isGizmoTool = 0 != (flags & (eObjectUpdateFlags_MoveTool | eObjectUpdateFlags_ScaleTool | eObjectUpdateFlags_UserInput)); if (!isGizmoTool && 0 != (m_flags & OBJFLAG_DONT_SAVE)) @@ -1659,28 +1488,18 @@ void CBaseObject::StoreUndo(const char* UndoDescription, bool minimal, int flags { if (minimal) { - CUndo::Record(new CUndoBaseObjectMinimal(this, UndoDescription, flags)); + CUndo::Record(new CUndoBaseObjectMinimal(this, flags)); } else { - CUndo::Record(new CUndoBaseObject(this, UndoDescription)); + CUndo::Record(new CUndoBaseObject(this)); } } } -////////////////////////////////////////////////////////////////////////// -bool CBaseObject::IsCreateGameObjects() const -{ - return GetObjectManager()->IsCreateGameObjects(); -} - ////////////////////////////////////////////////////////////////////////// QString CBaseObject::GetTypeName() const { - if (m_objType == OBJTYPE_DUMMY) - { - return ""; - } QString className = m_classDesc->ClassName(); QString subClassName = strstr(className.toUtf8().data(), "::"); if (subClassName.isEmpty()) @@ -1689,7 +1508,7 @@ QString CBaseObject::GetTypeName() const } QString name; - name.append(className.mid(0, className.length() - subClassName.length())); + name.append(className.midRef(0, className.length() - subClassName.length())); return name; } @@ -1941,85 +1760,6 @@ bool CBaseObject::HitTestRect(HitContext& hc) return bHit; } -////////////////////////////////////////////////////////////////////////// -bool CBaseObject::HitHelperTest(HitContext& hc) -{ - return HitHelperAtTest(hc, GetWorldPos()); -} - -////////////////////////////////////////////////////////////////////////// -bool CBaseObject::HitHelperAtTest(HitContext& hc, const Vec3& pos) -{ - AZ_PROFILE_FUNCTION(Editor); - - bool bResult = false; - - if (m_nTextureIcon && (gSettings.viewports.bShowIcons || gSettings.viewports.bShowSizeBasedIcons) && !hc.bUseSelectionHelpers) - { - int iconSizeX = OBJECT_TEXTURE_ICON_SIZEX; - int iconSizeY = OBJECT_TEXTURE_ICON_SIZEY; - - if (gSettings.viewports.bDistanceScaleIcons) - { - float fScreenScale = hc.view->GetScreenScaleFactor(pos); - - iconSizeX = static_cast(static_cast(iconSizeX) * OBJECT_TEXTURE_ICON_SCALE / fScreenScale); - iconSizeY = static_cast(static_cast(iconSizeY) * OBJECT_TEXTURE_ICON_SCALE / fScreenScale); - } - - // Hit Test icon of this object. - Vec3 testPos = pos; - int y0 = -(iconSizeY / 2); - int y1 = +(iconSizeY / 2); - if (CheckFlags(OBJFLAG_SHOW_ICONONTOP)) - { - Vec3 objectPos = GetWorldPos(); - - AABB box; - GetBoundBox(box); - testPos.z = (pos.z - objectPos.z) + box.max.z; - y0 = -(iconSizeY); - y1 = 0; - } - QPoint pnt = hc.view->WorldToView(testPos); - - if (hc.point2d.x() >= pnt.x() - (iconSizeX / 2) && hc.point2d.x() <= pnt.x() + (iconSizeX / 2) && - hc.point2d.y() >= pnt.y() + y0 && hc.point2d.y() <= pnt.y() + y1) - { - hc.dist = hc.raySrc.GetDistance(testPos) - 0.2f; - hc.iconHit = true; - bResult = true; - } - } - else if (hc.bUseSelectionHelpers) - { - // Check potentially children first - bResult = HitHelperTestForChildObjects(hc); - - // If no hit check this object - if (!bResult) - { - // Hit test helper. - Vec3 w = pos - hc.raySrc; - w = hc.rayDir.Cross(w); - float d = w.GetLengthSquared(); - - static const float screenScaleToRadiusFactor = 0.008f; - const float radius = hc.view->GetScreenScaleFactor(pos) * screenScaleToRadiusFactor; - const float pickDistance = hc.raySrc.GetDistance(pos); - if (d < radius * radius + hc.distanceTolerance && hc.dist >= pickDistance) - { - hc.dist = pickDistance; - hc.object = this; - bResult = true; - } - } - } - - - return bResult; -} - ////////////////////////////////////////////////////////////////////////// CBaseObject* CBaseObject::GetChild(size_t const i) const { @@ -2027,47 +1767,6 @@ CBaseObject* CBaseObject::GetChild(size_t const i) const return m_childs[i]; } -////////////////////////////////////////////////////////////////////////// -bool CBaseObject::IsChildOf(CBaseObject* node) -{ - CBaseObject* p = m_parent; - while (p && p != node) - { - p = p->m_parent; - } - if (p == node) - { - return true; - } - return false; -} - -////////////////////////////////////////////////////////////////////////// - - -////////////////////////////////////////////////////////////////////////// -void CBaseObject::CloneChildren(CBaseObject* pFromObject) -{ - if (pFromObject == nullptr) - { - return; - } - - for (size_t i = 0, nChildCount(pFromObject->GetChildCount()); i < nChildCount; ++i) - { - CBaseObject* pFromChildObject = pFromObject->GetChild(i); - - CBaseObject* pChildClone = GetObjectManager()->CloneObject(pFromChildObject); - if (pChildClone == nullptr) - { - continue; - } - - pChildClone->CloneChildren(pFromChildObject); - AddMember(pChildClone, false); - } -} - ////////////////////////////////////////////////////////////////////////// void CBaseObject::AttachChild(CBaseObject* child, bool bKeepPos) { @@ -2084,7 +1783,6 @@ void CBaseObject::AttachChild(CBaseObject* child, bool bKeepPos) return; } - static_cast(GetObjectManager())->NotifyObjectListeners(child, ON_PREATTACHED); child->NotifyListeners(bKeepPos ? ON_PREATTACHEDKEEPXFORM : ON_PREATTACHED); pTransformDelegate = m_pTransformDelegate; @@ -2129,7 +1827,6 @@ void CBaseObject::AttachChild(CBaseObject* child, bool bKeepPos) m_pTransformDelegate = pTransformDelegate; child->m_pTransformDelegate = pChildTransformDelegate; - static_cast(GetObjectManager())->NotifyObjectListeners(child, ON_ATTACHED); child->NotifyListeners(ON_ATTACHED); NotifyListeners(ON_CHILDATTACHED); @@ -2167,7 +1864,6 @@ void CBaseObject::DetachThis(bool bKeepPos) { CScopedSuspendUndo suspendUndo; - static_cast(GetObjectManager())->NotifyObjectListeners(this, ON_PREDETACHED); NotifyListeners(bKeepPos ? ON_PREDETACHEDKEEPXFORM : ON_PREDETACHED); pTransformDelegate = m_pTransformDelegate; @@ -2198,7 +1894,6 @@ void CBaseObject::DetachThis(bool bKeepPos) SetTransformDelegate(pTransformDelegate); - static_cast(GetObjectManager())->NotifyObjectListeners(this, ON_DETACHED); NotifyListeners(ON_DETACHED); } } @@ -2302,12 +1997,6 @@ Matrix34 CBaseObject::GetParentAttachPointWorldTM() const return Matrix34(IDENTITY); } -////////////////////////////////////////////////////////////////////////// -bool CBaseObject::IsParentAttachmentValid() const -{ - return true; -} - ////////////////////////////////////////////////////////////////////////// void CBaseObject::InvalidateTM([[maybe_unused]] int flags) { @@ -2458,7 +2147,7 @@ void CBaseObject::SetLookAt(CBaseObject* target) return; } - StoreUndo("Change LookAt"); + StoreUndo(); if (m_lookat) { @@ -2583,63 +2272,6 @@ void CBaseObject::Validate(IErrorReport* report) ////////////////////////////////////////////////////////////////////////// }; -////////////////////////////////////////////////////////////////////////// -Ang3 CBaseObject::GetWorldAngles() const -{ - if (m_scale == Vec3(1, 1, 1)) - { - Quat q = Quat(GetWorldTM()); - Ang3 angles = RAD2DEG(Ang3::GetAnglesXYZ(Matrix33(q))); - return angles; - } - else - { - Matrix34 tm = GetWorldTM(); - tm.OrthonormalizeFast(); - Quat q = Quat(tm); - Ang3 angles = RAD2DEG(Ang3::GetAnglesXYZ(Matrix33(q))); - return angles; - } -}; - -////////////////////////////////////////////////////////////////////////// -void CBaseObject::PostClone(CBaseObject* pFromObject, CObjectCloneContext& ctx) -{ - CBaseObject* pFromParent = pFromObject->GetParent(); - if (pFromParent) - { - SetFloorNumber(pFromObject->GetFloorNumber()); - - CBaseObject* pFromParentInContext = ctx.FindClone(pFromParent); - if (pFromParentInContext) - { - pFromParentInContext->AddMember(this, false); - } - else - { - pFromParent->AddMember(this, false); - } - } - if (pFromObject->ShouldCloneChildren()) - { - for (int i = 0; i < pFromObject->GetChildCount(); i++) - { - CBaseObject* pChildObject = pFromObject->GetChild(i); - CBaseObject* pClonedChild = GetObjectManager()->CloneObject(pChildObject); - ctx.AddClone(pChildObject, pClonedChild); - } - for (int i = 0; i < pFromObject->GetChildCount(); i++) - { - CBaseObject* pChildObject = pFromObject->GetChild(i); - CBaseObject* pClonedChild = ctx.FindClone(pChildObject); - if (pClonedChild) - { - pClonedChild->PostClone(pChildObject, ctx); - } - } - } -} - ////////////////////////////////////////////////////////////////////////// void CBaseObject::GatherUsedResources(CUsedResources& resources) { @@ -2675,79 +2307,6 @@ void CBaseObject::SetMinSpec(uint32 nSpec, bool bSetChildren) } } -////////////////////////////////////////////////////////////////////////// -void CBaseObject::OnPropertyChanged(IVariable*) -{ -} - -////////////////////////////////////////////////////////////////////////// -void CBaseObject::OnMultiSelPropertyChanged(IVariable*) -{ -} - -////////////////////////////////////////////////////////////////////////// -void CBaseObject::OnMenuShowInAssetBrowser() -{ - if (!IsSelected()) - { - CUndo undo("Select Object"); - GetIEditor()->GetObjectManager()->ClearSelection(); - GetIEditor()->SelectObject(this); - } - - GetIEditor()->ExecuteCommand("asset_browser.show_viewport_selection"); -} - -////////////////////////////////////////////////////////////////////////// -void CBaseObject::OnContextMenu(QMenu* menu) -{ - if (!menu->isEmpty()) - { - menu->addSeparator(); - } - CUsedResources resources; - GatherUsedResources(resources); - - static_cast(GetIEditor())->OnObjectContextMenuOpened(menu, this); -} - -////////////////////////////////////////////////////////////////////////// -bool CBaseObject::IntersectRayMesh(const Vec3& raySrc, const Vec3& rayDir, SRayHitInfo& outHitInfo) const -{ - const float fRenderMeshTestDistance = 0.2f; - IRenderNode* pRenderNode = GetEngineNode(); - if (!pRenderNode) - { - return false; - } - - Matrix34 worldTM; - IStatObj* pStatObj = pRenderNode->GetEntityStatObj(0, 0, &worldTM); - if (!pStatObj) - { - return false; - } - - // transform decal into object space - Matrix34 worldTM_Inverted = worldTM.GetInverted(); - Matrix33 worldRot(worldTM_Inverted); - worldRot.Transpose(); - // put hit direction into the object space - Vec3 vRayDir = rayDir.GetNormalized() * worldRot; - // put hit position into the object space - Vec3 vHitPos = worldTM_Inverted.TransformPoint(raySrc); - Vec3 vLineP1 = vHitPos - vRayDir * fRenderMeshTestDistance; - - memset(&outHitInfo, 0, sizeof(outHitInfo)); - outHitInfo.inReferencePoint = vHitPos; - outHitInfo.inRay.origin = vLineP1; - outHitInfo.inRay.direction = vRayDir; - outHitInfo.bInFirstHit = false; - outHitInfo.bUseCache = false; - - return pStatObj->RayIntersection(outHitInfo, nullptr); -} - ////////////////////////////////////////////////////////////////////////// EScaleWarningLevel CBaseObject::GetScaleWarningLevel() const { diff --git a/Code/Editor/Objects/BaseObject.h b/Code/Editor/Objects/BaseObject.h index 3865696ecb..f78755e81d 100644 --- a/Code/Editor/Objects/BaseObject.h +++ b/Code/Editor/Objects/BaseObject.h @@ -35,8 +35,6 @@ struct SSubObjSelectionModifyContext; struct SRayHitInfo; class CPopupMenuItem; class QMenu; -struct IRenderNode; -struct IStatObj; ////////////////////////////////////////////////////////////////////////// typedef _smart_ptr CBaseObjectPtr; @@ -119,15 +117,6 @@ enum ObjectFlags #define ERF_GET_WRITABLE(flags) (flags) -////////////////////////////////////////////////////////////////////////// -//! This flags passed to CBaseObject::BeginEditParams method. -enum ObjectEditFlags -{ - OBJECT_CREATE = 0x001, - OBJECT_EDIT = 0x002, - OBJECT_COLLAPSE_OBJECTPANEL = 0x004 -}; - ////////////////////////////////////////////////////////////////////////// //! Return values from CBaseObject::MouseCreateCallback method. enum MouseCreateResult @@ -137,19 +126,6 @@ enum MouseCreateResult MOUSECREATE_OK, //!< Accept this object. }; -////////////////////////////////////////////////////////////////////////// -// Interface to the object create with the mouse callback. -////////////////////////////////////////////////////////////////////////// -struct IMouseCreateCallback -{ - virtual void Release() = 0; - virtual MouseCreateResult OnMouseEvent(CViewport* view, EMouseEvent event, QPoint& point, int flags) = 0; - // Some process of creation need to be able to be displayed such as creation for custom solid. - virtual void Display([[maybe_unused]] DisplayContext& dc){} - // Called after accepting an object to see if new object creation mode should be continued. - virtual bool ContinueCreation() = 0; -}; - // Flags used for object interaction enum EObjectUpdateFlags { @@ -261,22 +237,11 @@ public: /** Check if both object are of same class. */ - virtual bool IsSameClass(CBaseObject* obj); - virtual void SetDefaultType() { m_objType = OBJTYPE_DUMMY; }; virtual ObjectType GetType() const { - if (m_objType == OBJTYPE_DUMMY) - { - return m_objType; - } - else - { - return m_classDesc->GetObjectType(); - } + return m_classDesc->GetObjectType(); }; - // const char* GetTypeName() const { return m_classDesc->ClassName(); }; QString GetTypeName() const; - virtual QString GetTypeDescription() const { return m_classDesc->ClassName(); }; ////////////////////////////////////////////////////////////////////////// // Flags. @@ -289,8 +254,6 @@ public: // Hidden ID ////////////////////////////////////////////////////////////////////////// static const uint64 s_invalidHiddenID = 0; - uint64 GetHideOrder() const { return m_hideOrder; } - void SetHideOrder(uint64 newID) { m_hideOrder = newID; } //! Returns true if object hidden. bool IsHidden() const; @@ -307,26 +270,19 @@ public: virtual bool IsSelectable() const; // Return texture icon. - bool HaveTextureIcon() const { return m_nTextureIcon != 0; }; int GetTextureIcon() const { return m_nTextureIcon; } void SetTextureIcon(int nTexIcon) { m_nTextureIcon = nTexIcon; } - //! Set shared between missions flag. - virtual void SetShared(bool bShared); //! Set object hidden status. - virtual void SetHidden(bool bHidden, uint64 hiddenId = CBaseObject::s_invalidHiddenID, bool bAnimated = false); + virtual void SetHidden(bool bHidden, bool bAnimated = false); //! Set object frozen status. virtual void SetFrozen(bool bFrozen); //! Set object selected status. virtual void SetSelected(bool bSelect); - //! Return associated 3DEngine render node - virtual IRenderNode* GetEngineNode() const { return nullptr; }; //! Set object highlighted (Note: not selected) virtual void SetHighlight(bool bHighlight); //! Check if object is highlighted. bool IsHighlighted() const { return CheckFlags(OBJFLAG_HIGHLIGHT); } - //! Check if object can have measurement axises. - virtual bool HasMeasurementAxis() const { return true; } //! Check if the object is isolated when the editor is in Isolation Mode virtual bool IsIsolated() const { return false; } @@ -345,8 +301,6 @@ public: ////////////////////////////////////////////////////////////////////////// //! Get name of object. const QString& GetName() const; - virtual QString GetComment() const { return QString(); } - virtual QString GetWarningsText() const; //! Change name of object. virtual void SetName(const QString& name); @@ -376,10 +330,6 @@ public: //! Get object scale. const Vec3 GetScale() const; - virtual bool StartScaling() { return false; } - virtual bool GetUntransformedScale([[maybe_unused]] Vec3& scale) const { return false; } - virtual bool TransformScale([[maybe_unused]] const Vec3& scale) { return false; } - //! Set flatten area. void SetArea(float area); float GetArea() const { return m_flattenArea; }; @@ -397,8 +347,6 @@ public: // CHILDS ////////////////////////////////////////////////////////////////////////// - //! Return true if node have childs. - bool HaveChilds() const { return !m_childs.empty(); } //! Return true if have attached childs. size_t GetChildCount() const { return m_childs.size(); } @@ -406,10 +354,6 @@ public: CBaseObject* GetChild(size_t const i) const; //! Return parent node if exist. CBaseObject* GetParent() const { return m_parent; }; - //! Scans hierarchy up to determine if we child of specified node. - virtual bool IsChildOf(CBaseObject* node); - //! Clone Children - void CloneChildren(CBaseObject* pFromObject); //! Attach new child node. //! @param bKeepPos if true Child node will keep its world space position. virtual void AttachChild(CBaseObject* child, bool bKeepPos = true); @@ -422,8 +366,6 @@ public: virtual void DetachAll(bool bKeepPos = true); // Detach this node from parent. virtual void DetachThis(bool bKeepPos = true); - // Returns the link parent. - virtual CBaseObject* GetLinkParent() const { return GetParent(); } ////////////////////////////////////////////////////////////////////////// // MATRIX @@ -437,15 +379,11 @@ public: // Gets matrix of parent attachment point virtual Matrix34 GetParentAttachPointWorldTM() const; - // Checks if the attachment point is valid - virtual bool IsParentAttachmentValid() const; - //! Set position in world space. virtual void SetWorldPos(const Vec3& pos, int flags = 0); //! Get position in world space. Vec3 GetWorldPos() const { return GetWorldTM().GetTranslation(); }; - Ang3 GetWorldAngles() const; //! Set xform of object given in world space. virtual void SetWorldTM(const Matrix34& tm, int flags = 0); @@ -460,12 +398,6 @@ public: // Interface to be implemented in plugins. ////////////////////////////////////////////////////////////////////////// - //! Called when object is being created (use GetMouseCreateCallback for more advanced mouse creation callback). - virtual int MouseCreateCallback(CViewport* view, EMouseEvent event, QPoint& point, int flags); - // Return pointer to the callback object used when creating object by the mouse. - // If this function return nullptr MouseCreateCallback method will be used instead. - virtual IMouseCreateCallback* GetMouseCreateCallback() { return nullptr; }; - //! Draw object to specified viewport. virtual void Display([[maybe_unused]] DisplayContext& disp) {} @@ -477,10 +409,6 @@ public: //! Return true if was hit. virtual bool HitTestRect(HitContext& hc); - //! Perform intersection testing of this object based on its icon helper. - //! Return true if was hit. - virtual bool HitHelperTest(HitContext& hc); - //! Get bounding box of object in world coordinate space. virtual void GetBoundBox(AABB& box); @@ -500,8 +428,6 @@ public: //! @param bUndo true if loading or saving data for Undo/Redo purposes. virtual void Serialize(CObjectArchive& ar); - //// Pre load called before serialize after all objects where completly loaded. - //virtual void PreLoad( CObjectArchive &ar ) {}; // Post load called after all objects where completely loaded. virtual void PostLoad([[maybe_unused]] CObjectArchive& ar) {}; @@ -513,9 +439,6 @@ public: //! Override in derived classes, to handle specific events. virtual void OnEvent(ObjectEvent event); - //! Generate dynamic context menu for the object - virtual void OnContextMenu(QMenu* menu); - ////////////////////////////////////////////////////////////////////////// // LookAt Target. ////////////////////////////////////////////////////////////////////////// @@ -523,13 +446,11 @@ public: CBaseObject* GetLookAt() const { return m_lookat; }; //! Returns true if this object is a look-at target. bool IsLookAtTarget() const; - CBaseObject* GetLookAtSource() const { return m_lookatSource; }; - IObjectManager* GetObjectManager() const; //! Store undo information for this object. - void StoreUndo(const char* undoDescription, bool minimal = false, int flags = 0); + void StoreUndo(bool minimal = false, int flags = 0); //! Add event listener callback. void AddEventListener(EventListener* listener); @@ -548,52 +469,21 @@ public: //! Check if specified object is very similar to this one. virtual bool IsSimilarObject(CBaseObject* pObject); - ////////////////////////////////////////////////////////////////////////// - // Material Layers Mask. - ////////////////////////////////////////////////////////////////////////// - virtual void SetMaterialLayersMask(uint32 nLayersMask) { m_nMaterialLayersMask = nLayersMask; } - uint32 GetMaterialLayersMask() const { return m_nMaterialLayersMask; }; - ////////////////////////////////////////////////////////////////////////// // Object minimal usage spec (All/Low/Medium/High) ////////////////////////////////////////////////////////////////////////// uint32 GetMinSpec() const { return m_nMinSpec; } virtual void SetMinSpec(uint32 nSpec, bool bSetChildren = true); - ////////////////////////////////////////////////////////////////////////// - // SubObj selection. - ////////////////////////////////////////////////////////////////////////// - // Return true if object support selecting of this sub object element type. - virtual bool StartSubObjSelection([[maybe_unused]] int elemType) { return false; }; - virtual void EndSubObjectSelection() {}; - virtual void ModifySubObjSelection([[maybe_unused]] SSubObjSelectionModifyContext& modCtx) {}; - virtual void AcceptSubObjectModify() {}; - //! In This function variables of the object must be initialized. virtual void InitVariables() {}; - ////////////////////////////////////////////////////////////////////////// - // Procedural Floor Management. - ////////////////////////////////////////////////////////////////////////// - int GetFloorNumber() const { return m_floorNumber; }; - void SetFloorNumber(int floorNumber) { m_floorNumber = floorNumber; }; - - virtual void OnPropertyChanged(IVariable*); - virtual void OnMultiSelPropertyChanged(IVariable*); - //! Draw a reddish highlight indicating its budget usage. virtual void DrawBudgetUsage(DisplayContext& dc, const QColor& color); - bool IntersectRayMesh(const Vec3& raySrc, const Vec3& rayDir, SRayHitInfo& outHitInfo) const; - - virtual void EditTags([[maybe_unused]] bool alwaysTag) {} - virtual bool SupportsEditTags() const { return false; } - bool CanBeHightlighted() const; bool IsSkipSelectionHelper() const; - virtual IStatObj* GetIStatObj() { return nullptr; } - // Invalidates cached transformation matrix. // nWhyFlags - Flags that indicate the reason for matrix invalidation. virtual void InvalidateTM(int nWhyFlags); @@ -612,26 +502,14 @@ protected: //! Optional file parameter specify initial object or script for this object. virtual bool Init(IEditor* ie, CBaseObject* prev, const QString& file); - ////////////////////////////////////////////////////////////////////////// - //! Must be called after cloning the object on clone of object. - //! This will make sure object references are cloned correctly. - virtual void PostClone(CBaseObject* pFromObject, CObjectCloneContext& ctx); - //! Must be implemented by derived class to create game related objects. virtual bool CreateGameObject() { return true; }; - //! If true, all attached chilren will be cloned when the parent object is cloned. - virtual bool ShouldCloneChildren() const { return true; } - /** Called when object is about to be deleted. - All Game resources should be freed in this function. + All Game resources should be freed in this function. */ virtual void Done(); - /** Change current id of object. - */ - //virtual void SetId( uint32 objectId ) { m_id = objectId; }; - //! Call this to delete an object. virtual void DeleteThis() = 0; @@ -674,11 +552,6 @@ protected: //! Returns if the object can be drawn, and if its selection helper should also be drawn. bool CanBeDrawn(const DisplayContext& dc, bool& outDisplaySelectionHelper) const; - //! Returns if object is in the camera view. - virtual bool IsInCameraView(const CCamera& camera); - //! Returns vis ratio of object in camera - virtual float GetCameraVisRatio(const CCamera& camera); - // Do basic intersection tests virtual bool IntersectRectBounds(const AABB& bbox); virtual bool IntersectRayBounds(const Ray& ray); @@ -687,17 +560,11 @@ protected: // Function can be used by derived classes. bool HitTestRectBounds(HitContext& hc, const AABB& box); - // Do helper hit testing as specific location. - bool HitHelperAtTest(HitContext& hc, const Vec3& pos); - // Do helper hit testing taking child objects into account (e.g. opened prefab) virtual bool HitHelperTestForChildObjects([[maybe_unused]] HitContext& hc) { return false; } CBaseObject* FindObject(REFGUID id) const; - // Returns true if game objects should be created. - bool IsCreateGameObjects() const; - // Helper gizmo functions. void AddGizmo(CGizmo* gizmo); void RemoveGizmo(CGizmo* gizmo); @@ -708,14 +575,6 @@ protected: //! Only used by ObjectManager. bool IsPotentiallyVisible() const; - ////////////////////////////////////////////////////////////////////////// - // May be overridden in derived classes to handle helpers scaling. - ////////////////////////////////////////////////////////////////////////// - virtual void SetHelperScale([[maybe_unused]] float scale) {}; - virtual float GetHelperScale() { return 1.0f; }; - - void SetNameInternal(const QString& name) { m_name = name; } - void SetDrawTextureIconProperties(DisplayContext& dc, const Vec3& pos, float alpha = 1.0f, int texIconFlags = 0); const Vec3& GetTextureIconDrawPos(){ return m_vDrawIconPos; }; int GetTextureIconFlags(){ return m_nIconFlags; }; @@ -737,8 +596,6 @@ private: friend class CObjectArchive; friend class CSelectionGroup; - void OnMenuShowInAssetBrowser(); - //! Set class description for this object, //! Only called once after creation by ObjectManager. void SetClassDesc(CObjectClassDesc* classDesc); @@ -746,9 +603,6 @@ private: EScaleWarningLevel GetScaleWarningLevel() const; ERotationWarningLevel GetRotationWarningLevel() const; - // auto resolving - void OnMtlResolved(uint32 id, bool success, const char* orgName, const char* newName); - bool IsInSelectionBox() const { return m_bInSelectionBox; } void SetId(REFGUID guid) { m_guid = guid; } @@ -771,13 +625,10 @@ private: //! Unique object Id. GUID m_guid; - // floor number of object if procedural object flag is set - int m_floorNumber; - //! Flags of this object. int m_flags; - // Id of the texture icon for this object. + //! Id of the texture icon for this object. int m_nTextureIcon; //! Display color. @@ -828,13 +679,10 @@ private: mutable uint32 m_bMatrixValid : 1; mutable uint32 m_bWorldBoxValid : 1; uint32 m_bInSelectionBox : 1; - uint32 m_nMaterialLayersMask : 8; uint32 m_nMinSpec : 8; Vec3 m_vDrawIconPos; AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING - - uint64 m_hideOrder; }; Q_DECLARE_METATYPE(CBaseObject*) diff --git a/Code/Editor/Objects/DisplayContext.h b/Code/Editor/Objects/DisplayContext.h index 0f0f0e665a..8f54be78fe 100644 --- a/Code/Editor/Objects/DisplayContext.h +++ b/Code/Editor/Objects/DisplayContext.h @@ -31,7 +31,6 @@ struct IRenderer; struct IRenderAuxGeom; struct IIconManager; class CDisplaySettings; -class CCamera; class QPoint; enum DisplayFlags @@ -66,7 +65,6 @@ struct SANDBOX_API DisplayContext IDisplayViewport* view; IRenderAuxGeom* pRenderAuxGeom; IIconManager* pIconManager; - CCamera* camera; AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING AABB box; // Bounding box of volume that need to be repainted. AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING @@ -83,11 +81,38 @@ struct SANDBOX_API DisplayContext // Draw functions ////////////////////////////////////////////////////////////////////////// //! Set current materialc color. - void SetColor(float r, float g, float b, float a = 1) { m_color4b = ColorB(static_cast(r * 255.0f), static_cast(g * 255.0f), static_cast(b * 255.0f), static_cast(a * 255.0f)); }; - void SetColor(const Vec3& color, float a = 1) { m_color4b = ColorB(static_cast(color.x * 255.0f), static_cast(color.y * 255.0f), static_cast(color.z * 255.0f), static_cast(a * 255.0f)); }; - void SetColor(const QColor& rgb, float a) { m_color4b = ColorB(static_cast(rgb.red()), static_cast(rgb.green()), static_cast(rgb.blue()), static_cast(a * 255.0f)); }; - void SetColor(const QColor& color) { m_color4b = ColorB(static_cast(color.red()), static_cast(color.green()), static_cast(color.blue()), static_cast(color.alpha())); }; - void SetColor(const ColorB& color) { m_color4b = color; }; + void SetColor(float r, float g, float b, float a = 1) + { + m_color4b = ColorB( + static_cast(r * 255.0f), static_cast(g * 255.0f), static_cast(b * 255.0f), static_cast(a * 255.0f)); + }; + void SetColor(const Vec3& color, float a = 1) + { + m_color4b = ColorB( + static_cast(color.x * 255.0f), static_cast(color.y * 255.0f), static_cast(color.z * 255.0f), + static_cast(a * 255.0f)); + }; + void SetColor(const AZ::Vector3& color, float a = 1) + { + m_color4b = ColorB( + static_cast(color.GetX() * 255.0f), static_cast(color.GetY() * 255.0f), static_cast(color.GetZ() * 255.0f), + static_cast(a * 255.0f)); + }; + void SetColor(const QColor& rgb, float a) + { + m_color4b = ColorB( + static_cast(rgb.red()), static_cast(rgb.green()), static_cast(rgb.blue()), static_cast(a * 255.0f)); + }; + void SetColor(const QColor& color) + { + m_color4b = ColorB( + static_cast(color.red()), static_cast(color.green()), static_cast(color.blue()), + static_cast(color.alpha())); + }; + void SetColor(const ColorB& color) + { + m_color4b = color; + }; void SetAlpha(float a = 1) { m_color4b.a = static_cast(a * 255.0f); }; ColorB GetColor() const { return m_color4b; } @@ -110,6 +135,7 @@ struct SANDBOX_API DisplayContext void DrawTrianglesIndexed(const AZStd::vector& vertices, const AZStd::vector& indices, const ColorB& color); // Draw wireframe box. void DrawWireBox(const Vec3& min, const Vec3& max); + void DrawWireBox(const AZ::Vector3& min, const AZ::Vector3& max); // Draw filled box void DrawSolidBox(const Vec3& min, const Vec3& max); void DrawSolidOBB(const Vec3& center, const Vec3& axisX, const Vec3& axisY, const Vec3& axisZ, const Vec3& halfExtents); diff --git a/Code/Editor/Objects/DisplayContextShared.inl b/Code/Editor/Objects/DisplayContextShared.inl index df0b1f82a1..1fbd2ef7b6 100644 --- a/Code/Editor/Objects/DisplayContextShared.inl +++ b/Code/Editor/Objects/DisplayContextShared.inl @@ -225,6 +225,12 @@ void DisplayContext::DrawWireBox(const Vec3& min, const Vec3& max) pRenderAuxGeom->DrawAABB(AABB(min, max), m_matrixStack[m_currentMatrix], false, m_color4b, eBBD_Faceted); } +void DisplayContext::DrawWireBox(const AZ::Vector3& min, const AZ::Vector3& max) +{ + pRenderAuxGeom->DrawAABB( + AABB(Vec3(min.GetX(), min.GetY(), min.GetZ()), Vec3(max.GetX(), max.GetY(), max.GetZ())), + m_matrixStack[m_currentMatrix], false, m_color4b, eBBD_Faceted); +} ////////////////////////////////////////////////////////////////////////// void DisplayContext::DrawSolidBox(const Vec3& min, const Vec3& max) { @@ -1138,10 +1144,6 @@ bool DisplayContext::IsVisible(const AABB& bounds) return true; } } - else - { - return camera->IsAABBVisible_F(AABB(bounds.min, bounds.max)); - } return false; } diff --git a/Code/Editor/Objects/EntityObject.cpp b/Code/Editor/Objects/EntityObject.cpp index b2dfc04102..86512377c6 100644 --- a/Code/Editor/Objects/EntityObject.cpp +++ b/Code/Editor/Objects/EntityObject.cpp @@ -28,8 +28,7 @@ #include "HitContext.h" #include "Objects/SelectionGroup.h" -#include -#include +static constexpr int VIEW_DISTANCE_MULTIPLIER_MAX = 100; ////////////////////////////////////////////////////////////////////////// //! Undo Entity Link @@ -58,7 +57,6 @@ public: protected: void Release() override { delete this; }; int GetSize() override { return sizeof(*this); }; // Return size of xml state. - QString GetDescription() override { return "Entity Link"; }; QString GetObjectName() override{ return ""; }; void Undo([[maybe_unused]] bool bUndo) override @@ -139,7 +137,6 @@ private: } int GetSize() override { return sizeof(CUndoAttachEntity); } - QString GetDescription() override { return "Attachment Changed"; } GUID m_attachedEntityGUID; CEntityObject::EAttachmentType m_attachmentType; @@ -151,8 +148,6 @@ private: // CBase implementation. ////////////////////////////////////////////////////////////////////////// -float CEntityObject::m_helperScale = 1; - namespace { CEntityObject* s_pPropertyPanelEntityObject = nullptr; @@ -163,12 +158,9 @@ namespace ////////////////////////////////////////////////////////////////////////// CEntityObject::CEntityObject() - : m_listeners(1) { m_bLoadFailed = false; - m_visualObject = nullptr; - m_box.min.Set(0, 0, 0); m_box.max.Set(0, 0, 0); @@ -223,7 +215,7 @@ CEntityObject::CEntityObject() mv_ratioLOD = 100; mv_viewDistanceMultiplier = 1.0f; mv_ratioLOD.SetLimits(0, 255); - mv_viewDistanceMultiplier.SetLimits(0.0f, IRenderNode::VIEW_DISTANCE_MULTIPLIER_MAX); + mv_viewDistanceMultiplier.SetLimits(0.0f, VIEW_DISTANCE_MULTIPLIER_MAX); m_physicsState = nullptr; @@ -247,7 +239,6 @@ CEntityObject::CEntityObject() m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnProjectInAllDirsChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnProjectorFOVChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnProjectorTextureChange(var); }); - m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnPropertyChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnRadiusChange(var); }); } @@ -295,11 +286,6 @@ void CEntityObject::Done() ReleaseEventTargets(); RemoveAllEntityLinks(); - for (CListenerSet::Notifier notifier(m_listeners); notifier.IsValid(); notifier.Next()) - { - notifier->OnDone(); - } - CBaseObject::Done(); } @@ -365,12 +351,6 @@ void CEntityObject::SetTransformDelegate(ITransformDelegate* pTransformDelegate) ResetCallbacks(); } -////////////////////////////////////////////////////////////////////////// -bool CEntityObject::IsSameClass(CBaseObject* obj) -{ - return (GetClassDesc() == obj->GetClassDesc()); -} - ////////////////////////////////////////////////////////////////////////// bool CEntityObject::ConvertFromObject(CBaseObject* object) { @@ -458,33 +438,10 @@ bool CEntityObject::HitTest(HitContext& hc) return false; } -////////////////////////////////////////////////////////////////////////// -bool CEntityObject::HitHelperTest(HitContext& hc) -{ - bool bResult = CBaseObject::HitHelperTest(hc); - if (bResult) - { - hc.object = this; - } - - return bResult; -} - ////////////////////////////////////////////////////////////////////////// bool CEntityObject::HitTestRect(HitContext& hc) { - bool bResult = false; - - if (m_visualObject && !gSettings.viewports.bShowIcons && !gSettings.viewports.bShowSizeBasedIcons) - { - AABB box; - box.SetTransformedAABB(GetWorldTM(), m_visualObject->GetAABB()); - bResult = HitTestRectBounds(hc, box); - } - else - { - bResult = CBaseObject::HitTestRect(hc); - } + bool bResult = CBaseObject::HitTestRect(hc); if (bResult) { @@ -494,42 +451,6 @@ bool CEntityObject::HitTestRect(HitContext& hc) return bResult; } -////////////////////////////////////////////////////////////////////////// -int CEntityObject::MouseCreateCallback(CViewport* view, EMouseEvent event, QPoint& point, int flags) -{ - AZ_PROFILE_FUNCTION(Entity); - - if (event == eMouseMove || event == eMouseLDown) - { - Vec3 pos; - // Rise Entity above ground on Bounding box amount. - if (GetIEditor()->GetAxisConstrains() != AXIS_TERRAIN) - { - pos = view->MapViewToCP(point); - } - else - { - // Snap to terrain. - bool hitTerrain; - pos = view->ViewToWorld(point, &hitTerrain); - if (hitTerrain) - { - pos.z = GetIEditor()->GetTerrainElevation(pos.x, pos.y); - pos.z = pos.z - m_box.min.z; - } - pos = view->SnapToGrid(pos); - } - SetPos(pos); - - if (event == eMouseLDown) - { - return MOUSECREATE_OK; - } - return MOUSECREATE_CONTINUE; - } - return CBaseObject::MouseCreateCallback(view, event, point, flags); -} - ////////////////////////////////////////////////////////////////////////// IVariable* CEntityObject::FindVariableInSubBlock(CVarBlockPtr& properties, IVariable* pSubBlockVar, const char* pVarName) { @@ -592,11 +513,11 @@ void CEntityObject::AdjustLightProperties(CVarBlockPtr& properties, const char* if (IVariable* pCastShadowVarLegacy = FindVariableInSubBlock(properties, pSubBlockVar, "bCastShadow")) { pCastShadowVarLegacy->SetFlags(pCastShadowVarLegacy->GetFlags() | IVariable::UI_INVISIBLE); - - if (pCastShadowVarLegacy->GetDisplayValue()[0] != '0') + const QString zeroPrefix("0"); + if (!pCastShadowVarLegacy->GetDisplayValue().startsWith(zeroPrefix)) { bCastShadowLegacy = true; - pCastShadowVarLegacy->SetDisplayValue("0"); + pCastShadowVarLegacy->SetDisplayValue(zeroPrefix); } } @@ -680,11 +601,6 @@ void CEntityObject::SetName(const QString& name) CBaseObject::SetName(name); - CListenerSet listeners = m_listeners; - for (CListenerSet::Notifier notifier(listeners); notifier.IsValid(); notifier.Next()) - { - notifier->OnNameChanged(name.toUtf8().data()); - } } ////////////////////////////////////////////////////////////////////////// @@ -697,19 +613,6 @@ void CEntityObject::SetSelected(bool bSelect) UpdateLightProperty(); } - for (CListenerSet::Notifier notifier(m_listeners); notifier.IsValid(); notifier.Next()) - { - notifier->OnSelectionChanged(bSelect); - } -} - -////////////////////////////////////////////////////////////////////////// -void CEntityObject::OnPropertyChange([[maybe_unused]] IVariable* var) -{ - if (s_ignorePropertiesUpdate) - { - return; - } } template @@ -941,11 +844,9 @@ void CEntityObject::Serialize(CObjectArchive& ar) m_eventTargets.emplace_back(AZStd::move(et)); if (targetId != GUID_NULL) { - using namespace AZStd::placeholders; ar.SetResolveCallback( this, targetId, - [this](CBaseObject* object, unsigned int index) { ResolveEventTarget(object, index); }, - i); + [this,i](CBaseObject* object) { ResolveEventTarget(object, i); }); } } } @@ -1154,12 +1055,6 @@ XmlNodeRef CEntityObject::Export([[maybe_unused]] const QString& levelPath, XmlN objNode->setAttr("MinSpec", ( uint32 )GetMinSpec()); } - uint32 nMtlLayersMask = GetMaterialLayersMask(); - if (nMtlLayersMask != 0) - { - objNode->setAttr("MatLayersMask", nMtlLayersMask); - } - if (mv_hiddenInGame) { objNode->setAttr("HiddenInGame", true); @@ -1256,11 +1151,6 @@ void CEntityObject::OnEvent(ObjectEvent event) case EVENT_CONFIG_SPEC_CHANGE: { - IObjectManager* objMan = GetIEditor()->GetObjectManager(); - if (objMan && objMan->IsLightClass(this)) - { - OnPropertyChange(nullptr); - } break; } default: @@ -1350,56 +1240,6 @@ QString CEntityObject::GetLightAnimation() const return ""; } -////////////////////////////////////////////////////////////////////////// -void CEntityObject::PostClone(CBaseObject* pFromObject, CObjectCloneContext& ctx) -{ - CBaseObject::PostClone(pFromObject, ctx); - - CEntityObject* pFromEntity = ( CEntityObject* )pFromObject; - // Clone event targets. - if (!pFromEntity->m_eventTargets.empty()) - { - size_t numTargets = pFromEntity->m_eventTargets.size(); - for (size_t i = 0; i < numTargets; i++) - { - CEntityEventTarget& et = pFromEntity->m_eventTargets[i]; - CBaseObject* pClonedTarget = ctx.FindClone(et.target); - if (!pClonedTarget) - { - pClonedTarget = et.target; // If target not cloned, link to original target. - } - - // Add cloned event. - AddEventTarget(pClonedTarget, et.event, et.sourceEvent, true); - } - } - - // Clone links. - if (!pFromEntity->m_links.empty()) - { - int numTargets = static_cast(pFromEntity->m_links.size()); - for (int i = 0; i < numTargets; i++) - { - CEntityLink& et = pFromEntity->m_links[i]; - CBaseObject* pClonedTarget = ctx.FindClone(et.target); - if (!pClonedTarget) - { - pClonedTarget = et.target; // If target not cloned, link to original target. - } - - // Add cloned event. - if (pClonedTarget) - { - AddEntityLink(et.name, pClonedTarget->GetId()); - } - else - { - AddEntityLink(et.name, GUID_NULL); - } - } - } -} - ////////////////////////////////////////////////////////////////////////// void CEntityObject::ResolveEventTarget(CBaseObject* object, unsigned int index) { @@ -1554,7 +1394,7 @@ void CEntityObject::OnObjectEvent(CBaseObject* target, int event) ////////////////////////////////////////////////////////////////////////// int CEntityObject::AddEventTarget(CBaseObject* target, const QString& event, const QString& sourceEvent, [[maybe_unused]] bool bUpdateScript) { - StoreUndo("Add EventTarget"); + StoreUndo(); CEntityEventTarget et; et.target = target; et.event = event; @@ -1588,7 +1428,7 @@ void CEntityObject::RemoveEventTarget(int index, [[maybe_unused]] bool bUpdateSc { if (index >= 0 && index < m_eventTargets.size()) { - StoreUndo("Remove EventTarget"); + StoreUndo(); if (m_eventTargets[index].pLineGizmo) { @@ -1624,7 +1464,7 @@ int CEntityObject::AddEntityLink(const QString& name, GUID targetEntityId) } } - StoreUndo("Add EntityLink"); + StoreUndo(); CLineGizmo* pLineGizmo = nullptr; @@ -1672,7 +1512,7 @@ void CEntityObject::RemoveEntityLink(int index) if (index >= 0 && index < m_links.size()) { CEntityLink& link = m_links[index]; - StoreUndo("Remove EntityLink"); + StoreUndo(); if (link.pLineGizmo) { @@ -1695,7 +1535,7 @@ void CEntityObject::RenameEntityLink(int index, const QString& newName) { if (index >= 0 && index < m_links.size()) { - StoreUndo("Rename EntityLink"); + StoreUndo(); if (m_links[index].pLineGizmo) { @@ -1842,18 +1682,6 @@ void CEntityObject::OnLoadFailed() GetIEditor()->GetErrorReport()->ReportError(err); } -////////////////////////////////////////////////////////////////////////// -void CEntityObject::SetHelperScale(float scale) -{ - m_helperScale = scale; -} - -////////////////////////////////////////////////////////////////////////// -float CEntityObject::GetHelperScale() -{ - return m_helperScale; -} - ////////////////////////////////////////////////////////////////////////// //! Analyze errors for this object. void CEntityObject::Validate(IErrorReport* report) @@ -1901,19 +1729,6 @@ bool CEntityObject::IsSimilarObject(CBaseObject* pObject) return false; } -////////////////////////////////////////////////////////////////////////// -void CEntityObject::OnContextMenu(QMenu* pMenu) -{ - if (!pMenu->isEmpty()) - { - pMenu->addSeparator(); - } - - // Events - - CBaseObject::OnContextMenu(pMenu); -} - ////////////////////////////////////////////////////////////////////////// void CEntityObject::PreInitLightProperty() { @@ -2171,16 +1986,6 @@ void CEntityObject::StoreUndoEntityLink(CSelectionGroup* pGroup) } } -void CEntityObject::RegisterListener(IEntityObjectListener* pListener) -{ - m_listeners.Add(pListener); -} - -void CEntityObject::UnregisterListener(IEntityObjectListener* pListener) -{ - m_listeners.Remove(pListener); -} - template T CEntityObject::GetEntityProperty(const char* pName, T defaultvalue) const { diff --git a/Code/Editor/Objects/EntityObject.h b/Code/Editor/Objects/EntityObject.h index a4f4752b75..c3e379bccc 100644 --- a/Code/Editor/Objects/EntityObject.h +++ b/Code/Editor/Objects/EntityObject.h @@ -16,10 +16,7 @@ #include "BaseObject.h" #include "IMovieSystem.h" -#include "IEntityObjectListener.h" #include "Gizmo.h" -#include "CryListenerSet.h" -#include "StatObjBus.h" #include #endif @@ -81,11 +78,6 @@ public: ////////////////////////////////////////////////////////////////////////// // Overrides from CBaseObject. ////////////////////////////////////////////////////////////////////////// - //! Return type name of Entity. - QString GetTypeDescription() const override { return GetEntityClass(); }; - - ////////////////////////////////////////////////////////////////////////// - bool IsSameClass(CBaseObject* obj) override; bool Init(IEditor* ie, CBaseObject* prev, const QString& file) override; void InitVariables() override; @@ -102,16 +94,12 @@ public: void SetEntityPropertyFloat(const char* name, float value); void SetEntityPropertyString(const char* name, const QString& value); - int MouseCreateCallback(CViewport* view, EMouseEvent event, QPoint& point, int flags) override; - void OnContextMenu(QMenu* menu) override; - void SetName(const QString& name) override; void SetSelected(bool bSelect) override; void GetLocalBounds(AABB& box) override; bool HitTest(HitContext& hc) override; - bool HitHelperTest(HitContext& hc) override; bool HitTestRect(HitContext& hc) override; void UpdateVisibility(bool bVisible) override; bool ConvertFromObject(CBaseObject* object) override; @@ -139,14 +127,9 @@ public: EAttachmentType GetAttachType() const { return m_attachmentType; } QString GetAttachTarget() const { return m_attachmentTarget; } - void SetHelperScale(float scale) override; - float GetHelperScale() override; - void GatherUsedResources(CUsedResources& resources) override; bool IsSimilarObject(CBaseObject* pObject) override; - bool HasMeasurementAxis() const override { return false; } - bool IsIsolated() const override { return false; } ////////////////////////////////////////////////////////////////////////// @@ -220,20 +203,12 @@ public: static void StoreUndoEntityLink(CSelectionGroup* pGroup); - void RegisterListener(IEntityObjectListener* pListener); - void UnregisterListener(IEntityObjectListener* pListener); - protected: template void SetEntityProperty(const char* name, T value); template T GetEntityProperty(const char* name, T defaultvalue) const; - ////////////////////////////////////////////////////////////////////////// - //! Must be called after cloning the object on clone of object. - //! This will make sure object references are cloned correctly. - void PostClone(CBaseObject* pFromObject, CObjectCloneContext& ctx) override; - //! Draw default object items. void DrawProjectorPyramid(DisplayContext& dc, float dist); void DrawProjectorFrustum(DisplayContext& dc, Vec2 size, float dist); @@ -242,10 +217,6 @@ protected: CVarBlock* CloneProperties(CVarBlock* srcProperties); - ////////////////////////////////////////////////////////////////////////// - //! Callback called when one of entity properties have been modified. - void OnPropertyChange(IVariable* var); - ////////////////////////////////////////////////////////////////////////// void OnObjectEvent(CBaseObject* target, int event) override; void ResolveEventTarget(CBaseObject* object, unsigned int index); @@ -327,7 +298,6 @@ protected: // Used for light entities float m_projectorFOV; - IStatObj* m_visualObject; AABB m_box; ////////////////////////////////////////////////////////////////////////// @@ -388,8 +358,6 @@ protected: XmlNodeRef m_physicsState; AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING - static float m_helperScale; - EAttachmentType m_attachmentType; bool m_bEnableReload; @@ -433,7 +401,6 @@ private: void ForceVariableUpdate(); AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING - CListenerSet m_listeners; std::vector< std::pair > m_callbacks; AZStd::fixed_vector< IVariable::OnSetCallback, VariableCallbackIndex::Count > m_onSetCallbacksCache; AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING diff --git a/Code/Editor/Objects/IEntityObjectListener.h b/Code/Editor/Objects/IEntityObjectListener.h deleted file mode 100644 index 9072a837d0..0000000000 --- a/Code/Editor/Objects/IEntityObjectListener.h +++ /dev/null @@ -1,20 +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 - -class IEntityObjectListener -{ -public: - virtual ~IEntityObjectListener() = default; - - virtual void OnNameChanged(const char* pName) = 0; - virtual void OnSelectionChanged(const bool bSelected) = 0; - virtual void OnDone() = 0; -}; - diff --git a/Code/Editor/Objects/LineGizmo.cpp b/Code/Editor/Objects/LineGizmo.cpp index 31ea341f5b..991f449cd6 100644 --- a/Code/Editor/Objects/LineGizmo.cpp +++ b/Code/Editor/Objects/LineGizmo.cpp @@ -98,16 +98,8 @@ void CLineGizmo::Display(DisplayContext& dc) Vec3 pos = 0.5f * (m_point[0] + m_point[1]); //dc.renderer->DrawLabelEx( p3+Vec3(0,0,0.3f),1.2f,col,true,true,m_name ); - float camDist = dc.camera->GetPosition().GetDistance(pos); - float maxDist = dc.settings->GetLabelsDistance(); - if (camDist < dc.settings->GetLabelsDistance()) { - float range = maxDist / 2.0f; float col[4] = { m_color[0].r, m_color[0].g, m_color[0].b, m_color[0].a }; - if (camDist > range) - { - col[3] = col[3] * (1.0f - (camDist - range) / range); - } dc.SetColor(col[0], col[1], col[2], col[3]); dc.DrawTextLabel(pos + Vec3(0, 0, 0.2f), 1.2f, m_name.toUtf8().data()); } diff --git a/Code/Editor/Objects/ObjectLoader.cpp b/Code/Editor/Objects/ObjectLoader.cpp index 9596265bb9..63ac4c93af 100644 --- a/Code/Editor/Objects/ObjectLoader.cpp +++ b/Code/Editor/Objects/ObjectLoader.cpp @@ -28,19 +28,12 @@ CObjectArchive::CObjectArchive(IObjectManager* objMan, XmlNodeRef xmlRoot, bool m_nFlags = 0; node = xmlRoot; m_pCurrentErrorReport = GetIEditor()->GetErrorReport(); - m_pGeometryPak = nullptr; - m_pCurrentObject = nullptr; m_bNeedResolveObjects = false; - m_bProgressBarEnabled = true; } ////////////////////////////////////////////////////////////////////////// CObjectArchive::~CObjectArchive() { - if (m_pGeometryPak) - { - delete m_pGeometryPak; - } // Always make sure objects are resolved when loading from archive. if (bLoading && m_bNeedResolveObjects) { @@ -74,31 +67,6 @@ void CObjectArchive::SetResolveCallback(CBaseObject* fromObject, REFGUID objectI } } -////////////////////////////////////////////////////////////////////////// -void CObjectArchive::SetResolveCallback(CBaseObject* fromObject, REFGUID objectId, ResolveObjRefFunctor2 func, uint32 userData) -{ - if (objectId == GUID_NULL) - { - func(0, userData); - return; - } - - CBaseObject* object = m_objectManager->FindObject(objectId); - if (object && !(m_nFlags & eObjectLoader_MakeNewIDs)) - { - // Object is already resolved. immidiatly call callback. - func(object, userData); - } - else - { - Callback cb; - cb.fromObject = fromObject; - cb.func2 = func; - cb.userData = userData; - m_resolveCallbacks.insert(Callbacks::value_type(objectId, cb)); - } -} - ////////////////////////////////////////////////////////////////////////// GUID CObjectArchive::ResolveID(REFGUID id) { @@ -117,10 +85,7 @@ void CObjectArchive::ResolveObjects() { CWaitProgress wait("Loading Objects", false); - if (m_bProgressBarEnabled) - { - wait.Start(); - } + wait.Start(); GetIEditor()->SuspendUndo(); ////////////////////////////////////////////////////////////////////////// @@ -129,10 +94,7 @@ void CObjectArchive::ResolveObjects() int numObj = static_cast(m_loadedObjects.size()); for (i = 0; i < numObj; i++) { - if (m_bProgressBarEnabled) - { - wait.Step((i * 100) / numObj); - } + wait.Step((i * 100) / numObj); SLoadedObjectInfo& obj = m_loadedObjects[i]; m_pCurrentErrorReport->SetCurrentValidatorObject(obj.pObject); @@ -204,30 +166,20 @@ void CObjectArchive::ResolveObjects() { (cb.func1)(object); } - if (cb.func2) - { - (cb.func2)(object, cb.userData); - } } m_resolveCallbacks.clear(); ////////////////////////////////////////////////////////////////////////// { CWaitProgress wait("Creating Objects", false); - if (m_bProgressBarEnabled) - { - wait.Start(); - } + wait.Start(); ////////////////////////////////////////////////////////////////////////// // Serialize All Objects from XML. ////////////////////////////////////////////////////////////////////////// int numObj = static_cast(m_loadedObjects.size()); for (i = 0; i < numObj; i++) { - if (m_bProgressBarEnabled) - { - wait.Step((i * 100) / numObj); - } + wait.Step((i * 100) / numObj); SLoadedObjectInfo& obj = m_loadedObjects[i]; m_pCurrentErrorReport->SetCurrentValidatorObject(obj.pObject); @@ -258,8 +210,6 @@ void CObjectArchive::ResolveObjects() m_bNeedResolveObjects = false; m_pCurrentErrorReport->SetCurrentValidatorObject(nullptr); - m_sequenceIdRemap.clear(); - m_pendingIds.clear(); } ////////////////////////////////////////////////////////////////////////// @@ -272,7 +222,6 @@ void CObjectArchive::SaveObject(CBaseObject* pObject) if (m_savedObjects.find(pObject) == m_savedObjects.end()) { - m_pCurrentObject = pObject; m_savedObjects.insert(pObject); // If this object was not saved before. XmlNodeRef objNode = node->newChild("Object"); @@ -307,45 +256,6 @@ CBaseObject* CObjectArchive::LoadObject(const XmlNodeRef& objNode, CBaseObject* return pObject; } -////////////////////////////////////////////////////////////////////////// -void CObjectArchive::LoadObjects(XmlNodeRef& rootObjectsNode) -{ - int numObjects = rootObjectsNode->getChildCount(); - for (int i = 0; i < numObjects; i++) - { - XmlNodeRef objNode = rootObjectsNode->getChild(i); - LoadObject(objNode, nullptr); - } -} - -////////////////////////////////////////////////////////////////////////// -void CObjectArchive::ReportError(CErrorRecord& err) -{ - if (m_pCurrentErrorReport) - { - m_pCurrentErrorReport->ReportError(err); - } -} - -////////////////////////////////////////////////////////////////////////// -void CObjectArchive::SetErrorReport(CErrorReport* errReport) -{ - if (errReport) - { - m_pCurrentErrorReport = errReport; - } - else - { - m_pCurrentErrorReport = GetIEditor()->GetErrorReport(); - } -} - -////////////////////////////////////////////////////////////////////////// -void CObjectArchive::ShowErrors() -{ - GetIEditor()->GetErrorReport()->Display(); -} - ////////////////////////////////////////////////////////////////////////// void CObjectArchive::MakeNewIds(bool bEnable) { @@ -359,70 +269,8 @@ void CObjectArchive::MakeNewIds(bool bEnable) } } -////////////////////////////////////////////////////////////////////////// -void CObjectArchive::SetShouldResetInternalMembers(bool reset) -{ - if (reset) - { - m_nFlags |= eObjectLoader_ResetInternalMembers; - } - else - { - m_nFlags &= ~(eObjectLoader_ResetInternalMembers); - } -} - ////////////////////////////////////////////////////////////////////////// void CObjectArchive::RemapID(REFGUID oldId, REFGUID newId) { m_IdRemap[oldId] = newId; } - -////////////////////////////////////////////////////////////////////////// -CPakFile* CObjectArchive::GetGeometryPak(const char* sFilename) -{ - if (m_pGeometryPak) - { - return m_pGeometryPak; - } - m_pGeometryPak = new CPakFile; - m_pGeometryPak->Open(sFilename); - return m_pGeometryPak; -} - -////////////////////////////////////////////////////////////////////////// -CBaseObject* CObjectArchive::GetCurrentObject() -{ - return m_pCurrentObject; -} - -////////////////////////////////////////////////////////////////////////// -void CObjectArchive::AddSequenceIdMapping(uint32 oldId, uint32 newId) -{ - assert(oldId != newId); - assert(GetIEditor()->GetMovieSystem()->FindSequenceById(oldId) || stl::find(m_pendingIds, oldId)); - assert(GetIEditor()->GetMovieSystem()->FindSequenceById(newId) == nullptr); - assert(stl::find(m_pendingIds, newId) == false); - m_sequenceIdRemap[oldId] = newId; - m_pendingIds.push_back(newId); -} - -////////////////////////////////////////////////////////////////////////// -uint32 CObjectArchive::RemapSequenceId(uint32 id) const -{ - std::map::const_iterator itr = m_sequenceIdRemap.find(id); - if (itr == m_sequenceIdRemap.end()) - { - return id; - } - else - { - return itr->second; - } -} - -////////////////////////////////////////////////////////////////////////// -bool CObjectArchive::IsAmongPendingIds(uint32 id) const -{ - return stl::find(m_pendingIds, id); -} diff --git a/Code/Editor/Objects/ObjectLoader.h b/Code/Editor/Objects/ObjectLoader.h index a491e50da0..fc1014ad03 100644 --- a/Code/Editor/Objects/ObjectLoader.h +++ b/Code/Editor/Objects/ObjectLoader.h @@ -6,18 +6,13 @@ * */ - -#ifndef CRYINCLUDE_EDITOR_OBJECTS_OBJECTLOADER_H -#define CRYINCLUDE_EDITOR_OBJECTS_OBJECTLOADER_H #pragma once #include "Util/GuidUtil.h" #include "ErrorReport.h" +#include -#include - -class CPakFile; class CErrorRecord; struct IObjectManager; @@ -43,63 +38,27 @@ AZ_POP_DISABLE_DLL_EXPORT_BASECLASS_WARNING //! Resolve callback with only one parameter of CBaseObject. typedef AZStd::function ResolveObjRefFunctor1; - //! Resolve callback with two parameters one is pointer to CBaseObject and second use data integer. - typedef AZStd::function ResolveObjRefFunctor2; - - /** Register Object id. - @param objectId Original object id from the file. - @param realObjectId Changed object id. - */ - //void RegisterObjectId( int objectId,int realObjectId ); // Return object ID remapped after loading. GUID ResolveID(REFGUID id); //! Set object resolve callback, it will be called once object with specified Id is loaded. void SetResolveCallback(CBaseObject* fromObject, REFGUID objectId, ResolveObjRefFunctor1 func); - //! Set object resolve callback, it will be called once object with specified Id is loaded. - void SetResolveCallback(CBaseObject* fromObject, REFGUID objectId, ResolveObjRefFunctor2 func, uint32 userData); //! Resolve all object ids and call callbacks on resolved objects. void ResolveObjects(); // Save object to archive. void SaveObject(CBaseObject* pObject); - //! Load multiple objects from archive. - void LoadObjects(XmlNodeRef& rootObjectsNode); - //! Load one object from archive. CBaseObject* LoadObject(const XmlNodeRef& objNode, CBaseObject* pPrevObject = nullptr); - ////////////////////////////////////////////////////////////////////////// - int GetLoadedObjectsCount() { return static_cast(m_loadedObjects.size()); } - CBaseObject* GetLoadedObject(int nIndex) const { return m_loadedObjects[nIndex].pObject; } - //! If true new loaded objects will be assigned new GUIDs. void MakeNewIds(bool bEnable); //! Remap object ids. void RemapID(REFGUID oldId, REFGUID newId); - //! Report error during loading. - void ReportError(CErrorRecord& err); - //! Assigner different error report class. - void SetErrorReport(CErrorReport* errReport); - //! Display collected error reports. - void ShowErrors(); - - void EnableProgressBar(bool bEnable) { m_bProgressBarEnabled = bEnable; }; - - CPakFile* GetGeometryPak(const char* sFilename); - CBaseObject* GetCurrentObject(); - - void AddSequenceIdMapping(uint32 oldId, uint32 newId); - uint32 RemapSequenceId(uint32 id) const; - bool IsAmongPendingIds(uint32 id) const; - - void SetShouldResetInternalMembers(bool reset); - bool ShouldResetInternalMembers() const { return m_nFlags & eObjectLoader_ResetInternalMembers; } - private: struct SLoadedObjectInfo { @@ -110,22 +69,19 @@ private: bool operator <(const SLoadedObjectInfo& oi) const { return nSortOrder < oi.nSortOrder; } }; - IObjectManager* m_objectManager; struct Callback { ResolveObjRefFunctor1 func1; - ResolveObjRefFunctor2 func2; - uint32 userData; _smart_ptr fromObject; - Callback() { func1 = 0; func2 = 0; userData = 0; }; + Callback() { func1 = 0; } }; typedef std::multimap Callbacks; AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING Callbacks m_resolveCallbacks; // Set of all saved objects to this archive. - typedef std::set<_smart_ptr > ObjectsSet; + typedef AZStd::set<_smart_ptr > ObjectsSet; ObjectsSet m_savedObjects; //typedef std::multimap > OrderedObjects; @@ -138,20 +94,11 @@ private: enum EObjectLoaderFlags { eObjectLoader_MakeNewIDs = 0x0001, // If true new loaded objects will be assigned new GUIDs. - eObjectLoader_ResetInternalMembers = 0x0004, // In case we are deserializing and we would like to wipe all previous state }; int m_nFlags; IErrorReport* m_pCurrentErrorReport; - CPakFile* m_pGeometryPak; - CBaseObject* m_pCurrentObject; bool m_bNeedResolveObjects; - bool m_bProgressBarEnabled; - // This table is used when there is any collision of ids while importing TrackView sequences. - std::map m_sequenceIdRemap; - std::vector m_pendingIds; AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING }; - -#endif // CRYINCLUDE_EDITOR_OBJECTS_OBJECTLOADER_H diff --git a/Code/Editor/Objects/ObjectManager.cpp b/Code/Editor/Objects/ObjectManager.cpp index 265d828481..1dea6ece7f 100644 --- a/Code/Editor/Objects/ObjectManager.cpp +++ b/Code/Editor/Objects/ObjectManager.cpp @@ -50,7 +50,7 @@ public: GUID guid; public: - virtual ~CXMLObjectClassDesc() = default; + virtual ~CXMLObjectClassDesc() = default; REFGUID ClassID() override { return guid; @@ -81,60 +81,27 @@ CObjectManager* g_pObjectManager = nullptr; ////////////////////////////////////////////////////////////////////////// CObjectManager::CObjectManager() - : m_lastHideMask(0) - , m_maxObjectViewDistRatio(0.00001f) - , m_currSelection(&m_defaultSelection) - , m_nLastSelCount(0) + : m_currSelection(&m_defaultSelection) , m_bSelectionChanged(false) - , m_selectCallback(nullptr) - , m_currEditObject(nullptr) - , m_bSingleSelection(false) - , m_createGameObjects(true) - , m_bGenUniqObjectNames(true) , m_gizmoManager(new CGizmoManager()) - , m_pLoadProgress(nullptr) - , m_loadedObjects(0) - , m_totalObjectsToLoad(0) , m_bExiting(false) , m_isUpdateVisibilityList(false) , m_currentHideCount(CBaseObject::s_invalidHiddenID) - , m_bInReloading(false) - , m_bSkipObjectUpdate(false) - , m_bLevelExporting(false) { g_pObjectManager = this; - RegisterObjectClasses(); - m_objectsByName.reserve(1024); - LoadRegistry(); } ////////////////////////////////////////////////////////////////////////// CObjectManager::~CObjectManager() { m_bExiting = true; - SaveRegistry(); DeleteAllObjects(); delete m_gizmoManager; } -////////////////////////////////////////////////////////////////////////// -void CObjectManager::RegisterObjectClasses() -{ - LoadRegistry(); -} - -////////////////////////////////////////////////////////////////////////// -void CObjectManager::SaveRegistry() -{ -} - -void CObjectManager::LoadRegistry() -{ -} - ////////////////////////////////////////////////////////////////////////// CBaseObject* CObjectManager::NewObject(CObjectClassDesc* cls, CBaseObject* prev, const QString& file, const char* newObjectName) { @@ -186,17 +153,6 @@ CBaseObject* CObjectManager::NewObject(CObjectClassDesc* cls, CBaseObject* prev, if (obj->GetType() != OBJTYPE_AZENTITY) { GetIEditor()->RecordUndo(new CUndoBaseObjectNew(obj)); - - // check for script entities - const char* scriptClassName = ""; - CEntityObject* entityObj = qobject_cast(obj); - QByteArray entityClass; // Leave it outside of the if. Otherwise buffer is deleted. - if (entityObj) - { - entityClass = entityObj->GetEntityClass().toUtf8(); - scriptClassName = entityClass.data(); - } - } } @@ -318,12 +274,6 @@ CBaseObject* CObjectManager::NewObject(CObjectArchive& ar, CBaseObject* pUndoObj } } - m_loadedObjects++; - if (m_pLoadProgress && m_totalObjectsToLoad > 0) - { - m_pLoadProgress->Step((m_loadedObjects * 100) / m_totalObjectsToLoad); - } - return pObject; } @@ -351,10 +301,6 @@ CBaseObject* CObjectManager::NewObject(const QString& typeName, CBaseObject* pre void CObjectManager::DeleteObject(CBaseObject* obj) { AZ_PROFILE_FUNCTION(Editor); - if (m_currEditObject == obj) - { - EndEditParams(); - } if (!obj) { @@ -367,7 +313,6 @@ void CObjectManager::DeleteObject(CBaseObject* obj) return; } - NotifyObjectListeners(obj, CBaseObject::ON_PREDELETE); obj->NotifyListeners(CBaseObject::ON_PREDELETE); // Must be after object DetachAll to support restoring Parent/Child relations. @@ -377,7 +322,7 @@ void CObjectManager::DeleteObject(CBaseObject* obj) // Store undo for all child objects. for (int i = 0; i < obj->GetChildCount(); i++) { - obj->GetChild(i)->StoreUndo("DeleteParent"); + obj->GetChild(i)->StoreUndo(); } CUndo::Record(new CUndoBaseObjectDelete(obj)); } @@ -388,8 +333,6 @@ void CObjectManager::DeleteObject(CBaseObject* obj) obj->Done(); - NotifyObjectListeners(obj, CBaseObject::ON_DELETE); - RemoveObject(obj); } @@ -462,22 +405,10 @@ void CObjectManager::DeleteAllObjects() { AZ_PROFILE_FUNCTION(Editor); - EndEditParams(); - ClearSelection(); - int i; InvalidateVisibleList(); - // Delete all selection groups. - std::vector sel; - stl::map_to_vector(m_selections, sel); - for (i = 0; i < sel.size(); i++) - { - delete sel[i]; - } - m_selections.clear(); - TBaseObjects objectsHolder; GetAllObjects(objectsHolder); @@ -485,7 +416,7 @@ void CObjectManager::DeleteAllObjects() m_objects.clear(); m_objectsByName.clear(); - for (i = 0; i < objectsHolder.size(); i++) + for (int i = 0; i < objectsHolder.size(); i++) { objectsHolder[i]->Done(); } @@ -499,17 +430,6 @@ void CObjectManager::DeleteAllObjects() m_animatedAttachedEntities.clear(); } -CBaseObject* CObjectManager::CloneObject(CBaseObject* obj) -{ - AZ_PROFILE_FUNCTION(Editor); - assert(obj); - //CRuntimeClass *cls = obj->GetRuntimeClass(); - //CBaseObject *clone = (CBaseObject*)cls->CreateObject(); - //clone->CloneCopy( obj ); - CBaseObject* clone = NewObject(obj->GetClassDesc(), obj); - return clone; -} - ////////////////////////////////////////////////////////////////////////// CBaseObject* CObjectManager::FindObject(REFGUID guid) const { @@ -620,7 +540,6 @@ bool CObjectManager::AddObject(CBaseObject* obj) RegisterObjectName(obj->GetName()); InvalidateVisibleList(); - NotifyObjectListeners(obj, CBaseObject::ON_ADD); return true; } @@ -641,12 +560,6 @@ void CObjectManager::RemoveObject(CBaseObject* obj) // Remove this object from selection groups. m_currSelection->RemoveObject(obj); - std::vector sel; - stl::map_to_vector(m_selections, sel); - for (int i = 0; i < sel.size(); i++) - { - sel[i]->RemoveObject(obj); - } m_objectsByName.erase(AZ::Crc32(obj->GetName().toUtf8().data(), obj->GetName().toUtf8().count(), true)); @@ -674,54 +587,7 @@ void CObjectManager::ChangeObjectId(REFGUID oldGuid, REFGUID newGuid) CBaseObjectPtr pRemappedObject = (*it).second; pRemappedObject->SetId(newGuid); m_objects.erase(it); - m_objects.insert(std::make_pair(newGuid, pRemappedObject)); - } -} - -////////////////////////////////////////////////////////////////////////// -void CObjectManager::ShowDuplicationMsgWarning(CBaseObject* obj, const QString& newName, bool bShowMsgBox) const -{ - CBaseObject* pExisting = FindObject(newName); - if (pExisting) - { - QString sRenameWarning = QObject::tr("%1 \"%2\" was NOT renamed to \"%3\" because %4 with the same name already exists.") - .arg(obj->GetClassDesc()->ClassName()) - .arg(obj->GetName()) - .arg(newName) - .arg(pExisting->GetClassDesc()->ClassName() - ); - - // If id is taken. - CryWarning(VALIDATOR_MODULE_EDITOR, VALIDATOR_WARNING, "%s", sRenameWarning.toUtf8().data()); - - if (bShowMsgBox) - { - QMessageBox::critical(QApplication::activeWindow(), QString(), sRenameWarning); - } - } -} - -////////////////////////////////////////////////////////////////////////// -void CObjectManager::ChangeObjectName(CBaseObject* obj, const QString& newName) -{ - assert(obj); - - if (newName != obj->GetName()) - { - if (IsDuplicateObjectName(newName)) - { - return; - } - - // Remove previous name from map - const AZ::Crc32 oldNameCrc(obj->GetName().toUtf8().data(), obj->GetName().count(), true); - m_objectsByName.erase(oldNameCrc); - - obj->SetName(newName); - - // Add new name to map - const AZ::Crc32 nameCrc(newName.toUtf8().data(), newName.count(), true); - m_objectsByName[nameCrc] = obj; + m_objects.insert(AZStd::make_pair(newGuid, pRemappedObject)); } } @@ -742,28 +608,9 @@ void CObjectManager::GetObjects(CBaseObjectsArray& objects) const } } -void CObjectManager::GetObjects(CBaseObjectsArray& objects, BaseObjectFilterFunctor const& filter) const -{ - objects.clear(); - objects.reserve(m_objects.size()); - for (Objects::const_iterator it = m_objects.begin(); it != m_objects.end(); ++it) - { - assert(it->second); - if (filter.first(*it->second, filter.second)) - { - objects.push_back(it->second); - } - } -} - ////////////////////////////////////////////////////////////////////////// void CObjectManager::SendEvent(ObjectEvent event) { - if (event == EVENT_RELOAD_ENTITY) - { - m_bInReloading = true; - } - for (Objects::iterator it = m_objects.begin(); it != m_objects.end(); ++it) { CBaseObject* obj = it->second; @@ -772,7 +619,6 @@ void CObjectManager::SendEvent(ObjectEvent event) if (event == EVENT_RELOAD_ENTITY) { - m_bInReloading = false; GetIEditor()->Notify(eNotify_OnReloadTrackView); } } @@ -792,93 +638,6 @@ void CObjectManager::SendEvent(ObjectEvent event, const AABB& bounds) } } -////////////////////////////////////////////////////////////////////////// -void CObjectManager::Update() -{ - if (m_bSkipObjectUpdate) - { - return; - } - - QWidget* prevActiveWindow = QApplication::activeWindow(); - - // Restore focus if it changed. - if (prevActiveWindow && QApplication::activeWindow() != prevActiveWindow) - { - prevActiveWindow->setFocus(); - } -} - -////////////////////////////////////////////////////////////////////////// -void CObjectManager::HideObject(CBaseObject* obj, bool hide) -{ - assert(obj != 0); - if (hide) - { - obj->SetHidden(hide, ++m_currentHideCount); - } - else - { - obj->SetHidden(false); - } - InvalidateVisibleList(); -} - -void CObjectManager::ShowLastHiddenObject() -{ - uint64 mostRecentID = CBaseObject::s_invalidHiddenID; - CBaseObject* mostRecentObject = nullptr; - for (auto it : m_objects) - { - CBaseObject* obj = it.second; - - uint64 hiddenID = obj->GetHideOrder(); - - if (hiddenID > mostRecentID) - { - mostRecentID = hiddenID; - mostRecentObject = obj; - } - } - - if (mostRecentObject != nullptr) - { - mostRecentObject->SetHidden(false); - } -} - -////////////////////////////////////////////////////////////////////////// -void CObjectManager::UnhideAll() -{ - for (Objects::iterator it = m_objects.begin(); it != m_objects.end(); ++it) - { - CBaseObject* obj = it->second; - obj->SetHidden(false); - } - - InvalidateVisibleList(); -} - -////////////////////////////////////////////////////////////////////////// -void CObjectManager::FreezeObject(CBaseObject* obj, bool freeze) -{ - assert(obj != 0); - // Remove object from main object set and put it to hidden set. - obj->SetFrozen(freeze); - InvalidateVisibleList(); -} - -////////////////////////////////////////////////////////////////////////// -void CObjectManager::UnfreezeAll() -{ - for (Objects::iterator it = m_objects.begin(); it != m_objects.end(); ++it) - { - CBaseObject* obj = it->second; - obj->SetFrozen(false); - } - InvalidateVisibleList(); -} - ////////////////////////////////////////////////////////////////////////// bool CObjectManager::SelectObject(CBaseObject* obj, bool bUseMask) { @@ -894,14 +653,6 @@ bool CObjectManager::SelectObject(CBaseObject* obj, bool bUseMask) return false; } - if (m_selectCallback) - { - if (!m_selectCallback->OnSelectObject(obj)) - { - return true; - } - } - m_currSelection->AddObject(obj); // while in ComponentMode we never explicitly change selection (the entity will always be selected). @@ -921,14 +672,6 @@ bool CObjectManager::SelectObject(CBaseObject* obj, bool bUseMask) return true; } -void CObjectManager::SelectEntities(std::set& s) -{ - for (std::set::iterator it = s.begin(), end = s.end(); it != end; ++it) - { - SelectObject(*it); - } -} - void CObjectManager::UnselectObject(CBaseObject* obj) { // while in ComponentMode we never explicitly change selection (the entity will always be selected). @@ -947,139 +690,6 @@ void CObjectManager::UnselectObject(CBaseObject* obj) m_currSelection->RemoveObject(obj); } -CSelectionGroup* CObjectManager::GetSelection(const QString& name) const -{ - CSelectionGroup* selection = stl::find_in_map(m_selections, name, (CSelectionGroup*)nullptr); - return selection; -} - -void CObjectManager::GetNameSelectionStrings(QStringList& names) -{ - for (TNameSelectionMap::iterator it = m_selections.begin(); it != m_selections.end(); ++it) - { - names.push_back(it->first); - } -} - -void CObjectManager::NameSelection(const QString& name) -{ - if (m_currSelection->IsEmpty()) - { - return; - } - - CSelectionGroup* selection = stl::find_in_map(m_selections, name, (CSelectionGroup*)nullptr); - if (selection) - { - assert(selection != 0); - // Check if trying to rename itself to the same name. - if (selection == m_currSelection) - { - return; - } - m_selections.erase(name); - delete selection; - } - selection = new CSelectionGroup; - selection->Copy(*m_currSelection); - selection->SetName(name); - m_selections[name] = selection; - m_currSelection = selection; - m_defaultSelection.RemoveAll(); -} - -void CObjectManager::SerializeNameSelection(XmlNodeRef& rootNode, bool bLoading) -{ - if (!rootNode) - { - return; - } - - _smart_ptr tmpGroup(nullptr); - - QString selRootStr("NameSelection"); - QString selNodeStr("NameSelectionNode"); - QString selNodeNameStr("name"); - QString idStr("id"); - QString objAttrStr("obj"); - - XmlNodeRef startNode = rootNode->findChild(selRootStr.toUtf8().data()); - - if (bLoading) - { - m_selections.erase(m_selections.begin(), m_selections.end()); - - if (startNode) - { - for (int selNodeNo = 0; selNodeNo < startNode->getChildCount(); ++selNodeNo) - { - XmlNodeRef selNode = startNode->getChild(selNodeNo); - tmpGroup = new CSelectionGroup; - - for (int objIDNodeNo = 0; objIDNodeNo < selNode->getChildCount(); ++objIDNodeNo) - { - GUID curID = GUID_NULL; - XmlNodeRef idNode = selNode->getChild(objIDNodeNo); - if (!idNode->getAttr(idStr.toUtf8().data(), curID)) - { - continue; - } - - if (curID != GUID_NULL) - { - if (GetIEditor()->GetObjectManager()->FindObject(curID)) - { - tmpGroup->AddObject(GetIEditor()->GetObjectManager()->FindObject(curID)); - } - } - } - - if (tmpGroup->GetCount() > 0) - { - QString nameStr; - if (!selNode->getAttr(selNodeNameStr.toUtf8().data(), nameStr)) - { - continue; - } - tmpGroup->SetName(nameStr); - m_selections[nameStr] = tmpGroup; - } - } - } - } - else - { - startNode = rootNode->newChild(selRootStr.toUtf8().data()); - CSelectionGroup* objSelection = nullptr; - - for (TNameSelectionMap::iterator it = m_selections.begin(); it != m_selections.end(); ++it) - { - XmlNodeRef selectionNameNode = startNode->newChild(selNodeStr.toUtf8().data()); - selectionNameNode->setAttr(selNodeNameStr.toUtf8().data(), it->first.toUtf8().data()); - objSelection = it->second; - - if (!objSelection) - { - continue; - } - - if (objSelection->GetCount() == 0) - { - continue; - } - - for (int i = 0; i < objSelection->GetCount(); ++i) - { - if (objSelection->GetObject(i)) - { - XmlNodeRef objNode = selectionNameNode->newChild(objAttrStr.toUtf8().data()); - objNode->setAttr(idStr.toUtf8().data(), GuidUtil::ToString(objSelection->GetObject(i)->GetId())); - } - } - } - } -} - ////////////////////////////////////////////////////////////////////////// int CObjectManager::ClearSelection() { @@ -1133,63 +743,6 @@ int CObjectManager::ClearSelection() return numSel; } -////////////////////////////////////////////////////////////////////////// -int CObjectManager::InvertSelection() -{ - AZ_PROFILE_FUNCTION(Editor); - - int selCount = 0; - // iterate all objects. - for (Objects::const_iterator it = m_objects.begin(); it != m_objects.end(); ++it) - { - CBaseObject* pObj = it->second; - if (pObj->IsSelected()) - { - UnselectObject(pObj); - } - else - { - if (SelectObject(pObj)) - { - selCount++; - } - } - } - return selCount; -} - -void CObjectManager::SetSelection(const QString& name) -{ - AZ_PROFILE_FUNCTION(Editor); - CSelectionGroup* selection = stl::find_in_map(m_selections, name, (CSelectionGroup*)nullptr); - if (selection) - { - UnselectCurrent(); - assert(selection != 0); - m_currSelection = selection; - SelectCurrent(); - } -} - -void CObjectManager::RemoveSelection(const QString& name) -{ - AZ_PROFILE_FUNCTION(Editor); - - QString selName = name; - CSelectionGroup* selection = stl::find_in_map(m_selections, name, (CSelectionGroup*)nullptr); - if (selection) - { - if (selection == m_currSelection) - { - UnselectCurrent(); - m_currSelection = &m_defaultSelection; - m_defaultSelection.RemoveAll(); - } - delete selection; - m_selections.erase(selName); - } -} - void CObjectManager::SelectCurrent() { AZ_PROFILE_FUNCTION(Editor); @@ -1261,157 +814,7 @@ void CObjectManager::Display(DisplayContext& dc) } } -void CObjectManager::ForceUpdateVisibleObjectCache([[maybe_unused]] DisplayContext& dc) -{ - AZ_Assert(false, "CObjectManager::ForceUpdateVisibleObjectCache is legacy/deprecated and should not be used."); -} - -void CObjectManager::FindDisplayableObjects([[maybe_unused]] DisplayContext& dc, [[maybe_unused]] bool bDisplay) -{ - AZ_Assert(false, "CObjectManager::FindDisplayableObjects is legacy/deprecated and should not be used."); -} - -void CObjectManager::BeginEditParams(CBaseObject* obj, int flags) -{ - assert(obj != 0); - if (obj == m_currEditObject) - { - return; - } - - if (GetSelection()->GetCount() > 1) - { - return; - } - - QWidget* prevActiveWindow = QApplication::activeWindow(); - - if (m_currEditObject) - { - //if (obj->GetClassDesc() != m_currEditObject->GetClassDesc()) - if (!obj->IsSameClass(m_currEditObject)) - { - EndEditParams(flags); - } - } - - m_currEditObject = obj; - - if (flags & OBJECT_CREATE) - { - // Unselect all other objects. - ClearSelection(); - // Select this object. - SelectObject(obj, false); - } - - m_bSingleSelection = true; - - // Restore focus if it changed. - // OBJECT_EDIT is used by the EntityOutliner when items are selected. Using it here to prevent shifting focus to the EntityInspector on select. - if (!(flags & OBJECT_EDIT) && prevActiveWindow && QApplication::activeWindow() != prevActiveWindow) - { - prevActiveWindow->setFocus(); - } -} - -void CObjectManager::EndEditParams([[maybe_unused]] int flags) -{ - m_bSingleSelection = false; - m_currEditObject = nullptr; - //m_bSelectionChanged = false; // don't need to clear for ungroup -} - -//! Select objects within specified distance from given position. -int CObjectManager::SelectObjects(const AABB& box, bool bUnselect) -{ - AZ_PROFILE_FUNCTION(Editor); - int numSel = 0; - - AABB objBounds; - for (Objects::iterator it = m_objects.begin(); it != m_objects.end(); ++it) - { - CBaseObject* obj = it->second; - - if (obj->IsHidden()) - { - continue; - } - - obj->GetBoundBox(objBounds); - if (box.IsIntersectBox(objBounds)) - { - numSel++; - if (!bUnselect) - { - SelectObject(obj); - } - else - { - UnselectObject(obj); - } - } - } - return numSel; -} - ////////////////////////////////////////////////////////////////////////// -int CObjectManager::MoveObjects(const AABB& box, const Vec3& offset, ImageRotationDegrees rotation, [[maybe_unused]] bool bIsCopy) -{ - AABB objBounds; - - Vec3 src = (box.min + box.max) / 2; - Vec3 dst = src + offset; - float alpha = 0.0f; - switch (rotation) - { - case ImageRotationDegrees::Rotate90: - alpha = gf_halfPI; - break; - case ImageRotationDegrees::Rotate180: - alpha = gf_PI; - break; - case ImageRotationDegrees::Rotate270: - alpha = gf_PI + gf_halfPI; - break; - default: - break; - } - - float cosa = cos(alpha); - float sina = sin(alpha); - - for (Objects::iterator it = m_objects.begin(); it != m_objects.end(); ++it) - { - CBaseObject* obj = it->second; - - if (obj->GetParent()) - { - continue; - } - - obj->GetBoundBox(objBounds); - if (box.IsIntersectBox(objBounds)) - { - if (rotation == ImageRotationDegrees::Rotate0) - { - obj->SetPos(obj->GetPos() - src + dst); - } - else - { - Vec3 pos = obj->GetPos() - src; - Vec3 newPos(pos); - newPos.x = cosa * pos.x - sina * pos.y; - newPos.y = sina * pos.x + cosa * pos.y; - obj->SetPos(newPos + dst); - Quat q; - obj->SetRotation(q.CreateRotationZ(alpha) * obj->GetRotation()); - } - } - } - return 0; -} - bool CObjectManager::IsObjectDeletionAllowed(CBaseObject* pObject) { if (!pObject) @@ -1442,99 +845,12 @@ void CObjectManager::DeleteSelection() objects.AddObject(m_currSelection->GetObject(i)); } - RemoveSelection(m_currSelection->GetName()); m_currSelection = &m_defaultSelection; m_defaultSelection.RemoveAll(); DeleteSelection(&objects); } -////////////////////////////////////////////////////////////////////////// -bool CObjectManager::HitTestObject(CBaseObject* obj, HitContext& hc) -{ - AZ_PROFILE_FUNCTION(Editor); - - if (obj->IsFrozen()) - { - return false; - } - - if (obj->IsHidden()) - { - return false; - } - - // This object is rejected by deep selection. - if (obj->CheckFlags(OBJFLAG_NO_HITTEST)) - { - return false; - } - - ObjectType objType = obj->GetType(); - - // Check if this object type is masked for selection. - if (!(objType & gSettings.objectSelectMask)) - { - return false; - } - - const bool bSelectionHelperHit = obj->HitHelperTest(hc); - - if (hc.bUseSelectionHelpers && !bSelectionHelperHit) - { - return false; - } - - if (!bSelectionHelperHit) - { - // Fast checking. - if (hc.camera && !obj->IsInCameraView(*hc.camera)) - { - return false; - } - else if (hc.bounds && !obj->IntersectRectBounds(*hc.bounds)) - { - return false; - } - - // Do 2D space testing. - if (hc.nSubObjFlags == 0) - { - Ray ray(hc.raySrc, hc.rayDir); - if (!obj->IntersectRayBounds(ray)) - { - return false; - } - } - else if (!obj->HitTestRect(hc)) - { - return false; - } - } - - return (bSelectionHelperHit || obj->HitTest(hc)); -} - -////////////////////////////////////////////////////////////////////////// -bool CObjectManager::HitTest([[maybe_unused]] HitContext& hitInfo) -{ - AZ_Assert(false, "CObjectManager::HitTest is legacy/deprecated and should not be used."); - return false; -} - -void CObjectManager::FindObjectsInRect( - [[maybe_unused]] CViewport* view, [[maybe_unused]] const QRect& rect, [[maybe_unused]] std::vector& guids) -{ - AZ_Assert(false, "CObjectManager::FindObjectsInRect is legacy/deprecated and should not be used."); -} - -////////////////////////////////////////////////////////////////////////// -void CObjectManager::SelectObjectsInRect( - [[maybe_unused]] CViewport* view, [[maybe_unused]] const QRect& rect, [[maybe_unused]] bool bSelect) -{ - AZ_Assert(false, "CObjectManager::SelectObjectsInRect is legacy/deprecated and should not be used."); -} - ////////////////////////////////////////////////////////////////////////// uint16 FindPossibleObjectNameNumber(std::set& numberSet) { @@ -1585,50 +901,9 @@ void CObjectManager::RegisterObjectName(const QString& name) } } -////////////////////////////////////////////////////////////////////////// -void CObjectManager::UpdateRegisterObjectName(const QString& name) -{ - // Remove all numbers from the end of typename. - QString typeName = name; - int nameLen = typeName.length(); - int len = nameLen; - - while (len > 0 && typeName[len - 1].isDigit()) - { - len--; - } - - typeName = typeName.left(len); - - uint16 num = 1; - if (len < nameLen) - { - num = (uint16)atoi((const char*)name.toUtf8().data() + len) + 0; - } - - NameNumbersMap::iterator it = m_nameNumbersMap.find(typeName); - - if (it != m_nameNumbersMap.end()) - { - if (it->second.end() != it->second.find(num)) - { - it->second.erase(num); - if (it->second.empty()) - { - m_nameNumbersMap.erase(it); - } - } - } -} - ////////////////////////////////////////////////////////////////////////// QString CObjectManager::GenerateUniqueObjectName(const QString& theTypeName) { - if (!m_bGenUniqObjectNames) - { - return theTypeName; - } - QString typeName = theTypeName; const int subIndex = theTypeName.indexOf("::"); if (subIndex != -1 && subIndex > typeName.length() - 2) @@ -1663,14 +938,6 @@ QString CObjectManager::GenerateUniqueObjectName(const QString& theTypeName) return str; } -////////////////////////////////////////////////////////////////////////// -bool CObjectManager::EnableUniqObjectNames(bool bEnable) -{ - bool bPrev = m_bGenUniqObjectNames; - m_bGenUniqObjectNames = bEnable; - return bPrev; -} - ////////////////////////////////////////////////////////////////////////// CObjectClassDesc* CObjectManager::FindClass(const QString& className) { @@ -1682,64 +949,6 @@ CObjectClassDesc* CObjectManager::FindClass(const QString& className) return nullptr; } -////////////////////////////////////////////////////////////////////////// -void CObjectManager::GetClassCategories(QStringList& categories) -{ - std::vector classes; - CClassFactory::Instance()->GetClassesBySystemID(ESYSTEM_CLASS_OBJECT, classes); - std::set cset; - for (int i = 0; i < classes.size(); i++) - { - QString category = classes[i]->Category(); - if (!category.isEmpty()) - { - cset.insert(category); - } - } - categories.clear(); - categories.reserve(static_cast(cset.size())); - for (std::set::iterator cit = cset.begin(); cit != cset.end(); ++cit) - { - categories.push_back(*cit); - } -} - -void CObjectManager::GetClassCategoryToolClassNamePairs(std::vector< std::pair >& categoryToolClassNamePairs) -{ - std::vector classes; - CClassFactory::Instance()->GetClassesBySystemID(ESYSTEM_CLASS_OBJECT, classes); - std::set< std::pair > cset; - for (int i = 0; i < classes.size(); i++) - { - QString category = classes[i]->Category(); - QString toolClassName = ((CObjectClassDesc*)classes[i])->GetToolClassName(); - if (!category.isEmpty()) - { - cset.insert(std::pair(category, toolClassName)); - } - } - categoryToolClassNamePairs.clear(); - categoryToolClassNamePairs.reserve(cset.size()); - for (std::set< std::pair >::iterator cit = cset.begin(); cit != cset.end(); ++cit) - { - categoryToolClassNamePairs.push_back(*cit); - } -} - -void CObjectManager::GetClassTypes(const QString& category, QStringList& types) -{ - std::vector classes; - CClassFactory::Instance()->GetClassesBySystemID(ESYSTEM_CLASS_OBJECT, classes); - for (int i = 0; i < classes.size(); i++) - { - QString cat = classes[i]->Category(); - if (QString::compare(cat, category, Qt::CaseInsensitive) == 0 && classes[i]->IsEnabled()) - { - types.push_back(classes[i]->ClassName()); - } - } -} - ////////////////////////////////////////////////////////////////////////// void CObjectManager::RegisterClassTemplate(const XmlNodeRef& templ) { @@ -1804,181 +1013,6 @@ void CObjectManager::RegisterCVars() "Adjust the hit radius used for axis helpers, like the transform gizmo."); } -////////////////////////////////////////////////////////////////////////// -void CObjectManager::Serialize(XmlNodeRef& xmlNode, bool bLoading, int flags) -{ - if (!xmlNode) - { - return; - } - - if (bLoading) - { - m_loadedObjects = 0; - - if (flags == SERIALIZE_ONLY_NOTSHARED) - { - DeleteNotSharedObjects(); - } - else if (flags == SERIALIZE_ONLY_SHARED) - { - DeleteSharedObjects(); - } - else - { - DeleteAllObjects(); - } - - - XmlNodeRef root = xmlNode->findChild("Objects"); - - int totalObjects = 0; - - if (root) - { - root->getAttr("NumObjects", totalObjects); - } - - - StartObjectsLoading(totalObjects); - - CObjectArchive ar(this, xmlNode, true); - - // Loading. - if (root) - { - ar.node = root; - LoadObjects(ar, false); - } - EndObjectsLoading(); - } - else - { - // Saving. - XmlNodeRef root = xmlNode->newChild("Objects"); - - CObjectArchive ar(this, root, false); - - // Save all objects to XML. - for (Objects::iterator it = m_objects.begin(); it != m_objects.end(); ++it) - { - CBaseObject* obj = it->second; - - if (obj->CheckFlags(OBJFLAG_DONT_SAVE)) - { - continue; - } - - if ((flags == SERIALIZE_ONLY_SHARED) && !obj->CheckFlags(OBJFLAG_SHARED)) - { - continue; - } - else if ((flags == SERIALIZE_ONLY_NOTSHARED) && obj->CheckFlags(OBJFLAG_SHARED)) - { - continue; - } - - XmlNodeRef objNode = root->newChild("Object"); - ar.node = objNode; - obj->Serialize(ar); - } - } -} - -////////////////////////////////////////////////////////////////////////// -void CObjectManager::LoadObjects(CObjectArchive& objectArchive, bool bSelect) -{ - m_bLoadingObjects = true; - - XmlNodeRef objectsNode = objectArchive.node; - int numObjects = objectsNode->getChildCount(); - for (int i = 0; i < numObjects; i++) - { - objectArchive.node = objectsNode->getChild(i); - CBaseObject* obj = objectArchive.LoadObject(objectsNode->getChild(i)); - if (obj && bSelect) - { - SelectObject(obj); - } - } - EndObjectsLoading(); // End progress bar, here, Resolve objects have his own. - objectArchive.ResolveObjects(); - - InvalidateVisibleList(); - - m_bLoadingObjects = false; -} - -////////////////////////////////////////////////////////////////////////// -void CObjectManager::Export(const QString& levelPath, XmlNodeRef& rootNode, bool onlyShared) -{ - // Clear export files. - QFile::remove(QStringLiteral("%1TagPoints.ini").arg(levelPath)); - QFile::remove(QStringLiteral("%1Volumes.ini").arg(levelPath)); - - // Save all objects to XML. - for (Objects::iterator it = m_objects.begin(); it != m_objects.end(); ++it) - { - CBaseObject* obj = it->second; - // Export Only shared objects. - if ((obj->CheckFlags(OBJFLAG_SHARED) && onlyShared) || - (!obj->CheckFlags(OBJFLAG_SHARED) && !onlyShared)) - { - obj->Export(levelPath, rootNode); - } - } -} - -////////////////////////////////////////////////////////////////////////// -void CObjectManager::ExportEntities(XmlNodeRef& rootNode) -{ - // Save all objects to XML. - for (Objects::iterator it = m_objects.begin(); it != m_objects.end(); ++it) - { - CBaseObject* obj = it->second; - if (qobject_cast(obj)) - { - obj->Export("", rootNode); - } - } -} - -void CObjectManager::DeleteNotSharedObjects() -{ - TBaseObjects objects; - GetAllObjects(objects); - for (int i = 0; i < objects.size(); i++) - { - CBaseObject* obj = objects[i]; - if (!obj->CheckFlags(OBJFLAG_SHARED)) - { - DeleteObject(obj); - } - } -} - -void CObjectManager::DeleteSharedObjects() -{ - TBaseObjects objects; - GetAllObjects(objects); - for (int i = 0; i < objects.size(); i++) - { - CBaseObject* obj = objects[i]; - if (obj->CheckFlags(OBJFLAG_SHARED)) - { - DeleteObject(obj); - } - } -} - -////////////////////////////////////////////////////////////////////////// -IObjectSelectCallback* CObjectManager::SetSelectCallback(IObjectSelectCallback* callback) -{ - IObjectSelectCallback* prev = m_selectCallback; - m_selectCallback = callback; - return prev; -} - ////////////////////////////////////////////////////////////////////////// void CObjectManager::InvalidateVisibleList() { @@ -2020,27 +1054,6 @@ void CObjectManager::UpdateVisibilityList() m_isUpdateVisibilityList = false; } -////////////////////////////////////////////////////////////////////////// -bool CObjectManager::ConvertToType(CBaseObject* pObject, const QString& typeName) -{ - QString message = QString("Convert ") + pObject->GetName() + " to " + typeName; - CUndo undo(message.toUtf8().data()); - - CBaseObjectPtr pNewObject = GetIEditor()->NewObject(typeName.toUtf8().data()); - if (pNewObject) - { - if (pNewObject->ConvertFromObject(pObject)) - { - DeleteObject(pObject); - return true; - } - DeleteObject(pNewObject); - } - - Log((message + " is failed.").toUtf8().data()); - return false; -} - ////////////////////////////////////////////////////////////////////////// void CObjectManager::SetObjectSelected(CBaseObject* pObject, bool bSelect) { @@ -2069,69 +1082,6 @@ void CObjectManager::SetObjectSelected(CBaseObject* pObject, bool bSelect) m_gizmoManager->AddGizmo(new CAxisGizmo(pObject)); } } - - if (bSelect) - { - NotifyObjectListeners(pObject, CBaseObject::ON_SELECT); - } - else - { - NotifyObjectListeners(pObject, CBaseObject::ON_UNSELECT); - } -} - -////////////////////////////////////////////////////////////////////////// -void CObjectManager::HideTransformManipulators() -{ - m_gizmoManager->DeleteAllTransformManipulators(); -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -void CObjectManager::AddObjectEventListener(EventListener* listener) -{ - stl::push_back_unique(m_objectEventListeners, listener); -} - -////////////////////////////////////////////////////////////////////////// -void CObjectManager::RemoveObjectEventListener(EventListener* listener) -{ - stl::find_and_erase(m_objectEventListeners, listener); -} - -////////////////////////////////////////////////////////////////////////// -void CObjectManager::NotifyObjectListeners(CBaseObject* pObject, CBaseObject::EObjectListenerEvent event) -{ - std::list::iterator next; - for (std::list::iterator it = m_objectEventListeners.begin(); it != m_objectEventListeners.end(); it = next) - { - next = it; - ++next; - // Call listener callback. - (*it)->OnObjectEvent(pObject, event); - } -} - -////////////////////////////////////////////////////////////////////////// -void CObjectManager::StartObjectsLoading(int numObjects) -{ - if (m_pLoadProgress) - { - return; - } - m_pLoadProgress = new CWaitProgress("Loading Objects"); - m_totalObjectsToLoad = numObjects; - m_loadedObjects = 0; -} - -////////////////////////////////////////////////////////////////////////// -void CObjectManager::EndObjectsLoading() -{ - if (m_pLoadProgress) - { - delete m_pLoadProgress; - } - m_pLoadProgress = nullptr; } ////////////////////////////////////////////////////////////////////////// @@ -2179,130 +1129,6 @@ bool CObjectManager::IsLightClass(CBaseObject* pObject) return false; } -void CObjectManager::FindAndRenameProperty2(const char* property2Name, const QString& oldValue, const QString& newValue) -{ - CBaseObjectsArray objects; - GetObjects(objects); - - for (size_t i = 0, n = objects.size(); i < n; ++i) - { - CBaseObject* pObject = objects[i]; - if (qobject_cast(pObject)) - { - CEntityObject* pEntity = static_cast(pObject); - CVarBlock* pProperties2 = pEntity->GetProperties2(); - if (pProperties2) - { - IVariable* pVariable = pProperties2->FindVariable(property2Name); - if (pVariable) - { - QString sValue; - pVariable->Get(sValue); - if (sValue == oldValue) - { - pEntity->StoreUndo("Rename Property2"); - - pVariable->Set(newValue); - } - } - } - } - } -} - -void CObjectManager::FindAndRenameProperty2If(const char* property2Name, const QString& oldValue, const QString& newValue, const char* otherProperty2Name, const QString& otherValue) -{ - CBaseObjectsArray objects; - GetObjects(objects); - - for (size_t i = 0, n = objects.size(); i < n; ++i) - { - CBaseObject* pObject = objects[i]; - if (qobject_cast(pObject)) - { - CEntityObject* pEntity = static_cast(pObject); - CVarBlock* pProperties2 = pEntity->GetProperties2(); - if (pProperties2) - { - IVariable* pVariable = pProperties2->FindVariable(property2Name); - IVariable* pOtherVariable = pProperties2->FindVariable(otherProperty2Name); - if (pVariable && pOtherVariable) - { - QString sValue; - pVariable->Get(sValue); - - QString sOtherValue; - pOtherVariable->Get(sOtherValue); - - if ((sValue == oldValue) && (sOtherValue == otherValue)) - { - pEntity->StoreUndo("Rename Property2 If"); - - pVariable->Set(newValue); - } - } - } - } - } -} - -////////////////////////////////////////////////////////////////////////// -void CObjectManager::HitTestObjectAgainstRect(CBaseObject* pObj, CViewport* view, HitContext hc, std::vector& guids) -{ - if (!pObj->IsSelectable()) - { - return; - } - - AABB box; - - // Retrieve world space bound box. - pObj->GetBoundBox(box); - - // Check if object visible in viewport. - if (!view->IsBoundsVisible(box)) - { - return; - } - - if (pObj->HitTestRect(hc)) - { - stl::push_back_unique(guids, pObj->GetId()); - } -} - -////////////////////////////////////////////////////////////////////////// -void CObjectManager::SelectObjectInRect(CBaseObject* pObj, CViewport* view, HitContext hc, bool bSelect) -{ - if (!pObj->IsSelectable()) - { - return; - } - - AABB box; - - // Retrieve world space bound box. - pObj->GetBoundBox(box); - - // Check if object visible in viewport. - if (!view->IsBoundsVisible(box)) - { - return; - } - - if (pObj->HitTestRect(hc)) - { - if (bSelect) - { - SelectObject(pObj); - } - else - { - UnselectObject(pObj); - } - } -} - ////////////////////////////////////////////////////////////////////////// namespace { @@ -2382,93 +1208,6 @@ namespace } } - bool PyIsObjectHidden(const char* objName) - { - CBaseObject* pObject = GetIEditor()->GetObjectManager()->FindObject(objName); - if (!pObject) - { - throw std::logic_error((QString("\"") + objName + "\" is an invalid object name.").toUtf8().data()); - } - return pObject->IsHidden(); - } - - void PyHideAllObjects() - { - CBaseObjectsArray baseObjects; - GetIEditor()->GetObjectManager()->GetObjects(baseObjects); - - if (baseObjects.size() <= 0) - { - throw std::logic_error("Objects not found."); - } - - CUndo undo("Hide All Objects"); - for (int i = 0; i < baseObjects.size(); i++) - { - GetIEditor()->GetObjectManager()->HideObject(baseObjects[i], true); - } - } - - void PyUnHideAllObjects() - { - CUndo undo("Unhide All Objects"); - GetIEditor()->GetObjectManager()->UnhideAll(); - } - - void PyHideObject(const char* objName) - { - CUndo undo("Hide Object"); - - CBaseObject* pObject = GetIEditor()->GetObjectManager()->FindObject(objName); - if (pObject) - { - GetIEditor()->GetObjectManager()->HideObject(pObject, true); - } - } - - void PyUnhideObject(const char* objName) - { - CUndo undo("Unhide Object"); - - CBaseObject* pObject = GetIEditor()->GetObjectManager()->FindObject(objName); - if (pObject) - { - GetIEditor()->GetObjectManager()->HideObject(pObject, false); - } - } - - void PyFreezeObject(const char* objName) - { - CUndo undo("Freeze Object"); - - CBaseObject* pObject = GetIEditor()->GetObjectManager()->FindObject(objName); - if (pObject) - { - GetIEditor()->GetObjectManager()->FreezeObject(pObject, true); - } - } - - void PyUnfreezeObject(const char* objName) - { - CUndo undo("Unfreeze Object"); - - CBaseObject* pObject = GetIEditor()->GetObjectManager()->FindObject(objName); - if (pObject) - { - GetIEditor()->GetObjectManager()->FreezeObject(pObject, false); - } - } - - bool PyIsObjectFrozen(const char* objName) - { - CBaseObject* pObject = GetIEditor()->GetObjectManager()->FindObject(objName); - if (!pObject) - { - throw std::logic_error((QString("\"") + objName + "\" is an invalid object name.").toUtf8().data()); - } - return pObject->IsFrozen(); - } - void PyDeleteObject(const char* objName) { CUndo undo("Delete Object"); @@ -2656,16 +1395,6 @@ namespace AzToolsFramework addLegacyGeneral(behaviorContext->Method("get_selection_center", PyGetSelectionCenter, nullptr, "Returns the center point of the selection group.")); addLegacyGeneral(behaviorContext->Method("get_selection_aabb", PyGetSelectionAABB, nullptr, "Returns the aabb of the selection group.")); - addLegacyGeneral(behaviorContext->Method("hide_object", PyHideObject, nullptr, "Hides a specified object.")); - addLegacyGeneral(behaviorContext->Method("is_object_hidden", PyIsObjectHidden, nullptr, "Checks if object is hidden and returns a bool value.")); - addLegacyGeneral(behaviorContext->Method("unhide_object", PyUnhideObject, nullptr, "Unhides a specified object.")); - addLegacyGeneral(behaviorContext->Method("hide_all_objects", PyHideAllObjects, nullptr, "Hides all objects.")); - addLegacyGeneral(behaviorContext->Method("unhide_all_objects", PyUnHideAllObjects, nullptr, "Unhides all objects.")); - - addLegacyGeneral(behaviorContext->Method("freeze_object", PyFreezeObject, nullptr, "Freezes a specified object.")); - addLegacyGeneral(behaviorContext->Method("is_object_frozen", PyIsObjectFrozen, nullptr, "Checks if object is frozen and returns a bool value.")); - addLegacyGeneral(behaviorContext->Method("unfreeze_object", PyUnfreezeObject, nullptr, "Unfreezes a specified object.")); - addLegacyGeneral(behaviorContext->Method("delete_object", PyDeleteObject, nullptr, "Deletes a specified object.")); addLegacyGeneral(behaviorContext->Method("delete_selected", PyDeleteSelected, nullptr, "Deletes selected object(s).")); diff --git a/Code/Editor/Objects/ObjectManager.h b/Code/Editor/Objects/ObjectManager.h index 7389dfa6a1..fb16c2fec3 100644 --- a/Code/Editor/Objects/ObjectManager.h +++ b/Code/Editor/Objects/ObjectManager.h @@ -8,10 +8,6 @@ // Description : ObjectManager definition. - - -#ifndef CRYINCLUDE_EDITOR_OBJECTS_OBJECTMANAGER_H -#define CRYINCLUDE_EDITOR_OBJECTS_OBJECTMANAGER_H #pragma once #include "IObjectManager.h" @@ -42,12 +38,10 @@ public: CObjectManagerLevelIsExporting() { AZ::ObjectManagerEventBus::Broadcast(&AZ::ObjectManagerEventBus::Events::OnExportingStarting); - GetIEditor()->GetObjectManager()->SetExportingLevel(true); } ~CObjectManagerLevelIsExporting() { - GetIEditor()->GetObjectManager()->SetExportingLevel(false); AZ::ObjectManagerEventBus::Broadcast(&AZ::ObjectManagerEventBus::Events::OnExportingFinished); } }; @@ -66,20 +60,12 @@ public: CObjectManager(); ~CObjectManager(); - void RegisterObjectClasses(); - CBaseObject* NewObject(CObjectClassDesc* cls, CBaseObject* prev = 0, const QString& file = "", const char* newObjectName = nullptr) override; CBaseObject* NewObject(const QString& typeName, CBaseObject* prev = 0, const QString& file = "", const char* newEntityName = nullptr) override; void DeleteObject(CBaseObject* obj) override; void DeleteSelection(CSelectionGroup* pSelection) override; void DeleteAllObjects() override; - CBaseObject* CloneObject(CBaseObject* obj) override; - - void BeginEditParams(CBaseObject* obj, int flags) override; - void EndEditParams(int flags = 0) override; - // Hides all transform manipulators. - void HideTransformManipulators(); //! Get number of objects manager by ObjectManager (not contain sub objects of groups). int GetObjectCount() const override; @@ -88,30 +74,9 @@ public: //! @param layer if 0 get objects for all layers, or layer to get objects from. void GetObjects(CBaseObjectsArray& objects) const override; - //! Get array of objects that pass the filter. - //! @param filter The filter functor, return true if you want to get the certain obj, return false if want to skip it. - void GetObjects(CBaseObjectsArray& objects, BaseObjectFilterFunctor const& filter) const override; - - //! Update objects. - void Update(); - //! Display objects on display context. void Display(DisplayContext& dc) override; - //! Called when selecting without selection helpers - this is needed since - //! the visible object cache is normally not updated when not displaying helpers. - void ForceUpdateVisibleObjectCache(DisplayContext& dc) override; - - //! Check intersection with objects. - //! Find intersection with nearest to ray origin object hit by ray. - //! If distance tollerance is specified certain relaxation applied on collision test. - //! @return true if hit any object, and fills hitInfo structure. - bool HitTest(HitContext& hitInfo) override; - - //! Check intersection with an object. - //! @return true if hit, and fills hitInfo structure. - bool HitTestObject(CBaseObject* obj, HitContext& hc) override; - //! Send event to all objects. //! Will cause OnEvent handler to be called on all objects. void SendEvent(ObjectEvent event) override; @@ -134,76 +99,28 @@ public: //! Find objects which intersect with a given AABB. void FindObjectsInAABB(const AABB& aabb, std::vector& result) const override; - ////////////////////////////////////////////////////////////////////////// - // Operations on objects. - ////////////////////////////////////////////////////////////////////////// - //! Makes object visible or invisible. - void HideObject(CBaseObject* obj, bool hide) override; - //! Shows the last hidden object based on hidden ID - void ShowLastHiddenObject() override; - //! Freeze object, making it unselectable. - void FreezeObject(CBaseObject* obj, bool freeze) override; - //! Unhide all hidden objects. - void UnhideAll() override; - //! Unfreeze all frozen objects. - void UnfreezeAll() override; - ////////////////////////////////////////////////////////////////////////// // Object Selection. ////////////////////////////////////////////////////////////////////////// bool SelectObject(CBaseObject* obj, bool bUseMask = true) override; void UnselectObject(CBaseObject* obj) override; - //! Select objects within specified distance from given position. - //! Return number of selected objects. - int SelectObjects(const AABB& box, bool bUnselect = false) override; - - void SelectEntities(std::set& s) override; - - int MoveObjects(const AABB& box, const Vec3& offset, ImageRotationDegrees rotation, bool bIsCopy = false) override; - - //! Selects/Unselects all objects within 2d rectangle in given viewport. - void SelectObjectsInRect(CViewport* view, const QRect& rect, bool bSelect) override; - void FindObjectsInRect(CViewport* view, const QRect& rect, std::vector& guids) override; - //! Clear default selection set. //! @Return number of objects removed from selection. int ClearSelection() override; - //! Deselect all current selected objects and selects object that were unselected. - //! @Return number of selected objects. - int InvertSelection() override; - //! Get current selection. CSelectionGroup* GetSelection() const override { return m_currSelection; }; - //! Get named selection. - CSelectionGroup* GetSelection(const QString& name) const override; - // Get selection group names - void GetNameSelectionStrings(QStringList& names) override; - //! Change name of current selection group. - //! And store it in list. - void NameSelection(const QString& name) override; - //! Set one of name selections as current selection. - void SetSelection(const QString& name) override; - void RemoveSelection(const QString& name) override; bool IsObjectDeletionAllowed(CBaseObject* pObject); //! Delete all objects in selection group. void DeleteSelection() override; - uint32 ForceID() const override{return m_ForceID; } - void ForceID(uint32 FID) override{m_ForceID = FID; } - //! Generates uniq name base on type name of object. QString GenerateUniqueObjectName(const QString& typeName) override; //! Register object name in object manager, needed for generating uniq names. void RegisterObjectName(const QString& name) override; - //! Decrease name number and remove if it was last in object manager, needed for generating uniq names. - void UpdateRegisterObjectName(const QString& name); - //! Enable/Disable generating of unique object names (Enabled by default). - //! Return previous value. - bool EnableUniqObjectNames(bool bEnable) override; //! Register XML template of runtime class. void RegisterClassTemplate(const XmlNodeRef& templ); @@ -215,52 +132,10 @@ public: //! Find object class by name. CObjectClassDesc* FindClass(const QString& className) override; - void GetClassCategories(QStringList& categories) override; - void GetClassCategoryToolClassNamePairs(std::vector< std::pair >& categoryToolClassNamePairs) override; - void GetClassTypes(const QString& category, QStringList& types) override; - - //! Export objects to xml. - //! When onlyShared is true ony objects with shared flags exported, overwise only not shared object exported. - void Export(const QString& levelPath, XmlNodeRef& rootNode, bool onlyShared) override; - void ExportEntities(XmlNodeRef& rootNode) override; - - //! Serialize Objects in manager to specified XML Node. - //! @param flags Can be one of SerializeFlags. - void Serialize(XmlNodeRef& rootNode, bool bLoading, int flags = SERIALIZE_ALL) override; - - void SerializeNameSelection(XmlNodeRef& rootNode, bool bLoading) override; - - //! Load objects from object archive. - //! @param bSelect if set newly loaded object will be selected. - void LoadObjects(CObjectArchive& ar, bool bSelect) override; - - //! Delete from Object manager all objects without SHARED flag. - void DeleteNotSharedObjects(); - //! Delete from Object manager all objects with SHARED flag. - void DeleteSharedObjects(); bool AddObject(CBaseObject* obj); void RemoveObject(CBaseObject* obj); void ChangeObjectId(REFGUID oldId, REFGUID newId) override; - bool IsDuplicateObjectName(const QString& newName) const override - { - return FindObject(newName) ? true : false; - } - void ShowDuplicationMsgWarning(CBaseObject* obj, const QString& newName, bool bShowMsgBox) const override; - void ChangeObjectName(CBaseObject* obj, const QString& newName) override; - - //! Convert object of one type to object of another type. - //! Original object is deleted. - bool ConvertToType(CBaseObject* pObject, const QString& typeName) override; - - //! Set new selection callback. - //! @return previous selection callback. - IObjectSelectCallback* SetSelectCallback(IObjectSelectCallback* callback) override; - - // Enables/Disables creating of game objects. - void SetCreateGameObject(bool enable) override { m_createGameObjects = enable; }; - //! Return true if objects loaded from xml should immidiatly create game objects associated with them. - bool IsCreateGameObjects() const override { return m_createGameObjects; }; ////////////////////////////////////////////////////////////////////////// //! Get access to gizmo manager. @@ -270,33 +145,12 @@ public: //! Invalidate visibily settings of objects. void InvalidateVisibleList() override; - ////////////////////////////////////////////////////////////////////////// - // ObjectManager notification Callbacks. - ////////////////////////////////////////////////////////////////////////// - void AddObjectEventListener(EventListener* listener) override; - void RemoveObjectEventListener(EventListener* listener) override; - - ////////////////////////////////////////////////////////////////////////// - // Used to indicate starting and ending of objects loading. - ////////////////////////////////////////////////////////////////////////// - void StartObjectsLoading(int numObjects) override; - void EndObjectsLoading() override; - ////////////////////////////////////////////////////////////////////////// // Gathers all resources used by all objects. void GatherUsedResources(CUsedResources& resources) override; bool IsLightClass(CBaseObject* pObject) override; - virtual void FindAndRenameProperty2(const char* property2Name, const QString& oldValue, const QString& newValue) override; - virtual void FindAndRenameProperty2If(const char* property2Name, const QString& oldValue, const QString& newValue, const char* otherProperty2Name, const QString& otherValue) override; - - bool IsReloading() const override { return m_bInReloading; } - void SetSkipUpdate(bool bSkipUpdate) override { m_bSkipObjectUpdate = bSkipUpdate; } - - void SetExportingLevel(bool bExporting) override { m_bLevelExporting = bExporting; } - bool IsExportingLevelInprogress() const override { return m_bLevelExporting; } - int GetAxisHelperHitRadius() const override { return m_axisHelperHitRadius; } private: @@ -317,29 +171,12 @@ private: void SelectCurrent(); void SetObjectSelected(CBaseObject* pObject, bool bSelect); - // Recursive functions potentially taking into child objects into account - void SelectObjectInRect(CBaseObject* pObj, CViewport* view, HitContext hc, bool bSelect); - void HitTestObjectAgainstRect(CBaseObject* pObj, CViewport* view, HitContext hc, std::vector& guids); - - void SaveRegistry(); - void LoadRegistry(); - - void NotifyObjectListeners(CBaseObject* pObject, CBaseObject::EObjectListenerEvent event); - - void FindDisplayableObjects(DisplayContext& dc, bool bDisplay); - private: - typedef std::map Objects; + typedef AZStd::map Objects; Objects m_objects; - typedef std::unordered_map ObjectsByNameCrc; + typedef AZStd::unordered_map ObjectsByNameCrc; ObjectsByNameCrc m_objectsByName; - typedef std::map TNameSelectionMap; - TNameSelectionMap m_selections; - - //! Used for forcing IDs of "GetEditorObjectID" of PreFabs, as they used to have random IDs on each load - uint32 m_ForceID; - //! Array of currently visible objects. TBaseObjects m_visibleObjects; @@ -349,16 +186,11 @@ private: unsigned int m_lastComputedVisibility = 0; // when the object manager itself last updated visibility (since it also has a cache) int m_lastHideMask = 0; - float m_maxObjectViewDistRatio; - ////////////////////////////////////////////////////////////////////////// // Selection. //! Current selection group. CSelectionGroup* m_currSelection; - int m_nLastSelCount; bool m_bSelectionChanged; - IObjectSelectCallback* m_selectCallback; - bool m_bLoadingObjects; // True while performing a select or deselect operation on more than one object. // Prevents individual undo/redo commands for every object, allowing bulk undo/redo @@ -367,20 +199,9 @@ private: //! Default selection. CSelectionGroup m_defaultSelection; - CBaseObjectPtr m_currEditObject; - bool m_bSingleSelection; - - bool m_createGameObjects; - bool m_bGenUniqObjectNames; - // Object manager also handles Gizmo manager. CGizmoManager* m_gizmoManager; - ////////////////////////////////////////////////////////////////////////// - // Loading progress. - CWaitProgress* m_pLoadProgress; - int m_loadedObjects; - int m_totalObjectsToLoad; ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -389,10 +210,6 @@ private: typedef std::map, stl::less_stricmp > NameNumbersMap; NameNumbersMap m_nameNumbersMap; - ////////////////////////////////////////////////////////////////////////// - // Listeners. - std::list m_objectEventListeners; - bool m_bExiting; std::unordered_set m_animatedAttachedEntities; @@ -401,10 +218,6 @@ private: uint64 m_currentHideCount; - bool m_bInReloading; - bool m_bSkipObjectUpdate; - bool m_bLevelExporting; - int m_axisHelperHitRadius = 20; }; @@ -426,4 +239,3 @@ namespace AzToolsFramework } // namespace AzToolsFramework -#endif // CRYINCLUDE_EDITOR_OBJECTS_OBJECTMANAGER_H diff --git a/Code/Editor/Objects/ObjectManagerLegacyUndo.h b/Code/Editor/Objects/ObjectManagerLegacyUndo.h index 721d188171..29dc2aa4ae 100644 --- a/Code/Editor/Objects/ObjectManagerLegacyUndo.h +++ b/Code/Editor/Objects/ObjectManagerLegacyUndo.h @@ -24,12 +24,11 @@ public: CUndoBaseObjectNew(CBaseObject* object); protected: - virtual int GetSize() override { return sizeof(*this); }; // Return size of xml state. - virtual QString GetDescription() override { return "New BaseObject"; }; - virtual QString GetObjectName() override { return m_object->GetName(); }; + int GetSize() override { return sizeof(*this); } // Return size of xml state. + QString GetObjectName() override { return m_object->GetName(); } - virtual void Undo(bool bUndo) override; - virtual void Redo() override; + void Undo(bool bUndo) override; + void Redo() override; private: CBaseObjectPtr m_object; @@ -47,12 +46,11 @@ public: CUndoBaseObjectDelete(CBaseObject* object); protected: - virtual int GetSize() override { return sizeof(*this); }; // Return size of xml state. - virtual QString GetDescription() override { return "Delete BaseObject"; }; - virtual QString GetObjectName() override { return m_object->GetName(); }; + int GetSize() override { return sizeof(*this); } // Return size of xml state. + QString GetObjectName() override { return m_object->GetName(); } - virtual void Undo(bool bUndo) override; - virtual void Redo() override; + void Undo(bool bUndo) override; + void Redo() override; private: CBaseObjectPtr m_object; @@ -73,20 +71,19 @@ public: * This Undo command can be used for either Legacy or Component Entities, though for * performance reasons Component Entities are typically undone using CUndoBaseObjectBulkSelect * - * @param pObj The object to perform the undo/redo operation on. + * @param object The object to perform the undo/redo operation on. * @param isSelect This is true if you are trying to undo a select operation, and false if * trying to undo a deselect operation */ CUndoBaseObjectSelect(CBaseObject* object, bool isSelect); protected: - virtual void Release() override { delete this; }; - virtual int GetSize() override { return sizeof(*this); }; // Return size of xml state. - virtual QString GetDescription() override { return "Select Object"; }; - virtual QString GetObjectName() override; + void Release() override { delete this; } + int GetSize() override { return sizeof(*this); } // Return size of xml state. + QString GetObjectName() override; - virtual void Undo(bool bUndo) override; - virtual void Redo() override; + void Undo(bool bUndo) override; + void Redo() override; private: GUID m_guid; @@ -116,7 +113,6 @@ public: protected: int GetSize() override { return sizeof(*this); } // Return size of xml state. - QString GetDescription() override { return QObject::tr("Select Objects"); } /* * Deselects the objects @@ -153,7 +149,6 @@ public: protected: int GetSize() override { return sizeof(*this); } // Return size of xml state. - QString GetDescription() override { return QObject::tr("Select Objects"); } void Undo(bool bUndo) override; diff --git a/Code/Editor/Objects/SelectionGroup.cpp b/Code/Editor/Objects/SelectionGroup.cpp index 06d881d607..95a3cc7b14 100644 --- a/Code/Editor/Objects/SelectionGroup.cpp +++ b/Code/Editor/Objects/SelectionGroup.cpp @@ -18,8 +18,6 @@ #include "ViewManager.h" #include "Include/IObjectManager.h" -#include - ////////////////////////////////////////////////////////////////////////// CSelectionGroup::CSelectionGroup() : m_ref(1) @@ -226,7 +224,6 @@ void CSelectionGroup::Move(const Vec3& offset, EMoveSelectionFlag moveFlag, [[ma m_bVertexSnapped = false; FilterParents(); - Vec3 newPos; bool bValidFollowGeometryMode(true); if (point.x() == -1 || point.y() == -1) @@ -234,8 +231,6 @@ void CSelectionGroup::Move(const Vec3& offset, EMoveSelectionFlag moveFlag, [[ma bValidFollowGeometryMode = false; } - SRayHitInfo pickedInfo; - if (moveFlag == eMS_FollowGeometryPosNorm) { if (m_LastestMoveSelectionFlag != eMS_FollowGeometryPosNorm) @@ -249,6 +244,8 @@ void CSelectionGroup::Move(const Vec3& offset, EMoveSelectionFlag moveFlag, [[ma } m_LastestMoveSelectionFlag = moveFlag; + Vec3 zeroPickedInfo_vHitNormal; + Vec3 zeroPickedInfo_vHitPos; for (int i = 0; i < GetFilteredCount(); i++) { @@ -264,16 +261,15 @@ void CSelectionGroup::Move(const Vec3& offset, EMoveSelectionFlag moveFlag, [[ma Vec3 zaxis = m_LastestMovedObjectRot * Vec3(0, 0, 1); zaxis.Normalize(); Quat nq; - nq.SetRotationV0V1(zaxis, pickedInfo.vHitNormal); - obj->SetPos(pickedInfo.vHitPos); + nq.SetRotationV0V1(zaxis, zeroPickedInfo_vHitNormal); + obj->SetPos(zeroPickedInfo_vHitPos); obj->SetRotation(nq * m_LastestMovedObjectRot); continue; } - Matrix34 wtm = obj->GetWorldTM(); + const Matrix34 &wtm = obj->GetWorldTM(); Vec3 wp = wtm.GetTranslation(); - - newPos = wp + offset; + Vec3 newPos = wp + offset; if (moveFlag == eMS_FollowTerrain) { // Make sure object keeps it height. @@ -465,17 +461,6 @@ void CSelectionGroup::SetScale(const Vec3& scale, int referenceCoordSys) Scale(relScale, referenceCoordSys); } - -void CSelectionGroup::StartScaling() -{ - for (int i = 0; i < GetFilteredCount(); i++) - { - CBaseObject* obj = GetFilteredObject(i); - obj->StartScaling(); - } -} - - ////////////////////////////////////////////////////////////////////////// void CSelectionGroup::Align() { @@ -533,45 +518,6 @@ void CSelectionGroup::ResetTransformation() } } -////////////////////////////////////////////////////////////////////////// -void CSelectionGroup::Clone(CSelectionGroup& newGroup) -{ - IObjectManager* pObjMan = GetIEditor()->GetObjectManager(); - assert(pObjMan); - - int i; - CObjectCloneContext cloneContext; - - FilterParents(); - - ////////////////////////////////////////////////////////////////////////// - // Clone every object. - for (i = 0; i < GetFilteredCount(); i++) - { - CBaseObject* pFromObject = GetFilteredObject(i); - CBaseObject* newObj = pObjMan->CloneObject(pFromObject); - if (!newObj) // can be null, e.g. sequence can't be cloned - { - continue; - } - - cloneContext.AddClone(pFromObject, newObj); - newGroup.AddObject(newObj); - } - - ////////////////////////////////////////////////////////////////////////// - // Only after everything was cloned, call PostClone on all cloned objects. - for (i = 0; i < newGroup.GetCount(); ++i) - { - CBaseObject* pFromObject = GetFilteredObject(i); - CBaseObject* pClonedObject = newGroup.GetObject(i); - if (pClonedObject) - { - pClonedObject->PostClone(pFromObject, cloneContext); - } - } -} - ////////////////////////////////////////////////////////////////////////// void CSelectionGroup::SendEvent(ObjectEvent event) { diff --git a/Code/Editor/Objects/SelectionGroup.h b/Code/Editor/Objects/SelectionGroup.h index 600bce5f26..8d979d23be 100644 --- a/Code/Editor/Objects/SelectionGroup.h +++ b/Code/Editor/Objects/SelectionGroup.h @@ -98,7 +98,6 @@ public: //! Resets rotation and scale to identity and (1.0f, 1.0f, 1.0f) void ResetTransformation(); //! Scale objects in selection by given scale. - void StartScaling(); void Scale(const Vec3& scale, int referenceCoordSys); void SetScale(const Vec3& scale, int referenceCoordSys); //! Align objects in selection to surface normal @@ -106,11 +105,6 @@ public: //! Very special method to move contents of a voxel. void MoveContent(const Vec3& offset); - ////////////////////////////////////////////////////////////////////////// - //! Clone objects in this group and add cloned objects to new selection group. - //! Only topmost parent objects will be added to this selection group. - void Clone(CSelectionGroup& newGroup); - // Send event to all objects in selection group. void SendEvent(ObjectEvent event); diff --git a/Code/Editor/Plugin.cpp b/Code/Editor/Plugin.cpp index 4f6fe0279d..68f8d8833b 100644 --- a/Code/Editor/Plugin.cpp +++ b/Code/Editor/Plugin.cpp @@ -14,6 +14,7 @@ // Editor #include "Include/IViewPane.h" +#include CClassFactory* CClassFactory::s_pInstance = nullptr; CAutoRegisterClassHelper* CAutoRegisterClassHelper::s_pFirst = nullptr; @@ -79,7 +80,7 @@ void CClassFactory::RegisterClass(IClassDesc* pClassDesc) existingUUIDString, findByGuid->second->ClassName().toUtf8().data()); - CryMessageBox(errorMessageBuffer, "Invalid class registration - Duplicate UUID", MB_OK); + QMessageBox::critical(nullptr,"Invalid class registration - Duplicate UUID",QString::fromLatin1(errorMessageBuffer)); return; } @@ -107,7 +108,7 @@ void CClassFactory::RegisterClass(IClassDesc* pClassDesc) newUUIDString, existingUUIDString); - CryMessageBox(errorMessageBuffer, "Invalid class registration - Duplicate Class Name", MB_OK); + QMessageBox::critical(nullptr, "Invalid class registration - Duplicate Class Name", QString::fromLatin1(errorMessageBuffer)); return; } diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/Objects/ComponentEntityObject.cpp b/Code/Editor/Plugins/ComponentEntityEditorPlugin/Objects/ComponentEntityObject.cpp index 8957b6ced6..fbb700723b 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/Objects/ComponentEntityObject.cpp +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/Objects/ComponentEntityObject.cpp @@ -36,7 +36,6 @@ #include #include #include -#include #include #include @@ -48,14 +47,12 @@ /** * Scalars for icon drawing behavior. */ -static const int s_kIconSize = 36; /// Icon display size (in pixels) - CComponentEntityObject::CComponentEntityObject() - : m_hasIcon(false) + : m_accentType(AzToolsFramework::EntityAccentType::None) + , m_hasIcon(false) , m_entityIconVisible(false) , m_iconOnlyHitTest(false) , m_drawAccents(true) - , m_accentType(AzToolsFramework::EntityAccentType::None) , m_isIsolated(false) , m_iconTexture(nullptr) { @@ -243,7 +240,7 @@ void CComponentEntityObject::SetSelected(bool bSelect) } bool anySelected = false; - + AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(anySelected, &AzToolsFramework::ToolsApplicationRequests::AreAnyEntitiesSelected); if (!anySelected) @@ -266,18 +263,6 @@ void CComponentEntityObject::SetHighlight(bool bHighlight) } } -IRenderNode* CComponentEntityObject::GetEngineNode() const -{ - // It's possible for AZ::Entities to have multiple IRenderNodes. - // However, the editor currently expects a single IRenderNode per "editor object". - // Therefore, return the highest priority handler. - if (auto* renderNodeHandler = LmbrCentral::RenderNodeRequestBus::FindFirstHandler(m_entityId)) - { - return renderNodeHandler->GetRenderNode(); - } - return nullptr; -} - void CComponentEntityObject::OnEntityNameChanged(const AZStd::string& name) { if (m_nameReentryGuard) @@ -331,14 +316,6 @@ void CComponentEntityObject::DetachThis(bool /*bKeepPos*/) } } -CBaseObject* CComponentEntityObject::GetLinkParent() const -{ - AZ::EntityId parentId; - EBUS_EVENT_ID_RESULT(parentId, m_entityId, AZ::TransformBus, GetParentId); - - return CComponentEntityObject::FindObjectForEntity(parentId); -} - bool CComponentEntityObject::IsFrozen() const { return CheckFlags(OBJFLAG_FROZEN); @@ -358,16 +335,6 @@ void CComponentEntityObject::OnEntityLockChanged(bool locked) CEntityObject::SetFrozen(locked); } -void CComponentEntityObject::SetHidden( - bool bHidden, [[maybe_unused]] uint64 hiddenId /*=CBaseObject::s_invalidHiddenID*/, [[maybe_unused]] bool bAnimated /*=false*/) -{ - if (m_visibilityFlagReentryGuard) - { - EditorActionScope flagChange(m_visibilityFlagReentryGuard); - AzToolsFramework::SetEntityVisibility(m_entityId, !bHidden); - } -} - void CComponentEntityObject::OnEntityVisibilityChanged(bool visible) { CEntityObject::SetHidden(!visible); @@ -612,66 +579,6 @@ void CComponentEntityObject::OnTransformChanged([[maybe_unused]] const AZ::Trans } } -int CComponentEntityObject::MouseCreateCallback(CViewport* view, EMouseEvent event, QPoint& point, int flags) -{ - if (event == eMouseMove || event == eMouseLDown) - { - Vec3 pos; - if (GetIEditor()->GetAxisConstrains() != AXIS_TERRAIN) - { - pos = view->MapViewToCP(point); - } - else - { - // Snap to terrain. - bool hitTerrain; - pos = view->ViewToWorld(point, &hitTerrain); - if (hitTerrain) - { - pos.z = GetIEditor()->GetTerrainElevation(pos.x, pos.y); - } - pos = view->SnapToGrid(pos); - } - - pos = view->SnapToGrid(pos); - SetPos(pos); - - if (event == eMouseLDown) - { - return MOUSECREATE_OK; - } - - return MOUSECREATE_CONTINUE; - } - - return CBaseObject::MouseCreateCallback(view, event, point, flags); -} - -bool CComponentEntityObject::HitHelperTest(HitContext& hc) -{ - bool hit = CEntityObject::HitHelperTest(hc); - if (!hit && m_entityId.IsValid()) - { - // Pick against icon in screen space. - if (IsEntityIconVisible()) - { - const QPoint entityScreenPos = hc.view->WorldToView(GetWorldPos()); - const float screenPosX = static_cast(entityScreenPos.x()); - const float screenPosY = static_cast(entityScreenPos.y()); - const float iconRange = static_cast(s_kIconSize / 2); - - if ((hc.point2d.x() >= screenPosX - iconRange && hc.point2d.x() <= screenPosX + iconRange) - && (hc.point2d.y() >= screenPosY - iconRange && hc.point2d.y() <= screenPosY + iconRange)) - { - hc.dist = hc.raySrc.GetDistance(GetWorldPos()); - hc.iconHit = true; - return true; - } - } - } - return hit; -} - bool CComponentEntityObject::HitTest(HitContext& hc) { AZ_PROFILE_FUNCTION(Entity); @@ -907,11 +814,6 @@ void CComponentEntityObject::DrawDefault(DisplayContext& dc, const QColor& label DrawAccent(dc); } -IStatObj* CComponentEntityObject::GetIStatObj() -{ - return nullptr; -} - bool CComponentEntityObject::IsIsolated() const { return m_isIsolated; @@ -952,11 +854,6 @@ void CComponentEntityObject::SetWorldPos(const Vec3& pos, int flags) CEntityObject::SetWorldPos(pos, flags); } -void CComponentEntityObject::OnContextMenu(QMenu* /*pMenu*/) -{ - // Deliberately bypass the base class implementation (CEntityObject::OnContextMenu()). -} - void CComponentEntityObject::SetupEntityIcon() { bool hideIconInViewport = false; diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/Objects/ComponentEntityObject.h b/Code/Editor/Plugins/ComponentEntityEditorPlugin/Objects/ComponentEntityObject.h index bb19f4d77e..7ccea8da84 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/Objects/ComponentEntityObject.h +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/Objects/ComponentEntityObject.h @@ -56,27 +56,21 @@ public: bool SetScale(const Vec3& scale, int flags) override; void InvalidateTM(int nWhyFlags) override; void Display(DisplayContext& disp) override; - void OnContextMenu(QMenu* pMenu) override; - int MouseCreateCallback(CViewport* view, EMouseEvent event, QPoint& point, int flags) override; - bool HitHelperTest(HitContext& hc) override; bool HitTest(HitContext& hc) override; void GetLocalBounds(AABB& box) override; void GetBoundBox(AABB& box) override; void SetName(const QString& name) override; bool IsFrozen() const override; void SetFrozen(bool bFrozen) override; - void SetHidden(bool bHidden, uint64 hiddenId = CBaseObject::s_invalidHiddenID, bool bAnimated = false) override; void SetSelected(bool bSelect) override; void SetHighlight(bool bHighlight) override; - IRenderNode* GetEngineNode() const override; void AttachChild(CBaseObject* child, bool bKeepPos = true) override; void DetachAll(bool bKeepPos = true) override; void DetachThis(bool bKeepPos = true) override; - CBaseObject* GetLinkParent() const override; XmlNodeRef Export(const QString& levelPath, XmlNodeRef& xmlNode) override; void DeleteEntity() override; void DrawDefault(DisplayContext& dc, const QColor& labelColor = QColor(255, 255, 255)) override; - IStatObj* GetIStatObj() override; + bool IsIsolated() const override; bool IsSelected() const override; bool IsSelectable() const override; @@ -88,12 +82,6 @@ public: // Component entity highlighting (accenting) is taken care of elsewhere void DrawHighlight(DisplayContext& /*dc*/) override {}; - // Don't auto-clone children. Cloning happens in groups with reference fixups, - // and individually selected objercts should be cloned as individuals. - bool ShouldCloneChildren() const override { return false; } - - - ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp b/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp index 64bd943d63..d5b903585e 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -208,6 +209,9 @@ void SandboxIntegrationManager::Setup() m_editorEntityAPI = AZ::Interface::Get(); AZ_Assert(m_editorEntityAPI, "SandboxIntegrationManager requires an EditorEntityAPI instance to be present on Setup()."); + m_readOnlyEntityPublicInterface = AZ::Interface::Get(); + AZ_Assert(m_readOnlyEntityPublicInterface, "SandboxIntegrationManager requires an ReadOnlyEntityPublicInterface instance to be present on Setup()."); + AzToolsFramework::Layers::EditorLayerComponentNotificationBus::Handler::BusConnect(); } @@ -447,7 +451,7 @@ void SandboxIntegrationManager::OnEndUndo(const char* label, bool changed) // Add the undo only after we know it's got a legit change, we can't remove undos from the cry undo system so we do it here instead of OnBeginUndo if (changed && CUndo::IsRecording()) { - CUndo::Record(new CToolsApplicationUndoLink(label)); + CUndo::Record(new CToolsApplicationUndoLink()); } if (m_startedUndoRecordingNestingLevel) { @@ -498,13 +502,11 @@ void SandboxIntegrationManager::EntityParentChanged( // before finally being saved, it will result in all of those layers saving, too. AZ::EntityId oldAncestor = oldParentId; - bool wasNotInLayer = false; AZ::EntityId oldLayer; do { if (!oldAncestor.IsValid()) { - wasNotInLayer = true; break; } @@ -530,13 +532,11 @@ void SandboxIntegrationManager::EntityParentChanged( AZ::EntityId newAncestor = newParentId; - bool isGoingToRootScene = false; AZ::EntityId newLayer; do { if (!newAncestor.IsValid()) { - isGoingToRootScene = true; break; } @@ -662,15 +662,17 @@ void SandboxIntegrationManager::PopulateEditorGlobalContextMenu(QMenu* menu, con // when a single entity is selected, entity is created as its child else if (selected.size() == 1) { + AZ::EntityId selectedEntityId = selected.front(); + bool selectedEntityIsReadOnly = m_readOnlyEntityPublicInterface->IsReadOnly(selectedEntityId); auto containerEntityInterface = AZ::Interface::Get(); - if (!prefabSystemEnabled || (containerEntityInterface && containerEntityInterface->IsContainerOpen(selected.front()))) + if (!prefabSystemEnabled || (containerEntityInterface && containerEntityInterface->IsContainerOpen(selectedEntityId) && !selectedEntityIsReadOnly)) { action = menu->addAction(QObject::tr("Create entity")); QObject::connect( action, &QAction::triggered, action, - [selected] + [selectedEntityId] { - AzToolsFramework::EditorRequestBus::Broadcast(&AzToolsFramework::EditorRequestBus::Handler::CreateNewEntityAsChild, selected.front()); + AzToolsFramework::EditorRequestBus::Broadcast(&AzToolsFramework::EditorRequestBus::Handler::CreateNewEntityAsChild, selectedEntityId); } ); } @@ -695,11 +697,27 @@ void SandboxIntegrationManager::PopulateEditorGlobalContextMenu(QMenu* menu, con SetupSliceContextMenu(menu); } - action = menu->addAction(QObject::tr("Duplicate")); - QObject::connect(action, &QAction::triggered, action, [this] { ContextMenu_Duplicate(); }); - if (selected.size() == 0) + if (!selected.empty()) { - action->setDisabled(true); + // Don't allow duplication if any of the selected entities are direct desendants of a read-only entity + bool selectionContainsDescendantOfReadOnlyEntity = false; + for (const auto& entityId : selected) + { + AZ::EntityId parentEntityId; + AZ::TransformBus::EventResult(parentEntityId, entityId, &AZ::TransformBus::Events::GetParentId); + + if (parentEntityId.IsValid() && m_readOnlyEntityPublicInterface->IsReadOnly(parentEntityId)) + { + selectionContainsDescendantOfReadOnlyEntity = true; + break; + } + } + + if (!selectionContainsDescendantOfReadOnlyEntity) + { + action = menu->addAction(QObject::tr("Duplicate")); + QObject::connect(action, &QAction::triggered, action, [this] { ContextMenu_Duplicate(); }); + } } if (!prefabSystemEnabled) @@ -1400,7 +1418,7 @@ void SandboxIntegrationManager::ContextMenu_NewEntity() { worldPosition = AzToolsFramework::FindClosestPickIntersection( view->GetViewportId(), AzFramework::ScreenPointFromVector2(m_contextMenuViewPoint), AzToolsFramework::EditorPickRayLength, - GetDefaultEntityPlacementDistance()); + AzToolsFramework::GetDefaultEntityPlacementDistance()); } CreateNewEntityAtPosition(worldPosition); diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.h b/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.h index 9afa944438..79e7a4334d 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.h +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.h @@ -76,6 +76,7 @@ namespace AzToolsFramework { class EditorEntityAPI; class EditorEntityUiInterface; + class ReadOnlyEntityPublicInterface; namespace AssetBrowser { @@ -295,6 +296,7 @@ private: AzToolsFramework::EditorEntityUiInterface* m_editorEntityUiInterface = nullptr; AzToolsFramework::Prefab::PrefabIntegrationInterface* m_prefabIntegrationInterface = nullptr; AzToolsFramework::EditorEntityAPI* m_editorEntityAPI = nullptr; + AzToolsFramework::ReadOnlyEntityPublicInterface* m_readOnlyEntityPublicInterface = nullptr; // Overrides UI styling and behavior for Layer Entities AzToolsFramework::LayerUiHandler m_layerUiOverrideHandler; @@ -306,8 +308,7 @@ class CToolsApplicationUndoLink { public: - CToolsApplicationUndoLink(const char* description) - : m_description(description) + CToolsApplicationUndoLink() { } @@ -316,11 +317,6 @@ public: return 0; } - QString GetDescription() override - { - return m_description.c_str(); - } - void Undo(bool bUndo = true) override { // Always run the undo even if the flag was set to false, that just means that undo wasn't expressly desired, but can be used in cases of canceling the current super undo. @@ -354,8 +350,6 @@ public: w->setFocus(Qt::OtherFocusReason); } } - - AZStd::string m_description; }; #endif // CRYINCLUDE_COMPONENTENTITYEDITORPLUGIN_SANDBOXINTEGRATION_H diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.cpp b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.cpp index 2073d0dd5a..60fda239dc 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.cpp +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.cpp @@ -188,48 +188,46 @@ AssetCatalogModel::~AssetCatalogModel() AzFramework::AssetCatalogEventBus::Handler::BusDisconnect(); } -AZ::Data::AssetType AssetCatalogModel::GetAssetType(QString filename) const +AZ::Data::AssetType AssetCatalogModel::GetAssetType(const QString &filename) const { - AZ::Data::AssetType returnType = AZ::Uuid::CreateNull(); // Compare file extensions with the map created from the asset database. int dotIndex = filename.lastIndexOf('.'); - if (dotIndex >= 0) + if (dotIndex < 0) { - QString extension = filename.mid(dotIndex); - for (auto pair : m_extensionToAssetType) - { - QString qExtensions = pair.first.c_str(); - if (qExtensions.indexOf(extension) >= 0) - { - if (pair.second.size() > 1) - { - // There are multiple types with this extension. Check each handler to see if they can handle this data type. - AZStd::string azFilename = filename.toStdString().c_str(); - EBUS_EVENT(AzFramework::ApplicationRequests::Bus, MakePathAssetRootRelative, azFilename); - AZ::Data::AssetId assetId; - EBUS_EVENT_RESULT(assetId, AZ::Data::AssetCatalogRequestBus, GetAssetIdByPath, azFilename.c_str(), AZ::Data::s_invalidAssetType, false); + return AZ::Uuid::CreateNull(); + } - for (AZ::Uuid type : pair.second) - { - const AZ::Data::AssetHandler* handler = AZ::Data::AssetManager::Instance().GetHandler(type); - if (handler && handler->CanHandleAsset(assetId)) - { - returnType = type; - break; - } - } - } - else - { - returnType = pair.second[0]; - break; - } + QStringRef extension = filename.midRef(dotIndex); + for (const auto& pair : m_extensionToAssetType) + { + QString qExtensions = pair.first.c_str(); + if (qExtensions.indexOf(extension) < 0 || pair.second.empty()) + { + continue; + } + if (pair.second.size() == 1) + { + return pair.second[0]; + } + + // There are multiple types with this extension. Search for a handler that can handle this data type. + AZStd::string azFilename = filename.toStdString().c_str(); + EBUS_EVENT(AzFramework::ApplicationRequests::Bus, MakePathAssetRootRelative, azFilename); + AZ::Data::AssetId assetId; + EBUS_EVENT_RESULT(assetId, AZ::Data::AssetCatalogRequestBus, GetAssetIdByPath, azFilename.c_str(), AZ::Data::s_invalidAssetType, false); + + for (const AZ::Uuid& type : pair.second) + { + const AZ::Data::AssetHandler* handler = AZ::Data::AssetManager::Instance().GetHandler(type); + if (handler && handler->CanHandleAsset(assetId)) + { + return type; } } } - return returnType; + return AZ::Uuid::CreateNull(); } QStandardItem* AssetCatalogModel::GetPath(QString& path, bool createIfNeeded, QStandardItem* parent) @@ -410,7 +408,7 @@ AssetCatalogEntry* AssetCatalogModel::AddAsset(QString assetPath, AZ::Data::Asse // icons' memory being reclaimed and crashing the Editor. QSize size = fileIcon.actualSize(QSize(16, 16)); QIcon deepCopy = fileIcon.pixmap(size).copy(0, 0, size.width(), size.height()); - + if (!fileIcon.isNull()) { m_assetTypeToIcon[assetType] = deepCopy; diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.h b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.h index c9143bb259..1dffa81d67 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.h +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/AssetCatalogModel.h @@ -110,7 +110,7 @@ protected: void SetFilterRegExp(const AZStd::string& filterType, const QRegExp& regExp); void ClearFilterRegExp(const AZStd::string& filterType = AZStd::string()); - AZ::Data::AssetType GetAssetType(QString filename) const; + AZ::Data::AssetType GetAssetType(const QString &filename) const; QStandardItem* GetPath(QString& path, bool createIfNeeded, QStandardItem* parent = nullptr); void ApplyFilter(QStandardItem* parent); diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/Outliner/EntityOutliner.qss b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/Outliner/EntityOutliner.qss index de35f91678..a7d3949527 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/Outliner/EntityOutliner.qss +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/Outliner/EntityOutliner.qss @@ -11,7 +11,6 @@ OutlinerWidget #m_display_options { qproperty-icon: url(:/Menu/menu.svg); qproperty-iconSize: 16px 16px; - qproperty-flat: true; } OutlinerWidget QWidget[PulseHighlight="true"] diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/Outliner/OutlinerListModel.cpp b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/Outliner/OutlinerListModel.cpp index c25248d4f6..08d79adcf5 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/Outliner/OutlinerListModel.cpp +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/Outliner/OutlinerListModel.cpp @@ -2640,7 +2640,7 @@ QSize OutlinerItemDelegate::sizeHint(const QStyleOptionViewItem& option, const Q m_cachedBoundingRectOfTallCharacter = QRect(); }; - QTimer::singleShot(0, resetFunction); + QTimer::singleShot(0, this, resetFunction); } // And add 8 to it gives the outliner roughly the visible spacing we're looking for. diff --git a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/Outliner/OutlinerWidget.cpp b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/Outliner/OutlinerWidget.cpp index 803deb3509..af58ac4b6b 100644 --- a/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/Outliner/OutlinerWidget.cpp +++ b/Code/Editor/Plugins/ComponentEntityEditorPlugin/UI/Outliner/OutlinerWidget.cpp @@ -121,6 +121,18 @@ namespace SortEntityChildrenRecursively(childId, comparer); } } + + QModelIndex nextIndexForTree(bool direction, OutlinerTreeView *tree, QModelIndex current) + { + if (direction) + { + return tree->indexAbove(current); + } + else + { + return tree->indexBelow(current); + } + } } OutlinerWidget::OutlinerWidget(QWidget* pParent, Qt::WindowFlags flags) @@ -891,9 +903,7 @@ void OutlinerWidget::DoSelectSliceRootNextToSelection(bool isTraversalUpwards) return; } - AZStd::function getNextIdxFunction = - AZStd::bind(isTraversalUpwards ? &QTreeView::indexAbove : &QTreeView::indexBelow, treeView, AZStd::placeholders::_1); - QModelIndex nextIdx = getNextIdxFunction(currentIdx); + QModelIndex nextIdx = nextIndexForTree(isTraversalUpwards,treeView,currentIdx); bool foundSliceRoot = false; while (nextIdx.isValid() && !foundSliceRoot) @@ -904,7 +914,7 @@ void OutlinerWidget::DoSelectSliceRootNextToSelection(bool isTraversalUpwards) AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult( foundSliceRoot, &AzToolsFramework::ToolsApplicationRequests::IsSliceRootEntity, currentEntityId); - nextIdx = getNextIdxFunction(currentIdx); + nextIdx = nextIndexForTree(isTraversalUpwards, treeView, currentIdx); } if (foundSliceRoot) @@ -934,13 +944,10 @@ void OutlinerWidget::DoSelectEdgeSliceRoot(bool shouldSelectTopMostSlice) } QModelIndex currentIdx; - AZStd::function getNextIdxFunction; + if (shouldSelectTopMostSlice) { currentIdx = itemModel->index(0, OutlinerListModel::ColumnName); - - getNextIdxFunction = - AZStd::bind(&QTreeView::indexBelow, treeView, AZStd::placeholders::_1); } else { @@ -949,9 +956,6 @@ void OutlinerWidget::DoSelectEdgeSliceRoot(bool shouldSelectTopMostSlice) { currentIdx = itemModel->index(itemModel->rowCount(currentIdx) - 1, OutlinerListModel::ColumnName, currentIdx); } - - getNextIdxFunction = - AZStd::bind(&QTreeView::indexAbove, treeView, AZStd::placeholders::_1); } QModelIndex nextIdx = currentIdx; @@ -964,7 +968,7 @@ void OutlinerWidget::DoSelectEdgeSliceRoot(bool shouldSelectTopMostSlice) AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult( foundSliceRoot, &AzToolsFramework::ToolsApplicationRequests::IsSliceRootEntity, currentEntityId); - nextIdx = getNextIdxFunction(currentIdx); + nextIdx = nextIndexForTree(shouldSelectTopMostSlice,treeView,currentIdx); } while (nextIdx.isValid() && !foundSliceRoot); if (foundSliceRoot) @@ -1416,7 +1420,10 @@ void OutlinerWidget::SortContent() } m_entitiesToSort.clear(); - auto comparer = AZStd::bind(&CompareEntitiesForSorting, AZStd::placeholders::_1, AZStd::placeholders::_2, m_sortMode); + auto comparer = [sortMode = m_sortMode](AZ::EntityId left, AZ::EntityId right) -> bool + { + return CompareEntitiesForSorting(left, right, sortMode); + }; for (const AZ::EntityId& entityId : parentsToSort) { SortEntityChildren(entityId, comparer); @@ -1433,7 +1440,10 @@ void OutlinerWidget::OnSortModeChanged(EntityOutliner::DisplaySortMode sortMode) if (sortMode != EntityOutliner::DisplaySortMode::Manually) { AZ_PROFILE_FUNCTION(AzToolsFramework); - auto comparer = AZStd::bind(&CompareEntitiesForSorting, AZStd::placeholders::_1, AZStd::placeholders::_2, sortMode); + auto comparer = [sortMode = m_sortMode](AZ::EntityId left, AZ::EntityId right) -> bool + { + return CompareEntitiesForSorting(left, right, sortMode); + }; SortEntityChildrenRecursively(AZ::EntityId(), comparer); } diff --git a/Code/Editor/Plugins/EditorCommon/editorcommon_files.cmake b/Code/Editor/Plugins/EditorCommon/editorcommon_files.cmake index c1f1a531e8..1c6efbdf2c 100644 --- a/Code/Editor/Plugins/EditorCommon/editorcommon_files.cmake +++ b/Code/Editor/Plugins/EditorCommon/editorcommon_files.cmake @@ -13,7 +13,6 @@ set(FILES EditorCommonAPI.h ActionOutput.h ActionOutput.cpp - UiEditorDLLBus.h DockTitleBarWidget.cpp DockTitleBarWidget.h SaveUtilities/AsyncSaveRunner.h diff --git a/Code/Editor/Settings.cpp b/Code/Editor/Settings.cpp index 908e1c40f8..acb48c3545 100644 --- a/Code/Editor/Settings.cpp +++ b/Code/Editor/Settings.cpp @@ -112,8 +112,6 @@ SEditorSettings::SEditorSettings() m_showCircularDependencyError = true; bAutoloadLastLevelAtStartup = false; bMuteAudio = false; - bEnableGameModeVR = false; - objectHideMask = 0; objectSelectMask = 0xFFFFFFFF; // Initially all selectable. diff --git a/Code/Editor/Settings.h b/Code/Editor/Settings.h index f7617dd8c9..9276d9b715 100644 --- a/Code/Editor/Settings.h +++ b/Code/Editor/Settings.h @@ -305,7 +305,6 @@ AZ_POP_DISABLE_DLL_EXPORT_BASECLASS_WARNING bool m_showCircularDependencyError; bool bAutoloadLastLevelAtStartup; bool bMuteAudio; - bool bEnableGameModeVR; //! Speed of camera movement. float cameraMoveSpeed; diff --git a/Code/Editor/StartupLogoDialog.ui b/Code/Editor/StartupLogoDialog.ui index c2cbfcfd69..cbc596f53a 100644 --- a/Code/Editor/StartupLogoDialog.ui +++ b/Code/Editor/StartupLogoDialog.ui @@ -103,7 +103,7 @@ - Stable 21.11 + development diff --git a/Code/Editor/TrackView/SoundKeyUIControls.cpp b/Code/Editor/TrackView/SoundKeyUIControls.cpp index 8c18b58aed..f001937e82 100644 --- a/Code/Editor/TrackView/SoundKeyUIControls.cpp +++ b/Code/Editor/TrackView/SoundKeyUIControls.cpp @@ -108,18 +108,15 @@ void CSoundKeyUIControls::OnUIChange(IVariable* pVar, CTrackViewKeyBundle& selec { ISoundKey soundKey; keyHandle.GetKey(&soundKey); - bool bChangedSoundFile = false; if (pVar == mv_startTrigger.GetVar()) { QString sFilename = mv_startTrigger; - bChangedSoundFile = sFilename != soundKey.sStartTrigger.c_str(); soundKey.sStartTrigger = sFilename.toUtf8().data(); } else if (pVar == mv_stopTrigger.GetVar()) { QString sFilename = mv_stopTrigger; - bChangedSoundFile = sFilename != soundKey.sStopTrigger.c_str(); soundKey.sStopTrigger = sFilename.toUtf8().data(); } diff --git a/Code/Editor/TrackView/TrackViewDopeSheetBase.cpp b/Code/Editor/TrackView/TrackViewDopeSheetBase.cpp index a14da0ae09..2907bebdb4 100644 --- a/Code/Editor/TrackView/TrackViewDopeSheetBase.cpp +++ b/Code/Editor/TrackView/TrackViewDopeSheetBase.cpp @@ -2233,7 +2233,6 @@ void CTrackViewDopeSheetBase::AddKeys(const QPoint& point, const bool bTryAddKey if (pTrack && inRange) { - bool keyCreated = false; if (bTryAddKeysInGroup && pNode->GetParentNode()) // Add keys in group { CTrackViewTrackBundle tracksInGroup = pNode->GetTracksByParam(pTrack->GetParameterType()); @@ -2248,8 +2247,6 @@ void CTrackViewDopeSheetBase::AddKeys(const QPoint& point, const bool bTryAddKey AzToolsFramework::ScopedUndoBatch undoBatch("Create Key"); pCurrTrack->CreateKey(keyTime); undoBatch.MarkEntityDirty(sequence->GetSequenceComponentEntityId()); - - keyCreated = true; } } else // A compound track @@ -2262,8 +2259,6 @@ void CTrackViewDopeSheetBase::AddKeys(const QPoint& point, const bool bTryAddKey AzToolsFramework::ScopedUndoBatch undoBatch("Create Key"); pSubTrack->CreateKey(keyTime); undoBatch.MarkEntityDirty(sequence->GetSequenceComponentEntityId()); - - keyCreated = true; } } } @@ -2276,15 +2271,13 @@ void CTrackViewDopeSheetBase::AddKeys(const QPoint& point, const bool bTryAddKey AzToolsFramework::ScopedUndoBatch undoBatch("Create Key"); pTrack->CreateKey(keyTime); undoBatch.MarkEntityDirty(sequence->GetSequenceComponentEntityId()); - - keyCreated = true; } } else // A compound track { if (pTrack->GetValueType() == AnimValueType::RGB) { - keyCreated = CreateColorKey(pTrack, keyTime); + CreateColorKey(pTrack, keyTime); } else { @@ -2295,7 +2288,6 @@ void CTrackViewDopeSheetBase::AddKeys(const QPoint& point, const bool bTryAddKey if (IsOkToAddKeyHere(pSubTrack, keyTime)) { pSubTrack->CreateKey(keyTime); - keyCreated = true; } } undoBatch.MarkEntityDirty(sequence->GetSequenceComponentEntityId()); @@ -2617,7 +2609,6 @@ void CTrackViewDopeSheetBase::DrawSelectTrack(const Range& timeRange, QPainter* void CTrackViewDopeSheetBase::DrawBoolTrack(const Range& timeRange, QPainter* painter, CTrackViewTrack* pTrack, const QRect& rc) { int x0 = TimeToClient(timeRange.start); - float t0 = timeRange.start; const QBrush prevBrush = painter->brush(); painter->setBrush(m_visibilityBrush); @@ -2648,7 +2639,6 @@ void CTrackViewDopeSheetBase::DrawBoolTrack(const Range& timeRange, QPainter* pa painter->fillRect(QRect(QPoint(x0, rc.top() + 4), QPoint(x, rc.bottom() - 4)), gradient); } - t0 = time; x0 = x; } int x = TimeToClient(timeRange.end); diff --git a/Code/Editor/TrackView/TrackViewKeyPropertiesDlg.cpp b/Code/Editor/TrackView/TrackViewKeyPropertiesDlg.cpp index 2fd46b0fc5..a17d92ac0a 100644 --- a/Code/Editor/TrackView/TrackViewKeyPropertiesDlg.cpp +++ b/Code/Editor/TrackView/TrackViewKeyPropertiesDlg.cpp @@ -198,7 +198,6 @@ void CTrackViewKeyPropertiesDlg::OnKeySelectionChanged(CTrackViewSequence* seque m_wndProps->setEnabled(false); m_wndTrackProps->setEnabled(false); - bool bAssigned = false; if (selectedKeys.GetKeyCount() > 0 && selectedKeys.AreAllKeysOfSameType()) { CTrackViewTrack* pTrack = selectedKeys.GetKey(0).GetTrack(); @@ -215,12 +214,6 @@ void CTrackViewKeyPropertiesDlg::OnKeySelectionChanged(CTrackViewSequence* seque { AddVars(m_keyControls[i]); } - - if (m_keyControls[i]->OnKeySelectionChange(selectedKeys)) - { - bAssigned = true; - } - break; } } diff --git a/Code/Editor/TrackView/TrackViewSplineCtrl.cpp b/Code/Editor/TrackView/TrackViewSplineCtrl.cpp index 352f2f3699..b80818db0d 100644 --- a/Code/Editor/TrackView/TrackViewSplineCtrl.cpp +++ b/Code/Editor/TrackView/TrackViewSplineCtrl.cpp @@ -57,7 +57,6 @@ protected: } int GetSize() override { return sizeof(*this); } - QString GetDescription() override { return "UndoTrackViewSplineCtrl"; }; void Undo(bool bUndo) override { diff --git a/Code/Editor/TrackView/TrackViewUndo.h b/Code/Editor/TrackView/TrackViewUndo.h index ff5964ec7f..7f3b4737a1 100644 --- a/Code/Editor/TrackView/TrackViewUndo.h +++ b/Code/Editor/TrackView/TrackViewUndo.h @@ -6,9 +6,6 @@ * */ - -#ifndef CRYINCLUDE_EDITOR_TRACKVIEW_TRACKVIEWUNDO_H -#define CRYINCLUDE_EDITOR_TRACKVIEW_TRACKVIEWUNDO_H #pragma once #include "TrackViewTrack.h" @@ -27,11 +24,10 @@ public: CUndoComponentEntityTrackObject(CTrackViewTrack* track); protected: - virtual int GetSize() override { return sizeof(*this); } - virtual QString GetDescription() override { return "Undo Component Entity Track Modify"; }; + int GetSize() override { return sizeof(*this); } - virtual void Undo(bool bUndo) override; - virtual void Redo() override; + void Undo(bool bUndo) override; + void Redo() override; private: @@ -50,5 +46,3 @@ private: CTrackViewTrackMemento m_undo; CTrackViewTrackMemento m_redo; }; - -#endif // CRYINCLUDE_EDITOR_TRACKVIEW_TRACKVIEWUNDO_H diff --git a/Code/Editor/Undo/IUndoObject.h b/Code/Editor/Undo/IUndoObject.h index 47d853dd9a..3791b76d48 100644 --- a/Code/Editor/Undo/IUndoObject.h +++ b/Code/Editor/Undo/IUndoObject.h @@ -8,10 +8,6 @@ // Description : Interface for implementation of IUndo objects. - - -#ifndef CRYINCLUDE_EDITOR_UNDO_IUNDOOBJECT_H -#define CRYINCLUDE_EDITOR_UNDO_IUNDOOBJECT_H #pragma once #include @@ -26,8 +22,6 @@ struct IUndoObject virtual void Release() { delete this; }; //! Return size of this Undo object. virtual int GetSize() = 0; - //! Return description of this Undo object. - virtual QString GetDescription() = 0; //! Undo this object. //! @param bUndo If true this operation called in response to Undo operation. @@ -37,14 +31,5 @@ struct IUndoObject virtual void Redo() = 0; // Returns the name of undo object - virtual QString GetObjectName(){ return QString(); }; - - // Returns the name of related editor object. - // Ex: For a undo action which would modify value for var "Emitter Strength" of emitter "Level.example", - // this function will return emitter name "Level.example" - Vera, Confetti - virtual QString GetEditorObjectName() { return QString(); }; - - virtual bool IsChanged([[maybe_unused]] unsigned int& compareValue) const { return false; } + virtual QString GetObjectName(){ return QString(); } }; - -#endif // CRYINCLUDE_EDITOR_UNDO_IUNDOOBJECT_H diff --git a/Code/Editor/Util/Image.cpp b/Code/Editor/Util/Image.cpp index 773bfa93d2..8b26f54075 100644 --- a/Code/Editor/Util/Image.cpp +++ b/Code/Editor/Util/Image.cpp @@ -75,17 +75,16 @@ void CImageEx::ReverseUpDown() } uint32* pPixData = GetData(); - uint32* pReversePix = new uint32[GetWidth() * GetHeight()]; - - for (int i = GetHeight() - 1, i2 = 0; i >= 0; i--, i2++) + const int height = GetHeight(); + const int width = GetWidth(); + for (int i = 0; i < height / 2; i++) { - for (int k = 0; k < GetWidth(); k++) + for (int j = 0; j < width; j++) { - pReversePix[i2 * GetWidth() + k] = pPixData[i * GetWidth() + k]; + AZStd::swap(pPixData[i * width + j], pPixData[(height - 1 - i) * width + j]); } } - Attach(pReversePix, GetWidth(), GetHeight()); } void CImageEx::FillAlpha(unsigned char value) diff --git a/Code/Editor/Util/ImageGif.cpp b/Code/Editor/Util/ImageGif.cpp index 67c5911344..336584b959 100644 --- a/Code/Editor/Util/ImageGif.cpp +++ b/Code/Editor/Util/ImageGif.cpp @@ -197,11 +197,9 @@ bool CImageGif::Load(const QString& fileName, CImageEx& outImage) return false; } - int numcols; unsigned char ch, ch1; uint8* ptr1; int i; - short transparency = -1; TImage outImageIndex; @@ -246,7 +244,6 @@ bool CImageGif::Load(const QString& fileName, CImageEx& outImage) HasColormap = ((ch & COLORMAPMASK) ? true : false); BitsPerPixel = (ch & 7) + 1; - numcols = ColorMapSize = 1 << BitsPerPixel; BitMask = ColorMapSize - 1; Background = NEXTBYTE; /* background color... not used. */ @@ -290,10 +287,6 @@ bool CImageGif::Load(const QString& fileName, CImageEx& outImage) { case GRAPHIC_EXT: ch = NEXTBYTE; - if (ptr[0] & 0x1) - { - transparency = ptr[3]; /* transparent color index */ - } ptr += ch; break; case PLAINTEXT_EXT: @@ -317,9 +310,6 @@ bool CImageGif::Load(const QString& fileName, CImageEx& outImage) } } - //if (transparency >= 0) - //mfSet_transparency(transparency); - /* Now read in values from the image descriptor */ ch = NEXTBYTE; diff --git a/Code/Editor/Util/KDTree.cpp b/Code/Editor/Util/KDTree.cpp deleted file mode 100644 index af06a58c9f..0000000000 --- a/Code/Editor/Util/KDTree.cpp +++ /dev/null @@ -1,572 +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 "EditorDefs.h" - -#include "KDTree.h" - -#include - -class KDTreeNode -{ -public: - KDTreeNode() - { - pChildren[0] = nullptr; - pChildren[1] = nullptr; - pVertexIndices = nullptr; - } - ~KDTreeNode() - { - if (!IsLeaf()) - { - if (pChildren[0]) - { - delete pChildren[0]; - } - if (pChildren[1]) - { - delete pChildren[1]; - } - } - else if (GetVertexBufferSize() > 1) - { - if (pVertexIndices) - { - delete [] pVertexIndices; - } - } - } - uint32 GetVertexBufferSize() const - { - return nVertexIndexBufferSize; - } - float GetSplitPos() const - { - return splitPos; - } - void SetSplitPos(float pos) - { - splitPos = pos; - } - CKDTree::ESplitAxis GetSplitAxis() const - { - if (splitAxis == 0) - { - return CKDTree::eSA_X; - } - if (splitAxis == 1) - { - return CKDTree::eSA_Y; - } - if (splitAxis == 2) - { - return CKDTree::eSA_Z; - } - return CKDTree::eSA_Invalid; - } - void SetSplitAxis(const CKDTree::ESplitAxis& axis) - { - splitAxis = axis; - } - bool IsLeaf() const - { - return pChildren[0] == nullptr && pChildren[1] == nullptr; - } - KDTreeNode* GetChild(uint32 nIndex) const - { - if (nIndex > 1) - { - return nullptr; - } - return pChildren[nIndex]; - } - void SetChild(uint32 nIndex, KDTreeNode* pNode) - { - if (nIndex > 1) - { - return; - } - if (pChildren[nIndex]) - { - delete pChildren[nIndex]; - } - pChildren[nIndex] = pNode; - } - const AABB& GetBoundBox() - { - return boundbox; - } - void SetBoundBox(const AABB& aabb) - { - boundbox = aabb; - } - void SetVertexIndexBuffer(std::vector& vertexInfos) - { - nVertexIndexBufferSize = (uint32)vertexInfos.size(); - if (nVertexIndexBufferSize == 0) - { - return; - } - if (nVertexIndexBufferSize == 1) - { - oneIndex = vertexInfos[0]; - } - else - { - pVertexIndices = new uint32[nVertexIndexBufferSize]; - memcpy(pVertexIndices, &vertexInfos[0], sizeof(uint32) * nVertexIndexBufferSize); - } - } - uint32 GetVertexIndex(uint32 nIndex) const - { - if (GetVertexBufferSize() == 1) - { - return oneIndex & 0x00FFFFFF; - } - - return pVertexIndices[nIndex] & 0x00FFFFFF; - } - uint32 GetObjIndex(uint32 nIndex) const - { - if (GetVertexBufferSize() == 1) - { - return (oneIndex & 0xFF000000) >> 24; - } - - return (pVertexIndices[nIndex] & 0xFF000000) >> 24; - } - -private: - union - { - float splitPos; // Interior - uint32 oneIndex; // Leaf - uint32* pVertexIndices; // Leaf : high 8bits - object index, low 24bits - vertex index - }; - union - { - uint32 splitAxis; // Interior - uint32 nVertexIndexBufferSize; // Leaf - }; - AABB boundbox; // Both - KDTreeNode* pChildren[2]; // Interior -}; - -CKDTree::ESplitAxis SearchForBestSplitAxis(const AABB& aabb) -{ - float xsize = aabb.max.x - aabb.min.x; - float ysize = aabb.max.y - aabb.min.y; - float zsize = aabb.max.z - aabb.min.z; - - CKDTree::ESplitAxis axis; - if (xsize > ysize && xsize > zsize) - { - axis = CKDTree::eSA_X; - } - else if (ysize > zsize && ysize > xsize) - { - axis = CKDTree::eSA_Y; - } - else - { - axis = CKDTree::eSA_Z; - } - - return axis; -} - -bool SearchForBestSplitPos(CKDTree::ESplitAxis axis, const std::vector& statObjList, std::vector& indices, float& outBestSplitPos) -{ - if (axis != CKDTree::eSA_X && axis != CKDTree::eSA_Y && axis != CKDTree::eSA_Z) - { - return false; - } - - outBestSplitPos = 0; - - int nSizeOfIndices = static_cast(indices.size()); - - for (int i = 0; i < nSizeOfIndices; ++i) - { - int nObjIndex = (indices[i] & 0xFF000000) >> 24; - int nVertexIndex = (indices[i] & 0xFFFFFF); - - const CKDTree::SStatObj* pObj = &statObjList[nObjIndex]; - - const IIndexedMesh* pMesh = pObj->pStatObj->GetIndexedMesh(); - if (pMesh == nullptr) - { - continue; - } - - IIndexedMesh::SMeshDescription meshDesc; - pMesh->GetMeshDescription(meshDesc); - - if (meshDesc.m_pVerts) - { - outBestSplitPos += pObj->tm.TransformPoint(meshDesc.m_pVerts[nVertexIndex])[axis]; - } - else if (meshDesc.m_pVertsF16) - { - outBestSplitPos += pObj->tm.TransformPoint(meshDesc.m_pVertsF16[nVertexIndex].ToVec3())[axis]; - } - } - - outBestSplitPos /= nSizeOfIndices; - - return true; -} - -struct SSplitInfo -{ - AABB aboveBoundbox; - std::vector aboveIndices; - AABB belowBoundbox; - std::vector belowIndices; -}; - -bool SplitNode(const std::vector& statObjList, const AABB& boundbox, const std::vector& indices, CKDTree::ESplitAxis splitAxis, float splitPos, SSplitInfo& outInfo) -{ - if (splitAxis != CKDTree::eSA_X && splitAxis != CKDTree::eSA_Y && splitAxis != CKDTree::eSA_Z) - { - return false; - } - - outInfo.aboveBoundbox = boundbox; - outInfo.belowBoundbox = boundbox; - - outInfo.aboveBoundbox.max[splitAxis] = splitPos; - outInfo.belowBoundbox.min[splitAxis] = splitPos; - - uint32 iIndexSize = (uint32)indices.size(); - outInfo.aboveIndices.reserve(iIndexSize); - outInfo.belowIndices.reserve(iIndexSize); - - for (uint32 i = 0; i < iIndexSize; ++i) - { - int nObjIndex = (indices[i] & 0xFF000000) >> 24; - int nVertexIndex = indices[i] & 0xFFFFFF; - - const CKDTree::SStatObj* pObj = &statObjList[nObjIndex]; - - const IIndexedMesh* pMesh = pObj->pStatObj->GetIndexedMesh(); - if (pMesh == nullptr) - { - return false; - } - - IIndexedMesh::SMeshDescription meshDesc; - pMesh->GetMeshDescription(meshDesc); - - Vec3 vPos; - if (meshDesc.m_pVerts) - { - vPos = pObj->tm.TransformPoint(meshDesc.m_pVerts[nVertexIndex]); - } - else if (meshDesc.m_pVertsF16) - { - vPos = pObj->tm.TransformPoint(meshDesc.m_pVertsF16[nVertexIndex].ToVec3()); - } - else - { - continue; - } - - if (vPos[splitAxis] < splitPos) - { - outInfo.aboveIndices.push_back(indices[i]); - assert(outInfo.aboveBoundbox.IsContainPoint(vPos)); - } - else - { - outInfo.belowIndices.push_back(indices[i]); - assert(outInfo.belowBoundbox.IsContainPoint(vPos)); - } - } - - return true; -} - -CKDTree::CKDTree() -{ - m_pRootNode = nullptr; -} - -CKDTree::~CKDTree() -{ - if (m_pRootNode) - { - delete m_pRootNode; - } -} - -bool CKDTree::Build(IStatObj* pStatObj) -{ - if (pStatObj == nullptr) - { - return false; - } - - m_StatObjectList.clear(); - - if (pStatObj->GetIndexedMesh(true)) - { - SStatObj rootObj; - rootObj.tm.SetIdentity(); - rootObj.pStatObj = pStatObj; - m_StatObjectList.push_back(rootObj); - } - - ConstructStatObjList(pStatObj, Matrix34::CreateIdentity()); - - AABB entireBoundBox; - entireBoundBox.Reset(); - - std::vector indices; - for (int i = 0, iStatObjSize = static_cast(m_StatObjectList.size()); i < iStatObjSize; ++i) - { - IIndexedMesh* pMesh = m_StatObjectList[i].pStatObj->GetIndexedMesh(true); - if (pMesh == nullptr) - { - continue; - } - - IIndexedMesh::SMeshDescription meshDesc; - pMesh->GetMeshDescription(meshDesc); - - for (int k = 0; k < meshDesc.m_nVertCount; ++k) - { - entireBoundBox.Add(m_StatObjectList[i].tm.TransformPoint(meshDesc.m_pVerts[k])); - indices.push_back((i << 24) | k); - } - } - - if (m_pRootNode) - { - delete m_pRootNode; - } - - m_pRootNode = new KDTreeNode; - BuildRecursively(m_pRootNode, entireBoundBox, indices); - - return true; -} - -void CKDTree::BuildRecursively(KDTreeNode* pNode, const AABB& boundbox, std::vector& indices) const -{ - pNode->SetBoundBox(boundbox); - - if (indices.size() <= s_MinimumVertexSizeInLeafNode) - { - pNode->SetVertexIndexBuffer(indices); - return; - } - - ESplitAxis splitAxis = SearchForBestSplitAxis(boundbox); - float splitPos(0); - SearchForBestSplitPos(splitAxis, m_StatObjectList, indices, splitPos); - pNode->SetSplitAxis(splitAxis); - pNode->SetSplitPos(splitPos); - - SSplitInfo splitInfo; - if (!SplitNode(m_StatObjectList, boundbox, indices, splitAxis, splitPos, splitInfo)) - { - return; - } - - if (splitInfo.aboveIndices.empty() || splitInfo.belowIndices.empty()) - { - pNode->SetVertexIndexBuffer(indices); - return; - } - - KDTreeNode* pChild0 = new KDTreeNode; - KDTreeNode* pChild1 = new KDTreeNode; - - pNode->SetChild(0, pChild0); - pNode->SetChild(1, pChild1); - - BuildRecursively(pChild0, splitInfo.aboveBoundbox, splitInfo.aboveIndices); - BuildRecursively(pChild1, splitInfo.belowBoundbox, splitInfo.belowIndices); -} - -void CKDTree::ConstructStatObjList(IStatObj* pStatObj, const Matrix34& matParent) -{ - if (pStatObj == nullptr) - { - return; - } - for (int i = 0, nChildObjSize(pStatObj->GetSubObjectCount()); i < nChildObjSize; ++i) - { - IStatObj::SSubObject* pSubObj = pStatObj->GetSubObject(i); - SStatObj s; - s.tm = matParent * pSubObj->localTM; - if (pSubObj->pStatObj && pSubObj->pStatObj->GetIndexedMesh(true)) - { - s.pStatObj = pSubObj->pStatObj; - m_StatObjectList.push_back(s); - } - ConstructStatObjList(pSubObj->pStatObj, s.tm); - } -} - -bool CKDTree::FindNearestVertex(const Vec3& raySrc, const Vec3& rayDir, float vVertexBoxSize, const Vec3& localCameraPos, Vec3& outPos, Vec3& vOutHitPosOnCube) const -{ - return FindNearestVertexRecursively(m_pRootNode, raySrc, rayDir, vVertexBoxSize, localCameraPos, outPos, vOutHitPosOnCube); -} - -AABB GetNodeBoundBox(KDTreeNode* pNode, float vVertexBoxSize, const Vec3& localCameraPos) -{ - AABB nodeAABB = pNode->GetBoundBox(); - float fScreenFactorMin = localCameraPos.GetDistance(nodeAABB.min); - Vec3 vBoundBoxMin(fScreenFactorMin * vVertexBoxSize, fScreenFactorMin * vVertexBoxSize, fScreenFactorMin * vVertexBoxSize); - float fScreenFactorMax = localCameraPos.GetDistance(nodeAABB.max); - Vec3 vBoundBoxMax(fScreenFactorMax * vVertexBoxSize, fScreenFactorMax * vVertexBoxSize, fScreenFactorMax * vVertexBoxSize); - nodeAABB.min -= vBoundBoxMin; - nodeAABB.max += vBoundBoxMax; - return nodeAABB; -} - -bool CKDTree::FindNearestVertexRecursively(KDTreeNode* pNode, const Vec3& raySrc, const Vec3& rayDir, float vVertexBoxSize, const Vec3& localCameraPos, Vec3& outPos, Vec3& vOutHitPosOnCube) const -{ - if (!pNode) - { - return false; - } - - Vec3 vHitPos; - AABB nodeAABB = GetNodeBoundBox(pNode, vVertexBoxSize, localCameraPos); - if (!pNode->GetBoundBox().IsContainPoint(raySrc) && !Intersect::Ray_AABB(raySrc, rayDir, nodeAABB, vHitPos)) - { - return false; - } - - if (pNode->IsLeaf()) - { - if (m_StatObjectList.empty()) - { - return false; - } - - uint32 nVBuffSize = pNode->GetVertexBufferSize(); - if (nVBuffSize == 0) - { - return false; - } - - float fNearestDist = 3e10f; - - for (uint32 i = 0; i < nVBuffSize; ++i) - { - uint32 nVertexIndex = pNode->GetVertexIndex(i); - uint32 nObjIndex = pNode->GetObjIndex(i); - - assert(nObjIndex < m_StatObjectList.size()); - - const SStatObj* pStatObjInfo = &(m_StatObjectList[nObjIndex]); - - IIndexedMesh* pMesh = m_StatObjectList[nObjIndex].pStatObj->GetIndexedMesh(); - if (pMesh == nullptr) - { - continue; - } - - IIndexedMesh::SMeshDescription meshDesc; - pMesh->GetMeshDescription(meshDesc); - - Vec3 vCandidatePos(0, 0, 0); - if (meshDesc.m_pVerts) - { - vCandidatePos = pStatObjInfo->tm.TransformPoint(meshDesc.m_pVerts[nVertexIndex]); - } - else if (meshDesc.m_pVertsF16) - { - vCandidatePos = pStatObjInfo->tm.TransformPoint(meshDesc.m_pVertsF16[nVertexIndex].ToVec3()); - } - else - { - continue; - } - - float fScreenFactor = localCameraPos.GetDistance(vCandidatePos); - Vec3 vBoundBox(fScreenFactor * vVertexBoxSize, fScreenFactor * vVertexBoxSize, fScreenFactor * vVertexBoxSize); - - Vec3 vHitPosOnCube; - if (Intersect::Ray_AABB(raySrc, rayDir, AABB(vCandidatePos - vBoundBox, vCandidatePos + vBoundBox), vHitPosOnCube)) - { - float fDist = vHitPosOnCube.GetDistance(raySrc); - if (fDist < fNearestDist) - { - fNearestDist = fDist; - outPos = vCandidatePos; - vOutHitPosOnCube = vHitPosOnCube; - } - } - } - - if (fNearestDist < 3e10f) - { - return true; - } - - return false; - } - - Vec3 vNearestPos0, vNearestPos0OnCube; - Vec3 vNearestPos1, vNearestPos1OnCube; - bool bFoundChild0 = FindNearestVertexRecursively(pNode->GetChild(0), raySrc, rayDir, vVertexBoxSize, localCameraPos, vNearestPos0, vNearestPos0OnCube); - bool bFoundChild1 = FindNearestVertexRecursively(pNode->GetChild(1), raySrc, rayDir, vVertexBoxSize, localCameraPos, vNearestPos1, vNearestPos1OnCube); - - if (bFoundChild0 && bFoundChild1) - { - float fDist0 = raySrc.GetDistance(vNearestPos0OnCube); - float fDist1 = raySrc.GetDistance(vNearestPos1OnCube); - if (fDist0 < fDist1) - { - outPos = vNearestPos0; - vOutHitPosOnCube = vNearestPos0OnCube; - } - else - { - outPos = vNearestPos1; - vOutHitPosOnCube = vNearestPos1OnCube; - } - } - else if (bFoundChild0 && !bFoundChild1) - { - outPos = vNearestPos0; - vOutHitPosOnCube = vNearestPos0OnCube; - } - else if (!bFoundChild0 && bFoundChild1) - { - outPos = vNearestPos1; - vOutHitPosOnCube = vNearestPos1OnCube; - } - - return bFoundChild0 || bFoundChild1; -} - -void CKDTree::GetPenetratedBoxes(const Vec3& raySrc, const Vec3& rayDir, std::vector& outBoxes) -{ - GetPenetratedBoxesRecursively(m_pRootNode, raySrc, rayDir, outBoxes); -} - -void CKDTree::GetPenetratedBoxesRecursively(KDTreeNode* pNode, const Vec3& raySrc, const Vec3& rayDir, std::vector& outBoxes) -{ - Vec3 vHitPos; - if (!pNode || (!pNode->GetBoundBox().IsContainPoint(raySrc) && !Intersect::Ray_AABB(raySrc, rayDir, pNode->GetBoundBox(), vHitPos))) - { - return; - } - - outBoxes.push_back(pNode->GetBoundBox()); - - GetPenetratedBoxesRecursively(pNode->GetChild(0), raySrc, rayDir, outBoxes); - GetPenetratedBoxesRecursively(pNode->GetChild(1), raySrc, rayDir, outBoxes); -} diff --git a/Code/Editor/Util/KDTree.h b/Code/Editor/Util/KDTree.h deleted file mode 100644 index df25a9ded7..0000000000 --- a/Code/Editor/Util/KDTree.h +++ /dev/null @@ -1,57 +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 - * - */ - - -#ifndef CRYINCLUDE_EDITOR_UTIL_KDTREE_H -#define CRYINCLUDE_EDITOR_UTIL_KDTREE_H -#pragma once - -struct IStatObj; - -class KDTreeNode; - -class CKDTree -{ -public: - - CKDTree(); - ~CKDTree(); - - bool Build(IStatObj* pStatObj); - bool FindNearestVertex(const Vec3& raySrc, const Vec3& rayDir, float vVertexBoxSize, const Vec3& localCameraPos, Vec3& outPos, Vec3& vOutHitPosOnCube) const; - void GetPenetratedBoxes(const Vec3& raySrc, const Vec3& rayDir, std::vector& outBoxes); - - enum ESplitAxis - { - eSA_X = 0, - eSA_Y, - eSA_Z, - eSA_Invalid - }; - - struct SStatObj - { - Matrix34 tm; - IStatObj* pStatObj; - }; - -private: - - void BuildRecursively(KDTreeNode* pNode, const AABB& boundbox, std::vector& indices) const; - bool FindNearestVertexRecursively(KDTreeNode* pNode, const Vec3& raySrc, const Vec3& rayDir, float vVertexBoxSize, const Vec3& localCameraPos, Vec3& outPos, Vec3& vOutHitPosOnCube) const; - void GetPenetratedBoxesRecursively(KDTreeNode* pNode, const Vec3& raySrc, const Vec3& rayDir, std::vector& outBoxes); - void ConstructStatObjList(IStatObj* pStatObj, const Matrix34& matParent); - - static const int s_MinimumVertexSizeInLeafNode = 4; - -private: - - KDTreeNode* m_pRootNode; - std::vector m_StatObjectList; -}; -#endif // CRYINCLUDE_EDITOR_UTIL_KDTREE_H diff --git a/Code/Editor/Util/XmlArchive.cpp b/Code/Editor/Util/XmlArchive.cpp index 3a965fb239..72999efdf4 100644 --- a/Code/Editor/Util/XmlArchive.cpp +++ b/Code/Editor/Util/XmlArchive.cpp @@ -16,102 +16,6 @@ ////////////////////////////////////////////////////////////////////////// // CXmlArchive -bool CXmlArchive::Load(const QString& file) -{ - bLoading = true; - - char filename[AZ_MAX_PATH_LEN] = { 0 }; - AZ::IO::FileIOBase::GetInstance()->ResolvePath(file.toUtf8().data(), filename, AZ_MAX_PATH_LEN); - - QFile cFile(filename); - if (!cFile.open(QFile::ReadOnly)) - { - CLogFile::FormatLine("Warning: Loading of %s failed", filename); - return false; - } - CArchive ar(&cFile, CArchive::load); - - QString str; - ar >> str; - - root = XmlHelpers::LoadXmlFromBuffer(str.toUtf8().data(), str.toUtf8().length()); - if (!root) - { - // If we didn't extract valid XML, attempt to check the header to see if we're dealing with an improperly serialized archive - // When deserializing QStrings, we use readStringLength in EditorUtils, which mimics MFC's decoding. - // In this encoding, the length is first read as an unsigned 8-bit value, if that is 0xFF then the next two bytes are read - // If the 16 bit uint is 0xFFFF, then the next four bytes are read, etc. up to a final 64 bit value. - - // In 1.09, there was a bug in which we'd serialize out the 32-bit length improperly like so: - // 0xFF 0xFF 0x00 <4 byte proper length> - - // Note that the header could also historically start with 0xFF 0xFF 0xFE to indicate wide strings prior to the length data - // but we don't have to deal with that here as the broken version of the code never prepended this - cFile.seek(0); - quint8 len8; - ar >> len8; - - quint16 len16; - ar >> len16; - - // Possible bad header, attempt to read 32 bit length. - if (len8 == 0xff && len16 == 0xff) - { - // This version of operator<< only serialized out UTF8 strings up to 32 bits of length, no need to 64-bit check or do wchar. - quint32 len32; - ar >> len32; - - str = QString::fromUtf8(cFile.read(len32)); - root = XmlHelpers::LoadXmlFromBuffer(str.toUtf8().data(), str.toUtf8().length()); - } - - if (!root) - { - CLogFile::FormatLine("Warning: Loading of %s failed", filename); - return false; - } - } - - const bool loaded = pNamedData->Serialize(ar); - if (!loaded) - { - CLogFile::FormatLine("Error: Can't load xml file: '%s'! File corrupted. Binary file possibly was corrupted by Source Control if it was marked like text format.", filename); - return false; - } - - return true; -} - -////////////////////////////////////////////////////////////////////////// -void CXmlArchive::Save(const QString& file) -{ - char filename[AZ_MAX_PATH_LEN] = { 0 }; - AZ::IO::FileIOBase::GetInstance()->ResolvePath(file.toUtf8().data(), filename, AZ_MAX_PATH_LEN); - - bLoading = false; - if (!root) - { - return; - } - - QFile cFile(filename); - // Open the file for writing, create it if needed - if (!cFile.open(QFile::WriteOnly)) - { - CLogFile::FormatLine("Warning: Saving of %s failed", filename); - return; - } - // Create the archive object - CArchive ar(&cFile, CArchive::store); - - _smart_ptr pXmlStrData = root->getXMLData(5000000); - - // Need convert to QString for CArchive::operator<< - QString str = pXmlStrData->GetString(); - ar << str; - - pNamedData->Serialize(ar); -} ////////////////////////////////////////////////////////////////////////// bool CXmlArchive::SaveToPak([[maybe_unused]] const QString& levelPath, CPakFile& pakFile) diff --git a/Code/Editor/Util/XmlArchive.h b/Code/Editor/Util/XmlArchive.h index 3d45a7a2e6..e7de4c5be8 100644 --- a/Code/Editor/Util/XmlArchive.h +++ b/Code/Editor/Util/XmlArchive.h @@ -60,9 +60,6 @@ public: return *this; } - bool Load(const QString& file); - void Save(const QString& file); - //! Save XML Archive to pak file. //! @return true if saved. bool SaveToPak(const QString& levelPath, CPakFile& pakFile); diff --git a/Code/Editor/Viewport.cpp b/Code/Editor/Viewport.cpp index 77be6e260e..437dcead3b 100644 --- a/Code/Editor/Viewport.cpp +++ b/Code/Editor/Viewport.cpp @@ -14,14 +14,11 @@ // Qt #include -// AzCore -#include - // AzQtComponents #include +// AzToolsFramework #include -#include #include #include @@ -41,19 +38,6 @@ #undef LoadCursor #endif -AZ_CVAR( - float, - ed_defaultEntityPlacementDistance, - 10.0f, - nullptr, - AZ::ConsoleFunctorFlags::Null, - "The default distance to place an entity from the camera if no intersection is found"); - -float GetDefaultEntityPlacementDistance() -{ - return ed_defaultEntityPlacementDistance; -} - ////////////////////////////////////////////////////////////////////// // Viewport drag and drop support ////////////////////////////////////////////////////////////////////// @@ -63,7 +47,7 @@ void QtViewport::BuildDragDropContext( { context.m_hitLocation = AzToolsFramework::FindClosestPickIntersection( viewportId, AzToolsFramework::ViewportInteraction::ScreenPointFromQPoint(point), AzToolsFramework::EditorPickRayLength, - GetDefaultEntityPlacementDistance()); + AzToolsFramework::GetDefaultEntityPlacementDistance()); } void QtViewport::dragEnterEvent(QDragEnterEvent* event) @@ -1062,11 +1046,6 @@ Vec3 QtViewport::SnapToGrid(const Vec3& vec) return vec; } -float QtViewport::GetGridStep() const -{ - return 0.0f; -} - ////////////////////////////////////////////////////////////////////////// void QtViewport::BeginUndo() { @@ -1121,30 +1100,6 @@ bool QtViewport::IsBoundsVisible([[maybe_unused]] const AABB& box) const return true; } -////////////////////////////////////////////////////////////////////////// -bool QtViewport::HitTestLine(const Vec3& lineP1, const Vec3& lineP2, const QPoint& hitpoint, int pixelRadius, float* pToCameraDistance) const -{ - float dist = GetDistanceToLine(lineP1, lineP2, hitpoint); - if (dist <= pixelRadius) - { - if (pToCameraDistance) - { - Vec3 raySrc, rayDir; - ViewToWorldRay(hitpoint, raySrc, rayDir); - Vec3 rayTrg = raySrc + rayDir * 10000.0f; - - Vec3 pa, pb; - float mua, mub; - LineLineIntersect(lineP1, lineP2, raySrc, rayTrg, pa, pb, mua, mub); - *pToCameraDistance = mub; - } - - return true; - } - - return false; -} - ////////////////////////////////////////////////////////////////////////// float QtViewport::GetDistanceToLine(const Vec3& lineP1, const Vec3& lineP2, const QPoint& point) const { @@ -1223,6 +1178,69 @@ void QtViewport::ProcessRenderLisneters(DisplayContext& rstDisplayContext) } ////////////////////////////////////////////////////////////////////////// #if defined(AZ_PLATFORM_WINDOWS) +// Note: Both CreateAnglesYPR and CreateOrientationYPR were copied verbatim from Cry_Camera.h which has been removed. +// +// Description +//
+//   x-YAW
+//   y-PITCH (negative=looking down / positive=looking up)
+//   z-ROLL
+//   
+// Note: If we are looking along the z-axis, its not possible to specify the x and z-angle +inline Ang3 CreateAnglesYPR(const Matrix33& m) +{ + assert(m.IsOrthonormal()); + float l = Vec3(m.m01, m.m11, 0.0f).GetLength(); + if (l > 0.0001) + { + return Ang3(atan2f(-m.m01 / l, m.m11 / l), atan2f(m.m21, l), atan2f(-m.m20 / l, m.m22 / l)); + } + else + { + return Ang3(0, atan2f(m.m21, l), 0); + } +} + +// Description +// This function builds a 3x3 orientation matrix using YPR-angles +// Rotation order for the orientation-matrix is Z-X-Y. (Zaxis=YAW / Xaxis=PITCH / Yaxis=ROLL) +// +//
+//  COORDINATE-SYSTEM
+//
+//  z-axis
+//    ^
+//    |
+//    |  y-axis
+//    |  /
+//    | /
+//    |/
+//    +--------------->   x-axis
+// 
+// +// Example: +// Matrix33 orientation=CreateOrientationYPR( Ang3(1,2,3) ); +inline Matrix33 CreateOrientationYPR(const Ang3& ypr) +{ + f32 sz, cz; + sincos_tpl(ypr.x, &sz, &cz); //Zaxis = YAW + f32 sx, cx; + sincos_tpl(ypr.y, &sx, &cx); //Xaxis = PITCH + f32 sy, cy; + sincos_tpl(ypr.z, &sy, &cy); //Yaxis = ROLL + Matrix33 c; + c.m00 = cy * cz - sy * sz * sx; + c.m01 = -sz * cx; + c.m02 = sy * cz + cy * sz * sx; + c.m10 = cy * sz + sy * sx * cz; + c.m11 = cz * cx; + c.m12 = sy * sz - cy * sx * cz; + c.m20 = -sy * cx; + c.m21 = sx; + c.m22 = cy * cx; + return c; +} + void QtViewport::OnRawInput([[maybe_unused]] UINT wParam, HRAWINPUT lParam) { static C3DConnexionDriver* p3DConnexionDriver = 0; @@ -1266,12 +1284,12 @@ void QtViewport::OnRawInput([[maybe_unused]] UINT wParam, HRAWINPUT lParam) t *= sys_scale3DMouseTranslation->GetFVal(); float as = 0.001f * gSettings.cameraMoveSpeed; - Ang3 ypr = CCamera::CreateAnglesYPR(Matrix33(viewTM)); + Ang3 ypr = CreateAnglesYPR(Matrix33(viewTM)); ypr.x += -all6DOFs[5] * as * fScaleYPR; ypr.y = AZStd::clamp(ypr.y + all6DOFs[3] * as * fScaleYPR, -1.5f, 1.5f); // to keep rotation in reasonable range ypr.z = 0; // to have camera always upward - viewTM = Matrix34(CCamera::CreateOrientationYPR(ypr), viewTM.GetTranslation()); + viewTM = Matrix34(CreateOrientationYPR(ypr), viewTM.GetTranslation()); viewTM = viewTM * Matrix34::CreateTranslationMat(t); SetViewTM(viewTM); @@ -1286,6 +1304,7 @@ float QtViewport::GetFOV() const { return gSettings.viewports.fDefaultFov; } + ////////////////////////////////////////////////////////////////////////// void QtViewport::setRay(QPoint& vp, Vec3& raySrc, Vec3& rayDir) { @@ -1293,12 +1312,5 @@ void QtViewport::setRay(QPoint& vp, Vec3& raySrc, Vec3& rayDir) m_raySrc = raySrc; m_rayDir = rayDir; } -//////////////////////////////////////////////////////////////////////// -void QtViewport::setHitcontext(QPoint& vp, Vec3& raySrc, Vec3& rayDir) -{ - vp = m_vp; - raySrc = m_raySrc; - rayDir = m_rayDir; -} #include diff --git a/Code/Editor/Viewport.h b/Code/Editor/Viewport.h index bf44b914aa..a71df6a9cd 100644 --- a/Code/Editor/Viewport.h +++ b/Code/Editor/Viewport.h @@ -87,9 +87,6 @@ enum EStdCursor STD_CURSOR_LAST, }; -//! The default distance an entity is placed from the camera if there is no intersection -SANDBOX_API float GetDefaultEntityPlacementDistance(); - AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING class SANDBOX_API CViewport : public IDisplayViewport @@ -397,7 +394,6 @@ public: //! Snap any given 3D world position to grid lines if snap is enabled. Vec3 SnapToGrid(const Vec3& vec) override; - float GetGridStep() const override; //! Returns the screen scale factor for a point given in world coordinates. //! This factor gives the width in world-space units at the point's distance of the viewport. @@ -434,19 +430,12 @@ public: //! Performs hit testing of 2d point in view to find which object hit. bool HitTest(const QPoint& point, HitContext& hitInfo) override; - //! Do 2D hit testing of line in world space. - // pToCameraDistance is an optional output parameter in which distance from the camera to the line is returned. - bool HitTestLine(const Vec3& lineP1, const Vec3& lineP2, const QPoint& hitpoint, int pixelRadius, float* pToCameraDistance = 0) const override; - float GetDistanceToLine(const Vec3& lineP1, const Vec3& lineP2, const QPoint& point) const override; // Access to the member m_bAdvancedSelectMode so interested modules can know its value. bool GetAdvancedSelectModeFlag() override; void GetPerpendicularAxis(EAxis* pAxis, bool* pIs2D) const override; - const ::Plane* GetConstructionPlane() const override { return &m_constructionPlane; } - - ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// //! Set construction plane from given position construction matrix refrence coord system and axis settings. @@ -502,7 +491,7 @@ public: void ReleaseMouse() override { m_mouseCaptured = false; QWidget::releaseMouse(); } void setRay(QPoint& vp, Vec3& raySrc, Vec3& rayDir) override; - void setHitcontext(QPoint& vp, Vec3& raySrc, Vec3& rayDir) override; + QPoint m_vp; AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING Vec3 m_raySrc; diff --git a/Code/Editor/ViewportTitleDlg.cpp b/Code/Editor/ViewportTitleDlg.cpp index 75d16e9a40..49459fad0f 100644 --- a/Code/Editor/ViewportTitleDlg.cpp +++ b/Code/Editor/ViewportTitleDlg.cpp @@ -138,14 +138,11 @@ CViewportTitleDlg::CViewportTitleDlg(QWidget* pParent) connect(this, &CViewportTitleDlg::ActionTriggered, MainWindow::instance()->GetActionManager(), &ActionManager::ActionTriggered); - AZ::VR::VREventBus::Handler::BusConnect(); - OnInitDialog(); } CViewportTitleDlg::~CViewportTitleDlg() { - AZ::VR::VREventBus::Handler::BusDisconnect(); GetISystem()->GetISystemEventDispatcher()->RemoveListener(this); GetIEditor()->UnregisterNotifyListener(this); @@ -236,10 +233,6 @@ void CViewportTitleDlg::SetupOverflowMenu() connect(m_audioMuteAction, &QAction::triggered, this, &CViewportTitleDlg::OnBnClickedMuteAudio); overFlowMenu->addAction(m_audioMuteAction); - m_enableVRAction = new QAction("Enable VR Preview", overFlowMenu); - connect(m_enableVRAction, &QAction::triggered, this, &CViewportTitleDlg::OnBnClickedEnableVR); - overFlowMenu->addAction(m_enableVRAction); - overFlowMenu->addSeparator(); m_enableGridSnappingAction = new QAction("Enable Grid Snapping", overFlowMenu); @@ -305,16 +298,6 @@ void CViewportTitleDlg::OnInitDialog() connect(displayInfoHelper, &CViewportTitleDlgDisplayInfoHelper::ViewportInfoStatusUpdated, this, &CViewportTitleDlg::UpdateDisplayInfo); UpdateDisplayInfo(); - // This is here just in case this class hasn't been created before - // a VR headset was initialized - m_enableVRAction->setEnabled(false); - if (AZ::VR::HMDDeviceRequestBus::GetTotalNumOfEventHandlers() != 0) - { - m_enableVRAction->setEnabled(true); - } - - AZ::VR::VREventBus::Handler::BusConnect(); - QFontMetrics metrics({}); int width = static_cast(metrics.boundingRect("-9999.99").width() * m_fieldWidthMultiplier); @@ -931,23 +914,6 @@ void CViewportTitleDlg::UpdateMuteActionText() } } -void CViewportTitleDlg::OnHMDInitialized() -{ - m_enableVRAction->setEnabled(true); -} - -void CViewportTitleDlg::OnHMDShutdown() -{ - m_enableVRAction->setEnabled(false); -} - -void CViewportTitleDlg::OnBnClickedEnableVR() -{ - gSettings.bEnableGameModeVR = !gSettings.bEnableGameModeVR; - - m_enableVRAction->setText(gSettings.bEnableGameModeVR ? tr("Disable VR Preview") : tr("Enable VR Preview")); -} - inline double Round(double fVal, double fStep) { if (fStep > 0.f) diff --git a/Code/Editor/ViewportTitleDlg.h b/Code/Editor/ViewportTitleDlg.h index 6996fe7750..a3e19837b4 100644 --- a/Code/Editor/ViewportTitleDlg.h +++ b/Code/Editor/ViewportTitleDlg.h @@ -22,7 +22,6 @@ #include #include -#include #endif // CViewportTitleDlg dialog @@ -44,7 +43,6 @@ class CViewportTitleDlg : public QWidget , public IEditorNotifyListener , public ISystemEventListener - , public AZ::VR::VREventBus::Handler { Q_OBJECT public: @@ -85,13 +83,6 @@ protected: void OnToggleHelpers(); void UpdateDisplayInfo(); - ////////////////////////////////////////////////////////////////////////// - /// VR Event Bus Implementation - ////////////////////////////////////////////////////////////////////////// - void OnHMDInitialized() override; - void OnHMDShutdown() override; - ////////////////////////////////////////////////////////////////////////// - void SetupCameraDropdownMenu(); void SetupResolutionDropdownMenu(); void SetupViewportInformationMenu(); @@ -140,7 +131,6 @@ protected: void OnBnClickedGotoPosition(); void OnBnClickedMuteAudio(); - void OnBnClickedEnableVR(); void UpdateMuteActionText(); @@ -168,7 +158,6 @@ protected: QAction* m_fullInformationAction = nullptr; QAction* m_compactInformationAction = nullptr; QAction* m_audioMuteAction = nullptr; - QAction* m_enableVRAction = nullptr; QAction* m_enableGridSnappingAction = nullptr; QAction* m_enableAngleSnappingAction = nullptr; QComboBox* m_cameraSpeed = nullptr; diff --git a/Code/Editor/editor_lib_files.cmake b/Code/Editor/editor_lib_files.cmake index 47b69765ba..fa627176d8 100644 --- a/Code/Editor/editor_lib_files.cmake +++ b/Code/Editor/editor_lib_files.cmake @@ -488,7 +488,6 @@ set(FILES UndoViewPosition.h UndoViewRotation.h Util/GeometryUtil.h - Util/KDTree.h WipFeaturesDlg.h WipFeaturesDlg.ui WipFeaturesDlg.qrc @@ -499,7 +498,6 @@ set(FILES Objects/ClassDesc.cpp Objects/ClassDesc.h Objects/DisplayContextShared.inl - Objects/IEntityObjectListener.h Objects/SelectionGroup.cpp Objects/SelectionGroup.h Objects/SubObjSelection.cpp @@ -710,7 +708,6 @@ set(FILES Util/GuidUtil.cpp Util/GuidUtil.h Util/IObservable.h - Util/KDTree.cpp Util/Mailer.h Util/NamedData.cpp Util/NamedData.h diff --git a/Code/Editor/editor_lib_test_files.cmake b/Code/Editor/editor_lib_test_files.cmake index 2ae3d22c19..17f36228db 100644 --- a/Code/Editor/editor_lib_test_files.cmake +++ b/Code/Editor/editor_lib_test_files.cmake @@ -22,6 +22,7 @@ set(FILES Lib/Tests/test_DisplaySettingsPythonBindings.cpp Lib/Tests/test_ViewportManipulatorController.cpp Lib/Tests/test_ModularViewportCameraController.cpp + Lib/Tests/Camera/test_EditorCamera.cpp DisplaySettingsPythonFuncs.cpp DisplaySettingsPythonFuncs.h ) diff --git a/Code/Framework/AtomCore/Tests/Main.cpp b/Code/Framework/AtomCore/Tests/Main.cpp index 29ef408551..728624d427 100644 --- a/Code/Framework/AtomCore/Tests/Main.cpp +++ b/Code/Framework/AtomCore/Tests/Main.cpp @@ -7,7 +7,6 @@ */ -#include #include #include #include @@ -38,7 +37,7 @@ namespace AZ using namespace AZ; // Handle asserts -class TraceDrillerHook +class TestEnvironmentHook : public AZ::Test::ITestEnvironment , public UnitTest::TraceBusRedirector { @@ -58,5 +57,5 @@ public: } }; -AZ_UNIT_TEST_HOOK(new TraceDrillerHook()); +AZ_UNIT_TEST_HOOK(new TestEnvironmentHook()); diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetCommon.cpp b/Code/Framework/AzCore/AzCore/Asset/AssetCommon.cpp index fdc3053b5c..bf3ad20768 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetCommon.cpp +++ b/Code/Framework/AzCore/AzCore/Asset/AssetCommon.cpp @@ -14,453 +14,450 @@ #include #include -namespace AZ +namespace AZ::Data { - namespace Data + AssetFilterInfo::AssetFilterInfo(const AssetId& id, const AssetType& assetType, AssetLoadBehavior loadBehavior) + : m_assetId(id) + , m_assetType(assetType) + , m_loadBehavior(loadBehavior) { - AssetFilterInfo::AssetFilterInfo(const AssetId& id, const AssetType& assetType, AssetLoadBehavior loadBehavior) - : m_assetId(id) - , m_assetType(assetType) - , m_loadBehavior(loadBehavior) + } + + AssetFilterInfo::AssetFilterInfo(const Asset& asset) + : m_assetId(asset.GetId()) + , m_assetType(asset.GetType()) + , m_loadBehavior(asset.GetAutoLoadBehavior()) + { + } + + + AssetId AssetId::CreateString(AZStd::string_view input) + { + size_t separatorIdx = input.find(':'); + if (separatorIdx == AZStd::string_view::npos) { + return AssetId(); } - AssetFilterInfo::AssetFilterInfo(const Asset& asset) - : m_assetId(asset.GetId()) - , m_assetType(asset.GetType()) - , m_loadBehavior(asset.GetAutoLoadBehavior()) + AssetId assetId; + assetId.m_guid = Uuid::CreateString(input.data(), separatorIdx); + if (assetId.m_guid.IsNull()) { + return AssetId(); } + assetId.m_subId = strtoul(&input[separatorIdx + 1], nullptr, 16); - AssetId AssetId::CreateString(AZStd::string_view input) + return assetId; + } + + void AssetId::Reflect(AZ::ReflectContext* context) + { + if (SerializeContext* serializeContext = azrtti_cast(context)) { - size_t separatorIdx = input.find(':'); - if (separatorIdx == AZStd::string_view::npos) - { - return AssetId(); - } - - AssetId assetId; - assetId.m_guid = Uuid::CreateString(input.data(), separatorIdx); - if (assetId.m_guid.IsNull()) - { - return AssetId(); - } - - assetId.m_subId = strtoul(&input[separatorIdx + 1], nullptr, 16); - - return assetId; + serializeContext->Class() + ->Version(1) + ->Field("guid", &Data::AssetId::m_guid) + ->Field("subId", &Data::AssetId::m_subId) + ; } - void AssetId::Reflect(AZ::ReflectContext* context) + if (BehaviorContext* behaviorContext = azrtti_cast(context)) { - if (SerializeContext* serializeContext = azrtti_cast(context)) - { - serializeContext->Class() - ->Version(1) - ->Field("guid", &Data::AssetId::m_guid) - ->Field("subId", &Data::AssetId::m_subId) - ; - } + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Category, "Asset") + ->Attribute(AZ::Script::Attributes::Module, "asset") + ->Constructor() + ->Constructor() + ->Method("CreateString", &Data::AssetId::CreateString) + ->Method("IsValid", &Data::AssetId::IsValid) + ->Attribute(AZ::Script::Attributes::Alias, "is_valid") + ->Method("ToString", [](const Data::AssetId* self) { return self->ToString(); }) + ->Attribute(AZ::Script::Attributes::Alias, "to_string") + ->Method("IsEqual", [](const Data::AssetId& self, const Data::AssetId& other) { return self == other; }) + ->Attribute(AZ::Script::Attributes::Alias, "is_equal") + ->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Equal) + ; - if (BehaviorContext* behaviorContext = azrtti_cast(context)) - { - behaviorContext->Class() - ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) - ->Attribute(AZ::Script::Attributes::Category, "Asset") - ->Attribute(AZ::Script::Attributes::Module, "asset") - ->Constructor() - ->Constructor() - ->Method("CreateString", &Data::AssetId::CreateString) - ->Method("IsValid", &Data::AssetId::IsValid) - ->Attribute(AZ::Script::Attributes::Alias, "is_valid") - ->Method("ToString", [](const Data::AssetId* self) { return self->ToString(); }) - ->Attribute(AZ::Script::Attributes::Alias, "to_string") - ->Method("IsEqual", [](const Data::AssetId& self, const Data::AssetId& other) { return self == other; }) - ->Attribute(AZ::Script::Attributes::Alias, "is_equal") - ->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Equal) - ; + behaviorContext->Class() + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Category, "Asset") + ->Attribute(AZ::Script::Attributes::Module, "asset") + ->Property("assetId", BehaviorValueGetter(&Data::AssetInfo::m_assetId), nullptr) + ->Property("assetType", BehaviorValueGetter(&Data::AssetInfo::m_assetType), nullptr) + ->Property("sizeBytes", BehaviorValueGetter(&Data::AssetInfo::m_sizeBytes), nullptr) + ->Property("relativePath", BehaviorValueGetter(&Data::AssetInfo::m_relativePath), nullptr) + ; + } + } - behaviorContext->Class() - ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) - ->Attribute(AZ::Script::Attributes::Category, "Asset") - ->Attribute(AZ::Script::Attributes::Module, "asset") - ->Property("assetId", BehaviorValueGetter(&Data::AssetInfo::m_assetId), nullptr) - ->Property("assetType", BehaviorValueGetter(&Data::AssetInfo::m_assetType), nullptr) - ->Property("sizeBytes", BehaviorValueGetter(&Data::AssetInfo::m_sizeBytes), nullptr) - ->Property("relativePath", BehaviorValueGetter(&Data::AssetInfo::m_relativePath), nullptr) - ; - } + namespace AssetInternal + { + Asset FindOrCreateAsset(const AssetId& id, const AssetType& type, AssetLoadBehavior assetReferenceLoadBehavior) + { + return AssetManager::Instance().FindOrCreateAsset(id, type, assetReferenceLoadBehavior); } - namespace AssetInternal + Asset GetAsset(const AssetId& id, const AssetType& type, AssetLoadBehavior assetReferenceLoadBehavior, + const AssetLoadParameters& loadParams) { - Asset FindOrCreateAsset(const AssetId& id, const AssetType& type, AssetLoadBehavior assetReferenceLoadBehavior) - { - return AssetManager::Instance().FindOrCreateAsset(id, type, assetReferenceLoadBehavior); - } - - Asset GetAsset(const AssetId& id, const AssetType& type, AssetLoadBehavior assetReferenceLoadBehavior, - const AssetLoadParameters& loadParams) - { - return AssetManager::Instance().GetAsset(id, type, assetReferenceLoadBehavior, loadParams); - } - - AssetData::AssetStatus BlockUntilLoadComplete(const Asset& asset) - { - return AssetManager::Instance().BlockUntilLoadComplete(asset); - } - - void UpdateAssetInfo(AssetId& id, AZStd::string& assetHint) - { - // it is possible that the assetID given is legacy / old and we have a new assetId we can use instead for it. - // in that case, upgrade the AssetID to the new one, so that future saves are in the new format. - // this function should only be invoked if the feature is turned on in the asset manager as it can be (slightly) expensive - - if ((!AssetManager::IsReady()) || (!AssetManager::Instance().GetAssetInfoUpgradingEnabled())) - { - return; - } - - AZ::Data::AssetInfo assetInfo; - AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, id); - if (assetInfo.m_assetId.IsValid()) - { - id = assetInfo.m_assetId; - if (!assetInfo.m_relativePath.empty()) - { - assetHint = assetInfo.m_relativePath; - } - } - } - - bool ReloadAsset(AssetData* assetData, AssetLoadBehavior assetReferenceLoadBehavior) - { - AssetManager::Instance().ReloadAsset(assetData->GetId(), assetReferenceLoadBehavior); - return true; - } - - bool SaveAsset(AssetData* assetData, AssetLoadBehavior assetReferenceLoadBehavior) - { - AssetManager::Instance().SaveAsset({ assetData, assetReferenceLoadBehavior }); - return true; - } - - Asset GetAssetData(const AssetId& id, AssetLoadBehavior assetReferenceLoadBehavior) - { - if (AssetManager::IsReady()) - { - AZStd::lock_guard assetLock(AssetManager::Instance().m_assetMutex); - auto it = AssetManager::Instance().m_assets.find(id); - if (it != AssetManager::Instance().m_assets.end()) - { - return { it->second, assetReferenceLoadBehavior }; - } - } - return {}; - } - - AssetId ResolveAssetId(const AssetId& id) - { - AZ::Data::AssetInfo assetInfo; - AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, id); - if (assetInfo.m_assetId.IsValid()) - { - return assetInfo.m_assetId; - } - else - { - return id; - } - - } + return AssetManager::Instance().GetAsset(id, type, assetReferenceLoadBehavior, loadParams); } - AssetData::~AssetData() + AssetData::AssetStatus BlockUntilLoadComplete(const Asset& asset) { - UnregisterWithHandler(); + return AssetManager::Instance().BlockUntilLoadComplete(asset); } - void AssetData::Reflect(AZ::ReflectContext* context) + void UpdateAssetInfo(AssetId& id, AZStd::string& assetHint) { - if (SerializeContext* serializeContext = azrtti_cast(context)) + // it is possible that the assetID given is legacy / old and we have a new assetId we can use instead for it. + // in that case, upgrade the AssetID to the new one, so that future saves are in the new format. + // this function should only be invoked if the feature is turned on in the asset manager as it can be (slightly) expensive + + if ((!AssetManager::IsReady()) || (!AssetManager::Instance().GetAssetInfoUpgradingEnabled())) { - serializeContext->Class() - ->Version(1) - ; - } - - if (BehaviorContext* behaviorContext = azrtti_cast(context)) - { - behaviorContext->Class("AssetData") - ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) - ->Attribute(AZ::Script::Attributes::Category, "Asset") - ->Attribute(AZ::Script::Attributes::Module, "asset") - ->Method("IsReady", &AssetData::IsReady) - ->Attribute(AZ::Script::Attributes::Alias, "is_ready") - ->Method("IsError", &AssetData::IsError) - ->Attribute(AZ::Script::Attributes::Alias, "is_error") - ->Method("IsLoading", &AssetData::IsLoading) - ->Attribute(AZ::Script::Attributes::Alias, "is_loading") - ->Method("GetId", &AssetData::GetId) - ->Attribute(AZ::Script::Attributes::Alias, "get_id") - ->Method("GetUseCount", &AssetData::GetUseCount) - ->Attribute(AZ::Script::Attributes::Alias, "get_use_count") - ; - } - } - - void AssetData::Acquire() - { - AZ_Assert(m_useCount >= 0, "AssetData has been deleted"); - - AcquireWeak(); - ++m_useCount; - } - - void AssetData::Release() - { - AZ_Assert(m_useCount > 0, "Usecount is already 0!"); - - if (m_useCount.fetch_sub(1) == 1) - { - if (AssetManager::IsReady()) - { - AssetManager::Instance().OnAssetUnused(this); - } - else - { - AZ_Assert(false, "Attempting to release asset after AssetManager has been destroyed!"); - } - } - - ReleaseWeak(); - } - - void AssetData::AcquireWeak() - { - AZ_Assert(m_useCount >= 0, "AssetData has been deleted"); - ++m_weakUseCount; - } - - void AssetData::ReleaseWeak() - { - AZ_Assert(m_weakUseCount > 0, "WeakUseCount is already 0"); - - AssetId assetId = m_assetId; - int creationToken = m_creationToken; - AssetType assetType = GetType(); - bool removeFromHash = IsRegisterReadonlyAndShareable(); - // default creation token implies that the asset was not created by the asset manager and therefore it cannot be in the asset map. - removeFromHash = creationToken == s_defaultCreationToken ? false : removeFromHash; - - if (m_weakUseCount.fetch_sub(1) == 1) - { - if (AssetManager::IsReady()) - { - AssetManager::Instance().ReleaseAsset(this, assetId, assetType, removeFromHash, creationToken); - } - else - { - AZ_Assert(false, "Attempting to release asset after AssetManager has been destroyed!"); - } - } - } - - bool AssetData::IsLoading(bool includeQueued) const - { - auto curStatus = GetStatus(); - return(curStatus == AssetStatus::Loading || curStatus == AssetStatus::LoadedPreReady || curStatus==AssetStatus::StreamReady || - (includeQueued && curStatus == AssetStatus::Queued)); - } - - void AssetData::RegisterWithHandler(AssetHandler* handler) - { - if (!handler) - { - AZ_Error("AssetData", false, "No handler to register with"); return; } - m_registeredHandler = handler; - } - void AssetData::UnregisterWithHandler() - { - if (m_registeredHandler) + AZ::Data::AssetInfo assetInfo; + AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, id); + if (assetInfo.m_assetId.IsValid()) { - m_registeredHandler = nullptr; - } - } - - bool AssetData::GetFlag(const AssetDataFlags& checkFlag) const - { - return m_flags[aznumeric_cast(checkFlag)]; - } - - void AssetData::SetFlag(const AssetDataFlags& checkFlag, bool setValue) - { - m_flags.set(aznumeric_cast(checkFlag), setValue); - } - - bool AssetData::GetRequeue() const - { - return GetFlag(AssetDataFlags::Requeue); - } - void AssetData::SetRequeue(bool requeue) - { - SetFlag(AssetDataFlags::Requeue, requeue); - } - - void AssetBusCallbacks::SetCallbacks(const AssetReadyCB& readyCB, const AssetMovedCB& movedCB, const AssetReloadedCB& reloadedCB, - const AssetSavedCB& savedCB, const AssetUnloadedCB& unloadedCB, const AssetErrorCB& errorCB, const AssetCanceledCB& cancelCB) - { - m_onAssetReadyCB = readyCB; - m_onAssetMovedCB = movedCB; - m_onAssetReloadedCB = reloadedCB; - m_onAssetSavedCB = savedCB; - m_onAssetUnloadedCB = unloadedCB; - m_onAssetErrorCB = errorCB; - m_onAssetCanceledCB = cancelCB; - } - - void AssetBusCallbacks::ClearCallbacks() - { - SetCallbacks(AssetBusCallbacks::AssetReadyCB(), - AssetBusCallbacks::AssetMovedCB(), - AssetBusCallbacks::AssetReloadedCB(), - AssetBusCallbacks::AssetSavedCB(), - AssetBusCallbacks::AssetUnloadedCB(), - AssetBusCallbacks::AssetErrorCB(), - AssetBusCallbacks::AssetCanceledCB()); - } - - - void AssetBusCallbacks::SetOnAssetReadyCallback(const AssetReadyCB& readyCB) - { - m_onAssetReadyCB = readyCB; - } - - void AssetBusCallbacks::SetOnAssetMovedCallback(const AssetMovedCB& movedCB) - { - m_onAssetMovedCB = movedCB; - } - - void AssetBusCallbacks::SetOnAssetReloadedCallback(const AssetReloadedCB& reloadedCB) - { - m_onAssetReloadedCB = reloadedCB; - } - - void AssetBusCallbacks::SetOnAssetSavedCallback(const AssetSavedCB& savedCB) - { - m_onAssetSavedCB = savedCB; - } - - void AssetBusCallbacks::SetOnAssetUnloadedCallback(const AssetUnloadedCB& unloadedCB) - { - m_onAssetUnloadedCB = unloadedCB; - } - - void AssetBusCallbacks::SetOnAssetErrorCallback(const AssetErrorCB& errorCB) - { - m_onAssetErrorCB = errorCB; - } - - void AssetBusCallbacks::SetOnAssetCanceledCallback(const AssetCanceledCB& cancelCB) - { - m_onAssetCanceledCB = cancelCB; - } - - void AssetBusCallbacks::OnAssetReady(Asset asset) - { - if (m_onAssetReadyCB) - { - m_onAssetReadyCB(asset, *this); - } - } - - void AssetBusCallbacks::OnAssetMoved(Asset asset, void* oldDataPointer) - { - if (m_onAssetMovedCB) - { - m_onAssetMovedCB(asset, oldDataPointer, *this); - } - } - - void AssetBusCallbacks::OnAssetReloaded(Asset asset) - { - if (m_onAssetReloadedCB) - { - m_onAssetReloadedCB(asset, *this); - } - } - - void AssetBusCallbacks::OnAssetSaved(Asset asset, bool isSuccessful) - { - if (m_onAssetSavedCB) - { - m_onAssetSavedCB(asset, isSuccessful, *this); - } - } - - void AssetBusCallbacks::OnAssetUnloaded(const AssetId assetId, const AssetType assetType) - { - if (m_onAssetUnloadedCB) - { - m_onAssetUnloadedCB(assetId, assetType, *this); - } - } - - void AssetBusCallbacks::OnAssetError(Asset asset) - { - if (m_onAssetErrorCB) - { - m_onAssetErrorCB(asset, *this); - } - } - - void AssetBusCallbacks::OnAssetCanceled(const AssetId assetId) - { - if (m_onAssetCanceledCB) - { - m_onAssetCanceledCB(assetId, *this); - } - } - - /*static*/ bool AssetFilterNoAssetLoading([[maybe_unused]] const AssetFilterInfo& filterInfo) - { - return false; - } - namespace ProductDependencyInfo - { - AZ::Data::AssetLoadBehavior LoadBehaviorFromFlags(const ProductDependencyFlags& dependencyFlags) - { - AZ::u8 loadBehaviorValue = 0; - for (AZ::u8 thisFlag = aznumeric_cast(ProductDependencyFlagBits::LoadBehaviorLow); - thisFlag <= aznumeric_cast(ProductDependencyFlagBits::LoadBehaviorHigh); ++thisFlag) + id = assetInfo.m_assetId; + if (!assetInfo.m_relativePath.empty()) { - if (dependencyFlags[thisFlag]) - { - loadBehaviorValue |= (1 << thisFlag); - } + assetHint = assetInfo.m_relativePath; } - return static_cast(loadBehaviorValue); - } - - ProductDependencyFlags CreateFlags(AZ::Data::AssetLoadBehavior autoLoadBehavior) - { - AZ::Data::ProductDependencyInfo::ProductDependencyFlags returnFlags; - AZ::u8 loadBehavior = aznumeric_caster(autoLoadBehavior); - for (AZ::u8 thisFlag = aznumeric_cast(ProductDependencyFlagBits::LoadBehaviorLow); - thisFlag <= aznumeric_cast(ProductDependencyFlagBits::LoadBehaviorHigh); ++thisFlag) - { - if (loadBehavior & (1 << thisFlag)) - { - returnFlags[thisFlag] = true; - } - } - return returnFlags; } } - } // namespace Data -} // namespace AZ + + bool ReloadAsset(AssetData* assetData, AssetLoadBehavior assetReferenceLoadBehavior) + { + AssetManager::Instance().ReloadAsset(assetData->GetId(), assetReferenceLoadBehavior); + return true; + } + + bool SaveAsset(AssetData* assetData, AssetLoadBehavior assetReferenceLoadBehavior) + { + AssetManager::Instance().SaveAsset({ assetData, assetReferenceLoadBehavior }); + return true; + } + + Asset GetAssetData(const AssetId& id, AssetLoadBehavior assetReferenceLoadBehavior) + { + if (AssetManager::IsReady()) + { + AZStd::lock_guard assetLock(AssetManager::Instance().m_assetMutex); + auto it = AssetManager::Instance().m_assets.find(id); + if (it != AssetManager::Instance().m_assets.end()) + { + return { it->second, assetReferenceLoadBehavior }; + } + } + return {}; + } + + AssetId ResolveAssetId(const AssetId& id) + { + AZ::Data::AssetInfo assetInfo; + AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, id); + if (assetInfo.m_assetId.IsValid()) + { + return assetInfo.m_assetId; + } + else + { + return id; + } + + } + } + + AssetData::~AssetData() + { + UnregisterWithHandler(); + } + + void AssetData::Reflect(AZ::ReflectContext* context) + { + if (SerializeContext* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1) + ; + } + + if (BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->Class("AssetData") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Category, "Asset") + ->Attribute(AZ::Script::Attributes::Module, "asset") + ->Method("IsReady", &AssetData::IsReady) + ->Attribute(AZ::Script::Attributes::Alias, "is_ready") + ->Method("IsError", &AssetData::IsError) + ->Attribute(AZ::Script::Attributes::Alias, "is_error") + ->Method("IsLoading", &AssetData::IsLoading) + ->Attribute(AZ::Script::Attributes::Alias, "is_loading") + ->Method("GetId", &AssetData::GetId) + ->Attribute(AZ::Script::Attributes::Alias, "get_id") + ->Method("GetUseCount", &AssetData::GetUseCount) + ->Attribute(AZ::Script::Attributes::Alias, "get_use_count") + ; + } + } + + void AssetData::Acquire() + { + AZ_Assert(m_useCount >= 0, "AssetData has been deleted"); + + AcquireWeak(); + ++m_useCount; + } + + void AssetData::Release() + { + AZ_Assert(m_useCount > 0, "Usecount is already 0!"); + + if (m_useCount.fetch_sub(1) == 1) + { + if (AssetManager::IsReady()) + { + AssetManager::Instance().OnAssetUnused(this); + } + else + { + AZ_Assert(false, "Attempting to release asset after AssetManager has been destroyed!"); + } + } + + ReleaseWeak(); + } + + void AssetData::AcquireWeak() + { + AZ_Assert(m_useCount >= 0, "AssetData has been deleted"); + ++m_weakUseCount; + } + + void AssetData::ReleaseWeak() + { + AZ_Assert(m_weakUseCount > 0, "WeakUseCount is already 0"); + + AssetId assetId = m_assetId; + int creationToken = m_creationToken; + AssetType assetType = GetType(); + bool removeFromHash = IsRegisterReadonlyAndShareable(); + // default creation token implies that the asset was not created by the asset manager and therefore it cannot be in the asset map. + removeFromHash = creationToken == s_defaultCreationToken ? false : removeFromHash; + + if (m_weakUseCount.fetch_sub(1) == 1) + { + if (AssetManager::IsReady()) + { + AssetManager::Instance().ReleaseAsset(this, assetId, assetType, removeFromHash, creationToken); + } + else + { + AZ_Assert(false, "Attempting to release asset after AssetManager has been destroyed!"); + } + } + } + + bool AssetData::IsLoading(bool includeQueued) const + { + auto curStatus = GetStatus(); + return(curStatus == AssetStatus::Loading || curStatus == AssetStatus::LoadedPreReady || curStatus==AssetStatus::StreamReady || + (includeQueued && curStatus == AssetStatus::Queued)); + } + + void AssetData::RegisterWithHandler(AssetHandler* handler) + { + if (!handler) + { + AZ_Error("AssetData", false, "No handler to register with"); + return; + } + m_registeredHandler = handler; + } + + void AssetData::UnregisterWithHandler() + { + if (m_registeredHandler) + { + m_registeredHandler = nullptr; + } + } + + bool AssetData::GetFlag(const AssetDataFlags& checkFlag) const + { + return m_flags[aznumeric_cast(checkFlag)]; + } + + void AssetData::SetFlag(const AssetDataFlags& checkFlag, bool setValue) + { + m_flags.set(aznumeric_cast(checkFlag), setValue); + } + + bool AssetData::GetRequeue() const + { + return GetFlag(AssetDataFlags::Requeue); + } + void AssetData::SetRequeue(bool requeue) + { + SetFlag(AssetDataFlags::Requeue, requeue); + } + + void AssetBusCallbacks::SetCallbacks(const AssetReadyCB& readyCB, const AssetMovedCB& movedCB, const AssetReloadedCB& reloadedCB, + const AssetSavedCB& savedCB, const AssetUnloadedCB& unloadedCB, const AssetErrorCB& errorCB, const AssetCanceledCB& cancelCB) + { + m_onAssetReadyCB = readyCB; + m_onAssetMovedCB = movedCB; + m_onAssetReloadedCB = reloadedCB; + m_onAssetSavedCB = savedCB; + m_onAssetUnloadedCB = unloadedCB; + m_onAssetErrorCB = errorCB; + m_onAssetCanceledCB = cancelCB; + } + + void AssetBusCallbacks::ClearCallbacks() + { + SetCallbacks(AssetBusCallbacks::AssetReadyCB(), + AssetBusCallbacks::AssetMovedCB(), + AssetBusCallbacks::AssetReloadedCB(), + AssetBusCallbacks::AssetSavedCB(), + AssetBusCallbacks::AssetUnloadedCB(), + AssetBusCallbacks::AssetErrorCB(), + AssetBusCallbacks::AssetCanceledCB()); + } + + + void AssetBusCallbacks::SetOnAssetReadyCallback(const AssetReadyCB& readyCB) + { + m_onAssetReadyCB = readyCB; + } + + void AssetBusCallbacks::SetOnAssetMovedCallback(const AssetMovedCB& movedCB) + { + m_onAssetMovedCB = movedCB; + } + + void AssetBusCallbacks::SetOnAssetReloadedCallback(const AssetReloadedCB& reloadedCB) + { + m_onAssetReloadedCB = reloadedCB; + } + + void AssetBusCallbacks::SetOnAssetSavedCallback(const AssetSavedCB& savedCB) + { + m_onAssetSavedCB = savedCB; + } + + void AssetBusCallbacks::SetOnAssetUnloadedCallback(const AssetUnloadedCB& unloadedCB) + { + m_onAssetUnloadedCB = unloadedCB; + } + + void AssetBusCallbacks::SetOnAssetErrorCallback(const AssetErrorCB& errorCB) + { + m_onAssetErrorCB = errorCB; + } + + void AssetBusCallbacks::SetOnAssetCanceledCallback(const AssetCanceledCB& cancelCB) + { + m_onAssetCanceledCB = cancelCB; + } + + void AssetBusCallbacks::OnAssetReady(Asset asset) + { + if (m_onAssetReadyCB) + { + m_onAssetReadyCB(asset, *this); + } + } + + void AssetBusCallbacks::OnAssetMoved(Asset asset, void* oldDataPointer) + { + if (m_onAssetMovedCB) + { + m_onAssetMovedCB(asset, oldDataPointer, *this); + } + } + + void AssetBusCallbacks::OnAssetReloaded(Asset asset) + { + if (m_onAssetReloadedCB) + { + m_onAssetReloadedCB(asset, *this); + } + } + + void AssetBusCallbacks::OnAssetSaved(Asset asset, bool isSuccessful) + { + if (m_onAssetSavedCB) + { + m_onAssetSavedCB(asset, isSuccessful, *this); + } + } + + void AssetBusCallbacks::OnAssetUnloaded(const AssetId assetId, const AssetType assetType) + { + if (m_onAssetUnloadedCB) + { + m_onAssetUnloadedCB(assetId, assetType, *this); + } + } + + void AssetBusCallbacks::OnAssetError(Asset asset) + { + if (m_onAssetErrorCB) + { + m_onAssetErrorCB(asset, *this); + } + } + + void AssetBusCallbacks::OnAssetCanceled(const AssetId assetId) + { + if (m_onAssetCanceledCB) + { + m_onAssetCanceledCB(assetId, *this); + } + } + + /*static*/ bool AssetFilterNoAssetLoading([[maybe_unused]] const AssetFilterInfo& filterInfo) + { + return false; + } + namespace ProductDependencyInfo + { + AZ::Data::AssetLoadBehavior LoadBehaviorFromFlags(const ProductDependencyFlags& dependencyFlags) + { + AZ::u8 loadBehaviorValue = 0; + for (AZ::u8 thisFlag = aznumeric_cast(ProductDependencyFlagBits::LoadBehaviorLow); + thisFlag <= aznumeric_cast(ProductDependencyFlagBits::LoadBehaviorHigh); ++thisFlag) + { + if (dependencyFlags[thisFlag]) + { + loadBehaviorValue |= (1 << thisFlag); + } + } + return static_cast(loadBehaviorValue); + } + + ProductDependencyFlags CreateFlags(AZ::Data::AssetLoadBehavior autoLoadBehavior) + { + AZ::Data::ProductDependencyInfo::ProductDependencyFlags returnFlags; + AZ::u8 loadBehavior = aznumeric_caster(autoLoadBehavior); + for (AZ::u8 thisFlag = aznumeric_cast(ProductDependencyFlagBits::LoadBehaviorLow); + thisFlag <= aznumeric_cast(ProductDependencyFlagBits::LoadBehaviorHigh); ++thisFlag) + { + if (loadBehavior & (1 << thisFlag)) + { + returnFlags[thisFlag] = true; + } + } + return returnFlags; + } + } +} // namespace AZ::Data diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h b/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h index c45bb21c6d..2459764e52 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h +++ b/Code/Framework/AzCore/AzCore/Asset/AssetCommon.h @@ -19,7 +19,6 @@ #include #include #include -#include #include namespace AZ @@ -325,13 +324,13 @@ namespace AZ T& operator*() const { - AZ_Assert(m_assetData, "Asset is not loaded"); + AZ_Assert(m_assetData, "Asset %s (%s) is not loaded", m_assetId.ToString().c_str(), m_assetHint.c_str()); return *Get(); } T* operator->() const { - AZ_Assert(m_assetData, "Asset is not loaded"); + AZ_Assert(m_assetData, "Asset %s (%s) is not loaded", m_assetId.ToString().c_str(), m_assetHint.c_str()); return Get(); } @@ -581,7 +580,6 @@ namespace AZ template using ConnectionPolicy = AssetConnectionPolicy; - using EventProcessingPolicy = Debug::AssetTrackingEventProcessingPolicy<>; ////////////////////////////////////////////////////////////////////////// virtual ~AssetEvents() {} diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetContainer.cpp b/Code/Framework/AzCore/AzCore/Asset/AssetContainer.cpp index eb75852508..bab161fc0a 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetContainer.cpp +++ b/Code/Framework/AzCore/AzCore/Asset/AssetContainer.cpp @@ -11,483 +11,469 @@ #include #include -namespace AZ +namespace AZ::Data { - namespace Data + AssetContainer::AssetContainer(Asset rootAsset, const AssetLoadParameters& loadParams) { - AssetContainer::AssetContainer(Asset rootAsset, const AssetLoadParameters& loadParams) - { - m_rootAsset = AssetInternal::WeakAsset(rootAsset); - m_containerAssetId = m_rootAsset.GetId(); + m_rootAsset = AssetInternal::WeakAsset(rootAsset); + m_containerAssetId = m_rootAsset.GetId(); - AddDependentAssets(rootAsset, loadParams); + AddDependentAssets(rootAsset, loadParams); + } + + AssetContainer::~AssetContainer() + { + // Validate that if the AssetManager is performing normal processing duties, the AssetContainer is only destroyed once all + // dependent asset loads have completed. + if (AssetManager::IsReady() && !AssetManager::Instance().ShouldCancelAllActiveJobs()) + { + AZ_Assert(m_waitingCount == 0, "Container destroyed while dependent assets are still loading. The dependent assets may " + "end up in a perpetual loading state if there is no top-level container signalling the completion of the full load."); } - AssetContainer::~AssetContainer() - { - // Validate that if the AssetManager is performing normal processing duties, the AssetContainer is only destroyed once all - // dependent asset loads have completed. - if (AssetManager::IsReady() && !AssetManager::Instance().ShouldCancelAllActiveJobs()) - { - AZ_Assert(m_waitingCount == 0, "Container destroyed while dependent assets are still loading. The dependent assets may " - "end up in a perpetual loading state if there is no top-level container signalling the completion of the full load."); - } + AssetBus::MultiHandler::BusDisconnect(); + AssetLoadBus::MultiHandler::BusDisconnect(); + } - AssetBus::MultiHandler::BusDisconnect(); - AssetLoadBus::MultiHandler::BusDisconnect(); + AZStd::vector>> AssetContainer::CreateAndQueueDependentAssets( + const AZStd::vector& dependencyInfoList, const AssetLoadParameters& loadParamsCopyWithNoLoadingFilter) + { + AZStd::vector>> dependencyAssets; + + for (auto& thisInfo : dependencyInfoList) + { + auto dependentAsset = AssetManager::Instance().FindOrCreateAsset( + thisInfo.m_assetId, thisInfo.m_assetType, AZ::Data::AssetLoadBehavior::Default); + + if (!dependentAsset || !dependentAsset.GetId().IsValid()) + { + AZ_Warning("AssetContainer", false, "Dependency Asset %s (%s) was not found\n", + thisInfo.m_assetId.ToString().c_str(), thisInfo.m_relativePath.c_str()); + RemoveWaitingAsset(thisInfo.m_assetId); + continue; + } + dependencyAssets.emplace_back(thisInfo, AZStd::move(dependentAsset)); } - AZStd::vector>> AssetContainer::CreateAndQueueDependentAssets( - const AZStd::vector& dependencyInfoList, const AssetLoadParameters& loadParamsCopyWithNoLoadingFilter) + // Queue the loading of all of the dependent assets before loading the root asset. + for (auto& [dependentAssetInfo, dependentAsset] : dependencyAssets) { - AZStd::vector>> dependencyAssets; + // Queue each asset to load. + auto queuedDependentAsset = AssetManager::Instance().GetAssetInternal( + dependentAsset.GetId(), dependentAsset.GetType(), + AZ::Data::AssetLoadBehavior::Default, loadParamsCopyWithNoLoadingFilter, + dependentAssetInfo, HasPreloads(dependentAsset.GetId())); - for (auto& thisInfo : dependencyInfoList) - { - auto dependentAsset = AssetManager::Instance().FindOrCreateAsset( - thisInfo.m_assetId, thisInfo.m_assetType, AZ::Data::AssetLoadBehavior::Default); - - if (!dependentAsset || !dependentAsset.GetId().IsValid()) - { - AZ_Warning("AssetContainer", false, "Dependency Asset %s (%s) was not found\n", - thisInfo.m_assetId.ToString().c_str(), thisInfo.m_relativePath.c_str()); - RemoveWaitingAsset(thisInfo.m_assetId); - continue; - } - dependencyAssets.emplace_back(thisInfo, AZStd::move(dependentAsset)); - } - - // Queue the loading of all of the dependent assets before loading the root asset. - for (auto& [dependentAssetInfo, dependentAsset] : dependencyAssets) - { - // Queue each asset to load. - auto queuedDependentAsset = AssetManager::Instance().GetAssetInternal( - dependentAsset.GetId(), dependentAsset.GetType(), - AZ::Data::AssetLoadBehavior::Default, loadParamsCopyWithNoLoadingFilter, - dependentAssetInfo, HasPreloads(dependentAsset.GetId())); - - // Verify that the returned asset reference matches the one that we found or created and queued to load. - AZ_Assert(dependentAsset == queuedDependentAsset, "GetAssetInternal returned an unexpected asset reference for Asset %s", - dependentAsset.GetId().ToString().c_str()); - } - - return dependencyAssets; + // Verify that the returned asset reference matches the one that we found or created and queued to load. + AZ_Assert(dependentAsset == queuedDependentAsset, "GetAssetInternal returned an unexpected asset reference for Asset %s", + dependentAsset.GetId().ToString().c_str()); } - void AssetContainer::AddDependentAssets(Asset rootAsset, const AssetLoadParameters& loadParams) + return dependencyAssets; + } + + void AssetContainer::AddDependentAssets(Asset rootAsset, const AssetLoadParameters& loadParams) + { + AssetId rootAssetId = rootAsset.GetId(); + AssetType rootAssetType = rootAsset.GetType(); + + // Every asset we're going to be waiting on a load for - the root and all valid dependencies + AZStd::vector waitingList; + waitingList.push_back(rootAssetId); + + // Every asset dependency that we're aware of, whether or not it gets filtered out by the asset filter callback. + // This will be used at the point that asset references get serialized in to see whether or not we've received any + // unexpected assets that didn't appear in our asset catalog dependency list that need to be loaded anyways. + AZStd::vector handledAssetDependencyList; + + // Cached AssetInfo to save another lookup inside Assetmanager + AZStd::vector dependencyInfoList; + Outcome, AZStd::string> getDependenciesResult = Failure(AZStd::string()); + + // Track preloads in an additional list - they're in our waiting/dependencyInfo lists as well, but preloads require us to + // suppress emitting "AssetReady" until everything we care about in this context is ready + PreloadAssetListType preloadDependencies; + if (loadParams.m_dependencyRules == AssetDependencyLoadRules::UseLoadBehavior) { - AssetId rootAssetId = rootAsset.GetId(); - AssetType rootAssetType = rootAsset.GetType(); - - // Every asset we're going to be waiting on a load for - the root and all valid dependencies - AZStd::vector waitingList; - waitingList.push_back(rootAssetId); - - // Every asset dependency that we're aware of, whether or not it gets filtered out by the asset filter callback. - // This will be used at the point that asset references get serialized in to see whether or not we've received any - // unexpected assets that didn't appear in our asset catalog dependency list that need to be loaded anyways. - AZStd::vector handledAssetDependencyList; - - // Cached AssetInfo to save another lookup inside Assetmanager - AZStd::vector dependencyInfoList; - Outcome, AZStd::string> getDependenciesResult = Failure(AZStd::string()); - - // Track preloads in an additional list - they're in our waiting/dependencyInfo lists as well, but preloads require us to - // suppress emitting "AssetReady" until everything we care about in this context is ready - PreloadAssetListType preloadDependencies; - if (loadParams.m_dependencyRules == AssetDependencyLoadRules::UseLoadBehavior) - { - AZStd::unordered_set noloadDependencies; - AssetCatalogRequestBus::BroadcastResult(getDependenciesResult, &AssetCatalogRequestBus::Events::GetLoadBehaviorProductDependencies, - rootAssetId, noloadDependencies, preloadDependencies); - if (!noloadDependencies.empty()) - { - AZStd::lock_guard dependencyLock(m_dependencyMutex); - m_unloadedDependencies.insert(noloadDependencies.begin(), noloadDependencies.end()); - } - } - else if (loadParams.m_dependencyRules == AssetDependencyLoadRules::LoadAll) - { - AssetCatalogRequestBus::BroadcastResult(getDependenciesResult, &AssetCatalogRequestBus::Events::GetAllProductDependencies, rootAssetId); - } - // Do as much validation of dependencies as we can before the AddWaitingAssets and GetAsset calls for dependencies below - if (getDependenciesResult.IsSuccess()) - { - for (const auto& thisAsset : getDependenciesResult.GetValue()) - { - AssetInfo assetInfo; - AssetCatalogRequestBus::BroadcastResult(assetInfo, &AssetCatalogRequestBus::Events::GetAssetInfoById, thisAsset.m_assetId); - - // No matter whether or not the asset dependency is valid, loaded, or filtered out, mark it as successfully handled. - // When we encounter the asset reference during serialization, we will know that it should intentionally be skipped. - // Otherwise, it would be treated as a missing dependency and assert. - handledAssetDependencyList.emplace_back(thisAsset.m_assetId); - - if (!assetInfo.m_assetId.IsValid()) - { - // Handlers may just not currently be around for a given asset type so we only warn here - AZ_Warning("AssetContainer", false, "Asset %s (%s) references/depends on asset %s which does not exist in the catalog and cannot be loaded.", - rootAsset.GetHint().c_str(), - rootAssetId.ToString().c_str(), - thisAsset.m_assetId.ToString().c_str()); - m_invalidDependencies++; - continue; - } - if (assetInfo.m_assetId == rootAssetId) - { - // Circular dependencies in our graph need to be raised as errors as they could cause problems elsewhere - AZ_Error("AssetContainer", false, "Circular dependency found under asset %s", rootAssetId.ToString().c_str()); - m_invalidDependencies++; - continue; - } - if (!AssetManager::Instance().GetHandler(assetInfo.m_assetType)) - { - // Handlers may just not currently be around for a given asset type so we only warn here - m_invalidDependencies++; - continue; - } - if (loadParams.m_assetLoadFilterCB) - { - if (!loadParams.m_assetLoadFilterCB({thisAsset.m_assetId, assetInfo.m_assetType, - AZ::Data::ProductDependencyInfo::LoadBehaviorFromFlags(thisAsset.m_flags) })) - { - continue; - } - } - dependencyInfoList.push_back(assetInfo); - } - } - for (auto& thisInfo : dependencyInfoList) - { - waitingList.push_back(thisInfo.m_assetId); - } - - // Add waiting assets ahead of time to hear signals for any which may already be loading - AddWaitingAssets(waitingList); - SetupPreloadLists(move(preloadDependencies), rootAssetId); - - auto loadParamsCopyWithNoLoadingFilter = loadParams; - - // All asset dependencies below the root asset should be provided by the asset catalog, and therefore should *not* - // get triggered to load when the asset reference is serialized in. However, it's useful to detect, warn, and handle - // the case where the asset dependencies are NOT set up correctly. - loadParamsCopyWithNoLoadingFilter.m_assetLoadFilterCB = [handledAssetDependencyList](const AssetFilterInfo& filterInfo) - { - // NoLoad dependencies should always get filtered out and not loaded. - if (filterInfo.m_loadBehavior == AZ::Data::AssetLoadBehavior::NoLoad) - { - return false; - } - - // In the normal case, the dependent asset appears in the handled asset list, and we should return false so that - // the asset isn't attempted to be loaded, since the asset will already be triggered to get loaded or was possibly - // already filtered out by the load filter callback. - // In the error case, the asset dependencies haven't been produced by the builder correctly, so assets - // have shown up that the asset container hasn't triggered to load and isn't listening for. Assert that this case - // has happened so that the builder for this asset type can be fixed. - // Ideally we would proceed forward and load them by returning "true", but the triggered load would use this lambda - // function as the asset load filter for that load as well, which isn't correct. If we ever want to support that - // behavior, we would need to rework the way filters work as well as the code in AssetSerializer.cpp to pass down - // the loadParams.m_assetLoadFilterCB that was passed into the AddDependentAssets() methods to use as the dependent - // asset filter instead of this lambda function. - AZ_UNUSED(handledAssetDependencyList); // Prevent unused warning in release builds - AZ_Assert(AZStd::find(handledAssetDependencyList.begin(), handledAssetDependencyList.end(), filterInfo.m_assetId) != - handledAssetDependencyList.end(), - "Dependent Asset ID (%s) is expected to load, but the Asset Catalog has no dependency recorded. " - "Examine the asset builder for the asset relying on this to ensure it is generating the correct dependencies.", - filterInfo.m_assetId.ToString().c_str()); - - // The dependent asset should have already been created and at least queued to load prior to reaching this point. - // The asset serializer needs to get a successful result from FindAsset(), or else our asset reference will fail - // to point to the asset data once it is loaded. - if (!Data::AssetManager::Instance().FindAsset(filterInfo.m_assetId, AZ::Data::AssetLoadBehavior::Default)) - { - AZ_Assert(!Data::AssetManager::Instance().FindAsset(filterInfo.m_assetId, AZ::Data::AssetLoadBehavior::Default), - "Dependent Asset ID (%s) can't be found in the AssetManager, which means the asset referencing it has probably " - "started loading before the dependent asset has been queued to load. Verify that the asset dependencies have " - "been created correctly for the parent asset.", - filterInfo.m_assetId.ToString().c_str()); - } - - return false; - }; - - // This will contain the list of dependent assets that have been created (or found) and queued to load. - // We also keep a copy of the AssetInfo structure as a small optimization to avoid a redundant lookup in GetAssetInternal. - AZStd::vector>> dependencyAssets; - - // Make sure all the dependencies are created first before we try to load them. - // Since we've set the load filter to not load dependencies, we need to ensure all the assets are created beforehand - // so the dependencies can be hooked up as soon as each asset gets serialized in, even if they start getting serialized - // while we're still in the middle of triggering all of the asset loads below. - dependencyAssets = CreateAndQueueDependentAssets(dependencyInfoList, loadParamsCopyWithNoLoadingFilter); - - // Add all of the queued dependent assets as dependencies + AZStd::unordered_set noloadDependencies; + AssetCatalogRequestBus::BroadcastResult(getDependenciesResult, &AssetCatalogRequestBus::Events::GetLoadBehaviorProductDependencies, + rootAssetId, noloadDependencies, preloadDependencies); + if (!noloadDependencies.empty()) { AZStd::lock_guard dependencyLock(m_dependencyMutex); - for (auto& [dependentAssetInfo, dependentAsset] : dependencyAssets) + m_unloadedDependencies.insert(noloadDependencies.begin(), noloadDependencies.end()); + } + } + else if (loadParams.m_dependencyRules == AssetDependencyLoadRules::LoadAll) + { + AssetCatalogRequestBus::BroadcastResult(getDependenciesResult, &AssetCatalogRequestBus::Events::GetAllProductDependencies, rootAssetId); + } + // Do as much validation of dependencies as we can before the AddWaitingAssets and GetAsset calls for dependencies below + if (getDependenciesResult.IsSuccess()) + { + for (const auto& thisAsset : getDependenciesResult.GetValue()) + { + AssetInfo assetInfo; + AssetCatalogRequestBus::BroadcastResult(assetInfo, &AssetCatalogRequestBus::Events::GetAssetInfoById, thisAsset.m_assetId); + + // No matter whether or not the asset dependency is valid, loaded, or filtered out, mark it as successfully handled. + // When we encounter the asset reference during serialization, we will know that it should intentionally be skipped. + // Otherwise, it would be treated as a missing dependency and assert. + handledAssetDependencyList.emplace_back(thisAsset.m_assetId); + + if (!assetInfo.m_assetId.IsValid()) { - AddDependency(AZStd::move(dependentAsset)); + // Handlers may just not currently be around for a given asset type so we only warn here + AZ_Warning("AssetContainer", false, "Asset %s (%s) references/depends on asset %s which does not exist in the catalog and cannot be loaded.", + rootAsset.GetHint().c_str(), + rootAssetId.ToString().c_str(), + thisAsset.m_assetId.ToString().c_str()); + m_invalidDependencies++; + continue; } + if (assetInfo.m_assetId == rootAssetId) + { + // Circular dependencies in our graph need to be raised as errors as they could cause problems elsewhere + AZ_Error("AssetContainer", false, "Circular dependency found under asset %s", rootAssetId.ToString().c_str()); + m_invalidDependencies++; + continue; + } + if (!AssetManager::Instance().GetHandler(assetInfo.m_assetType)) + { + // Handlers may just not currently be around for a given asset type so we only warn here + m_invalidDependencies++; + continue; + } + if (loadParams.m_assetLoadFilterCB) + { + if (!loadParams.m_assetLoadFilterCB({thisAsset.m_assetId, assetInfo.m_assetType, + AZ::Data::ProductDependencyInfo::LoadBehaviorFromFlags(thisAsset.m_flags) })) + { + continue; + } + } + dependencyInfoList.push_back(assetInfo); + } + } + for (auto& thisInfo : dependencyInfoList) + { + waitingList.push_back(thisInfo.m_assetId); + } + + // Add waiting assets ahead of time to hear signals for any which may already be loading + AddWaitingAssets(waitingList); + SetupPreloadLists(move(preloadDependencies), rootAssetId); + + auto loadParamsCopyWithNoLoadingFilter = loadParams; + + // All asset dependencies below the root asset should be provided by the asset catalog, and therefore should *not* + // get triggered to load when the asset reference is serialized in. However, it's useful to detect, warn, and handle + // the case where the asset dependencies are NOT set up correctly. + loadParamsCopyWithNoLoadingFilter.m_assetLoadFilterCB = [handledAssetDependencyList](const AssetFilterInfo& filterInfo) + { + // NoLoad dependencies should always get filtered out and not loaded. + if (filterInfo.m_loadBehavior == AZ::Data::AssetLoadBehavior::NoLoad) + { + return false; } - // Finally, after creating and queueing the dependent assets, queue the root asset. This is saved until last to ensure that - // it doesn't have any chance of serializing in until after all the dependent assets have been queued for loading and have - // been added to the list of dependencies. - auto thisAsset = AssetManager::Instance().GetAssetInternal(rootAssetId, rootAssetType, rootAsset.GetAutoLoadBehavior(), - loadParamsCopyWithNoLoadingFilter, AssetInfo(), HasPreloads(rootAssetId)); + // In the normal case, the dependent asset appears in the handled asset list, and we should return false so that + // the asset isn't attempted to be loaded, since the asset will already be triggered to get loaded or was possibly + // already filtered out by the load filter callback. + // In the error case, the asset dependencies haven't been produced by the builder correctly, so assets + // have shown up that the asset container hasn't triggered to load and isn't listening for. Assert that this case + // has happened so that the builder for this asset type can be fixed. + // Ideally we would proceed forward and load them by returning "true", but the triggered load would use this lambda + // function as the asset load filter for that load as well, which isn't correct. If we ever want to support that + // behavior, we would need to rework the way filters work as well as the code in AssetSerializer.cpp to pass down + // the loadParams.m_assetLoadFilterCB that was passed into the AddDependentAssets() methods to use as the dependent + // asset filter instead of this lambda function. + AZ_UNUSED(handledAssetDependencyList); // Prevent unused warning in release builds + AZ_Assert(AZStd::find(handledAssetDependencyList.begin(), handledAssetDependencyList.end(), filterInfo.m_assetId) != + handledAssetDependencyList.end(), + "Dependent Asset ID (%s) is expected to load, but the Asset Catalog has no dependency recorded. " + "Examine the asset builder for the asset relying on this to ensure it is generating the correct dependencies.", + filterInfo.m_assetId.ToString().c_str()); - if (!thisAsset) + // The dependent asset should have already been created and at least queued to load prior to reaching this point. + // The asset serializer needs to get a successful result from FindAsset(), or else our asset reference will fail + // to point to the asset data once it is loaded. + if (!Data::AssetManager::Instance().FindAsset(filterInfo.m_assetId, AZ::Data::AssetLoadBehavior::Default)) { - AZ_Assert(false, "Root asset with id %s failed to load, asset container is invalid.", - rootAssetId.ToString().c_str()); - ClearWaitingAssets(); - // initComplete remains false, because we have failed to initialize successfully. + AZ_Assert(!Data::AssetManager::Instance().FindAsset(filterInfo.m_assetId, AZ::Data::AssetLoadBehavior::Default), + "Dependent Asset ID (%s) can't be found in the AssetManager, which means the asset referencing it has probably " + "started loading before the dependent asset has been queued to load. Verify that the asset dependencies have " + "been created correctly for the parent asset.", + filterInfo.m_assetId.ToString().c_str()); + } + + return false; + }; + + // This will contain the list of dependent assets that have been created (or found) and queued to load. + // We also keep a copy of the AssetInfo structure as a small optimization to avoid a redundant lookup in GetAssetInternal. + AZStd::vector>> dependencyAssets; + + // Make sure all the dependencies are created first before we try to load them. + // Since we've set the load filter to not load dependencies, we need to ensure all the assets are created beforehand + // so the dependencies can be hooked up as soon as each asset gets serialized in, even if they start getting serialized + // while we're still in the middle of triggering all of the asset loads below. + dependencyAssets = CreateAndQueueDependentAssets(dependencyInfoList, loadParamsCopyWithNoLoadingFilter); + + // Add all of the queued dependent assets as dependencies + { + AZStd::lock_guard dependencyLock(m_dependencyMutex); + for (auto& [dependentAssetInfo, dependentAsset] : dependencyAssets) + { + AddDependency(AZStd::move(dependentAsset)); + } + } + + // Finally, after creating and queueing the dependent assets, queue the root asset. This is saved until last to ensure that + // it doesn't have any chance of serializing in until after all the dependent assets have been queued for loading and have + // been added to the list of dependencies. + auto thisAsset = AssetManager::Instance().GetAssetInternal(rootAssetId, rootAssetType, rootAsset.GetAutoLoadBehavior(), + loadParamsCopyWithNoLoadingFilter, AssetInfo(), HasPreloads(rootAssetId)); + + if (!thisAsset) + { + AZ_Assert(false, "Root asset with id %s failed to load, asset container is invalid.", + rootAssetId.ToString().c_str()); + ClearWaitingAssets(); + // initComplete remains false, because we have failed to initialize successfully. + return; + } + + m_initComplete = true; + + // *After* setting initComplete to true, check to see if the assets are already ready. + // This check needs to wait until after setting initComplete because if they *are* ready, we want the final call to + // RemoveWaitingAsset to trigger the OnAssetContainerReady/Canceled event. If we call CheckReady() *before* setting + // initComplete, if all the assets are ready, the event will never get triggered. + CheckReady(); + } + + bool AssetContainer::IsReady() const + { + return (m_rootAsset && m_waitingCount == 0); + } + + bool AssetContainer::IsLoading() const + { + return (m_rootAsset || m_waitingCount); + } + + bool AssetContainer::IsValid() const + { + return (m_containerAssetId.IsValid() && m_initComplete && m_rootAsset); + } + + void AssetContainer::CheckReady() + { + if (!m_dependencies.empty()) + { + for (auto& [assetId, dependentAsset] : m_dependencies) + { + if (dependentAsset->IsReady() || dependentAsset->IsError()) + { + HandleReadyAsset(dependentAsset); + } + } + } + if (auto asset = m_rootAsset.GetStrongReference(); asset.IsReady() || asset.IsError()) + { + HandleReadyAsset(asset); + } + } + + Asset AssetContainer::GetRootAsset() + { + return m_rootAsset.GetStrongReference(); + } + + AssetId AssetContainer::GetContainerAssetId() + { + return m_containerAssetId; + } + + void AssetContainer::ClearRootAsset() + { + AssetId rootId = m_rootAsset.GetId(); + + { + AZStd::lock_guard preloadGuard(m_preloadMutex); + + // Erase the entry in the preloadWaitList for the root asset if one exists. + m_preloadWaitList.erase(rootId); + + // It's possible that the root asset has preload dependencies, so make sure to check the preload list and remove + // the entry for the root asset if it has one. + auto rootAssetPreloadIter = m_preloadList.find(rootId); + if (rootAssetPreloadIter != m_preloadList.end()) + { + // Since the root asset has a preload list, that means the preload wait list will also have references to the + // root asset. (The preload wait list is a list of assets waiting on a preload asset to finish) Clear those + // out as well. + auto waitAssetSet = rootAssetPreloadIter->second; + for (auto& waitId : waitAssetSet) + { + auto waitAssetIter = m_preloadWaitList.find(waitId); + if (waitAssetIter != m_preloadWaitList.end()) + { + waitAssetIter->second.erase(rootId); + } + } + + m_preloadList.erase(rootAssetPreloadIter); + } + } + + // Clear out the root asset before removing it from the waiting list to ensure that we trigger an "OnAssetContainerCanceled" + // event instead of "OnAssetContainerReady". + m_rootAsset = {}; + RemoveWaitingAsset(rootId); + + } + + void AssetContainer::AddDependency(const Asset& newDependency) + { + m_dependencies[newDependency->GetId()] = newDependency; + } + void AssetContainer::AddDependency(Asset&& newDependency) + { + m_dependencies[newDependency->GetId()] = AZStd::move(newDependency); + } + + void AssetContainer::OnAssetReady(Asset asset) + { + HandleReadyAsset(asset); + } + + void AssetContainer::OnAssetError(Asset asset) + { + AZ_Warning("AssetContainer", false, "Error loading asset %s", asset->GetId().ToString().c_str()); + HandleReadyAsset(asset); + } + + void AssetContainer::HandleReadyAsset(Asset asset) + { + // Wait until we've finished initialization before allowing this + // If a ready event happens before we've gotten all the maps/structures set up, there may be some missing data + // which can lead to a crash + // We'll go through and check the ready status of every dependency immediately after finishing initialization anyway + if (m_initComplete) + { + RemoveFromAllWaitingPreloads(asset->GetId()); + RemoveWaitingAsset(asset->GetId()); + } + } + + void AssetContainer::OnAssetDataLoaded(Asset asset) + { + // Remove only from this asset's waiting list. Anything else should + // listen for OnAssetReady as the true signal. This is essentially removing the + // "marker" we placed in SetupPreloads that we need to wait for our own data + RemoveFromWaitingPreloads(asset->GetId(), asset->GetId()); + } + + void AssetContainer::RemoveFromWaitingPreloads(const AssetId& waiterId, const AssetId& preloadID) + { + { + AZStd::lock_guard preloadGuard(m_preloadMutex); + + auto remainingPreloadIter = m_preloadList.find(waiterId); + if (remainingPreloadIter == m_preloadList.end()) + { + // If we got here without an entry on the preload list, it probably means this asset was triggered to load multiple + // times, some with dependencies and some without. To ensure that we don't disturb the loads that expect the + // dependencies, just silently return and don't treat the asset as finished loading. We'll rely on the other load + // to send an OnAssetReady() whenever its expected dependencies are met. return; } - - m_initComplete = true; - - // *After* setting initComplete to true, check to see if the assets are already ready. - // This check needs to wait until after setting initComplete because if they *are* ready, we want the final call to - // RemoveWaitingAsset to trigger the OnAssetContainerReady/Canceled event. If we call CheckReady() *before* setting - // initComplete, if all the assets are ready, the event will never get triggered. - CheckReady(); - } - - bool AssetContainer::IsReady() const - { - return (m_rootAsset && m_waitingCount == 0); - } - - bool AssetContainer::IsLoading() const - { - return (m_rootAsset || m_waitingCount); - } - - bool AssetContainer::IsValid() const - { - return (m_containerAssetId.IsValid() && m_initComplete && m_rootAsset); - } - - void AssetContainer::CheckReady() - { - if (!m_dependencies.empty()) + if (!remainingPreloadIter->second.erase(preloadID)) { - for (auto& [assetId, dependentAsset] : m_dependencies) - { - if (dependentAsset->IsReady() || dependentAsset->IsError()) - { - HandleReadyAsset(dependentAsset); - } - } + AZ_Warning("AssetContainer", !m_initComplete, "Couldn't remove %s from waiting list of %s", preloadID.ToString().c_str(), waiterId.ToString().c_str()); + return; } - if (auto asset = m_rootAsset.GetStrongReference(); asset.IsReady() || asset.IsError()) + if (!remainingPreloadIter->second.empty()) { - HandleReadyAsset(asset); + return; } } + auto thisAsset = GetAssetData(waiterId); + AssetManager::Instance().ValidateAndPostLoad(thisAsset, true, false, nullptr); + } - Asset AssetContainer::GetRootAsset() + void AssetContainer::RemoveFromAllWaitingPreloads(const AssetId& thisId) + { + AZStd::unordered_set checkList; { - return m_rootAsset.GetStrongReference(); - } - - AssetId AssetContainer::GetContainerAssetId() - { - return m_containerAssetId; - } - - void AssetContainer::ClearRootAsset() - { - AssetId rootId = m_rootAsset.GetId(); - - { - AZStd::lock_guard preloadGuard(m_preloadMutex); - - // Erase the entry in the preloadWaitList for the root asset if one exists. - m_preloadWaitList.erase(rootId); - - // It's possible that the root asset has preload dependencies, so make sure to check the preload list and remove - // the entry for the root asset if it has one. - auto rootAssetPreloadIter = m_preloadList.find(rootId); - if (rootAssetPreloadIter != m_preloadList.end()) - { - // Since the root asset has a preload list, that means the preload wait list will also have references to the - // root asset. (The preload wait list is a list of assets waiting on a preload asset to finish) Clear those - // out as well. - auto waitAssetSet = rootAssetPreloadIter->second; - for (auto& waitId : waitAssetSet) - { - auto waitAssetIter = m_preloadWaitList.find(waitId); - if (waitAssetIter != m_preloadWaitList.end()) - { - waitAssetIter->second.erase(rootId); - } - } - - m_preloadList.erase(rootAssetPreloadIter); - } - } - - // Clear out the root asset before removing it from the waiting list to ensure that we trigger an "OnAssetContainerCanceled" - // event instead of "OnAssetContainerReady". - m_rootAsset = {}; - RemoveWaitingAsset(rootId); - - } - - void AssetContainer::AddDependency(const Asset& newDependency) - { - m_dependencies[newDependency->GetId()] = newDependency; - } - void AssetContainer::AddDependency(Asset&& newDependency) - { - m_dependencies[newDependency->GetId()] = AZStd::move(newDependency); - } - - void AssetContainer::OnAssetReady(Asset asset) - { - HandleReadyAsset(asset); - } - - void AssetContainer::OnAssetError(Asset asset) - { - AZ_Warning("AssetContainer", false, "Error loading asset %s", asset->GetId().ToString().c_str()); - HandleReadyAsset(asset); - } - - void AssetContainer::HandleReadyAsset(Asset asset) - { - // Wait until we've finished initialization before allowing this - // If a ready event happens before we've gotten all the maps/structures set up, there may be some missing data - // which can lead to a crash - // We'll go through and check the ready status of every dependency immediately after finishing initialization anyway - if (m_initComplete) - { - RemoveFromAllWaitingPreloads(asset->GetId()); - RemoveWaitingAsset(asset->GetId()); - } - } - - void AssetContainer::OnAssetDataLoaded(Asset asset) - { - // Remove only from this asset's waiting list. Anything else should - // listen for OnAssetReady as the true signal. This is essentially removing the - // "marker" we placed in SetupPreloads that we need to wait for our own data - RemoveFromWaitingPreloads(asset->GetId(), asset->GetId()); - } - - void AssetContainer::RemoveFromWaitingPreloads(const AssetId& waiterId, const AssetId& preloadID) - { - { - AZStd::lock_guard preloadGuard(m_preloadMutex); - - auto remainingPreloadIter = m_preloadList.find(waiterId); - if (remainingPreloadIter == m_preloadList.end()) - { - // If we got here without an entry on the preload list, it probably means this asset was triggered to load multiple - // times, some with dependencies and some without. To ensure that we don't disturb the loads that expect the - // dependencies, just silently return and don't treat the asset as finished loading. We'll rely on the other load - // to send an OnAssetReady() whenever its expected dependencies are met. - return; - } - if (!remainingPreloadIter->second.erase(preloadID)) - { - AZ_Warning("AssetContainer", !m_initComplete, "Couldn't remove %s from waiting list of %s", preloadID.ToString().c_str(), waiterId.ToString().c_str()); - return; - } - if (!remainingPreloadIter->second.empty()) - { - return; - } - } - auto thisAsset = GetAssetData(waiterId); - AssetManager::Instance().ValidateAndPostLoad(thisAsset, true, false, nullptr); - } - - void AssetContainer::RemoveFromAllWaitingPreloads(const AssetId& thisId) - { - AZStd::unordered_set checkList; - { - AZStd::lock_guard preloadGuard(m_preloadMutex); - - auto waitingList = m_preloadWaitList.find(thisId); - if (waitingList != m_preloadWaitList.end()) - { - checkList = move(waitingList->second); - m_preloadWaitList.erase(waitingList); - } - } - for (auto& thisDepId : checkList) - { - if (thisDepId != thisId) - { - RemoveFromWaitingPreloads(thisDepId, thisId); - } - } - } - - void AssetContainer::ClearWaitingAssets() - { - AZStd::lock_guard lock(m_readyMutex); - m_waitingCount = 0; - for (auto& thisAsset : m_waitingAssets) - { - AssetBus::MultiHandler::BusDisconnect(thisAsset); - } - m_waitingAssets.clear(); - } - - void AssetContainer::ListWaitingAssets() const - { -#if defined(AZ_ENABLE_TRACING) - AZStd::lock_guard lock(m_readyMutex); - AZ_TracePrintf("AssetContainer", "Waiting on assets:\n"); - for (auto& thisAsset : m_waitingAssets) - { - AZ_TracePrintf("AssetContainer", " %s\n",thisAsset.ToString().c_str()); - } -#endif - } - - void AssetContainer::ListWaitingPreloads([[maybe_unused]] const AssetId& assetId) const - { -#if defined(AZ_ENABLE_TRACING) AZStd::lock_guard preloadGuard(m_preloadMutex); - auto preloadEntry = m_preloadList.find(assetId); - if (preloadEntry != m_preloadList.end()) + + auto waitingList = m_preloadWaitList.find(thisId); + if (waitingList != m_preloadWaitList.end()) { - AZ_TracePrintf("AssetContainer", "%s waiting on preloads : \n",assetId.ToString().c_str()); - for (auto& thisId : preloadEntry->second) - { - AZ_TracePrintf("AssetContainer", " %s\n",thisId.ToString().c_str()); - } + checkList = move(waitingList->second); + m_preloadWaitList.erase(waitingList); } - else + } + for (auto& thisDepId : checkList) + { + if (thisDepId != thisId) { - AZ_TracePrintf("AssetContainer", "%s isn't waiting on any preloads:\n", assetId.ToString().c_str()); + RemoveFromWaitingPreloads(thisDepId, thisId); } + } + } + + void AssetContainer::ClearWaitingAssets() + { + AZStd::lock_guard lock(m_readyMutex); + m_waitingCount = 0; + for (auto& thisAsset : m_waitingAssets) + { + AssetBus::MultiHandler::BusDisconnect(thisAsset); + } + m_waitingAssets.clear(); + } + + void AssetContainer::ListWaitingAssets() const + { +#if defined(AZ_ENABLE_TRACING) + AZStd::lock_guard lock(m_readyMutex); + AZ_TracePrintf("AssetContainer", "Waiting on assets:\n"); + for (auto& thisAsset : m_waitingAssets) + { + AZ_TracePrintf("AssetContainer", " %s\n",thisAsset.ToString().c_str()); + } #endif - } + } - void AssetContainer::AddWaitingAssets(const AZStd::vector& assetList) + void AssetContainer::ListWaitingPreloads([[maybe_unused]] const AssetId& assetId) const + { +#if defined(AZ_ENABLE_TRACING) + AZStd::lock_guard preloadGuard(m_preloadMutex); + auto preloadEntry = m_preloadList.find(assetId); + if (preloadEntry != m_preloadList.end()) { - AZStd::lock_guard lock(m_readyMutex); - for (auto& thisAsset : assetList) + AZ_TracePrintf("AssetContainer", "%s waiting on preloads : \n",assetId.ToString().c_str()); + for (auto& thisId : preloadEntry->second) { - if (m_waitingAssets.insert(thisAsset).second) - { - ++m_waitingCount; - AssetBus::MultiHandler::BusConnect(thisAsset); - AssetLoadBus::MultiHandler::BusConnect(thisAsset); - } + AZ_TracePrintf("AssetContainer", " %s\n",thisId.ToString().c_str()); } } - - void AssetContainer::AddWaitingAsset(const AssetId& thisAsset) + else + { + AZ_TracePrintf("AssetContainer", "%s isn't waiting on any preloads:\n", assetId.ToString().c_str()); + } +#endif + } + + void AssetContainer::AddWaitingAssets(const AZStd::vector& assetList) + { + AZStd::lock_guard lock(m_readyMutex); + for (auto& thisAsset : assetList) { - AZStd::lock_guard lock(m_readyMutex); if (m_waitingAssets.insert(thisAsset).second) { ++m_waitingCount; @@ -495,196 +481,207 @@ namespace AZ AssetLoadBus::MultiHandler::BusConnect(thisAsset); } } + } - void AssetContainer::RemoveWaitingAsset(const AssetId& thisAsset) + void AssetContainer::AddWaitingAsset(const AssetId& thisAsset) + { + AZStd::lock_guard lock(m_readyMutex); + if (m_waitingAssets.insert(thisAsset).second) { - bool allReady{ false }; - { - bool disconnectEbus = false; + ++m_waitingCount; + AssetBus::MultiHandler::BusConnect(thisAsset); + AssetLoadBus::MultiHandler::BusConnect(thisAsset); + } + } - { // Intentionally limiting lock scope - AZStd::lock_guard lock(m_readyMutex); - // If we're trying to remove something already removed, just ignore it - if (m_waitingAssets.erase(thisAsset)) - { - m_waitingCount -= 1; - disconnectEbus = true; + void AssetContainer::RemoveWaitingAsset(const AssetId& thisAsset) + { + bool allReady{ false }; + { + bool disconnectEbus = false; - } - if (m_waitingAssets.empty()) - { - allReady = true; - } - } - - if(disconnectEbus) + { // Intentionally limiting lock scope + AZStd::lock_guard lock(m_readyMutex); + // If we're trying to remove something already removed, just ignore it + if (m_waitingAssets.erase(thisAsset)) { - AssetBus::MultiHandler::BusDisconnect(thisAsset); - AssetLoadBus::MultiHandler::BusDisconnect(thisAsset); + m_waitingCount -= 1; + disconnectEbus = true; + + } + if (m_waitingAssets.empty()) + { + allReady = true; } } - // If there are no assets left to be loaded, trigger the final AssetContainer notification (ready or canceled). - // We guard against prematurely sending it (m_initComplete) because it's possible for assets to get removed from our waiting - // list *while* we're still building up the list, so the list would appear to be empty too soon. - // We also guard against sending it multiple times (m_finalNotificationSent), because in some error conditions, it may be - // possible to try to remove the same asset multiple times, which if it's the last asset, it could trigger multiple - // notifications. - if (allReady && m_initComplete && !m_finalNotificationSent) + if(disconnectEbus) { - m_finalNotificationSent = true; - if (m_rootAsset) - { - AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetContainerReady, this); - } - else - { - AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetContainerCanceled, this); - } + AssetBus::MultiHandler::BusDisconnect(thisAsset); + AssetLoadBus::MultiHandler::BusDisconnect(thisAsset); } } - AssetContainer::operator bool() const + // If there are no assets left to be loaded, trigger the final AssetContainer notification (ready or canceled). + // We guard against prematurely sending it (m_initComplete) because it's possible for assets to get removed from our waiting + // list *while* we're still building up the list, so the list would appear to be empty too soon. + // We also guard against sending it multiple times (m_finalNotificationSent), because in some error conditions, it may be + // possible to try to remove the same asset multiple times, which if it's the last asset, it could trigger multiple + // notifications. + if (allReady && m_initComplete && !m_finalNotificationSent) { - return m_rootAsset ? true : false; - } - - const AssetContainer::DependencyList& AssetContainer::GetDependencies() const - { - return m_dependencies; - } - - const AZStd::unordered_set& AssetContainer::GetUnloadedDependencies() const - { - return m_unloadedDependencies; - } - - void AssetContainer::SetupPreloadLists(PreloadAssetListType&& preloadList, const AssetId& rootAssetId) - { - if (!preloadList.empty()) + m_finalNotificationSent = true; + if (m_rootAsset) { - // This method can be entered as additional NoLoad dependency groups are loaded - the container could - // be in the middle of loading so we need to grab both mutexes. - AZStd::scoped_lock lock(m_readyMutex, m_preloadMutex); + AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetContainerReady, this); + } + else + { + AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetContainerCanceled, this); + } + } + } - for (auto thisListPair = preloadList.begin(); thisListPair != preloadList.end();) + AssetContainer::operator bool() const + { + return m_rootAsset ? true : false; + } + + const AssetContainer::DependencyList& AssetContainer::GetDependencies() const + { + return m_dependencies; + } + + const AZStd::unordered_set& AssetContainer::GetUnloadedDependencies() const + { + return m_unloadedDependencies; + } + + void AssetContainer::SetupPreloadLists(PreloadAssetListType&& preloadList, const AssetId& rootAssetId) + { + if (!preloadList.empty()) + { + // This method can be entered as additional NoLoad dependency groups are loaded - the container could + // be in the middle of loading so we need to grab both mutexes. + AZStd::scoped_lock lock(m_readyMutex, m_preloadMutex); + + for (auto thisListPair = preloadList.begin(); thisListPair != preloadList.end();) + { + // We only should add ourselves if we have another valid preload we're waiting on + bool foundAsset{ false }; + // It's possible this set of preload dependencies was culled out by lack of asset handler + // Or filtering rules. This is not an error, we should just remove it from the list of + // Preloads we're waiting on + if (!m_waitingAssets.count(thisListPair->first)) { - // We only should add ourselves if we have another valid preload we're waiting on - bool foundAsset{ false }; - // It's possible this set of preload dependencies was culled out by lack of asset handler - // Or filtering rules. This is not an error, we should just remove it from the list of - // Preloads we're waiting on - if (!m_waitingAssets.count(thisListPair->first)) + thisListPair = preloadList.erase(thisListPair); + continue; + } + for (auto thisAsset = thisListPair->second.begin(); thisAsset != thisListPair->second.end();) + { + // These are data errors. We'll emit the error but carry on. The container + // will load the assets but won't/can't create a circular preload dependency chain + if (*thisAsset == rootAssetId) { - thisListPair = preloadList.erase(thisListPair); + AZ_Error("AssetContainer", false, "Circular preload dependency found - %s has a preload" + "dependency back to root %s\n", + thisListPair->first.ToString().c_str(), + rootAssetId.ToString().c_str()); + thisAsset = thisListPair->second.erase(thisAsset); continue; } - for (auto thisAsset = thisListPair->second.begin(); thisAsset != thisListPair->second.end();) + else if (*thisAsset == thisListPair->first) { - // These are data errors. We'll emit the error but carry on. The container - // will load the assets but won't/can't create a circular preload dependency chain - if (*thisAsset == rootAssetId) - { - AZ_Error("AssetContainer", false, "Circular preload dependency found - %s has a preload" - "dependency back to root %s\n", - thisListPair->first.ToString().c_str(), - rootAssetId.ToString().c_str()); - thisAsset = thisListPair->second.erase(thisAsset); - continue; - } - else if (*thisAsset == thisListPair->first) - { - AZ_Error("AssetContainer", false, "Circular preload dependency found - Root asset %s has a preload" - "dependency on %s which depends back back to itself\n", - rootAssetId.ToString().c_str(), - thisListPair->first.ToString().c_str()); - thisAsset = thisListPair->second.erase(thisAsset); - continue; - } - else if (m_preloadWaitList.count(thisListPair->first) && m_preloadWaitList[thisListPair->first].count(*thisAsset)) - { - AZ_Error("AssetContainer", false, "Circular dependency found - Root asset %s has a preload" - "dependency on %s which has a circular dependency with %s\n", - rootAssetId.ToString().c_str(), - thisListPair->first.ToString().c_str(), - thisAsset->ToString().c_str()); - thisAsset = thisListPair->second.erase(thisAsset); - continue; - } - else if (m_waitingAssets.count(*thisAsset)) - { - foundAsset = true; - m_preloadWaitList[*thisAsset].insert(thisListPair->first); - ++thisAsset; - } - else - { - // This particular preload dependency of this asset was culled - // similar to the case above this can be due to no established asset handler - // or filtering rules. We'll just erase the entry because we're not loading this - thisAsset = thisListPair->second.erase(thisAsset); - continue; - } + AZ_Error("AssetContainer", false, "Circular preload dependency found - Root asset %s has a preload" + "dependency on %s which depends back back to itself\n", + rootAssetId.ToString().c_str(), + thisListPair->first.ToString().c_str()); + thisAsset = thisListPair->second.erase(thisAsset); + continue; } - if (foundAsset) + else if (m_preloadWaitList.count(thisListPair->first) && m_preloadWaitList[thisListPair->first].count(*thisAsset)) { - // We've established that this asset has at least one preload dependency it needs to wait on - // so we additionally add the waiting asset as its own preload so all of our "waiting assets" - // are managed in the same list. We can't consider this asset to be "ready" until all - // of its preloads are ready and it has been loaded. It will request an OnAssetDataLoaded - // notification from AssetManager rather than an OnAssetReady because of these additional dependencies. - thisListPair->second.insert(thisListPair->first); - m_preloadWaitList[thisListPair->first].insert(thisListPair->first); + AZ_Error("AssetContainer", false, "Circular dependency found - Root asset %s has a preload" + "dependency on %s which has a circular dependency with %s\n", + rootAssetId.ToString().c_str(), + thisListPair->first.ToString().c_str(), + thisAsset->ToString().c_str()); + thisAsset = thisListPair->second.erase(thisAsset); + continue; + } + else if (m_waitingAssets.count(*thisAsset)) + { + foundAsset = true; + m_preloadWaitList[*thisAsset].insert(thisListPair->first); + ++thisAsset; + } + else + { + // This particular preload dependency of this asset was culled + // similar to the case above this can be due to no established asset handler + // or filtering rules. We'll just erase the entry because we're not loading this + thisAsset = thisListPair->second.erase(thisAsset); + continue; } - ++thisListPair; } - for(auto& thisList : preloadList) + if (foundAsset) { - // Only save the entry to the final preload list if it has at least one dependent asset still remaining after - // the checks above. - if (!thisList.second.empty()) - { - m_preloadList[thisList.first].insert(thisList.second.begin(), thisList.second.end()); - } + // We've established that this asset has at least one preload dependency it needs to wait on + // so we additionally add the waiting asset as its own preload so all of our "waiting assets" + // are managed in the same list. We can't consider this asset to be "ready" until all + // of its preloads are ready and it has been loaded. It will request an OnAssetDataLoaded + // notification from AssetManager rather than an OnAssetReady because of these additional dependencies. + thisListPair->second.insert(thisListPair->first); + m_preloadWaitList[thisListPair->first].insert(thisListPair->first); + } + ++thisListPair; + } + for(auto& thisList : preloadList) + { + // Only save the entry to the final preload list if it has at least one dependent asset still remaining after + // the checks above. + if (!thisList.second.empty()) + { + m_preloadList[thisList.first].insert(thisList.second.begin(), thisList.second.end()); } } } + } - bool AssetContainer::HasPreloads(const AssetId& assetId) const + bool AssetContainer::HasPreloads(const AssetId& assetId) const + { + AZStd::lock_guard preloadGuard(m_preloadMutex); + auto preloadEntry = m_preloadList.find(assetId); + if (preloadEntry != m_preloadList.end()) { - AZStd::lock_guard preloadGuard(m_preloadMutex); - auto preloadEntry = m_preloadList.find(assetId); - if (preloadEntry != m_preloadList.end()) - { - return !preloadEntry->second.empty(); - } - return false; + return !preloadEntry->second.empty(); } + return false; + } - Asset AssetContainer::GetAssetData(const AssetId& assetId) const + Asset AssetContainer::GetAssetData(const AssetId& assetId) const + { + AZStd::lock_guard dependenciesGuard(m_dependencyMutex); + if (auto rootAsset = m_rootAsset.GetStrongReference(); rootAsset.GetId() == assetId) { - AZStd::lock_guard dependenciesGuard(m_dependencyMutex); - if (auto rootAsset = m_rootAsset.GetStrongReference(); rootAsset.GetId() == assetId) - { - return rootAsset; - } - auto dependencyIter = m_dependencies.find(assetId); - if (dependencyIter != m_dependencies.end()) - { - return dependencyIter->second; - } - AZ_Warning("AssetContainer", false, "Asset %s not found in container", assetId.ToString().c_str()); - return {}; + return rootAsset; } + auto dependencyIter = m_dependencies.find(assetId); + if (dependencyIter != m_dependencies.end()) + { + return dependencyIter->second; + } + AZ_Warning("AssetContainer", false, "Asset %s not found in container", assetId.ToString().c_str()); + return {}; + } - int AssetContainer::GetNumWaitingDependencies() const - { - return m_waitingCount.load(); - } + int AssetContainer::GetNumWaitingDependencies() const + { + return m_waitingCount.load(); + } - int AssetContainer::GetInvalidDependencies() const - { - return m_invalidDependencies.load(); - } - } // namespace Data -} // namespace AZ + int AssetContainer::GetInvalidDependencies() const + { + return m_invalidDependencies.load(); + } +} // namespace AZ::Data diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetDataStream.h b/Code/Framework/AzCore/AzCore/Asset/AssetDataStream.h index 62f5808207..d2b069b55b 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetDataStream.h +++ b/Code/Framework/AzCore/AzCore/Asset/AssetDataStream.h @@ -70,6 +70,9 @@ namespace AZ::Data const char* GetFilename() const override { return m_filePath.c_str(); } + AZStd::chrono::milliseconds GetStreamingDeadline() const { return m_curDeadline; } + AZ::IO::IStreamerTypes::Priority GetStreamingPriority() const { return m_curPriority; } + // AssetDataStream specific APIs //! Whether or not all data has been loaded. diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp b/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp index 3fa3b39ca5..0d28036646 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp +++ b/Code/Framework/AzCore/AzCore/Asset/AssetJsonSerializer.cpp @@ -12,207 +12,204 @@ #include #include -namespace AZ +namespace AZ::Data { - namespace Data + AZ_CLASS_ALLOCATOR_IMPL(AssetJsonSerializer, SystemAllocator, 0); + + JsonSerializationResult::Result AssetJsonSerializer::Load(void* outputValue, const Uuid& /*outputValueTypeId*/, + const rapidjson::Value& inputValue, JsonDeserializerContext& context) { - AZ_CLASS_ALLOCATOR_IMPL(AssetJsonSerializer, SystemAllocator, 0); + namespace JSR = JsonSerializationResult; - JsonSerializationResult::Result AssetJsonSerializer::Load(void* outputValue, const Uuid& /*outputValueTypeId*/, - const rapidjson::Value& inputValue, JsonDeserializerContext& context) + switch (inputValue.GetType()) { - namespace JSR = JsonSerializationResult; + case rapidjson::kObjectType: + return LoadAsset(outputValue, inputValue, context); + case rapidjson::kArrayType: // fall through + case rapidjson::kNullType: // fall through + case rapidjson::kStringType: // fall through + case rapidjson::kFalseType: // fall through + case rapidjson::kTrueType: // fall through + case rapidjson::kNumberType: + return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported, + "Unsupported type. Asset can only be read from an object."); - switch (inputValue.GetType()) + default: + return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unknown, "Unknown json type encountered for Asset."); + } + } + + JsonSerializationResult::Result AssetJsonSerializer::Store(rapidjson::Value& outputValue, const void* inputValue, + const void* defaultValue, const Uuid& /*valueTypeId*/, JsonSerializerContext& context) + { + namespace JSR = JsonSerializationResult; + + const Asset* instance = reinterpret_cast*>(inputValue); + const Asset* defaultInstance = reinterpret_cast*>(defaultValue); + + JSR::ResultCode result(JSR::Tasks::WriteValue); + { + ScopedContextPath subPathId(context, "m_assetId"); + const auto* id = &instance->GetId(); + const auto* defaultId = defaultInstance ? &defaultInstance->GetId() : nullptr; + rapidjson::Value assetIdValue; + result = ContinueStoring(assetIdValue, id, defaultId, azrtti_typeid(), context); + if (result.GetOutcome() == JSR::Outcomes::Success || result.GetOutcome() == JSR::Outcomes::PartialDefaults) { - case rapidjson::kObjectType: - return LoadAsset(outputValue, inputValue, context); - case rapidjson::kArrayType: // fall through - case rapidjson::kNullType: // fall through - case rapidjson::kStringType: // fall through - case rapidjson::kFalseType: // fall through - case rapidjson::kTrueType: // fall through - case rapidjson::kNumberType: - return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unsupported, - "Unsupported type. Asset can only be read from an object."); - - default: - return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unknown, "Unknown json type encountered for Asset."); + if (!outputValue.IsObject()) + { + outputValue.SetObject(); + } + outputValue.AddMember(rapidjson::StringRef("assetId"), AZStd::move(assetIdValue), context.GetJsonAllocator()); } } - JsonSerializationResult::Result AssetJsonSerializer::Store(rapidjson::Value& outputValue, const void* inputValue, - const void* defaultValue, const Uuid& /*valueTypeId*/, JsonSerializerContext& context) { - namespace JSR = JsonSerializationResult; + const AZ::Data::AssetLoadBehavior autoLoadBehavior = instance->GetAutoLoadBehavior(); + const AZ::Data::AssetLoadBehavior defaultAutoLoadBehavior = defaultInstance ? + defaultInstance->GetAutoLoadBehavior() : AZ::Data::AssetLoadBehavior::Default; - const Asset* instance = reinterpret_cast*>(inputValue); - const Asset* defaultInstance = reinterpret_cast*>(defaultValue); - - JSR::ResultCode result(JSR::Tasks::WriteValue); - { - ScopedContextPath subPathId(context, "m_assetId"); - const auto* id = &instance->GetId(); - const auto* defaultId = defaultInstance ? &defaultInstance->GetId() : nullptr; - rapidjson::Value assetIdValue; - result = ContinueStoring(assetIdValue, id, defaultId, azrtti_typeid(), context); - if (result.GetOutcome() == JSR::Outcomes::Success || result.GetOutcome() == JSR::Outcomes::PartialDefaults) - { - if (!outputValue.IsObject()) - { - outputValue.SetObject(); - } - outputValue.AddMember(rapidjson::StringRef("assetId"), AZStd::move(assetIdValue), context.GetJsonAllocator()); - } - } - - { - const AZ::Data::AssetLoadBehavior autoLoadBehavior = instance->GetAutoLoadBehavior(); - const AZ::Data::AssetLoadBehavior defaultAutoLoadBehavior = defaultInstance ? - defaultInstance->GetAutoLoadBehavior() : AZ::Data::AssetLoadBehavior::Default; - - result.Combine( - ContinueStoringToJsonObjectField(outputValue, "loadBehavior", - &autoLoadBehavior, &defaultAutoLoadBehavior, - azrtti_typeid(), context)); - } - - { - ScopedContextPath subPathHint(context, "m_assetHint"); - const AZStd::string* hint = &instance->GetHint(); - const AZStd::string defaultHint; - rapidjson::Value assetHintValue; - JSR::ResultCode resultHint = ContinueStoring(assetHintValue, hint, &defaultHint, azrtti_typeid(), context); - if (resultHint.GetOutcome() == JSR::Outcomes::Success || resultHint.GetOutcome() == JSR::Outcomes::PartialDefaults) - { - if (!outputValue.IsObject()) - { - outputValue.SetObject(); - } - outputValue.AddMember(rapidjson::StringRef("assetHint"), AZStd::move(assetHintValue), context.GetJsonAllocator()); - } - result.Combine(resultHint); - } - - return context.Report(result, - result.GetProcessing() == JSR::Processing::Completed ? "Successfully stored Asset." : "Failed to store Asset."); + result.Combine( + ContinueStoringToJsonObjectField(outputValue, "loadBehavior", + &autoLoadBehavior, &defaultAutoLoadBehavior, + azrtti_typeid(), context)); } - JsonSerializationResult::Result AssetJsonSerializer::LoadAsset(void* outputValue, const rapidjson::Value& inputValue, - JsonDeserializerContext& context) { - namespace JSR = JsonSerializationResult; - - Asset* instance = reinterpret_cast*>(outputValue); - AssetId id; - JSR::ResultCode result(JSR::Tasks::ReadField); - - SerializedAssetTracker* assetTracker = - context.GetMetadata().Find(); - + ScopedContextPath subPathHint(context, "m_assetHint"); + const AZStd::string* hint = &instance->GetHint(); + const AZStd::string defaultHint; + rapidjson::Value assetHintValue; + JSR::ResultCode resultHint = ContinueStoring(assetHintValue, hint, &defaultHint, azrtti_typeid(), context); + if (resultHint.GetOutcome() == JSR::Outcomes::Success || resultHint.GetOutcome() == JSR::Outcomes::PartialDefaults) { - Data::AssetLoadBehavior loadBehavior = instance->GetAutoLoadBehavior(); - - result = - ContinueLoadingFromJsonObjectField(&loadBehavior, - azrtti_typeid(), - inputValue, "loadBehavior", context); - - instance->SetAutoLoadBehavior(loadBehavior); + if (!outputValue.IsObject()) + { + outputValue.SetObject(); + } + outputValue.AddMember(rapidjson::StringRef("assetHint"), AZStd::move(assetHintValue), context.GetJsonAllocator()); } + result.Combine(resultHint); + } - auto it = inputValue.FindMember("assetId"); - if (it != inputValue.MemberEnd()) + return context.Report(result, + result.GetProcessing() == JSR::Processing::Completed ? "Successfully stored Asset." : "Failed to store Asset."); + } + + JsonSerializationResult::Result AssetJsonSerializer::LoadAsset(void* outputValue, const rapidjson::Value& inputValue, + JsonDeserializerContext& context) + { + namespace JSR = JsonSerializationResult; + + Asset* instance = reinterpret_cast*>(outputValue); + AssetId id; + JSR::ResultCode result(JSR::Tasks::ReadField); + + SerializedAssetTracker* assetTracker = + context.GetMetadata().Find(); + + { + Data::AssetLoadBehavior loadBehavior = instance->GetAutoLoadBehavior(); + + result = + ContinueLoadingFromJsonObjectField(&loadBehavior, + azrtti_typeid(), + inputValue, "loadBehavior", context); + + instance->SetAutoLoadBehavior(loadBehavior); + } + + auto it = inputValue.FindMember("assetId"); + if (it != inputValue.MemberEnd()) + { + ScopedContextPath subPath(context, "assetId"); + result.Combine(ContinueLoading(&id, azrtti_typeid(), it->value, context)); + if (!id.m_guid.IsNull()) { - ScopedContextPath subPath(context, "assetId"); - result.Combine(ContinueLoading(&id, azrtti_typeid(), it->value, context)); - if (!id.m_guid.IsNull()) + *instance = AssetManager::Instance().FindOrCreateAsset(id, instance->GetType(), instance->GetAutoLoadBehavior()); + if (!instance->GetId().IsValid()) { - *instance = AssetManager::Instance().FindOrCreateAsset(id, instance->GetType(), instance->GetAutoLoadBehavior()); - if (!instance->GetId().IsValid()) - { - // If the asset failed to be created, FindOrCreateAsset returns an asset instance with a null - // id. To preserve the asset id in the source json, reset the asset to an empty one, but with - // the right id. - const auto loadBehavior = instance->GetAutoLoadBehavior(); - *instance = Asset(id, instance->GetType()); - instance->SetAutoLoadBehavior(loadBehavior); - } + // If the asset failed to be created, FindOrCreateAsset returns an asset instance with a null + // id. To preserve the asset id in the source json, reset the asset to an empty one, but with + // the right id. + const auto loadBehavior = instance->GetAutoLoadBehavior(); + *instance = Asset(id, instance->GetType()); + instance->SetAutoLoadBehavior(loadBehavior); + } - result.Combine(context.Report(result, "Successfully created Asset with id.")); - } - else if (result.GetProcessing() == JSR::Processing::Completed) - { - result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed, - "Null Asset created.")); - } - else - { - result.Combine(context.Report(result, "Failed to retrieve asset id for Asset.")); - } + result.Combine(context.Report(result, "Successfully created Asset with id.")); + } + else if (result.GetProcessing() == JSR::Processing::Completed) + { + result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed, + "Null Asset created.")); } else { - result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed, - "The asset id is missing, so there's not enough information to create an Asset.")); - } - - it = inputValue.FindMember("assetHint"); - if (it != inputValue.MemberEnd()) - { - ScopedContextPath subPath(context, "assetHint"); - AZStd::string hint; - result.Combine(ContinueLoading(&hint, azrtti_typeid(), it->value, context)); - instance->SetHint(AZStd::move(hint)); - } - else - { - result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed, - "The asset hint is missing for Asset, so it will be left empty.")); - } - - if (assetTracker) - { - assetTracker->FixUpAsset(*instance); - assetTracker->AddAsset(*instance); - } - - bool success = result.GetOutcome() <= JSR::Outcomes::PartialSkip; - bool defaulted = result.GetOutcome() == JSR::Outcomes::DefaultsUsed || result.GetOutcome() == JSR::Outcomes::PartialDefaults; - AZStd::string_view message = - success ? "Successfully loaded information and created instance of Asset." : - defaulted ? "A default id was provided for Asset, so no instance could be created." : - "Not enough information was available to create an instance of Asset or data was corrupted."; - return context.Report(result, message); - } - - void SerializedAssetTracker::SetAssetFixUp(AssetFixUp assetFixUpCallback) - { - m_assetFixUpCallback = AZStd::move(assetFixUpCallback); - } - - void SerializedAssetTracker::FixUpAsset(Asset& asset) - { - if (m_assetFixUpCallback) - { - m_assetFixUpCallback(asset); + result.Combine(context.Report(result, "Failed to retrieve asset id for Asset.")); } } - - void SerializedAssetTracker::AddAsset(Asset asset) + else { - m_serializedAssets.emplace_back(asset); + result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed, + "The asset id is missing, so there's not enough information to create an Asset.")); } - const AZStd::vector>& SerializedAssetTracker::GetTrackedAssets() const + it = inputValue.FindMember("assetHint"); + if (it != inputValue.MemberEnd()) { - return m_serializedAssets; + ScopedContextPath subPath(context, "assetHint"); + AZStd::string hint; + result.Combine(ContinueLoading(&hint, azrtti_typeid(), it->value, context)); + instance->SetHint(AZStd::move(hint)); + } + else + { + result.Combine(context.Report(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed, + "The asset hint is missing for Asset, so it will be left empty.")); } - AZStd::vector>& SerializedAssetTracker::GetTrackedAssets() + if (assetTracker) { - return m_serializedAssets; + assetTracker->FixUpAsset(*instance); + assetTracker->AddAsset(*instance); } - } // namespace Data -} // namespace AZ + bool success = result.GetOutcome() <= JSR::Outcomes::PartialSkip; + bool defaulted = result.GetOutcome() == JSR::Outcomes::DefaultsUsed || result.GetOutcome() == JSR::Outcomes::PartialDefaults; + AZStd::string_view message = + success ? "Successfully loaded information and created instance of Asset." : + defaulted ? "A default id was provided for Asset, so no instance could be created." : + "Not enough information was available to create an instance of Asset or data was corrupted."; + return context.Report(result, message); + } + + void SerializedAssetTracker::SetAssetFixUp(AssetFixUp assetFixUpCallback) + { + m_assetFixUpCallback = AZStd::move(assetFixUpCallback); + } + + void SerializedAssetTracker::FixUpAsset(Asset& asset) + { + if (m_assetFixUpCallback) + { + m_assetFixUpCallback(asset); + } + } + + void SerializedAssetTracker::AddAsset(Asset asset) + { + m_serializedAssets.emplace_back(asset); + } + + const AZStd::vector>& SerializedAssetTracker::GetTrackedAssets() const + { + return m_serializedAssets; + } + + AZStd::vector>& SerializedAssetTracker::GetTrackedAssets() + { + return m_serializedAssets; + } + +} // namespace AZ::Data diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetManager.cpp b/Code/Framework/AzCore/AzCore/Asset/AssetManager.cpp index b03e1affdc..afe3330bd3 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetManager.cpp +++ b/Code/Framework/AzCore/AzCore/Asset/AssetManager.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -27,2169 +26,2162 @@ #include #include -namespace AZ +namespace AZ::Data { - namespace Data + AZ_CVAR(bool, cl_assetLoadWarningEnable, false, nullptr, AZ::ConsoleFunctorFlags::Null, + "Enable warnings that show when AssetHandler::LoadAssetData has exceeded the time set in cl_assetLoadWarningMsThreshold."); + AZ_CVAR(uint32_t, cl_assetLoadWarningMsThreshold, 100, nullptr, AZ::ConsoleFunctorFlags::Null, + "Number of milliseconds that AssetHandler::LoadAssetData can execute for before printing a warning."); + AZ_CVAR(int, cl_assetLoadDelay, 0, nullptr, AZ::ConsoleFunctorFlags::Null, + "Number of milliseconds to artifically delay an asset load."); + AZ_CVAR(bool, cl_assetLoadError, false, nullptr, AZ::ConsoleFunctorFlags::Null, + "Enable failure of all asset loads."); + + static constexpr char kAssetDBInstanceVarName[] = "AssetDatabaseInstance"; + + /* + * This is the base class for Async AssetDatabase jobs + */ + class AssetDatabaseAsyncJob + : public AssetDatabaseJob + , public Job { - AZ_CVAR(bool, cl_assetLoadWarningEnable, false, nullptr, AZ::ConsoleFunctorFlags::Null, - "Enable warnings that show when AssetHandler::LoadAssetData has exceeded the time set in cl_assetLoadWarningMsThreshold."); - AZ_CVAR(uint32_t, cl_assetLoadWarningMsThreshold, 100, nullptr, AZ::ConsoleFunctorFlags::Null, - "Number of milliseconds that AssetHandler::LoadAssetData can execute for before printing a warning."); - AZ_CVAR(int, cl_assetLoadDelay, 0, nullptr, AZ::ConsoleFunctorFlags::Null, - "Number of milliseconds to artifically delay an asset load."); - AZ_CVAR(bool, cl_assetLoadError, false, nullptr, AZ::ConsoleFunctorFlags::Null, - "Enable failure of all asset loads."); - - static constexpr char kAssetDBInstanceVarName[] = "AssetDatabaseInstance"; - - /* - * This is the base class for Async AssetDatabase jobs - */ - class AssetDatabaseAsyncJob - : public AssetDatabaseJob - , public Job + public: + AssetDatabaseAsyncJob(JobContext* jobContext, bool deleteWhenDone, AssetManager* owner, const Asset& asset, AssetHandler* assetHandler) + : AssetDatabaseJob(owner, asset, assetHandler) + , Job(deleteWhenDone, jobContext) { - public: - AssetDatabaseAsyncJob(JobContext* jobContext, bool deleteWhenDone, AssetManager* owner, const Asset& asset, AssetHandler* assetHandler) - : AssetDatabaseJob(owner, asset, assetHandler) - , Job(deleteWhenDone, jobContext) - { - } + } - ~AssetDatabaseAsyncJob() override - { - } - }; - - /** - * Internally allows threads blocking on asset loads to be notified on load completion. - */ - class BlockingAssetLoadEvents - : public EBusTraits + ~AssetDatabaseAsyncJob() override { - public: - ////////////////////////////////////////////////////////////////////////// - // EBusTraits overrides - static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; - using BusIdType = AssetId; - using MutexType = AZStd::recursive_mutex; - - template - struct AssetJobConnectionPolicy - : public EBusConnectionPolicy - { - static void Connect(typename Bus::BusPtr& busPtr, typename Bus::Context& context, typename Bus::HandlerNode& handler, typename Bus::Context::ConnectLockGuard& connectLock, const typename Bus::BusIdType& id = 0) - { - typename Bus::BusIdType actualId = AssetInternal::ResolveAssetId(id); - EBusConnectionPolicy::Connect(busPtr, context, handler, connectLock, actualId); - - // If the asset is loaded or failed already, deliver the status update immediately - // Note that we check IsReady here, ReadyPreNotify must be tested because there is - // a small gap between ReadyPreNotify and Ready where the callback could be missed. - // Also note because the Asset<> reference isn't passed around anywhere, it doesn't matter - // what the AssetLoadBehavior is set to, as it will never make it back to any callers. - Asset assetData(AssetInternal::GetAssetData(actualId, AssetLoadBehavior::Default)); - if (assetData) - { - if (assetData->IsReady() || assetData->IsError()) - { - connectLock.unlock(); - handler->OnLoadComplete(); - } - } - } - }; - - template - using ConnectionPolicy = AssetJobConnectionPolicy; - - virtual void OnLoadComplete() = 0; - virtual void OnLoadCanceled(AssetId assetId) = 0; - }; - - using BlockingAssetLoadBus = EBus; - - /* - * This class processes async AssetDatabase load jobs - */ - class LoadAssetJob - : public AssetDatabaseAsyncJob - { - public: - AZ_CLASS_ALLOCATOR(LoadAssetJob, ThreadPoolAllocator, 0); - - LoadAssetJob(AssetManager* owner, const Asset& asset, - AZStd::shared_ptr dataStream, bool isReload, AZ::IO::IStreamerTypes::RequestStatus requestState, - AssetHandler* handler, const AssetLoadParameters& loadParams, bool signalLoaded) - : AssetDatabaseAsyncJob(JobContext::GetGlobalContext(), true, owner, asset, handler) - , m_dataStream(dataStream) - , m_isReload(isReload) - , m_requestState(requestState) - , m_loadParams(loadParams) - , m_signalLoaded(signalLoaded) - { - AZ_Assert(m_dataStream, "Data stream pointer received through the callback from AZ::IO::Streamer is invalid."); - - AZ_Assert((m_requestState == AZ::IO::IStreamerTypes::RequestStatus::Completed) - || (m_requestState == AZ::IO::IStreamerTypes::RequestStatus::Canceled) - || (m_requestState == AZ::IO::IStreamerTypes::RequestStatus::Failed), - "AssetManager::LoadAssetJob was called with an unexpected streamer state: %i", m_requestState); - } - - ~LoadAssetJob() override - { - } - - void Process() override - { - Asset asset = m_asset.GetStrongReference(); - - // Verify that we didn't somehow get here after the Asset Manager has finished shutting down. - AZ_Assert(AssetManager::IsReady(), "Asset Manager shutdown didn't clean up pending asset loads properly."); - if (!AssetManager::IsReady()) - { - return; - } - - bool shouldCancel = m_owner->ShouldCancelAllActiveJobs() - || !asset // No outstanding references, so cancel the load - || m_requestState == AZ::IO::IStreamerTypes::RequestStatus::Canceled; - - if (shouldCancel) - { - BlockingAssetLoadBus::Event(m_asset.GetId(), &BlockingAssetLoadBus::Events::OnLoadCanceled, m_asset.GetId()); - AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetCanceled, m_asset.GetId()); - } - else - { - - AZ_PROFILE_SCOPE(AzCore, "AZ::Data::LoadAssetJob::Process: %s", - asset.GetHint().c_str()); - - AZ_ASSET_ATTACH_TO_SCOPE(this); - - if (m_owner->ValidateAndRegisterAssetLoading(asset)) - { - LoadAndSignal(asset); - } - } - } - - void LoadAndSignal(Asset& asset) - { - const bool loadSucceeded = LoadData(); - - if (m_signalLoaded && loadSucceeded) - { - AZ_Assert(!m_isReload, "OnAssetDataLoaded signal isn't supported for asset reloads."); - // This asset has preload dependencies, we need to evaluate whether they're all ready before calling PostLoad - AssetLoadBus::Event(asset.GetId(), &AssetLoadBus::Events::OnAssetDataLoaded, asset); - } - else - { - // As long as we don't need to signal preload dependencies, just finish the load whether or not it was successful. - m_owner->PostLoad(asset, loadSucceeded, m_isReload, m_assetHandler); - } - } - - bool LoadData() - { - Asset asset = m_asset.GetStrongReference(); - - if(cl_assetLoadDelay > 0) - { - AZ_PROFILE_SCOPE(AzCore, "LoadData suspended"); - AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(cl_assetLoadDelay)); - } - - AZ_ASSET_NAMED_SCOPE(asset.GetHint().c_str()); - bool loadedSuccessfully = false; - - if (!cl_assetLoadError && m_requestState == AZ::IO::IStreamerTypes::RequestStatus::Completed) - { - if (m_dataStream->IsFullyLoaded()) - { - AssetHandler::LoadResult result = - m_assetHandler->LoadAssetDataFromStream(asset, m_dataStream, m_loadParams.m_assetLoadFilterCB); - loadedSuccessfully = (result == AssetHandler::LoadResult::LoadComplete); - } - } - - return loadedSuccessfully; - } - - private: - AZStd::shared_ptr m_dataStream; - AssetLoadParameters m_loadParams{}; - AZ::IO::IStreamerTypes::RequestStatus m_requestState{ AZ::IO::IStreamerTypes::RequestStatus::Pending}; - bool m_isReload{ false }; - bool m_signalLoaded{ false }; - }; - - - /** - * Utility class to wait when a blocking load is requested for an asset that's already loading asynchronously. - * Uses the BlockingAssetLoadBus to detect completion, and a semaphore to signal it. - */ - - class WaitForAsset - : public BlockingAssetLoadBus::Handler - { - public: - AZ_CLASS_ALLOCATOR(WaitForAsset, ThreadPoolAllocator, 0); - - - WaitForAsset(const Asset& assetToWaitFor, bool shouldDispatchEvents) - : m_assetData(assetToWaitFor) - , m_shouldDispatchEvents(shouldDispatchEvents) - { - // Track all blocking requests with the AssetManager. This enables load jobs to potentially get routed - // to the thread that's currently blocking waiting on the load job to complete. - AssetManager::Instance().AddBlockingRequest(m_assetData.GetId(), this); - } - - ~WaitForAsset() override - { - // Stop tracking the blocking request, which will ensure that load jobs won't be provided to this instance - // for processing. - AssetManager::Instance().RemoveBlockingRequest(m_assetData.GetId(), this); - - // It shouldn't be possible to destroy a blocking load request before the load job that it's blocked on - // has been processed, so assert if it ever happens, but make sure to process it just in case. - if (m_loadJob) - { - // (If a valid case is ever found where this can occur, it should be safe to remove the assert) - AZ_Assert(false, "Blocking load request is being deleted before it could process the blocking load."); - ProcessLoadJob(); - } - } - - // Provides a blocked load with a LoadJob to process while it's blocking. - // Returns true if it can be queued, false if it can't. - bool QueueAssetLoadJob(LoadAssetJob* loadJob) - { - if(m_shouldDispatchEvents) - { - // Any load job that is going to be dispatching events should not accept additional work since dispatching events - // can lead to more code that's blocking on an asset load which prevents us from finishing the dispatch - // and doing the assigned work. - // Specifically, if dispatching leads to a second block call, the load job will be assigned to the first block call, - // which will never be completed until the second block call is finished. If both blocks are on the same asset, - // we end up deadlocked. - return false; - } - - AZStd::scoped_lock mutexLock(m_loadJobMutex); - - AZ_Assert(!m_loadJob, "Trying to process multiple load jobs for the same asset with the same blocking handler."); - if (!m_loadJob) - { - m_loadJob = loadJob; - m_waitEvent.release(); - return true; - } - - return false; - } - - void OnLoadComplete() override - { - Finish(); - } - - void OnLoadCanceled([[maybe_unused]] const AssetId assetId) override - { - Finish(); - } - - void WaitUntilReady() - { - BusConnect(m_assetData.GetId()); - - Wait(); - - BusDisconnect(m_assetData.GetId()); - } - - protected: - void Wait() - { - AZ_PROFILE_SCOPE(AzCore, "WaitForAsset - %s", m_assetData.GetHint().c_str()); - - // Continue to loop until the load completes. (Most of the time in the loop will be spent in a thread-blocking state) - while (!m_loadCompleted) - { - if (m_shouldDispatchEvents) - { - // The event will wake up either when the load finishes, a load job is queued for processing, or every - // N milliseconds to see if it should dispatch events. - constexpr int MaxWaitBetweenDispatchMs = 1; - while (!m_waitEvent.try_acquire_for(AZStd::chrono::milliseconds(MaxWaitBetweenDispatchMs))) - { - AssetManager::Instance().DispatchEvents(); - } - } - else - { - - // Don't wake up until a load job is queued for processing or the load is entirely finished. - m_waitEvent.acquire(); - } - - // Check to see if any load jobs have been provided for this thread to process. - // (Load jobs will attempt to reuse blocked threads before spinning off new job threads) - ProcessLoadJob(); - } - - // Pump the AssetBus function queue once more after the load has completed in case additional - // functions have been queued between the last call to DispatchEvents and the completion - // of the current load job - if (m_shouldDispatchEvents) - { - AssetManager::Instance().DispatchEvents(); - } - } - - void Finish() - { - AZ_PROFILE_FUNCTION(AzCore); - m_loadCompleted = true; - m_waitEvent.release(); - } - - bool ProcessLoadJob() - { - AZStd::scoped_lock mutexLock(m_loadJobMutex); - bool jobProcessed = false; - - if (m_loadJob) - { - m_loadJob->Process(); - if (m_loadJob->IsAutoDelete()) - { - delete m_loadJob; - } - m_loadJob = nullptr; - jobProcessed = true; - } - - return jobProcessed; - } - - Asset m_assetData; - AZStd::binary_semaphore m_waitEvent; - const bool m_shouldDispatchEvents{ false }; - LoadAssetJob* m_loadJob{ nullptr }; - AZStd::mutex m_loadJobMutex; - AZStd::atomic_bool m_loadCompleted{ false }; - }; - - - /* - * This class processes async AssetDatabase save jobs - */ - class SaveAssetJob - : public AssetDatabaseAsyncJob - { - public: - AZ_CLASS_ALLOCATOR(SaveAssetJob, ThreadPoolAllocator, 0); - - SaveAssetJob(JobContext* jobContext, AssetManager* owner, const Asset& asset, AssetHandler* assetHandler) - : AssetDatabaseAsyncJob(jobContext, true, owner, asset, assetHandler) - { - } - - ~SaveAssetJob() override - { - } - - void Process() override - { - SaveAsset(); - } - - void SaveAsset() - { - auto asset = m_asset.GetStrongReference(); - AZ_PROFILE_FUNCTION(AzCore); - bool isSaved = false; - AssetStreamInfo saveInfo = m_owner->GetSaveStreamInfoForAsset(asset.GetId(), asset.GetType()); - if (saveInfo.IsValid()) - { - IO::FileIOStream stream(saveInfo.m_streamName.c_str(), saveInfo.m_streamFlags); - stream.Seek(saveInfo.m_dataOffset, IO::GenericStream::SeekMode::ST_SEEK_BEGIN); - isSaved = m_assetHandler->SaveAssetData(asset, &stream); - } - // queue broadcast message for delivery on game thread - AssetBus::QueueEvent(asset.GetId(), &AssetBus::Events::OnAssetSaved, asset, isSaved); - } - }; + } + }; + /** + * Internally allows threads blocking on asset loads to be notified on load completion. + */ + class BlockingAssetLoadEvents + : public EBusTraits + { + public: ////////////////////////////////////////////////////////////////////////// - // Globals - EnvironmentVariable AssetManager::s_assetDB = nullptr; - ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; + using BusIdType = AssetId; + using MutexType = AZStd::recursive_mutex; - //========================================================================= - // AssetDatabaseJob - // [4/3/2014] - //========================================================================= - AssetDatabaseJob::AssetDatabaseJob(AssetManager* owner, const Asset& asset, AssetHandler* assetHandler) + template + struct AssetJobConnectionPolicy + : public EBusConnectionPolicy { - m_owner = owner; - m_asset = AssetInternal::WeakAsset(asset); - m_assetHandler = assetHandler; - owner->AddJob(this); - } - - //========================================================================= - // ~AssetDatabaseJob - // [4/3/2014] - //========================================================================= - AssetDatabaseJob::~AssetDatabaseJob() - { - // Make sure that the asset reference is cleared out prior to removing the job registration. - // It's possible that clearing this reference will trigger the cleanup code for the asset, so if we wait for the - // destructor to clear it *after* the RemoveJob call, then HasActiveJobsOrStreamerRequests() will be able to return - // false even though the job is still executing asset-related code. - m_asset = {}; - m_owner->RemoveJob(this); - } - - //========================================================================= - // Create - // [6/12/2012] - //========================================================================= - bool AssetManager::Create(const Descriptor& desc) - { - AZ_Assert(!s_assetDB || !s_assetDB.Get(), "AssetManager already created!"); - - if (!s_assetDB) + static void Connect(typename Bus::BusPtr& busPtr, typename Bus::Context& context, typename Bus::HandlerNode& handler, typename Bus::Context::ConnectLockGuard& connectLock, const typename Bus::BusIdType& id = 0) { - s_assetDB = Environment::CreateVariable(kAssetDBInstanceVarName); - } - if (!s_assetDB.Get()) - { - s_assetDB.Set(aznew AssetManager(desc)); - } + typename Bus::BusIdType actualId = AssetInternal::ResolveAssetId(id); + EBusConnectionPolicy::Connect(busPtr, context, handler, connectLock, actualId); - return true; - } - - //========================================================================= - // Destroy - // [6/12/2012] - //========================================================================= - void AssetManager::Destroy() - { - AZ_Assert(s_assetDB, "AssetManager not created!"); - delete (*s_assetDB); - *s_assetDB = nullptr; - } - - //========================================================================= - // IsReady - //========================================================================= - bool AssetManager::IsReady() - { - if (!s_assetDB) - { - s_assetDB = Environment::FindVariable(kAssetDBInstanceVarName); - } - - return s_assetDB && *s_assetDB; - } - - //========================================================================= - // Instance - //========================================================================= - AssetManager& AssetManager::Instance() - { - if (!s_assetDB) - { - s_assetDB = Environment::FindVariable(kAssetDBInstanceVarName); - } - - AZ_Assert(s_assetDB && *s_assetDB, "AssetManager not created!"); - return *(*s_assetDB); - } - - bool AssetManager::SetInstance(AssetManager* assetManager) - { - if (!s_assetDB) - { - s_assetDB = Environment::CreateVariable(kAssetDBInstanceVarName); - } - - // The old instance needs to be null or else it will leak on the assignment. - AZ_Assert(!(*s_assetDB), - "AssetManager::SetInstance was called without first destroying the old instance and setting it to nullptr. " - "This will cause the previous AssetManager instance to leak." ); - - (*s_assetDB) = assetManager; - return true; - } - - //========================================================================= - // AssetDatabase - // [6/12/2012] - //========================================================================= - AssetManager::AssetManager(const AssetManager::Descriptor& desc) - : m_mainThreadId(AZStd::this_thread::get_id()) - , m_debugAssetEvents(AZ::Interface::Get()) - { - (void)desc; - - AssetManagerBus::Handler::BusConnect(); - } - - //========================================================================= - // ~AssetManager - // [6/12/2012] - //========================================================================= - AssetManager::~AssetManager() - { - PrepareShutDown(); - - // Acquire the asset lock to make sure nobody else is trying to do anything fancy with assets - AZStd::scoped_lock assetLock(m_assetMutex); - - while (!m_handlers.empty()) - { - AssetHandlerMap::iterator it = m_handlers.begin(); - AssetHandler* handler = it->second; - UnregisterHandler(handler); - delete handler; - } - - AssetManagerBus::Handler::BusDisconnect(); - } - - //========================================================================= - // DispatchEvents - // [04/02/2014] - //========================================================================= - void AssetManager::DispatchEvents() - { - AZ_PROFILE_FUNCTION(AzCore); - AssetManagerNotificationBus::Broadcast(&AssetManagerNotificationBus::Events::OnAssetEventsDispatchBegin); - while (AssetBus::QueuedEventCount()) - { - AssetBus::ExecuteQueuedEvents(); - } - AssetManagerNotificationBus::Broadcast(&AssetManagerNotificationBus::Events::OnAssetEventsDispatchEnd); - } - - //========================================================================= - void AssetManager::SetAssetInfoUpgradingEnabled(bool enable) - { - m_assetInfoUpgradingEnabled = enable; - } - - bool AssetManager::GetAssetInfoUpgradingEnabled() const - { -#if defined(_RELEASE) - // in release ("FINAL") builds, we never do this. - return false; -#else - return m_assetInfoUpgradingEnabled; -#endif - } - - bool AssetManager::ShouldCancelAllActiveJobs() const - { - return m_cancelAllActiveJobs; - } - - void AssetManager::SetParallelDependentLoadingEnabled(bool enable) - { - m_enableParallelDependentLoading = enable; - } - - bool AssetManager::GetParallelDependentLoadingEnabled() const - { - return m_enableParallelDependentLoading; - } - - void AssetManager::PrepareShutDown() - { - m_cancelAllActiveJobs = true; - - // We want to ensure that no active load jobs are in flight and - // therefore we need to wait till all jobs have completed. Please note that jobs get deleted automatically once they complete. - WaitForActiveJobsAndStreamerRequestsToFinish(); - - m_ownedAssetContainerLookup.clear(); - m_ownedAssetContainers.clear(); - m_assetContainers.clear(); - - // Ensure that there are no queued events on the AssetBus - DispatchEvents(); - } - - void AssetManager::WaitForActiveJobsAndStreamerRequestsToFinish() - { - while (HasActiveJobsOrStreamerRequests()) - { - DispatchEvents(); - AZStd::this_thread::yield(); - } - } - - //========================================================================= - // RegisterHandler - // [7/9/2014] - //========================================================================= - void AssetManager::RegisterHandler(AssetHandler* handler, const AssetType& assetType) - { - AZ_Error("AssetDatabase", handler != nullptr, "Attempting to register a null asset handler!"); - if (handler) - { - if (m_handlers.insert(AZStd::make_pair(assetType, handler)).second) + // If the asset is loaded or failed already, deliver the status update immediately + // Note that we check IsReady here, ReadyPreNotify must be tested because there is + // a small gap between ReadyPreNotify and Ready where the callback could be missed. + // Also note because the Asset<> reference isn't passed around anywhere, it doesn't matter + // what the AssetLoadBehavior is set to, as it will never make it back to any callers. + Asset assetData(AssetInternal::GetAssetData(actualId, AssetLoadBehavior::Default)); + if (assetData) { - handler->m_nHandledTypes++; - } - else - { - AZ_Error("AssetDatabase", false, "Asset type %s already has a handler registered! New registration ignored!", assetType.ToString().c_str()); - } - } - } - - //========================================================================= - // UnregisterHandler - // [7/9/2014] - //========================================================================= - void AssetManager::UnregisterHandler(AssetHandler* handler) - { - AZ_Error("AssetDatabase", handler != nullptr, "Attempting to unregister a null asset handler!"); - if (handler) - { - for (AssetHandlerMap::iterator it = m_handlers.begin(); it != m_handlers.end(); /*++it*/) - { - if (it->second == handler) + if (assetData->IsReady() || assetData->IsError()) { - // When unregistering asset handlers, it's possible that there are still some load jobs that have "finished" but - // haven't destroyed themselves yet by the time the asset handler gets unregistered. LoadAssetJob contains a weak - // asset reference that doesn't clear until the job is destroyed, which happens *after* the OnAssetReady - // notification is triggered. If the thread gets swapped out between the OnAssetReady and the job destruction, - // the job will still be holding onto an asset reference for this asset handler, and it will trigger the - // error below. To ensure that this case doesn't happen, we will instead call - // WaitForActiveJobsAndStreamerRequestsToFinish() to make sure that any in-process jobs have completely cleaned - // themselves up before proceeding forward. - // One example of this pattern occurs in unit tests, where the test loads an asset, validates it, destroys the - // asset, and unregisters the handler, all in rapid succession. This would extremely infrequently - // (~1 per 5000 runs) trigger the error case if we didn't wait for the jobs to finish here. - WaitForActiveJobsAndStreamerRequestsToFinish(); - - { - // this scope is used to control the scope of the lock. - AZStd::lock_guard assetLock(m_assetMutex); - for (const auto &assetEntry : m_assets) - { - // is the handler that handles this type, this handler we're removing? - if (assetEntry.second->m_registeredHandler == handler) - { - AZ_Error("AssetManager", false, "Asset handler for %s is being removed, when assetid %s is still loaded!\n", - assetEntry.second->GetType().ToString().c_str(), - assetEntry.second->GetId().ToString().c_str()); // this will write the name IF AVAILABLE - assetEntry.second->UnregisterWithHandler(); - } - } - } - it = m_handlers.erase(it); - handler->m_nHandledTypes--; - } - else - { - ++it; + connectLock.unlock(); + handler->OnLoadComplete(); } } } + }; + + template + using ConnectionPolicy = AssetJobConnectionPolicy; + + virtual void OnLoadComplete() = 0; + virtual void OnLoadCanceled(AssetId assetId) = 0; + }; + + using BlockingAssetLoadBus = EBus; + + /* + * This class processes async AssetDatabase load jobs + */ + class LoadAssetJob + : public AssetDatabaseAsyncJob + { + public: + AZ_CLASS_ALLOCATOR(LoadAssetJob, ThreadPoolAllocator, 0); + + LoadAssetJob(AssetManager* owner, const Asset& asset, + AZStd::shared_ptr dataStream, bool isReload, AZ::IO::IStreamerTypes::RequestStatus requestState, + AssetHandler* handler, const AssetLoadParameters& loadParams, bool signalLoaded) + : AssetDatabaseAsyncJob(JobContext::GetGlobalContext(), true, owner, asset, handler) + , m_dataStream(dataStream) + , m_isReload(isReload) + , m_requestState(requestState) + , m_loadParams(loadParams) + , m_signalLoaded(signalLoaded) + { + AZ_Assert(m_dataStream, "Data stream pointer received through the callback from AZ::IO::Streamer is invalid."); + + AZ_Assert((m_requestState == AZ::IO::IStreamerTypes::RequestStatus::Completed) + || (m_requestState == AZ::IO::IStreamerTypes::RequestStatus::Canceled) + || (m_requestState == AZ::IO::IStreamerTypes::RequestStatus::Failed), + "AssetManager::LoadAssetJob was called with an unexpected streamer state: %i", m_requestState); } - //========================================================================= - // RegisterCatalog - // [8/27/2012] - //========================================================================= - void AssetManager::RegisterCatalog(AssetCatalog* catalog, const AssetType& assetType) + ~LoadAssetJob() override { - AZ_Error("AssetDatabase", catalog != nullptr, "Attempting to register a null catalog!"); - if (catalog) - { - AZStd::scoped_lock l(m_catalogMutex); - if (m_catalogs.insert(AZStd::make_pair(assetType, catalog)).second == false) - { - AZ_Error("AssetDatabase", false, "Asset type %s already has a catalog registered! New registration ignored!", assetType.ToString().c_str()); - } - } } - //========================================================================= - // UnregisterCatalog - // [8/27/2012] - //========================================================================= - void AssetManager::UnregisterCatalog(AssetCatalog* catalog) + void Process() override { - AZ_Error("AssetDatabase", catalog != nullptr, "Attempting to unregister a null catalog!"); - if (catalog) - { - AZStd::scoped_lock l(m_catalogMutex); - for (AssetCatalogMap::iterator iter = m_catalogs.begin(); iter != m_catalogs.end(); ) - { - if (iter->second == catalog) - { - iter = m_catalogs.erase(iter); - } - else - { - ++iter; - } + Asset asset = m_asset.GetStrongReference(); - } - } - } - - //========================================================================= - // GetHandledAssetTypes - // [6/27/2016] - //========================================================================= - void AssetManager::GetHandledAssetTypes(AssetCatalog* catalog, AZStd::vector& assetTypes) - { - for (AssetCatalogMap::iterator iter = m_catalogs.begin(); iter != m_catalogs.end(); iter++) - { - if (iter->second == catalog) - { - assetTypes.push_back(iter->first); - } - } - } - - void AssetManager::SuspendAssetRelease() - { - ++m_suspendAssetRelease; - } - - void AssetManager::ResumeAssetRelease() - { - if(--m_suspendAssetRelease != 0) + // Verify that we didn't somehow get here after the Asset Manager has finished shutting down. + AZ_Assert(AssetManager::IsReady(), "Asset Manager shutdown didn't clean up pending asset loads properly."); + if (!AssetManager::IsReady()) { return; } - AZStd::scoped_lock assetLock(m_assetMutex); - // First, release any containers that were loading this asset - for (auto asset = m_assets.begin();asset != m_assets.end();) + bool shouldCancel = m_owner->ShouldCancelAllActiveJobs() + || !asset // No outstanding references, so cancel the load + || m_requestState == AZ::IO::IStreamerTypes::RequestStatus::Canceled; + + if (shouldCancel) { - if (asset->second->m_useCount == 0) + BlockingAssetLoadBus::Event(m_asset.GetId(), &BlockingAssetLoadBus::Events::OnLoadCanceled, m_asset.GetId()); + AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetCanceled, m_asset.GetId()); + } + else + { + + AZ_PROFILE_SCOPE(AzCore, "AZ::Data::LoadAssetJob::Process: %s", + asset.GetHint().c_str()); + + if (m_owner->ValidateAndRegisterAssetLoading(asset)) { - auto releaseAsset = asset->second; - ++asset; - ReleaseAssetContainersForAsset(releaseAsset); + LoadAndSignal(asset); + } + } + } + + void LoadAndSignal(Asset& asset) + { + const bool loadSucceeded = LoadData(); + + if (m_signalLoaded && loadSucceeded) + { + AZ_Assert(!m_isReload, "OnAssetDataLoaded signal isn't supported for asset reloads."); + // This asset has preload dependencies, we need to evaluate whether they're all ready before calling PostLoad + AssetLoadBus::Event(asset.GetId(), &AssetLoadBus::Events::OnAssetDataLoaded, asset); + } + else + { + // As long as we don't need to signal preload dependencies, just finish the load whether or not it was successful. + m_owner->PostLoad(asset, loadSucceeded, m_isReload, m_assetHandler); + } + } + + bool LoadData() + { + Asset asset = m_asset.GetStrongReference(); + + if(cl_assetLoadDelay > 0) + { + AZ_PROFILE_SCOPE(AzCore, "LoadData suspended"); + AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(cl_assetLoadDelay)); + } + + bool loadedSuccessfully = false; + + if (!cl_assetLoadError && m_requestState == AZ::IO::IStreamerTypes::RequestStatus::Completed) + { + if (m_dataStream->IsFullyLoaded()) + { + AssetHandler::LoadResult result = + m_assetHandler->LoadAssetDataFromStream(asset, m_dataStream, m_loadParams.m_assetLoadFilterCB); + loadedSuccessfully = (result == AssetHandler::LoadResult::LoadComplete); + } + } + + return loadedSuccessfully; + } + + private: + AZStd::shared_ptr m_dataStream; + AssetLoadParameters m_loadParams{}; + AZ::IO::IStreamerTypes::RequestStatus m_requestState{ AZ::IO::IStreamerTypes::RequestStatus::Pending}; + bool m_isReload{ false }; + bool m_signalLoaded{ false }; + }; + + + /** + * Utility class to wait when a blocking load is requested for an asset that's already loading asynchronously. + * Uses the BlockingAssetLoadBus to detect completion, and a semaphore to signal it. + */ + + class WaitForAsset + : public BlockingAssetLoadBus::Handler + { + public: + AZ_CLASS_ALLOCATOR(WaitForAsset, ThreadPoolAllocator, 0); + + + WaitForAsset(const Asset& assetToWaitFor, bool shouldDispatchEvents) + : m_assetData(assetToWaitFor) + , m_shouldDispatchEvents(shouldDispatchEvents) + { + // Track all blocking requests with the AssetManager. This enables load jobs to potentially get routed + // to the thread that's currently blocking waiting on the load job to complete. + AssetManager::Instance().AddBlockingRequest(m_assetData.GetId(), this); + } + + ~WaitForAsset() override + { + // Stop tracking the blocking request, which will ensure that load jobs won't be provided to this instance + // for processing. + AssetManager::Instance().RemoveBlockingRequest(m_assetData.GetId(), this); + + // It shouldn't be possible to destroy a blocking load request before the load job that it's blocked on + // has been processed, so assert if it ever happens, but make sure to process it just in case. + if (m_loadJob) + { + // (If a valid case is ever found where this can occur, it should be safe to remove the assert) + AZ_Assert(false, "Blocking load request is being deleted before it could process the blocking load."); + ProcessLoadJob(); + } + } + + // Provides a blocked load with a LoadJob to process while it's blocking. + // Returns true if it can be queued, false if it can't. + bool QueueAssetLoadJob(LoadAssetJob* loadJob) + { + if(m_shouldDispatchEvents) + { + // Any load job that is going to be dispatching events should not accept additional work since dispatching events + // can lead to more code that's blocking on an asset load which prevents us from finishing the dispatch + // and doing the assigned work. + // Specifically, if dispatching leads to a second block call, the load job will be assigned to the first block call, + // which will never be completed until the second block call is finished. If both blocks are on the same asset, + // we end up deadlocked. + return false; + } + + AZStd::scoped_lock mutexLock(m_loadJobMutex); + + AZ_Assert(!m_loadJob, "Trying to process multiple load jobs for the same asset with the same blocking handler."); + if (!m_loadJob) + { + m_loadJob = loadJob; + m_waitEvent.release(); + return true; + } + + return false; + } + + void OnLoadComplete() override + { + Finish(); + } + + void OnLoadCanceled([[maybe_unused]] const AssetId assetId) override + { + Finish(); + } + + void WaitUntilReady() + { + BusConnect(m_assetData.GetId()); + + Wait(); + + BusDisconnect(m_assetData.GetId()); + } + + protected: + void Wait() + { + AZ_PROFILE_SCOPE(AzCore, "WaitForAsset - %s", m_assetData.GetHint().c_str()); + + // Continue to loop until the load completes. (Most of the time in the loop will be spent in a thread-blocking state) + while (!m_loadCompleted) + { + if (m_shouldDispatchEvents) + { + // The event will wake up either when the load finishes, a load job is queued for processing, or every + // N milliseconds to see if it should dispatch events. + constexpr int MaxWaitBetweenDispatchMs = 1; + while (!m_waitEvent.try_acquire_for(AZStd::chrono::milliseconds(MaxWaitBetweenDispatchMs))) + { + AssetManager::Instance().DispatchEvents(); + } } else { - ++asset; + + // Don't wake up until a load job is queued for processing or the load is entirely finished. + m_waitEvent.acquire(); } + + // Check to see if any load jobs have been provided for this thread to process. + // (Load jobs will attempt to reuse blocked threads before spinning off new job threads) + ProcessLoadJob(); } - // Second, release the assets themselves - - AZStd::vector assetsToRelease; - - for(auto&& asset : m_assets) + // Pump the AssetBus function queue once more after the load has completed in case additional + // functions have been queued between the last call to DispatchEvents and the completion + // of the current load job + if (m_shouldDispatchEvents) { - if(asset.second->m_weakUseCount == 0) + AssetManager::Instance().DispatchEvents(); + } + } + + void Finish() + { + AZ_PROFILE_FUNCTION(AzCore); + m_loadCompleted = true; + m_waitEvent.release(); + } + + bool ProcessLoadJob() + { + AZStd::scoped_lock mutexLock(m_loadJobMutex); + bool jobProcessed = false; + + if (m_loadJob) + { + m_loadJob->Process(); + if (m_loadJob->IsAutoDelete()) { - // Keep a separate list of assets to release, because releasing them will modify the m_assets list that we're - // currently looping on. - assetsToRelease.push_back(asset.second); + delete m_loadJob; + } + m_loadJob = nullptr; + jobProcessed = true; + } + + return jobProcessed; + } + + Asset m_assetData; + AZStd::binary_semaphore m_waitEvent; + const bool m_shouldDispatchEvents{ false }; + LoadAssetJob* m_loadJob{ nullptr }; + AZStd::mutex m_loadJobMutex; + AZStd::atomic_bool m_loadCompleted{ false }; + }; + + + /* + * This class processes async AssetDatabase save jobs + */ + class SaveAssetJob + : public AssetDatabaseAsyncJob + { + public: + AZ_CLASS_ALLOCATOR(SaveAssetJob, ThreadPoolAllocator, 0); + + SaveAssetJob(JobContext* jobContext, AssetManager* owner, const Asset& asset, AssetHandler* assetHandler) + : AssetDatabaseAsyncJob(jobContext, true, owner, asset, assetHandler) + { + } + + ~SaveAssetJob() override + { + } + + void Process() override + { + SaveAsset(); + } + + void SaveAsset() + { + auto asset = m_asset.GetStrongReference(); + AZ_PROFILE_FUNCTION(AzCore); + bool isSaved = false; + AssetStreamInfo saveInfo = m_owner->GetSaveStreamInfoForAsset(asset.GetId(), asset.GetType()); + if (saveInfo.IsValid()) + { + IO::FileIOStream stream(saveInfo.m_streamName.c_str(), saveInfo.m_streamFlags); + stream.Seek(saveInfo.m_dataOffset, IO::GenericStream::SeekMode::ST_SEEK_BEGIN); + isSaved = m_assetHandler->SaveAssetData(asset, &stream); + } + // queue broadcast message for delivery on game thread + AssetBus::QueueEvent(asset.GetId(), &AssetBus::Events::OnAssetSaved, asset, isSaved); + } + }; + + ////////////////////////////////////////////////////////////////////////// + // Globals + EnvironmentVariable AssetManager::s_assetDB = nullptr; + ////////////////////////////////////////////////////////////////////////// + + //========================================================================= + // AssetDatabaseJob + // [4/3/2014] + //========================================================================= + AssetDatabaseJob::AssetDatabaseJob(AssetManager* owner, const Asset& asset, AssetHandler* assetHandler) + { + m_owner = owner; + m_asset = AssetInternal::WeakAsset(asset); + m_assetHandler = assetHandler; + owner->AddJob(this); + } + + //========================================================================= + // ~AssetDatabaseJob + // [4/3/2014] + //========================================================================= + AssetDatabaseJob::~AssetDatabaseJob() + { + // Make sure that the asset reference is cleared out prior to removing the job registration. + // It's possible that clearing this reference will trigger the cleanup code for the asset, so if we wait for the + // destructor to clear it *after* the RemoveJob call, then HasActiveJobsOrStreamerRequests() will be able to return + // false even though the job is still executing asset-related code. + m_asset = {}; + m_owner->RemoveJob(this); + } + + //========================================================================= + // Create + // [6/12/2012] + //========================================================================= + bool AssetManager::Create(const Descriptor& desc) + { + AZ_Assert(!s_assetDB || !s_assetDB.Get(), "AssetManager already created!"); + + if (!s_assetDB) + { + s_assetDB = Environment::CreateVariable(kAssetDBInstanceVarName); + } + if (!s_assetDB.Get()) + { + s_assetDB.Set(aznew AssetManager(desc)); + } + + return true; + } + + //========================================================================= + // Destroy + // [6/12/2012] + //========================================================================= + void AssetManager::Destroy() + { + AZ_Assert(s_assetDB, "AssetManager not created!"); + delete (*s_assetDB); + *s_assetDB = nullptr; + } + + //========================================================================= + // IsReady + //========================================================================= + bool AssetManager::IsReady() + { + if (!s_assetDB) + { + s_assetDB = Environment::FindVariable(kAssetDBInstanceVarName); + } + + return s_assetDB && *s_assetDB; + } + + //========================================================================= + // Instance + //========================================================================= + AssetManager& AssetManager::Instance() + { + if (!s_assetDB) + { + s_assetDB = Environment::FindVariable(kAssetDBInstanceVarName); + } + + AZ_Assert(s_assetDB && *s_assetDB, "AssetManager not created!"); + return *(*s_assetDB); + } + + bool AssetManager::SetInstance(AssetManager* assetManager) + { + if (!s_assetDB) + { + s_assetDB = Environment::CreateVariable(kAssetDBInstanceVarName); + } + + // The old instance needs to be null or else it will leak on the assignment. + AZ_Assert(!(*s_assetDB), + "AssetManager::SetInstance was called without first destroying the old instance and setting it to nullptr. " + "This will cause the previous AssetManager instance to leak." ); + + (*s_assetDB) = assetManager; + return true; + } + + //========================================================================= + // AssetDatabase + // [6/12/2012] + //========================================================================= + AssetManager::AssetManager(const AssetManager::Descriptor& desc) + : m_mainThreadId(AZStd::this_thread::get_id()) + , m_debugAssetEvents(AZ::Interface::Get()) + { + (void)desc; + + AssetManagerBus::Handler::BusConnect(); + } + + //========================================================================= + // ~AssetManager + // [6/12/2012] + //========================================================================= + AssetManager::~AssetManager() + { + PrepareShutDown(); + + // Acquire the asset lock to make sure nobody else is trying to do anything fancy with assets + AZStd::scoped_lock assetLock(m_assetMutex); + + while (!m_handlers.empty()) + { + AssetHandlerMap::iterator it = m_handlers.begin(); + AssetHandler* handler = it->second; + UnregisterHandler(handler); + delete handler; + } + + AssetManagerBus::Handler::BusDisconnect(); + } + + //========================================================================= + // DispatchEvents + // [04/02/2014] + //========================================================================= + void AssetManager::DispatchEvents() + { + AZ_PROFILE_FUNCTION(AzCore); + AssetManagerNotificationBus::Broadcast(&AssetManagerNotificationBus::Events::OnAssetEventsDispatchBegin); + while (AssetBus::QueuedEventCount()) + { + AssetBus::ExecuteQueuedEvents(); + } + AssetManagerNotificationBus::Broadcast(&AssetManagerNotificationBus::Events::OnAssetEventsDispatchEnd); + } + + //========================================================================= + void AssetManager::SetAssetInfoUpgradingEnabled(bool enable) + { + m_assetInfoUpgradingEnabled = enable; + } + + bool AssetManager::GetAssetInfoUpgradingEnabled() const + { +#if defined(_RELEASE) + // in release ("FINAL") builds, we never do this. + return false; +#else + return m_assetInfoUpgradingEnabled; +#endif + } + + bool AssetManager::ShouldCancelAllActiveJobs() const + { + return m_cancelAllActiveJobs; + } + + void AssetManager::SetParallelDependentLoadingEnabled(bool enable) + { + m_enableParallelDependentLoading = enable; + } + + bool AssetManager::GetParallelDependentLoadingEnabled() const + { + return m_enableParallelDependentLoading; + } + + void AssetManager::PrepareShutDown() + { + m_cancelAllActiveJobs = true; + + // We want to ensure that no active load jobs are in flight and + // therefore we need to wait till all jobs have completed. Please note that jobs get deleted automatically once they complete. + WaitForActiveJobsAndStreamerRequestsToFinish(); + + m_ownedAssetContainerLookup.clear(); + m_ownedAssetContainers.clear(); + m_assetContainers.clear(); + + // Ensure that there are no queued events on the AssetBus + DispatchEvents(); + } + + void AssetManager::WaitForActiveJobsAndStreamerRequestsToFinish() + { + while (HasActiveJobsOrStreamerRequests()) + { + DispatchEvents(); + AZStd::this_thread::yield(); + } + } + + //========================================================================= + // RegisterHandler + // [7/9/2014] + //========================================================================= + void AssetManager::RegisterHandler(AssetHandler* handler, const AssetType& assetType) + { + AZ_Error("AssetDatabase", handler != nullptr, "Attempting to register a null asset handler!"); + if (handler) + { + if (m_handlers.insert(AZStd::make_pair(assetType, handler)).second) + { + handler->m_nHandledTypes++; + } + else + { + AZ_Error("AssetDatabase", false, "Asset type %s already has a handler registered! New registration ignored!", assetType.ToString().c_str()); + } + } + } + + //========================================================================= + // UnregisterHandler + // [7/9/2014] + //========================================================================= + void AssetManager::UnregisterHandler(AssetHandler* handler) + { + AZ_Error("AssetDatabase", handler != nullptr, "Attempting to unregister a null asset handler!"); + if (handler) + { + for (AssetHandlerMap::iterator it = m_handlers.begin(); it != m_handlers.end(); /*++it*/) + { + if (it->second == handler) + { + // When unregistering asset handlers, it's possible that there are still some load jobs that have "finished" but + // haven't destroyed themselves yet by the time the asset handler gets unregistered. LoadAssetJob contains a weak + // asset reference that doesn't clear until the job is destroyed, which happens *after* the OnAssetReady + // notification is triggered. If the thread gets swapped out between the OnAssetReady and the job destruction, + // the job will still be holding onto an asset reference for this asset handler, and it will trigger the + // error below. To ensure that this case doesn't happen, we will instead call + // WaitForActiveJobsAndStreamerRequestsToFinish() to make sure that any in-process jobs have completely cleaned + // themselves up before proceeding forward. + // One example of this pattern occurs in unit tests, where the test loads an asset, validates it, destroys the + // asset, and unregisters the handler, all in rapid succession. This would extremely infrequently + // (~1 per 5000 runs) trigger the error case if we didn't wait for the jobs to finish here. + WaitForActiveJobsAndStreamerRequestsToFinish(); + + { + // this scope is used to control the scope of the lock. + AZStd::lock_guard assetLock(m_assetMutex); + for (const auto &assetEntry : m_assets) + { + // is the handler that handles this type, this handler we're removing? + if (assetEntry.second->m_registeredHandler == handler) + { + AZ_Error("AssetManager", false, "Asset handler for %s is being removed, when assetid %s is still loaded!\n", + assetEntry.second->GetType().ToString().c_str(), + assetEntry.second->GetId().ToString().c_str()); // this will write the name IF AVAILABLE + assetEntry.second->UnregisterWithHandler(); + } + } + } + it = m_handlers.erase(it); + handler->m_nHandledTypes--; + } + else + { + ++it; } } + } + } - for(auto&& asset : assetsToRelease) + //========================================================================= + // RegisterCatalog + // [8/27/2012] + //========================================================================= + void AssetManager::RegisterCatalog(AssetCatalog* catalog, const AssetType& assetType) + { + AZ_Error("AssetDatabase", catalog != nullptr, "Attempting to register a null catalog!"); + if (catalog) + { + AZStd::scoped_lock l(m_catalogMutex); + if (m_catalogs.insert(AZStd::make_pair(assetType, catalog)).second == false) { - bool removeFromHash = asset->IsRegisterReadonlyAndShareable(); - // default creation token implies that the asset was not created by the asset manager and therefore it cannot be in the asset map. - removeFromHash = asset->m_creationToken == s_defaultCreationToken ? false : removeFromHash; + AZ_Error("AssetDatabase", false, "Asset type %s already has a catalog registered! New registration ignored!", assetType.ToString().c_str()); + } + } + } - ReleaseAsset(asset, asset->GetId(), asset->GetType(), removeFromHash, asset->m_creationToken); + //========================================================================= + // UnregisterCatalog + // [8/27/2012] + //========================================================================= + void AssetManager::UnregisterCatalog(AssetCatalog* catalog) + { + AZ_Error("AssetDatabase", catalog != nullptr, "Attempting to unregister a null catalog!"); + if (catalog) + { + AZStd::scoped_lock l(m_catalogMutex); + for (AssetCatalogMap::iterator iter = m_catalogs.begin(); iter != m_catalogs.end(); ) + { + if (iter->second == catalog) + { + iter = m_catalogs.erase(iter); + } + else + { + ++iter; + } + + } + } + } + + //========================================================================= + // GetHandledAssetTypes + // [6/27/2016] + //========================================================================= + void AssetManager::GetHandledAssetTypes(AssetCatalog* catalog, AZStd::vector& assetTypes) + { + for (AssetCatalogMap::iterator iter = m_catalogs.begin(); iter != m_catalogs.end(); iter++) + { + if (iter->second == catalog) + { + assetTypes.push_back(iter->first); + } + } + } + + void AssetManager::SuspendAssetRelease() + { + ++m_suspendAssetRelease; + } + + void AssetManager::ResumeAssetRelease() + { + if(--m_suspendAssetRelease != 0) + { + return; + } + + AZStd::scoped_lock assetLock(m_assetMutex); + // First, release any containers that were loading this asset + for (auto asset = m_assets.begin();asset != m_assets.end();) + { + if (asset->second->m_useCount == 0) + { + auto releaseAsset = asset->second; + ++asset; + ReleaseAssetContainersForAsset(releaseAsset); + } + else + { + ++asset; } } - AssetData::AssetStatus AssetManager::BlockUntilLoadComplete(const Asset& asset) + // Second, release the assets themselves + + AZStd::vector assetsToRelease; + + for(auto&& asset : m_assets) { - if(asset.GetStatus() == AssetData::AssetStatus::NotLoaded) + if(asset.second->m_weakUseCount == 0) { - AZ_Error("AssetManager", false, "BlockUntilLoadComplete must be called after an asset has been queued for load. Asset %s (%s) is not queued for load", - asset.GetHint().c_str(), asset.GetId().ToString().c_str()); + // Keep a separate list of assets to release, because releasing them will modify the m_assets list that we're + // currently looping on. + assetsToRelease.push_back(asset.second); } - else if(!asset.IsReady()) - { - // If this is the main thread we'll need to call DispatchEvents to make sure the events we're waiting on actually fire - // since the main thread is typically responsible for calling DispatchEvents elsewhere - const bool shouldDispatch = AZStd::this_thread::get_id() == m_mainThreadId; - - // Wait for the asset and all queued dependencies to finish loading. - WaitForAsset blockingWait(asset, shouldDispatch); - - blockingWait.WaitUntilReady(); - } - - return asset.GetStatus(); } - //========================================================================= - // FindAsset - //========================================================================= - Asset AssetManager::FindAsset(const AssetId& assetId, AssetLoadBehavior assetReferenceLoadBehavior) + for(auto&& asset : assetsToRelease) { - // Look up the asset id in the catalog, and use the result of that instead. - // If assetId is a legacy id, assetInfo.m_assetId will be the canonical id. Otherwise, assetInfo.m_assetID == assetId. - // This is because only canonical ids are stored in m_assets (see below). - // Only do the look up if upgrading is enabled - AZ::Data::AssetInfo assetInfo; - if (GetAssetInfoUpgradingEnabled()) + bool removeFromHash = asset->IsRegisterReadonlyAndShareable(); + // default creation token implies that the asset was not created by the asset manager and therefore it cannot be in the asset map. + removeFromHash = asset->m_creationToken == s_defaultCreationToken ? false : removeFromHash; + + ReleaseAsset(asset, asset->GetId(), asset->GetType(), removeFromHash, asset->m_creationToken); + } + } + + AssetData::AssetStatus AssetManager::BlockUntilLoadComplete(const Asset& asset) + { + if(asset.GetStatus() == AssetData::AssetStatus::NotLoaded) + { + AZ_Error("AssetManager", false, "BlockUntilLoadComplete must be called after an asset has been queued for load. Asset %s (%s) is not queued for load", + asset.GetHint().c_str(), asset.GetId().ToString().c_str()); + } + else if(!asset.IsReady()) + { + // If this is the main thread we'll need to call DispatchEvents to make sure the events we're waiting on actually fire + // since the main thread is typically responsible for calling DispatchEvents elsewhere + const bool shouldDispatch = AZStd::this_thread::get_id() == m_mainThreadId; + + // Wait for the asset and all queued dependencies to finish loading. + WaitForAsset blockingWait(asset, shouldDispatch); + + blockingWait.WaitUntilReady(); + } + + return asset.GetStatus(); + } + + //========================================================================= + // FindAsset + //========================================================================= + Asset AssetManager::FindAsset(const AssetId& assetId, AssetLoadBehavior assetReferenceLoadBehavior) + { + // Look up the asset id in the catalog, and use the result of that instead. + // If assetId is a legacy id, assetInfo.m_assetId will be the canonical id. Otherwise, assetInfo.m_assetID == assetId. + // This is because only canonical ids are stored in m_assets (see below). + // Only do the look up if upgrading is enabled + AZ::Data::AssetInfo assetInfo; + if (GetAssetInfoUpgradingEnabled()) + { + AssetCatalogRequestBus::BroadcastResult(assetInfo, &AssetCatalogRequestBus::Events::GetAssetInfoById, assetId); + } + + // If the catalog is not available, use the original assetId + const AssetId& assetToFind(assetInfo.m_assetId.IsValid() ? assetInfo.m_assetId : assetId); + + AZStd::scoped_lock assetLock(m_assetMutex); + AssetMap::iterator it = m_assets.find(assetToFind); + if (it != m_assets.end()) + { + Asset asset(assetReferenceLoadBehavior); + asset.SetData(it->second); + + return asset; + } + return Asset(assetReferenceLoadBehavior); + } + + AZStd::pair GetEffectiveDeadlineAndPriority( + const AssetHandler& handler, AssetType assetType, const AssetLoadParameters& loadParams) + { + AZStd::chrono::milliseconds deadline; + AZ::IO::IStreamerTypes::Priority priority; + + handler.GetDefaultAssetLoadPriority(assetType, deadline, priority); + + if (loadParams.m_deadline) + { + deadline = loadParams.m_deadline.value(); + } + + if (loadParams.m_priority) + { + priority = loadParams.m_priority.value(); + } + + return make_pair(deadline, priority); + } + + //========================================================================= + // GetAsset + // [6/19/2012] + //========================================================================= + Asset AssetManager::GetAsset(const AssetId& assetId, const AssetType& assetType, AssetLoadBehavior assetReferenceLoadBehavior, const AssetLoadParameters& loadParams) + { + // If parallel dependent loads are disabled, just try to load the requested asset directly, and let it trigger + // dependent loads as they're encountered. + // Parallel dependent loads are disabled during asset building because there is no guarantee that dependency information + // will be available and complete until after all assets are finished building. + if(!GetParallelDependentLoadingEnabled()) + { + return GetAssetInternal(assetId, assetType, assetReferenceLoadBehavior, loadParams); + } + + // Otherwise, use Asset Containers to load all dependent assets in parallel. + + Asset asset = FindOrCreateAsset(assetId, assetType, assetReferenceLoadBehavior); + + if(!asset || (!loadParams.m_reloadMissingDependencies && asset.IsReady())) + { + // If the asset is already ready, just return it and skip the container + return AZStd::move(asset); + } + + auto container = GetAssetContainer(asset, loadParams); + + AZStd::scoped_lock lock(m_assetContainerMutex); + + m_ownedAssetContainers.insert({ container.get(), container }); + + // Only insert a new entry into m_ownedAssetContainerLookup if one doesn't already exist for this container. + // Because it's a multimap, it is possible to add duplicate entries by mistake. + bool entryExists = false; + auto rangeItr = m_ownedAssetContainerLookup.equal_range(assetId); + for (auto itr = rangeItr.first; itr != rangeItr.second; ++itr) + { + if (itr->second == container.get()) + { + entryExists = true; + break; + } + } + + // Entry for this container doesn't exist yet, so add it. + if (!entryExists) + { + m_ownedAssetContainerLookup.insert({ assetId, container.get() }); + } + + return asset; + } + + Asset AssetManager::GetAssetInternal(const AssetId& assetId, [[maybe_unused]] const AssetType& assetType, + AssetLoadBehavior assetReferenceLoadBehavior, const AssetLoadParameters& loadParams, AssetInfo assetInfo /*= () */, bool signalLoaded /*= false */) + { + AZ_PROFILE_FUNCTION(AzCore); + + AZ_Error("AssetDatabase", assetId.IsValid(), "GetAsset called with invalid asset Id."); + AZ_Error("AssetDatabase", !assetType.IsNull(), "GetAsset called with invalid asset type."); + bool assetMissing = false; + + { + AZ_PROFILE_SCOPE(AzCore, "GetAsset: GetAssetInfo"); + + // Attempt to look up asset info from catalog + // This is so that when assetId is a legacy id, we're operating on the canonical id anyway + if (!assetInfo.m_assetId.IsValid()) { AssetCatalogRequestBus::BroadcastResult(assetInfo, &AssetCatalogRequestBus::Events::GetAssetInfoById, assetId); } - // If the catalog is not available, use the original assetId - const AssetId& assetToFind(assetInfo.m_assetId.IsValid() ? assetInfo.m_assetId : assetId); + // If the asset was found in the catalog, ensure the type infos match + if (assetInfo.m_assetId.IsValid()) + { + AZ_Warning("AssetManager", assetInfo.m_assetType == assetType, + "Requested asset id %s with type %s, but type is actually %s.", + assetId.ToString().c_str(), assetType.ToString().c_str(), + assetInfo.m_assetType.ToString().c_str()); + } + else + { + AZ_Warning("AssetManager", false, "GetAsset called for asset which does not exist in asset catalog and cannot be loaded. Asset may be missing, not processed or moved. AssetId: %s", + assetId.ToString().c_str()); + // If asset not found, use the id and type given. We will create a valid asset, but it will likely get an error + // status below if the asset handler doesn't reroute it to a default asset. + assetInfo.m_assetId = assetId; + assetInfo.m_assetType = assetType; + assetMissing = true; + } + } + + AZ_PROFILE_SCOPE(AzCore, "GetAsset: %s", assetInfo.m_relativePath.c_str()); + + AZStd::shared_ptr dataStream; + AssetStreamInfo loadInfo; + bool triggerAssetErrorNotification = false; + bool wasUnloaded = false; + AssetHandler* handler = nullptr; + AssetData* assetData = nullptr; + Asset asset; // Used to hold a reference while job is dispatched and while outside of the assetMutex lock. + + // Control the scope of the assetMutex lock + { AZStd::scoped_lock assetLock(m_assetMutex); - AssetMap::iterator it = m_assets.find(assetToFind); - if (it != m_assets.end()) + bool isNewEntry = false; + + // check if asset already exists { - Asset asset(assetReferenceLoadBehavior); - asset.SetData(it->second); + AZ_PROFILE_SCOPE(AzCore, "GetAsset: FindAsset"); - return asset; - } - return Asset(assetReferenceLoadBehavior); - } - - AZStd::pair GetEffectiveDeadlineAndPriority( - const AssetHandler& handler, AssetType assetType, const AssetLoadParameters& loadParams) - { - AZStd::chrono::milliseconds deadline; - AZ::IO::IStreamerTypes::Priority priority; - - handler.GetDefaultAssetLoadPriority(assetType, deadline, priority); - - if (loadParams.m_deadline) - { - deadline = loadParams.m_deadline.value(); - } - - if (loadParams.m_priority) - { - priority = loadParams.m_priority.value(); - } - - return make_pair(deadline, priority); - } - - //========================================================================= - // GetAsset - // [6/19/2012] - //========================================================================= - Asset AssetManager::GetAsset(const AssetId& assetId, const AssetType& assetType, AssetLoadBehavior assetReferenceLoadBehavior, const AssetLoadParameters& loadParams) - { - // If parallel dependent loads are disabled, just try to load the requested asset directly, and let it trigger - // dependent loads as they're encountered. - // Parallel dependent loads are disabled during asset building because there is no guarantee that dependency information - // will be available and complete until after all assets are finished building. - if(!GetParallelDependentLoadingEnabled()) - { - return GetAssetInternal(assetId, assetType, assetReferenceLoadBehavior, loadParams); - } - - // Otherwise, use Asset Containers to load all dependent assets in parallel. - - Asset asset = FindOrCreateAsset(assetId, assetType, assetReferenceLoadBehavior); - - if(!asset || (!loadParams.m_reloadMissingDependencies && asset.IsReady())) - { - // If the asset is already ready, just return it and skip the container - return AZStd::move(asset); - } - - auto container = GetAssetContainer(asset, loadParams); - - AZStd::scoped_lock lock(m_assetContainerMutex); - - m_ownedAssetContainers.insert({ container.get(), container }); - - // Only insert a new entry into m_ownedAssetContainerLookup if one doesn't already exist for this container. - // Because it's a multimap, it is possible to add duplicate entries by mistake. - bool entryExists = false; - auto rangeItr = m_ownedAssetContainerLookup.equal_range(assetId); - for (auto itr = rangeItr.first; itr != rangeItr.second; ++itr) - { - if (itr->second == container.get()) + AssetMap::iterator it = m_assets.find(assetInfo.m_assetId); + if (it != m_assets.end()) { - entryExists = true; - break; - } - } - - // Entry for this container doesn't exist yet, so add it. - if (!entryExists) - { - m_ownedAssetContainerLookup.insert({ assetId, container.get() }); - } - - return asset; - } - - Asset AssetManager::GetAssetInternal(const AssetId& assetId, [[maybe_unused]] const AssetType& assetType, - AssetLoadBehavior assetReferenceLoadBehavior, const AssetLoadParameters& loadParams, AssetInfo assetInfo /*= () */, bool signalLoaded /*= false */) - { - AZ_PROFILE_FUNCTION(AzCore); - - AZ_Error("AssetDatabase", assetId.IsValid(), "GetAsset called with invalid asset Id."); - AZ_Error("AssetDatabase", !assetType.IsNull(), "GetAsset called with invalid asset type."); - bool assetMissing = false; - - { - AZ_PROFILE_SCOPE(AzCore, "GetAsset: GetAssetInfo"); - - // Attempt to look up asset info from catalog - // This is so that when assetId is a legacy id, we're operating on the canonical id anyway - if (!assetInfo.m_assetId.IsValid()) - { - AssetCatalogRequestBus::BroadcastResult(assetInfo, &AssetCatalogRequestBus::Events::GetAssetInfoById, assetId); - } - - // If the asset was found in the catalog, ensure the type infos match - if (assetInfo.m_assetId.IsValid()) - { - AZ_Warning("AssetManager", assetInfo.m_assetType == assetType, - "Requested asset id %s with type %s, but type is actually %s.", - assetId.ToString().c_str(), assetType.ToString().c_str(), - assetInfo.m_assetType.ToString().c_str()); + assetData = it->second; + asset.SetData(assetData); } else { - AZ_Warning("AssetManager", false, "GetAsset called for asset which does not exist in asset catalog and cannot be loaded. Asset may be missing, not processed or moved. AssetId: %s", - assetId.ToString().c_str()); - - // If asset not found, use the id and type given. We will create a valid asset, but it will likely get an error - // status below if the asset handler doesn't reroute it to a default asset. - assetInfo.m_assetId = assetId; - assetInfo.m_assetType = assetType; - assetMissing = true; + isNewEntry = true; } } - AZ_PROFILE_SCOPE(AzCore, "GetAsset: %s", assetInfo.m_relativePath.c_str()); - AZ_ASSET_NAMED_SCOPE("GetAsset: %s", assetInfo.m_relativePath.c_str()); - - AZStd::shared_ptr dataStream; - AssetStreamInfo loadInfo; - bool triggerAssetErrorNotification = false; - bool wasUnloaded = false; - AssetHandler* handler = nullptr; - AssetData* assetData = nullptr; - Asset asset; // Used to hold a reference while job is dispatched and while outside of the assetMutex lock. - - // Control the scope of the assetMutex lock { - AZStd::scoped_lock assetLock(m_assetMutex); - bool isNewEntry = false; + AZ_PROFILE_SCOPE(AzCore, "GetAsset: FindAssetHandler"); - // check if asset already exists + // find the asset type handler + AssetHandlerMap::iterator handlerIt = m_handlers.find(assetInfo.m_assetType); + AZ_Error("AssetDatabase", handlerIt != m_handlers.end(), "No handler was registered for this asset [type:%s id:%s]!", + assetInfo.m_assetType.ToString().c_str(), assetInfo.m_assetId.ToString().c_str()); + if (handlerIt != m_handlers.end()) { - AZ_PROFILE_SCOPE(AzCore, "GetAsset: FindAsset"); - - AssetMap::iterator it = m_assets.find(assetInfo.m_assetId); - if (it != m_assets.end()) + // Create the asset ptr and insert it into our asset map. + handler = handlerIt->second; + if (isNewEntry) { - assetData = it->second; - asset.SetData(assetData); - } - else - { - isNewEntry = true; - } - } + AZ_PROFILE_SCOPE(AzCore, "GetAsset: CreateAsset"); - { - AZ_PROFILE_SCOPE(AzCore, "GetAsset: FindAssetHandler"); - - // find the asset type handler - AssetHandlerMap::iterator handlerIt = m_handlers.find(assetInfo.m_assetType); - AZ_Error("AssetDatabase", handlerIt != m_handlers.end(), "No handler was registered for this asset [type:%s id:%s]!", - assetInfo.m_assetType.ToString().c_str(), assetInfo.m_assetId.ToString().c_str()); - if (handlerIt != m_handlers.end()) - { - // Create the asset ptr and insert it into our asset map. - handler = handlerIt->second; - if (isNewEntry) + assetData = handler->CreateAsset(assetInfo.m_assetId, assetInfo.m_assetType); + if (assetData) { - AZ_PROFILE_SCOPE(AzCore, "GetAsset: CreateAsset"); - - assetData = handler->CreateAsset(assetInfo.m_assetId, assetInfo.m_assetType); - if (assetData) - { - assetData->m_assetId = assetInfo.m_assetId; - assetData->m_creationToken = ++m_creationTokenGenerator; - assetData->RegisterWithHandler(handler); - asset.SetData(assetData); - } - else - { - AZ_Error("AssetDatabase", false, "Failed to create asset with (id=%s, type=%s)", - assetInfo.m_assetId.ToString().c_str(), - assetInfo.m_assetType.ToString().c_str()); - } - } - } - } - - if (assetData) - { - if (isNewEntry && assetData->IsRegisterReadonlyAndShareable()) - { - AZ_PROFILE_SCOPE(AzCore, "GetAsset: RegisterAsset"); - m_assets.insert(AZStd::make_pair(assetInfo.m_assetId, assetData)); - } - if (assetData->GetStatus() == AssetData::AssetStatus::NotLoaded) - { - assetData->m_status = AssetData::AssetStatus::Queued; - UpdateDebugStatus(asset); - loadInfo = GetModifiedLoadStreamInfoForAsset(asset, handler); - wasUnloaded = true; - - if (loadInfo.IsValid()) - { - // Create the AssetDataStream instance here so it can claim an asset reference inside the lock (for a total - // count of 2 before starting the load), otherwise the refcount will be 1, and the load could be canceled - // before it is started, which creates state consistency issues. - - dataStream = AZStd::make_shared(handler->GetAssetBufferAllocator()); + assetData->m_assetId = assetInfo.m_assetId; + assetData->m_creationToken = ++m_creationTokenGenerator; + assetData->RegisterWithHandler(handler); + asset.SetData(assetData); } else { - // Asset creation was successful, but asset loading isn't, so trigger the OnAssetError notification - triggerAssetErrorNotification = true; + AZ_Error("AssetDatabase", false, "Failed to create asset with (id=%s, type=%s)", + assetInfo.m_assetId.ToString().c_str(), + assetInfo.m_assetType.ToString().c_str()); } } } } - if (!assetInfo.m_relativePath.empty()) + if (assetData) { - asset.m_assetHint = assetInfo.m_relativePath; - } - - asset.SetAutoLoadBehavior(assetReferenceLoadBehavior); - - // We delay queueing the async file I/O until we release m_assetMutex - if (dataStream) - { - AZ_Assert(loadInfo.IsValid(), "Expected valid stream info when dataStream is valid."); - constexpr bool isReload = false; - QueueAsyncStreamLoad(asset, dataStream, loadInfo, isReload, - handler, loadParams, signalLoaded); - } - else - { - AZ_Assert(!loadInfo.IsValid(), "Expected invalid stream info when dataStream is invalid."); - - if(!wasUnloaded && assetData && assetData->GetStatus() == AssetData::AssetStatus::Queued) + if (isNewEntry && assetData->IsRegisterReadonlyAndShareable()) { - auto&& [deadline, priority] = GetEffectiveDeadlineAndPriority(*handler, assetData->GetType(), loadParams); - - RescheduleStreamerRequest(assetData->GetId(), deadline, priority); + AZ_PROFILE_SCOPE(AzCore, "GetAsset: RegisterAsset"); + m_assets.insert(AZStd::make_pair(assetInfo.m_assetId, assetData)); } - - if (triggerAssetErrorNotification) + if (assetData->GetStatus() == AssetData::AssetStatus::NotLoaded) { - // If the asset was missing from the catalog, we already printed an error, so we can skip printing this one. - if (!assetMissing) + assetData->m_status = AssetData::AssetStatus::Queued; + UpdateDebugStatus(asset); + loadInfo = GetModifiedLoadStreamInfoForAsset(asset, handler); + wasUnloaded = true; + + if (loadInfo.IsValid()) { - AZ_Error("AssetDatabase", false, "Failed to retrieve required information for asset %s (%s)", - assetInfo.m_assetId.ToString().c_str(), - assetInfo.m_relativePath.empty() ? "" : assetInfo.m_relativePath.c_str()); - } + // Create the AssetDataStream instance here so it can claim an asset reference inside the lock (for a total + // count of 2 before starting the load), otherwise the refcount will be 1, and the load could be canceled + // before it is started, which creates state consistency issues. - PostLoad(asset, false, false, handler); - } - } - - return asset; - } - - void AssetManager::UpdateDebugStatus(const AZ::Data::Asset& asset) - { - if(!m_debugAssetEvents) - { - m_debugAssetEvents = AZ::Interface::Get(); - } - - if(m_debugAssetEvents) - { - m_debugAssetEvents->AssetStatusUpdate(asset.GetId(), asset.GetStatus()); - } - } - - Asset AssetManager::FindOrCreateAsset(const AssetId& assetId, const AssetType& assetType, AssetLoadBehavior assetReferenceLoadBehavior) - { - AZStd::scoped_lock asset_lock(m_assetMutex); - - Asset asset = FindAsset(assetId, assetReferenceLoadBehavior); - - if (!asset) - { - asset = CreateAsset(assetId, assetType, assetReferenceLoadBehavior); - } - - return asset; - } - - //========================================================================= - // CreateAsset - // [8/31/2012] - //========================================================================= - Asset AssetManager::CreateAsset(const AssetId& assetId, const AssetType& assetType, AssetLoadBehavior assetReferenceLoadBehavior) - { - AZStd::scoped_lock asset_lock(m_assetMutex); - - // check if asset already exist - AssetMap::iterator it = m_assets.find(assetId); - if (it == m_assets.end()) - { - // find the asset type handler - AssetHandlerMap::iterator handlerIt = m_handlers.find(assetType); - AZ_Error("AssetDatabase", handlerIt != m_handlers.end(), "No handler was registered for this asset (id=%s, type=%s)!", assetId.ToString().c_str(), assetType.ToString().c_str()); - if (handlerIt != m_handlers.end()) - { - // Create the asset ptr - AssetHandler* handler = handlerIt->second; - auto assetData = handler->CreateAsset(assetId, assetType); - AZ_Error("AssetDatabase", assetData, "Failed to create asset with (id=%s, type=%s)", assetId.ToString().c_str(), assetType.ToString().c_str()); - if (assetData) - { - assetData->m_assetId = assetId; - assetData->m_creationToken = ++m_creationTokenGenerator; - assetData->RegisterWithHandler(handler); - if (assetData->IsRegisterReadonlyAndShareable()) - { - m_assets.insert(AZStd::make_pair(assetId, assetData)); - } - - Asset asset(assetReferenceLoadBehavior); - asset.SetData(assetData); - - return asset; - } - } - } - else - { - AZ_Error("AssetDatabase", false, "Asset (id=%s, type=%s) already exists in the database! Asset not created!", assetId.ToString().c_str(), assetType.ToString().c_str()); - } - return Asset(assetReferenceLoadBehavior); - } - - //========================================================================= - // ReleaseAsset - //========================================================================= - void AssetManager::ReleaseAsset(AssetData* asset, AssetId assetId, AssetType assetType, bool removeAssetFromHash, int creationToken) - { - AZ_Assert(asset, "Cannot release NULL AssetPtr!"); - - if(m_suspendAssetRelease) - { - return; - } - - bool wasInAssetsHash = false; // We do support assets that are not registered in the asset manager (with the same ID too). - bool destroyAsset = false; - - if (removeAssetFromHash) - { - AZStd::scoped_lock asset_lock(m_assetMutex); - AssetMap::iterator it = m_assets.find(assetId); - // need to check the count again in here in case - // someone was trying to get the asset on another thread - // Set it to -1 so only this thread will attempt to clean up the cache and delete the asset - int expectedRefCount = 0; - // if the assetId is not in the map or if the identifierId - // do not match it implies that the asset has been already destroyed. - // if the usecount is non zero it implies that we cannot destroy this asset. - if (it != m_assets.end() && it->second->m_creationToken == creationToken && it->second->m_weakUseCount.compare_exchange_strong(expectedRefCount, -1)) - { - wasInAssetsHash = true; - m_assets.erase(it); - destroyAsset = true; - } - } - else - { - // if an asset is not shareable, it implies that that asset is not in the map - // and therefore once its ref count goes to zero it cannot go back up again and therefore we can safely destroy it - destroyAsset = true; - } - - // We have to separate the code which was removing the asset from the m_asset map while being locked, but then actually destroy the asset - // while the lock is not held since destroying the asset while holding the lock can cause a deadlock. - if (destroyAsset) - { - if(m_debugAssetEvents) - { - m_debugAssetEvents->ReleaseAsset(assetId); - } - - // find the asset type handler - AssetHandlerMap::iterator handlerIt = m_handlers.find(assetType); - if (handlerIt != m_handlers.end()) - { - AssetHandler* handler = handlerIt->second; - if (asset) - { - handler->DestroyAsset(asset); - - if (wasInAssetsHash) - { - AssetBus::QueueEvent(assetId, &AssetBus::Events::OnAssetUnloaded, assetId, assetType); - } - } - } - else - { - AZ_Assert(false, "No handler was registered for asset of type %s but it was still in the AssetManager as %s", assetType.ToString().c_str(), asset->GetId().ToString().c_str()); - } - } - } - - void AssetManager::OnAssetUnused(AssetData* asset) - { - // If we're currently suspending asset releases, don't get rid of the asset containers either. - if (m_suspendAssetRelease) - { - return; - } - - ReleaseAssetContainersForAsset(asset); - } - - void AssetManager::ReleaseAssetContainersForAsset(AssetData* asset) - { - // Release any containers that were loading this asset - AZStd::scoped_lock lock(m_assetContainerMutex); - - AssetId assetId = asset->GetId(); - - auto rangeItr = m_ownedAssetContainerLookup.equal_range(assetId); - - for (auto itr = rangeItr.first; itr != rangeItr.second;) - { - AZ_Assert(itr->second->GetContainerAssetId() == assetId, - "Asset container is incorrectly associated with the asset being destroyed."); - itr->second->ClearRootAsset(); - - // Only remove owned asset containers if they aren't currently loading. - // If they *are* currently loading, removing them could cause dependent asset loads that were triggered to - // remain in a perpetual loading state. Instead, leave the containers for now, they will get removed during - // the OnAssetContainerReady callback. - if (!itr->second->IsLoading()) - { - m_ownedAssetContainers.erase(itr->second); - itr = m_ownedAssetContainerLookup.erase(itr); - } - else - { - ++itr; - } - } - } - - //========================================================================= - // SaveAsset - // [9/13/2012] - //========================================================================= - void AssetManager::SaveAsset(const Asset& asset) - { - AssetHandler* handler; - { - // find the asset type handler - AssetHandlerMap::iterator handlerIt = m_handlers.find(asset.GetType()); - AZ_Assert(handlerIt != m_handlers.end(), "No handler was registered for this asset [type:%s id:%s]!", asset.GetType().ToString().c_str(), asset.GetId().ToString().c_str()); - handler = handlerIt->second; - } - - // start the data saving - SaveAssetJob* saveJob = aznew SaveAssetJob(JobContext::GetGlobalContext(), this, asset, handler); - saveJob->Start(); - } - - //========================================================================= - // ReloadAsset - //========================================================================= - void AssetManager::ReloadAsset(const AssetId& assetId, AssetLoadBehavior assetReferenceLoadBehavior, bool isAutoReload) - { - AZStd::scoped_lock assetLock(m_assetMutex); - auto assetIter = m_assets.find(assetId); - - if (assetIter == m_assets.end() || assetIter->second->IsLoading()) - { - // Only existing assets can be reloaded. - return; - } - - auto reloadIter = m_reloads.find(assetId); - if (reloadIter != m_reloads.end()) - { - auto curStatus = reloadIter->second.GetData()->GetStatus(); - // We don't need another reload if we're in "Queued" state because that reload has not actually begun yet. - // If it is in Loading state we want to pass by and allow the new assetData to be created and start the new reload - // As the current load could already be stale - if (curStatus == AssetData::AssetStatus::Queued) - { - return; - } - else if (curStatus == AssetData::AssetStatus::Loading || curStatus == AssetData::AssetStatus::StreamReady) - { - // Don't flood the tick bus - this value will be checked when the asset load completes - reloadIter->second->SetRequeue(true); - return; - } - } - - AssetData* newAssetData = nullptr; - AssetHandler* handler = nullptr; - - bool preventAutoReload = isAutoReload && assetIter->second && !assetIter->second->HandleAutoReload(); - - // when Asset's constructor is called (the one that takes an AssetData), it updates the AssetID - // of the Asset to be the real latest canonical assetId of the asset, so we cache that here instead of have it happen - // implicitly and repeatedly for anything we call. - Asset currentAsset(assetIter->second, AZ::Data::AssetLoadBehavior::Default); - - if (!assetIter->second->IsRegisterReadonlyAndShareable() && !preventAutoReload) - { - // Reloading an "instance asset" is basically a no-op. - // We'll simply notify users to reload the asset. - AssetBus::QueueFunction(&AssetManager::NotifyAssetReloaded, this, currentAsset); - return; - } - else - { - AssetBus::QueueFunction(&AssetManager::NotifyAssetPreReload, this, currentAsset); - } - - // Current AssetData has requested not to be auto reloaded - if (preventAutoReload) - { - return; - } - - // Resolve the asset handler and allocate new data for the reload. - { - AssetHandlerMap::iterator handlerIt = m_handlers.find(currentAsset.GetType()); - AZ_Assert(handlerIt != m_handlers.end(), "No handler was registered for this asset [type:%s id:%s]!", - currentAsset.GetType().ToString().c_str(), currentAsset.GetId().ToString().c_str()); - handler = handlerIt->second; - - newAssetData = handler->CreateAsset(currentAsset.GetId(), currentAsset.GetType()); - if (newAssetData) - { - newAssetData->m_assetId = currentAsset.GetId(); - newAssetData->RegisterWithHandler(handler); - } - } - - if (newAssetData) - { - // For reloaded assets, we need to hold an internal reference to ensure the data - // isn't immediately destroyed. Since reloads are not a shipping feature, we'll - // hold this reference indefinitely, but we'll only hold the most recent one for - // a given asset Id. - - newAssetData->m_status = AssetData::AssetStatus::Queued; - Asset newAsset(newAssetData, assetReferenceLoadBehavior); - - m_reloads[newAsset.GetId()] = newAsset; - - UpdateDebugStatus(newAsset); - - AZStd::shared_ptr dataStream; - AssetStreamInfo loadInfo = GetModifiedLoadStreamInfoForAsset(newAsset, handler); - constexpr bool isReload = true; - if (loadInfo.IsValid()) - { - // Create the AssetDataStream instance here so it can claim an asset reference inside the lock (for a total - // count of 2 before starting the load), otherwise the refcount will be 1, and the load could be canceled - // before it is started, which creates state consistency issues. - - dataStream = AZStd::make_shared(handler->GetAssetBufferAllocator()); - if (dataStream) - { - // Currently there isn't a clear use case for needing to adjust priority for reloads so the default load priority is used - constexpr bool signalLoaded = false; // this is a reload, so don't signal dependent-asset loads - QueueAsyncStreamLoad(newAsset, dataStream, loadInfo, isReload, - handler, {}, signalLoaded); + dataStream = AZStd::make_shared(handler->GetAssetBufferAllocator()); } else { - AZ_Assert(false, "Failed to create dataStream to reload asset %s (%s)", - newAsset.GetId().ToString().c_str(), - newAsset.GetHint().c_str()); + // Asset creation was successful, but asset loading isn't, so trigger the OnAssetError notification + triggerAssetErrorNotification = true; } } - else + } + } + + if (!assetInfo.m_relativePath.empty()) + { + asset.m_assetHint = assetInfo.m_relativePath; + } + + asset.SetAutoLoadBehavior(assetReferenceLoadBehavior); + + // We delay queueing the async file I/O until we release m_assetMutex + if (dataStream) + { + AZ_Assert(loadInfo.IsValid(), "Expected valid stream info when dataStream is valid."); + constexpr bool isReload = false; + QueueAsyncStreamLoad(asset, dataStream, loadInfo, isReload, + handler, loadParams, signalLoaded); + } + else + { + AZ_Assert(!loadInfo.IsValid(), "Expected invalid stream info when dataStream is invalid."); + + if(!wasUnloaded && assetData && assetData->GetStatus() == AssetData::AssetStatus::Queued) + { + auto&& [deadline, priority] = GetEffectiveDeadlineAndPriority(*handler, assetData->GetType(), loadParams); + + RescheduleStreamerRequest(assetData->GetId(), deadline, priority); + } + + if (triggerAssetErrorNotification) + { + // If the asset was missing from the catalog, we already printed an error, so we can skip printing this one. + if (!assetMissing) { - // Asset creation was successful, but asset loading isn't, so trigger the OnAssetError notification AZ_Error("AssetDatabase", false, "Failed to retrieve required information for asset %s (%s)", - newAsset.GetId().ToString().c_str(), - newAsset.GetHint().c_str()); - - constexpr bool loadSucceeded = false; - AssetManager::Instance().PostLoad(newAsset, loadSucceeded, isReload, handler); + assetInfo.m_assetId.ToString().c_str(), + assetInfo.m_relativePath.empty() ? "" : assetInfo.m_relativePath.c_str()); } + PostLoad(asset, false, false, handler); } } - //========================================================================= - // ReloadAssetFromData - //========================================================================= - void AssetManager::ReloadAssetFromData(const Asset& asset) + return asset; + } + + void AssetManager::UpdateDebugStatus(const AZ::Data::Asset& asset) + { + if(!m_debugAssetEvents) { - bool shouldAssignAssetData = false; - - { - AZ_Assert(asset.Get(), "Asset data for reload is missing."); - AZStd::scoped_lock assetLock(m_assetMutex); - AZ_Assert( - m_assets.find(asset.GetId()) != m_assets.end(), - "Unable to reload asset %s because it's not in the AssetManager's asset list.", asset.ToString().c_str()); - AZ_Assert( - m_assets.find(asset.GetId()) == m_assets.end() || - asset->RTTI_GetType() == m_assets.find(asset.GetId())->second->RTTI_GetType(), - "New and old data types are mismatched!"); - - auto found = m_assets.find(asset.GetId()); - if ((found == m_assets.end()) || (asset->RTTI_GetType() != found->second->RTTI_GetType())) - { - return; // this will just lead to crashes down the line and the above asserts cover this. - } - - AssetData* newData = asset.Get(); - - if (found->second != newData) - { - // Notify users that we are about to change asset - AssetBus::Event(asset.GetId(), &AssetBus::Events::OnAssetPreReload, asset); - - // Resolve the asset handler and account for the new asset instance. - { - [[maybe_unused]] AssetHandlerMap::iterator handlerIt = m_handlers.find(newData->GetType()); - AZ_Assert( - handlerIt != m_handlers.end(), "No handler was registered for this asset [type:%s id:%s]!", - newData->GetType().ToString().c_str(), newData->GetId().ToString().c_str()); - } - - shouldAssignAssetData = true; - } - } - - // We specifically perform this outside of the m_assetMutex lock so that the lock isn't held at the point that - // OnAssetReload is triggered inside of AssignAssetData. Otherwise, we open up a high potential for deadlocks. - if (shouldAssignAssetData) - { - AssignAssetData(asset); - } + m_debugAssetEvents = AZ::Interface::Get(); } - //========================================================================= - // GetHandler - //========================================================================= - AssetHandler* AssetManager::GetHandler(const AssetType& assetType) + if(m_debugAssetEvents) { - auto handlerEntry = m_handlers.find(assetType); - if (handlerEntry != m_handlers.end()) - { - return handlerEntry->second; - } - return nullptr; + m_debugAssetEvents->AssetStatusUpdate(asset.GetId(), asset.GetStatus()); + } + } + + Asset AssetManager::FindOrCreateAsset(const AssetId& assetId, const AssetType& assetType, AssetLoadBehavior assetReferenceLoadBehavior) + { + AZStd::scoped_lock asset_lock(m_assetMutex); + + Asset asset = FindAsset(assetId, assetReferenceLoadBehavior); + + if (!asset) + { + asset = CreateAsset(assetId, assetType, assetReferenceLoadBehavior); } - //========================================================================= - // AssignAssetData - //========================================================================= - void AssetManager::AssignAssetData(const Asset& asset) + return asset; + } + + //========================================================================= + // CreateAsset + // [8/31/2012] + //========================================================================= + Asset AssetManager::CreateAsset(const AssetId& assetId, const AssetType& assetType, AssetLoadBehavior assetReferenceLoadBehavior) + { + AZStd::scoped_lock asset_lock(m_assetMutex); + + // check if asset already exist + AssetMap::iterator it = m_assets.find(assetId); + if (it == m_assets.end()) { - AZ_Assert(asset.Get(), "Reloaded data is missing!"); - - const AssetId& assetId = asset.GetId(); - - asset->m_status = AssetData::AssetStatus::Ready; - UpdateDebugStatus(asset); - - if (asset->IsRegisterReadonlyAndShareable()) + // find the asset type handler + AssetHandlerMap::iterator handlerIt = m_handlers.find(assetType); + AZ_Error("AssetDatabase", handlerIt != m_handlers.end(), "No handler was registered for this asset (id=%s, type=%s)!", assetId.ToString().c_str(), assetType.ToString().c_str()); + if (handlerIt != m_handlers.end()) { - bool requeue{ false }; + // Create the asset ptr + AssetHandler* handler = handlerIt->second; + auto assetData = handler->CreateAsset(assetId, assetType); + AZ_Error("AssetDatabase", assetData, "Failed to create asset with (id=%s, type=%s)", assetId.ToString().c_str(), assetType.ToString().c_str()); + if (assetData) { - AZStd::scoped_lock assetLock(m_assetMutex); - auto found = m_assets.find(assetId); - AZ_Assert(found == m_assets.end() || asset.Get()->RTTI_GetType() == found->second->RTTI_GetType(), - "New and old data types are mismatched!"); - - // if we are here it implies that we have two assets with the same asset id, and we are - // trying to replace the old asset with the new asset which was not created using the asset manager system. - // In this scenario if any other system have cached the old asset then the asset wont be destroyed - // because of creation token mismatch when it's ref count finally goes to zero. Since the old asset is not shareable anymore - // manually setting the creationToken to default creation token will ensure that the asset is destroyed correctly. - asset.m_assetData->m_creationToken = ++m_creationTokenGenerator; - if (found != m_assets.end()) + assetData->m_assetId = assetId; + assetData->m_creationToken = ++m_creationTokenGenerator; + assetData->RegisterWithHandler(handler); + if (assetData->IsRegisterReadonlyAndShareable()) { - found->second->m_creationToken = AZ::Data::s_defaultCreationToken; + m_assets.insert(AZStd::make_pair(assetId, assetData)); } - // Held references to old data are retained, but replace the entry in the DB for future requests. - // Fire an OnAssetReloaded message so listeners can react to the new data. - m_assets[assetId] = asset.Get(); + Asset asset(assetReferenceLoadBehavior); + asset.SetData(assetData); - // Release the reload reference. - auto reloadInfo = m_reloads.find(assetId); - if (reloadInfo != m_reloads.end()) - { - requeue = reloadInfo->second->GetRequeue(); - m_reloads.erase(reloadInfo); - } + return asset; } - // Call reloaded before we can call ReloadAsset below to preserve order - AssetBus::Event(assetId, &AssetBus::Events::OnAssetReloaded, asset); - // Release the lock before we call reload - if (requeue) + } + } + else + { + AZ_Error("AssetDatabase", false, "Asset (id=%s, type=%s) already exists in the database! Asset not created!", assetId.ToString().c_str(), assetType.ToString().c_str()); + } + return Asset(assetReferenceLoadBehavior); + } + + //========================================================================= + // ReleaseAsset + //========================================================================= + void AssetManager::ReleaseAsset(AssetData* asset, AssetId assetId, AssetType assetType, bool removeAssetFromHash, int creationToken) + { + AZ_Assert(asset, "Cannot release NULL AssetPtr!"); + + if(m_suspendAssetRelease) + { + return; + } + + bool wasInAssetsHash = false; // We do support assets that are not registered in the asset manager (with the same ID too). + bool destroyAsset = false; + + if (removeAssetFromHash) + { + AZStd::scoped_lock asset_lock(m_assetMutex); + AssetMap::iterator it = m_assets.find(assetId); + // need to check the count again in here in case + // someone was trying to get the asset on another thread + // Set it to -1 so only this thread will attempt to clean up the cache and delete the asset + int expectedRefCount = 0; + // if the assetId is not in the map or if the identifierId + // do not match it implies that the asset has been already destroyed. + // if the usecount is non zero it implies that we cannot destroy this asset. + if (it != m_assets.end() && it->second->m_creationToken == creationToken && it->second->m_weakUseCount.compare_exchange_strong(expectedRefCount, -1)) + { + wasInAssetsHash = true; + m_assets.erase(it); + destroyAsset = true; + } + } + else + { + // if an asset is not shareable, it implies that that asset is not in the map + // and therefore once its ref count goes to zero it cannot go back up again and therefore we can safely destroy it + destroyAsset = true; + } + + // We have to separate the code which was removing the asset from the m_asset map while being locked, but then actually destroy the asset + // while the lock is not held since destroying the asset while holding the lock can cause a deadlock. + if (destroyAsset) + { + if(m_debugAssetEvents) + { + m_debugAssetEvents->ReleaseAsset(assetId); + } + + // find the asset type handler + AssetHandlerMap::iterator handlerIt = m_handlers.find(assetType); + if (handlerIt != m_handlers.end()) + { + AssetHandler* handler = handlerIt->second; + if (asset) { - ReloadAsset(assetId, asset.GetAutoLoadBehavior()); + handler->DestroyAsset(asset); + + if (wasInAssetsHash) + { + AssetBus::QueueEvent(assetId, &AssetBus::Events::OnAssetUnloaded, assetId, assetType); + } } } else { - AssetBus::Event(assetId, &AssetBus::Events::OnAssetReloaded, asset); + AZ_Assert(false, "No handler was registered for asset of type %s but it was still in the AssetManager as %s", assetType.ToString().c_str(), asset->GetId().ToString().c_str()); + } + } + } + + void AssetManager::OnAssetUnused(AssetData* asset) + { + // If we're currently suspending asset releases, don't get rid of the asset containers either. + if (m_suspendAssetRelease) + { + return; + } + + ReleaseAssetContainersForAsset(asset); + } + + void AssetManager::ReleaseAssetContainersForAsset(AssetData* asset) + { + // Release any containers that were loading this asset + AZStd::scoped_lock lock(m_assetContainerMutex); + + AssetId assetId = asset->GetId(); + + auto rangeItr = m_ownedAssetContainerLookup.equal_range(assetId); + + for (auto itr = rangeItr.first; itr != rangeItr.second;) + { + AZ_Assert(itr->second->GetContainerAssetId() == assetId, + "Asset container is incorrectly associated with the asset being destroyed."); + itr->second->ClearRootAsset(); + + // Only remove owned asset containers if they aren't currently loading. + // If they *are* currently loading, removing them could cause dependent asset loads that were triggered to + // remain in a perpetual loading state. Instead, leave the containers for now, they will get removed during + // the OnAssetContainerReady callback. + if (!itr->second->IsLoading()) + { + m_ownedAssetContainers.erase(itr->second); + itr = m_ownedAssetContainerLookup.erase(itr); + } + else + { + ++itr; + } + } + } + + //========================================================================= + // SaveAsset + // [9/13/2012] + //========================================================================= + void AssetManager::SaveAsset(const Asset& asset) + { + AssetHandler* handler; + { + // find the asset type handler + AssetHandlerMap::iterator handlerIt = m_handlers.find(asset.GetType()); + AZ_Assert(handlerIt != m_handlers.end(), "No handler was registered for this asset [type:%s id:%s]!", asset.GetType().ToString().c_str(), asset.GetId().ToString().c_str()); + handler = handlerIt->second; + } + + // start the data saving + SaveAssetJob* saveJob = aznew SaveAssetJob(JobContext::GetGlobalContext(), this, asset, handler); + saveJob->Start(); + } + + //========================================================================= + // ReloadAsset + //========================================================================= + void AssetManager::ReloadAsset(const AssetId& assetId, AssetLoadBehavior assetReferenceLoadBehavior, bool isAutoReload) + { + AZStd::scoped_lock assetLock(m_assetMutex); + auto assetIter = m_assets.find(assetId); + + if (assetIter == m_assets.end() || assetIter->second->IsLoading()) + { + // Only existing assets can be reloaded. + return; + } + + auto reloadIter = m_reloads.find(assetId); + if (reloadIter != m_reloads.end()) + { + auto curStatus = reloadIter->second.GetData()->GetStatus(); + // We don't need another reload if we're in "Queued" state because that reload has not actually begun yet. + // If it is in Loading state we want to pass by and allow the new assetData to be created and start the new reload + // As the current load could already be stale + if (curStatus == AssetData::AssetStatus::Queued) + { + return; + } + else if (curStatus == AssetData::AssetStatus::Loading || curStatus == AssetData::AssetStatus::StreamReady) + { + // Don't flood the tick bus - this value will be checked when the asset load completes + reloadIter->second->SetRequeue(true); + return; } } - //========================================================================= - // GetModifiedLoadStreamInfoForAsset - //========================================================================= - AssetStreamInfo AssetManager::GetModifiedLoadStreamInfoForAsset(const Asset& asset, AssetHandler* handler) + AssetData* newAssetData = nullptr; + AssetHandler* handler = nullptr; + + bool preventAutoReload = isAutoReload && assetIter->second && !assetIter->second->HandleAutoReload(); + + // when Asset's constructor is called (the one that takes an AssetData), it updates the AssetID + // of the Asset to be the real latest canonical assetId of the asset, so we cache that here instead of have it happen + // implicitly and repeatedly for anything we call. + Asset currentAsset(assetIter->second, AZ::Data::AssetLoadBehavior::Default); + + if (!assetIter->second->IsRegisterReadonlyAndShareable() && !preventAutoReload) { - AssetStreamInfo loadInfo = GetLoadStreamInfoForAsset(asset.GetId(), asset.GetType()); - if (!loadInfo.IsValid()) - { - // opportunity for handler to do default substitution: - AZ::Data::AssetId fallbackId = handler->AssetMissingInCatalog(asset); - if (fallbackId.IsValid()) - { - loadInfo = GetLoadStreamInfoForAsset(fallbackId, asset.GetType()); - } - } - - // Give the handler an opportunity to modify any of the load info before creating the dataStream. - handler->GetCustomAssetStreamInfoForLoad(loadInfo); - - return loadInfo; + // Reloading an "instance asset" is basically a no-op. + // We'll simply notify users to reload the asset. + AssetBus::QueueFunction(&AssetManager::NotifyAssetReloaded, this, currentAsset); + return; + } + else + { + AssetBus::QueueFunction(&AssetManager::NotifyAssetPreReload, this, currentAsset); } - //========================================================================= - // QueueAsyncStreamLoad - //========================================================================= - void AssetManager::QueueAsyncStreamLoad(Asset asset, AZStd::shared_ptr dataStream, - const AZ::Data::AssetStreamInfo& streamInfo, bool isReload, - AssetHandler* handler, const AssetLoadParameters& loadParams, bool signalLoaded) + // Current AssetData has requested not to be auto reloaded + if (preventAutoReload) { - AZ_PROFILE_FUNCTION(AzCore); + return; + } - // Set up the callback that will process the asset data once the raw file load is finished. - // The callback is declared as mutable so that we can clear weakAsset within the callback. The refcount in weakAsset - // can trigger an AssetManager::ReleaseAsset call. If this occurs during lambda cleanup, it could happen at any time - // on the file streamer thread as streamer requests get recycled, including during (or after) AssetManager shutdown. - // By controlling when the refcount is changed, we can ensure that it occurs while the AssetManager is still active. - auto assetDataStreamCallback = [this, loadParams, handler, dataStream, signalLoaded, isReload, - weakAsset = AssetInternal::WeakAsset(asset)] - (AZ::IO::IStreamerTypes::RequestStatus status) mutable + // Resolve the asset handler and allocate new data for the reload. + { + AssetHandlerMap::iterator handlerIt = m_handlers.find(currentAsset.GetType()); + AZ_Assert(handlerIt != m_handlers.end(), "No handler was registered for this asset [type:%s id:%s]!", + currentAsset.GetType().ToString().c_str(), currentAsset.GetId().ToString().c_str()); + handler = handlerIt->second; + + newAssetData = handler->CreateAsset(currentAsset.GetId(), currentAsset.GetType()); + if (newAssetData) { - auto assetId = weakAsset.GetId(); + newAssetData->m_assetId = currentAsset.GetId(); + newAssetData->RegisterWithHandler(handler); + } + } - Asset loadingAsset = weakAsset.GetStrongReference(); + if (newAssetData) + { + // For reloaded assets, we need to hold an internal reference to ensure the data + // isn't immediately destroyed. Since reloads are not a shipping feature, we'll + // hold this reference indefinitely, but we'll only hold the most recent one for + // a given asset Id. - if (loadingAsset) + newAssetData->m_status = AssetData::AssetStatus::Queued; + Asset newAsset(newAssetData, assetReferenceLoadBehavior); + + m_reloads[newAsset.GetId()] = newAsset; + + UpdateDebugStatus(newAsset); + + AZStd::shared_ptr dataStream; + AssetStreamInfo loadInfo = GetModifiedLoadStreamInfoForAsset(newAsset, handler); + constexpr bool isReload = true; + if (loadInfo.IsValid()) + { + // Create the AssetDataStream instance here so it can claim an asset reference inside the lock (for a total + // count of 2 before starting the load), otherwise the refcount will be 1, and the load could be canceled + // before it is started, which creates state consistency issues. + + dataStream = AZStd::make_shared(handler->GetAssetBufferAllocator()); + if (dataStream) { - AZ_PROFILE_SCOPE(AzCore, "AZ::Data::LoadAssetStreamerCallback %s", - loadingAsset.GetHint().c_str()); - { - AZStd::scoped_lock assetLock(m_assetMutex); - AssetData* data = loadingAsset.Get(); - if (data->GetStatus() != AssetData::AssetStatus::Queued) - { - AZ_Warning("AssetManager", false, "Asset %s no longer in Queued state, abandoning load", loadingAsset.GetId().ToString().c_str()); - return; - } - data->m_status = AssetData::AssetStatus::StreamReady; - } - - // The callback from AZ Streamer blocks the streaming thread until this function completes. To minimize the overhead, - // do the majority of the work in a separate job. - auto loadJob = aznew LoadAssetJob(this, loadingAsset, - dataStream, isReload, status, handler, loadParams, signalLoaded); - - bool jobQueued = false; - - // If there's already an active blocking request waiting for this load to complete, let that thread handle - // the load itself instead of consuming a second thread. - { - AZStd::scoped_lock requestLock(m_activeBlockingRequestMutex); - auto range = m_activeBlockingRequests.equal_range(assetId); - for(auto blockingRequest = range.first; blockingRequest != range.second; ++blockingRequest) - { - if(blockingRequest->second->QueueAssetLoadJob(loadJob)) - { - jobQueued = true; - break; - } - } - } - - if (!jobQueued) - { - loadJob->Start(); - } + // Currently there isn't a clear use case for needing to adjust priority for reloads so the default load priority is used + constexpr bool signalLoaded = false; // this is a reload, so don't signal dependent-asset loads + QueueAsyncStreamLoad(newAsset, dataStream, loadInfo, isReload, + handler, {}, signalLoaded); } else { - BlockingAssetLoadBus::Event(assetId, &BlockingAssetLoadBus::Events::OnLoadCanceled, assetId); - AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetCanceled, assetId); + AZ_Assert(false, "Failed to create dataStream to reload asset %s (%s)", + newAsset.GetId().ToString().c_str(), + newAsset.GetHint().c_str()); + } + } + else + { + // Asset creation was successful, but asset loading isn't, so trigger the OnAssetError notification + AZ_Error("AssetDatabase", false, "Failed to retrieve required information for asset %s (%s)", + newAsset.GetId().ToString().c_str(), + newAsset.GetHint().c_str()); + + constexpr bool loadSucceeded = false; + AssetManager::Instance().PostLoad(newAsset, loadSucceeded, isReload, handler); + } + + } + } + + //========================================================================= + // ReloadAssetFromData + //========================================================================= + void AssetManager::ReloadAssetFromData(const Asset& asset) + { + bool shouldAssignAssetData = false; + + { + AZ_Assert(asset.Get(), "Asset data for reload is missing."); + AZStd::scoped_lock assetLock(m_assetMutex); + AZ_Assert( + m_assets.find(asset.GetId()) != m_assets.end(), + "Unable to reload asset %s because it's not in the AssetManager's asset list.", asset.ToString().c_str()); + AZ_Assert( + m_assets.find(asset.GetId()) == m_assets.end() || + asset->RTTI_GetType() == m_assets.find(asset.GetId())->second->RTTI_GetType(), + "New and old data types are mismatched!"); + + auto found = m_assets.find(asset.GetId()); + if ((found == m_assets.end()) || (asset->RTTI_GetType() != found->second->RTTI_GetType())) + { + return; // this will just lead to crashes down the line and the above asserts cover this. + } + + AssetData* newData = asset.Get(); + + if (found->second != newData) + { + // Notify users that we are about to change asset + AssetBus::Event(asset.GetId(), &AssetBus::Events::OnAssetPreReload, asset); + + // Resolve the asset handler and account for the new asset instance. + { + [[maybe_unused]] AssetHandlerMap::iterator handlerIt = m_handlers.find(newData->GetType()); + AZ_Assert( + handlerIt != m_handlers.end(), "No handler was registered for this asset [type:%s id:%s]!", + newData->GetType().ToString().c_str(), newData->GetId().ToString().c_str()); } - // *After* the loadJob has been created, clear our asset references and remove the active streamer requests. - // This needs to happen after the loadJob creation to ensure that on AssetManager shutdown, there are no brief - // windows in which requests and/or jobs are still active after we've removed our tracking of the requests and jobs. - - // Also, if the asset references don't get cleared until after the callback completes, or at some indeterminate later - // time when the File Streamer cleans up the file requests (for the weakAsset lambda parameter), then it's possible that - // they will trigger a ReleaseAsset call sometime after the AssetManager has begun to shut down, which can lead to - // race conditions. - - // Make sure the streamer request is removed first before the asset is released - // If the asset is released first it could lead to a race condition where another thread starts loading the asset - // again and attempts to add a new streamer request with the same ID before the old one has been removed, causing - // that load request to fail - RemoveActiveStreamerRequest(assetId); - weakAsset = {}; - loadingAsset.Reset(); - }; - - auto&& [deadline, priority] = GetEffectiveDeadlineAndPriority(*handler, asset.GetType(), loadParams); - - // Track the load request and queue the asset data stream load. - AddActiveStreamerRequest(asset.GetId(), dataStream); - dataStream->Open( - streamInfo.m_streamName, - streamInfo.m_dataOffset, - streamInfo.m_dataLen, - deadline, priority, assetDataStreamCallback); + shouldAssignAssetData = true; + } } - //========================================================================= - // NotifyAssetReady - //========================================================================= - void AssetManager::NotifyAssetReady(Asset asset) - { - AssetData* data = asset.Get(); - AZ_Assert(data, "NotifyAssetReady: asset is missing info!"); - data->m_status = AssetData::AssetStatus::Ready; - - AssetBus::Event(asset.GetId(), &AssetBus::Events::OnAssetReady, asset); - } - - //========================================================================= - // NotifyAssetPreReload - //========================================================================= - void AssetManager::NotifyAssetPreReload(Asset asset) - { - AssetBus::Event(asset.GetId(), &AssetBus::Events::OnAssetPreReload, asset); - } - - //========================================================================= - // NotifyAssetReloaded - //========================================================================= - void AssetManager::NotifyAssetReloaded(Asset asset) + // We specifically perform this outside of the m_assetMutex lock so that the lock isn't held at the point that + // OnAssetReload is triggered inside of AssignAssetData. Otherwise, we open up a high potential for deadlocks. + if (shouldAssignAssetData) { AssignAssetData(asset); } + } - //========================================================================= - // NotifyAssetReloaded - //========================================================================= - void AssetManager::NotifyAssetReloadError(Asset asset) + //========================================================================= + // GetHandler + //========================================================================= + AssetHandler* AssetManager::GetHandler(const AssetType& assetType) + { + auto handlerEntry = m_handlers.find(assetType); + if (handlerEntry != m_handlers.end()) { - // Failed reloads have no side effects. Just notify observers (error reporting, etc). + return handlerEntry->second; + } + return nullptr; + } + + //========================================================================= + // AssignAssetData + //========================================================================= + void AssetManager::AssignAssetData(const Asset& asset) + { + AZ_Assert(asset.Get(), "Reloaded data is missing!"); + + const AssetId& assetId = asset.GetId(); + + asset->m_status = AssetData::AssetStatus::Ready; + UpdateDebugStatus(asset); + + if (asset->IsRegisterReadonlyAndShareable()) + { + bool requeue{ false }; { - AZStd::lock_guard assetLock(m_assetMutex); - m_reloads.erase(asset.GetId()); - } - AssetBus::Event(asset.GetId(), &AssetBus::Events::OnAssetReloadError, asset); - } - - //========================================================================= - // NotifyAssetError - //========================================================================= - void AssetManager::NotifyAssetError(Asset asset) - { - asset.Get()->m_status = AssetData::AssetStatus::Error; - AssetBus::Event(asset.GetId(), &AssetBus::Events::OnAssetError, asset); - } - - void AssetManager::NotifyAssetCanceled(AssetId assetId) - { - AssetBus::Event(assetId, &AssetBus::Events::OnAssetCanceled, assetId); - } - - void AssetManager::NotifyAssetContainerReady(Asset asset) - { - AssetBus::Event(asset.GetId(), &AssetBus::Events::OnAssetContainerReady, asset); - } - - //========================================================================= - // AddJob - // [04/02/2014] - //========================================================================= - void AssetManager::AddJob(AssetDatabaseJob* job) - { - AZStd::scoped_lock assetLock(m_activeJobOrRequestMutex); - - m_activeJobs.push_back(*job); - } - - //========================================================================= - // ValidateAndRegisterAssetLoading - //========================================================================= - bool AssetManager::ValidateAndRegisterAssetLoading(const Asset& asset) - { - AssetData* data = asset.Get(); - { - AZStd::scoped_lock assetLock(m_assetMutex); - if (data) + auto found = m_assets.find(assetId); + AZ_Assert(found == m_assets.end() || asset.Get()->RTTI_GetType() == found->second->RTTI_GetType(), + "New and old data types are mismatched!"); + + // if we are here it implies that we have two assets with the same asset id, and we are + // trying to replace the old asset with the new asset which was not created using the asset manager system. + // In this scenario if any other system have cached the old asset then the asset wont be destroyed + // because of creation token mismatch when it's ref count finally goes to zero. Since the old asset is not shareable anymore + // manually setting the creationToken to default creation token will ensure that the asset is destroyed correctly. + asset.m_assetData->m_creationToken = ++m_creationTokenGenerator; + if (found != m_assets.end()) { - // The purpose of this function is to validate this asset is still in a StreamReady - // and only then continue the load. We change status to loading if everything - // is expected which the blocking RegisterAssetLoading call does not do because it - // is already in loading status - if (data->GetStatus() != AssetData::AssetStatus::StreamReady) - { - // Something else has attempted to load this asset - return false; - } - data->m_status = AssetData::AssetStatus::Loading; - UpdateDebugStatus(asset); + found->second->m_creationToken = AZ::Data::s_defaultCreationToken; + } + + // Held references to old data are retained, but replace the entry in the DB for future requests. + // Fire an OnAssetReloaded message so listeners can react to the new data. + m_assets[assetId] = asset.Get(); + + // Release the reload reference. + auto reloadInfo = m_reloads.find(assetId); + if (reloadInfo != m_reloads.end()) + { + requeue = reloadInfo->second->GetRequeue(); + m_reloads.erase(reloadInfo); } } + // Call reloaded before we can call ReloadAsset below to preserve order + AssetBus::Event(assetId, &AssetBus::Events::OnAssetReloaded, asset); + // Release the lock before we call reload + if (requeue) + { + ReloadAsset(assetId, asset.GetAutoLoadBehavior()); + } + } + else + { + AssetBus::Event(assetId, &AssetBus::Events::OnAssetReloaded, asset); + } + } - return true; + //========================================================================= + // GetModifiedLoadStreamInfoForAsset + //========================================================================= + AssetStreamInfo AssetManager::GetModifiedLoadStreamInfoForAsset(const Asset& asset, AssetHandler* handler) + { + AssetStreamInfo loadInfo = GetLoadStreamInfoForAsset(asset.GetId(), asset.GetType()); + if (!loadInfo.IsValid()) + { + // opportunity for handler to do default substitution: + AZ::Data::AssetId fallbackId = handler->AssetMissingInCatalog(asset); + if (fallbackId.IsValid()) + { + loadInfo = GetLoadStreamInfoForAsset(fallbackId, asset.GetType()); + } } - //========================================================================= - // RegisterAssetLoading - //========================================================================= - void AssetManager::RegisterAssetLoading(const Asset& asset) - { - AZ_PROFILE_FUNCTION(AzCore); + // Give the handler an opportunity to modify any of the load info before creating the dataStream. + handler->GetCustomAssetStreamInfoForLoad(loadInfo); - AssetData* data = asset.Get(); + return loadInfo; + } + + //========================================================================= + // QueueAsyncStreamLoad + //========================================================================= + void AssetManager::QueueAsyncStreamLoad(Asset asset, AZStd::shared_ptr dataStream, + const AZ::Data::AssetStreamInfo& streamInfo, bool isReload, + AssetHandler* handler, const AssetLoadParameters& loadParams, bool signalLoaded) + { + AZ_PROFILE_FUNCTION(AzCore); + + // Set up the callback that will process the asset data once the raw file load is finished. + // The callback is declared as mutable so that we can clear weakAsset within the callback. The refcount in weakAsset + // can trigger an AssetManager::ReleaseAsset call. If this occurs during lambda cleanup, it could happen at any time + // on the file streamer thread as streamer requests get recycled, including during (or after) AssetManager shutdown. + // By controlling when the refcount is changed, we can ensure that it occurs while the AssetManager is still active. + auto assetDataStreamCallback = [this, loadParams, handler, dataStream, signalLoaded, isReload, + weakAsset = AssetInternal::WeakAsset(asset)] + (AZ::IO::IStreamerTypes::RequestStatus status) mutable + { + auto assetId = weakAsset.GetId(); + + Asset loadingAsset = weakAsset.GetStrongReference(); + + if (loadingAsset) + { + AZ_PROFILE_SCOPE(AzCore, "AZ::Data::LoadAssetStreamerCallback %s", + loadingAsset.GetHint().c_str()); + { + AZStd::scoped_lock assetLock(m_assetMutex); + AssetData* data = loadingAsset.Get(); + if (data->GetStatus() != AssetData::AssetStatus::Queued) + { + AZ_Warning("AssetManager", false, "Asset %s no longer in Queued state, abandoning load", loadingAsset.GetId().ToString().c_str()); + return; + } + data->m_status = AssetData::AssetStatus::StreamReady; + } + + // The callback from AZ Streamer blocks the streaming thread until this function completes. To minimize the overhead, + // do the majority of the work in a separate job. + auto loadJob = aznew LoadAssetJob(this, loadingAsset, + dataStream, isReload, status, handler, loadParams, signalLoaded); + + bool jobQueued = false; + + // If there's already an active blocking request waiting for this load to complete, let that thread handle + // the load itself instead of consuming a second thread. + { + AZStd::scoped_lock requestLock(m_activeBlockingRequestMutex); + auto range = m_activeBlockingRequests.equal_range(assetId); + for(auto blockingRequest = range.first; blockingRequest != range.second; ++blockingRequest) + { + if(blockingRequest->second->QueueAssetLoadJob(loadJob)) + { + jobQueued = true; + break; + } + } + } + + if (!jobQueued) + { + loadJob->Start(); + } + } + else + { + BlockingAssetLoadBus::Event(assetId, &BlockingAssetLoadBus::Events::OnLoadCanceled, assetId); + AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetCanceled, assetId); + } + + // *After* the loadJob has been created, clear our asset references and remove the active streamer requests. + // This needs to happen after the loadJob creation to ensure that on AssetManager shutdown, there are no brief + // windows in which requests and/or jobs are still active after we've removed our tracking of the requests and jobs. + + // Also, if the asset references don't get cleared until after the callback completes, or at some indeterminate later + // time when the File Streamer cleans up the file requests (for the weakAsset lambda parameter), then it's possible that + // they will trigger a ReleaseAsset call sometime after the AssetManager has begun to shut down, which can lead to + // race conditions. + + // Make sure the streamer request is removed first before the asset is released + // If the asset is released first it could lead to a race condition where another thread starts loading the asset + // again and attempts to add a new streamer request with the same ID before the old one has been removed, causing + // that load request to fail + RemoveActiveStreamerRequest(assetId); + weakAsset = {}; + loadingAsset.Reset(); + }; + + auto&& [deadline, priority] = GetEffectiveDeadlineAndPriority(*handler, asset.GetType(), loadParams); + + // Track the load request and queue the asset data stream load. + AddActiveStreamerRequest(asset.GetId(), dataStream); + dataStream->Open( + streamInfo.m_streamName, + streamInfo.m_dataOffset, + streamInfo.m_dataLen, + deadline, priority, assetDataStreamCallback); + } + + //========================================================================= + // NotifyAssetReady + //========================================================================= + void AssetManager::NotifyAssetReady(Asset asset) + { + AssetData* data = asset.Get(); + AZ_Assert(data, "NotifyAssetReady: asset is missing info!"); + data->m_status = AssetData::AssetStatus::Ready; + + AssetBus::Event(asset.GetId(), &AssetBus::Events::OnAssetReady, asset); + } + + //========================================================================= + // NotifyAssetPreReload + //========================================================================= + void AssetManager::NotifyAssetPreReload(Asset asset) + { + AssetBus::Event(asset.GetId(), &AssetBus::Events::OnAssetPreReload, asset); + } + + //========================================================================= + // NotifyAssetReloaded + //========================================================================= + void AssetManager::NotifyAssetReloaded(Asset asset) + { + AssignAssetData(asset); + } + + //========================================================================= + // NotifyAssetReloaded + //========================================================================= + void AssetManager::NotifyAssetReloadError(Asset asset) + { + // Failed reloads have no side effects. Just notify observers (error reporting, etc). + { + AZStd::lock_guard assetLock(m_assetMutex); + m_reloads.erase(asset.GetId()); + } + AssetBus::Event(asset.GetId(), &AssetBus::Events::OnAssetReloadError, asset); + } + + //========================================================================= + // NotifyAssetError + //========================================================================= + void AssetManager::NotifyAssetError(Asset asset) + { + asset.Get()->m_status = AssetData::AssetStatus::Error; + AssetBus::Event(asset.GetId(), &AssetBus::Events::OnAssetError, asset); + } + + void AssetManager::NotifyAssetCanceled(AssetId assetId) + { + AssetBus::Event(assetId, &AssetBus::Events::OnAssetCanceled, assetId); + } + + void AssetManager::NotifyAssetContainerReady(Asset asset) + { + AssetBus::Event(asset.GetId(), &AssetBus::Events::OnAssetContainerReady, asset); + } + + //========================================================================= + // AddJob + // [04/02/2014] + //========================================================================= + void AssetManager::AddJob(AssetDatabaseJob* job) + { + AZStd::scoped_lock assetLock(m_activeJobOrRequestMutex); + + m_activeJobs.push_back(*job); + } + + //========================================================================= + // ValidateAndRegisterAssetLoading + //========================================================================= + bool AssetManager::ValidateAndRegisterAssetLoading(const Asset& asset) + { + AssetData* data = asset.Get(); + { + + AZStd::scoped_lock assetLock(m_assetMutex); if (data) { + // The purpose of this function is to validate this asset is still in a StreamReady + // and only then continue the load. We change status to loading if everything + // is expected which the blocking RegisterAssetLoading call does not do because it + // is already in loading status + if (data->GetStatus() != AssetData::AssetStatus::StreamReady) + { + // Something else has attempted to load this asset + return false; + } data->m_status = AssetData::AssetStatus::Loading; UpdateDebugStatus(asset); } } - //========================================================================= - // UnregisterAssetLoadingByThread - //========================================================================= - void AssetManager::UnregisterAssetLoading([[maybe_unused]] const Asset& asset) + return true; + } + + //========================================================================= + // RegisterAssetLoading + //========================================================================= + void AssetManager::RegisterAssetLoading(const Asset& asset) + { + AZ_PROFILE_FUNCTION(AzCore); + + AssetData* data = asset.Get(); + if (data) { - AZ_PROFILE_FUNCTION(AzCore); - } - - //========================================================================= - // RemoveJob - // [04/02/2014] - //========================================================================= - void AssetManager::RemoveJob(AssetDatabaseJob* job) - { - AZStd::scoped_lock assetLock(m_activeJobOrRequestMutex); - - m_activeJobs.erase(*job); - } - - //========================================================================= - // AddActiveStreamerRequest - //========================================================================= - void AssetManager::AddActiveStreamerRequest(AssetId assetId, AZStd::shared_ptr readRequest) - { - AZStd::scoped_lock assetLock(m_activeJobOrRequestMutex); - - // Track the request to allow for manual cancellation and for validating completion before AssetManager shutdown - [[maybe_unused]] auto inserted = - m_activeAssetDataStreamRequests.insert(AZStd::make_pair(assetId, readRequest)); - AZ_Assert(inserted.second, "Failed to insert streaming request into map for later retrieval by asset."); - - } - - void AssetManager::RescheduleStreamerRequest(AssetId assetId, AZStd::chrono::milliseconds newDeadline, AZ::IO::IStreamerTypes::Priority newPriority) - { - AZStd::scoped_lock lock(m_activeJobOrRequestMutex); - - auto iterator = m_activeAssetDataStreamRequests.find(assetId); - - if (iterator != m_activeAssetDataStreamRequests.end()) - { - iterator->second->Reschedule(newDeadline, newPriority); - } - } - - //========================================================================= - // RemoveActiveStreamerRequest - //========================================================================= - void AssetManager::RemoveActiveStreamerRequest(AssetId assetData) - { - AZStd::scoped_lock assetLock(m_activeJobOrRequestMutex); - m_activeAssetDataStreamRequests.erase(assetData); - } - - //========================================================================= - // HasActiveJobsOrStreamerRequests - //========================================================================= - bool AssetManager::HasActiveJobsOrStreamerRequests() - { - AZStd::scoped_lock assetLock(m_activeJobOrRequestMutex); - - return (!(m_activeJobs.empty() && m_activeAssetDataStreamRequests.empty())); - } - - //========================================================================= - // AddBlockingRequest - //========================================================================= - void AssetManager::AddBlockingRequest(AssetId assetId, WaitForAsset* blockingRequest) - { - AZStd::scoped_lock requestLock(m_activeBlockingRequestMutex); - - [[maybe_unused]] auto inserted = m_activeBlockingRequests.insert(AZStd::make_pair(assetId, blockingRequest)); - AZ_Assert(inserted.second, "Failed to track blocking request for asset %s", assetId.ToString().c_str()); - } - - //========================================================================= - // RemoveBlockingRequest - //========================================================================= - void AssetManager::RemoveBlockingRequest(AssetId assetId, WaitForAsset* blockingRequest) - { - AZStd::scoped_lock requestLock(m_activeBlockingRequestMutex); - [[maybe_unused]] bool requestFound = false; - for (auto assetIdIterator = m_activeBlockingRequests.find(assetId); assetIdIterator != m_activeBlockingRequests.end(); ) - { - if (assetIdIterator->second == blockingRequest) - { - m_activeBlockingRequests.erase(assetIdIterator); - requestFound = true; - break; - } - else - { - assetIdIterator++; - } - } - - AZ_Assert(requestFound, "Failed to erase blocking request for asset %s", assetId.ToString().c_str()); - } - - - //========================================================================= - // GetLoadStreamInfoForAsset() - // [04/04/2014] - //========================================================================= - AssetStreamInfo AssetManager::GetLoadStreamInfoForAsset(const AssetId& assetId, const AssetType& assetType) - { - AZStd::scoped_lock catalogLock(m_catalogMutex); - AssetCatalogMap::iterator catIt = m_catalogs.find(assetType); - if (catIt == m_catalogs.end()) - { - AZ_Error("Asset", false, "Asset [type:%s id:%s] with this type doesn't have a catalog!", assetType.template ToString().c_str(), assetId.ToString().c_str()); - return AssetStreamInfo(); - } - return catIt->second->GetStreamInfoForLoad(assetId, assetType); - } - - //========================================================================= - // GetSaveStreamInfoForAsset() - // [04/04/2014] - //========================================================================= - AssetStreamInfo AssetManager::GetSaveStreamInfoForAsset(const AssetId& assetId, const AssetType& assetType) - { - AZStd::scoped_lock catalogLock(m_catalogMutex); - AssetCatalogMap::iterator catIt = m_catalogs.find(assetType); - if (catIt == m_catalogs.end()) - { - AZ_Error("Asset", false, "Asset [type:%s id:%s] with this type doesn't have a catalog!", assetType.template ToString().c_str(), assetId.ToString().c_str()); - return AssetStreamInfo(); - } - return catIt->second->GetStreamInfoForSave(assetId, assetType); - } - - //========================================================================= - // OnAssetReady - // [04/02/2014] - //========================================================================= - void AssetManager::OnAssetReady(const Asset& asset) - { - AZ_Assert(asset.Get(), "OnAssetReady fired for an asset with no data."); - - // Set status immediately from within the AssetManagerBus dispatch, so it's committed before anyone is notified (e.g. job to job, via AssetJobBus). - asset.Get()->m_status = AssetData::AssetStatus::ReadyPreNotify; + data->m_status = AssetData::AssetStatus::Loading; UpdateDebugStatus(asset); - - // Queue broadcast message for delivery on game thread. - AssetBus::QueueFunction(&AssetManager::NotifyAssetReady, this, Asset(asset)); } + } - //========================================================================= - // OnAssetError - //========================================================================= - void AssetManager::OnAssetError(const Asset& asset) + //========================================================================= + // UnregisterAssetLoadingByThread + //========================================================================= + void AssetManager::UnregisterAssetLoading([[maybe_unused]] const Asset& asset) + { + AZ_PROFILE_FUNCTION(AzCore); + } + + //========================================================================= + // RemoveJob + // [04/02/2014] + //========================================================================= + void AssetManager::RemoveJob(AssetDatabaseJob* job) + { + AZStd::scoped_lock assetLock(m_activeJobOrRequestMutex); + + m_activeJobs.erase(*job); + } + + //========================================================================= + // AddActiveStreamerRequest + //========================================================================= + void AssetManager::AddActiveStreamerRequest(AssetId assetId, AZStd::shared_ptr readRequest) + { + AZStd::scoped_lock assetLock(m_activeJobOrRequestMutex); + + // Track the request to allow for manual cancellation and for validating completion before AssetManager shutdown + [[maybe_unused]] auto inserted = + m_activeAssetDataStreamRequests.insert(AZStd::make_pair(assetId, readRequest)); + AZ_Assert(inserted.second, "Failed to insert streaming request into map for later retrieval by asset."); + + } + + void AssetManager::RescheduleStreamerRequest(AssetId assetId, AZStd::chrono::milliseconds newDeadline, AZ::IO::IStreamerTypes::Priority newPriority) + { + AZStd::scoped_lock lock(m_activeJobOrRequestMutex); + + auto iterator = m_activeAssetDataStreamRequests.find(assetId); + + if (iterator != m_activeAssetDataStreamRequests.end()) { - // Set status immediately from within the AssetManagerBus dispatch, so it's committed before anyone is notified (e.g. job to job, via AssetJobBus). - asset.Get()->m_status = AssetData::AssetStatus::Error; - UpdateDebugStatus(asset); - - // Queue broadcast message for delivery on game thread. - AssetBus::QueueFunction(&AssetManager::NotifyAssetError, this, Asset(asset)); + iterator->second->Reschedule(newDeadline, newPriority); } + } - void AssetManager::OnAssetCanceled(AssetId assetId) + //========================================================================= + // RemoveActiveStreamerRequest + //========================================================================= + void AssetManager::RemoveActiveStreamerRequest(AssetId assetData) + { + AZStd::scoped_lock assetLock(m_activeJobOrRequestMutex); + m_activeAssetDataStreamRequests.erase(assetData); + } + + //========================================================================= + // HasActiveJobsOrStreamerRequests + //========================================================================= + bool AssetManager::HasActiveJobsOrStreamerRequests() + { + AZStd::scoped_lock assetLock(m_activeJobOrRequestMutex); + + return (!(m_activeJobs.empty() && m_activeAssetDataStreamRequests.empty())); + } + + //========================================================================= + // AddBlockingRequest + //========================================================================= + void AssetManager::AddBlockingRequest(AssetId assetId, WaitForAsset* blockingRequest) + { + AZStd::scoped_lock requestLock(m_activeBlockingRequestMutex); + + [[maybe_unused]] auto inserted = m_activeBlockingRequests.insert(AZStd::make_pair(assetId, blockingRequest)); + AZ_Assert(inserted.second, "Failed to track blocking request for asset %s", assetId.ToString().c_str()); + } + + //========================================================================= + // RemoveBlockingRequest + //========================================================================= + void AssetManager::RemoveBlockingRequest(AssetId assetId, WaitForAsset* blockingRequest) + { + AZStd::scoped_lock requestLock(m_activeBlockingRequestMutex); + [[maybe_unused]] bool requestFound = false; + for (auto assetIdIterator = m_activeBlockingRequests.find(assetId); assetIdIterator != m_activeBlockingRequests.end(); ) { - // Queue broadcast message for delivery on game thread. - AssetBus::QueueFunction(&AssetManager::NotifyAssetCanceled, this, assetId); - } - - void AssetManager::ReleaseOwnedAssetContainer(AssetContainer* assetContainer) - { - AZ_Assert(assetContainer, "Trying to release a null assetContainer pointer!"); - AZStd::scoped_lock lock(m_assetContainerMutex); - auto rangeItr = m_ownedAssetContainerLookup.equal_range(assetContainer->GetContainerAssetId()); - - for (auto itr = rangeItr.first; itr != rangeItr.second; ++itr) + if (assetIdIterator->second == blockingRequest) { - if (itr->second == assetContainer) - { - m_ownedAssetContainerLookup.erase(itr); - break; - } - } - - m_ownedAssetContainers.erase(assetContainer); - } - - void AssetManager::OnAssetContainerReady(AssetContainer* assetContainer) - { - AssetBus::QueueFunction([this, assetContainer, asset = assetContainer->GetRootAsset()]() - { - NotifyAssetContainerReady(asset); - ReleaseOwnedAssetContainer(assetContainer); - }); - } - - void AssetManager::OnAssetContainerCanceled(AssetContainer* assetContainer) - { - AssetBus::QueueFunction([this, assetContainer]() - { - ReleaseOwnedAssetContainer(assetContainer); - }); - } - - //========================================================================= - // OnAssetReloaded - //========================================================================= - void AssetManager::OnAssetReloaded(const Asset& asset) - { - // Queue broadcast message for delivery on game thread. - AssetBus::QueueFunction(&AssetManager::NotifyAssetReloaded, this, Asset(asset)); - } - - //========================================================================= - // OnAssetReloadError - //========================================================================= - void AssetManager::OnAssetReloadError(const Asset& asset) - { - // Queue broadcast message for delivery on game thread. - AssetBus::QueueFunction(&AssetManager::NotifyAssetReloadError, this, Asset(asset)); - } - - - //========================================================================= - // AssetHandler - // [04/03/2014] - //========================================================================= - AssetHandler::AssetHandler() - : m_nHandledTypes(0) - { - } - - //========================================================================= - // ~AssetHandler - // [04/03/2014] - //========================================================================= - AssetHandler::~AssetHandler() - { - if (m_nHandledTypes > 0) - { - AssetManager::Instance().UnregisterHandler(this); - } - - AZ_Error("AssetDatabase", m_nHandledTypes == 0, "Asset handler is being destroyed but there are still %d asset types being handled by it!", (int)m_nHandledTypes); - } - - //========================================================================= - // LoadAssetDataFromStream - //========================================================================= - AssetHandler::LoadResult AssetHandler::LoadAssetDataFromStream( - const Asset& asset, - AZStd::shared_ptr stream, - const AssetFilterCB& assetLoadFilterCB) - { - AZ_PROFILE_SCOPE(AzCore, "AssetHandler::LoadAssetData - %s", asset.GetHint().c_str()); - -#ifdef AZ_ENABLE_TRACING - auto start = AZStd::chrono::system_clock::now(); -#endif - - LoadResult result = LoadAssetData(asset, stream, assetLoadFilterCB); - -#ifdef AZ_ENABLE_TRACING - auto loadMs = AZStd::chrono::duration_cast( - AZStd::chrono::system_clock::now() - start); - AZ_Warning("AssetDatabase", (!cl_assetLoadWarningEnable) || - loadMs <= AZStd::chrono::milliseconds(cl_assetLoadWarningMsThreshold), - "Load time threshold exceeded: LoadAssetData call for %s took %" PRId64 " ms", - asset.GetHint().c_str(), loadMs.count()); -#endif - - return result; - } - - //========================================================================= - // InitAsset - // [04/03/2014] - //========================================================================= - void AssetHandler::InitAsset(const Asset& asset, bool loadStageSucceeded, bool isReload) - { - if (loadStageSucceeded) - { - if (isReload) - { - AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetReloaded, asset); - } - else - { - AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetReady, asset); - } + m_activeBlockingRequests.erase(assetIdIterator); + requestFound = true; + break; } else { - if (!isReload) - { - AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetError, asset); - } - else - { - AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetReloadError, asset); - } + assetIdIterator++; } } - void AssetManager::ValidateAndPostLoad(AZ::Data::Asset& asset, bool loadSucceeded, - bool isReload, AZ::Data::AssetHandler* assetHandler) + AZ_Assert(requestFound, "Failed to erase blocking request for asset %s", assetId.ToString().c_str()); + } + + + //========================================================================= + // GetLoadStreamInfoForAsset() + // [04/04/2014] + //========================================================================= + AssetStreamInfo AssetManager::GetLoadStreamInfoForAsset(const AssetId& assetId, const AssetType& assetType) + { + AZStd::scoped_lock catalogLock(m_catalogMutex); + AssetCatalogMap::iterator catIt = m_catalogs.find(assetType); + if (catIt == m_catalogs.end()) { + AZ_Error("Asset", false, "Asset [type:%s id:%s] with this type doesn't have a catalog!", assetType.template ToString().c_str(), assetId.ToString().c_str()); + return AssetStreamInfo(); + } + return catIt->second->GetStreamInfoForLoad(assetId, assetType); + } + + //========================================================================= + // GetSaveStreamInfoForAsset() + // [04/04/2014] + //========================================================================= + AssetStreamInfo AssetManager::GetSaveStreamInfoForAsset(const AssetId& assetId, const AssetType& assetType) + { + AZStd::scoped_lock catalogLock(m_catalogMutex); + AssetCatalogMap::iterator catIt = m_catalogs.find(assetType); + if (catIt == m_catalogs.end()) + { + AZ_Error("Asset", false, "Asset [type:%s id:%s] with this type doesn't have a catalog!", assetType.template ToString().c_str(), assetId.ToString().c_str()); + return AssetStreamInfo(); + } + return catIt->second->GetStreamInfoForSave(assetId, assetType); + } + + //========================================================================= + // OnAssetReady + // [04/02/2014] + //========================================================================= + void AssetManager::OnAssetReady(const Asset& asset) + { + AZ_Assert(asset.Get(), "OnAssetReady fired for an asset with no data."); + + // Set status immediately from within the AssetManagerBus dispatch, so it's committed before anyone is notified (e.g. job to job, via AssetJobBus). + asset.Get()->m_status = AssetData::AssetStatus::ReadyPreNotify; + UpdateDebugStatus(asset); + + // Queue broadcast message for delivery on game thread. + AssetBus::QueueFunction(&AssetManager::NotifyAssetReady, this, Asset(asset)); + } + + //========================================================================= + // OnAssetError + //========================================================================= + void AssetManager::OnAssetError(const Asset& asset) + { + // Set status immediately from within the AssetManagerBus dispatch, so it's committed before anyone is notified (e.g. job to job, via AssetJobBus). + asset.Get()->m_status = AssetData::AssetStatus::Error; + UpdateDebugStatus(asset); + + // Queue broadcast message for delivery on game thread. + AssetBus::QueueFunction(&AssetManager::NotifyAssetError, this, Asset(asset)); + } + + void AssetManager::OnAssetCanceled(AssetId assetId) + { + // Queue broadcast message for delivery on game thread. + AssetBus::QueueFunction(&AssetManager::NotifyAssetCanceled, this, assetId); + } + + void AssetManager::ReleaseOwnedAssetContainer(AssetContainer* assetContainer) + { + AZ_Assert(assetContainer, "Trying to release a null assetContainer pointer!"); + AZStd::scoped_lock lock(m_assetContainerMutex); + auto rangeItr = m_ownedAssetContainerLookup.equal_range(assetContainer->GetContainerAssetId()); + + for (auto itr = rangeItr.first; itr != rangeItr.second; ++itr) + { + if (itr->second == assetContainer) { - // We may need to revalidate that this asset hasn't already passed through postLoad - AZStd::scoped_lock assetLock(m_assetMutex); - if (asset->IsReady() || asset->m_status == AssetData::AssetStatus::LoadedPreReady) - { - return; - } - asset->m_status = AssetData::AssetStatus::LoadedPreReady; - UpdateDebugStatus(asset); + m_ownedAssetContainerLookup.erase(itr); + break; } - PostLoad(asset, loadSucceeded, isReload, assetHandler); } - void AssetManager::PostLoad(AZ::Data::Asset& asset, bool loadSucceeded, - bool isReload, AZ::Data::AssetHandler* assetHandler) - { - AZ_PROFILE_FUNCTION(AzCore); - if (!assetHandler) - { - assetHandler = GetHandler(asset.GetType()); - } + m_ownedAssetContainers.erase(assetContainer); + } - if (assetHandler) + void AssetManager::OnAssetContainerReady(AssetContainer* assetContainer) + { + AssetBus::QueueFunction([this, assetContainer, asset = assetContainer->GetRootAsset()]() + { + NotifyAssetContainerReady(asset); + ReleaseOwnedAssetContainer(assetContainer); + }); + } + + void AssetManager::OnAssetContainerCanceled(AssetContainer* assetContainer) + { + AssetBus::QueueFunction([this, assetContainer]() + { + ReleaseOwnedAssetContainer(assetContainer); + }); + } + + //========================================================================= + // OnAssetReloaded + //========================================================================= + void AssetManager::OnAssetReloaded(const Asset& asset) + { + // Queue broadcast message for delivery on game thread. + AssetBus::QueueFunction(&AssetManager::NotifyAssetReloaded, this, Asset(asset)); + } + + //========================================================================= + // OnAssetReloadError + //========================================================================= + void AssetManager::OnAssetReloadError(const Asset& asset) + { + // Queue broadcast message for delivery on game thread. + AssetBus::QueueFunction(&AssetManager::NotifyAssetReloadError, this, Asset(asset)); + } + + + //========================================================================= + // AssetHandler + // [04/03/2014] + //========================================================================= + AssetHandler::AssetHandler() + : m_nHandledTypes(0) + { + } + + //========================================================================= + // ~AssetHandler + // [04/03/2014] + //========================================================================= + AssetHandler::~AssetHandler() + { + if (m_nHandledTypes > 0) + { + AssetManager::Instance().UnregisterHandler(this); + } + + AZ_Error("AssetDatabase", m_nHandledTypes == 0, "Asset handler is being destroyed but there are still %d asset types being handled by it!", (int)m_nHandledTypes); + } + + //========================================================================= + // LoadAssetDataFromStream + //========================================================================= + AssetHandler::LoadResult AssetHandler::LoadAssetDataFromStream( + const Asset& asset, + AZStd::shared_ptr stream, + const AssetFilterCB& assetLoadFilterCB) + { + AZ_PROFILE_SCOPE(AzCore, "AssetHandler::LoadAssetData - %s", asset.GetHint().c_str()); + +#ifdef AZ_ENABLE_TRACING + auto start = AZStd::chrono::system_clock::now(); +#endif + + LoadResult result = LoadAssetData(asset, stream, assetLoadFilterCB); + +#ifdef AZ_ENABLE_TRACING + auto loadMs = AZStd::chrono::duration_cast( + AZStd::chrono::system_clock::now() - start); + AZ_Warning("AssetDatabase", (!cl_assetLoadWarningEnable) || + loadMs <= AZStd::chrono::milliseconds(cl_assetLoadWarningMsThreshold), + "Load time threshold exceeded: LoadAssetData call for %s took %" PRId64 " ms", + asset.GetHint().c_str(), loadMs.count()); +#endif + + return result; + } + + //========================================================================= + // InitAsset + // [04/03/2014] + //========================================================================= + void AssetHandler::InitAsset(const Asset& asset, bool loadStageSucceeded, bool isReload) + { + if (loadStageSucceeded) + { + if (isReload) { - // Queue the result for dispatch to main thread. - assetHandler->InitAsset(asset, loadSucceeded, isReload); + AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetReloaded, asset); } else { - AZ_Warning("AssetManager", false, "Couldn't find handler for asset %s (%s)", asset.GetId().ToString().c_str(), asset.GetHint().c_str()); + AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetReady, asset); } + } + else + { + if (!isReload) + { + AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetError, asset); + } + else + { + AssetManagerBus::Broadcast(&AssetManagerBus::Events::OnAssetReloadError, asset); + } + } + } - // Notify any dependent jobs. - BlockingAssetLoadBus::Event(asset.GetId(), &BlockingAssetLoadBus::Events::OnLoadComplete); + void AssetManager::ValidateAndPostLoad(AZ::Data::Asset& asset, bool loadSucceeded, + bool isReload, AZ::Data::AssetHandler* assetHandler) + { + { + // We may need to revalidate that this asset hasn't already passed through postLoad + AZStd::scoped_lock assetLock(m_assetMutex); + if (asset->IsReady() || asset->m_status == AssetData::AssetStatus::LoadedPreReady) + { + return; + } + asset->m_status = AssetData::AssetStatus::LoadedPreReady; + UpdateDebugStatus(asset); + } + PostLoad(asset, loadSucceeded, isReload, assetHandler); + } - UnregisterAssetLoading(asset); + void AssetManager::PostLoad(AZ::Data::Asset& asset, bool loadSucceeded, + bool isReload, AZ::Data::AssetHandler* assetHandler) + { + AZ_PROFILE_FUNCTION(AzCore); + if (!assetHandler) + { + assetHandler = GetHandler(asset.GetType()); } - AZStd::shared_ptr AssetManager::GetAssetContainer(Asset asset, const AssetLoadParameters& loadParams) + if (assetHandler) { - // If we're doing a custom load through a filter just hand back a one off container - if (loadParams.m_assetLoadFilterCB) - { - return CreateAssetContainer(asset, loadParams); - } + // Queue the result for dispatch to main thread. + assetHandler->InitAsset(asset, loadSucceeded, isReload); + } + else + { + AZ_Warning("AssetManager", false, "Couldn't find handler for asset %s (%s)", asset.GetId().ToString().c_str(), asset.GetHint().c_str()); + } - AZStd::scoped_lock containerLock(m_assetContainerMutex); - AssetContainerKey containerKey{ asset.GetId(), loadParams }; + // Notify any dependent jobs. + BlockingAssetLoadBus::Event(asset.GetId(), &BlockingAssetLoadBus::Events::OnLoadComplete); - auto curIter = m_assetContainers.find(containerKey); - if (curIter != m_assetContainers.end()) + UnregisterAssetLoading(asset); + } + + AZStd::shared_ptr AssetManager::GetAssetContainer(Asset asset, const AssetLoadParameters& loadParams) + { + // If we're doing a custom load through a filter just hand back a one off container + if (loadParams.m_assetLoadFilterCB) + { + return CreateAssetContainer(asset, loadParams); + } + + AZStd::scoped_lock containerLock(m_assetContainerMutex); + AssetContainerKey containerKey{ asset.GetId(), loadParams }; + + auto curIter = m_assetContainers.find(containerKey); + if (curIter != m_assetContainers.end()) + { + auto newRef = curIter->second.lock(); + if (newRef && newRef->IsValid()) { - auto newRef = curIter->second.lock(); - if (newRef && newRef->IsValid()) - { - return newRef; - } - auto newContainer = CreateAssetContainer(asset, loadParams); - curIter->second = newContainer; - return newContainer; + return newRef; } auto newContainer = CreateAssetContainer(asset, loadParams); - - m_assetContainers.insert({ containerKey, newContainer }); - + curIter->second = newContainer; return newContainer; } + auto newContainer = CreateAssetContainer(asset, loadParams); - AZStd::shared_ptr AssetManager::CreateAssetContainer(Asset asset, const AssetLoadParameters& loadParams) const - { - return AZStd::shared_ptr( aznew AssetContainer(AZStd::move(asset), loadParams)); - } - } // namespace Data -} // namespace AZ + m_assetContainers.insert({ containerKey, newContainer }); + + return newContainer; + } + + AZStd::shared_ptr AssetManager::CreateAssetContainer(Asset asset, const AssetLoadParameters& loadParams) const + { + return AZStd::shared_ptr( aznew AssetContainer(AZStd::move(asset), loadParams)); + } +} // namespace AZ::Data size_t AZStd::hash::operator()(const AZ::Data::AssetContainerKey& obj) const { diff --git a/Code/Framework/AzCore/AzCore/Asset/AssetManagerBus.h b/Code/Framework/AzCore/AzCore/Asset/AssetManagerBus.h index f76ea19589..44707645d0 100644 --- a/Code/Framework/AzCore/AzCore/Asset/AssetManagerBus.h +++ b/Code/Framework/AzCore/AzCore/Asset/AssetManagerBus.h @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -129,7 +130,8 @@ namespace AZ /// Remove a catalog from our delta list and rebuild the catalog from remaining items virtual bool RemoveDeltaCatalog(AZStd::shared_ptr /*deltaCatalog*/) { return true; } /// Creates a manifest with the given DeltaCatalog name - virtual bool CreateBundleManifest(const AZStd::string& /*deltaCatalogPath*/, const AZStd::vector& /*dependentBundleNames*/, const AZStd::string& /*fileDirectory*/, int /*bundleVersion*/, const AZStd::vector& /*levelDirs*/) { return false; } + virtual bool CreateBundleManifest(const AZStd::string& /*deltaCatalogPath*/, const AZStd::vector& /*dependentBundleNames*/, + const AZStd::string& /*fileDirectory*/, int /*bundleVersion*/, const AZStd::vector& /*levelDirs*/) { return false; } /// Creates an instance of a registry containing info for just the specified files, and writes it out to a file at the specified path virtual bool CreateDeltaCatalog(const AZStd::vector& /*files*/, const AZStd::string& /*filePath*/) { return false; } diff --git a/Code/Framework/AzCore/AzCore/AzCoreModule.cpp b/Code/Framework/AzCore/AzCore/AzCoreModule.cpp index 67123ed826..e7e87559b0 100644 --- a/Code/Framework/AzCore/AzCore/AzCoreModule.cpp +++ b/Code/Framework/AzCore/AzCore/AzCoreModule.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -40,7 +39,6 @@ namespace AZ SliceComponent::CreateDescriptor(), SliceSystemComponent::CreateDescriptor(), SliceMetadataInfoComponent::CreateDescriptor(), - TimeSystemComponent::CreateDescriptor(), LoggerSystemComponent::CreateDescriptor(), EventSchedulerSystemComponent::CreateDescriptor(), TaskGraphSystemComponent::CreateDescriptor(), @@ -59,7 +57,6 @@ namespace AZ { return AZ::ComponentTypeList { - azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), azrtti_typeid(), diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp index bd225cf634..693b2f1648 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.cpp @@ -45,12 +45,9 @@ #include #include +#include #include -#include -#include -#include -#include #include #include @@ -72,6 +69,7 @@ #include #include +#include static void PrintEntityName(const AZ::ConsoleCommandContainer& arguments) { @@ -154,7 +152,6 @@ namespace AZ m_reservedDebug = 0; m_recordingMode = Debug::AllocationRecords::RECORD_STACK_IF_NO_FILE_LINE; m_stackRecordLevels = 5; - m_enableDrilling = false; m_useOverrunDetection = false; m_useMalloc = false; } @@ -326,7 +323,6 @@ namespace AZ ->Field("blockSize", &Descriptor::m_memoryBlocksByteSize) ->Field("reservedOS", &Descriptor::m_reservedOS) ->Field("reservedDebug", &Descriptor::m_reservedDebug) - ->Field("enableDrilling", &Descriptor::m_enableDrilling) ->Field("useOverrunDetection", &Descriptor::m_useOverrunDetection) ->Field("useMalloc", &Descriptor::m_useMalloc) ->Field("allocatorRemappings", &Descriptor::m_allocatorRemappings) @@ -365,7 +361,6 @@ namespace AZ ->Attribute(Edit::Attributes::Step, &Descriptor::m_pageSize) ->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_reservedOS, "OS reserved memory", "System memory reserved for OS (used only when 'Allocate all memory at startup' is true)") ->DataElement(Edit::UIHandlers::SpinBox, &Descriptor::m_reservedDebug, "Memory reserved for debugger", "System memory reserved for Debug allocator, like memory tracking (used only when 'Allocate all memory at startup' is true)") - ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_enableDrilling, "Enable Driller", "Enable Drilling support for the application (ignored in Release builds)") ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_useOverrunDetection, "Use Overrun Detection", "Use the overrun detection memory manager (only available on some platforms, ignored in Release builds)") ->DataElement(Edit::UIHandlers::CheckBox, &Descriptor::m_useMalloc, "Use Malloc", "Use malloc for memory allocations (for memory debugging only, ignored in Release builds)") ; @@ -416,6 +411,7 @@ namespace AZ ComponentApplication::ComponentApplication(int argC, char** argV) : m_eventLogger{} + , m_timeSystem(AZStd::make_unique()) { if (Interface::Get() == nullptr) { @@ -483,9 +479,11 @@ namespace AZ // Merge Command Line arguments constexpr bool executeRegDumpCommands = false; - SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands); +#if defined(AZ_DEBUG_BUILD) || defined(AZ_PROFILE_BUILD) + // Skip over merging the User Registry in non-debug and profile configurations SettingsRegistryMergeUtils::MergeSettingsToRegistry_O3deUserRegistry(*m_settingsRegistry, AZ_TRAIT_OS_PLATFORM_CODENAME, {}); +#endif SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_settingsRegistry, m_commandLine, executeRegDumpCommands); SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*m_settingsRegistry); @@ -574,7 +572,6 @@ namespace AZ DestroyAllocator(); } - void ReportBadEngineRoot() { AZStd::string errorMessage = {"Unable to determine a valid path to the engine.\n" @@ -677,7 +674,6 @@ namespace AZ ComponentApplicationBus::Handler::BusConnect(); - m_currentTime = AZStd::chrono::system_clock::now(); TickRequestBus::Handler::BusConnect(); #if defined(AZ_ENABLE_DEBUG_TOOLS) @@ -1413,31 +1409,23 @@ namespace AZ #endif } - void ComponentApplication::Tick(float deltaOverride /*= -1.f*/) + void ComponentApplication::Tick() { + AZ_PROFILE_SCOPE(System, "Component application simulation tick"); + { - AZ_PROFILE_SCOPE(System, "Component application simulation tick"); - - AZStd::chrono::system_clock::time_point now = AZStd::chrono::system_clock::now(); - - m_deltaTime = 0.0f; - - if (now >= m_currentTime) - { - AZStd::chrono::duration delta = now - m_currentTime; - m_deltaTime = deltaOverride >= 0.f ? deltaOverride : delta.count(); - } - - { - AZ_PROFILE_SCOPE(AzCore, "ComponentApplication::Tick:ExecuteQueuedEvents"); - TickBus::ExecuteQueuedEvents(); - } - m_currentTime = now; - { - AZ_PROFILE_SCOPE(AzCore, "ComponentApplication::Tick:OnTick"); - EBUS_EVENT(TickBus, OnTick, m_deltaTime, ScriptTimePoint(now)); - } + AZ_PROFILE_SCOPE(AzCore, "ComponentApplication::Tick:ExecuteQueuedEvents"); + TickBus::ExecuteQueuedEvents(); } + + { + AZ_PROFILE_SCOPE(AzCore, "ComponentApplication::Tick:OnTick"); + const AZ::TimeUs deltaTimeUs = m_timeSystem->AdvanceTickDeltaTimes(); + const float deltaTimeSeconds = AZ::TimeUsToSeconds(deltaTimeUs); + AZ::TickBus::Broadcast(&TickEvents::OnTick, deltaTimeSeconds, GetTimeAtCurrentTick()); + } + + m_timeSystem->ApplyTickRateLimiterIfNeeded(); } void ComponentApplication::TickSystem() @@ -1519,13 +1507,10 @@ namespace AZ appType.m_maskValue = AZ::ApplicationTypeQuery::Masks::Invalid; } - //========================================================================= - // GetFrameTime - // [1/22/2016] - //========================================================================= float ComponentApplication::GetTickDeltaTime() { - return m_deltaTime; + const AZ::TimeUs gameTickTime = m_timeSystem->GetSimulationTickDeltaTimeUs(); + return AZ::TimeUsToSeconds(gameTickTime); } //========================================================================= @@ -1534,7 +1519,8 @@ namespace AZ //========================================================================= ScriptTimePoint ComponentApplication::GetTimeAtCurrentTick() { - return ScriptTimePoint(m_currentTime); + const AZ::TimeUs lastGameTickTime = m_timeSystem->GetLastSimulationTickTime(); + return ScriptTimePoint(AZ::TimeUsToChrono(lastGameTickTime)); } //========================================================================= @@ -1557,7 +1543,7 @@ namespace AZ // reflect name dictionary. Name::Reflect(context); // reflect path - IO::PathReflection::Reflect(context); + IO::PathReflect(context); // reflect the SettingsRegistryInterface, SettignsRegistryImpl and the global Settings Registry // instance (AZ::SettingsRegistry::Get()) into the Behavior Context diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h index 4e551b6c47..d53b8e1a4e 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplication.h @@ -30,16 +30,17 @@ #include #include + namespace AZ { class BehaviorContext; class IConsole; class Module; class ModuleManager; + class TimeSystem; } namespace AZ::Debug { - class DrillerManager; class LocalFileEventLogger; } @@ -141,7 +142,6 @@ namespace AZ AZ::u64 m_reservedDebug; //!< Reserved memory for Debugging (allocation,etc.). Used only when m_grabAllMemory is set to true. (default: 0) Debug::AllocationRecords::Mode m_recordingMode; //!< When to record stack traces (default: AZ::Debug::AllocationRecords::RECORD_STACK_IF_NO_FILE_LINE) AZ::u64 m_stackRecordLevels; //!< If stack recording is enabled, how many stack levels to record. (default: 5) - bool m_enableDrilling; //!< True to enabled drilling support for the application. RegisterDrillers will be called. Ignored in release. (default: true) bool m_useOverrunDetection; //!< True to use the overrun detection memory management scheme. Only available on some platforms; greatly increases memory consumption. bool m_useMalloc; //!< True to use malloc instead of the internal memory manager. Intended for debugging purposes only. @@ -237,7 +237,7 @@ namespace AZ /** * Ticks all components using the \ref AZ::TickBus during simulation time. May not tick if the application is not active (i.e. not in focus) */ - virtual void Tick(float deltaOverride = -1.f); + virtual void Tick(); /** * Ticks all using the \ref AZ::SystemTickBus at all times. Should always tick even if the application is not active. @@ -359,8 +359,6 @@ namespace AZ } } - AZStd::chrono::system_clock::time_point m_currentTime{ AZStd::chrono::system_clock::time_point::max() }; - float m_deltaTime{ 0.0f }; AZStd::unique_ptr m_moduleManager; AZStd::unique_ptr m_settingsRegistry; EntityAddedEvent m_entityAddedEvent; @@ -381,6 +379,8 @@ namespace AZ AZ::SettingsRegistryInterface::NotifyEventHandler m_projectNameChangedHandler; AZ::SettingsRegistryInterface::NotifyEventHandler m_commandLineUpdatedHandler; + AZStd::unique_ptr m_timeSystem; + // ConsoleFunctorHandle is responsible for unregistering the Settings Registry Console // from the m_console member when it goes out of scope AZ::SettingsRegistryConsoleUtils::ConsoleFunctorHandle m_settingsRegistryConsoleFunctors; diff --git a/Code/Framework/AzCore/AzCore/Component/ComponentApplicationBus.h b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationBus.h index feefa95973..10f35d6e00 100644 --- a/Code/Framework/AzCore/AzCore/Component/ComponentApplicationBus.h +++ b/Code/Framework/AzCore/AzCore/Component/ComponentApplicationBus.h @@ -37,11 +37,6 @@ namespace AZ class ComponentFactoryInterface; } - namespace Debug - { - class DrillerManager; - } - struct ApplicationTypeQuery { bool IsEditor() const; diff --git a/Code/Framework/AzCore/AzCore/Component/Entity.cpp b/Code/Framework/AzCore/AzCore/Component/Entity.cpp index 0fe201d448..c5cda98f2c 100644 --- a/Code/Framework/AzCore/AzCore/Component/Entity.cpp +++ b/Code/Framework/AzCore/AzCore/Component/Entity.cpp @@ -811,12 +811,12 @@ namespace AZ if (behaviorContext) { behaviorContext->Class() - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly) ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) ->Attribute(AZ::Script::Attributes::Module, "entity") ->Method("IsValid", &EntityId::IsValid) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly) ->Method("ToString", &EntityId::ToString) ->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString) ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) diff --git a/Code/Framework/AzCore/AzCore/Component/EntityUtils.cpp b/Code/Framework/AzCore/AzCore/Component/EntityUtils.cpp index fcc8cd6424..54e40c1fc0 100644 --- a/Code/Framework/AzCore/AzCore/Component/EntityUtils.cpp +++ b/Code/Framework/AzCore/AzCore/Component/EntityUtils.cpp @@ -10,292 +10,289 @@ #include #include -namespace AZ +namespace AZ::EntityUtils { - namespace EntityUtils + //========================================================================= + // Reflect + //========================================================================= + void Reflect(ReflectContext* context) { - //========================================================================= - // Reflect - //========================================================================= - void Reflect(ReflectContext* context) + if (auto serializeContext = azrtti_cast(context)) { - if (auto serializeContext = azrtti_cast(context)) + serializeContext->Class()-> + Version(1)-> + Field("Entities", &SerializableEntityContainer::m_entities); + } + } + + struct StackDataType + { + const SerializeContext::ClassData* m_classData; + const SerializeContext::ClassElement* m_elementData; + void* m_dataPtr; + bool m_isModifiedContainer; + }; + + //========================================================================= + // EnumerateEntityIds + //========================================================================= + void EnumerateEntityIds(const void* classPtr, const Uuid& classUuid, const EntityIdVisitor& visitor, SerializeContext* context) + { + AZ_PROFILE_FUNCTION(AzCore); + + if (!context) + { + context = GetApplicationSerializeContext(); + if (!context) { - serializeContext->Class()-> - Version(1)-> - Field("Entities", &SerializableEntityContainer::m_entities); + AZ_Error("Serialization", false, "No serialize context provided! Failed to get component application default serialize context! ComponentApp is not started or input serialize context should not be null!"); + return; } } + AZStd::vector parentStack; + parentStack.reserve(30); + auto beginCB = [ &](void* ptr, const SerializeContext::ClassData* classData, const SerializeContext::ClassElement* elementData) -> bool + { + (void)elementData; - struct StackDataType + if (classData->m_typeId == SerializeTypeInfo::GetUuid()) + { + // determine if this is entity ref or just entityId (please refer to the function documentation for more info) + bool isEntityId = false; + if (!parentStack.empty() && parentStack.back()->m_typeId == SerializeTypeInfo::GetUuid()) + { + // our parent in the entity (currently entity has only one EntityId member, but we can check the offset for future proof + AZ_Assert(elementData && strcmp(elementData->m_name, "Id") == 0, "class Entity, should have only ONE EntityId member, the actual entity id!"); + isEntityId = true; + } + + EntityId* entityIdPtr = (elementData->m_flags & SerializeContext::ClassElement::FLG_POINTER) ? + *reinterpret_cast(ptr) : reinterpret_cast(ptr); + visitor(*entityIdPtr, isEntityId, elementData); + } + + parentStack.push_back(classData); + return true; + }; + + auto endCB = [ &]() -> bool + { + parentStack.pop_back(); + return true; + }; + + SerializeContext::EnumerateInstanceCallContext callContext( + beginCB, + endCB, + context, + SerializeContext::ENUM_ACCESS_FOR_READ, + nullptr + ); + + context->EnumerateInstanceConst( + &callContext, + classPtr, + classUuid, + nullptr, + nullptr + ); + } + + //========================================================================= + // GetApplicationSerializeContext + //========================================================================= + SerializeContext* GetApplicationSerializeContext() + { + SerializeContext* context = nullptr; + EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext); + return context; + } + + //========================================================================= + // FindFirstDerivedComponent + //========================================================================= + Component* FindFirstDerivedComponent(const Entity* entity, const Uuid& typeId) + { + for (AZ::Component* component : entity->GetComponents()) { - const SerializeContext::ClassData* m_classData; - const SerializeContext::ClassElement* m_elementData; - void* m_dataPtr; - bool m_isModifiedContainer; + if (azrtti_istypeof(typeId, component)) + { + return component; + } + } + return nullptr; + } + + Component* FindFirstDerivedComponent(EntityId entityId, const Uuid& typeId) + { + Entity* entity{}; + ComponentApplicationBus::BroadcastResult(entity, &ComponentApplicationRequests::FindEntity, entityId); + return entity ? FindFirstDerivedComponent(entity, typeId) : nullptr; + } + + //========================================================================= + // FindDerivedComponents + //========================================================================= + Entity::ComponentArrayType FindDerivedComponents(const Entity* entity, const Uuid& typeId) + { + Entity::ComponentArrayType result; + for (AZ::Component* component : entity->GetComponents()) + { + if (azrtti_istypeof(typeId, component)) + { + result.push_back(component); + } + } + return result; + } + + Entity::ComponentArrayType FindDerivedComponents(EntityId entityId, const Uuid& typeId) + { + Entity* entity{}; + ComponentApplicationBus::BroadcastResult(entity, &ComponentApplicationRequests::FindEntity, entityId); + return entity ? FindDerivedComponents(entity, typeId) : Entity::ComponentArrayType(); + } + + bool EnumerateBaseRecursive(SerializeContext* context, const EnumerateBaseRecursiveVisitor& baseClassVisitor, const TypeId& typeToExamine) + { + AZ_Assert(context, "CheckDeclaresSerializeBaseClass called with no serialize context."); + if (!context) + { + return false; + } + + AZStd::fixed_vector knownBaseClasses = { typeToExamine }; // avoid allocating heap here if possible. 64 types are 64*sizeof(Uuid) which is only 1k. + bool foundBaseClass = false; + auto enumerateBaseVisitor = [&baseClassVisitor, &knownBaseClasses](const AZ::SerializeContext::ClassData* classData, const TypeId& examineTypeId) + { + if (!classData) + { + return false; + } + + if (AZStd::find(knownBaseClasses.begin(), knownBaseClasses.end(), classData->m_typeId) == knownBaseClasses.end()) + { + if (knownBaseClasses.size() == 64) + { + // this should be pretty unlikely since a single class would have to have many other classes in its heirarchy + // and it'd all have to be basically in one layer, as we are popping as we explore. + AZ_WarningOnce("EntityUtils", false, "While trying to find a base class, all available slots were consumed. consider increasing the size of knownBaseClasses.\n"); + // we cannot continue any further, assume we did not find it. + return false; + } + knownBaseClasses.push_back(classData->m_typeId); + } + + return baseClassVisitor(classData, examineTypeId); }; - //========================================================================= - // EnumerateEntityIds - //========================================================================= - void EnumerateEntityIds(const void* classPtr, const Uuid& classUuid, const EntityIdVisitor& visitor, SerializeContext* context) + while (!knownBaseClasses.empty() && !foundBaseClass) { - AZ_PROFILE_FUNCTION(AzCore); + TypeId toExamine = knownBaseClasses.back(); + knownBaseClasses.pop_back(); - if (!context) - { - context = GetApplicationSerializeContext(); - if (!context) - { - AZ_Error("Serialization", false, "No serialize context provided! Failed to get component application default serialize context! ComponentApp is not started or input serialize context should not be null!"); - return; - } - } - AZStd::vector parentStack; - parentStack.reserve(30); - auto beginCB = [ &](void* ptr, const SerializeContext::ClassData* classData, const SerializeContext::ClassElement* elementData) -> bool - { - (void)elementData; - - if (classData->m_typeId == SerializeTypeInfo::GetUuid()) - { - // determine if this is entity ref or just entityId (please refer to the function documentation for more info) - bool isEntityId = false; - if (!parentStack.empty() && parentStack.back()->m_typeId == SerializeTypeInfo::GetUuid()) - { - // our parent in the entity (currently entity has only one EntityId member, but we can check the offset for future proof - AZ_Assert(elementData && strcmp(elementData->m_name, "Id") == 0, "class Entity, should have only ONE EntityId member, the actual entity id!"); - isEntityId = true; - } - - EntityId* entityIdPtr = (elementData->m_flags & SerializeContext::ClassElement::FLG_POINTER) ? - *reinterpret_cast(ptr) : reinterpret_cast(ptr); - visitor(*entityIdPtr, isEntityId, elementData); - } - - parentStack.push_back(classData); - return true; - }; - - auto endCB = [ &]() -> bool - { - parentStack.pop_back(); - return true; - }; - - SerializeContext::EnumerateInstanceCallContext callContext( - beginCB, - endCB, - context, - SerializeContext::ENUM_ACCESS_FOR_READ, - nullptr - ); - - context->EnumerateInstanceConst( - &callContext, - classPtr, - classUuid, - nullptr, - nullptr - ); + context->EnumerateBase(enumerateBaseVisitor, toExamine); } - //========================================================================= - // GetApplicationSerializeContext - //========================================================================= - SerializeContext* GetApplicationSerializeContext() - { - SerializeContext* context = nullptr; - EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext); - return context; - } + return foundBaseClass; + } - //========================================================================= - // FindFirstDerivedComponent - //========================================================================= - Component* FindFirstDerivedComponent(const Entity* entity, const Uuid& typeId) + bool CheckIfClassIsDeprecated(SerializeContext* context, const TypeId& typeToExamine) + { + bool isDeprecated = false; + auto classVisitorFn = [&isDeprecated](const AZ::SerializeContext::ClassData* classData, const TypeId& /*rttiBase*/) { - for (AZ::Component* component : entity->GetComponents()) - { - if (azrtti_istypeof(typeId, component)) - { - return component; - } - } - return nullptr; - } - - Component* FindFirstDerivedComponent(EntityId entityId, const Uuid& typeId) - { - Entity* entity{}; - ComponentApplicationBus::BroadcastResult(entity, &ComponentApplicationRequests::FindEntity, entityId); - return entity ? FindFirstDerivedComponent(entity, typeId) : nullptr; - } - - //========================================================================= - // FindDerivedComponents - //========================================================================= - Entity::ComponentArrayType FindDerivedComponents(const Entity* entity, const Uuid& typeId) - { - Entity::ComponentArrayType result; - for (AZ::Component* component : entity->GetComponents()) - { - if (azrtti_istypeof(typeId, component)) - { - result.push_back(component); - } - } - return result; - } - - Entity::ComponentArrayType FindDerivedComponents(EntityId entityId, const Uuid& typeId) - { - Entity* entity{}; - ComponentApplicationBus::BroadcastResult(entity, &ComponentApplicationRequests::FindEntity, entityId); - return entity ? FindDerivedComponents(entity, typeId) : Entity::ComponentArrayType(); - } - - bool EnumerateBaseRecursive(SerializeContext* context, const EnumerateBaseRecursiveVisitor& baseClassVisitor, const TypeId& typeToExamine) - { - AZ_Assert(context, "CheckDeclaresSerializeBaseClass called with no serialize context."); - if (!context) + // Stop iterating once we stop receiving SerializeContext::ClassData*. + if (!classData) { return false; } - AZStd::fixed_vector knownBaseClasses = { typeToExamine }; // avoid allocating heap here if possible. 64 types are 64*sizeof(Uuid) which is only 1k. - bool foundBaseClass = false; - auto enumerateBaseVisitor = [&baseClassVisitor, &knownBaseClasses](const AZ::SerializeContext::ClassData* classData, const TypeId& examineTypeId) - { - if (!classData) - { - return false; - } - - if (AZStd::find(knownBaseClasses.begin(), knownBaseClasses.end(), classData->m_typeId) == knownBaseClasses.end()) - { - if (knownBaseClasses.size() == 64) - { - // this should be pretty unlikely since a single class would have to have many other classes in its heirarchy - // and it'd all have to be basically in one layer, as we are popping as we explore. - AZ_WarningOnce("EntityUtils", false, "While trying to find a base class, all available slots were consumed. consider increasing the size of knownBaseClasses.\n"); - // we cannot continue any further, assume we did not find it. - return false; - } - knownBaseClasses.push_back(classData->m_typeId); - } - - return baseClassVisitor(classData, examineTypeId); - }; - - while (!knownBaseClasses.empty() && !foundBaseClass) - { - TypeId toExamine = knownBaseClasses.back(); - knownBaseClasses.pop_back(); - - context->EnumerateBase(enumerateBaseVisitor, toExamine); - } - - return foundBaseClass; - } - - bool CheckIfClassIsDeprecated(SerializeContext* context, const TypeId& typeToExamine) - { - bool isDeprecated = false; - auto classVisitorFn = [&isDeprecated](const AZ::SerializeContext::ClassData* classData, const TypeId& /*rttiBase*/) - { - // Stop iterating once we stop receiving SerializeContext::ClassData*. - if (!classData) - { - return false; - } - - // Stop iterating if we've found that the class is deprecated - if (classData->IsDeprecated()) - { - isDeprecated = true; - return false; - } - - return true; // keep iterating - }; - - // Check if the type is deprecated - const AZ::SerializeContext::ClassData* classData = context->FindClassData(typeToExamine); + // Stop iterating if we've found that the class is deprecated if (classData->IsDeprecated()) { - return true; - } - - // Check if any of its bases are deprecated - EnumerateBaseRecursive(context, classVisitorFn, typeToExamine); - - return isDeprecated; - } - - bool CheckDeclaresSerializeBaseClass(SerializeContext* context, const TypeId& typeToFind, const TypeId& typeToExamine) - { - AZ_Assert(context, "CheckDeclaresSerializeBaseClass called with no serialize context."); - if (!context) - { + isDeprecated = true; return false; } - bool foundBaseClass = false; - auto baseClassVisitorFn = [&typeToFind, &foundBaseClass](const AZ::SerializeContext::ClassData* reflectedBase, const TypeId& /*rttiBase*/) - { - if (!reflectedBase) - { - foundBaseClass = false; - return false; // stop iterating - } + return true; // keep iterating + }; - foundBaseClass = (reflectedBase->m_typeId == typeToFind); - if (foundBaseClass) - { - return false; // we have a base, stop iterating - } - - return true; // keep iterating - }; - - EnumerateBaseRecursive(context, baseClassVisitorFn, typeToExamine); - - return foundBaseClass; - } - - bool RemoveDuplicateServicesOfAndAfterIterator( - const ComponentDescriptor::DependencyArrayType::iterator& iterator, - ComponentDescriptor::DependencyArrayType& providedServiceArray, - const Entity* entity) + // Check if the type is deprecated + const AZ::SerializeContext::ClassData* classData = context->FindClassData(typeToExamine); + if (classData->IsDeprecated()) { - // Build types that strip out AZ_Warnings will complain that entity is unused without this. - (void)entity; - if (iterator == providedServiceArray.end()) - { - return false; - } - - bool duplicateFound = false; - - for (ComponentDescriptor::DependencyArrayType::iterator duplicateCheckIter = AZStd::next(iterator); - duplicateCheckIter != providedServiceArray.end();) - { - if (*iterator == *duplicateCheckIter) - { - AZ_Warning("Entity", false, "Duplicate service %d found on entity %s [%s]", - *duplicateCheckIter, - entity ? entity->GetName().c_str() : "Entity not provided", - entity ? entity->GetId().ToString().c_str() : ""); - duplicateCheckIter = providedServiceArray.erase(duplicateCheckIter); - duplicateFound = true; - } - else - { - ++duplicateCheckIter; - } - } - return duplicateFound; + return true; } - } // namespace EntityUtils -} // namespace AZ + + // Check if any of its bases are deprecated + EnumerateBaseRecursive(context, classVisitorFn, typeToExamine); + + return isDeprecated; + } + + bool CheckDeclaresSerializeBaseClass(SerializeContext* context, const TypeId& typeToFind, const TypeId& typeToExamine) + { + AZ_Assert(context, "CheckDeclaresSerializeBaseClass called with no serialize context."); + if (!context) + { + return false; + } + + bool foundBaseClass = false; + auto baseClassVisitorFn = [&typeToFind, &foundBaseClass](const AZ::SerializeContext::ClassData* reflectedBase, const TypeId& /*rttiBase*/) + { + if (!reflectedBase) + { + foundBaseClass = false; + return false; // stop iterating + } + + foundBaseClass = (reflectedBase->m_typeId == typeToFind); + if (foundBaseClass) + { + return false; // we have a base, stop iterating + } + + return true; // keep iterating + }; + + EnumerateBaseRecursive(context, baseClassVisitorFn, typeToExamine); + + return foundBaseClass; + } + + bool RemoveDuplicateServicesOfAndAfterIterator( + const ComponentDescriptor::DependencyArrayType::iterator& iterator, + ComponentDescriptor::DependencyArrayType& providedServiceArray, + const Entity* entity) + { + // Build types that strip out AZ_Warnings will complain that entity is unused without this. + (void)entity; + if (iterator == providedServiceArray.end()) + { + return false; + } + + bool duplicateFound = false; + + for (ComponentDescriptor::DependencyArrayType::iterator duplicateCheckIter = AZStd::next(iterator); + duplicateCheckIter != providedServiceArray.end();) + { + if (*iterator == *duplicateCheckIter) + { + AZ_Warning("Entity", false, "Duplicate service %d found on entity %s [%s]", + *duplicateCheckIter, + entity ? entity->GetName().c_str() : "Entity not provided", + entity ? entity->GetId().ToString().c_str() : ""); + duplicateCheckIter = providedServiceArray.erase(duplicateCheckIter); + duplicateFound = true; + } + else + { + ++duplicateCheckIter; + } + } + return duplicateFound; + } +} // namespace AZ::EntityUtils diff --git a/Code/Framework/AzCore/AzCore/Component/TickBus.h b/Code/Framework/AzCore/AzCore/Component/TickBus.h index e65efb93f2..1b588f41cc 100644 --- a/Code/Framework/AzCore/AzCore/Component/TickBus.h +++ b/Code/Framework/AzCore/AzCore/Component/TickBus.h @@ -16,7 +16,6 @@ #define AZCORE_COMPONENT_TICK_BUS_H #include -#include #include #include // For TickBus thread events. #include @@ -112,10 +111,6 @@ namespace AZ AZ_FORCE_INLINE bool operator()(TickEvents* left, TickEvents* right) const { return left->GetTickOrder() < right->GetTickOrder(); } }; - /** - * Enable tick bus to work with the AssetTracking - */ - using EventProcessingPolicy = Debug::AssetTrackingEventProcessingPolicy<>; ////////////////////////////////////////////////////////////////////////// /** @@ -217,10 +212,6 @@ namespace AZ */ typedef AZStd::mutex EventQueueMutexType; - /** - * Enable tick bus to work with the AssetTracking - */ - using EventProcessingPolicy = Debug::AssetTrackingEventProcessingPolicy<>; ////////////////////////////////////////////////////////////////////////// /** diff --git a/Code/Framework/AzCore/AzCore/Component/TransformBus.h b/Code/Framework/AzCore/AzCore/Component/TransformBus.h index 1af77da51b..f8e30147da 100644 --- a/Code/Framework/AzCore/AzCore/Component/TransformBus.h +++ b/Code/Framework/AzCore/AzCore/Component/TransformBus.h @@ -168,6 +168,11 @@ namespace AZ //! Rotation modifiers //! @{ + //! Set the world rotation matrix using the composition of rotations around + //! the principle axes in the order of z-axis first and y-axis and then x-axis. + //! @param eulerRadianAngles A Vector3 denoting radian angles of the rotations around each principle axis. + virtual void SetWorldRotation([[maybe_unused]] const AZ::Vector3& eulerAnglesRadian) {} + //! Sets the entity's rotation in the world in quaternion notation. //! The origin of the axes is the entity's position in world space. //! @param quaternion A quaternion that represents the rotation to use for the entity. diff --git a/Code/Framework/AzCore/AzCore/Compression/zstd_compression.cpp b/Code/Framework/AzCore/AzCore/Compression/zstd_compression.cpp index 9db59bc781..ecab62d123 100644 --- a/Code/Framework/AzCore/AzCore/Compression/zstd_compression.cpp +++ b/Code/Framework/AzCore/AzCore/Compression/zstd_compression.cpp @@ -57,6 +57,7 @@ void ZStd::StartCompressor(unsigned int compressionLevel) ZSTD_customMem customAlloc; customAlloc.customAlloc = reinterpret_cast(&AllocateMem); customAlloc.customFree = &FreeMem; + customAlloc.opaque = nullptr; AZ_UNUSED(compressionLevel); m_streamCompression = (ZSTD_createCStream_advanced(customAlloc)); diff --git a/Code/Framework/AzCore/AzCore/Console/ConsoleDataWrapper.inl b/Code/Framework/AzCore/AzCore/Console/ConsoleDataWrapper.inl index 46774f23b2..b3a59d287d 100644 --- a/Code/Framework/AzCore/AzCore/Console/ConsoleDataWrapper.inl +++ b/Code/Framework/AzCore/AzCore/Console/ConsoleDataWrapper.inl @@ -32,7 +32,16 @@ namespace AZ template inline void ConsoleDataWrapper::operator =(const BASE_TYPE& rhs) { + const BASE_TYPE currentValue = this->m_value; + // Do the value assignment outside new value check. + // Client code can supply a type for m_value that overrides the operator= function and trigger side effects + // in the operator= function body. Doing the assignment outside the value change check avoids those side + // effects not being triggered because AzCore believes the value wouldn't change. this->m_value = rhs; + if (currentValue != rhs) + { + InvokeCallback(); + } } template diff --git a/Code/Framework/AzCore/AzCore/DOM/Backends/JSON/JsonBackend.h b/Code/Framework/AzCore/AzCore/DOM/Backends/JSON/JsonBackend.h new file mode 100644 index 0000000000..1023796f61 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/DOM/Backends/JSON/JsonBackend.h @@ -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 +#include +#include + +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({ buffer, size }, lifetime, visitor); + } + + Visitor::Result ReadFromBufferInPlace(char* buffer, [[maybe_unused]] AZStd::optional size, Visitor& visitor) override + { + return Json::VisitSerializedJsonInPlace(buffer, visitor); + } + + Visitor::Result WriteToBuffer(AZStd::string& buffer, WriteCallback callback) + { + AZ::IO::ByteContainerStream stream{ &buffer }; + AZStd::unique_ptr visitor = Json::CreateJsonStreamWriter(stream, WriteFormat); + return callback(*visitor); + } + }; +} // namespace AZ::Dom diff --git a/Code/Framework/AzCore/AzCore/DOM/Backends/JSON/JsonSerializationUtils.cpp b/Code/Framework/AzCore/AzCore/DOM/Backends/JSON/JsonSerializationUtils.cpp new file mode 100644 index 0000000000..17f9bfea54 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/DOM/Backends/JSON/JsonSerializationUtils.cpp @@ -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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(value.length()), m_allocator); + } + else + { + CurrentValue().SetString(value.data(), aznumeric_cast(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(key.size())); + } + else + { + m_entryStack.front().m_key.SetString(key.data(), aznumeric_cast(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 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(value.size()), shouldCopy)); + } + + Result StartObject() override + { + return CheckWrite(m_writer.StartObject()); + } + + Result EndObject(AZ::u64 attributeCount) override + { + return CheckWrite(m_writer.EndObject(aznumeric_cast(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(key.size()), shouldCopy)); + } + + Result StartArray() override + { + return CheckWrite(m_writer.StartArray()); + } + + Result EndArray(AZ::u64 elementCount) override + { + return CheckWrite(m_writer.EndArray(aznumeric_cast(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(i))); + } + + bool RapidJsonReadHandler::Uint(unsigned i) + { + return CheckResult(m_visitor->Uint64(aznumeric_cast(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 CreateJsonStreamWriter(AZ::IO::GenericStream& stream, OutputFormatting format) + { + if (format == OutputFormatting::MinifiedJson) + { + using WriterType = rapidjson::Writer; + return AZStd::make_unique>(&stream); + } + else + { + using WriterType = rapidjson::PrettyWriter; + return AZStd::make_unique>(&stream); + } + } + + // + // In-memory rapidjson util functions + // + AZ::Outcome 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; + AZStd::stack entryStack; + AZStd::stack 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; + if constexpr (AZStd::is_same_v) + { + 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(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(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) + { + result = visitor.EndArray(entryCountStack.top()); + entryCountStack.pop(); + } + else if constexpr (AZStd::is_same_v) + { + result = visitor.EndObject(entryCountStack.top()); + entryCountStack.pop(); + } + else if constexpr (AZStd::is_same_v) + { + 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 diff --git a/Code/Framework/AzCore/AzCore/DOM/Backends/JSON/JsonSerializationUtils.h b/Code/Framework/AzCore/AzCore/DOM/Backends/JSON/JsonSerializationUtils.h new file mode 100644 index 0000000000..af0955dcfe --- /dev/null +++ b/Code/Framework/AzCore/AzCore/DOM/Backends/JSON/JsonSerializationUtils.h @@ -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 +#include +#include +#include +#include +#include +#include +#include + +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 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; //(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(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 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 + 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 + 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 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 + 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(parseFlags)>(stream, handler); + } + else + { + rapidjson::MemoryStream stream(buffer.data(), buffer.size()); + reader.Parse(parseFlags)>(stream, handler); + } + return handler.TakeOutcome(); + } + + template + Visitor::Result VisitSerializedJsonInPlace(char* buffer, Visitor& visitor) + { + rapidjson::Reader reader; + NullDelimitedStringStream stream(buffer); + RapidJsonReadHandler handler(&visitor, Lifetime::Persistent); + + reader.Parse(parseFlags) | rapidjson::kParseInsituFlag>(stream, handler); + return handler.TakeOutcome(); + } +} // namespace AZ::Dom::Json diff --git a/Code/Framework/AzCore/AzCore/DOM/DomBackend.cpp b/Code/Framework/AzCore/AzCore/DOM/DomBackend.cpp new file mode 100644 index 0000000000..b29c0be231 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/DOM/DomBackend.cpp @@ -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 + +namespace AZ::Dom +{ + Visitor::Result Backend::ReadFromBufferInPlace(char* buffer, AZStd::optional size, Visitor& visitor) + { + return ReadFromBuffer(buffer, size.value_or(strlen(buffer)), AZ::Dom::Lifetime::Persistent, visitor); + } +} // namespace AZ::Dom diff --git a/Code/Framework/AzCore/AzCore/DOM/DomBackend.h b/Code/Framework/AzCore/AzCore/DOM/DomBackend.h new file mode 100644 index 0000000000..2e4ccf8f3e --- /dev/null +++ b/Code/Framework/AzCore/AzCore/DOM/DomBackend.h @@ -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 +#include +#include +#include +#include +#include + +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, 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; + //! 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 diff --git a/Code/Framework/AzCore/AzCore/DOM/DomUtils.cpp b/Code/Framework/AzCore/AzCore/DOM/DomUtils.cpp new file mode 100644 index 0000000000..9751b9cecc --- /dev/null +++ b/Code/Framework/AzCore/AzCore/DOM/DomUtils.cpp @@ -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 + +#include + +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); + } +} diff --git a/Code/Framework/AzCore/AzCore/DOM/DomUtils.h b/Code/Framework/AzCore/AzCore/DOM/DomUtils.h new file mode 100644 index 0000000000..84a6eb5687 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/DOM/DomUtils.h @@ -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 + +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 diff --git a/Code/Framework/AzCore/AzCore/DOM/DomVisitor.cpp b/Code/Framework/AzCore/AzCore/DOM/DomVisitor.cpp new file mode 100644 index 0000000000..ad314da385 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/DOM/DomVisitor.cpp @@ -0,0 +1,239 @@ +/* + * 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 + +namespace AZ::Dom +{ + const char* VisitorError::CodeToString(VisitorErrorCode code) + { + switch (code) + { + case VisitorErrorCode::UnsupportedOperation: + return "operation not supported"; + case VisitorErrorCode::InvalidData: + return "invalid data specified"; + case VisitorErrorCode::InternalError: + return "internal error"; + default: + return "unknown error"; + } + } + + VisitorError::VisitorError(VisitorErrorCode code) + : m_code(code) + { + } + + VisitorError::VisitorError(VisitorErrorCode code, AZStd::string additionalInfo) + : m_code(code) + , m_additionalInfo(AZStd::move(additionalInfo)) + { + } + + VisitorErrorCode VisitorError::GetCode() const + { + return m_code; + } + + const AZStd::string& VisitorError::GetAdditionalInfo() const + { + return m_additionalInfo; + } + + AZStd::string VisitorError::FormatVisitorErrorMessage() const + { + if (m_additionalInfo.empty()) + { + return AZStd::string::format("VisitorError: %s.", CodeToString(m_code)); + } + return AZStd::string::format("VisitorError: %s. %s.", CodeToString(m_code), m_additionalInfo.c_str()); + } + + Visitor::Result Visitor::VisitorFailure(VisitorErrorCode code) + { + return AZ::Failure(VisitorError(code)); + } + + Visitor::Result Visitor::VisitorFailure(VisitorErrorCode code, AZStd::string additionalInfo) + { + return AZ::Failure(VisitorError(code, AZStd::move(additionalInfo))); + } + + Visitor::Result Visitor::VisitorFailure(VisitorError error) + { + return AZ::Failure(error); + } + + Visitor::Result Visitor::VisitorSuccess() + { + return AZ::Success(); + } + + Visitor::Result Visitor::Null() + { + return VisitorSuccess(); + } + + Visitor::Result Visitor::Bool([[maybe_unused]] bool value) + { + return VisitorSuccess(); + } + + Visitor::Result Visitor::Int64([[maybe_unused]] AZ::s64 value) + { + return VisitorSuccess(); + } + + Visitor::Result Visitor::Uint64([[maybe_unused]] AZ::u64 value) + { + return VisitorSuccess(); + } + + Visitor::Result Visitor::Double([[maybe_unused]] double value) + { + return VisitorSuccess(); + } + + Visitor::Result Visitor::String([[maybe_unused]] AZStd::string_view value, [[maybe_unused]] Lifetime lifetime) + { + return VisitorSuccess(); + } + + Visitor::Result Visitor::OpaqueValue([[maybe_unused]] const OpaqueType& value, [[maybe_unused]] Lifetime lifetime) + { + if (!SupportsOpaqueValues()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Opaque values are not supported by this visitor"); + } + return VisitorSuccess(); + } + + Visitor::Result Visitor::RawValue([[maybe_unused]] AZStd::string_view value, [[maybe_unused]] Lifetime lifetime) + { + if (!SupportsRawValues()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Raw values are not supported by this visitor"); + } + return VisitorSuccess(); + } + + Visitor::Result Visitor::StartObject() + { + if (!SupportsObjects()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Objects are not supported by this visitor"); + } + return VisitorSuccess(); + } + + Visitor::Result Visitor::EndObject([[maybe_unused]] AZ::u64 attributeCount) + { + if (!SupportsObjects()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Objects are not supported by this visitor"); + } + return VisitorSuccess(); + } + + Visitor::Result Visitor::Key([[maybe_unused]] AZ::Name key) + { + if (!SupportsObjects() && !SupportsNodes()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Keys are not supported by this visitor"); + } + return VisitorSuccess(); + } + + Visitor::Result Visitor::RawKey(AZStd::string_view key, [[maybe_unused]] Lifetime lifetime) + { + if (!SupportsRawKeys()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Raw keys are not supported by this visitor"); + } + return Key(AZ::Name(key)); + } + + Visitor::Result Visitor::StartArray() + { + if (!SupportsArrays()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Arrays are not supported by this visitor"); + } + return VisitorSuccess(); + } + + Visitor::Result Visitor::EndArray([[maybe_unused]] AZ::u64 elementCount) + { + if (!SupportsArrays()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Arrays are not supported by this visitor"); + } + return VisitorSuccess(); + } + + Visitor::Result Visitor::StartNode([[maybe_unused]] AZ::Name name) + { + if (!SupportsNodes()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Nodes are not supported by this visitor"); + } + return VisitorSuccess(); + } + + Visitor::Result Visitor::RawStartNode(AZStd::string_view name, [[maybe_unused]] Lifetime lifetime) + { + return StartNode(AZ::Name(name)); + } + + Visitor::Result Visitor::EndNode([[maybe_unused]] AZ::u64 attributeCount, [[maybe_unused]] AZ::u64 elementCount) + { + if (!SupportsNodes()) + { + return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Nodes are not supported by this visitor"); + } + return VisitorSuccess(); + } + + VisitorFlags Visitor::GetVisitorFlags() const + { + // By default support raw keys (promoting them to AZ::Name) and support Array / Object / Node + // We leave Opaque type support and Raw Values to more specialized, implementation-specific cases + return VisitorFlags::SupportsRawKeys | VisitorFlags::SupportsArrays | VisitorFlags::SupportsObjects | VisitorFlags::SupportsNodes; + } + + bool Visitor::SupportsRawValues() const + { + return (GetVisitorFlags() & VisitorFlags::SupportsRawValues) != VisitorFlags::Null; + } + + bool Visitor::SupportsRawKeys() const + { + return (GetVisitorFlags() & VisitorFlags::SupportsRawKeys) != VisitorFlags::Null; + } + + bool Visitor::SupportsObjects() const + { + return (GetVisitorFlags() & VisitorFlags::SupportsObjects) != VisitorFlags::Null; + } + + bool Visitor::SupportsArrays() const + { + return (GetVisitorFlags() & VisitorFlags::SupportsArrays) != VisitorFlags::Null; + } + + bool Visitor::SupportsNodes() const + { + return (GetVisitorFlags() & VisitorFlags::SupportsNodes) != VisitorFlags::Null; + } + + bool Visitor::SupportsOpaqueValues() const + { + return (GetVisitorFlags() & VisitorFlags::SupportsOpaqueValues) != VisitorFlags::Null; + } +} // namespace AZ::Dom diff --git a/Code/Framework/AzCore/AzCore/DOM/DomVisitor.h b/Code/Framework/AzCore/AzCore/DOM/DomVisitor.h new file mode 100644 index 0000000000..bbe78131c3 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/DOM/DomVisitor.h @@ -0,0 +1,238 @@ +/* + * 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 +#include +#include +#include + +namespace AZ::Dom +{ + // + // Lifetime enum + // + //! Specifies the period in which a reference value will still be alive and safe to read. + enum class Lifetime + { + //! Specifies that the value is safe to read and will remain so indefinitely. + //! This implies that the value will not be mutated for the duration of this storage. + Persistent, + //! Specifies that the value may change or be deallocated, and must be copied to be safely stored. + Temporary, + }; + + // + // VisitorErrorCode enum + // + //! Error code specifying the reason a Visitor operation failed. + enum class VisitorErrorCode + { + //! Set when a Visitor doesn't have an implementation for a given attribute type. + //! A pure-JSON serializer might reject a Node attribute, for example, and serialization visitors + //! can forbid non-serializable Opaque types. + UnsupportedOperation, + //! Set when a Visitor has received malformed or invalid data. + //! Potential sources include mismatching Begin/End call pairs or invalid attribute or element counts + //! being sent to End methods. + InvalidData, + //! The Visitor failed for some other reason not caused by invalid input. + //! If returning a custom error with this code, it's preferrable to also provide supplemental info + //! in the form of an explanatory string. + InternalError + }; + + // + // VisitorError class + // + //! Details of the reason for failure within a VisitorInterface operation. + class VisitorError final + { + public: + explicit VisitorError(VisitorErrorCode code); + VisitorError(VisitorErrorCode code, AZStd::string additionalInfo); + + //! Gets the error code associated with this error. + VisitorErrorCode GetCode() const; + //! Gets a supplemental error info string from the error. + //! Returns an empty string if no additional information was provided to the error. + const AZStd::string& GetAdditionalInfo() const; + //! Provides a formatted, human-readable error description that can be used for logging purposes. + AZStd::string FormatVisitorErrorMessage() const; + + //! Helper method, translates a VisitorErrorCode to a human readable string. + static const char* CodeToString(VisitorErrorCode code); + + private: + VisitorErrorCode m_code; + AZStd::string m_additionalInfo; + }; + + //! A type alias for opaque DOM types that aren't meant to be serializable. + //! \see VisitorInterface::OpaqueValue + using OpaqueType = AZStd::any; + + // + // VisitorFlags enum + // + //! Flags representning capabilities of a \ref Visitor. + enum class VisitorFlags : AZ::u16 + { + //! No flags are set. This can be used in conjunction with bitwise operators to check a flag. + Null = 0, + //! If set, this Visitor interface supports raw strings in place of specific value types. + //! Visitors with this flag accept RawValue calls in lieu of more specific value calls such as Int64 or String. + SupportsRawValues = (1 << 1), + //! If set, this Visitor interface supports raw strings in place of Name types for keys and Node names. + //! Visitors with this flag accept RawKey and RawStartNode in lieu of Key and StartNode calls. + SupportsRawKeys = (1 << 2), + //! If set, this Visitor interface supports Object types described via BeginObject and EndObject. + SupportsObjects = (1 << 3), + //! If set, this Visitor interface supports Array types described via BeginArray and EndArray. + SupportsArrays = (1 << 4), + //! If set, this Visitor interface supports Node types described BeginNode and EndNode. + SupportsNodes = (1 << 4), + //! If set, this Visitor interface supports opaque values described via OpaqueValue. + SupportsOpaqueValues = (1 << 5), + }; + + AZ_DEFINE_ENUM_BITWISE_OPERATORS(VisitorFlags); + + // + // Visitor class + // + //! An interface for performing operations on elements of a generic DOM (Document Object Model). + //! A Document Object Model is defined here as a tree structure comprised of one of the following values: + //! - Primitives: plain data types, including + //! - \ref Int64: 64 bit signed integer + //! - \ref Uint64: 64 bit unsigned integer + //! - \ref Bool: boolean value + //! - \ref Double: 64 bit double precision float + //! - \ref Null: sentinel "empty" type with no value representation + //! - \ref String: UTF8 encoded string + //! - \ref Object: an ordered container of key/value pairs where keys are \ref AZ::Name and values may be any DOM type + //! (including Object) + //! - \ref Array: an ordered container of values, in which values are any DOM value type (including Array) + //! - \ref Node: a container + //! - \ref OpaqueValue: An arbitrary value stored in an AZStd::any. This is a non-serializable representation of an + //! entry useful for in-memory options. This is intended to be used as an intermediate value over the course of DOM + //! transformation and as a proxy to pass through types of which the DOM has no knowledge to other systems. + //! + //! Opaque values are rejected by the default VisitorInterface implementation. + //! + //! Care should be ensured that DOMs representing opaque types are only visited by consumers that understand them. + class Visitor + { + public: + virtual ~Visitor() = default; + + //! The result of a Visitor operation. + //! A failure indicates a non-recoverable issue and signals that no further visit calls may be made in the + //! current state. + using Result = AZ::Outcome; + + //! Returns a set of flags representing the operations this Visitor supports. + //! The base implementation supports raw keys (\see VisitorFlags::SupportsRawKeys) and + //! arrays (\see VisitorFlags::SupportsArrays), objects (\see VisitorFlags::SupportsObjects), and + //! nodes (\see VisitorFlags::SupportsNodes). + //! Raw (\see VisitorFlags::SupportsRawValues) and opaque values (\see VisitorFlags::SupportsOpaqueValues) + //! are disallowed by default, as their handling is intended to be implementation-specific. + virtual VisitorFlags GetVisitorFlags() const; + //! \see VisitorFlags::SupportsRawValues + bool SupportsRawValues() const; + //! \see VisitorFlags::SupportsRawKeys + bool SupportsRawKeys() const; + //! \see VisitorFlags::SupportsObjects + bool SupportsObjects() const; + //! \see VisitorFlags::SupportsArrays + bool SupportsArrays() const; + //! \see VisitorFlags::SupportsNodes + bool SupportsNodes() const; + //! \see VisitorFlags::SupportsOpaqueValues + bool SupportsOpaqueValues() const; + + //! Operates on an empty null value. + virtual Result Null(); + //! Operates on a bool value. + virtual Result Bool(bool value); + //! Operates on a signed, 64 bit integer value. + virtual Result Int64(AZ::s64 value); + //! Operates on an unsigned, 64 bit integer value. + virtual Result Uint64(AZ::u64 value); + //! Operates on a double precision, 64 bit floating point value. + virtual Result Double(double value); + //! Operates on a string value. As strings are a reference type. + //! Storage semantics are provided to indicate where the value may be stored persistently or requires a copy. + virtual Result String(AZStd::string_view value, Lifetime lifetime); + //! Operates on an opaque value. As opaque values are a reference type, storage semantics are provided to + //! indicate where the value may be stored persistently or requires a copy. + //! The base implementation of OpaqueValue rejects the operation, as opaque values are meant for special + //! cases with specific implementations, not generic usage. + //! Storage semantics are provided to indicate where the value may be stored persistently or requires a copy. + virtual Result OpaqueValue(const OpaqueType& value, Lifetime lifetime); + //! Operates on a raw value encoded as a UTF-8 string that hasn't had its type deduced. + //! Visitors that support raw values (\see VisitorFlags::SupportsRawValues) may parse the raw value and + //! forward it to the corresponding value call or calls of their choice. + //! The base implementation of RawValue rejects the operation, as raw values are meant to be handled on + //! a per-implementation basis. + virtual Result RawValue(AZStd::string_view value, Lifetime lifetime); + + //! Operates on an Object. + //! Callers may make any number of Key calls, followed by calls representing a value (including a nested + //! StartObject call) and then must call EndObject. + virtual Result StartObject(); + //! Finishes operating on an Object. + //! Callers must provide the number of attributes that were provided to the object, i.e. the number of key + //! and value calls made within the direct context of this object (but not any nested objects / nodes). + virtual Result EndObject(AZ::u64 attributeCount); + + //! Specifies a key for a key/value pair. + //! Key must be called subsequent to a call to \ref StartObject or \ref StartNode and immediately followed by + //! calls representing the key's associated value. + virtual Result Key(AZ::Name key); + //! Specifies a key for a key/value pair using a raw string instead of \ref AZ::Name. + //! \see Key + virtual Result RawKey(AZStd::string_view key, Lifetime lifetime); + + //! Operates on an Array. + //! Callers may make any number of subsequent value calls to represent the elements of the array, and then must + //! call EndArray. + virtual Result StartArray(); + //! Finishes operating on an Array. + //! Callers must provide the number of elements that were provided to the array, i.e. the number of value calls + //! made within the direct context of this array (but not any nested arrays / nodes). + virtual Result EndArray(AZ::u64 elementCount); + + //! Operates on a Node. + //! Callers may make any number of Key calls followed by value calls or value calls not prefixed with a Key + //! call, and then must call EndNode. See \ref StartObject and \ref StartArray as Node types combine the + //! functionality of both structures into a named Node structure. + virtual Result StartNode(AZ::Name name); + //! Operates on a Node using a raw string instead of \ref AZ::Name. + //! \see StartNode + virtual Result RawStartNode(AZStd::string_view name, Lifetime lifetime); + //! Finishes operating on a Node. + //! Callers must provide both the number of attributes the were provided and the number of elements that were + //! provided to the node, attributes being values prefaced by a call to Key. + virtual Result EndNode(AZ::u64 attributeCount, AZ::u64 elementCount); + + protected: + Visitor() = default; + + //! Helper method, constructs a failure \ref Result with the specified code. + static Result VisitorFailure(VisitorErrorCode code); + //! Helper method, constructs a failure \ref Result with the specified code and supplemental info. + static Result VisitorFailure(VisitorErrorCode code, AZStd::string additionalInfo); + //! Helper method, constructs a failure \ref Result with the specified error. + static Result VisitorFailure(VisitorError error); + + //! Helper method, constructs a success \ref Result. + static Result VisitorSuccess(); + }; +} // namespace AZ::Dom diff --git a/Code/Framework/AzCore/AzCore/Debug/AssetTracking.cpp b/Code/Framework/AzCore/AzCore/Debug/AssetTracking.cpp deleted file mode 100644 index 3cb895c992..0000000000 --- a/Code/Framework/AzCore/AzCore/Debug/AssetTracking.cpp +++ /dev/null @@ -1,336 +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 -#include -#include -#include -#include - -namespace AZ -{ - namespace Debug - { - namespace - { - struct AssetTreeNode; - - // Per-thread data that needs to be stored. - struct ThreadData - { - AZStd::vector 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& GetEnvironmentVariable(); - static AssetTrackingImpl* GetSharedInstance(); - static ThreadData& GetSharedThreadData(); - - using PrimaryAssets = AZStd::unordered_map, AZStd::equal_to, AZStdAssetTrackingAllocator>; - using ThreadData = ThreadData; - using mutex_type = AZStd::mutex; - using lock_type = AZStd::lock_guard; - - 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 -/////////////////////////////////////////////////////////////////////////////// - -namespace AZ -{ - namespace Debug - { - 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(GetSharedInstance())->GetThreadData(); - } - - AssetTrackingImpl::ThreadData& AssetTrackingImpl::GetThreadData() - { - static thread_local ThreadData* data = nullptr; - static thread_local typename AZStd::aligned_storage_t storage; - - if (!data) - { - data = new (&storage) ThreadData; - } - - return *data; - } - - EnvironmentVariable& AssetTrackingImpl::GetEnvironmentVariable() - { - static EnvironmentVariable assetTrackingImpl = Environment::CreateVariable(AzTypeInfo::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, ""); - } - 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 diff --git a/Code/Framework/AzCore/AzCore/Debug/AssetTracking.h b/Code/Framework/AzCore/AzCore/Debug/AssetTracking.h deleted file mode 100644 index 615634c05a..0000000000 --- a/Code/Framework/AzCore/AzCore/Debug/AssetTracking.h +++ /dev/null @@ -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 -#include -#include - -#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 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 - struct AssetTrackingEventProcessingPolicy - { - template - static void CallResult(Results& results, Function&& func, Interface&& iface, InputArgs&&... args) - { - AZ_ASSET_ATTACH_TO_SCOPE(iface); - Parent::CallResult(results, AZStd::forward(func), AZStd::forward(iface), AZStd::forward(args)...); - } - - template - static void Call(Function&& func, Interface&& iface, InputArgs&&... args) - { - AZ_ASSET_ATTACH_TO_SCOPE(iface); - Parent::Call(AZStd::forward(func), AZStd::forward(iface), AZStd::forward(args)...); - } - }; - } - -} // namespace AzFramework diff --git a/Code/Framework/AzCore/AzCore/Debug/AssetTrackingTypes.h b/Code/Framework/AzCore/AzCore/Debug/AssetTrackingTypes.h deleted file mode 100644 index f538c516c3..0000000000 --- a/Code/Framework/AzCore/AzCore/Debug/AssetTrackingTypes.h +++ /dev/null @@ -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 -#include -#include - -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 - { - 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 - { - public: - AZ_TYPE_INFO(AssetTrackingAllocator, "{F6C08E92-559C-4153-9620-6A8491F78F10}"); - - using Base = AZ::SimpleSchemaAllocator; - using Descriptor = Base::Descriptor; - - AssetTrackingAllocator() - : Base("AssetTrackingAllocator", "Allocator for the AssetTracking") - { - DisableOverriding(); - } - }; - - using AZStdAssetTrackingAllocator = AZ::AZStdAlloc; - using AssetTrackingString = AZStd::basic_string, AZStdAssetTrackingAllocator>; - - template - using AssetTrackingMap = AZStd::unordered_map, AZStd::equal_to, 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::operator()(const AZ::Debug::AssetTrackingId& info) const -{ - return AZStd::hash()(info.m_id); -} - diff --git a/Code/Framework/AzCore/AzCore/Debug/AssetTrackingTypesImpl.h b/Code/Framework/AzCore/AzCore/Debug/AssetTrackingTypesImpl.h deleted file mode 100644 index eac809c406..0000000000 --- a/Code/Framework/AzCore/AzCore/Debug/AssetTrackingTypesImpl.h +++ /dev/null @@ -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 - -#include - -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 - 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; - - const AssetPrimaryInfo* m_primaryinfo; - AssetTreeNode* m_parent; - AssetMap m_children; - AssetDataT m_data; - }; - - template - class AssetTree : public AssetTreeBase - { - public: - ~AssetTree() override = default; - - AssetTreeNodeBase& GetRoot() override - { - return m_rootAssets; - } - - using NodeType = AssetTreeNode; - - NodeType m_rootAssets; - }; - - - template - struct AllocationRecord - { - AssetTreeNodeBase* m_asset; - uint32_t m_size; - AllocationDataT m_data; - }; - - - template - class AllocationTable : public AssetAllocationTableBase - { - public: - using RecordType = AllocationRecord; - using AllocationReverseMap = AZStd::map, AZStdAssetTrackingAllocator>; - using mutex_type = AZStd::mutex; - using lock_type = AZStd::lock_guard; - - 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(ptr) <= reinterpret_cast(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; - }; - } -} diff --git a/Code/Framework/AzCore/AzCore/Debug/EventTrace.cpp b/Code/Framework/AzCore/AzCore/Debug/EventTrace.cpp deleted file mode 100644 index 88a78de031..0000000000 --- a/Code/Framework/AzCore/AzCore/Debug/EventTrace.cpp +++ /dev/null @@ -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 -#include -#include -#include - -namespace AZ -{ - namespace 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)); - } - } -} diff --git a/Code/Framework/AzCore/AzCore/Debug/EventTrace.h b/Code/Framework/AzCore/AzCore/Debug/EventTrace.h deleted file mode 100644 index 1bc7ee20a6..0000000000 --- a/Code/Framework/AzCore/AzCore/Debug/EventTrace.h +++ /dev/null @@ -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 -#include -#include - -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) diff --git a/Code/Framework/AzCore/AzCore/Debug/EventTraceDriller.cpp b/Code/Framework/AzCore/AzCore/Debug/EventTraceDriller.cpp deleted file mode 100644 index 658021b018..0000000000 --- a/Code/Framework/AzCore/AzCore/Debug/EventTraceDriller.cpp +++ /dev/null @@ -1,161 +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 -#include -#include -#include - -namespace AZ -{ - namespace Debug - { - namespace Crc - { - const u32 EventTraceDriller = AZ_CRC("EventTraceDriller", 0xf7aeae55); - const u32 Slice = AZ_CRC("Slice", 0x3dae78a5); - const u32 ThreadInfo = AZ_CRC("ThreadInfo", 0x89bf78be); - const u32 Name = AZ_CRC("Name", 0x5e237e06); - const u32 Category = AZ_CRC("Category", 0x064c19c1); - const u32 ThreadId = AZ_CRC("ThreadId", 0xd0fd9043); - const u32 Timestamp = AZ_CRC("Timestamp", 0xa5d6e63e); - const u32 Duration = AZ_CRC("Duration", 0x865f80c0); - const u32 Instant = AZ_CRC("Instant", 0x0e9047ad); - } - - 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 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 lock(m_ThreadMutex); - m_Threads.erase((size_t)id.m_id); - } - - void EventTraceDriller::RecordThreads() - { - if (m_output && m_Threads.size()) - { - // Main bus mutex guards m_output. - auto& context = EventTraceDrillerBus::GetOrCreateContext(); - - AZStd::scoped_lock 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); - } - } -} diff --git a/Code/Framework/AzCore/AzCore/Debug/EventTraceDriller.h b/Code/Framework/AzCore/AzCore/Debug/EventTraceDriller.h deleted file mode 100644 index fee71147ca..0000000000 --- a/Code/Framework/AzCore/AzCore/Debug/EventTraceDriller.h +++ /dev/null @@ -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 -#include -#include -#include -#include - -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, AZStd::equal_to, OSStdAllocator> m_Threads; - }; - } -} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Debug/EventTraceDrillerBus.h b/Code/Framework/AzCore/AzCore/Debug/EventTraceDrillerBus.h deleted file mode 100644 index c588559088..0000000000 --- a/Code/Framework/AzCore/AzCore/Debug/EventTraceDrillerBus.h +++ /dev/null @@ -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 -#include -#include - -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 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 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, "") diff --git a/Code/Framework/AzCore/AzCore/Debug/MemoryProfiler.h b/Code/Framework/AzCore/AzCore/Debug/MemoryProfiler.h index 5b22e20c60..827f00134d 100644 --- a/Code/Framework/AzCore/AzCore/Debug/MemoryProfiler.h +++ b/Code/Framework/AzCore/AzCore/Debug/MemoryProfiler.h @@ -8,7 +8,7 @@ #pragma once #ifndef AZ_PROFILE_MEMORY_ALLOC -// No other profiler has defined the performance markers AZ_PROFILE_MEMORY_ALLOC (and friends), fall back to a Driller implementation (currently empty) +// No other profiler has defined the performance markers AZ_PROFILE_MEMORY_ALLOC (and friends), fall back to current implementation (empty) # define AZ_PROFILE_MEMORY_ALLOC(category, address, size, context) # define AZ_PROFILE_MEMORY_ALLOC_EX(category, filename, lineNumber, address, size, context) # define AZ_PROFILE_MEMORY_FREE(category, address) diff --git a/Code/Framework/AzCore/AzCore/Debug/Profiler.cpp b/Code/Framework/AzCore/AzCore/Debug/Profiler.cpp index 0bb92b923d..0cb77b2d6e 100644 --- a/Code/Framework/AzCore/AzCore/Debug/Profiler.cpp +++ b/Code/Framework/AzCore/AzCore/Debug/Profiler.cpp @@ -7,4 +7,63 @@ */ #include +#include +#include +#include +#include +namespace AZ::Debug +{ + AZStd::string GenerateOutputFile(const char* nameHint) + { + AZ::IO::FixedMaxPathString captureOutput = GetProfilerCaptureLocation(); + return AZStd::string::format("%s/capture_%s_%lld.json", captureOutput.c_str(), nameHint, AZStd::GetTimeNowSecond()); + } + + void ProfilerCaptureFrame([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) + { + if (auto profilerSystem = ProfilerSystemInterface::Get(); profilerSystem) + { + AZStd::string captureFile = GenerateOutputFile("single"); + AZLOG_INFO("Setting capture file to %s", captureFile.c_str()); + profilerSystem->CaptureFrame(captureFile); + } + } + AZ_CONSOLEFREEFUNC(ProfilerCaptureFrame, AZ::ConsoleFunctorFlags::DontReplicate, "Capture a single frame of profiling data"); + + void ProfilerStartCapture([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) + { + if (auto profilerSystem = ProfilerSystemInterface::Get(); profilerSystem) + { + AZStd::string captureFile = GenerateOutputFile("multi"); + AZLOG_INFO("Setting capture file to %s", captureFile.c_str()); + profilerSystem->StartCapture(AZStd::move(captureFile)); + } + } + AZ_CONSOLEFREEFUNC(ProfilerStartCapture, AZ::ConsoleFunctorFlags::DontReplicate, "Start a multi-frame capture of profiling data"); + + void ProfilerEndCapture([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) + { + if (auto profilerSystem = ProfilerSystemInterface::Get(); profilerSystem) + { + profilerSystem->EndCapture(); + } + } + AZ_CONSOLEFREEFUNC(ProfilerEndCapture, AZ::ConsoleFunctorFlags::DontReplicate, "End and dump an in-progress continuous capture"); + + AZ::IO::FixedMaxPathString GetProfilerCaptureLocation() + { + AZ::IO::FixedMaxPathString captureOutput; + if (AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry) + { + settingsRegistry->Get(captureOutput, RegistryKey_ProfilerCaptureLocation); + } + + if (captureOutput.empty()) + { + captureOutput = ProfilerCaptureLocationFallback; + } + + return captureOutput; + } +} // namespace AZ::Debug diff --git a/Code/Framework/AzCore/AzCore/Debug/ProfilerBus.h b/Code/Framework/AzCore/AzCore/Debug/ProfilerBus.h index 1537be3b81..322bfb60c2 100644 --- a/Code/Framework/AzCore/AzCore/Debug/ProfilerBus.h +++ b/Code/Framework/AzCore/AzCore/Debug/ProfilerBus.h @@ -9,11 +9,20 @@ #pragma once #include +#include +#include +#include namespace AZ { namespace Debug { + //! settings registry entry for specifying where to output profiler captures + static constexpr const char* RegistryKey_ProfilerCaptureLocation = "/O3DE/AzCore/Debug/Profiler/CaptureLocation"; + + //! fallback value in the event the settings registry isn't ready or doesn't contain the key + static constexpr const char* ProfilerCaptureLocationFallback = "@user@/Profiler"; + /** * ProfilerNotifications provides a profiler event interface that can be used to update listeners on profiler status */ @@ -23,32 +32,38 @@ namespace AZ public: virtual ~ProfilerNotifications() = default; - virtual void OnProfileSystemInitialized() = 0; + //! Notify when the current profiler capture is finished + //! @param result Set to true if it's finished successfully + //! @param info The output file path or error information which depends on the return. + virtual void OnCaptureFinished(bool result, const AZStd::string& info) = 0; }; using ProfilerNotificationBus = AZ::EBus; - enum class ProfileFrameAdvanceType - { - Game, - Render, - Default = Game - }; - /** * ProfilerRequests provides an interface for making profiling system requests */ class ProfilerRequests - : public AZ::EBusTraits { public: - // Allow multiple threads to concurrently make requests - using MutexType = AZStd::mutex; - + AZ_RTTI(ProfilerRequests, "{90AEC117-14C1-4BAE-9704-F916E49EF13F}"); virtual ~ProfilerRequests() = default; - virtual bool IsActive() = 0; - virtual void FrameAdvance(ProfileFrameAdvanceType type) = 0; + //! Getter/setter for the profiler active state + virtual bool IsActive() const = 0; + virtual void SetActive(bool active) = 0; + + //! Capture a single frame of profiling data + virtual bool CaptureFrame(const AZStd::string& outputFilePath) = 0; + + //! Starting/ending a multi-frame capture of profiling data + virtual bool StartCapture(AZStd::string outputFilePath) = 0; + virtual bool EndCapture() = 0; }; - using ProfilerRequestBus = AZ::EBus; - } -} + + using ProfilerSystemInterface = AZ::Interface; + + //! helper function for getting the profiler capture location from the settings registry that + //! includes fallback handing in the event the registry value can't be determined + AZ::IO::FixedMaxPathString GetProfilerCaptureLocation(); + } // namespace Debug +} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Debug/ProfilerReflection.cpp b/Code/Framework/AzCore/AzCore/Debug/ProfilerReflection.cpp new file mode 100644 index 0000000000..42151a6996 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Debug/ProfilerReflection.cpp @@ -0,0 +1,92 @@ +/* + * 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 + +#include +#include +#include +#include + +namespace AZ::Debug +{ + static constexpr const char* ProfilerScriptCategory = "Profiler"; + static constexpr const char* ProfilerScriptModule = "debug"; + static constexpr AZ::Script::Attributes::ScopeFlags ProfilerScriptScope = AZ::Script::Attributes::ScopeFlags::Automation; + + class ProfilerNotificationBusHandler final + : public ProfilerNotificationBus::Handler + , public AZ::BehaviorEBusHandler + { + public: + AZ_EBUS_BEHAVIOR_BINDER(ProfilerNotificationBusHandler, "{44161459-B816-4876-95A4-BA16DEC767D6}", AZ::SystemAllocator, + OnCaptureFinished + ); + + void OnCaptureFinished(bool result, const AZStd::string& info) override + { + Call(FN_OnCaptureFinished, result, info); + } + + static void Reflect(AZ::ReflectContext* context) + { + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->EBus("ProfilerNotificationBus") + ->Attribute(AZ::Script::Attributes::Category, ProfilerScriptCategory) + ->Attribute(AZ::Script::Attributes::Module, ProfilerScriptModule) + ->Attribute(AZ::Script::Attributes::Scope, ProfilerScriptScope) + ->Handler(); + } + } + }; + + class ProfilerSystemScriptProxy + : public BehaviorInterfaceProxy + { + public: + AZ_RTTI(ProfilerSystemScriptProxy, "{D671FB70-8B09-4C3A-96CD-06A339F3138E}", BehaviorInterfaceProxy); + + AZ_BEHAVIOR_INTERFACE(ProfilerSystemScriptProxy, ProfilerRequests); + }; + + void ProfilerReflect(AZ::ReflectContext* context) + { + if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->ConstantProperty("g_ProfilerSystem", ProfilerSystemScriptProxy::GetProxy) + ->Attribute(AZ::Script::Attributes::Category, ProfilerScriptCategory) + ->Attribute(AZ::Script::Attributes::Module, ProfilerScriptModule) + ->Attribute(AZ::Script::Attributes::Scope, ProfilerScriptScope); + + behaviorContext->Class("ProfilerSystemInterface") + ->Attribute(AZ::Script::Attributes::Category, ProfilerScriptCategory) + ->Attribute(AZ::Script::Attributes::Module, ProfilerScriptModule) + ->Attribute(AZ::Script::Attributes::Scope, ProfilerScriptScope) + + ->Method("IsValid", &ProfilerSystemScriptProxy::IsValid) + + ->Method("GetCaptureLocation", + [](ProfilerSystemScriptProxy*) -> AZStd::string + { + AZ::IO::FixedMaxPathString captureOutput = GetProfilerCaptureLocation(); + return AZStd::string(captureOutput.c_str(), captureOutput.length()); + }) + + ->Method("IsActive", ProfilerSystemScriptProxy::WrapMethod<&ProfilerRequests::IsActive>()) + ->Method("SetActive", ProfilerSystemScriptProxy::WrapMethod<&ProfilerRequests::SetActive>()) + + ->Method("CaptureFrame", ProfilerSystemScriptProxy::WrapMethod<&ProfilerRequests::CaptureFrame>()) + + ->Method("StartCapture", ProfilerSystemScriptProxy::WrapMethod<&ProfilerRequests::StartCapture>()) + ->Method("EndCapture", ProfilerSystemScriptProxy::WrapMethod<&ProfilerRequests::EndCapture>()); + } + + ProfilerNotificationBusHandler::Reflect(context); + } +} // namespace AZ::Debug diff --git a/Code/Framework/AzFramework/Platform/Windows/AzFramework/Archive/ArchiveVars_Windows.h b/Code/Framework/AzCore/AzCore/Debug/ProfilerReflection.h similarity index 51% rename from Code/Framework/AzFramework/Platform/Windows/AzFramework/Archive/ArchiveVars_Windows.h rename to Code/Framework/AzCore/AzCore/Debug/ProfilerReflection.h index 5a80afe271..d6857defe5 100644 --- a/Code/Framework/AzFramework/Platform/Windows/AzFramework/Archive/ArchiveVars_Windows.h +++ b/Code/Framework/AzCore/AzCore/Debug/ProfilerReflection.h @@ -5,7 +5,15 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ - #pragma once -#define STREAM_CACHE_DEFAULT 0 +namespace AZ +{ + class ReflectContext; + + namespace Debug + { + //! Reflects the profiler bus script bindings + void ProfilerReflect(AZ::ReflectContext* context); + } // namespace Debug +} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Debug/Trace.cpp b/Code/Framework/AzCore/AzCore/Debug/Trace.cpp index 357149d096..74f27481c1 100644 --- a/Code/Framework/AzCore/AzCore/Debug/Trace.cpp +++ b/Code/Framework/AzCore/AzCore/Debug/Trace.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -27,26 +26,21 @@ #include #include -namespace AZ +namespace AZ::Debug { - namespace Debug + struct StackFrame; + + namespace Platform { - struct StackFrame; - - namespace Platform - { #if defined(AZ_ENABLE_DEBUG_TOOLS) - bool AttachDebugger(); - bool IsDebuggerPresent(); - void HandleExceptions(bool isEnabled); - void DebugBreak(); + bool AttachDebugger(); + bool IsDebuggerPresent(); + void HandleExceptions(bool isEnabled); + void DebugBreak(); #endif - void Terminate(int exitCode); - } + void Terminate(int exitCode); } - using namespace AZ::Debug; - namespace DebugInternal { // other threads can trigger fatals and errors, but the same thread should not, to avoid stack overflow. @@ -60,7 +54,7 @@ namespace AZ // Globals const int g_maxMessageLength = 4096; static const char* g_dbgSystemWnd = "System"; - Trace Debug::g_tracer; + Trace g_tracer; void* g_exceptionInfo = nullptr; // Environment var needed to track ignored asserts across systems and disable native UI under certain conditions @@ -83,6 +77,7 @@ namespace AZ constexpr LogLevel DefaultLogLevel = LogLevel::Info; AZ_CVAR_SCOPED(int, bg_traceLogLevel, DefaultLogLevel, nullptr, ConsoleFunctorFlags::Null, "Enable trace message logging in release mode. 0=disabled, 1=errors, 2=warnings, 3=info."); + AZ_CVAR_SCOPED(bool, bg_alwaysShowCallstack, false, nullptr, ConsoleFunctorFlags::Null, "Force stack trace output without allowing ebus interception."); /** * If any listener returns true, store the result so we don't outputs detailed information. @@ -280,10 +275,15 @@ namespace AZ logger->Flush(); // Flush as an assert may indicate a crash is imminent. } - EBUS_EVENT(TraceMessageDrillerBus, OnPreAssert, fileName, line, funcName, message); - TraceMessageResult result; EBUS_EVENT_RESULT(result, TraceMessageBus, OnPreAssert, fileName, line, funcName, message); + + if (bg_alwaysShowCallstack) + { + // If we're always showing the callstack, print it now before there's any chance of an ebus handler interrupting + PrintCallstack(g_dbgSystemWnd, 1); + } + if (result.m_value) { g_alreadyHandlingAssertOrFatal = false; @@ -299,7 +299,6 @@ namespace AZ azstrcat(message, g_maxMessageLength, "\n"); Output(g_dbgSystemWnd, message); - EBUS_EVENT(TraceMessageDrillerBus, OnAssert, message); EBUS_EVENT_RESULT(result, TraceMessageBus, OnAssert, message); if (result.m_value) { @@ -309,7 +308,10 @@ namespace AZ } Output(g_dbgSystemWnd, "------------------------------------------------\n"); - PrintCallstack(g_dbgSystemWnd, 1); + if (!bg_alwaysShowCallstack) + { + PrintCallstack(g_dbgSystemWnd, 1); + } Output(g_dbgSystemWnd, "==================================================================\n"); char dialogBoxText[g_maxMessageLength]; @@ -399,8 +401,6 @@ namespace AZ logger->RecordStringEvent(ErrorEventId, message); } - EBUS_EVENT(TraceMessageDrillerBus, OnPreError, window, fileName, line, funcName, message); - TraceMessageResult result; EBUS_EVENT_RESULT(result, TraceMessageBus, OnPreError, window, fileName, line, funcName, message); if (result.m_value) @@ -415,7 +415,6 @@ namespace AZ azstrcat(message, g_maxMessageLength, "\n"); Output(window, message); - EBUS_EVENT(TraceMessageDrillerBus, OnError, window, message); EBUS_EVENT_RESULT(result, TraceMessageBus, OnError, window, message); Output(window, "==================================================================\n"); if (result.m_value) @@ -451,8 +450,6 @@ namespace AZ logger->RecordStringEvent(WarningEventId, message); } - EBUS_EVENT(TraceMessageDrillerBus, OnPreWarning, window, fileName, line, funcName, message); - TraceMessageResult result; EBUS_EVENT_RESULT(result, TraceMessageBus, OnPreWarning, window, fileName, line, funcName, message); if (result.m_value) @@ -466,7 +463,6 @@ namespace AZ azstrcat(message, g_maxMessageLength, "\n"); Output(window, message); - EBUS_EVENT(TraceMessageDrillerBus, OnWarning, window, message); EBUS_EVENT_RESULT(result, TraceMessageBus, OnWarning, window, message); Output(window, "==================================================================\n"); } @@ -495,8 +491,6 @@ namespace AZ logger->RecordStringEvent(PrintfEventId, message); } - EBUS_EVENT(TraceMessageDrillerBus, OnPrintf, window, message); - TraceMessageResult result; EBUS_EVENT_RESULT(result, TraceMessageBus, OnPrintf, window, message); if (result.m_value) @@ -525,7 +519,6 @@ namespace AZ // only call into Ebusses if we are not in a recursive-exception situation as that // would likely just lead to even more exceptions. - EBUS_EVENT(TraceMessageDrillerBus, OnOutput, window, message); TraceMessageResult result; EBUS_EVENT_RESULT(result, TraceMessageBus, OnOutput, window, message); if (result.m_value) @@ -534,6 +527,16 @@ namespace AZ } } + RawOutput(window, message); + } + + void Trace::RawOutput(const char* window, const char* message) + { + if (!window) + { + window = g_dbgSystemWnd; + } + // printf on Windows platforms seem to have a buffer length limit of 4096 characters // Therefore fwrite is used directly to write the window and message to stdout AZStd::string_view windowView{ window }; @@ -577,9 +580,19 @@ namespace AZ } azstrcat(lines[i], AZ_ARRAY_SIZE(lines[i]), "\n"); + // Use Output instead of AZ_Printf to be consistent with the exception output code and avoid // this accidentally being suppressed as a normal message - Output(window, lines[i]); + + if (bg_alwaysShowCallstack) + { + // Use Raw Output as this cannot be suppressed + RawOutput(window, lines[i]); + } + else + { + Output(window, lines[i]); + } } } } @@ -616,4 +629,4 @@ namespace AZ val.Set(level); } } -} // namspace AZ +} // namspace AZ::Debug diff --git a/Code/Framework/AzCore/AzCore/Debug/Trace.h b/Code/Framework/AzCore/AzCore/Debug/Trace.h index 507ba48e53..fe33bf1b9c 100644 --- a/Code/Framework/AzCore/AzCore/Debug/Trace.h +++ b/Code/Framework/AzCore/AzCore/Debug/Trace.h @@ -73,6 +73,9 @@ namespace AZ static void Output(const char* window, const char* message); + /// Called by output to handle the actual output, does not interact with ebus or allow interception + static void RawOutput(const char* window, const char* message); + static void PrintCallstack(const char* window, unsigned int suppressCount = 0, void* nativeContext = 0); /// PEXCEPTION_POINTERS on Windows, always NULL on other platforms diff --git a/Code/Framework/AzCore/AzCore/Debug/TraceMessagesDriller.cpp b/Code/Framework/AzCore/AzCore/Debug/TraceMessagesDriller.cpp deleted file mode 100644 index 7e9e5b146e..0000000000 --- a/Code/Framework/AzCore/AzCore/Debug/TraceMessagesDriller.cpp +++ /dev/null @@ -1,102 +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 -#include - -namespace AZ -{ - namespace Debug - { - //========================================================================= - // Start - // [2/6/2013] - //========================================================================= - void TraceMessagesDriller::Start(const Param* params, int numParams) - { - (void)params; - (void)numParams; - BusConnect(); - } - - //========================================================================= - // Stop - // [2/6/2013] - //========================================================================= - void TraceMessagesDriller::Stop() - { - BusDisconnect(); - } - - //========================================================================= - // OnAssert - // [2/6/2013] - //========================================================================= - void TraceMessagesDriller::OnAssert(const char* message) - { - // Not sure if we can really capture assert since the code will stop executing very soon. - m_output->BeginTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00)); - m_output->Write(AZ_CRC("OnAssert", 0xb74db4ce), message); - m_output->EndTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00)); - } - - //========================================================================= - // OnException - // [2/6/2013] - //========================================================================= - void TraceMessagesDriller::OnException(const char* message) - { - // Not sure if we can really capture exception since the code will stop executing very soon. - m_output->BeginTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00)); - m_output->Write(AZ_CRC("OnException", 0xfe457d12), message); - m_output->EndTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00)); - } - - //========================================================================= - // OnError - // [2/6/2013] - //========================================================================= - void TraceMessagesDriller::OnError(const char* window, const char* message) - { - m_output->BeginTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00)); - m_output->BeginTag(AZ_CRC("OnError", 0x4993c634)); - m_output->Write(AZ_CRC("Window", 0x8be4f9dd), window); - m_output->Write(AZ_CRC("Message", 0xb6bd307f), message); - m_output->EndTag(AZ_CRC("OnError", 0x4993c634)); - m_output->EndTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00)); - } - - //========================================================================= - // OnWarning - // [2/6/2013] - //========================================================================= - void TraceMessagesDriller::OnWarning(const char* window, const char* message) - { - m_output->BeginTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00)); - m_output->BeginTag(AZ_CRC("OnWarning", 0x7d90abea)); - m_output->Write(AZ_CRC("Window", 0x8be4f9dd), window); - m_output->Write(AZ_CRC("Message", 0xb6bd307f), message); - m_output->EndTag(AZ_CRC("OnWarning", 0x7d90abea)); - m_output->EndTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00)); - } - - //========================================================================= - // OnPrintf - // [2/6/2013] - //========================================================================= - void TraceMessagesDriller::OnPrintf(const char* window, const char* message) - { - m_output->BeginTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00)); - m_output->BeginTag(AZ_CRC("OnPrintf", 0xd4b5c294)); - m_output->Write(AZ_CRC("Window", 0x8be4f9dd), window); - m_output->Write(AZ_CRC("Message", 0xb6bd307f), message); - m_output->EndTag(AZ_CRC("OnPrintf", 0xd4b5c294)); - m_output->EndTag(AZ_CRC("TraceMessagesDriller", 0xa61d1b00)); - } - } // namespace Debug -} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Debug/TraceMessagesDriller.h b/Code/Framework/AzCore/AzCore/Debug/TraceMessagesDriller.h deleted file mode 100644 index 32726931fc..0000000000 --- a/Code/Framework/AzCore/AzCore/Debug/TraceMessagesDriller.h +++ /dev/null @@ -1,49 +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 -#include - -namespace AZ -{ - namespace Debug - { - /** - * Trace messages driller class - */ - class TraceMessagesDriller - : public Driller - , public TraceMessageDrillerBus::Handler - { - public: - AZ_CLASS_ALLOCATOR(TraceMessagesDriller, OSAllocator, 0) - - protected: - ////////////////////////////////////////////////////////////////////////// - // Driller - const char* GroupName() const override { return "SystemDrillers"; } - const char* GetName() const override { return "TraceMessagesDriller"; } - const char* GetDescription() const override { return "Handles all system messages like Assert, Exception, Error, Warning, Printf, etc."; } - void Start(const Param* params = NULL, int numParams = 0) override; - void Stop() override; - ////////////////////////////////////////////////////////////////////////// - - ////////////////////////////////////////////////////////////////////////// - // TraceMessagesDrillerBus - /// Triggered when a AZ_Assert failed. This is terminating event! (the code will break, crash). - void OnAssert(const char* message) override; - void OnException(const char* message) override; - void OnError(const char* window, const char* message) override; - void OnWarning(const char* window, const char* message) override; - void OnPrintf(const char* window, const char* message) override; - ////////////////////////////////////////////////////////////////////////// - }; - } // namespace Debug -} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Debug/TraceMessagesDrillerBus.h b/Code/Framework/AzCore/AzCore/Debug/TraceMessagesDrillerBus.h deleted file mode 100644 index 6230f4af21..0000000000 --- a/Code/Framework/AzCore/AzCore/Debug/TraceMessagesDrillerBus.h +++ /dev/null @@ -1,53 +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 - -namespace AZ -{ - namespace Debug - { - /** - * Trace messages event handle. - * All messages are optional (they have default implementation) and you can handle only one at a time. - * Driller messages are similar to TraceMessages, but do not provide a return value, - * as we only care about collecting driller messages, not operating on them. - * - * We use a driller bus so all messages are sending in exclusive matter no other driller messages - * can be triggered at that moment, so we already preserve the calling order. You can assume - * all access code in the driller framework in guarded. You can manually lock the driller mutex are you - * use by using \ref AZ::Debug::DrillerEBusMutex. - */ - class TraceMessageDrillerEvents - : public DrillerEBusTraits - { - public: - virtual ~TraceMessageDrillerEvents() {} - - /// Triggered when a AZ_Assert failed. This is terminating event! (the code will break, crash). - virtual void OnPreAssert(const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) {} - virtual void OnAssert(const char* /*message*/) {} - virtual void OnException(const char* /*message*/) {} - virtual void OnPreError(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) {} - virtual void OnError(const char* /*window*/, const char* /*message*/) {} - virtual void OnPreWarning(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) {} - virtual void OnWarning(const char* /*window*/, const char* /*message*/) {} - virtual void OnPrintf(const char* /*window*/, const char* /*message*/) {} - /** - * All trace functions you output to anything. So if you want to handle all the output this is the place. - * You are not given the choice to disable the system output as if you listen at that level you can't make - * that decision. Otherwise we can trigger an assert without even one line of message send to the console/debugger. - */ - virtual void OnOutput(const char* /*window*/, const char* /*message*/) {} - }; - - typedef AZ::EBus TraceMessageDrillerBus; - } // namespace Debug -} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Debug/TraceReflection.cpp b/Code/Framework/AzCore/AzCore/Debug/TraceReflection.cpp index debbea5235..329a709994 100644 --- a/Code/Framework/AzCore/AzCore/Debug/TraceReflection.cpp +++ b/Code/Framework/AzCore/AzCore/Debug/TraceReflection.cpp @@ -12,283 +12,280 @@ #include #include -namespace AZ +namespace AZ::Debug { - namespace Debug + //! Trace Message Event Handler for Automation. + //! Since TraceMessageBus will be called from multiple threads and + //! python interpreter is single threaded, all the bus calls are + //! queued into a list and called at the end of the frame in the main thread. + //! @note this class is not using the usual AZ_EBUS_BEHAVIOR_BINDER + //! macro as the signature needs to be changed to connect to Tick bus. + class TraceMessageBusHandler + : public AZ::Debug::TraceMessageBus::Handler + , public AZ::BehaviorEBusHandler + , public AZ::TickBus::Handler { - //! Trace Message Event Handler for Automation. - //! Since TraceMessageBus will be called from multiple threads and - //! python interpreter is single threaded, all the bus calls are - //! queued into a list and called at the end of the frame in the main thread. - //! @note this class is not using the usual AZ_EBUS_BEHAVIOR_BINDER - //! macro as the signature needs to be changed to connect to Tick bus. - class TraceMessageBusHandler - : public AZ::Debug::TraceMessageBus::Handler - , public AZ::BehaviorEBusHandler - , public AZ::TickBus::Handler + public: + AZ_CLASS_ALLOCATOR(TraceMessageBusHandler, AZ::SystemAllocator, 0); + AZ_RTTI(TraceMessageBusHandler, "{5CDBAF09-5EB0-48AC-B327-2AF8601BB550}", AZ::BehaviorEBusHandler); + + TraceMessageBusHandler(); + + using EventFunctionsParameterPack = AZStd::Internal::pack_traits_arg_sequence< + decltype(&TraceMessageBusHandler::OnPreAssert), + decltype(&TraceMessageBusHandler::OnPreError), + decltype(&TraceMessageBusHandler::OnPreWarning), + decltype(&TraceMessageBusHandler::OnAssert), + decltype(&TraceMessageBusHandler::OnError), + decltype(&TraceMessageBusHandler::OnWarning), + decltype(&TraceMessageBusHandler::OnException), + decltype(&TraceMessageBusHandler::OnPrintf), + decltype(&TraceMessageBusHandler::OnOutput) + >; + + enum { - public: - AZ_CLASS_ALLOCATOR(TraceMessageBusHandler, AZ::SystemAllocator, 0); - AZ_RTTI(TraceMessageBusHandler, "{5CDBAF09-5EB0-48AC-B327-2AF8601BB550}", AZ::BehaviorEBusHandler); - - TraceMessageBusHandler(); - - using EventFunctionsParameterPack = AZStd::Internal::pack_traits_arg_sequence< - decltype(&TraceMessageBusHandler::OnPreAssert), - decltype(&TraceMessageBusHandler::OnPreError), - decltype(&TraceMessageBusHandler::OnPreWarning), - decltype(&TraceMessageBusHandler::OnAssert), - decltype(&TraceMessageBusHandler::OnError), - decltype(&TraceMessageBusHandler::OnWarning), - decltype(&TraceMessageBusHandler::OnException), - decltype(&TraceMessageBusHandler::OnPrintf), - decltype(&TraceMessageBusHandler::OnOutput) - >; - - enum - { - FN_OnPreAssert = 0, - FN_OnPreError, - FN_OnPreWarning, - FN_OnAssert, - FN_OnError, - FN_OnWarning, - FN_OnException, - FN_OnPrintf, - FN_OnOutput, - FN_MAX - }; - - static inline constexpr const char* m_functionNames[FN_MAX] = - { - "OnPreAssert", - "OnPreError", - "OnPreWarning", - "OnAssert", - "OnError", - "OnWarning", - "OnException", - "OnPrintf", - "OnOutput" - }; - - // AZ::BehaviorEBusHandler overrides... - int GetFunctionIndex(const char* functionName) const override; - void Disconnect() override; - bool Connect(AZ::BehaviorValueParameter* id = nullptr) override; - bool IsConnected() override; - bool IsConnectedId(AZ::BehaviorValueParameter* id) override; - - // TraceMessageBus - /* - * Note: Since at editor runtime there is already have a handler, for automation (OnPreAssert, OnPreWarning, OnPreWarning) - * must be used instead of (OnAssert, OnWarning, OnError) - */ - bool OnPreAssert(const char* fileName, int line, const char* func, const char* message) override; - bool OnPreError(const char* window, const char* fileName, int line, const char* func, const char* message) override; - bool OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message) override; - bool OnAssert(const char* message) override; - bool OnError(const char* window, const char* message) override; - bool OnWarning(const char* window, const char* message) override; - bool OnException(const char* message) override; - bool OnPrintf(const char* window, const char* message) override; - bool OnOutput(const char* window, const char* message) override; - - // AZ::TickBus::Handler overrides ... - void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; - int GetTickOrder() override; - - private: - void QueueMessageCall(AZStd::function messageCall); - void FlushMessageCalls(); - - AZStd::list> m_messageCalls; - AZStd::mutex m_messageCallsLock; + FN_OnPreAssert = 0, + FN_OnPreError, + FN_OnPreWarning, + FN_OnAssert, + FN_OnError, + FN_OnWarning, + FN_OnException, + FN_OnPrintf, + FN_OnOutput, + FN_MAX }; - TraceMessageBusHandler::TraceMessageBusHandler() + static inline constexpr const char* m_functionNames[FN_MAX] = { - m_events.resize(FN_MAX); + "OnPreAssert", + "OnPreError", + "OnPreWarning", + "OnAssert", + "OnError", + "OnWarning", + "OnException", + "OnPrintf", + "OnOutput" + }; - SetEvent(&TraceMessageBusHandler::OnPreAssert, m_functionNames[FN_OnPreAssert]); - SetEvent(&TraceMessageBusHandler::OnPreError, m_functionNames[FN_OnPreError]); - SetEvent(&TraceMessageBusHandler::OnPreWarning, m_functionNames[FN_OnPreWarning]); - SetEvent(&TraceMessageBusHandler::OnAssert, m_functionNames[FN_OnAssert]); - SetEvent(&TraceMessageBusHandler::OnError, m_functionNames[FN_OnError]); - SetEvent(&TraceMessageBusHandler::OnWarning, m_functionNames[FN_OnWarning]); - SetEvent(&TraceMessageBusHandler::OnException, m_functionNames[FN_OnException]); - SetEvent(&TraceMessageBusHandler::OnPrintf, m_functionNames[FN_OnPrintf]); - SetEvent(&TraceMessageBusHandler::OnOutput, m_functionNames[FN_OnOutput]); - } + // AZ::BehaviorEBusHandler overrides... + int GetFunctionIndex(const char* functionName) const override; + void Disconnect() override; + bool Connect(AZ::BehaviorValueParameter* id = nullptr) override; + bool IsConnected() override; + bool IsConnectedId(AZ::BehaviorValueParameter* id) override; - int TraceMessageBusHandler::GetFunctionIndex(const char* functionName) const + // TraceMessageBus + /* + * Note: Since at editor runtime there is already have a handler, for automation (OnPreAssert, OnPreWarning, OnPreWarning) + * must be used instead of (OnAssert, OnWarning, OnError) + */ + bool OnPreAssert(const char* fileName, int line, const char* func, const char* message) override; + bool OnPreError(const char* window, const char* fileName, int line, const char* func, const char* message) override; + bool OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message) override; + bool OnAssert(const char* message) override; + bool OnError(const char* window, const char* message) override; + bool OnWarning(const char* window, const char* message) override; + bool OnException(const char* message) override; + bool OnPrintf(const char* window, const char* message) override; + bool OnOutput(const char* window, const char* message) override; + + // AZ::TickBus::Handler overrides ... + void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; + int GetTickOrder() override; + + private: + void QueueMessageCall(AZStd::function messageCall); + void FlushMessageCalls(); + + AZStd::list> m_messageCalls; + AZStd::mutex m_messageCallsLock; + }; + + TraceMessageBusHandler::TraceMessageBusHandler() + { + m_events.resize(FN_MAX); + + SetEvent(&TraceMessageBusHandler::OnPreAssert, m_functionNames[FN_OnPreAssert]); + SetEvent(&TraceMessageBusHandler::OnPreError, m_functionNames[FN_OnPreError]); + SetEvent(&TraceMessageBusHandler::OnPreWarning, m_functionNames[FN_OnPreWarning]); + SetEvent(&TraceMessageBusHandler::OnAssert, m_functionNames[FN_OnAssert]); + SetEvent(&TraceMessageBusHandler::OnError, m_functionNames[FN_OnError]); + SetEvent(&TraceMessageBusHandler::OnWarning, m_functionNames[FN_OnWarning]); + SetEvent(&TraceMessageBusHandler::OnException, m_functionNames[FN_OnException]); + SetEvent(&TraceMessageBusHandler::OnPrintf, m_functionNames[FN_OnPrintf]); + SetEvent(&TraceMessageBusHandler::OnOutput, m_functionNames[FN_OnOutput]); + } + + int TraceMessageBusHandler::GetFunctionIndex(const char* functionName) const + { + for (int i = 0; i < FN_MAX; ++i) { - for (int i = 0; i < FN_MAX; ++i) + if (azstricmp(functionName, m_functionNames[i]) == 0) { - if (azstricmp(functionName, m_functionNames[i]) == 0) - { - return i; - } + return i; } - return -1; } + return -1; + } - void TraceMessageBusHandler::Disconnect() + void TraceMessageBusHandler::Disconnect() + { + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + AZ::TickBus::Handler::BusDisconnect(); + } + + bool TraceMessageBusHandler::Connect(AZ::BehaviorValueParameter* id) + { + AZ::TickBus::Handler::BusConnect(); + return AZ::Internal::EBusConnector::Connect(this, id); + } + + bool TraceMessageBusHandler::IsConnected() + { + return AZ::Internal::EBusConnector::IsConnected(this); + } + + bool TraceMessageBusHandler::IsConnectedId(AZ::BehaviorValueParameter* id) + { + return AZ::Internal::EBusConnector::IsConnectedId(this, id); + } + + ////////////////////////////////////////////////////////////////////////// + // TraceMessageBusHandler Implementation + inline bool TraceMessageBusHandler::OnPreAssert(const char* fileName, int line, const char* func, const char* message) + { + QueueMessageCall( + [this, fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]() { - AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); - AZ::TickBus::Handler::BusDisconnect(); - } + Call(FN_OnPreAssert, fileNameString.c_str(), line, funcString.c_str(), messageString.c_str()); + }); + return false; + } - bool TraceMessageBusHandler::Connect(AZ::BehaviorValueParameter* id) + inline bool TraceMessageBusHandler::OnPreError(const char* window, const char* fileName, int line, const char* func, const char* message) + { + QueueMessageCall( + [this, windowString = AZStd::string(window), fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]() { - AZ::TickBus::Handler::BusConnect(); - return AZ::Internal::EBusConnector::Connect(this, id); - } + Call(FN_OnPreError, windowString.c_str(), fileNameString.c_str(), line, funcString.c_str(), messageString.c_str()); + }); + return false; + } - bool TraceMessageBusHandler::IsConnected() + inline bool TraceMessageBusHandler::OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message) + { + QueueMessageCall( + [this, windowString = AZStd::string(window), fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]() { - return AZ::Internal::EBusConnector::IsConnected(this); - } + return Call(FN_OnPreWarning, windowString.c_str(), fileNameString.c_str(), line, funcString.c_str(), messageString.c_str()); + }); + return false; + } - bool TraceMessageBusHandler::IsConnectedId(AZ::BehaviorValueParameter* id) + inline bool TraceMessageBusHandler::OnAssert(const char* message) + { + QueueMessageCall( + [this, messageString = AZStd::string(message)]() { - return AZ::Internal::EBusConnector::IsConnectedId(this, id); - } + return Call(FN_OnAssert, messageString.c_str()); + }); + return false; + } - ////////////////////////////////////////////////////////////////////////// - // TraceMessageBusHandler Implementation - inline bool TraceMessageBusHandler::OnPreAssert(const char* fileName, int line, const char* func, const char* message) + inline bool TraceMessageBusHandler::OnError(const char* window, const char* message) + { + QueueMessageCall( + [this, windowString = AZStd::string(window), messageString = AZStd::string(message)]() { - QueueMessageCall( - [this, fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]() - { - Call(FN_OnPreAssert, fileNameString.c_str(), line, funcString.c_str(), messageString.c_str()); - }); - return false; - } + return Call(FN_OnError, windowString.c_str(), messageString.c_str()); + }); + return false; + } - inline bool TraceMessageBusHandler::OnPreError(const char* window, const char* fileName, int line, const char* func, const char* message) + inline bool TraceMessageBusHandler::OnWarning(const char* window, const char* message) + { + QueueMessageCall( + [this, windowString = AZStd::string(window), messageString = AZStd::string(message)]() { - QueueMessageCall( - [this, windowString = AZStd::string(window), fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]() - { - Call(FN_OnPreError, windowString.c_str(), fileNameString.c_str(), line, funcString.c_str(), messageString.c_str()); - }); - return false; - } + return Call(FN_OnWarning, windowString.c_str(), messageString.c_str()); + }); + return false; + } - inline bool TraceMessageBusHandler::OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message) + inline bool TraceMessageBusHandler::OnException(const char* message) + { + QueueMessageCall( + [this, messageString = AZStd::string(message)]() { - QueueMessageCall( - [this, windowString = AZStd::string(window), fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]() - { - return Call(FN_OnPreWarning, windowString.c_str(), fileNameString.c_str(), line, funcString.c_str(), messageString.c_str()); - }); - return false; - } + return Call(FN_OnException, messageString.c_str()); + }); + return false; + } - inline bool TraceMessageBusHandler::OnAssert(const char* message) + inline bool TraceMessageBusHandler::OnPrintf(const char* window, const char* message) + { + QueueMessageCall( + [this, windowString = AZStd::string(window), messageString = AZStd::string(message)]() { - QueueMessageCall( - [this, messageString = AZStd::string(message)]() - { - return Call(FN_OnAssert, messageString.c_str()); - }); - return false; - } + return Call(FN_OnPrintf, windowString.c_str(), messageString.c_str()); + }); + return false; + } - inline bool TraceMessageBusHandler::OnError(const char* window, const char* message) + inline bool TraceMessageBusHandler::OnOutput(const char* window, const char* message) + { + QueueMessageCall( + [this, windowString = AZStd::string(window), messageString = AZStd::string(message)]() { - QueueMessageCall( - [this, windowString = AZStd::string(window), messageString = AZStd::string(message)]() - { - return Call(FN_OnError, windowString.c_str(), messageString.c_str()); - }); - return false; - } + return Call(FN_OnOutput, windowString.c_str(), messageString.c_str()); + }); + return false; + } - inline bool TraceMessageBusHandler::OnWarning(const char* window, const char* message) - { - QueueMessageCall( - [this, windowString = AZStd::string(window), messageString = AZStd::string(message)]() - { - return Call(FN_OnWarning, windowString.c_str(), messageString.c_str()); - }); - return false; - } + void TraceMessageBusHandler::OnTick( + [[maybe_unused]] float deltaTime, + [[maybe_unused]] AZ::ScriptTimePoint time) + { + FlushMessageCalls(); + } - inline bool TraceMessageBusHandler::OnException(const char* message) - { - QueueMessageCall( - [this, messageString = AZStd::string(message)]() - { - return Call(FN_OnException, messageString.c_str()); - }); - return false; - } + int TraceMessageBusHandler::GetTickOrder() + { + return AZ::TICK_LAST; + } - inline bool TraceMessageBusHandler::OnPrintf(const char* window, const char* message) - { - QueueMessageCall( - [this, windowString = AZStd::string(window), messageString = AZStd::string(message)]() - { - return Call(FN_OnPrintf, windowString.c_str(), messageString.c_str()); - }); - return false; - } + void TraceMessageBusHandler::QueueMessageCall(AZStd::function messageCall) + { + AZStd::lock_guard lock(m_messageCallsLock); + m_messageCalls.emplace_back(messageCall); + } - inline bool TraceMessageBusHandler::OnOutput(const char* window, const char* message) - { - QueueMessageCall( - [this, windowString = AZStd::string(window), messageString = AZStd::string(message)]() - { - return Call(FN_OnOutput, windowString.c_str(), messageString.c_str()); - }); - return false; - } - - void TraceMessageBusHandler::OnTick( - [[maybe_unused]] float deltaTime, - [[maybe_unused]] AZ::ScriptTimePoint time) - { - FlushMessageCalls(); - } - - int TraceMessageBusHandler::GetTickOrder() - { - return AZ::TICK_LAST; - } - - void TraceMessageBusHandler::QueueMessageCall(AZStd::function messageCall) + void TraceMessageBusHandler::FlushMessageCalls() + { + AZStd::list> messageCalls; { AZStd::lock_guard lock(m_messageCallsLock); - m_messageCalls.push_back(messageCall); + m_messageCalls.swap(messageCalls); // Move calls to a new list to release the lock as soon as possible } - void TraceMessageBusHandler::FlushMessageCalls() + for (auto& messageCall : messageCalls) { - AZStd::list> messageCalls; - { - AZStd::lock_guard lock(m_messageCallsLock); - m_messageCalls.swap(messageCalls); // Move calls to a new list to release the lock as soon as possible - } - - for (auto& messageCall : messageCalls) - { - messageCall(); - } - } - - void TraceReflect(ReflectContext* context) - { - if (BehaviorContext* behaviorContext = azrtti_cast(context)) - { - behaviorContext->EBus("TraceMessageBus") - ->Attribute(AZ::Script::Attributes::Module, "debug") - ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) - ->Handler() - ; - } + messageCall(); } } -} + + void TraceReflect(ReflectContext* context) + { + if (BehaviorContext* behaviorContext = azrtti_cast(context)) + { + behaviorContext->EBus("TraceMessageBus") + ->Attribute(AZ::Script::Attributes::Module, "debug") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation) + ->Handler() + ; + } + } +} // namespace AZ::Debug diff --git a/Code/Framework/AzCore/AzCore/Driller/DefaultStringPool.h b/Code/Framework/AzCore/AzCore/Driller/DefaultStringPool.h deleted file mode 100644 index 630061f089..0000000000 --- a/Code/Framework/AzCore/AzCore/Driller/DefaultStringPool.h +++ /dev/null @@ -1,125 +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 - * - */ -#ifndef AZCORE_DRILLER_DEFAULT_STRING_POOL_H -#define AZCORE_DRILLER_DEFAULT_STRING_POOL_H - -#include - -#include -#include - -namespace AZ -{ - namespace Debug - { - template - struct unordered_map - { - typedef AZStd::unordered_map, AZStd::equal_to, OSStdAllocator> type; - }; - - template - struct unordered_set - { - typedef AZStd::unordered_set, AZStd::equal_to, OSStdAllocator> type; - }; - - /** - * Default implementation of a string pool. - */ - class DrillerDefaultStringPool - : public DrillerStringPool - { - public: - virtual ~DrillerDefaultStringPool() - { - Reset(); - } - - typedef unordered_map::type CrcToStringMapType; - typedef unordered_set::type OwnedStringsMapType; - - /** - * Add a copy of the string to the pool. If we return true the string was added otherwise it was already in the bool. - * In both cases the crc32 of the string and the pointer to the shared copy is returned (optional for poolStringAddress)! - */ - virtual bool InsertCopy(const char* string, unsigned int length, AZ::u32& crc32, const char** poolStringAddress = nullptr) - { - crc32 = AZ::Crc32(string, length); - CrcToStringMapType::pair_iter_bool insertIt = m_crcToStringMap.insert_key(crc32); - if (insertIt.second) - { - char* newString = reinterpret_cast(azmalloc(length + 1, 1, AZ::OSAllocator)); - memcpy(newString, string, length); - newString[length] = '\0'; // terminate - m_ownedStrings.insert(newString); - insertIt.first->second = newString; - } - if (poolStringAddress) - { - *poolStringAddress = insertIt.first->second; - } - return insertIt.second; - } - - /** - * Same as the InsertCopy above without actually coping the string into the pool. The pool assumes that - * none of the strings added to the pool will be deleted. - */ - virtual bool Insert(const char* string, unsigned int length, AZ::u32& crc32) - { - crc32 = AZ::Crc32(string, length); - return m_crcToStringMap.insert(AZStd::make_pair(crc32, string)).second; - } - - /// Finds a string in the pool by crc32. - virtual const char* Find(AZ::u32 crc32) - { - CrcToStringMapType::iterator it = m_crcToStringMap.find(crc32); - if (it != m_crcToStringMap.end()) - { - return it->second; - } - return NULL; - } - - virtual void Erase(AZ::u32 crc32) - { - CrcToStringMapType::iterator it = m_crcToStringMap.find(crc32); - if (it != m_crcToStringMap.end()) - { - OwnedStringsMapType::iterator ownerIt = m_ownedStrings.find(it->second); - if (ownerIt != m_ownedStrings.end()) - { - azfree(const_cast(it->second), AZ::OSAllocator); - m_ownedStrings.erase(ownerIt); - } - m_crcToStringMap.erase(it); - } - } - - virtual void Reset() - { - for (OwnedStringsMapType::iterator it = m_ownedStrings.begin(); it != m_ownedStrings.end(); ++it) - { - azfree(const_cast(*it), AZ::OSAllocator); - } - m_crcToStringMap.clear(); - m_ownedStrings.clear(); - } - protected: - CrcToStringMapType m_crcToStringMap; - OwnedStringsMapType m_ownedStrings; - }; - } // namespace Debug -} // namespace AZ - -#endif // AZCORE_DRILLER_DEFAULT_STRING_POOL_H -#pragma once - - diff --git a/Code/Framework/AzCore/AzCore/Driller/Driller.cpp b/Code/Framework/AzCore/AzCore/Driller/Driller.cpp deleted file mode 100644 index 41abd7793e..0000000000 --- a/Code/Framework/AzCore/AzCore/Driller/Driller.cpp +++ /dev/null @@ -1,304 +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 -#include - -#include -#include -#include - - -namespace AZ -{ - namespace Debug - { - class DrillerManagerImpl - : public DrillerManager - { - public: - AZ_CLASS_ALLOCATOR(DrillerManagerImpl, OSAllocator, 0); - - typedef forward_list::type SessionListType; - SessionListType m_sessions; - typedef vector::type DrillerArrayType; - DrillerArrayType m_drillers; - - ~DrillerManagerImpl() override; - - void Register(Driller* factory) override; - void Unregister(Driller* factory) override; - - void FrameUpdate() override; - - DrillerSession* Start(DrillerOutputStream& output, const DrillerListType& drillerList, int numFrames = -1) override; - void Stop(DrillerSession* session) override; - - int GetNumDrillers() const override { return static_cast(m_drillers.size()); } - Driller* GetDriller(int index) override { return m_drillers[index]; } - }; - - ////////////////////////////////////////////////////////////////////////// - // Driller - - //========================================================================= - // Register - // [3/17/2011] - //========================================================================= - AZ::u32 Driller::GetId() const - { - return AZ::Crc32(GetName()); - } - - ////////////////////////////////////////////////////////////////////////// - - ////////////////////////////////////////////////////////////////////////// - // Driller Manager - - //========================================================================= - // Register - // [3/17/2011] - //========================================================================= - DrillerManager* DrillerManager::Create(/*const Descriptor& desc*/) - { - const bool createAllocator = !AZ::AllocatorInstance::IsReady(); - if (createAllocator) - { - AZ::AllocatorInstance::Create(); - } - - DrillerManagerImpl* impl = aznew DrillerManagerImpl; - impl->m_ownsOSAllocator = createAllocator; - return impl; - } - - //========================================================================= - // Register - // [3/17/2011] - //========================================================================= - void DrillerManager::Destroy(DrillerManager* manager) - { - const bool allocatorCreated = manager->m_ownsOSAllocator; - delete manager; - if (allocatorCreated) - { - AZ::AllocatorInstance::Destroy(); - } - } - - ////////////////////////////////////////////////////////////////////////// - - ////////////////////////////////////////////////////////////////////////// - // DrillerManagerImpl - - //========================================================================= - // ~DrillerManagerImpl - // [3/17/2011] - //========================================================================= - DrillerManagerImpl::~DrillerManagerImpl() - { - while (!m_sessions.empty()) - { - Stop(&m_sessions.front()); - } - - while (!m_drillers.empty()) - { - Driller* driller = m_drillers[0]; - Unregister(driller); - delete driller; - } - } - - //========================================================================= - // Register - // [3/17/2011] - //========================================================================= - void - DrillerManagerImpl::Register(Driller* driller) - { - AZ_Assert(driller, "You must provide a valid factory!"); - for (size_t i = 0; i < m_drillers.size(); ++i) - { - if (m_drillers[i]->GetId() == driller->GetId()) - { - AZ_Error("Debug", false, "Driller with id %08x has already been registered! You can't have two factory instances for the same driller type", driller->GetId()); - return; - } - } - m_drillers.push_back(driller); - } - - //========================================================================= - // Unregister - // [3/17/2011] - //========================================================================= - void - DrillerManagerImpl::Unregister(Driller* driller) - { - AZ_Assert(driller, "You must provide a valid factory!"); - for (DrillerArrayType::iterator iter = m_drillers.begin(); iter != m_drillers.end(); ++iter) - { - if ((*iter)->GetId() == driller->GetId()) - { - m_drillers.erase(iter); - return; - } - } - - AZ_Error("Debug", false, "Failed to find driller factory with id %08x", driller->GetId()); - } - - //========================================================================= - // FrameUpdate - // [3/17/2011] - //========================================================================= - void - DrillerManagerImpl::FrameUpdate() - { - if (m_sessions.empty()) - { - return; - } - - AZStd::lock_guard lock(DrillerEBusMutex::GetMutex()); ///< Make sure no driller is writing to the stream - for (SessionListType::iterator sessionIter = m_sessions.begin(); sessionIter != m_sessions.end(); ) - { - DrillerSession& s = *sessionIter; - - // tick the drillers directly if they care. - for (size_t i = 0; i < s.drillers.size(); ++i) - { - s.drillers[i]->Update(); - } - - s.output->EndTag(AZ_CRC("Frame", 0xb5f83ccd)); - - s.output->OnEndOfFrame(); - - s.curFrame++; - - if (s.numFrames != -1) - { - if (s.curFrame == s.numFrames) - { - Stop(&s); - continue; - } - } - - s.output->BeginTag(AZ_CRC("Frame", 0xb5f83ccd)); - s.output->Write(AZ_CRC("FrameNum", 0x85a1a919), s.curFrame); - - ++sessionIter; - } - } - - //========================================================================= - // Start - // [3/17/2011] - //========================================================================= - DrillerSession* - DrillerManagerImpl::Start(DrillerOutputStream& output, const DrillerListType& drillerList, int numFrames) - { - if (drillerList.empty()) - { - return nullptr; - } - - m_sessions.push_back(); - DrillerSession& s = m_sessions.back(); - s.curFrame = 0; - s.numFrames = numFrames; - s.output = &output; - - s.output->WriteHeader(); // first write the header in the stream - - s.output->BeginTag(AZ_CRC("StartData", 0xecf3f53f)); - s.output->Write(AZ_CRC("Platform", 0x3952d0cb), (unsigned int)g_currentPlatform); - for (DrillerListType::const_iterator iDriller = drillerList.begin(); iDriller != drillerList.end(); ++iDriller) - { - const DrillerInfo& di = *iDriller; - s.output->BeginTag(AZ_CRC("Driller", 0xa6e1fb73)); - s.output->Write(AZ_CRC("Name", 0x5e237e06), di.id); - for (int iParam = 0; iParam < (int)di.params.size(); ++iParam) - { - s.output->BeginTag(AZ_CRC("Param", 0xa4fa7c89)); - s.output->Write(AZ_CRC("Name", 0x5e237e06), di.params[iParam].name); - s.output->Write(AZ_CRC("Description", 0x6de44026), di.params[iParam].desc); - s.output->Write(AZ_CRC("Type", 0x8cde5729), di.params[iParam].type); - s.output->Write(AZ_CRC("Value", 0x1d775834), di.params[iParam].value); - s.output->EndTag(AZ_CRC("Param", 0xa4fa7c89)); - } - s.output->EndTag(AZ_CRC("Driller", 0xa6e1fb73)); - } - s.output->EndTag(AZ_CRC("StartData", 0xecf3f53f)); - - s.output->BeginTag(AZ_CRC("Frame", 0xb5f83ccd)); - s.output->Write(AZ_CRC("FrameNum", 0x85a1a919), s.curFrame); - - { - AZStd::lock_guard lock(DrillerEBusMutex::GetMutex()); ///< Make sure no driller is writing to the stream - for (DrillerListType::const_iterator iDriller = drillerList.begin(); iDriller != drillerList.end(); ++iDriller) - { - Driller* driller = nullptr; - const DrillerInfo& di = *iDriller; - for (size_t iDesc = 0; iDesc < m_drillers.size(); ++iDesc) - { - if (m_drillers[iDesc]->GetId() == di.id) - { - driller = m_drillers[iDesc]; - AZ_Assert(driller->m_output == nullptr, "Driller with id %08x is already have an output stream %p (currently we support only 1 at a time)", di.id, driller->m_output); - driller->m_output = &output; - driller->Start(di.params.data(), static_cast(di.params.size())); - s.drillers.push_back(driller); - break; - } - } - AZ_Warning("Driller", driller != nullptr, "We can't start a driller with id %d!", di.id); - } - } - return &s; - } - - - //========================================================================= - // Stop - // [3/17/2011] - //========================================================================= - void - DrillerManagerImpl::Stop(DrillerSession* session) - { - SessionListType::iterator iter; - for (iter = m_sessions.begin(); iter != m_sessions.end(); ++iter) - { - if (&*iter == session) - { - break; - } - } - - AZ_Assert(iter != m_sessions.end(), "We did not find session ID 0x%08x in the list!", session); - if (iter != m_sessions.end()) - { - DrillerSession& s = *session; - - { - AZStd::lock_guard lock(DrillerEBusMutex::GetMutex()); - for (size_t i = 0; i < s.drillers.size(); ++i) - { - s.drillers[i]->Stop(); - s.drillers[i]->m_output = nullptr; - } - } - s.output->EndTag(AZ_CRC("Frame", 0xb5f83ccd)); - m_sessions.erase(iter); - } - } - } // namespace Debug -} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Driller/Driller.h b/Code/Framework/AzCore/AzCore/Driller/Driller.h deleted file mode 100644 index bb2178e51f..0000000000 --- a/Code/Framework/AzCore/AzCore/Driller/Driller.h +++ /dev/null @@ -1,141 +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 - * - */ -#ifndef AZCORE_DRILLER_H -#define AZCORE_DRILLER_H - -#include -namespace AZStd -{ - class mutex; -} -namespace AZ -{ - namespace Debug - { - class DrillerOutputStream; - - /** - * Driller base class. Every driller should inherit from this class. - * When a driller is need to start outputting data - * the DrillerManager will call Driller::Start() so the driller - * can output the initial state for all reported entities. - * The same applies for the Stop. - * Depending on the type of your driller you might choose to collect state - * even before the driller has started. This of course should be a fast as - * possible, as we don't want to burden engine systems and it's highly recommended - * that you use configuration parameters to change that behavior as not all drillers - * are used on a daily basis. - * All drillers should use DebugAllocators (AZ_CLASS_ALLOCATOR(Driller,OSAllocator,0)) - * and they should use 'aznew' to create one, as by default if you don't unregister a - * a driller, the manager will use "delete" to delete them. - * - * IMPORTANT: Driller systems works OUTSIDE engine systems, you should NOT use SystemAllocator or any other engine systems - * as they might be drilled or not available at the moment. - */ - class Driller - { - friend class DrillerManagerImpl; - - public: - struct Param - { - enum Type - { - PT_BOOL, - PT_INT, - PT_FLOAT - }; - const char* desc; - u32 name; - int type; - int value; - }; - - Driller() - : m_output(NULL) {} - virtual ~Driller() {} - - /// Returns the driller ID Crc32 of the name (Crc32(GetName()) - AZ::u32 GetId() const; - /// Driller group name, used only for organizational purpose - virtual const char* GroupName() const = 0; - /// Unique name of the Driller, driller ID is the Crc of the name - virtual const char* GetName() const = 0; - virtual const char* GetDescription() const = 0; - // @{ Managing the list of supported driller parameters. - virtual int GetNumParams() const { return 0; } - virtual const Param* GetParam(int index) const { (void)index; return NULL; } - - protected: - Driller& operator=(const Driller&); - - /// Called by DrillerManager - virtual void Start(const Param* params = NULL, int numParams = 0) { (void)params; (void)numParams; } - /// Called by DrillerManager - virtual void Stop() {} - /// Called every frame by DrillerManger (while the driller is started) - virtual void Update() {} - - DrillerOutputStream* m_output; ///< Session output stream. - }; - - /** - * Stores the information while an active - * driller(s) session is running. - */ - struct DrillerSession - { - int numFrames; - int curFrame; - typedef vector::type DrillerArrayType; - DrillerArrayType drillers; - DrillerOutputStream* output; - }; - - /** - * Driller manager will manage all active driller sessions and driller factories. Generally you will never - * need more than one driller manger. - * IMPORTANT: Driller systems works OUTSIDE engine systems, you should NOT use SystemAllocator or any other engine systems - * as they might be drilled or not available at the moment. - */ - class DrillerManager - { - friend class DrillerRemoteServer; - public: - struct DrillerInfo - { - AZ::u32 id; - vector::type params; - }; - typedef forward_list::type DrillerListType; - - virtual ~DrillerManager() {} - - static DrillerManager* Create(/*const Descriptor& desc*/); - static void Destroy(DrillerManager* manager); - - virtual void Register(Driller* driller) = 0; - virtual void Unregister(Driller* driller) = 0; - - virtual void FrameUpdate() = 0; - - virtual DrillerSession* Start(DrillerOutputStream& output, const DrillerListType& drillerList, int numFrames = -1) = 0; - virtual void Stop(DrillerSession* session) = 0; - - virtual int GetNumDrillers() const = 0; - virtual Driller* GetDriller(int index) = 0; - - private: - // If the manager created the allocator, it should destroy it when it gets destroyed - bool m_ownsOSAllocator = false; - }; - } -} - -#endif // AZCORE_DRILLER_H -#pragma once diff --git a/Code/Framework/AzCore/AzCore/Driller/DrillerBus.cpp b/Code/Framework/AzCore/AzCore/Driller/DrillerBus.cpp deleted file mode 100644 index 163db8a68b..0000000000 --- a/Code/Framework/AzCore/AzCore/Driller/DrillerBus.cpp +++ /dev/null @@ -1,68 +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 - -#include -#include -#include - -namespace AZ -{ - namespace Debug - { - ////////////////////////////////////////////////////////////////////////// - // Globals - // We need to synchronize all driller evens, so we have proper order, and access to the data - // We use a global mutex which should be used for all driller operations. - // The mutex is held in an environment variable so it works across DLLs. - EnvironmentVariable s_drillerGlobalMutex; - ////////////////////////////////////////////////////////////////////////// - - - //========================================================================= - // lock - // [4/11/2011] - //========================================================================= - void DrillerEBusMutex::lock() - { - GetMutex().lock(); - } - - //========================================================================= - // try_lock - // [4/11/2011] - //========================================================================= - bool DrillerEBusMutex::try_lock() - { - return GetMutex().try_lock(); - } - - //========================================================================= - // unlock - // [4/11/2011] - //========================================================================= - void DrillerEBusMutex::unlock() - { - GetMutex().unlock(); - } - - //========================================================================= - // unlock - // [4/11/2011] - //========================================================================= - AZStd::recursive_mutex& DrillerEBusMutex::GetMutex() - { - if (!s_drillerGlobalMutex) - { - s_drillerGlobalMutex = Environment::CreateVariable(AZ_FUNCTION_SIGNATURE); - } - return *s_drillerGlobalMutex; - } - } // namespace Debug -} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Driller/DrillerBus.h b/Code/Framework/AzCore/AzCore/Driller/DrillerBus.h deleted file mode 100644 index 4c625f007d..0000000000 --- a/Code/Framework/AzCore/AzCore/Driller/DrillerBus.h +++ /dev/null @@ -1,52 +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 - * - */ -#ifndef AZCORE_DRILLER_BUS_H -#define AZCORE_DRILLER_BUS_H - -#include -#include -#include - -namespace AZStd -{ - class mutex; -} - -namespace AZ -{ - namespace Debug - { - class DrillerEBusMutex - { - public: - typedef AZStd::recursive_mutex MutexType; - - static MutexType& GetMutex(); - void lock(); - bool try_lock(); - void unlock(); - }; - - /** - * Specialization of the EBusTraits for a driller bus. We make sure - * all allocation are made using DebugAllocation (so no engine systems are involved). - * In addition we make sure all driller buses use the same Mutex to synchronize data across - * threads (so all events came in order all the time), they are still executed in the context of - * the thread. - */ - struct DrillerEBusTraits - : public AZ::EBusTraits - { - typedef DrillerEBusMutex MutexType; - typedef OSStdAllocator AllocatorType; - }; - } -} - -#endif // AZCORE_DRILLER_BUS_H -#pragma once diff --git a/Code/Framework/AzCore/AzCore/Driller/DrillerRootHandler.h b/Code/Framework/AzCore/AzCore/Driller/DrillerRootHandler.h deleted file mode 100644 index 6a12c0cb8b..0000000000 --- a/Code/Framework/AzCore/AzCore/Driller/DrillerRootHandler.h +++ /dev/null @@ -1,170 +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 - * - */ -#ifndef AZCORE_DRILLER_ROOT_HANDLER_H -#define AZCORE_DRILLER_ROOT_HANDLER_H - -#include -#include - -namespace AZ -{ - namespace Debug - { - // Please check DrillerRootHandler class... this is the one for direct use. - - /** - * Handler for the tag. - */ - class DrillerDrillerdataHandler - : public DrillerHandlerParser - { - public: - class ParamHandler - : public DrillerHandlerParser - { - public: - virtual void OnData(const DrillerSAXParser::Data& dataNode) - { - if (dataNode.m_name == AZ_CRC("Name", 0x5e237e06)) - { - dataNode.Read(m_param->name); - } - else if (dataNode.m_name == AZ_CRC("Description", 0x6de44026)) - { - m_param->desc = NULL; // ignored - } - else if (dataNode.m_name == AZ_CRC("Type", 0x8cde5729)) - { - dataNode.Read(m_param->type); - } - else if (dataNode.m_name == AZ_CRC("Value", 0x1d775834)) - { - dataNode.Read(m_param->value); - } - } - Driller::Param* m_param; - }; - - virtual DrillerHandlerParser* OnEnterTag(u32 tagName) - { - if (tagName == AZ_CRC("Param", 0xa4fa7c89)) - { - m_drillerInfo->params.push_back(); - m_paramHandler.m_param = &m_drillerInfo->params.back(); - return &m_paramHandler; - } - return NULL; - } - - virtual void OnData(const DrillerSAXParser::Data& dataNode) - { - if (dataNode.m_name == AZ_CRC("Name", 0x5e237e06)) - { - dataNode.Read(m_drillerInfo->id); - } - } - - DrillerManager::DrillerInfo* m_drillerInfo; - ParamHandler m_paramHandler; - }; - - - /** - * Handler for the tag - */ - class DrillerStartdataHandler - : public DrillerHandlerParser - { - public: - virtual DrillerHandlerParser* OnEnterTag(u32 tagName) - { - if (tagName == AZ_CRC("Driller", 0xa6e1fb73)) - { - m_drillers.push_back(); - m_drillerDataHandler.m_drillerInfo = &m_drillers.back(); - return &m_drillerDataHandler; - } - return NULL; - } - virtual void OnData(const DrillerSAXParser::Data& dataNode) - { - if (dataNode.m_name == AZ_CRC("Platform", 0x3952d0cb)) - { - dataNode.Read(m_platform); - } - } - - unsigned int m_platform; - DrillerManager::DrillerListType m_drillers; - DrillerDrillerdataHandler m_drillerDataHandler; - }; - - /** - * Handler for the tag - */ - template - class FrameHandler - : public DrillerHandlerParser - { - public: - FrameHandler() - : DrillerHandlerParser(DrillerContainer::s_isWarnOnMissingDrillers) - , m_currentFrame(-1) {} - - virtual DrillerHandlerParser* OnEnterTag(u32 tagName) { return m_drillersContainer.FindDrillerHandler(tagName); } - virtual void OnData(const DrillerSAXParser::Data& dataNode) - { - if (dataNode.m_name == AZ_CRC("FrameNum", 0x85a1a919)) - { - dataNode.Read(m_currentFrame); - } - } - DrillerContainer m_drillersContainer; - int m_currentFrame; - }; - - /** - * Use this class a input parameter to DrillerSAXParserHandler::DrillerSAXParserHandler(). It will handle all root level - * tags for a standard driller input stream stream. - * - * DrillerContainer should comply to the following requirements: - * - default constructible - * - has a static const bool s_isWarnOnMissingDrillers member to indicate if you want to - * trigger a warning when a driller is not found in the class. - * - implement a function DrillerHandlerParser* DrillerContainer::FindDrillerHandler(u32 drillerName) - * - */ - template - class DrillerRootHandler - : public DrillerHandlerParser - { - public: - DrillerContainer* GetDrillerContainer() { return m_frameHandler.m_drillersContainer; } - - virtual DrillerHandlerParser* OnEnterTag(u32 tagName) - { - if (tagName == AZ_CRC("StartData", 0xecf3f53f)) - { - return &m_drillerSessionInfo; - } - if (tagName == AZ_CRC("Frame", 0xb5f83ccd)) - { - return &m_frameHandler; - } - return NULL; - } - DrillerStartdataHandler m_drillerSessionInfo; - FrameHandler m_frameHandler; - }; - } // namespace Debug -} // namespace AZ - -#endif // AZCORE_DRILLER_ROOT_HANDLER_H -#pragma once - - diff --git a/Code/Framework/AzCore/AzCore/Driller/Stream.cpp b/Code/Framework/AzCore/AzCore/Driller/Stream.cpp deleted file mode 100644 index 14e983b09c..0000000000 --- a/Code/Framework/AzCore/AzCore/Driller/Stream.cpp +++ /dev/null @@ -1,896 +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 - -#include -#include -#include -#include -#include - -#include - -#if !defined(AZCORE_EXCLUDE_ZLIB) -# define AZ_FILE_STREAM_COMPRESSION -#endif // AZCORE_EXCLUDE_ZLIB - -#if defined(AZ_FILE_STREAM_COMPRESSION) -# include -#endif // AZ_FILE_STREAM_COMPRESSION - -namespace AZ -{ - namespace Debug - { - ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - // Driller output stream - ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - void DrillerOutputStream::Write(u32 name, const AZ::Vector3& v) - { - float data[4]; - unsigned int dataSize = 3 * sizeof(float); - v.StoreToFloat4(data); - StreamEntry de; - de.name = name; - de.sizeAndFlags = dataSize; - WriteBinary(de); - WriteBinary(data, dataSize); - } - void DrillerOutputStream::Write(u32 name, const AZ::Vector4& v) - { - float data[4]; - unsigned int dataSize = 4 * sizeof(float); - v.StoreToFloat4(data); - StreamEntry de; - de.name = name; - de.sizeAndFlags = dataSize; - WriteBinary(de); - WriteBinary(data, dataSize); - } - void DrillerOutputStream::Write(u32 name, const AZ::Aabb& aabb) - { - float data[7]; - unsigned int dataSize = 6 * sizeof(float); - aabb.GetMin().StoreToFloat4(data); - aabb.GetMax().StoreToFloat4(&data[3]); - StreamEntry de; - de.name = name; - de.sizeAndFlags = dataSize; - WriteBinary(de); - WriteBinary(data, dataSize); - } - void DrillerOutputStream::Write(u32 name, const AZ::Obb& obb) - { - float data[10]; - unsigned int dataSize = 10 * sizeof(float); // position (Vector3), rotation (Quaternion) and halfLengths (Vector3) - obb.GetPosition().StoreToFloat3(data); - obb.GetRotation().StoreToFloat4(&data[3]); - obb.GetHalfLengths().StoreToFloat3(&data[7]); - StreamEntry de; - de.name = name; - de.sizeAndFlags = dataSize; - WriteBinary(de); - WriteBinary(data, dataSize); - } - void DrillerOutputStream::Write(u32 name, const AZ::Transform& tm) - { - float data[12]; - unsigned int dataSize = 12 * sizeof(float); - const Matrix3x4 matrix3x4 = Matrix3x4::CreateFromTransform(tm); - matrix3x4.StoreToRowMajorFloat12(data); - StreamEntry de; - de.name = name; - de.sizeAndFlags = dataSize; - WriteBinary(de); - WriteBinary(data, dataSize); - } - void DrillerOutputStream::Write(u32 name, const AZ::Matrix3x3& tm) - { - float data[9]; - unsigned int dataSize = 9 * sizeof(float); - tm.StoreToRowMajorFloat9(data); - StreamEntry de; - de.name = name; - de.sizeAndFlags = dataSize; - WriteBinary(de); - WriteBinary(data, dataSize); - } - void DrillerOutputStream::Write(u32 name, const AZ::Matrix4x4& tm) - { - float data[16]; - unsigned int dataSize = 16 * sizeof(float); - tm.StoreToRowMajorFloat16(data); - StreamEntry de; - de.name = name; - de.sizeAndFlags = dataSize; - WriteBinary(de); - WriteBinary(data, dataSize); - } - void DrillerOutputStream::Write(u32 name, const AZ::Quaternion& tm) - { - float data[4]; - unsigned int dataSize = 4 * sizeof(float); - tm.StoreToFloat4(data); - StreamEntry de; - de.name = name; - de.sizeAndFlags = dataSize; - WriteBinary(de); - WriteBinary(data, dataSize); - } - void DrillerOutputStream::Write(u32 name, const AZ::Plane& plane) - { - Write(name, plane.GetPlaneEquationCoefficients()); - } - void DrillerOutputStream::WriteHeader() - { - StreamHeader sh; // StreamHeader should be endianess independent. - WriteBinary(&sh, sizeof(sh)); - } - - void DrillerOutputStream::WriteTimeUTC(u32 name) - { - AZStd::sys_time_t now = AZStd::GetTimeUTCMilliSecond(); - Write(name, now); - } - - void DrillerOutputStream::WriteTimeMicrosecond(u32 name) - { - AZStd::sys_time_t now = AZStd::GetTimeNowMicroSecond(); - Write(name, now); - } - - ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - // Driller Input Stream - ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - bool DrillerInputStream::ReadHeader() - { - DrillerOutputStream::StreamHeader sh; // StreamHeader should be endianess independent. - unsigned int numRead = ReadBinary(&sh, sizeof(sh)); - (void)numRead; - AZ_Error("IO", numRead == sizeof(sh), "We should have atleast %d bytes in the stream to read the header!", sizeof(sh)); - if (numRead != sizeof(sh)) - { - return false; - } - m_isEndianSwap = AZ::IsBigEndian(static_cast(sh.platform)) != AZ::IsBigEndian(AZ::g_currentPlatform); - return true; - } - - ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - // Driller file stream - ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - //========================================================================= - // DrillerOutputFileStream::DrillerOutputFileStream - // [3/23/2011] - //========================================================================= - DrillerOutputFileStream::DrillerOutputFileStream() - { -#if defined(AZ_FILE_STREAM_COMPRESSION) - m_zlib = azcreate(ZLib, (&AllocatorInstance::GetAllocator()), OSAllocator); - m_zlib->StartCompressor(2); -#endif - } - - //========================================================================= - // DrillerOutputFileStream::~DrillerOutputFileStream - // [3/23/2011] - //========================================================================= - DrillerOutputFileStream::~DrillerOutputFileStream() - { -#if defined(AZ_FILE_STREAM_COMPRESSION) - azdestroy(m_zlib, OSAllocator); -#endif - } - - //========================================================================= - // DrillerOutputFileStream::Open - // [3/23/2011] - //========================================================================= - bool DrillerOutputFileStream::Open(const char* fileName, int mode, int platformFlags) - { - if (IO::SystemFile::Open(fileName, mode, platformFlags)) - { - m_dataBuffer.reserve(100 * 1024); -#if defined(AZ_FILE_STREAM_COMPRESSION) - // // Enable optional: encode the file in the same format as the streamer so they are interchangeable - // IO::CompressorHeader ch; - // ch.SetAZCS(); - // ch.m_compressorId = IO::CompressorZLib::TypeId(); - // ch.m_uncompressedSize = 0; // will be updated later - // AZStd::endian_swap(ch.m_compressorId); - // AZStd::endian_swap(ch.m_uncompressedSize); - // IO::SystemFile::Write(&ch,sizeof(ch)); - // IO::CompressorZLibHeader zlibHdr; - // zlibHdr.m_numSeekPoints = 0; - // IO::SystemFile::Write(&zlibHdr,sizeof(zlibHdr)); -#endif - return true; - } - return false; - } - - //========================================================================= - // DrillerOutputFileStream::Close - // [3/23/2011] - //========================================================================= - void DrillerOutputFileStream::Close() - { - unsigned int dataSizeInBuffer = static_cast(m_dataBuffer.size()); - { -#if defined(AZ_FILE_STREAM_COMPRESSION) - unsigned int minCompressBufferSize = m_zlib->GetMinCompressedBufferSize(dataSizeInBuffer); - if (m_compressionBuffer.size() < minCompressBufferSize) // grow compression buffer if needed - { - m_compressionBuffer.clear(); - m_compressionBuffer.resize(minCompressBufferSize); - } - unsigned int compressedSize; - do - { - compressedSize = m_zlib->Compress(m_dataBuffer.data(), dataSizeInBuffer, m_compressionBuffer.data(), (unsigned)m_compressionBuffer.size(), ZLib::FT_FINISH); - if (compressedSize) - { - IO::SystemFile::Write(m_compressionBuffer.data(), compressedSize); - } - } while (compressedSize > 0); - m_zlib->ResetCompressor(); -#else - if (dataSizeInBuffer) - { - IO::SystemFile::Write(m_dataBuffer.data(), m_dataBuffer.size()); - } -#endif - m_dataBuffer.clear(); - } - IO::SystemFile::Close(); - } - //========================================================================= - // DrillerOutputFileStream::WriteBinary - // [3/23/2011] - //========================================================================= - void DrillerOutputFileStream::WriteBinary(const void* data, unsigned int dataSize) - { - size_t dataSizeInBuffer = m_dataBuffer.size(); - if (dataSizeInBuffer + dataSize > m_dataBuffer.capacity()) - { - if (dataSizeInBuffer > 0) - { -#if defined(AZ_FILE_STREAM_COMPRESSION) - // we need to flush the data - unsigned int dataToCompress = static_cast(dataSizeInBuffer); - unsigned int minCompressBufferSize = m_zlib->GetMinCompressedBufferSize(dataToCompress); - if (m_compressionBuffer.size() < minCompressBufferSize) // grow compression buffer if needed - { - m_compressionBuffer.clear(); - m_compressionBuffer.resize(minCompressBufferSize); - } - while (dataToCompress > 0) - { - unsigned int compressedSize = m_zlib->Compress(m_dataBuffer.data(), dataToCompress, m_compressionBuffer.data(), (unsigned)m_compressionBuffer.size()); - if (compressedSize) - { - IO::SystemFile::Write(m_compressionBuffer.data(), compressedSize); - } - } -#else - IO::SystemFile::Write(m_dataBuffer.data(), m_dataBuffer.size()); -#endif - m_dataBuffer.clear(); - } - } - m_dataBuffer.insert(m_dataBuffer.end(), reinterpret_cast(data), reinterpret_cast(data) + dataSize); - } - - ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - // Driller file input stream - ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - - //========================================================================= - // DrillerInputFileStream::DrillerInputFileStream - // [3/23/2011] - //========================================================================= - DrillerInputFileStream::DrillerInputFileStream() - { -#if defined(AZ_FILE_STREAM_COMPRESSION) - m_zlib = azcreate(ZLib, (&AllocatorInstance::GetAllocator()), OSAllocator); - m_zlib->StartDecompressor(); -#endif - } - - //========================================================================= - // DrillerInputFileStream::DrillerInputFileStream - // [3/23/2011] - //========================================================================= - DrillerInputFileStream::~DrillerInputFileStream() - { -#if defined(AZ_FILE_STREAM_COMPRESSION) - azdestroy(m_zlib, OSAllocator); -#endif - } - - //========================================================================= - // DrillerInputFileStream::Open - // [3/23/2011] - //========================================================================= - bool DrillerInputFileStream::Open(const char* fileName, int mode, int platformFlags) - { - if (IO::SystemFile::Open(fileName, mode, platformFlags)) - { - DrillerOutputStream::StreamHeader sh; -#if defined(AZ_FILE_STREAM_COMPRESSION) - // TODO: optional encode the file in the same format as the streamer so they are interchangeable -#endif - // first read the header of the stream file. - return ReadHeader(); - } - return false; - } - //========================================================================= - // DrillerInputFileStream::ReadBinary - // [3/23/2011] - //========================================================================= - unsigned int DrillerInputFileStream::ReadBinary(void* data, unsigned int maxDataSize) - { - // make sure the compressed buffer if full enough... - size_t dataToLoad = maxDataSize * 2; - m_compressedData.reserve(dataToLoad); - while (m_compressedData.size() < dataToLoad) - { - unsigned char buffer[10 * 1024]; - IO::SystemFile::SizeType bytesRead = Read(AZ_ARRAY_SIZE(buffer), buffer); - if (bytesRead > 0) - { - m_compressedData.insert(m_compressedData.end(), (unsigned char*)buffer, buffer + bytesRead); - } - if (bytesRead < AZ_ARRAY_SIZE(buffer)) - { - break; - } - } -#if defined(AZ_FILE_STREAM_COMPRESSION) - unsigned int dataSize = maxDataSize; - unsigned int bytesProcessed = m_zlib->Decompress(m_compressedData.data(), (unsigned)m_compressedData.size(), data, dataSize); - unsigned int readSize = maxDataSize - dataSize; // Zlib::Decompress decrements the dataSize parameter by the amount uncompressed -#else - unsigned int bytesProcessed = AZStd::GetMin((unsigned int)m_compressedData.size(), maxDataSize); - unsigned int readSize = bytesProcessed; - memcpy(data, m_compressedData.data(), readSize); -#endif - m_compressedData.erase(m_compressedData.begin(), m_compressedData.begin() + bytesProcessed); - return readSize; - } - - //========================================================================= - // DrillerInputFileStream::Close - // [3/23/2011] - //========================================================================= - void DrillerInputFileStream::Close() - { -#if defined(AZ_FILE_STREAM_COMPRESSION) - if (m_zlib) - { - m_zlib->ResetDecompressor(); - } -#endif // AZ_FILE_STREAM_COMPRESSION - AZ::IO::SystemFile::Close(); - } - - ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - // DrillerSAXParser - ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - //========================================================================= - // DrillerSAXParser - // [3/23/2011] - //========================================================================= - DrillerSAXParser::DrillerSAXParser(const TagCallbackType& tcb, const DataCallbackType& dcb) - : m_tagCallback(tcb) - , m_dataCallback(dcb) - { - } - - //========================================================================= - // ProcessStream - // [3/23/2011] - //========================================================================= - void - DrillerSAXParser::ProcessStream(DrillerInputStream& stream) - { - static const int processChunkSize = 15 * 1024; - char buffer[processChunkSize]; - unsigned int dataSize; - bool isEndianSwap = stream.IsEndianSwap(); - while ((dataSize = stream.ReadBinary(buffer, processChunkSize)) > 0) - { - char* dataStart = buffer; - char* dataEnd = dataStart + dataSize; - bool dataInBuffer = false; - if (!m_buffer.empty()) - { - m_buffer.insert(m_buffer.end(), dataStart, dataEnd); - dataStart = m_buffer.data(); - dataEnd = dataStart + m_buffer.size(); - dataInBuffer = true; - } - const int entrySize = sizeof(DrillerOutputStream::StreamEntry); - while (dataStart != dataEnd) - { - if ((dataEnd - dataStart) < entrySize) // we need at least one entry to proceed - { - // not enough data to process, buffer it. - if (!dataInBuffer) - { - m_buffer.insert(m_buffer.end(), dataStart, dataEnd); - } - break; - } - - DrillerOutputStream::StreamEntry* se = reinterpret_cast(dataStart); - if (isEndianSwap) - { - // endian swap - AZStd::endian_swap(se->name); - AZStd::endian_swap(se->sizeAndFlags); - } - - u32 dataType = (se->sizeAndFlags & DrillerOutputStream::StreamEntry::dataInternalMask) >> DrillerOutputStream::StreamEntry::dataInternalShift; - u32 value = se->sizeAndFlags & DrillerOutputStream::StreamEntry::dataSizeMask; - Data de; - de.m_name = se->name; - de.m_stringPool = stream.GetStringPool(); - de.m_isPooledString = false; - de.m_isPooledStringCrc32 = false; - switch (dataType) - { - case DrillerOutputStream::StreamEntry::INT_TAG: - { - bool isStart = (value != 0); - m_tagCallback(se->name, isStart); - dataStart += entrySize; - } break; - case DrillerOutputStream::StreamEntry::INT_DATA_U8: - { - u8 value8 = static_cast(value); - de.m_data = &value8; - de.m_dataSize = 1; - de.m_isEndianSwap = false; - m_dataCallback(de); - dataStart += entrySize; - } break; - case DrillerOutputStream::StreamEntry::INT_DATA_U16: - { - u16 value16 = static_cast(value); - de.m_data = &value16; - de.m_dataSize = 2; - de.m_isEndianSwap = false; - m_dataCallback(de); - dataStart += entrySize; - } break; - case DrillerOutputStream::StreamEntry::INT_DATA_U29: - { - de.m_data = &value; - de.m_dataSize = 4; - de.m_isEndianSwap = false; - m_dataCallback(de); - dataStart += entrySize; - } break; - case DrillerOutputStream::StreamEntry::INT_POOLED_STRING: - { - unsigned int userDataSize = value; - if ((userDataSize + entrySize) <= (unsigned)(dataEnd - dataStart)) - { - // Add string to the pool - AZ_Assert(de.m_stringPool != nullptr, "We require a string pool to parse this stream"); - AZ::u32 crc32; - const char* stringPtr; - dataStart += entrySize; - de.m_stringPool->InsertCopy(reinterpret_cast(dataStart), userDataSize, crc32, &stringPtr); - de.m_dataSize = userDataSize; - de.m_isEndianSwap = isEndianSwap; - de.m_isPooledString = true; - de.m_data = const_cast(static_cast(stringPtr)); - m_dataCallback(de); - dataStart += userDataSize; - } - else - { - // we can't process data right now add it to the buffer (if we have not done that already) - if (!dataInBuffer) - { - m_buffer.insert(m_buffer.end(), dataStart, dataEnd); - } - dataEnd = dataStart; // exit the loop - } - } break; - case DrillerOutputStream::StreamEntry::INT_POOLED_STRING_CRC32: - { - de.m_isPooledStringCrc32 = true; - AZ_Assert(value == 4, "The data size for a pooled string crc32 should be 4 bytes!"); - } // continue to INT_SIZE - case DrillerOutputStream::StreamEntry::INT_SIZE: - { - unsigned int userDataSize = value; - if ((userDataSize + entrySize) <= (unsigned)(dataEnd - dataStart)) // do we have all the date we need to process... - { - dataStart += entrySize; - de.m_data = dataStart; - de.m_dataSize = userDataSize; - de.m_isEndianSwap = isEndianSwap; - m_dataCallback(de); - dataStart += userDataSize; - } - else - { - // we can't process data right now add it to the buffer (if we have not done that already) - if (!dataInBuffer) - { - m_buffer.insert(m_buffer.end(), dataStart, dataEnd); - } - dataEnd = dataStart; // exit the loop - } - } break; - default: - { - AZ_Error("DrillerSAXParser",false,"Encounted unknown symbol (%i) while processing stream (%s). Aborting stream.\n",dataType, stream.GetIdentifier()); - - // If we can't process anything, we want to just escape the loop, to avoid spinning infinitely - dataEnd = dataStart; - } break; - } - } - if (dataInBuffer) // if the data was in the buffer remove the processed data! - { - m_buffer.erase(m_buffer.begin(), m_buffer.begin() + (dataStart - m_buffer.data())); - } - } - } - - void DrillerSAXParser::Data::Read(AZ::Vector3& v) const - { - AZ_Assert(m_dataSize == sizeof(float) * 3, "We are expecting 3 floats for Vector3 element 0x%08x with size %d bytes", m_name, m_dataSize); - float* data = reinterpret_cast(m_data); - if (m_isEndianSwap) - { - AZStd::endian_swap(data, data + 3); - m_isEndianSwap = false; - } - v = Vector3::CreateFromFloat3(data); - } - void DrillerSAXParser::Data::Read(AZ::Vector4& v) const - { - AZ_Assert(m_dataSize == sizeof(float) * 4, "We are expecting 4 floats for Vector4 element 0x%08x with size %d bytes", m_name, m_dataSize); - float* data = reinterpret_cast(m_data); - if (m_isEndianSwap) - { - AZStd::endian_swap(data, data + 4); - m_isEndianSwap = false; - } - v = Vector4::CreateFromFloat4(data); - } - void DrillerSAXParser::Data::Read(AZ::Aabb& aabb) const - { - AZ_Assert(m_dataSize == sizeof(float) * 6, "We are expecting 6 floats for Aabb element 0x%08x with size %d bytes", m_name, m_dataSize); - float* data = reinterpret_cast(m_data); - if (m_isEndianSwap) - { - AZStd::endian_swap(data, data + 6); - m_isEndianSwap = false; - } - Vector3 min = Vector3::CreateFromFloat3(data); - Vector3 max = Vector3::CreateFromFloat3(&data[3]); - aabb = Aabb::CreateFromMinMax(min, max); - } - void DrillerSAXParser::Data::Read(AZ::Obb& obb) const - { - AZ_Assert(m_dataSize == sizeof(float) * 10, "We are expecting 10 floats for Obb element 0x%08x with size %d bytes", m_name, m_dataSize); - float* data = reinterpret_cast(m_data); - if (m_isEndianSwap) - { - AZStd::endian_swap(data, data + 10); - m_isEndianSwap = false; - } - Vector3 position = Vector3::CreateFromFloat3(data); - Quaternion rotation = Quaternion::CreateFromFloat4(&data[3]); - Vector3 halfLengths = Vector3::CreateFromFloat3(&data[7]); - obb = Obb::CreateFromPositionRotationAndHalfLengths(position, rotation, halfLengths); - } - void DrillerSAXParser::Data::Read(AZ::Transform& tm) const - { - AZ_Assert(m_dataSize == sizeof(float) * 12, "We are expecting 12 floats for Transform element 0x%08x with size %d bytes", m_name, m_dataSize); - float* data = reinterpret_cast(m_data); - if (m_isEndianSwap) - { - AZStd::endian_swap(data, data + 12); - m_isEndianSwap = false; - } - const Matrix3x4 matrix3x4 = Matrix3x4::CreateFromRowMajorFloat12(data); - tm = Transform::CreateFromMatrix3x4(matrix3x4); - } - void DrillerSAXParser::Data::Read(AZ::Matrix3x3& tm) const - { - AZ_Assert(m_dataSize == sizeof(float) * 9, "We are expecting 9 floats for Matrix3x3 element 0x%08x with size %d bytes", m_name, m_dataSize); - float* data = reinterpret_cast(m_data); - if (m_isEndianSwap) - { - AZStd::endian_swap(data, data + 9); - m_isEndianSwap = false; - } - tm = Matrix3x3::CreateFromRowMajorFloat9(data); - } - void DrillerSAXParser::Data::Read(AZ::Matrix4x4& tm) const - { - AZ_Assert(m_dataSize == sizeof(float) * 16, "We are expecting 16 floats for Matrix4x4 element 0x%08x with size %d bytes", m_name, m_dataSize); - float* data = reinterpret_cast(m_data); - if (m_isEndianSwap) - { - AZStd::endian_swap(data, data + 16); - m_isEndianSwap = false; - } - tm = Matrix4x4::CreateFromRowMajorFloat16(data); - } - void DrillerSAXParser::Data::Read(AZ::Quaternion& tm) const - { - AZ_Assert(m_dataSize == sizeof(float) * 4, "We are expecting 4 floats for Quaternion element 0x%08x with size %d bytes", m_name, m_dataSize); - float* data = reinterpret_cast(m_data); - if (m_isEndianSwap) - { - AZStd::endian_swap(data, data + 4); - m_isEndianSwap = false; - } - tm = Quaternion::CreateFromFloat4(data); - } - void DrillerSAXParser::Data::Read(AZ::Plane& plane) const - { - AZ::Vector4 coeff; - Read(coeff); - plane = Plane::CreateFromCoefficients(coeff.GetX(), coeff.GetY(), coeff.GetZ(), coeff.GetW()); - } - - const char* DrillerSAXParser::Data::PrepareString(unsigned int& stringLength) const - { - const char* srcData = reinterpret_cast(m_data); - stringLength = m_dataSize; - if (m_stringPool) - { - AZ::u32 crc32; - const char* stringPtr; - if (m_isPooledStringCrc32) - { - crc32 = *reinterpret_cast(m_data); - if (m_isEndianSwap) - { - AZStd::endian_swap(crc32); - } - stringPtr = m_stringPool->Find(crc32); - AZ_Assert(stringPtr != nullptr, "Failed to find string with id 0x%08x in the string pool, proper stream read is impossible!", crc32); - stringLength = static_cast(strlen(stringPtr)); - } - else if (m_isPooledString) - { - stringPtr = srcData; // already stored in the pool just transfer the pointer - } - else - { - // Store copy of the string in the pool to save memory (keep only one reference of the string). - m_stringPool->InsertCopy(reinterpret_cast(srcData), stringLength, crc32, &stringPtr); - } - srcData = stringPtr; - } - else - { - AZ_Assert(m_isPooledString == false && m_isPooledStringCrc32 == false, "This stream requires using of a string pool as the string is send only once and afterwards only the Crc32 is used!"); - } - return srcData; - } - - ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - // DrillerDOMParser - ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - - //========================================================================= - // Node::GetTag - // [1/23/2013] - //========================================================================= - const DrillerDOMParser::Node* DrillerDOMParser::Node::GetTag(u32 tagName) const - { - const Node* tagNode = nullptr; - for (Node::NodeListType::const_iterator i = m_tags.begin(); i != m_tags.end(); ++i) - { - if ((*i).m_name == tagName) - { - tagNode = &*i; - break; - } - } - return tagNode; - } - - //========================================================================= - // Node::GetData - // [3/23/2011] - //========================================================================= - const DrillerDOMParser::Data* DrillerDOMParser::Node::GetData(u32 dataName) const - { - const Data* dataNode = nullptr; - for (Node::DataListType::const_iterator i = m_data.begin(); i != m_data.end(); ++i) - { - if (i->m_name == dataName) - { - dataNode = &*i; - break; - } - } - return dataNode; - } - - //========================================================================= - // DrillerDOMParser - // [3/23/2011] - //========================================================================= - DrillerDOMParser::DrillerDOMParser(bool isPersistentInputData) - : DrillerSAXParser(TagCallbackType(this, &DrillerDOMParser::OnTag), DataCallbackType(this, &DrillerDOMParser::OnData)) - , m_isPersistentInputData(isPersistentInputData) - { - m_root.m_name = 0; - m_root.m_parent = nullptr; - m_topNode = &m_root; - } - static int g_numFree = 0; - //========================================================================= - // ~DrillerDOMParser - // [3/23/2011] - //========================================================================= - DrillerDOMParser::~DrillerDOMParser() - { - DeleteNode(m_root); - } - - //========================================================================= - // OnTag - // [3/23/2011] - //========================================================================= - void - DrillerDOMParser::OnTag(AZ::u32 name, bool isOpen) - { - if (isOpen) - { - m_topNode->m_tags.push_back(); - Node& node = m_topNode->m_tags.back(); - node.m_name = name; - node.m_parent = m_topNode; - - m_topNode = &node; - } - else - { - AZ_Assert(m_topNode->m_name == name, "We have opened tag with name 0x%08x and closing with name 0x%08x", m_topNode->m_name, name); - m_topNode = m_topNode->m_parent; - } - } - //========================================================================= - // OnData - // [3/23/2011] - //========================================================================= - void - DrillerDOMParser::OnData(const Data& data) - { - Data de = data; - if (!m_isPersistentInputData) - { - de.m_data = azmalloc(data.m_dataSize, 1, OSAllocator); - memcpy(const_cast(de.m_data), data.m_data, data.m_dataSize); - } - m_topNode->m_data.push_back(de); - } - //========================================================================= - // DeleteNode - // [3/23/2011] - //========================================================================= - void - DrillerDOMParser::DeleteNode(Node& node) - { - if (!m_isPersistentInputData) - { - for (Node::DataListType::iterator iter = node.m_data.begin(); iter != node.m_data.end(); ++iter) - { - azfree(iter->m_data, OSAllocator, iter->m_dataSize); - ++g_numFree; - } - node.m_data.clear(); - } - for (Node::NodeListType::iterator iter = node.m_tags.begin(); iter != node.m_tags.end(); ++iter) - { - DeleteNode(*iter); - } - node.m_tags.clear(); - } - - ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - // DrillerSAXParserHandler - ////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////// - - //========================================================================= - // DrillerSAXParserHandler - // [3/14/2013] - //========================================================================= - DrillerSAXParserHandler::DrillerSAXParserHandler(DrillerHandlerParser* rootHandler) - : DrillerSAXParser(TagCallbackType(this, &DrillerSAXParserHandler::OnTag), DataCallbackType(this, &DrillerSAXParserHandler::OnData)) - { - // Push the root element - m_stack.push_back(rootHandler); - } - - //========================================================================= - // OnTag - // [3/14/2013] - //========================================================================= - void DrillerSAXParserHandler::OnTag(u32 name, bool isOpen) - { - if (m_stack.size() == 0) - { - return; - } - - DrillerHandlerParser* childHandler = nullptr; - DrillerHandlerParser* currentHandler = m_stack.back(); - if (isOpen) - { - if (currentHandler != nullptr) - { - childHandler = currentHandler->OnEnterTag(name); - AZ_Warning("Driller", !currentHandler->IsWarnOnUnsupportedTags() || childHandler != nullptr, "Could not find handler for tag 0x%08x", name); - } - m_stack.push_back(childHandler); - } - else - { - m_stack.pop_back(); - if (m_stack.size() > 0) - { - DrillerHandlerParser* parentHandler = m_stack.back(); - if (parentHandler) - { - parentHandler->OnExitTag(currentHandler, name); - } - } - } - } - - //========================================================================= - // OnData - // [3/14/2013] - //========================================================================= - void DrillerSAXParserHandler::OnData(const DrillerSAXParser::Data& data) - { - if (m_stack.size() == 0) - { - return; - } - - DrillerHandlerParser* currentHandler = m_stack.back(); - if (currentHandler) - { - currentHandler->OnData(data); - } - } - } // namespace Debug -} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Driller/Stream.h b/Code/Framework/AzCore/AzCore/Driller/Stream.h deleted file mode 100644 index 5efa416ef4..0000000000 --- a/Code/Framework/AzCore/AzCore/Driller/Stream.h +++ /dev/null @@ -1,848 +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 - * - */ -#ifndef AZCORE_DRILLER_STREAM_H -#define AZCORE_DRILLER_STREAM_H - -#include - -#include -#include -#include -#include -#include - -#include // for the Driller direct file stream -#include -#include - -namespace AZ -{ - class Vector3; - class Vector4; - class Aabb; - class Obb; - class Transform; - class Matrix3x3; - class Matrix4x4; - class Quaternion; - class Plane; - class ZLib; - - namespace IO - { - class Stream; - } - - namespace Debug - { - template - struct vector - { - typedef AZStd::vector type; - }; - - template - struct forward_list - { - typedef AZStd::forward_list type; - }; - - /** - * Interface for a string pool which can be used by input/output streams to avoid storing multiple copies of the same - * string in the stream. Of course this comes at the bookkeeping cost of the table. - */ - class DrillerStringPool - { - public: - virtual ~DrillerStringPool() {} - - /** - * Add a copy of the string to the pool. If we return true the string was added otherwise it was already in the bool. - * In both cases the crc32 of the string and the pointer to the shared copy is returned (optional for poolStringAddress)! - */ - virtual bool InsertCopy(const char* string, unsigned int length, AZ::u32& crc32, const char** poolStringAddress = NULL) = 0; - - /** - * Same as the InsertCopy above without actually coping the string into the pool. The pool assumes that - * none of the strings added to the pool will be deleted. - */ - virtual bool Insert(const char* string, unsigned int length, AZ::u32& crc32) = 0; - - /// Finds a string in the pool by crc32. - virtual const char* Find(AZ::u32 crc32) = 0; - - virtual void Erase(AZ::u32 crc32) = 0; - - /// Clears all the strings in the pool, make sure you don't reference any strings before you call that function. - virtual void Reset() = 0; - }; - - /** - * - */ - class DrillerOutputStream - { - protected: - friend class DrillerManagerImpl; - friend class DrillerSAXParser; - struct StreamEntry - { - enum InternalDataSize // max 8 values as we use 3 bit to store them - { - INT_SIZE = 0, ///< No internal data, we store the data size. IMPORTANT: INT_SIZE should be 0 the code makes assumptions based on that - INT_TAG, ///< True if this entry is tag - INT_DATA_U8, ///< Internal data u8 stored (1 byte) - INT_DATA_U16, ///< Internal data u16 stored (2 bytes) - INT_DATA_U29, ///< Internal data u32 stored (4 bytes) for which we use only the first 29 bits. - INT_POOLED_STRING_CRC32, ///< Data size should be 4 bytes crc32 that a string CRC and it require string pool. - INT_POOLED_STRING, ///< This data contains a string which should be inserted in the string pool. - }; - static const u32 dataSizeMask = 0x1fffffff; - static const u32 dataInternalMask = 0xE0000000; - static const u32 dataInternalShift = 29; - - u32 name; ///< data or tag name - u32 sizeAndFlags; ///< - }; - - template - struct IntergralType; - - template - struct IntergralType - { - static void Write(DrillerOutputStream& stream, u32 name, const T& data) - { - StreamEntry de; - de.name = name; - de.sizeAndFlags = (u32)(StreamEntry::INT_DATA_U8) << StreamEntry::dataInternalShift; - de.sizeAndFlags |= *reinterpret_cast(&data); - stream.WriteBinary(de); - } - }; - template - struct IntergralType - { - static void Write(DrillerOutputStream& stream, u32 name, const T& data) - { - StreamEntry de; - de.name = name; - de.sizeAndFlags = (u32)(StreamEntry::INT_DATA_U16) << StreamEntry::dataInternalShift; - de.sizeAndFlags |= *reinterpret_cast(&data); - stream.WriteBinary(de); - } - }; - template - struct IntergralType - { - static void Write(DrillerOutputStream& stream, u32 name, const T& data) - { - StreamEntry de; - de.name = name; - const u32* uintData = reinterpret_cast(&data); - if (((*uintData) & StreamEntry::dataSizeMask) == *uintData) // check if we can store it internally - { - de.sizeAndFlags = (u32)(StreamEntry::INT_DATA_U29) << StreamEntry::dataInternalShift; - de.sizeAndFlags |= *uintData; - stream.WriteBinary(de); - } - else - { - de.sizeAndFlags = 4; - stream.WriteBinary(de); - stream.WriteBinary(&data, de.sizeAndFlags); - } - } - }; - template - struct IntergralType - { - static void Write(DrillerOutputStream& stream, u32 name, const T& data) - { - StreamEntry de; - de.name = name; - const u64* uintData = reinterpret_cast(&data); - if (((*uintData) & static_cast(StreamEntry::dataSizeMask)) == *uintData) // check if we can store it internally - { - de.sizeAndFlags = (u32)(StreamEntry::INT_DATA_U29) << StreamEntry::dataInternalShift; - de.sizeAndFlags |= static_cast(*uintData); - stream.WriteBinary(de); - } - else - { - de.sizeAndFlags = 8; - stream.WriteBinary(de); - stream.WriteBinary(&data, de.sizeAndFlags); - } - } - }; - template - struct IntergralType - { - static void Write(DrillerOutputStream& stream, u32 name, const T* pointer) - { - size_t id = reinterpret_cast(pointer); - IntergralType::Write(stream, name, id); - } - }; - - public: - /** - * Each stream with start with this header, before anything else. - */ - struct StreamHeader - { - StreamHeader() - : platform((u8)g_currentPlatform) {} - u8 platform; - }; - - DrillerOutputStream(DrillerStringPool* stringPool = NULL) - : m_stringPool(stringPool) { } - virtual ~DrillerOutputStream() {} - - ////////////////////////////////////////////////////////////////////////// - // Write - inline void BeginTag(u32 name) - { - StreamEntry de; - de.name = name; - de.sizeAndFlags = (u32)(StreamEntry::INT_TAG) << StreamEntry::dataInternalShift; - de.sizeAndFlags |= 1; // true - open tag - WriteBinary(de); - } - inline void EndTag(u32 name) - { - StreamEntry de; - de.name = name; - de.sizeAndFlags = (u32)(StreamEntry::INT_TAG) << StreamEntry::dataInternalShift; - WriteBinary(de); - } - - ////////////////////////////////////////////////////////////////////////// - // Generic - template - inline void Write(u32 name, const T& data) - { - // User should handle non specialized non integral types. - IntergralType::value || AZStd::is_enum::value>::Write(*this, name, data); - } - - ////////////////////////////////////////////////////////////////////////// - // Binary and strings - inline void Write(u32 name, const void* data, unsigned int dataSize) - { - StreamEntry de; - de.name = name; - de.sizeAndFlags = dataSize; - WriteBinary(de); - WriteBinary(data, dataSize); - } - inline void Write(u32 name, const char* string, bool isCopyString = true) - { - StreamEntry de; - de.name = name; - de.sizeAndFlags = static_cast(strlen(string)); - AZ_Assert(de.sizeAndFlags <= StreamEntry::dataSizeMask, "Invalid string length! String is too long, length is limited to %u bytes!", StreamEntry::dataSizeMask); - ; - if (m_stringPool) - { - AZ::u32 crc; - bool isInserted = isCopyString ? m_stringPool->InsertCopy(string, de.sizeAndFlags, crc) : m_stringPool->Insert(string, de.sizeAndFlags, crc); - if (!isInserted) // if already inserted, it means it's in the stream, so store the crc only. - { - de.sizeAndFlags = (u32)(StreamEntry::INT_POOLED_STRING_CRC32) << StreamEntry::dataInternalShift; - de.sizeAndFlags |= sizeof(crc); - WriteBinary(de); - WriteBinary(&crc, sizeof(crc)); - } - else - { - AZ::u32 stringSize = de.sizeAndFlags; - de.sizeAndFlags |= (u32)(StreamEntry::INT_POOLED_STRING) << StreamEntry::dataInternalShift; - WriteBinary(de); - WriteBinary(string, stringSize); - } - } - else - { - WriteBinary(de); - WriteBinary(string, de.sizeAndFlags); - } - } - template - inline void Write(u32 name, const AZStd::basic_string& str, bool isCopyString = true) - { - StreamEntry de; - de.name = name; - de.sizeAndFlags = static_cast(str.size()); - AZ_Assert(de.sizeAndFlags <= StreamEntry::dataSizeMask, "Invalid string length! String is too long, length is limited to %u bytes!", StreamEntry::dataSizeMask); - if (m_stringPool) - { - AZ::u32 crc; - bool isInserted = isCopyString ? m_stringPool->InsertCopy(str.c_str(), de.sizeAndFlags, crc) : m_stringPool->Insert(str.c_str(), de.sizeAndFlags, crc); - if (!isInserted) // if already inserted, it means it's in the stream, so store the crc only. - { - de.sizeAndFlags = (u32)(StreamEntry::INT_POOLED_STRING_CRC32) << StreamEntry::dataInternalShift; - de.sizeAndFlags |= sizeof(crc); - WriteBinary(de); - WriteBinary(&crc, sizeof(crc)); - } - else - { - AZ::u32 stringSize = de.sizeAndFlags; - de.sizeAndFlags |= (u32)(StreamEntry::INT_POOLED_STRING) << StreamEntry::dataInternalShift; - WriteBinary(de); - WriteBinary(str.data(), stringSize); - } - } - else - { - WriteBinary(de); - WriteBinary(str.data(), de.sizeAndFlags); - } - } - - template - inline void Write(u32 name, const AZStd::basic_string& str) - { - StreamEntry de; - de.name = name; - de.sizeAndFlags = static_cast(str.size()); - AZ_Assert(de.sizeAndFlags <= StreamEntry::dataSizeMask, "Invalid string length! String is too long, length is limited to %u bytes!", StreamEntry::dataSizeMask); - WriteBinary(de); - WriteBinary(str.data(), de.sizeAndFlags * sizeof(AZStd::wstring::value_type)); - } - - ////////////////////////////////////////////////////////////////////////// - // math types - inline void Write(u32 name, float f) - { - Write(name, &f, static_cast(sizeof(float))); - } - inline void Write(u32 name, double d) - { - Write(name, &d, static_cast(sizeof(double))); - } - - void Write(u32 name, const AZ::Vector3& v); - void Write(u32 name, const AZ::Vector4& v); - void Write(u32 name, const AZ::Aabb& aabb); - void Write(u32 name, const AZ::Obb& obb); - void Write(u32 name, const AZ::Transform& tm); - void Write(u32 name, const AZ::Matrix3x3& tm); - void Write(u32 name, const AZ::Matrix4x4& tm); - void Write(u32 name, const AZ::Quaternion& tm); - void Write(u32 name, const AZ::Plane& plane); - - ////////////////////////////////////////////////////////////////////////// - // containers - template - inline void Write(u32 name, InputIterator first, InputIterator last) - { - // we can specialize for contiguous_iterator_tag so have only 1 write for all elements - size_t numElements = AZStd::distance(first, last); - size_t elementSize = sizeof(typename AZStd::iterator_traits::value_type); - unsigned int dataSize = static_cast(numElements * elementSize); - StreamEntry de; - de.name = name; - de.sizeAndFlags = dataSize; - AZ_Assert(dataSize < StreamEntry::dataSizeMask, "Invalid data size, size is limited to %d bytes!", StreamEntry::dataSizeMask - 1); - WriteBinary(de); - //WriteBinary(data,dataSize); for contiguous_iterator_tag - for (; first != last; ++first) - { - WriteBinary(&*first, static_cast(elementSize)); - } - } - ////////////////////////////////////////////////////////////////////////// - - ////////////////////////////////////////////////////////////////////////// - // Raw data to the output - template - inline void WriteBinary(const T& data) - { - WriteBinary(&data, sizeof(T)); - } - - virtual void WriteBinary(const void* data, unsigned int dataSize) = 0; - ////////////////////////////////////////////////////////////////////////// - - /** - * Write a time stamp (AZStd::sys_time_t) in millisecond since 1970/01/01 00:00:00 UTC. - * On older windows this function can have ~15 ms resolution, in such cases use \ref GetTimeNowMicroSecond - */ - void WriteTimeUTC(u32 name); - - /** - * Write a time stamp (AZStd::sys_time_t) in micriseconds. This function is inaccurate for long periods but it has ms resolution. - * For long periods use \ref WriteTimeUTC. - */ - void WriteTimeMicrosecond(u32 name); - - /// Called when the driller is moving on the next frame, so you can flush you current buffer to network/disk. - virtual void OnEndOfFrame() {} - - /// Sets the string pool used for this stream. To disable the pool just set it to NULL. - void SetStringPool(DrillerStringPool* stringPool) { m_stringPool = stringPool; } - - protected: - /// Write the Stream header structure (should be endianess independent). - void WriteHeader(); - - DrillerStringPool* m_stringPool; ///< Optional pointer to a string pool. - }; - - /** - * For efficiency all data read functions are placed with the parsers. - */ - class DrillerInputStream - { - public: - DrillerInputStream(DrillerStringPool* stringPool = NULL) - : m_isEndianSwap(false) - , m_stringPool(stringPool) {} - virtual ~DrillerInputStream() {} - - bool IsEndianSwap() const { return m_isEndianSwap; } - /// Reads binary data from a stream to to maxDataSize. Returns 0 if no more data. - virtual unsigned int ReadBinary(void* data, unsigned int maxDataSize) = 0; - - /// Sets the string pool used for this stream. To disable the pool just set it to NULL. - void SetStringPool(DrillerStringPool* stringPool) { m_stringPool = stringPool; } - DrillerStringPool* GetStringPool() const { return m_stringPool; } - - void SetIdentifier(const char* identifier) { m_streamIdentifier = identifier; } - const char* GetIdentifier() const { return m_streamIdentifier.c_str(); } - - protected: - /// Read the Stream header structure - bool ReadHeader(); - - bool m_isEndianSwap; - DrillerStringPool* m_stringPool; ///< Optional pointer to a string pool. - AZStd::string m_streamIdentifier; - }; - - /** - * Outputs all stream data into a memory buffer. It will grow automatically. - */ - class DrillerOutputMemoryStream - : public DrillerOutputStream - { - protected: - vector::type m_data; - public: - AZ_CLASS_ALLOCATOR(DrillerOutputMemoryStream, OSAllocator, 0) - DrillerOutputMemoryStream(size_t memorySize = 2048) { m_data.reserve(memorySize); } - const unsigned char* GetData() const { return m_data.data(); } - unsigned int GetDataSize() const { return static_cast(m_data.size()); } - inline void Reset() { m_data.clear(); } - void WriteBinary(const void* data, unsigned int dataSize) override - { - m_data.insert(m_data.end(), reinterpret_cast(data), reinterpret_cast(data) + dataSize); - } - }; - /** - * Reads data from a memory stream. Data is NOT copied and must be persistent while we are using it. - */ - class DrillerInputMemoryStream - : public DrillerInputStream - { - const unsigned char* m_data; - const unsigned char* m_dataEnd; - - public: - AZ_CLASS_ALLOCATOR(DrillerInputMemoryStream, OSAllocator, 0) - DrillerInputMemoryStream(const char* streamIdentifier = "", const void* data = nullptr, unsigned int dataSize = 0) - : DrillerInputStream() - , m_data(nullptr) - , m_dataEnd(nullptr) - { - if (data != nullptr) - { - SetData(streamIdentifier, data, dataSize); - } - } - - void SetData(const char* streamIdentifier, const void* data, unsigned int dataSize) - { - SetIdentifier(streamIdentifier); - - AZ_Assert(data != nullptr && dataSize > 0, "We must have a valid pointer %p and data size %d !", data, dataSize); - if (m_data == nullptr) // this is the first data chuck, read the platform - { - m_data = reinterpret_cast(data); - m_dataEnd = m_data + dataSize; - ReadHeader(); - } - else - { - m_data = reinterpret_cast(data); - m_dataEnd = m_data + dataSize; - } - } - - unsigned int GetDataLeft() const { return static_cast(m_dataEnd - m_data); } - unsigned int ReadBinary(void* data, unsigned int maxDataSize) override - { - AZ_Assert(m_data != nullptr, "You must call SetData function, before you can read data!"); - AZ_Assert(data != nullptr && maxDataSize > 0, "We must have a valid pointer and max data size!"); - unsigned int dataToCopy = AZStd::GetMin(static_cast(m_dataEnd - m_data), maxDataSize); - if (dataToCopy) - { - memcpy(data, m_data, dataToCopy); - } - m_data += dataToCopy; - return dataToCopy; - } - }; - - /** - * Outputs driller data to a file (buffered) - * IMPORTANT: We provide direct IO classes (instead trough Streamer), because the driller - * framework should NOT use engine systems (for example imagine we are drilling the Streamer, using it to - * write the drilled data will invalidate all the results as the streamer is unaware which data is driller data and which not) - */ - class DrillerOutputFileStream - : public IO::SystemFile - , public DrillerOutputStream - { - ZLib* m_zlib; - vector::type m_compressionBuffer; - vector::type m_dataBuffer; - public: - AZ_CLASS_ALLOCATOR(DrillerOutputFileStream, OSAllocator, 0) - DrillerOutputFileStream(); - ~DrillerOutputFileStream(); - bool Open(const char* fileName, int mode, int platformFlags = 0); - void Close(); - - void WriteBinary(const void* data, unsigned int dataSize) override; - }; - - /** - * Reads driller data from a file. - */ - class DrillerInputFileStream - : public AZ::IO::SystemFile - , public DrillerInputStream - { - ZLib* m_zlib; - vector::type m_compressedData; - public: - AZ_CLASS_ALLOCATOR(DrillerInputFileStream, OSAllocator, 0) - DrillerInputFileStream(); - ~DrillerInputFileStream(); - bool Open(const char* fileName, int mode, int platformFlags = 0); - unsigned int ReadBinary(void* data, unsigned int maxDataSize) override; - void Close(); - }; - - /** - * SAX like stream parser for driller data. We can stream the data - * and we will trigger events as tags and data (attributes) arrive. We use less memory this way. - * \note SAX is used as reference name, we are NOT trying to compatible with - * any specs. (not that SAX has specs) - * IMPORTANT: All data callbacks (tag and data) are called in the order they were at store. You can - * use this order as event index. - */ - class DrillerSAXParser - { - public: - struct Data - { - u32 m_name; ///< Crc name of the data entry. - void* m_data; ///< Pointer to copy if the loaded data. - unsigned int m_dataSize; ///< Data size in bytes. - mutable bool m_isEndianSwap; ///< True if the user will need to swap the endian when he access the data. We swap the data is the storage so we can read it multiple times without swap. - DrillerStringPool* m_stringPool; ///< Pointer to optional data string pool. - bool m_isPooledString; ///< True if we have a pooled string (stored in the stringPool already). - bool m_isPooledStringCrc32; ///< True is we have stored a crc32 (4 bytes) which refers to a string from the String Pool. - - ////////////////////////////////////////////////////////////////////////// - // Generic - template - inline void Read(T& t) const - { - static_assert(AZStd::is_pod::value, "T must be plain-old-data"); - - AZ_Assert(sizeof(t) >= m_dataSize, "You are about to lose some data, this is wrong."); - if (m_dataSize == sizeof(t)) - { - // do a memcpy as alignment might be required for some data types! This is not performance critical as we usually load drill files on x86/x64 - // which doesn't care about alignment. - memcpy(&t, m_data, m_dataSize); - } - else - { - AZ_Assert(AZStd::is_pointer::value || AZStd::is_integral::value, "We support extending only for integral types, float and pointers up to 8 bytes!"); - - if (AZStd::is_signed::value) - { - switch (m_dataSize) - { - case 1: - t = static_cast(*reinterpret_cast(m_data)); - break; - case 2: - t = static_cast(*reinterpret_cast(m_data)); - break; - case 4: - t = static_cast(*reinterpret_cast(m_data)); - break; - default: - AZ_Assert(false, "Source data size unsupported... we can extend only 1,2,4 bytes into 2,4,8 bytes integrals"); - } - } - else - { - switch (m_dataSize) - { - case 1: - t = static_cast(*reinterpret_cast(m_data)); - break; - case 2: - t = static_cast(*reinterpret_cast(m_data)); - break; - case 4: - t = static_cast(*reinterpret_cast(m_data)); - break; - default: - AZ_Assert(false, "Source data size unsupported... we can extend only 1,2,4 bytes into 2,4,8 bytes integrals"); - } - } - } - if (m_isEndianSwap) - { - AZStd::endian_swap(t); - } - } - - inline void Read(bool& b) const - { - u8* data = reinterpret_cast(m_data); - b = false; - for (unsigned int i = 0; i < m_dataSize; ++i) - { - if (data[i] != 0) - { - b = true; - return; - } - } - } - ////////////////////////////////////////////////////////////////////////// - // Binary and strings - inline unsigned int Read(void* buffer, unsigned int bufferSize) const - { - unsigned int dataToCopy = AZStd::GetMin(m_dataSize, bufferSize); - memcpy(buffer, m_data, dataToCopy); - // no data swap - return dataToCopy; - } - // a call avilable only when we use a string pool, it will return the pointer of string in the pool, so you don't need to copy it or do any fancy procedures. - inline const char* ReadPooledString() const - { - AZ_Assert(m_stringPool != nullptr, "This read type is supported only when we use string pool!"); - unsigned int srcDataSize; - return PrepareString(srcDataSize); - } - inline unsigned int Read(char* string, unsigned int maxNumChars) const - { - unsigned int srcDataSize; - const char* srcData = PrepareString(srcDataSize); - unsigned int dataToCopy = AZStd::GetMin(maxNumChars - 1, srcDataSize); - memcpy(string, srcData, dataToCopy); - string[dataToCopy] = '\0'; - return dataToCopy; - } - template - inline unsigned int Read(AZStd::basic_string& str) const - { - unsigned int srcDataSize; - const char* srcData = PrepareString(srcDataSize); - str = AZStd::basic_string(static_cast(srcData), srcDataSize); - return m_dataSize; - } - template - inline unsigned int Read(AZStd::basic_string& str) const - { - // wstring pooling not supported yet - str = AZStd::basic_string(static_cast(m_data), m_dataSize / 2); - if (m_isEndianSwap) - { - AZStd::endian_swap(str.begin(), str.end()); - } - return m_dataSize; - } - - ////////////////////////////////////////////////////////////////////////// - // math types - void Read(AZ::Vector3& v) const; - void Read(AZ::Vector4& v) const; - void Read(AZ::Aabb& aabb) const; - void Read(AZ::Obb& obb) const; - void Read(AZ::Transform& tm) const; - void Read(AZ::Matrix3x3& tm) const; - void Read(AZ::Matrix4x4& tm) const; - void Read(AZ::Quaternion& tm) const; - void Read(AZ::Plane& plane) const; - - ////////////////////////////////////////////////////////////////////////// - // containers - template - inline void Read(AZStd::insert_iterator& iter) const - { - typedef typename AZStd::insert_iterator InsertIterator; - // we can specialize for contiguous_iterator_tag so have only 1 write for all elements - const size_t elementSize = sizeof(InsertIterator::container_type::value_type); - size_t numElements = m_dataSize / elementSize; - AZ_Assert(m_dataSize % elementSize == 0, "Stored elements size doesn't match the read parameters!"); - Data elementEntry = *this; - elementEntry.m_dataSize = elementSize; - char* dataPtr = reinterpret_cast(m_data); - for (size_t i = 0; i < numElements; ++i, ++iter) - { - typename InsertIterator::container_type::value_type value; - elementEntry.m_data = dataPtr; - Read(elementEntry, value); - iter = value; - dataPtr += elementSize; - } - } - ////////////////////////////////////////////////////////////////////////// - private: - const char* PrepareString(unsigned int& stringLength) const; - }; - - typedef AZStd::delegate TagCallbackType; - typedef AZStd::delegate DataCallbackType; - - AZ_CLASS_ALLOCATOR(DrillerSAXParser, OSAllocator, 0) - DrillerSAXParser(const TagCallbackType& tcb, const DataCallbackType& dcb); - /// Processes an input stream until all data is consumed (read returns 0 bytes). - void ProcessStream(DrillerInputStream& stream); - protected: - - typedef vector::type BufferType; - BufferType m_buffer; - TagCallbackType m_tagCallback; - DataCallbackType m_dataCallback; - }; - - /** - * DOM like parser, we will load the entire stream in memory (ProcessStream function). - * Depending on the data size this can be very memory consuming. - * \note DOM is used as reference we are NOT compliant with the DOM specs in any way. - * IMPORTANT: All data is stored (for parsing) in the same order the events occurred - * or the remote machine. Each next tad or data was recorded in the way. You can use - * this as an event index. - */ - class DrillerDOMParser - : public DrillerSAXParser - { - public: - struct Node - { - typedef forward_list::type DataListType; - typedef forward_list::type NodeListType; - - u32 m_name; - Node* m_parent; - DataListType m_data; - NodeListType m_tags; - - /// Return a pointer to the first tag with specific name. - const Node* GetTag(u32 tagName) const; - /// Returns pointer to the first data entry with specific name. NULL if not data has been found. - const Data* GetData(u32 dataName) const; - /// Returns pointer to the first data entry with specific name. If it can't be found it will assert - const Data* GetDataRequired(u32 dataName) const - { - const Data* dataNode = GetData(dataName); - AZ_Assert(dataNode != NULL, "Data node in tag 0x%08x with name 0x%08x is required but missing!", m_name, dataName); - return dataNode; - } - }; - - AZ_CLASS_ALLOCATOR(DrillerDOMParser, OSAllocator, 0) - - DrillerDOMParser(bool isPersistentInputData = false); - ~DrillerDOMParser(); - /// return true if we are at top level of the tree and we can parse the data safely (there may be still more data, but it's top level only). - bool CanParse() const { return m_topNode == &m_root; } - - const Node* GetRootNode() const { return &m_root; } - protected: - Node m_root; - Node* m_topNode; - bool m_isPersistentInputData; ///< true if data that we process is persistent so we don't need to copy it internally, false otherwise. - - void OnTag(u32 name, bool isOpen); - void OnData(const Data& data); - void DeleteNode(Node& node); - }; - - /** - * Base class for handling a Tag with a specific name. Handlers are kept in a hierarchy - * with one required by DrillerSAXParserHandler to be able to handle tags at a root - * level for the driller data stream. - */ - class DrillerHandlerParser - { - public: - DrillerHandlerParser(bool isWarnOnUnsupportedTags = true) - : m_isWarnOnUnsupportedTags(isWarnOnUnsupportedTags) {} - - virtual ~DrillerHandlerParser() {} - /// Enumerate all the child tags that we support for the tag we are handling. If the tag is not know you should return NULL - virtual DrillerHandlerParser* OnEnterTag(u32 tagName) { (void)tagName; return NULL; } - /// Exit tag you are not required to implement this, we always exist tags in order FILO. - virtual void OnExitTag(DrillerHandlerParser* handler, u32 tagName) { (void)handler; (void)tagName; } - /// Handle that data for the tag we are handling. - virtual void OnData(const DrillerSAXParser::Data& dataNode) { (void)dataNode; } - /// Return the warning state on unsupported tags (sometime you might want to warn usually) and sometimes not (if you load newer drills, etc.) - inline bool IsWarnOnUnsupportedTags() const { return m_isWarnOnUnsupportedTags; } - - protected: - bool m_isWarnOnUnsupportedTags; - }; - - /** - * Processes a driller driller and dispatches the data based on the - * the DrillerHandlerParser (handlers) and their ability to handle specific tags. - * If a tag is NOT found as a child of the current one it will display a warning with the tag name - * (useless it's allowed by DrillerHandlerParser::IsWarnOnUnsupportedTags) and process the stream - * is a safe manner by skipping all the data and tags we can't handle. - */ - class DrillerSAXParserHandler - : public DrillerSAXParser - { - public: - AZ_CLASS_ALLOCATOR(DrillerSAXParserHandler, OSAllocator, 0) - - DrillerSAXParserHandler(DrillerHandlerParser* rootHandler); - - protected: - - /// Called from DrillerSAXParser when we have an open tag. - void OnTag(u32 name, bool isOpen); - /// Called from DrillerSAXParser when we have data, which will be forwarded to the handler. - void OnData(const DrillerSAXParser::Data& data); - - typedef vector::type DrillerHandlerStackType; - DrillerHandlerStackType m_stack; - }; - } // namespace Debug -} // namespace AZ - -#endif // AZCORE_DRILLER_STREAM_H -#pragma once - - diff --git a/Code/Framework/AzCore/AzCore/EBus/EventSchedulerSystemComponent.cpp b/Code/Framework/AzCore/AzCore/EBus/EventSchedulerSystemComponent.cpp index 3cdc3fc461..dd92b340c5 100644 --- a/Code/Framework/AzCore/AzCore/EBus/EventSchedulerSystemComponent.cpp +++ b/Code/Framework/AzCore/AzCore/EBus/EventSchedulerSystemComponent.cpp @@ -60,7 +60,7 @@ namespace AZ void EventSchedulerSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) { - TimeMs startTime = GetElapsedTimeMs(); + TimeMs startTime = AZ::GetElapsedTimeMs(); bool usingTimeslice = bg_maxScheduledEventProcessTimeMs != TimeMs{ 0 }; while (!m_queue.empty()) @@ -76,7 +76,7 @@ namespace AZ while (!m_pendingQueue.empty()) { - if (usingTimeslice && (GetElapsedTimeMs() - startTime > bg_maxScheduledEventProcessTimeMs)) + if (usingTimeslice && (AZ::GetElapsedTimeMs() - startTime > bg_maxScheduledEventProcessTimeMs)) { AZLOG_WARN("Failed to trigger all pending scheduled events, %u events remain on the pending queue", aznumeric_cast(m_pendingQueue.size())); break; @@ -103,7 +103,7 @@ namespace AZ durationMs = TimeMs{ 0 }; } - TimeMs currentMilliseconds = GetElapsedTimeMs(); + TimeMs currentMilliseconds = AZ::GetElapsedTimeMs(); if (timedEvent->m_handle == nullptr) { timedEvent->m_handle = AllocateHandle(); @@ -122,7 +122,7 @@ namespace AZ durationMs = TimeMs{ 0 }; } - TimeMs currentMilliseconds = GetElapsedTimeMs(); + TimeMs currentMilliseconds = AZ::GetElapsedTimeMs(); ScheduledEvent* timedEvent = AllocateManagedEvent(callback, eventName); const bool ownsScheduledEvent = true; *(timedEvent->m_handle) = ScheduledEventHandle(TimeMs(currentMilliseconds + durationMs), durationMs, timedEvent, ownsScheduledEvent); diff --git a/Code/Framework/AzCore/AzCore/EBus/ScheduledEvent.cpp b/Code/Framework/AzCore/AzCore/EBus/ScheduledEvent.cpp index 8e496c3dbc..41230958e4 100644 --- a/Code/Framework/AzCore/AzCore/EBus/ScheduledEvent.cpp +++ b/Code/Framework/AzCore/AzCore/EBus/ScheduledEvent.cpp @@ -76,7 +76,7 @@ namespace AZ TimeMs ScheduledEvent::TimeInQueueMs() const { - return GetElapsedTimeMs() - m_timeInserted; + return AZ::GetElapsedTimeMs() - m_timeInserted; } TimeMs ScheduledEvent::RemainingTimeInQueueMs() const diff --git a/Code/Framework/AzCore/AzCore/IO/CompressionBus.cpp b/Code/Framework/AzCore/AzCore/IO/CompressionBus.cpp index fb9f8841a2..7878ec6e9e 100644 --- a/Code/Framework/AzCore/AzCore/IO/CompressionBus.cpp +++ b/Code/Framework/AzCore/AzCore/IO/CompressionBus.cpp @@ -8,38 +8,35 @@ #include -namespace AZ +namespace AZ::IO { - namespace IO + CompressionInfo::CompressionInfo(CompressionInfo&& rhs) { - CompressionInfo::CompressionInfo(CompressionInfo&& rhs) - { - *this = AZStd::move(rhs); - } + *this = AZStd::move(rhs); + } - CompressionInfo& CompressionInfo::operator=(CompressionInfo&& rhs) - { - m_decompressor = AZStd::move(rhs.m_decompressor); - m_archiveFilename = AZStd::move(rhs.m_archiveFilename); - m_compressionTag = rhs.m_compressionTag; - m_offset = rhs.m_offset; - m_compressedSize = rhs.m_compressedSize; - m_uncompressedSize = rhs.m_uncompressedSize; - m_conflictResolution = rhs.m_conflictResolution; - m_isCompressed = rhs.m_isCompressed; - m_isSharedPak = rhs.m_isSharedPak; + CompressionInfo& CompressionInfo::operator=(CompressionInfo&& rhs) + { + m_decompressor = AZStd::move(rhs.m_decompressor); + m_archiveFilename = AZStd::move(rhs.m_archiveFilename); + m_compressionTag = rhs.m_compressionTag; + m_offset = rhs.m_offset; + m_compressedSize = rhs.m_compressedSize; + m_uncompressedSize = rhs.m_uncompressedSize; + m_conflictResolution = rhs.m_conflictResolution; + m_isCompressed = rhs.m_isCompressed; + m_isSharedPak = rhs.m_isSharedPak; - return *this; - } + return *this; + } - namespace CompressionUtils + namespace CompressionUtils + { + bool FindCompressionInfo(CompressionInfo& info, const AZStd::string_view filename) { - bool FindCompressionInfo(CompressionInfo& info, const AZStd::string_view filename) - { - bool result = false; - CompressionBus::Broadcast(&CompressionBus::Events::FindCompressionInfo, result, info, filename); - return result; - } + bool result = false; + CompressionBus::Broadcast(&CompressionBus::Events::FindCompressionInfo, result, info, filename); + return result; } } -} +} // namespace AZ::IO diff --git a/Code/Framework/AzCore/AzCore/IO/Compressor.cpp b/Code/Framework/AzCore/AzCore/IO/Compressor.cpp index e223730ce8..16527422ad 100644 --- a/Code/Framework/AzCore/AzCore/IO/Compressor.cpp +++ b/Code/Framework/AzCore/AzCore/IO/Compressor.cpp @@ -10,32 +10,29 @@ #include #include -namespace AZ +namespace AZ::IO { - namespace IO + //========================================================================= + // WriteHeaderAndData + // [12/13/2012] + //========================================================================= + bool Compressor::WriteHeaderAndData(CompressorStream* compressorStream) { - //========================================================================= - // WriteHeaderAndData - // [12/13/2012] - //========================================================================= - bool Compressor::WriteHeaderAndData(CompressorStream* compressorStream) + AZ_Assert(compressorStream->CanWrite(), "Stream is not open for write!"); + AZ_Assert(compressorStream->GetCompressorData(), "Stream doesn't have attached compressor, call WriteCompressed first!"); + AZ_Assert(compressorStream->GetCompressorData()->m_compressor == this, "Invalid compressor data! Data belongs to a different compressor"); + CompressorHeader header; + header.SetAZCS(); + header.m_compressorId = GetTypeId(); + header.m_uncompressedSize = compressorStream->GetCompressorData()->m_uncompressedSize; + AZStd::endian_swap(header.m_compressorId); + AZStd::endian_swap(header.m_uncompressedSize); + GenericStream* baseStream = compressorStream->GetWrappedStream(); + if (baseStream->WriteAtOffset(sizeof(CompressorHeader), &header, 0U) == sizeof(CompressorHeader)) { - AZ_Assert(compressorStream->CanWrite(), "Stream is not open for write!"); - AZ_Assert(compressorStream->GetCompressorData(), "Stream doesn't have attached compressor, call WriteCompressed first!"); - AZ_Assert(compressorStream->GetCompressorData()->m_compressor == this, "Invalid compressor data! Data belongs to a different compressor"); - CompressorHeader header; - header.SetAZCS(); - header.m_compressorId = GetTypeId(); - header.m_uncompressedSize = compressorStream->GetCompressorData()->m_uncompressedSize; - AZStd::endian_swap(header.m_compressorId); - AZStd::endian_swap(header.m_uncompressedSize); - GenericStream* baseStream = compressorStream->GetWrappedStream(); - if (baseStream->WriteAtOffset(sizeof(CompressorHeader), &header, 0U) == sizeof(CompressorHeader)) - { - return true; - } - - return false; + return true; } - } // namespace IO -} // namespace AZ + + return false; + } +} // namespace AZ::IO diff --git a/Code/Framework/AzCore/AzCore/IO/Compressor.h b/Code/Framework/AzCore/AzCore/IO/Compressor.h index 9b910a0ea4..340366d82f 100644 --- a/Code/Framework/AzCore/AzCore/IO/Compressor.h +++ b/Code/Framework/AzCore/AzCore/IO/Compressor.h @@ -5,74 +5,67 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ -#ifndef AZCORE_IO_COMPRESSOR_H -#define AZCORE_IO_COMPRESSOR_H +#pragma once #include -namespace AZ +namespace AZ::IO { - namespace IO + class CompressorStream; + + /** + * Compressor/Decompressor base interface. + * Used for all stream compressors. + */ + class Compressor { - class CompressorStream; + public: + typedef AZ::u64 SizeType; + static const int m_maxHeaderSize = 4096; /// When we open a stream to check if it's compressed we read the first m_maxHeaderSize bytes. - /** - * Compressor/Decompressor base interface. - * Used for all stream compressors. - */ - class Compressor - { - public: - typedef AZ::u64 SizeType; - static const int m_maxHeaderSize = 4096; /// When we open a stream to check if it's compressed we read the first m_maxHeaderSize bytes. + virtual ~Compressor() {} + /// Return compressor type id. + virtual AZ::u32 GetTypeId() const = 0; + /// Called when we open a stream to Read for the first time. Data contains the first. dataSize <= m_maxHeaderSize. + virtual bool ReadHeaderAndData(CompressorStream* stream, AZ::u8* data, unsigned int dataSize) = 0; + /// Called when we are about to start writing to a compressed stream. (Must be called first to write compressor header) + virtual bool WriteHeaderAndData(CompressorStream* stream); + /// Forwarded function from the Device when we from a compressed stream. + virtual SizeType Read(CompressorStream* stream, SizeType byteSize, SizeType offset, void* buffer) = 0; + /// Forwarded function from the Device when we write to a compressed stream. + virtual SizeType Write(CompressorStream* stream, SizeType byteSize, const void* data, SizeType offset = SizeType(-1)) = 0; + /// Write a seek point. + virtual bool WriteSeekPoint(CompressorStream* stream) { (void)stream; return false; } + /// Initializes Compressor for writing data. + virtual bool StartCompressor(CompressorStream* stream, int compressionLevel, SizeType autoSeekDataSize) { (void)stream; (void)compressionLevel; (void)autoSeekDataSize; return false; } + /// Called just before we close the stream. All compression data will be flushed and finalized. (You can't add data afterwards). + virtual bool Close(CompressorStream* stream) = 0; + }; - virtual ~Compressor() {} - /// Return compressor type id. - virtual AZ::u32 GetTypeId() const = 0; - /// Called when we open a stream to Read for the first time. Data contains the first. dataSize <= m_maxHeaderSize. - virtual bool ReadHeaderAndData(CompressorStream* stream, AZ::u8* data, unsigned int dataSize) = 0; - /// Called when we are about to start writing to a compressed stream. (Must be called first to write compressor header) - virtual bool WriteHeaderAndData(CompressorStream* stream); - /// Forwarded function from the Device when we from a compressed stream. - virtual SizeType Read(CompressorStream* stream, SizeType byteSize, SizeType offset, void* buffer) = 0; - /// Forwarded function from the Device when we write to a compressed stream. - virtual SizeType Write(CompressorStream* stream, SizeType byteSize, const void* data, SizeType offset = SizeType(-1)) = 0; - /// Write a seek point. - virtual bool WriteSeekPoint(CompressorStream* stream) { (void)stream; return false; } - /// Initializes Compressor for writing data. - virtual bool StartCompressor(CompressorStream* stream, int compressionLevel, SizeType autoSeekDataSize) { (void)stream; (void)compressionLevel; (void)autoSeekDataSize; return false; } - /// Called just before we close the stream. All compression data will be flushed and finalized. (You can't add data afterwards). - virtual bool Close(CompressorStream* stream) = 0; - }; + /** + * Base compressor data assigned for all compressors. + */ + class CompressorData + { + public: + virtual ~CompressorData() {} - /** - * Base compressor data assigned for all compressors. - */ - class CompressorData - { - public: - virtual ~CompressorData() {} + Compressor* m_compressor; + AZ::u64 m_uncompressedSize; + }; - Compressor* m_compressor; - AZ::u64 m_uncompressedSize; - }; + /** + * All data is stored in network order (big endian). + */ + struct CompressorHeader + { + CompressorHeader() { m_azcs[0] = 0; m_azcs[1] = 0; m_azcs[2] = 0; m_azcs[3] = 0; } - /** - * All data is stored in network order (big endian). - */ - struct CompressorHeader - { - CompressorHeader() { m_azcs[0] = 0; m_azcs[1] = 0; m_azcs[2] = 0; m_azcs[3] = 0; } + bool IsValid() const { return (m_azcs[0] == 'A' && m_azcs[1] == 'Z' && m_azcs[2] == 'C' && m_azcs[3] == 'S'); } + void SetAZCS() { m_azcs[0] = 'A'; m_azcs[1] = 'Z'; m_azcs[2] = 'C'; m_azcs[3] = 'S'; } - inline bool IsValid() const { return (m_azcs[0] == 'A' && m_azcs[1] == 'Z' && m_azcs[2] == 'C' && m_azcs[3] == 'S'); } - void SetAZCS() { m_azcs[0] = 'A'; m_azcs[1] = 'Z'; m_azcs[2] = 'C'; m_azcs[3] = 'S'; } - - char m_azcs[4]; ///< String contains 'AZCS' AmaZon Compressed Stream - AZ::u32 m_compressorId; ///< Compression method. - AZ::u64 m_uncompressedSize; ///< Uncompressed file size. - }; - } // namespace IO -} // namespace AZ - -#endif // AZCORE_IO_COMPRESSOR_H -#pragma once + char m_azcs[4]; ///< String contains 'AZCS' AmaZon Compressed Stream + AZ::u32 m_compressorId; ///< Compression method. + AZ::u64 m_uncompressedSize; ///< Uncompressed file size. + }; +} // namespace AZ::IO diff --git a/Code/Framework/AzCore/AzCore/IO/CompressorStream.cpp b/Code/Framework/AzCore/AzCore/IO/CompressorStream.cpp index 6f82cc428d..a1012ee404 100644 --- a/Code/Framework/AzCore/AzCore/IO/CompressorStream.cpp +++ b/Code/Framework/AzCore/AzCore/IO/CompressorStream.cpp @@ -15,9 +15,7 @@ #include #include -namespace AZ -{ -namespace IO +namespace AZ::IO { /*! \brief Constructs a compressor stream using the supplied filename and OpenFlags to open a file on disk @@ -300,7 +298,4 @@ Compressor* CompressorStream::CreateCompressor(AZ::u32 compressorId) return m_compressor.get(); } -} // namespace IO -} // namespace AZ - - +} // namespace AZ::IO diff --git a/Code/Framework/AzCore/AzCore/IO/CompressorZLib.cpp b/Code/Framework/AzCore/AzCore/IO/CompressorZLib.cpp index f973c0e95a..03a218dbfe 100644 --- a/Code/Framework/AzCore/AzCore/IO/CompressorZLib.cpp +++ b/Code/Framework/AzCore/AzCore/IO/CompressorZLib.cpp @@ -13,543 +13,540 @@ #include #include -namespace AZ +namespace AZ::IO { - namespace IO + //========================================================================= + // CompressorZLib + // [12/13/2012] + //========================================================================= + CompressorZLib::CompressorZLib(unsigned int decompressionCachePerStream, unsigned int dataBufferSize) + : m_lastReadStream(nullptr) + , m_lastReadStreamOffset(0) + , m_lastReadStreamSize(0) + , m_compressedDataBuffer(nullptr) + , m_compressedDataBufferSize(dataBufferSize) + , m_compressedDataBufferUseCount(0) + , m_decompressionCachePerStream(decompressionCachePerStream) + { - //========================================================================= - // CompressorZLib - // [12/13/2012] - //========================================================================= - CompressorZLib::CompressorZLib(unsigned int decompressionCachePerStream, unsigned int dataBufferSize) - : m_lastReadStream(nullptr) - , m_lastReadStreamOffset(0) - , m_lastReadStreamSize(0) - , m_compressedDataBuffer(nullptr) - , m_compressedDataBufferSize(dataBufferSize) - , m_compressedDataBufferUseCount(0) - , m_decompressionCachePerStream(decompressionCachePerStream) + AZ_Assert((dataBufferSize % (32 * 1024)) == 0, "Data buffer size %d must be multiple of 32 KB!", dataBufferSize); + AZ_Assert((decompressionCachePerStream % (32 * 1024)) == 0, "Decompress cache size %d must be multiple of 32 KB!", decompressionCachePerStream); + } + //========================================================================= + // !CompressorZLib + // [12/13/2012] + //========================================================================= + CompressorZLib::~CompressorZLib() + { + AZ_Warning("IO", m_compressedDataBufferUseCount == 0, "CompressorZLib has it's data buffer still referenced, it means that %d compressed streams have NOT closed! Freeing data...", m_compressedDataBufferUseCount); + while (m_compressedDataBufferUseCount) { - AZ_Assert((dataBufferSize % (32 * 1024)) == 0, "Data buffer size %d must be multiple of 32 KB!", dataBufferSize); - AZ_Assert((decompressionCachePerStream % (32 * 1024)) == 0, "Decompress cache size %d must be multiple of 32 KB!", decompressionCachePerStream); + ReleaseDataBuffer(); } + } - //========================================================================= - // !CompressorZLib - // [12/13/2012] - //========================================================================= - CompressorZLib::~CompressorZLib() + //========================================================================= + // GetTypeId + // [12/13/2012] + //========================================================================= + AZ::u32 CompressorZLib::TypeId() + { + return AZ_CRC("ZLib", 0x73887d3a); + } + + //========================================================================= + // ReadHeaderAndData + // [12/13/2012] + //========================================================================= + bool CompressorZLib::ReadHeaderAndData(CompressorStream* stream, AZ::u8* data, unsigned int dataSize) + { + if (stream->GetCompressorData() != nullptr) // we already have compressor data { - AZ_Warning("IO", m_compressedDataBufferUseCount == 0, "CompressorZLib has it's data buffer still referenced, it means that %d compressed streams have NOT closed! Freeing data...", m_compressedDataBufferUseCount); - while (m_compressedDataBufferUseCount) - { - ReleaseDataBuffer(); - } - } - - //========================================================================= - // GetTypeId - // [12/13/2012] - //========================================================================= - AZ::u32 CompressorZLib::TypeId() - { - return AZ_CRC("ZLib", 0x73887d3a); - } - - //========================================================================= - // ReadHeaderAndData - // [12/13/2012] - //========================================================================= - bool CompressorZLib::ReadHeaderAndData(CompressorStream* stream, AZ::u8* data, unsigned int dataSize) - { - if (stream->GetCompressorData() != nullptr) // we already have compressor data - { - return false; - } - - // Read the ZLib header should be after the default compression header... - // We should not be in this function otherwise. - if (dataSize < sizeof(CompressorZLibHeader) + sizeof(ZLib::Header)) - { - AZ_Assert(false, "We did not read enough data, we have only %d bytes left in the buffer and we need %d!", dataSize, sizeof(CompressorZLibHeader) + sizeof(ZLib::Header)); - return false; - } - - AcquireDataBuffer(); - - CompressorZLibHeader* hdr = reinterpret_cast(data); - AZStd::endian_swap(hdr->m_numSeekPoints); - dataSize -= sizeof(CompressorZLibHeader); - data += sizeof(CompressorZLibHeader); - - CompressorZLibData* zlibData = aznew CompressorZLibData; - zlibData->m_compressor = this; - zlibData->m_uncompressedSize = 0; - zlibData->m_zlibHeader = *reinterpret_cast(data); - dataSize -= sizeof(zlibData->m_zlibHeader); - data += sizeof(zlibData->m_zlibHeader); - zlibData->m_decompressNextOffset = sizeof(CompressorHeader) + sizeof(CompressorZLibHeader) + sizeof(ZLib::Header); // start after the headers - - AZ_Assert(hdr->m_numSeekPoints > 0, "We should have at least one seek point for the entire stream!"); - - // go the end of the file and read all sync points. - SizeType compressedFileEnd = stream->GetLength(); - if (compressedFileEnd == 0) - { - delete zlibData; - return false; - } - - zlibData->m_seekPoints.resize(hdr->m_numSeekPoints); - SizeType dataToRead = sizeof(CompressorZLibSeekPoint) * static_cast(hdr->m_numSeekPoints); - SizeType seekPointOffset = compressedFileEnd - dataToRead; - AZ_Assert(seekPointOffset <= compressedFileEnd, "We have an invalid archive, this is impossible!"); - GenericStream* baseStream = stream->GetWrappedStream(); - if (baseStream->ReadAtOffset(dataToRead, zlibData->m_seekPoints.data(), seekPointOffset) != dataToRead) - { - delete zlibData; - return false; - } - for (size_t i = 0; i < zlibData->m_seekPoints.size(); ++i) - { - AZStd::endian_swap(zlibData->m_seekPoints[i].m_compressedOffset); - AZStd::endian_swap(zlibData->m_seekPoints[i].m_uncompressedOffset); - } - - if (m_decompressionCachePerStream) - { - zlibData->m_decompressedCache = reinterpret_cast(azmalloc(m_decompressionCachePerStream, m_CompressedDataBufferAlignment, AZ::SystemAllocator, "CompressorZLib")); - } - - zlibData->m_decompressLastOffset = seekPointOffset; // set the start address of the seek points as the last valid read address for the compressed stream. - - zlibData->m_zlib.StartDecompressor(&zlibData->m_zlibHeader); - - stream->SetCompressorData(zlibData); - - return true; - } - - //========================================================================= - // WriteHeaderAndData - // [12/13/2012] - //========================================================================= - bool CompressorZLib::WriteHeaderAndData(CompressorStream* stream) - { - if (!Compressor::WriteHeaderAndData(stream)) - { - return false; - } - - CompressorZLibData* compressorData = static_cast(stream->GetCompressorData()); - CompressorZLibHeader header; - header.m_numSeekPoints = static_cast(compressorData->m_seekPoints.size()); - AZStd::endian_swap(header.m_numSeekPoints); - GenericStream* baseStream = stream->GetWrappedStream(); - if (baseStream->WriteAtOffset(sizeof(header), &header, sizeof(CompressorHeader)) == sizeof(header)) - { - return true; - } - return false; } - //========================================================================= - // FillFromDecompressCache - // [12/14/2012] - //========================================================================= - inline CompressorZLib::SizeType CompressorZLib::FillFromDecompressCache(CompressorZLibData* zlibData, void*& buffer, SizeType& byteSize, SizeType& offset) + // Read the ZLib header should be after the default compression header... + // We should not be in this function otherwise. + if (dataSize < sizeof(CompressorZLibHeader) + sizeof(ZLib::Header)) { - SizeType firstOffsetInCache = zlibData->m_decompressedCacheOffset; - SizeType lastOffsetInCache = firstOffsetInCache + zlibData->m_decompressedCacheDataSize; - SizeType firstDataOffset = offset; - SizeType lastDataOffset = offset + byteSize; - SizeType numCopied = 0; - if (firstOffsetInCache < lastDataOffset && lastOffsetInCache > firstDataOffset) // check if there is data in the cache - { - size_t copyOffsetStart = 0; - size_t copyOffsetEnd = zlibData->m_decompressedCacheDataSize; - - size_t bufferCopyOffset = 0; - - if (firstOffsetInCache < firstDataOffset) - { - copyOffsetStart = static_cast(firstDataOffset - firstOffsetInCache); - } - else - { - bufferCopyOffset = static_cast(firstOffsetInCache - firstDataOffset); - } - - if (lastOffsetInCache >= lastDataOffset) - { - copyOffsetEnd -= static_cast(lastOffsetInCache - lastDataOffset); - } - else if (bufferCopyOffset > 0) // the cache block is in the middle of the data, we can't use it (since we need to split buffer request into 2) - { - return 0; - } - - numCopied = copyOffsetEnd - copyOffsetStart; - memcpy(static_cast(buffer) + bufferCopyOffset, zlibData->m_decompressedCache + copyOffsetStart, static_cast(numCopied)); - - // adjust pointers and sizes - byteSize -= numCopied; - if (bufferCopyOffset == 0) - { - // copied in the start - buffer = reinterpret_cast(buffer) + numCopied; - offset += numCopied; - } - } - - return numCopied; + AZ_Assert(false, "We did not read enough data, we have only %d bytes left in the buffer and we need %d!", dataSize, sizeof(CompressorZLibHeader) + sizeof(ZLib::Header)); + return false; } - //========================================================================= - // FillFromCompressedCache - // [12/17/2012] - //========================================================================= - inline CompressorZLib::SizeType CompressorZLib::FillCompressedBuffer(CompressorStream* stream) + AcquireDataBuffer(); + + CompressorZLibHeader* hdr = reinterpret_cast(data); + AZStd::endian_swap(hdr->m_numSeekPoints); + dataSize -= sizeof(CompressorZLibHeader); + data += sizeof(CompressorZLibHeader); + + CompressorZLibData* zlibData = aznew CompressorZLibData; + zlibData->m_compressor = this; + zlibData->m_uncompressedSize = 0; + zlibData->m_zlibHeader = *reinterpret_cast(data); + dataSize -= sizeof(zlibData->m_zlibHeader); + data += sizeof(zlibData->m_zlibHeader); + zlibData->m_decompressNextOffset = sizeof(CompressorHeader) + sizeof(CompressorZLibHeader) + sizeof(ZLib::Header); // start after the headers + + AZ_Assert(hdr->m_numSeekPoints > 0, "We should have at least one seek point for the entire stream!"); + + // go the end of the file and read all sync points. + SizeType compressedFileEnd = stream->GetLength(); + if (compressedFileEnd == 0) { - CompressorZLibData* zlibData = static_cast(stream->GetCompressorData()); - SizeType dataFromBuffer = 0; - if (stream == m_lastReadStream) // if the buffer is filled with data from the current stream, try to reuse - { - if (zlibData->m_decompressNextOffset > m_lastReadStreamOffset) - { - SizeType offsetInCache = zlibData->m_decompressNextOffset - m_lastReadStreamOffset; - if (offsetInCache < m_lastReadStreamSize) // last check if there is data overlap - { - // copy the usable part at the start of the - SizeType toMove = m_lastReadStreamSize - offsetInCache; - memmove(m_compressedDataBuffer, &m_compressedDataBuffer[static_cast(offsetInCache)], static_cast(toMove)); - dataFromBuffer += toMove; - } - } - } - - SizeType toReadFromStream = m_compressedDataBufferSize - dataFromBuffer; - SizeType readOffset = zlibData->m_decompressNextOffset + dataFromBuffer; - if (readOffset + toReadFromStream > zlibData->m_decompressLastOffset) - { - // don't read pass the end - AZ_Assert(readOffset <= zlibData->m_decompressLastOffset, "Read offset should always be before the end of stream!"); - toReadFromStream = zlibData->m_decompressLastOffset - readOffset; - } - - SizeType numReadFromStream = 0; - if (toReadFromStream) // if we did not reuse the whole buffer, read some data from the stream - { - GenericStream* baseStream = stream->GetWrappedStream(); - numReadFromStream = baseStream->ReadAtOffset(toReadFromStream, &m_compressedDataBuffer[static_cast(dataFromBuffer)], readOffset); - } - - // update what's actually in the read data buffer. - m_lastReadStream = stream; - m_lastReadStreamOffset = zlibData->m_decompressNextOffset; - m_lastReadStreamSize = dataFromBuffer + numReadFromStream; - return m_lastReadStreamSize; + delete zlibData; + return false; } - /** - * Helper class to find the best seek point for a specific offset. - */ - struct CompareUpper + zlibData->m_seekPoints.resize(hdr->m_numSeekPoints); + SizeType dataToRead = sizeof(CompressorZLibSeekPoint) * static_cast(hdr->m_numSeekPoints); + SizeType seekPointOffset = compressedFileEnd - dataToRead; + AZ_Assert(seekPointOffset <= compressedFileEnd, "We have an invalid archive, this is impossible!"); + GenericStream* baseStream = stream->GetWrappedStream(); + if (baseStream->ReadAtOffset(dataToRead, zlibData->m_seekPoints.data(), seekPointOffset) != dataToRead) { - inline bool operator()(const AZ::u64& offset, const CompressorZLibSeekPoint& sp) const {return offset < sp.m_uncompressedOffset; } - }; - - //========================================================================= - // Read - // [12/13/2012] - //========================================================================= - CompressorZLib::SizeType CompressorZLib::Read(CompressorStream* stream, SizeType byteSize, SizeType offset, void* buffer) + delete zlibData; + return false; + } + for (size_t i = 0; i < zlibData->m_seekPoints.size(); ++i) { - AZ_Assert(stream->GetCompressorData(), "This stream doesn't have decompression enabled!"); - CompressorZLibData* zlibData = static_cast(stream->GetCompressorData()); - AZ_Assert(!zlibData->m_zlib.IsCompressorStarted(), "You can't read/decompress while writing a compressed stream %s!"); + AZStd::endian_swap(zlibData->m_seekPoints[i].m_compressedOffset); + AZStd::endian_swap(zlibData->m_seekPoints[i].m_uncompressedOffset); + } - // check if the request can be finished from the decompressed cache - SizeType numRead = FillFromDecompressCache(zlibData, buffer, byteSize, offset); - if (byteSize == 0) // are we done + if (m_decompressionCachePerStream) + { + zlibData->m_decompressedCache = reinterpret_cast(azmalloc(m_decompressionCachePerStream, m_CompressedDataBufferAlignment, AZ::SystemAllocator, "CompressorZLib")); + } + + zlibData->m_decompressLastOffset = seekPointOffset; // set the start address of the seek points as the last valid read address for the compressed stream. + + zlibData->m_zlib.StartDecompressor(&zlibData->m_zlibHeader); + + stream->SetCompressorData(zlibData); + + return true; + } + + //========================================================================= + // WriteHeaderAndData + // [12/13/2012] + //========================================================================= + bool CompressorZLib::WriteHeaderAndData(CompressorStream* stream) + { + if (!Compressor::WriteHeaderAndData(stream)) + { + return false; + } + + CompressorZLibData* compressorData = static_cast(stream->GetCompressorData()); + CompressorZLibHeader header; + header.m_numSeekPoints = static_cast(compressorData->m_seekPoints.size()); + AZStd::endian_swap(header.m_numSeekPoints); + GenericStream* baseStream = stream->GetWrappedStream(); + if (baseStream->WriteAtOffset(sizeof(header), &header, sizeof(CompressorHeader)) == sizeof(header)) + { + return true; + } + + return false; + } + + //========================================================================= + // FillFromDecompressCache + // [12/14/2012] + //========================================================================= + inline CompressorZLib::SizeType CompressorZLib::FillFromDecompressCache(CompressorZLibData* zlibData, void*& buffer, SizeType& byteSize, SizeType& offset) + { + SizeType firstOffsetInCache = zlibData->m_decompressedCacheOffset; + SizeType lastOffsetInCache = firstOffsetInCache + zlibData->m_decompressedCacheDataSize; + SizeType firstDataOffset = offset; + SizeType lastDataOffset = offset + byteSize; + SizeType numCopied = 0; + if (firstOffsetInCache < lastDataOffset && lastOffsetInCache > firstDataOffset) // check if there is data in the cache + { + size_t copyOffsetStart = 0; + size_t copyOffsetEnd = zlibData->m_decompressedCacheDataSize; + + size_t bufferCopyOffset = 0; + + if (firstOffsetInCache < firstDataOffset) { - return numRead; + copyOffsetStart = static_cast(firstDataOffset - firstOffsetInCache); + } + else + { + bufferCopyOffset = static_cast(firstOffsetInCache - firstDataOffset); } - // find the best seek point for current offset - CompressorZLibData::SeekPointArray::iterator it = AZStd::upper_bound(zlibData->m_seekPoints.begin(), zlibData->m_seekPoints.end(), offset, CompareUpper()); - AZ_Assert(it != zlibData->m_seekPoints.begin(), "This should be impossible, we should always have a valid seek point at 0 offset!"); - const CompressorZLibSeekPoint& bestSeekPoint = *(--it); // get the previous (so it includes the current offset) - - // if read is continuous continue with decompression - bool isJumpToSeekPoint = false; - SizeType lastOffsetInCache = zlibData->m_decompressedCacheOffset + zlibData->m_decompressedCacheDataSize; - if (bestSeekPoint.m_uncompressedOffset > lastOffsetInCache) // if the best seek point is forward, jump forward to it. + if (lastOffsetInCache >= lastDataOffset) { - isJumpToSeekPoint = true; + copyOffsetEnd -= static_cast(lastOffsetInCache - lastDataOffset); } - else if (offset < lastOffsetInCache) // if the seek point is in the past and the requested offset is not in the cache jump back to it. + else if (bufferCopyOffset > 0) // the cache block is in the middle of the data, we can't use it (since we need to split buffer request into 2) { - isJumpToSeekPoint = true; + return 0; } - if (isJumpToSeekPoint) - { - zlibData->m_decompressNextOffset = bestSeekPoint.m_compressedOffset; // set next read point - zlibData->m_decompressedCacheOffset = bestSeekPoint.m_uncompressedOffset; // set uncompressed offset - zlibData->m_decompressedCacheDataSize = 0; // invalidate the cache - zlibData->m_zlib.ResetDecompressor(&zlibData->m_zlibHeader); // reset decompressor and setup the header. - } + numCopied = copyOffsetEnd - copyOffsetStart; + memcpy(static_cast(buffer) + bufferCopyOffset, zlibData->m_decompressedCache + copyOffsetStart, static_cast(numCopied)); - // decompress and move forward until the request is done - while (byteSize > 0) + // adjust pointers and sizes + byteSize -= numCopied; + if (bufferCopyOffset == 0) { - // fill buffer with compressed data - SizeType compressedDataSize = FillCompressedBuffer(stream); - if (compressedDataSize == 0) + // copied in the start + buffer = reinterpret_cast(buffer) + numCopied; + offset += numCopied; + } + } + + return numCopied; + } + + //========================================================================= + // FillFromCompressedCache + // [12/17/2012] + //========================================================================= + inline CompressorZLib::SizeType CompressorZLib::FillCompressedBuffer(CompressorStream* stream) + { + CompressorZLibData* zlibData = static_cast(stream->GetCompressorData()); + SizeType dataFromBuffer = 0; + if (stream == m_lastReadStream) // if the buffer is filled with data from the current stream, try to reuse + { + if (zlibData->m_decompressNextOffset > m_lastReadStreamOffset) + { + SizeType offsetInCache = zlibData->m_decompressNextOffset - m_lastReadStreamOffset; + if (offsetInCache < m_lastReadStreamSize) // last check if there is data overlap { - return numRead; // we are done reading and obviously we did not managed to read all data + // copy the usable part at the start of the + SizeType toMove = m_lastReadStreamSize - offsetInCache; + memmove(m_compressedDataBuffer, &m_compressedDataBuffer[static_cast(offsetInCache)], static_cast(toMove)); + dataFromBuffer += toMove; } - unsigned int processedCompressedData = 0; - while (byteSize > 0 && processedCompressedData < compressedDataSize) // decompressed data either until we are done with the request (byteSize == 0) or we need to fill the compression buffer again. - { - // if we have data in the cache move to the next offset, we always move forward by default. - zlibData->m_decompressedCacheOffset += zlibData->m_decompressedCacheDataSize; - - // decompress in the cache buffer - u32 availDecompressedCacheSize = m_decompressionCachePerStream; // reset buffer size - unsigned int processed = zlibData->m_zlib.Decompress(&m_compressedDataBuffer[processedCompressedData], static_cast(compressedDataSize) - processedCompressedData, zlibData->m_decompressedCache, availDecompressedCacheSize); - zlibData->m_decompressedCacheDataSize = m_decompressionCachePerStream - availDecompressedCacheSize; - if (processed == 0) - { - break; // we processed everything we could, load more compressed data. - } - processedCompressedData += processed; - // fill what we can from the cache - numRead += FillFromDecompressCache(zlibData, buffer, byteSize, offset); - } - // update next read position the the compressed stream - zlibData->m_decompressNextOffset += processedCompressedData; } + } + + SizeType toReadFromStream = m_compressedDataBufferSize - dataFromBuffer; + SizeType readOffset = zlibData->m_decompressNextOffset + dataFromBuffer; + if (readOffset + toReadFromStream > zlibData->m_decompressLastOffset) + { + // don't read pass the end + AZ_Assert(readOffset <= zlibData->m_decompressLastOffset, "Read offset should always be before the end of stream!"); + toReadFromStream = zlibData->m_decompressLastOffset - readOffset; + } + + SizeType numReadFromStream = 0; + if (toReadFromStream) // if we did not reuse the whole buffer, read some data from the stream + { + GenericStream* baseStream = stream->GetWrappedStream(); + numReadFromStream = baseStream->ReadAtOffset(toReadFromStream, &m_compressedDataBuffer[static_cast(dataFromBuffer)], readOffset); + } + + // update what's actually in the read data buffer. + m_lastReadStream = stream; + m_lastReadStreamOffset = zlibData->m_decompressNextOffset; + m_lastReadStreamSize = dataFromBuffer + numReadFromStream; + return m_lastReadStreamSize; + } + + /** + * Helper class to find the best seek point for a specific offset. + */ + struct CompareUpper + { + inline bool operator()(const AZ::u64& offset, const CompressorZLibSeekPoint& sp) const {return offset < sp.m_uncompressedOffset; } + }; + + //========================================================================= + // Read + // [12/13/2012] + //========================================================================= + CompressorZLib::SizeType CompressorZLib::Read(CompressorStream* stream, SizeType byteSize, SizeType offset, void* buffer) + { + AZ_Assert(stream->GetCompressorData(), "This stream doesn't have decompression enabled!"); + CompressorZLibData* zlibData = static_cast(stream->GetCompressorData()); + AZ_Assert(!zlibData->m_zlib.IsCompressorStarted(), "You can't read/decompress while writing a compressed stream %s!"); + + // check if the request can be finished from the decompressed cache + SizeType numRead = FillFromDecompressCache(zlibData, buffer, byteSize, offset); + if (byteSize == 0) // are we done + { return numRead; } - //========================================================================= - // Write - // [12/13/2012] - //========================================================================= - CompressorZLib::SizeType CompressorZLib::Write(CompressorStream* stream, SizeType byteSize, const void* data, SizeType offset) + // find the best seek point for current offset + CompressorZLibData::SeekPointArray::iterator it = AZStd::upper_bound(zlibData->m_seekPoints.begin(), zlibData->m_seekPoints.end(), offset, CompareUpper()); + AZ_Assert(it != zlibData->m_seekPoints.begin(), "This should be impossible, we should always have a valid seek point at 0 offset!"); + const CompressorZLibSeekPoint& bestSeekPoint = *(--it); // get the previous (so it includes the current offset) + + // if read is continuous continue with decompression + bool isJumpToSeekPoint = false; + SizeType lastOffsetInCache = zlibData->m_decompressedCacheOffset + zlibData->m_decompressedCacheDataSize; + if (bestSeekPoint.m_uncompressedOffset > lastOffsetInCache) // if the best seek point is forward, jump forward to it. { - (void)offset; + isJumpToSeekPoint = true; + } + else if (offset < lastOffsetInCache) // if the seek point is in the past and the requested offset is not in the cache jump back to it. + { + isJumpToSeekPoint = true; + } - AZ_Assert(stream && stream->GetCompressorData(), "This stream doesn't have compression enabled! Call Stream::WriteCompressed after you create the file!"); - AZ_Assert(offset == SizeType(-1) || offset == stream->GetCurPos(), "We can write compressed data only at the end of the stream!"); + if (isJumpToSeekPoint) + { + zlibData->m_decompressNextOffset = bestSeekPoint.m_compressedOffset; // set next read point + zlibData->m_decompressedCacheOffset = bestSeekPoint.m_uncompressedOffset; // set uncompressed offset + zlibData->m_decompressedCacheDataSize = 0; // invalidate the cache + zlibData->m_zlib.ResetDecompressor(&zlibData->m_zlibHeader); // reset decompressor and setup the header. + } - m_lastReadStream = nullptr; // invalidate last read position, otherwise m_dataBuffer will be corrupted (as we are about to write in it). - - CompressorZLibData* zlibData = static_cast(stream->GetCompressorData()); - AZ_Assert(!zlibData->m_zlib.IsDecompressorStarted(), "You can't write while reading/decompressing a compressed stream!"); - - const u8* bytes = reinterpret_cast(data); - unsigned int dataToCompress = static_cast(byteSize); - while (dataToCompress != 0) + // decompress and move forward until the request is done + while (byteSize > 0) + { + // fill buffer with compressed data + SizeType compressedDataSize = FillCompressedBuffer(stream); + if (compressedDataSize == 0) { - unsigned int oldDataToCompress = dataToCompress; - unsigned int compressedSize = zlibData->m_zlib.Compress(bytes, dataToCompress, m_compressedDataBuffer, m_compressedDataBufferSize); - if (compressedSize) - { - GenericStream* baseStream = stream->GetWrappedStream(); - SizeType numWritten = baseStream->Write(compressedSize, m_compressedDataBuffer); - if (numWritten != compressedSize) - { - return numWritten; // error we could not write all data - } - } - bytes += oldDataToCompress - dataToCompress; + return numRead; // we are done reading and obviously we did not managed to read all data } - zlibData->m_uncompressedSize += byteSize; - - if (zlibData->m_autoSeekSize > 0) + unsigned int processedCompressedData = 0; + while (byteSize > 0 && processedCompressedData < compressedDataSize) // decompressed data either until we are done with the request (byteSize == 0) or we need to fill the compression buffer again. { - // insert a seek point if needed. - if (zlibData->m_seekPoints.empty()) + // if we have data in the cache move to the next offset, we always move forward by default. + zlibData->m_decompressedCacheOffset += zlibData->m_decompressedCacheDataSize; + + // decompress in the cache buffer + u32 availDecompressedCacheSize = m_decompressionCachePerStream; // reset buffer size + unsigned int processed = zlibData->m_zlib.Decompress(&m_compressedDataBuffer[processedCompressedData], static_cast(compressedDataSize) - processedCompressedData, zlibData->m_decompressedCache, availDecompressedCacheSize); + zlibData->m_decompressedCacheDataSize = m_decompressionCachePerStream - availDecompressedCacheSize; + if (processed == 0) { - if (zlibData->m_uncompressedSize >= zlibData->m_autoSeekSize) - { - WriteSeekPoint(stream); - } + break; // we processed everything we could, load more compressed data. } - else if ((zlibData->m_uncompressedSize - zlibData->m_seekPoints.back().m_uncompressedOffset) > zlibData->m_autoSeekSize) + processedCompressedData += processed; + // fill what we can from the cache + numRead += FillFromDecompressCache(zlibData, buffer, byteSize, offset); + } + // update next read position the the compressed stream + zlibData->m_decompressNextOffset += processedCompressedData; + } + return numRead; + } + + //========================================================================= + // Write + // [12/13/2012] + //========================================================================= + CompressorZLib::SizeType CompressorZLib::Write(CompressorStream* stream, SizeType byteSize, const void* data, SizeType offset) + { + (void)offset; + + AZ_Assert(stream && stream->GetCompressorData(), "This stream doesn't have compression enabled! Call Stream::WriteCompressed after you create the file!"); + AZ_Assert(offset == SizeType(-1) || offset == stream->GetCurPos(), "We can write compressed data only at the end of the stream!"); + + m_lastReadStream = nullptr; // invalidate last read position, otherwise m_dataBuffer will be corrupted (as we are about to write in it). + + CompressorZLibData* zlibData = static_cast(stream->GetCompressorData()); + AZ_Assert(!zlibData->m_zlib.IsDecompressorStarted(), "You can't write while reading/decompressing a compressed stream!"); + + const u8* bytes = reinterpret_cast(data); + unsigned int dataToCompress = static_cast(byteSize); + while (dataToCompress != 0) + { + unsigned int oldDataToCompress = dataToCompress; + unsigned int compressedSize = zlibData->m_zlib.Compress(bytes, dataToCompress, m_compressedDataBuffer, m_compressedDataBufferSize); + if (compressedSize) + { + GenericStream* baseStream = stream->GetWrappedStream(); + SizeType numWritten = baseStream->Write(compressedSize, m_compressedDataBuffer); + if (numWritten != compressedSize) + { + return numWritten; // error we could not write all data + } + } + bytes += oldDataToCompress - dataToCompress; + } + zlibData->m_uncompressedSize += byteSize; + + if (zlibData->m_autoSeekSize > 0) + { + // insert a seek point if needed. + if (zlibData->m_seekPoints.empty()) + { + if (zlibData->m_uncompressedSize >= zlibData->m_autoSeekSize) { WriteSeekPoint(stream); } } - return byteSize; + else if ((zlibData->m_uncompressedSize - zlibData->m_seekPoints.back().m_uncompressedOffset) > zlibData->m_autoSeekSize) + { + WriteSeekPoint(stream); + } } + return byteSize; + } - //========================================================================= - // WriteSeekPoint - // [12/13/2012] - //========================================================================= - bool CompressorZLib::WriteSeekPoint(CompressorStream* stream) + //========================================================================= + // WriteSeekPoint + // [12/13/2012] + //========================================================================= + bool CompressorZLib::WriteSeekPoint(CompressorStream* stream) + { + AZ_Assert(stream && stream->GetCompressorData(), "This stream doesn't have compression enabled! Call Stream::WriteCompressed after you create the file!"); + CompressorZLibData* zlibData = static_cast(stream->GetCompressorData()); + + m_lastReadStream = nullptr; // invalidate last read position, otherwise m_dataBuffer will be corrupted (as we are about to write in it). + + unsigned int compressedSize; + unsigned int dataToCompress = 0; + do { - AZ_Assert(stream && stream->GetCompressorData(), "This stream doesn't have compression enabled! Call Stream::WriteCompressed after you create the file!"); - CompressorZLibData* zlibData = static_cast(stream->GetCompressorData()); + compressedSize = zlibData->m_zlib.Compress(nullptr, dataToCompress, m_compressedDataBuffer, m_compressedDataBufferSize, ZLib::FT_FULL_FLUSH); + if (compressedSize) + { + GenericStream* baseStream = stream->GetWrappedStream(); + SizeType numWritten = baseStream->Write(compressedSize, m_compressedDataBuffer); + if (numWritten != compressedSize) + { + return false; // error we wrote less than than requested! + } + } + } while (dataToCompress != 0); + CompressorZLibSeekPoint sp; + sp.m_compressedOffset = stream->GetLength(); + sp.m_uncompressedOffset = zlibData->m_uncompressedSize; + zlibData->m_seekPoints.push_back(sp); + return true; + } + + //========================================================================= + // StartCompressor + // [12/13/2012] + //========================================================================= + bool CompressorZLib::StartCompressor(CompressorStream* stream, int compressionLevel, SizeType autoSeekDataSize) + { + AZ_Assert(stream && stream->GetCompressorData() == nullptr, "Stream has compressor already enabled!"); + + AcquireDataBuffer(); + + CompressorZLibData* zlibData = aznew CompressorZLibData; + zlibData->m_compressor = this; + zlibData->m_zlibHeader = 0; // not used for compression + zlibData->m_uncompressedSize = 0; + zlibData->m_autoSeekSize = autoSeekDataSize; + compressionLevel = AZ::GetClamp(compressionLevel, 1, 9); // remap to zlib levels + + zlibData->m_zlib.StartCompressor(compressionLevel); + + stream->SetCompressorData(zlibData); + + if (WriteHeaderAndData(stream)) + { + // add the first and always present seek point at the start of the compressed stream + CompressorZLibSeekPoint sp; + sp.m_compressedOffset = sizeof(CompressorHeader) + sizeof(CompressorZLibHeader) + sizeof(zlibData->m_zlibHeader); + sp.m_uncompressedOffset = 0; + zlibData->m_seekPoints.push_back(sp); + return true; + } + return false; + } + + //========================================================================= + // Close + // [12/13/2012] + //========================================================================= + bool CompressorZLib::Close(CompressorStream* stream) + { + AZ_Assert(stream->IsOpen(), "Stream is not open to be closed!"); + + CompressorZLibData* zlibData = static_cast(stream->GetCompressorData()); + GenericStream* baseStream = stream->GetWrappedStream(); + + bool result = true; + if (zlibData->m_zlib.IsCompressorStarted()) + { m_lastReadStream = nullptr; // invalidate last read position, otherwise m_dataBuffer will be corrupted (as we are about to write in it). + // flush all compressed data unsigned int compressedSize; unsigned int dataToCompress = 0; do { - compressedSize = zlibData->m_zlib.Compress(nullptr, dataToCompress, m_compressedDataBuffer, m_compressedDataBufferSize, ZLib::FT_FULL_FLUSH); + compressedSize = zlibData->m_zlib.Compress(nullptr, dataToCompress, m_compressedDataBuffer, m_compressedDataBufferSize, ZLib::FT_FINISH); if (compressedSize) { - GenericStream* baseStream = stream->GetWrappedStream(); - SizeType numWritten = baseStream->Write(compressedSize, m_compressedDataBuffer); - if (numWritten != compressedSize) - { - return false; // error we wrote less than than requested! - } + baseStream->Write(compressedSize, m_compressedDataBuffer); } } while (dataToCompress != 0); - CompressorZLibSeekPoint sp; - sp.m_compressedOffset = stream->GetLength(); - sp.m_uncompressedOffset = zlibData->m_uncompressedSize; - zlibData->m_seekPoints.push_back(sp); - return true; - } - - //========================================================================= - // StartCompressor - // [12/13/2012] - //========================================================================= - bool CompressorZLib::StartCompressor(CompressorStream* stream, int compressionLevel, SizeType autoSeekDataSize) - { - AZ_Assert(stream && stream->GetCompressorData() == nullptr, "Stream has compressor already enabled!"); - - AcquireDataBuffer(); - - CompressorZLibData* zlibData = aznew CompressorZLibData; - zlibData->m_compressor = this; - zlibData->m_zlibHeader = 0; // not used for compression - zlibData->m_uncompressedSize = 0; - zlibData->m_autoSeekSize = autoSeekDataSize; - compressionLevel = AZ::GetClamp(compressionLevel, 1, 9); // remap to zlib levels - - zlibData->m_zlib.StartCompressor(compressionLevel); - - stream->SetCompressorData(zlibData); - - if (WriteHeaderAndData(stream)) + result = WriteHeaderAndData(stream); + if (result) { - // add the first and always present seek point at the start of the compressed stream - CompressorZLibSeekPoint sp; - sp.m_compressedOffset = sizeof(CompressorHeader) + sizeof(CompressorZLibHeader) + sizeof(zlibData->m_zlibHeader); - sp.m_uncompressedOffset = 0; - zlibData->m_seekPoints.push_back(sp); - return true; - } - return false; - } - - //========================================================================= - // Close - // [12/13/2012] - //========================================================================= - bool CompressorZLib::Close(CompressorStream* stream) - { - AZ_Assert(stream->IsOpen(), "Stream is not open to be closed!"); - - CompressorZLibData* zlibData = static_cast(stream->GetCompressorData()); - GenericStream* baseStream = stream->GetWrappedStream(); - - bool result = true; - if (zlibData->m_zlib.IsCompressorStarted()) - { - m_lastReadStream = nullptr; // invalidate last read position, otherwise m_dataBuffer will be corrupted (as we are about to write in it). - - // flush all compressed data - unsigned int compressedSize; - unsigned int dataToCompress = 0; - do + // now write the seek points and the end of the file + for (size_t i = 0; i < zlibData->m_seekPoints.size(); ++i) { - compressedSize = zlibData->m_zlib.Compress(nullptr, dataToCompress, m_compressedDataBuffer, m_compressedDataBufferSize, ZLib::FT_FINISH); - if (compressedSize) - { - baseStream->Write(compressedSize, m_compressedDataBuffer); - } - } while (dataToCompress != 0); - - result = WriteHeaderAndData(stream); - if (result) - { - // now write the seek points and the end of the file - for (size_t i = 0; i < zlibData->m_seekPoints.size(); ++i) - { - AZStd::endian_swap(zlibData->m_seekPoints[i].m_compressedOffset); - AZStd::endian_swap(zlibData->m_seekPoints[i].m_uncompressedOffset); - } - SizeType dataToWrite = zlibData->m_seekPoints.size() * sizeof(CompressorZLibSeekPoint); - baseStream->Seek(0U, GenericStream::SeekMode::ST_SEEK_END); - result = (baseStream->Write(dataToWrite, zlibData->m_seekPoints.data()) == dataToWrite); + AZStd::endian_swap(zlibData->m_seekPoints[i].m_compressedOffset); + AZStd::endian_swap(zlibData->m_seekPoints[i].m_uncompressedOffset); } + SizeType dataToWrite = zlibData->m_seekPoints.size() * sizeof(CompressorZLibSeekPoint); + baseStream->Seek(0U, GenericStream::SeekMode::ST_SEEK_END); + result = (baseStream->Write(dataToWrite, zlibData->m_seekPoints.data()) == dataToWrite); } - else - { - if (m_lastReadStream == stream) - { - m_lastReadStream = nullptr; // invalidate the data in m_dataBuffer if it was from the current stream. - } - } - - // if we have decompressor cache delete it - if (zlibData->m_decompressedCache) - { - azfree(zlibData->m_decompressedCache, AZ::SystemAllocator, m_decompressionCachePerStream, m_CompressedDataBufferAlignment); - } - - ReleaseDataBuffer(); - - // last step reset strream compressor data. - stream->SetCompressorData(nullptr); - return result; } - - //========================================================================= - // AcquireDataBuffer - // [2/27/2013] - //========================================================================= - void CompressorZLib::AcquireDataBuffer() + else { - if (m_compressedDataBuffer == nullptr) + if (m_lastReadStream == stream) { - AZ_Assert(m_compressedDataBufferUseCount == 0, "Buffer usecount should be 0 if the buffer is NULL"); - m_compressedDataBuffer = reinterpret_cast(azmalloc(m_compressedDataBufferSize, m_CompressedDataBufferAlignment, AZ::SystemAllocator, "CompressorZLib")); - m_lastReadStream = nullptr; // reset the cache info in the m_dataBuffer + m_lastReadStream = nullptr; // invalidate the data in m_dataBuffer if it was from the current stream. } - ++m_compressedDataBufferUseCount; } - //========================================================================= - // ReleaseDataBuffer - // [2/27/2013] - //========================================================================= - void CompressorZLib::ReleaseDataBuffer() + // if we have decompressor cache delete it + if (zlibData->m_decompressedCache) { - --m_compressedDataBufferUseCount; - if (m_compressedDataBufferUseCount == 0) - { - AZ_Assert(m_compressedDataBuffer != nullptr, "Invalid data buffer! We should have a non null pointer!"); - azfree(m_compressedDataBuffer, AZ::SystemAllocator, m_compressedDataBufferSize, m_CompressedDataBufferAlignment); - m_compressedDataBuffer = nullptr; - m_lastReadStream = nullptr; // reset the cache info in the m_dataBuffer - } + azfree(zlibData->m_decompressedCache, AZ::SystemAllocator, m_decompressionCachePerStream, m_CompressedDataBufferAlignment); } - } // namespace IO -} // namespace AZ + + ReleaseDataBuffer(); + + // last step reset strream compressor data. + stream->SetCompressorData(nullptr); + return result; + } + + //========================================================================= + // AcquireDataBuffer + // [2/27/2013] + //========================================================================= + void CompressorZLib::AcquireDataBuffer() + { + if (m_compressedDataBuffer == nullptr) + { + AZ_Assert(m_compressedDataBufferUseCount == 0, "Buffer usecount should be 0 if the buffer is NULL"); + m_compressedDataBuffer = reinterpret_cast(azmalloc(m_compressedDataBufferSize, m_CompressedDataBufferAlignment, AZ::SystemAllocator, "CompressorZLib")); + m_lastReadStream = nullptr; // reset the cache info in the m_dataBuffer + } + ++m_compressedDataBufferUseCount; + } + + //========================================================================= + // ReleaseDataBuffer + // [2/27/2013] + //========================================================================= + void CompressorZLib::ReleaseDataBuffer() + { + --m_compressedDataBufferUseCount; + if (m_compressedDataBufferUseCount == 0) + { + AZ_Assert(m_compressedDataBuffer != nullptr, "Invalid data buffer! We should have a non null pointer!"); + azfree(m_compressedDataBuffer, AZ::SystemAllocator, m_compressedDataBufferSize, m_CompressedDataBufferAlignment); + m_compressedDataBuffer = nullptr; + m_lastReadStream = nullptr; // reset the cache info in the m_dataBuffer + } + } +} // namespace AZ::IO #endif // #if !defined(AZCORE_EXCLUDE_ZLIB) diff --git a/Code/Framework/AzCore/AzCore/IO/CompressorZStd.cpp b/Code/Framework/AzCore/AzCore/IO/CompressorZStd.cpp index b1631d2d22..91f380d73b 100644 --- a/Code/Framework/AzCore/AzCore/IO/CompressorZStd.cpp +++ b/Code/Framework/AzCore/AzCore/IO/CompressorZStd.cpp @@ -14,478 +14,475 @@ #include #include -namespace AZ +namespace AZ::IO { - namespace IO + CompressorZStd::CompressorZStd(unsigned int decompressionCachePerStream, unsigned int dataBufferSize) + : m_compressedDataBufferSize(dataBufferSize) + , m_decompressionCachePerStream(decompressionCachePerStream) + { - CompressorZStd::CompressorZStd(unsigned int decompressionCachePerStream, unsigned int dataBufferSize) - : m_compressedDataBufferSize(dataBufferSize) - , m_decompressionCachePerStream(decompressionCachePerStream) + AZ_Assert((dataBufferSize % (32 * 1024)) == 0, "Data buffer size %d must be multiple of 32 KB.", dataBufferSize); + AZ_Assert((decompressionCachePerStream % (32 * 1024)) == 0, "Decompress cache size %d must be multiple of 32 KB.", decompressionCachePerStream); + } + CompressorZStd::~CompressorZStd() + { + AZ_Warning("IO", m_compressedDataBufferUseCount == 0, "CompressorZStd has it's data buffer still referenced, it means that %d compressed streams have NOT closed. Freeing data...", m_compressedDataBufferUseCount); + while (m_compressedDataBufferUseCount) { - AZ_Assert((dataBufferSize % (32 * 1024)) == 0, "Data buffer size %d must be multiple of 32 KB.", dataBufferSize); - AZ_Assert((decompressionCachePerStream % (32 * 1024)) == 0, "Decompress cache size %d must be multiple of 32 KB.", decompressionCachePerStream); + ReleaseDataBuffer(); } + } - CompressorZStd::~CompressorZStd() + AZ::u32 CompressorZStd::TypeId() + { + return AZ_CRC("ZStd", 0x72fd505e); + } + + bool CompressorZStd::ReadHeaderAndData(CompressorStream* stream, AZ::u8* data, unsigned int dataSize) + { + if (stream->GetCompressorData() != nullptr) // we already have compressor data { - AZ_Warning("IO", m_compressedDataBufferUseCount == 0, "CompressorZStd has it's data buffer still referenced, it means that %d compressed streams have NOT closed. Freeing data...", m_compressedDataBufferUseCount); - while (m_compressedDataBufferUseCount) - { - ReleaseDataBuffer(); - } - } - - AZ::u32 CompressorZStd::TypeId() - { - return AZ_CRC("ZStd", 0x72fd505e); - } - - bool CompressorZStd::ReadHeaderAndData(CompressorStream* stream, AZ::u8* data, unsigned int dataSize) - { - if (stream->GetCompressorData() != nullptr) // we already have compressor data - { - return false; - } - - // Read the ZStd header should be after the default compression header... - // We should not be in this function otherwise. - if (dataSize < sizeof(CompressorZStdHeader) + sizeof(ZStd::Header)) - { - AZ_Error("CompressorZStd", false, "We did not read enough data, we have only %d bytes left in the buffer and we need %d.", dataSize, sizeof(CompressorZStdHeader) + sizeof(ZStd::Header)); - return false; - } - - AcquireDataBuffer(); - - CompressorZStdHeader* hdr = reinterpret_cast(data); - dataSize -= sizeof(CompressorZStdHeader); - data += sizeof(CompressorZStdHeader); - - AZStd::unique_ptr zstdData = AZStd::make_unique(); - zstdData->m_compressor = this; - zstdData->m_uncompressedSize = 0; - zstdData->m_zstdHeader = *reinterpret_cast(data); - dataSize -= sizeof(zstdData->m_zstdHeader); - data += sizeof(zstdData->m_zstdHeader); - zstdData->m_decompressNextOffset = sizeof(CompressorHeader) + sizeof(CompressorZStdHeader) + sizeof(ZStd::Header); // start after the headers - - AZ_Error("CompressorZStd", hdr->m_numSeekPoints > 0, "We should have at least one seek point for the entire stream."); - - // go the end of the file and read all sync points. - SizeType compressedFileEnd = stream->GetLength(); - if (compressedFileEnd == 0) - { - return false; - } - - zstdData->m_seekPoints.resize(hdr->m_numSeekPoints); - SizeType dataToRead = sizeof(CompressorZStdSeekPoint) * static_cast(hdr->m_numSeekPoints); - SizeType seekPointOffset = compressedFileEnd - dataToRead; - - if (seekPointOffset > compressedFileEnd) - { - AZ_Error("CompressorZStd", false, "We have an invalid archive, this is impossible."); - return false; - } - - GenericStream* baseStream = stream->GetWrappedStream(); - if (baseStream->ReadAtOffset(dataToRead, zstdData->m_seekPoints.data(), seekPointOffset) != dataToRead) - { - return false; - } - - if (m_decompressionCachePerStream) - { - zstdData->m_decompressedCache = reinterpret_cast(azmalloc(m_decompressionCachePerStream, m_CompressedDataBufferAlignment, AZ::SystemAllocator, "CompressorZStd")); - } - - zstdData->m_decompressLastOffset = seekPointOffset; // set the start address of the seek points as the last valid read address for the compressed stream. - - zstdData->m_zstd.StartDecompressor(); - - stream->SetCompressorData(zstdData.release()); - - return true; - } - - bool CompressorZStd::WriteHeaderAndData(CompressorStream* stream) - { - if (!Compressor::WriteHeaderAndData(stream)) - { - return false; - } - - CompressorZStdData* compressorData = static_cast(stream->GetCompressorData()); - CompressorZStdHeader header; - header.m_numSeekPoints = aznumeric_caster(compressorData->m_seekPoints.size()); - GenericStream* baseStream = stream->GetWrappedStream(); - if (baseStream->WriteAtOffset(sizeof(header), &header, sizeof(CompressorHeader)) == sizeof(header)) - { - return true; - } - return false; } - inline CompressorZStd::SizeType CompressorZStd::FillFromDecompressCache(CompressorZStdData* zstdData, void*& buffer, SizeType& byteSize, SizeType& offset) + // Read the ZStd header should be after the default compression header... + // We should not be in this function otherwise. + if (dataSize < sizeof(CompressorZStdHeader) + sizeof(ZStd::Header)) { - SizeType firstOffsetInCache = zstdData->m_decompressedCacheOffset; - SizeType lastOffsetInCache = firstOffsetInCache + zstdData->m_decompressedCacheDataSize; - SizeType firstDataOffset = offset; - SizeType lastDataOffset = offset + byteSize; - SizeType numCopied = 0; - if (firstOffsetInCache < lastDataOffset && lastOffsetInCache >= firstDataOffset) // check if there is data in the cache - { - size_t copyOffsetStart = 0; - size_t copyOffsetEnd = zstdData->m_decompressedCacheDataSize; - - size_t bufferCopyOffset = 0; - - if (firstOffsetInCache < firstDataOffset) - { - copyOffsetStart = aznumeric_caster(firstDataOffset - firstOffsetInCache); - } - else - { - bufferCopyOffset = aznumeric_caster(firstOffsetInCache - firstDataOffset); - } - - if (lastOffsetInCache >= lastDataOffset) - { - copyOffsetEnd -= static_cast(lastOffsetInCache - lastDataOffset); - } - else if (bufferCopyOffset > 0) // the cache block is in the middle of the data, we can't use it (since we need to split buffer request into 2) - { - return 0; - } - - numCopied = copyOffsetEnd - copyOffsetStart; - memcpy(static_cast(buffer) + bufferCopyOffset, zstdData->m_decompressedCache + copyOffsetStart, static_cast(numCopied)); - - // adjust pointers and sizes - byteSize -= numCopied; - if (bufferCopyOffset == 0) - { - // copied in the start - buffer = reinterpret_cast(buffer) + numCopied; - offset += numCopied; - } - } - - return numCopied; + AZ_Error("CompressorZStd", false, "We did not read enough data, we have only %d bytes left in the buffer and we need %d.", dataSize, sizeof(CompressorZStdHeader) + sizeof(ZStd::Header)); + return false; } - inline CompressorZStd::SizeType CompressorZStd::FillCompressedBuffer(CompressorStream* stream) + AcquireDataBuffer(); + + CompressorZStdHeader* hdr = reinterpret_cast(data); + dataSize -= sizeof(CompressorZStdHeader); + data += sizeof(CompressorZStdHeader); + + AZStd::unique_ptr zstdData = AZStd::make_unique(); + zstdData->m_compressor = this; + zstdData->m_uncompressedSize = 0; + zstdData->m_zstdHeader = *reinterpret_cast(data); + dataSize -= sizeof(zstdData->m_zstdHeader); + data += sizeof(zstdData->m_zstdHeader); + zstdData->m_decompressNextOffset = sizeof(CompressorHeader) + sizeof(CompressorZStdHeader) + sizeof(ZStd::Header); // start after the headers + + AZ_Error("CompressorZStd", hdr->m_numSeekPoints > 0, "We should have at least one seek point for the entire stream."); + + // go the end of the file and read all sync points. + SizeType compressedFileEnd = stream->GetLength(); + if (compressedFileEnd == 0) { - CompressorZStdData* zstdData = static_cast(stream->GetCompressorData()); - SizeType dataFromBuffer = 0; - if (stream == m_lastReadStream) // if the buffer is filled with data from the current stream, try to reuse - { - if (zstdData->m_decompressNextOffset > m_lastReadStreamOffset) - { - SizeType offsetInCache = zstdData->m_decompressNextOffset - m_lastReadStreamOffset; - if (offsetInCache < m_lastReadStreamSize) // last check if there is data overlap - { - // copy the usable part at the start of the buffer - SizeType toMove = m_lastReadStreamSize - offsetInCache; - memcpy(m_compressedDataBuffer, &m_compressedDataBuffer[static_cast(offsetInCache)], static_cast(toMove)); - dataFromBuffer += toMove; - } - } - } - - SizeType toReadFromStream = m_compressedDataBufferSize - dataFromBuffer; - SizeType readOffset = zstdData->m_decompressNextOffset + dataFromBuffer; - if (readOffset + toReadFromStream > zstdData->m_decompressLastOffset) - { - // don't read past the end - AZ_Assert(readOffset <= zstdData->m_decompressLastOffset, "Read offset should always be before the end of stream."); - toReadFromStream = zstdData->m_decompressLastOffset - readOffset; - } - - SizeType numReadFromStream = 0; - if (toReadFromStream) // if we did not reuse the whole buffer, read some data from the stream - { - GenericStream* baseStream = stream->GetWrappedStream(); - numReadFromStream = baseStream->ReadAtOffset(toReadFromStream, &m_compressedDataBuffer[static_cast(dataFromBuffer)], readOffset); - } - - // update what's actually in the read data buffer. - m_lastReadStream = stream; - m_lastReadStreamOffset = zstdData->m_decompressNextOffset; - m_lastReadStreamSize = dataFromBuffer + numReadFromStream; - return m_lastReadStreamSize; + return false; } - struct ZStdCompareUpper + zstdData->m_seekPoints.resize(hdr->m_numSeekPoints); + SizeType dataToRead = sizeof(CompressorZStdSeekPoint) * static_cast(hdr->m_numSeekPoints); + SizeType seekPointOffset = compressedFileEnd - dataToRead; + + if (seekPointOffset > compressedFileEnd) { - bool operator()(const AZ::u64& offset, const CompressorZStdSeekPoint& sp) const - { - return offset < sp.m_uncompressedOffset; - } - }; + AZ_Error("CompressorZStd", false, "We have an invalid archive, this is impossible."); + return false; + } - CompressorZStd::SizeType CompressorZStd::Read(CompressorStream* stream, SizeType byteSize, SizeType offset, void* buffer) + GenericStream* baseStream = stream->GetWrappedStream(); + if (baseStream->ReadAtOffset(dataToRead, zstdData->m_seekPoints.data(), seekPointOffset) != dataToRead) { - AZ_Assert(stream->GetCompressorData(), "This stream doesn't have decompression enabled."); - CompressorZStdData* zstdData = static_cast(stream->GetCompressorData()); - AZ_Assert(!zstdData->m_zstd.IsCompressorStarted(), "You can't read/decompress while writing a compressed stream %s."); + return false; + } - // check if the request can be finished from the decompressed cache - SizeType numRead = FillFromDecompressCache(zstdData, buffer, byteSize, offset); - if (byteSize == 0) // are we done + if (m_decompressionCachePerStream) + { + zstdData->m_decompressedCache = reinterpret_cast(azmalloc(m_decompressionCachePerStream, m_CompressedDataBufferAlignment, AZ::SystemAllocator, "CompressorZStd")); + } + + zstdData->m_decompressLastOffset = seekPointOffset; // set the start address of the seek points as the last valid read address for the compressed stream. + + zstdData->m_zstd.StartDecompressor(); + + stream->SetCompressorData(zstdData.release()); + + return true; + } + + bool CompressorZStd::WriteHeaderAndData(CompressorStream* stream) + { + if (!Compressor::WriteHeaderAndData(stream)) + { + return false; + } + + CompressorZStdData* compressorData = static_cast(stream->GetCompressorData()); + CompressorZStdHeader header; + header.m_numSeekPoints = aznumeric_caster(compressorData->m_seekPoints.size()); + GenericStream* baseStream = stream->GetWrappedStream(); + if (baseStream->WriteAtOffset(sizeof(header), &header, sizeof(CompressorHeader)) == sizeof(header)) + { + return true; + } + + return false; + } + + inline CompressorZStd::SizeType CompressorZStd::FillFromDecompressCache(CompressorZStdData* zstdData, void*& buffer, SizeType& byteSize, SizeType& offset) + { + SizeType firstOffsetInCache = zstdData->m_decompressedCacheOffset; + SizeType lastOffsetInCache = firstOffsetInCache + zstdData->m_decompressedCacheDataSize; + SizeType firstDataOffset = offset; + SizeType lastDataOffset = offset + byteSize; + SizeType numCopied = 0; + if (firstOffsetInCache < lastDataOffset && lastOffsetInCache >= firstDataOffset) // check if there is data in the cache + { + size_t copyOffsetStart = 0; + size_t copyOffsetEnd = zstdData->m_decompressedCacheDataSize; + + size_t bufferCopyOffset = 0; + + if (firstOffsetInCache < firstDataOffset) { - return numRead; + copyOffsetStart = aznumeric_caster(firstDataOffset - firstOffsetInCache); + } + else + { + bufferCopyOffset = aznumeric_caster(firstOffsetInCache - firstDataOffset); } - // find the best seek point for current offset - CompressorZStdData::SeekPointArray::iterator it = AZStd::upper_bound(zstdData->m_seekPoints.begin(), zstdData->m_seekPoints.end(), offset, ZStdCompareUpper()); - AZ_Assert(it != zstdData->m_seekPoints.begin(), "This should be impossible, we should always have a valid seek point at 0 offset."); - const CompressorZStdSeekPoint& bestSeekPoint = *(--it); // get the previous (so it includes the current offset) - - // if read is continuous continue with decompression - bool isJumpToSeekPoint = false; - SizeType lastOffsetInCache = zstdData->m_decompressedCacheOffset + zstdData->m_decompressedCacheDataSize; - if (bestSeekPoint.m_uncompressedOffset > lastOffsetInCache) // if the best seek point is forward, jump forward to it. + if (lastOffsetInCache >= lastDataOffset) { - isJumpToSeekPoint = true; + copyOffsetEnd -= static_cast(lastOffsetInCache - lastDataOffset); } - else if (offset < lastOffsetInCache) // if the seek point is in the past and the requested offset is not in the cache jump back to it. + else if (bufferCopyOffset > 0) // the cache block is in the middle of the data, we can't use it (since we need to split buffer request into 2) { - isJumpToSeekPoint = true; + return 0; } - if (isJumpToSeekPoint) - { - zstdData->m_decompressNextOffset = bestSeekPoint.m_compressedOffset; // set next read point - zstdData->m_decompressedCacheOffset = bestSeekPoint.m_uncompressedOffset; // set uncompressed offset - zstdData->m_decompressedCacheDataSize = 0; // invalidate the cache - zstdData->m_zstd.ResetDecompressor(&zstdData->m_zstdHeader); // reset decompressor and setup the header. - } + numCopied = copyOffsetEnd - copyOffsetStart; + memcpy(static_cast(buffer) + bufferCopyOffset, zstdData->m_decompressedCache + copyOffsetStart, static_cast(numCopied)); - // decompress and move forward until the request is done - while (byteSize > 0) + // adjust pointers and sizes + byteSize -= numCopied; + if (bufferCopyOffset == 0) { - // fill buffer with compressed data - SizeType compressedDataSize = FillCompressedBuffer(stream); - if (compressedDataSize == 0) + // copied in the start + buffer = reinterpret_cast(buffer) + numCopied; + offset += numCopied; + } + } + + return numCopied; + } + + inline CompressorZStd::SizeType CompressorZStd::FillCompressedBuffer(CompressorStream* stream) + { + CompressorZStdData* zstdData = static_cast(stream->GetCompressorData()); + SizeType dataFromBuffer = 0; + if (stream == m_lastReadStream) // if the buffer is filled with data from the current stream, try to reuse + { + if (zstdData->m_decompressNextOffset > m_lastReadStreamOffset) + { + SizeType offsetInCache = zstdData->m_decompressNextOffset - m_lastReadStreamOffset; + if (offsetInCache < m_lastReadStreamSize) // last check if there is data overlap { - return numRead; // we are done reading and obviously we did not managed to read all data + // copy the usable part at the start of the buffer + SizeType toMove = m_lastReadStreamSize - offsetInCache; + memcpy(m_compressedDataBuffer, &m_compressedDataBuffer[static_cast(offsetInCache)], static_cast(toMove)); + dataFromBuffer += toMove; } - unsigned int processedCompressedData = 0; - while (byteSize > 0 && processedCompressedData < compressedDataSize) // decompressed data either until we are done with the request (byteSize == 0) or we need to fill the compression buffer again. - { - // if we have data in the cache move to the next offset, we always move forward by default. - zstdData->m_decompressedCacheOffset += zstdData->m_decompressedCacheDataSize; - - // decompress in the cache buffer - u32 availDecompressedCacheSize = m_decompressionCachePerStream; // reset buffer size - size_t nextBlockSize; - unsigned int processed = zstdData->m_zstd.Decompress(&m_compressedDataBuffer[processedCompressedData], - static_cast(compressedDataSize) - processedCompressedData, - zstdData->m_decompressedCache, - availDecompressedCacheSize, - &nextBlockSize); - zstdData->m_decompressedCacheDataSize = m_decompressionCachePerStream - availDecompressedCacheSize; - if (processed == 0) - { - break; // we processed everything we could, load more compressed data. - } - processedCompressedData += processed; - // fill what we can from the cache - numRead += FillFromDecompressCache(zstdData, buffer, byteSize, offset); - } - // update next read position the the compressed stream - zstdData->m_decompressNextOffset += processedCompressedData; } + } + + SizeType toReadFromStream = m_compressedDataBufferSize - dataFromBuffer; + SizeType readOffset = zstdData->m_decompressNextOffset + dataFromBuffer; + if (readOffset + toReadFromStream > zstdData->m_decompressLastOffset) + { + // don't read past the end + AZ_Assert(readOffset <= zstdData->m_decompressLastOffset, "Read offset should always be before the end of stream."); + toReadFromStream = zstdData->m_decompressLastOffset - readOffset; + } + + SizeType numReadFromStream = 0; + if (toReadFromStream) // if we did not reuse the whole buffer, read some data from the stream + { + GenericStream* baseStream = stream->GetWrappedStream(); + numReadFromStream = baseStream->ReadAtOffset(toReadFromStream, &m_compressedDataBuffer[static_cast(dataFromBuffer)], readOffset); + } + + // update what's actually in the read data buffer. + m_lastReadStream = stream; + m_lastReadStreamOffset = zstdData->m_decompressNextOffset; + m_lastReadStreamSize = dataFromBuffer + numReadFromStream; + return m_lastReadStreamSize; + } + + struct ZStdCompareUpper + { + bool operator()(const AZ::u64& offset, const CompressorZStdSeekPoint& sp) const + { + return offset < sp.m_uncompressedOffset; + } + }; + + CompressorZStd::SizeType CompressorZStd::Read(CompressorStream* stream, SizeType byteSize, SizeType offset, void* buffer) + { + AZ_Assert(stream->GetCompressorData(), "This stream doesn't have decompression enabled."); + CompressorZStdData* zstdData = static_cast(stream->GetCompressorData()); + AZ_Assert(!zstdData->m_zstd.IsCompressorStarted(), "You can't read/decompress while writing a compressed stream %s."); + + // check if the request can be finished from the decompressed cache + SizeType numRead = FillFromDecompressCache(zstdData, buffer, byteSize, offset); + if (byteSize == 0) // are we done + { return numRead; } - CompressorZStd::SizeType CompressorZStd::Write(CompressorStream* stream, SizeType byteSize, const void* data, SizeType offset) + // find the best seek point for current offset + CompressorZStdData::SeekPointArray::iterator it = AZStd::upper_bound(zstdData->m_seekPoints.begin(), zstdData->m_seekPoints.end(), offset, ZStdCompareUpper()); + AZ_Assert(it != zstdData->m_seekPoints.begin(), "This should be impossible, we should always have a valid seek point at 0 offset."); + const CompressorZStdSeekPoint& bestSeekPoint = *(--it); // get the previous (so it includes the current offset) + + // if read is continuous continue with decompression + bool isJumpToSeekPoint = false; + SizeType lastOffsetInCache = zstdData->m_decompressedCacheOffset + zstdData->m_decompressedCacheDataSize; + if (bestSeekPoint.m_uncompressedOffset > lastOffsetInCache) // if the best seek point is forward, jump forward to it. { - AZ_UNUSED(offset); + isJumpToSeekPoint = true; + } + else if (offset < lastOffsetInCache) // if the seek point is in the past and the requested offset is not in the cache jump back to it. + { + isJumpToSeekPoint = true; + } - AZ_Assert(stream && stream->GetCompressorData(), "This stream doesn't have compression enabled. Call Stream::WriteCompressed after you create the file."); - AZ_Assert(offset == SizeType(-1) || offset == stream->GetCurPos(), "We can write compressed data only at the end of the stream."); + if (isJumpToSeekPoint) + { + zstdData->m_decompressNextOffset = bestSeekPoint.m_compressedOffset; // set next read point + zstdData->m_decompressedCacheOffset = bestSeekPoint.m_uncompressedOffset; // set uncompressed offset + zstdData->m_decompressedCacheDataSize = 0; // invalidate the cache + zstdData->m_zstd.ResetDecompressor(&zstdData->m_zstdHeader); // reset decompressor and setup the header. + } - m_lastReadStream = nullptr; // invalidate last read position, otherwise m_dataBuffer will be corrupted (as we are about to write in it). - - CompressorZStdData* zstdData = static_cast(stream->GetCompressorData()); - AZ_Assert(!zstdData->m_zstd.IsDecompressorStarted(), "You can't write while reading/decompressing a compressed stream."); - - const u8* bytes = reinterpret_cast(data); - unsigned int dataToCompress = aznumeric_caster(byteSize); - while (dataToCompress != 0) + // decompress and move forward until the request is done + while (byteSize > 0) + { + // fill buffer with compressed data + SizeType compressedDataSize = FillCompressedBuffer(stream); + if (compressedDataSize == 0) { - unsigned int oldDataToCompress = dataToCompress; - unsigned int compressedSize = zstdData->m_zstd.Compress(bytes, dataToCompress, m_compressedDataBuffer, m_compressedDataBufferSize); - if (compressedSize) - { - GenericStream* baseStream = stream->GetWrappedStream(); - SizeType numWritten = baseStream->Write(compressedSize, m_compressedDataBuffer); - if (numWritten != compressedSize) - { - return numWritten; // error we could not write all data - } - } - bytes += oldDataToCompress - dataToCompress; + return numRead; // we are done reading and obviously we did not managed to read all data } - zstdData->m_uncompressedSize += byteSize; - - if (zstdData->m_autoSeekSize > 0) + unsigned int processedCompressedData = 0; + while (byteSize > 0 && processedCompressedData < compressedDataSize) // decompressed data either until we are done with the request (byteSize == 0) or we need to fill the compression buffer again. { - // insert a seek point if needed. - if (zstdData->m_seekPoints.empty()) + // if we have data in the cache move to the next offset, we always move forward by default. + zstdData->m_decompressedCacheOffset += zstdData->m_decompressedCacheDataSize; + + // decompress in the cache buffer + u32 availDecompressedCacheSize = m_decompressionCachePerStream; // reset buffer size + size_t nextBlockSize; + unsigned int processed = zstdData->m_zstd.Decompress(&m_compressedDataBuffer[processedCompressedData], + static_cast(compressedDataSize) - processedCompressedData, + zstdData->m_decompressedCache, + availDecompressedCacheSize, + &nextBlockSize); + zstdData->m_decompressedCacheDataSize = m_decompressionCachePerStream - availDecompressedCacheSize; + if (processed == 0) { - if (zstdData->m_uncompressedSize >= zstdData->m_autoSeekSize) - { - WriteSeekPoint(stream); - } + break; // we processed everything we could, load more compressed data. } - else if ((zstdData->m_uncompressedSize - zstdData->m_seekPoints.back().m_uncompressedOffset) > zstdData->m_autoSeekSize) + processedCompressedData += processed; + // fill what we can from the cache + numRead += FillFromDecompressCache(zstdData, buffer, byteSize, offset); + } + // update next read position the the compressed stream + zstdData->m_decompressNextOffset += processedCompressedData; + } + return numRead; + } + + CompressorZStd::SizeType CompressorZStd::Write(CompressorStream* stream, SizeType byteSize, const void* data, SizeType offset) + { + AZ_UNUSED(offset); + + AZ_Assert(stream && stream->GetCompressorData(), "This stream doesn't have compression enabled. Call Stream::WriteCompressed after you create the file."); + AZ_Assert(offset == SizeType(-1) || offset == stream->GetCurPos(), "We can write compressed data only at the end of the stream."); + + m_lastReadStream = nullptr; // invalidate last read position, otherwise m_dataBuffer will be corrupted (as we are about to write in it). + + CompressorZStdData* zstdData = static_cast(stream->GetCompressorData()); + AZ_Assert(!zstdData->m_zstd.IsDecompressorStarted(), "You can't write while reading/decompressing a compressed stream."); + + const u8* bytes = reinterpret_cast(data); + unsigned int dataToCompress = aznumeric_caster(byteSize); + while (dataToCompress != 0) + { + unsigned int oldDataToCompress = dataToCompress; + unsigned int compressedSize = zstdData->m_zstd.Compress(bytes, dataToCompress, m_compressedDataBuffer, m_compressedDataBufferSize); + if (compressedSize) + { + GenericStream* baseStream = stream->GetWrappedStream(); + SizeType numWritten = baseStream->Write(compressedSize, m_compressedDataBuffer); + if (numWritten != compressedSize) + { + return numWritten; // error we could not write all data + } + } + bytes += oldDataToCompress - dataToCompress; + } + zstdData->m_uncompressedSize += byteSize; + + if (zstdData->m_autoSeekSize > 0) + { + // insert a seek point if needed. + if (zstdData->m_seekPoints.empty()) + { + if (zstdData->m_uncompressedSize >= zstdData->m_autoSeekSize) { WriteSeekPoint(stream); } } - return byteSize; + else if ((zstdData->m_uncompressedSize - zstdData->m_seekPoints.back().m_uncompressedOffset) > zstdData->m_autoSeekSize) + { + WriteSeekPoint(stream); + } } + return byteSize; + } - bool CompressorZStd::WriteSeekPoint(CompressorStream* stream) + bool CompressorZStd::WriteSeekPoint(CompressorStream* stream) + { + AZ_Assert(stream && stream->GetCompressorData(), "This stream doesn't have compression enabled. Call Stream::WriteCompressed after you create the file."); + CompressorZStdData* zstdData = static_cast(stream->GetCompressorData()); + + m_lastReadStream = nullptr; // invalidate last read position, otherwise m_dataBuffer will be corrupted (as we are about to write in it). + + unsigned int compressedSize; + unsigned int dataToCompress = 0; + do { - AZ_Assert(stream && stream->GetCompressorData(), "This stream doesn't have compression enabled. Call Stream::WriteCompressed after you create the file."); - CompressorZStdData* zstdData = static_cast(stream->GetCompressorData()); + compressedSize = zstdData->m_zstd.Compress(nullptr, dataToCompress, m_compressedDataBuffer, m_compressedDataBufferSize, ZStd::FT_FULL_FLUSH); + if (compressedSize) + { + GenericStream* baseStream = stream->GetWrappedStream(); + SizeType numWritten = baseStream->Write(compressedSize, m_compressedDataBuffer); + if (numWritten != compressedSize) + { + return false; // error we wrote less than than requested! + } + } + } while (dataToCompress != 0); + CompressorZStdSeekPoint sp; + sp.m_compressedOffset = stream->GetLength(); + sp.m_uncompressedOffset = zstdData->m_uncompressedSize; + zstdData->m_seekPoints.push_back(sp); + return true; + } + + bool CompressorZStd::StartCompressor(CompressorStream* stream, int compressionLevel, SizeType autoSeekDataSize) + { + AZ_Assert(stream && !stream->GetCompressorData(), "Stream has compressor already enabled."); + + AcquireDataBuffer(); + + CompressorZStdData* zstdData = aznew CompressorZStdData; + zstdData->m_compressor = this; + zstdData->m_zstdHeader = 0; // not used for compression + zstdData->m_uncompressedSize = 0; + zstdData->m_autoSeekSize = autoSeekDataSize; + compressionLevel = AZ::GetClamp(compressionLevel, 1, 9); // remap to zlib levels + + zstdData->m_zstd.StartCompressor(compressionLevel); + + stream->SetCompressorData(zstdData); + + if (WriteHeaderAndData(stream)) + { + // add the first and always present seek point at the start of the compressed stream + CompressorZStdSeekPoint sp; + sp.m_compressedOffset = sizeof(CompressorHeader) + sizeof(CompressorZStdHeader) + sizeof(zstdData->m_zstdHeader); + sp.m_uncompressedOffset = 0; + zstdData->m_seekPoints.push_back(sp); + return true; + } + return false; + } + + bool CompressorZStd::Close(CompressorStream* stream) + { + AZ_Assert(stream->IsOpen(), "Stream is not open to be closed."); + + CompressorZStdData* zstdData = static_cast(stream->GetCompressorData()); + GenericStream* baseStream = stream->GetWrappedStream(); + + bool result = true; + if (zstdData->m_zstd.IsCompressorStarted()) + { m_lastReadStream = nullptr; // invalidate last read position, otherwise m_dataBuffer will be corrupted (as we are about to write in it). + // flush all compressed data unsigned int compressedSize; unsigned int dataToCompress = 0; do { - compressedSize = zstdData->m_zstd.Compress(nullptr, dataToCompress, m_compressedDataBuffer, m_compressedDataBufferSize, ZStd::FT_FULL_FLUSH); + compressedSize = zstdData->m_zstd.Compress(nullptr, dataToCompress, m_compressedDataBuffer, m_compressedDataBufferSize, ZStd::FT_FINISH); if (compressedSize) { - GenericStream* baseStream = stream->GetWrappedStream(); - SizeType numWritten = baseStream->Write(compressedSize, m_compressedDataBuffer); - if (numWritten != compressedSize) - { - return false; // error we wrote less than than requested! - } + baseStream->Write(compressedSize, m_compressedDataBuffer); } } while (dataToCompress != 0); - CompressorZStdSeekPoint sp; - sp.m_compressedOffset = stream->GetLength(); - sp.m_uncompressedOffset = zstdData->m_uncompressedSize; - zstdData->m_seekPoints.push_back(sp); - return true; + result = WriteHeaderAndData(stream); + if (result) + { + SizeType dataToWrite = zstdData->m_seekPoints.size() * sizeof(CompressorZStdSeekPoint); + baseStream->Seek(0U, GenericStream::SeekMode::ST_SEEK_END); + result = (baseStream->Write(dataToWrite, zstdData->m_seekPoints.data()) == dataToWrite); + } } - - bool CompressorZStd::StartCompressor(CompressorStream* stream, int compressionLevel, SizeType autoSeekDataSize) + else { - AZ_Assert(stream && !stream->GetCompressorData(), "Stream has compressor already enabled."); - - AcquireDataBuffer(); - - CompressorZStdData* zstdData = aznew CompressorZStdData; - zstdData->m_compressor = this; - zstdData->m_zstdHeader = 0; // not used for compression - zstdData->m_uncompressedSize = 0; - zstdData->m_autoSeekSize = autoSeekDataSize; - compressionLevel = AZ::GetClamp(compressionLevel, 1, 9); // remap to zlib levels - - zstdData->m_zstd.StartCompressor(compressionLevel); - - stream->SetCompressorData(zstdData); - - if (WriteHeaderAndData(stream)) + if (m_lastReadStream == stream) { - // add the first and always present seek point at the start of the compressed stream - CompressorZStdSeekPoint sp; - sp.m_compressedOffset = sizeof(CompressorHeader) + sizeof(CompressorZStdHeader) + sizeof(zstdData->m_zstdHeader); - sp.m_uncompressedOffset = 0; - zstdData->m_seekPoints.push_back(sp); - return true; + m_lastReadStream = nullptr; // invalidate the data in m_dataBuffer if it was from the current stream. } - return false; } - bool CompressorZStd::Close(CompressorStream* stream) + // if we have decompressor cache delete it + if (zstdData->m_decompressedCache) { - AZ_Assert(stream->IsOpen(), "Stream is not open to be closed."); - - CompressorZStdData* zstdData = static_cast(stream->GetCompressorData()); - GenericStream* baseStream = stream->GetWrappedStream(); - - bool result = true; - if (zstdData->m_zstd.IsCompressorStarted()) - { - m_lastReadStream = nullptr; // invalidate last read position, otherwise m_dataBuffer will be corrupted (as we are about to write in it). - - // flush all compressed data - unsigned int compressedSize; - unsigned int dataToCompress = 0; - do - { - compressedSize = zstdData->m_zstd.Compress(nullptr, dataToCompress, m_compressedDataBuffer, m_compressedDataBufferSize, ZStd::FT_FINISH); - if (compressedSize) - { - baseStream->Write(compressedSize, m_compressedDataBuffer); - } - } while (dataToCompress != 0); - - result = WriteHeaderAndData(stream); - if (result) - { - SizeType dataToWrite = zstdData->m_seekPoints.size() * sizeof(CompressorZStdSeekPoint); - baseStream->Seek(0U, GenericStream::SeekMode::ST_SEEK_END); - result = (baseStream->Write(dataToWrite, zstdData->m_seekPoints.data()) == dataToWrite); - } - } - else - { - if (m_lastReadStream == stream) - { - m_lastReadStream = nullptr; // invalidate the data in m_dataBuffer if it was from the current stream. - } - } - - // if we have decompressor cache delete it - if (zstdData->m_decompressedCache) - { - azfree(zstdData->m_decompressedCache, AZ::SystemAllocator, m_decompressionCachePerStream, m_CompressedDataBufferAlignment); - } - - ReleaseDataBuffer(); - - // last step reset strream compressor data. - stream->SetCompressorData(nullptr); - return result; + azfree(zstdData->m_decompressedCache, AZ::SystemAllocator, m_decompressionCachePerStream, m_CompressedDataBufferAlignment); } - void CompressorZStd::AcquireDataBuffer() + ReleaseDataBuffer(); + + // last step reset strream compressor data. + stream->SetCompressorData(nullptr); + return result; + } + + void CompressorZStd::AcquireDataBuffer() + { + if (m_compressedDataBuffer == nullptr) { - if (m_compressedDataBuffer == nullptr) - { - AZ_Assert(m_compressedDataBufferUseCount == 0, "Buffer usecount should be 0 if the buffer is NULL"); - m_compressedDataBuffer = reinterpret_cast(azmalloc(m_compressedDataBufferSize, m_CompressedDataBufferAlignment, AZ::SystemAllocator, "CompressorZStd")); - m_lastReadStream = nullptr; // reset the cache info in the m_dataBuffer - } - ++m_compressedDataBufferUseCount; + AZ_Assert(m_compressedDataBufferUseCount == 0, "Buffer usecount should be 0 if the buffer is NULL"); + m_compressedDataBuffer = reinterpret_cast(azmalloc(m_compressedDataBufferSize, m_CompressedDataBufferAlignment, AZ::SystemAllocator, "CompressorZStd")); + m_lastReadStream = nullptr; // reset the cache info in the m_dataBuffer } + ++m_compressedDataBufferUseCount; + } - void CompressorZStd::ReleaseDataBuffer() + void CompressorZStd::ReleaseDataBuffer() + { + --m_compressedDataBufferUseCount; + if (m_compressedDataBufferUseCount == 0) { - --m_compressedDataBufferUseCount; - if (m_compressedDataBufferUseCount == 0) - { - AZ_Assert(m_compressedDataBuffer, "Invalid data buffer. We should have a non null pointer."); - azfree(m_compressedDataBuffer, AZ::SystemAllocator, m_compressedDataBufferSize, m_CompressedDataBufferAlignment); - m_compressedDataBuffer = nullptr; - m_lastReadStream = nullptr; // reset the cache info in the m_dataBuffer - } + AZ_Assert(m_compressedDataBuffer, "Invalid data buffer. We should have a non null pointer."); + azfree(m_compressedDataBuffer, AZ::SystemAllocator, m_compressedDataBufferSize, m_CompressedDataBufferAlignment); + m_compressedDataBuffer = nullptr; + m_lastReadStream = nullptr; // reset the cache info in the m_dataBuffer } - } // namespace IO -} // namespace AZ + } +} // namespace AZ::IO #endif // #if !defined(AZCORE_EXCLUDE_ZSTD) diff --git a/Code/Framework/AzCore/AzCore/IO/FileIO.cpp b/Code/Framework/AzCore/AzCore/IO/FileIO.cpp index 837aca8d84..3522187131 100644 --- a/Code/Framework/AzCore/AzCore/IO/FileIO.cpp +++ b/Code/Framework/AzCore/AzCore/IO/FileIO.cpp @@ -21,485 +21,482 @@ # define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ #endif -namespace AZ +namespace AZ::IO { - namespace IO + static EnvironmentVariable g_fileIOInstance; + static EnvironmentVariable g_directFileIOInstance; + static const char* s_EngineFileIOName = "EngineFileIO"; + static const char* s_DirectFileIOName = "DirectFileIO"; + + FileIOBase* FileIOBase::GetInstance() { - static EnvironmentVariable g_fileIOInstance; - static EnvironmentVariable g_directFileIOInstance; - static const char* s_EngineFileIOName = "EngineFileIO"; - static const char* s_DirectFileIOName = "DirectFileIO"; - - FileIOBase* FileIOBase::GetInstance() + if (!g_fileIOInstance) { - if (!g_fileIOInstance) - { - g_fileIOInstance = Environment::FindVariable(s_EngineFileIOName); - } - - return g_fileIOInstance ? (*g_fileIOInstance) : nullptr; + g_fileIOInstance = Environment::FindVariable(s_EngineFileIOName); } - void FileIOBase::SetInstance(FileIOBase* instance) + return g_fileIOInstance ? (*g_fileIOInstance) : nullptr; + } + + void FileIOBase::SetInstance(FileIOBase* instance) + { + if (!g_fileIOInstance) { - if (!g_fileIOInstance) - { - g_fileIOInstance = Environment::CreateVariable(s_EngineFileIOName); - (*g_fileIOInstance) = nullptr; - } - - // at this point we're guaranteed to have g_fileIOInstance. Its value might be null. - - if ((instance) && (g_fileIOInstance) && (*g_fileIOInstance)) - { - AZ_Error("FileIO", false, "FileIOBase::SetInstance was called without first destroying the old instance and setting it to nullptr"); - } - - (*g_fileIOInstance) = instance; + g_fileIOInstance = Environment::CreateVariable(s_EngineFileIOName); + (*g_fileIOInstance) = nullptr; } - FileIOBase* FileIOBase::GetDirectInstance() + // at this point we're guaranteed to have g_fileIOInstance. Its value might be null. + + if ((instance) && (g_fileIOInstance) && (*g_fileIOInstance)) { - if (!g_directFileIOInstance) - { - g_directFileIOInstance = Environment::FindVariable(s_DirectFileIOName); - } - - // for backwards compatibilty, return the regular instance if this is not attached - if (!g_directFileIOInstance) - { - return GetInstance(); - } - - return g_directFileIOInstance ? (*g_directFileIOInstance) : nullptr; + AZ_Error("FileIO", false, "FileIOBase::SetInstance was called without first destroying the old instance and setting it to nullptr"); } - void FileIOBase::SetDirectInstance(FileIOBase* instance) + (*g_fileIOInstance) = instance; + } + + FileIOBase* FileIOBase::GetDirectInstance() + { + if (!g_directFileIOInstance) { - if (!g_directFileIOInstance) - { - g_directFileIOInstance = Environment::CreateVariable(s_DirectFileIOName); - (*g_directFileIOInstance) = nullptr; - } - - // at this point we're guaranteed to have g_directFileIOInstance. Its value might be null. - - if ((instance) && (g_directFileIOInstance) && (*g_directFileIOInstance)) - { - AZ_Error("FileIO", false, "FileIOBase::SetDirectInstance was called without first destroying the old instance and setting it to nullptr"); - } - - (*g_directFileIOInstance) = instance; + g_directFileIOInstance = Environment::FindVariable(s_DirectFileIOName); } - AZStd::optional FileIOBase::ConvertToAlias(const AZ::IO::PathView& path) const + // for backwards compatibilty, return the regular instance if this is not attached + if (!g_directFileIOInstance) { - AZ::IO::FixedMaxPath convertedPath; - if (ConvertToAlias(convertedPath, path)) - { - return convertedPath; - } - - return AZStd::nullopt; + return GetInstance(); } - AZStd::optional FileIOBase::ResolvePath(const AZ::IO::PathView& path) const - { - AZ::IO::FixedMaxPath resolvedPath; - if (ResolvePath(resolvedPath, path)) - { - return resolvedPath; - } + return g_directFileIOInstance ? (*g_directFileIOInstance) : nullptr; + } - return AZStd::nullopt; + void FileIOBase::SetDirectInstance(FileIOBase* instance) + { + if (!g_directFileIOInstance) + { + g_directFileIOInstance = Environment::CreateVariable(s_DirectFileIOName); + (*g_directFileIOInstance) = nullptr; } - SeekType GetSeekTypeFromFSeekMode(int mode) - { - switch (mode) - { - case SEEK_SET: - return SeekType::SeekFromStart; - case SEEK_CUR: - return SeekType::SeekFromCurrent; - case SEEK_END: - return SeekType::SeekFromEnd; - } + // at this point we're guaranteed to have g_directFileIOInstance. Its value might be null. - // Must have some default, hitting here means some random int mode + if ((instance) && (g_directFileIOInstance) && (*g_directFileIOInstance)) + { + AZ_Error("FileIO", false, "FileIOBase::SetDirectInstance was called without first destroying the old instance and setting it to nullptr"); + } + + (*g_directFileIOInstance) = instance; + } + + AZStd::optional FileIOBase::ConvertToAlias(const AZ::IO::PathView& path) const + { + AZ::IO::FixedMaxPath convertedPath; + if (ConvertToAlias(convertedPath, path)) + { + return convertedPath; + } + + return AZStd::nullopt; + } + + AZStd::optional FileIOBase::ResolvePath(const AZ::IO::PathView& path) const + { + AZ::IO::FixedMaxPath resolvedPath; + if (ResolvePath(resolvedPath, path)) + { + return resolvedPath; + } + + return AZStd::nullopt; + } + + SeekType GetSeekTypeFromFSeekMode(int mode) + { + switch (mode) + { + case SEEK_SET: return SeekType::SeekFromStart; + case SEEK_CUR: + return SeekType::SeekFromCurrent; + case SEEK_END: + return SeekType::SeekFromEnd; } - int GetFSeekModeFromSeekType(SeekType type) - { - switch (type) - { - case SeekType::SeekFromStart: - return SEEK_SET; - case SeekType::SeekFromCurrent: - return SEEK_CUR; - case SeekType::SeekFromEnd: - return SEEK_END; - } + // Must have some default, hitting here means some random int mode + return SeekType::SeekFromStart; + } + int GetFSeekModeFromSeekType(SeekType type) + { + switch (type) + { + case SeekType::SeekFromStart: return SEEK_SET; + case SeekType::SeekFromCurrent: + return SEEK_CUR; + case SeekType::SeekFromEnd: + return SEEK_END; } - void UpdateOpenModeForReading(OpenMode& openMode) + return SEEK_SET; + } + + void UpdateOpenModeForReading(OpenMode& openMode) + { + if (AnyFlag(openMode & OpenMode::ModeRead)) { - if (AnyFlag(openMode & OpenMode::ModeRead)) + if (AnyFlag(openMode & OpenMode::ModeText)) { - if (AnyFlag(openMode & OpenMode::ModeText)) - { - OpenMode extraModes = openMode & (OpenMode::ModeUpdate | OpenMode::ModeAppend); - openMode = OpenMode::ModeRead | OpenMode::ModeBinary | extraModes; - } - else if (!AnyFlag(openMode & OpenMode::ModeBinary)) - { - // if you haven't supplied any flag, supply binary - openMode = openMode | OpenMode::ModeBinary; - } + OpenMode extraModes = openMode & (OpenMode::ModeUpdate | OpenMode::ModeAppend); + openMode = OpenMode::ModeRead | OpenMode::ModeBinary | extraModes; + } + else if (!AnyFlag(openMode & OpenMode::ModeBinary)) + { + // if you haven't supplied any flag, supply binary + openMode = openMode | OpenMode::ModeBinary; } } + } - OpenMode GetOpenModeFromStringMode(const char* mode) + OpenMode GetOpenModeFromStringMode(const char* mode) + { + OpenMode openMode = OpenMode::Invalid; + + if (strstr(mode, "w")) { - OpenMode openMode = OpenMode::Invalid; - - if (strstr(mode, "w")) - { - openMode |= OpenMode::ModeWrite; - } - - if (strstr(mode, "r")) - { - openMode |= OpenMode::ModeRead; - } - - if (strstr(mode, "a")) - { - openMode |= OpenMode::ModeAppend; - } - - if (strstr(mode, "b")) - { - openMode |= OpenMode::ModeBinary; - } - - if (strstr(mode, "t")) - { - openMode |= OpenMode::ModeText; - } - - if (strstr(mode, "+")) - { - openMode |= OpenMode::ModeUpdate; - } - - UpdateOpenModeForReading(openMode); - - return openMode; + openMode |= OpenMode::ModeWrite; } - const char* GetStringModeFromOpenMode(OpenMode mode) + if (strstr(mode, "r")) { - UpdateOpenModeForReading(mode); - // Append is highest priority, followed by write and then read - // APPEND - if (AnyFlag(mode & OpenMode::ModeAppend)) + openMode |= OpenMode::ModeRead; + } + + if (strstr(mode, "a")) + { + openMode |= OpenMode::ModeAppend; + } + + if (strstr(mode, "b")) + { + openMode |= OpenMode::ModeBinary; + } + + if (strstr(mode, "t")) + { + openMode |= OpenMode::ModeText; + } + + if (strstr(mode, "+")) + { + openMode |= OpenMode::ModeUpdate; + } + + UpdateOpenModeForReading(openMode); + + return openMode; + } + + const char* GetStringModeFromOpenMode(OpenMode mode) + { + UpdateOpenModeForReading(mode); + // Append is highest priority, followed by write and then read + // APPEND + if (AnyFlag(mode & OpenMode::ModeAppend)) + { + if (AnyFlag(mode & OpenMode::ModeUpdate)) { - if (AnyFlag(mode & OpenMode::ModeUpdate)) - { - if (AnyFlag(mode & OpenMode::ModeBinary)) - { - return "a+b"; - } - if (AnyFlag(mode & OpenMode::ModeText)) - { - return "a+t"; - } - return "a+"; - } if (AnyFlag(mode & OpenMode::ModeBinary)) { - return "ab"; + return "a+b"; } if (AnyFlag(mode & OpenMode::ModeText)) { - return "at"; + return "a+t"; } - return "a"; + return "a+"; } - - // WRITE - if (AnyFlag(mode & OpenMode::ModeWrite)) + if (AnyFlag(mode & OpenMode::ModeBinary)) + { + return "ab"; + } + if (AnyFlag(mode & OpenMode::ModeText)) + { + return "at"; + } + return "a"; + } + + // WRITE + if (AnyFlag(mode & OpenMode::ModeWrite)) + { + if (AnyFlag(mode & OpenMode::ModeUpdate)) { - if (AnyFlag(mode & OpenMode::ModeUpdate)) - { - if (AnyFlag(mode & OpenMode::ModeBinary)) - { - return "w+b"; - } - if (AnyFlag(mode & OpenMode::ModeText)) - { - return "w+t"; - } - return "w+"; - } if (AnyFlag(mode & OpenMode::ModeBinary)) { - return "wb"; + return "w+b"; } if (AnyFlag(mode & OpenMode::ModeText)) { - return "wt"; + return "w+t"; } - return "w"; + return "w+"; } - - // READ - if (AnyFlag(mode & OpenMode::ModeRead)) + if (AnyFlag(mode & OpenMode::ModeBinary)) + { + return "wb"; + } + if (AnyFlag(mode & OpenMode::ModeText)) + { + return "wt"; + } + return "w"; + } + + // READ + if (AnyFlag(mode & OpenMode::ModeRead)) + { + if (AnyFlag(mode & OpenMode::ModeUpdate)) { - if (AnyFlag(mode & OpenMode::ModeUpdate)) - { - if (AnyFlag(mode & OpenMode::ModeBinary)) - { - return "r+b"; - } - if (AnyFlag(mode & OpenMode::ModeText)) - { - return "r+t"; - } - return "r+"; - } if (AnyFlag(mode & OpenMode::ModeBinary)) { - return "rb"; + return "r+b"; } if (AnyFlag(mode & OpenMode::ModeText)) { - return "rt"; + return "r+t"; } - return "r"; + return "r+"; } - - // Bad open mode passed in - AZ_Error("FileIO", false, "A bad open mode was sent to GetStringModeFromOpenMode()"); - return ""; + if (AnyFlag(mode & OpenMode::ModeBinary)) + { + return "rb"; + } + if (AnyFlag(mode & OpenMode::ModeText)) + { + return "rt"; + } + return "r"; } - bool NameMatchesFilter(const char* name, const char* filter) + // Bad open mode passed in + AZ_Error("FileIO", false, "A bad open mode was sent to GetStringModeFromOpenMode()"); + return ""; + } + + bool NameMatchesFilter(const char* name, const char* filter) + { + return AZStd::wildcard_match(filter, name); + } + + FileIOStream::FileIOStream() + : m_handle(InvalidHandle) + , m_mode(OpenMode::Invalid) + , m_ownsHandle(true) + { + + } + + FileIOStream::FileIOStream(HandleType fileHandle, AZ::IO::OpenMode mode, bool ownsHandle) + : m_handle(fileHandle) + , m_mode(mode) + , m_ownsHandle(ownsHandle) + { + + FileIOBase* fileIO = FileIOBase::GetInstance(); + AZ_Assert(fileIO, "FileIO is not initialized."); + AZStd::array resolvedPath{ {0} }; + fileIO->GetFilename(m_handle, resolvedPath.data(), resolvedPath.size() - 1); + m_filename = resolvedPath.data(); + } + + FileIOStream::FileIOStream(const char* path, AZ::IO::OpenMode mode, bool errorOnFailure) + : m_handle(InvalidHandle) + , m_mode(mode) + , m_errorOnFailure(errorOnFailure) + { + Open(path, mode); + } + + FileIOStream::~FileIOStream() + { + if (m_ownsHandle) { - return AZStd::wildcard_match(filter, name); + Close(); } + } - FileIOStream::FileIOStream() - : m_handle(InvalidHandle) - , m_mode(OpenMode::Invalid) - , m_ownsHandle(true) + bool FileIOStream::Open(const char* path, OpenMode mode) + { + AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized."); + FileIOBase* fileIO = FileIOBase::GetInstance(); + + Close(); + + const Result result = fileIO->Open(path, mode, m_handle); + m_ownsHandle = IsOpen(); + m_mode = mode; + + if (IsOpen()) { - - } - - FileIOStream::FileIOStream(HandleType fileHandle, AZ::IO::OpenMode mode, bool ownsHandle) - : m_handle(fileHandle) - , m_mode(mode) - , m_ownsHandle(ownsHandle) - { - - FileIOBase* fileIO = FileIOBase::GetInstance(); - AZ_Assert(fileIO, "FileIO is not initialized."); + // Not using supplied path parameter as it may be unresolved AZStd::array resolvedPath{ {0} }; fileIO->GetFilename(m_handle, resolvedPath.data(), resolvedPath.size() - 1); m_filename = resolvedPath.data(); } - - FileIOStream::FileIOStream(const char* path, AZ::IO::OpenMode mode, bool errorOnFailure) - : m_handle(InvalidHandle) - , m_mode(mode) - , m_errorOnFailure(errorOnFailure) + else { - Open(path, mode); + // remember the file name so you can try again with ReOpen + m_filename = path; } - FileIOStream::~FileIOStream() - { - if (m_ownsHandle) - { - Close(); - } - } + AZ_PROFILE_INTERVAL_START_COLORED(AzCore, &m_filename, 0xff0000ff, "FileIO: %s", m_filename.c_str()); + return result; + } - bool FileIOStream::Open(const char* path, OpenMode mode) + bool FileIOStream::ReOpen() + { + Close(); + return (m_mode != OpenMode::Invalid) ? Open(m_filename.data(), m_mode) : false; + } + + void FileIOStream::Close() + { + if (m_handle != InvalidHandle) { AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized."); - FileIOBase* fileIO = FileIOBase::GetInstance(); - Close(); - - const Result result = fileIO->Open(path, mode, m_handle); - m_ownsHandle = IsOpen(); - m_mode = mode; - - if (IsOpen()) - { - // Not using supplied path parameter as it may be unresolved - AZStd::array resolvedPath{ {0} }; - fileIO->GetFilename(m_handle, resolvedPath.data(), resolvedPath.size() - 1); - m_filename = resolvedPath.data(); - } - else - { - // remember the file name so you can try again with ReOpen - m_filename = path; - } - - AZ_PROFILE_INTERVAL_START_COLORED(AzCore, &m_filename, 0xff0000ff, "FileIO: %s", m_filename.c_str()); - return result; + FileIOBase::GetInstance()->Close(m_handle); + m_handle = InvalidHandle; + m_ownsHandle = false; + AZ_PROFILE_INTERVAL_END(AzCore, &m_filename); } + } - bool FileIOStream::ReOpen() + bool FileIOStream::IsOpen() const + { + return (m_handle != InvalidHandle); + } + + /*! + \brief Retrieves underlying FileIO Handle from file stream + \return HandleType + */ + HandleType FileIOStream::GetHandle() const + { + return m_handle; + } + + /*! + \brief Retrieves filename + \return const char* + */ + const char* FileIOStream::GetFilename() const + { + return m_filename.data(); + } + + /*! + \brief Retrieves OpenMode flags used to open this file + \return OpenMode + */ + AZ::IO::OpenMode FileIOStream::GetModeFlags() const + { + return m_mode; + } + + bool FileIOStream::CanSeek() const + { + return true; + } + + bool FileIOStream::CanRead() const + { + return (m_mode & (OpenMode::ModeRead | OpenMode::ModeUpdate)) != OpenMode::Invalid; + } + + bool FileIOStream::CanWrite() const + { + return (m_mode & (OpenMode::ModeWrite | OpenMode::ModeAppend | OpenMode::ModeUpdate)) != OpenMode::Invalid; + } + + void FileIOStream::Seek(OffsetType bytes, SeekMode mode) + { + AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized."); + AZ_Assert(IsOpen(), "Cannot seek on a FileIOStream that is not open."); + + SeekType seekType = SeekType::SeekFromCurrent; + switch (mode) { - Close(); - return (m_mode != OpenMode::Invalid) ? Open(m_filename.data(), m_mode) : false; + case GenericStream::ST_SEEK_BEGIN: + seekType = SeekType::SeekFromStart; + break; + case GenericStream::ST_SEEK_CUR: + seekType = SeekType::SeekFromCurrent; + break; + case GenericStream::ST_SEEK_END: + seekType = SeekType::SeekFromEnd; + break; + default: + seekType = SeekType::SeekFromCurrent; + break; } - void FileIOStream::Close() + const Result result = FileIOBase::GetInstance()->Seek(m_handle, static_cast(bytes), seekType); + (void)result; + AZ_Error("FileIOStream", result.GetResultCode() == ResultCode::Success, "Seek failed."); + } + + SizeType FileIOStream::Read(SizeType bytes, void* oBuffer) + { + AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized."); + AZ_Assert(IsOpen(), "Cannot read from a FileIOStream that is not open."); + + AZ::u64 bytesRead = 0; + const Result result = FileIOBase::GetInstance()->Read(m_handle, oBuffer, bytes, m_errorOnFailure, &bytesRead); + (void)result; + AZ_Error("FileIOStream", result.GetResultCode() == ResultCode::Success, "Read failed in file %s.", m_filename.empty() ? "NULL" : m_filename.c_str()); + return static_cast(bytesRead); + } + + SizeType FileIOStream::Write(SizeType bytes, const void* iBuffer) + { + AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized."); + AZ_Assert(IsOpen(), "Cannot write to a FileIOStream that is not open."); + + AZ::u64 bytesWritten = 0; + const Result result = FileIOBase::GetInstance()->Write(m_handle, iBuffer, bytes, &bytesWritten); + (void)result; + AZ_Error("FileIOStream", result.GetResultCode() == ResultCode::Success, "Write failed."); + return static_cast(bytesWritten); + } + + SizeType FileIOStream::GetCurPos() const + { + AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized."); + AZ_Assert(IsOpen(), "Cannot use a FileIOStream that is not open."); + + AZ::u64 currentPosition = 0; + const Result result = FileIOBase::GetInstance()->Tell(m_handle, currentPosition); + (void)result; + AZ_Error("FileIOStream", result.GetResultCode() == ResultCode::Success, "GetCurPos failed."); + return static_cast(currentPosition); + } + + SizeType FileIOStream::GetLength() const + { + AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized."); + AZ_Assert(IsOpen(), "Cannot use a FileIOStream that is not open."); + + SizeType fileLengthBytes = 0; + if (!FileIOBase::GetInstance()->Size(m_handle, fileLengthBytes)) { - if (m_handle != InvalidHandle) - { - AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized."); - - FileIOBase::GetInstance()->Close(m_handle); - m_handle = InvalidHandle; - m_ownsHandle = false; - AZ_PROFILE_INTERVAL_END(AzCore, &m_filename); - } + AZ_Error("FileIOStream", false, "GetLength failed."); } - bool FileIOStream::IsOpen() const - { - return (m_handle != InvalidHandle); - } + return fileLengthBytes; + } - /*! - \brief Retrieves underlying FileIO Handle from file stream - \return HandleType - */ - HandleType FileIOStream::GetHandle() const - { - return m_handle; - } - - /*! - \brief Retrieves filename - \return const char* - */ - const char* FileIOStream::GetFilename() const - { - return m_filename.data(); - } - - /*! - \brief Retrieves OpenMode flags used to open this file - \return OpenMode - */ - AZ::IO::OpenMode FileIOStream::GetModeFlags() const - { - return m_mode; - } - - bool FileIOStream::CanSeek() const - { - return true; - } - - bool FileIOStream::CanRead() const - { - return (m_mode & (OpenMode::ModeRead | OpenMode::ModeUpdate)) != OpenMode::Invalid; - } - - bool FileIOStream::CanWrite() const - { - return (m_mode & (OpenMode::ModeWrite | OpenMode::ModeAppend | OpenMode::ModeUpdate)) != OpenMode::Invalid; - } - - void FileIOStream::Seek(OffsetType bytes, SeekMode mode) - { - AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized."); - AZ_Assert(IsOpen(), "Cannot seek on a FileIOStream that is not open."); - - SeekType seekType = SeekType::SeekFromCurrent; - switch (mode) - { - case GenericStream::ST_SEEK_BEGIN: - seekType = SeekType::SeekFromStart; - break; - case GenericStream::ST_SEEK_CUR: - seekType = SeekType::SeekFromCurrent; - break; - case GenericStream::ST_SEEK_END: - seekType = SeekType::SeekFromEnd; - break; - default: - seekType = SeekType::SeekFromCurrent; - break; - } - - const Result result = FileIOBase::GetInstance()->Seek(m_handle, static_cast(bytes), seekType); - (void)result; - AZ_Error("FileIOStream", result.GetResultCode() == ResultCode::Success, "Seek failed."); - } - - SizeType FileIOStream::Read(SizeType bytes, void* oBuffer) - { - AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized."); - AZ_Assert(IsOpen(), "Cannot read from a FileIOStream that is not open."); - - AZ::u64 bytesRead = 0; - const Result result = FileIOBase::GetInstance()->Read(m_handle, oBuffer, bytes, m_errorOnFailure, &bytesRead); - (void)result; - AZ_Error("FileIOStream", result.GetResultCode() == ResultCode::Success, "Read failed in file %s.", m_filename.empty() ? "NULL" : m_filename.c_str()); - return static_cast(bytesRead); - } - - SizeType FileIOStream::Write(SizeType bytes, const void* iBuffer) - { - AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized."); - AZ_Assert(IsOpen(), "Cannot write to a FileIOStream that is not open."); - - AZ::u64 bytesWritten = 0; - const Result result = FileIOBase::GetInstance()->Write(m_handle, iBuffer, bytes, &bytesWritten); - (void)result; - AZ_Error("FileIOStream", result.GetResultCode() == ResultCode::Success, "Write failed."); - return static_cast(bytesWritten); - } - - SizeType FileIOStream::GetCurPos() const - { - AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized."); - AZ_Assert(IsOpen(), "Cannot use a FileIOStream that is not open."); - - AZ::u64 currentPosition = 0; - const Result result = FileIOBase::GetInstance()->Tell(m_handle, currentPosition); - (void)result; - AZ_Error("FileIOStream", result.GetResultCode() == ResultCode::Success, "GetCurPos failed."); - return static_cast(currentPosition); - } - - SizeType FileIOStream::GetLength() const - { - AZ_Assert(FileIOBase::GetInstance(), "FileIO is not initialized."); - AZ_Assert(IsOpen(), "Cannot use a FileIOStream that is not open."); - - SizeType fileLengthBytes = 0; - if (!FileIOBase::GetInstance()->Size(m_handle, fileLengthBytes)) - { - AZ_Error("FileIOStream", false, "GetLength failed."); - } - - return fileLengthBytes; - } - - } // namespace IO -} // namespace AZ +} // namespace AZ::IO diff --git a/Code/Framework/AzCore/AzCore/IO/FileIOEventBus.h b/Code/Framework/AzCore/AzCore/IO/FileIOEventBus.h deleted file mode 100644 index ba8fef9a7b..0000000000 --- a/Code/Framework/AzCore/AzCore/IO/FileIOEventBus.h +++ /dev/null @@ -1,72 +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 - * - */ -#ifndef AZCORE_SYSTEM_FILE_BUS_H -#define AZCORE_SYSTEM_FILE_BUS_H - -#include -#include -#include - -namespace AZ -{ - namespace IO - { - /** - * File IO interface. All events return true if we executed the - * specific operation and no other code will be executed. If we return false - * the normal code for the specific event will be executed. - * IMPORTANT: We support multiple listeners with the idea that many systems can listen - * for event. This interface allows to actually perform the operations, in such cases make - * sure only one of the listeners provides this service (otherwise depending on registration - * order service providers may change) - * IMPORTANT: We don't provide any sync for the FileIOBus. We do that for a couple of reasons. - * 1. If you will handle file IO youself or keeptrack of statistics you code will most likely already do that - * 2. It is NOT safe to BusConnect/BusDisconnect while the FileIO is in use (this is why you should connect in advance) - * otherwise if you provide service you can end up connecting in a middle of reads/writes/etc. - */ - class FileIO - : public AZ::EBusTraits - { - public: - virtual ~FileIO() {} - virtual bool OnOpen(SystemFile& file, const char* fileName, int mode, int platformFlags, bool& isFileOpened) = 0; - virtual bool OnClose(SystemFile& file) = 0; - virtual bool OnSeek(SystemFile& file, SystemFile::SizeType offset, SystemFile::SeekMode mode) = 0; - virtual bool OnRead(SystemFile& file, SystemFile::SizeType byteSize, void* buffer, SystemFile::SizeType& numRead) = 0; - virtual bool OnWrite(SystemFile& file, const void* buffer, SystemFile::SizeType byteSize, SystemFile::SizeType& numWritten) = 0; - }; - - typedef AZ::EBus FileIOBus; - - /** - * Interface for handling file io events. All events are syncronized - */ - class FileIOEvents - : public AZ::EBusTraits - { - public: - virtual ~FileIOEvents() {} - - ////////////////////////////////////////////////////////////////////////// - // EBusTraits overrides - //TODO rbbaklov or zolniery look into why a recursive lock was not needed previously - typedef AZStd::recursive_mutex MutexType; //< make sure all file events are thread safe as they will called from many threads - ////////////////////////////////////////////////////////////////////////// - - /** - * You will either have a file (SystemFile) pointer or fileName pointer to the file name. - * \param fileName is provided when there is NO SystemFile object (when you call static functions). - */ - virtual void OnError(const SystemFile* file, const char* fileName, int errorCode) = 0; - }; - - typedef AZ::EBus FileIOEventBus; - } -} -#endif // AZCORE_SYSTEM_FILE_BUS_H -#pragma once diff --git a/Code/Framework/AzCore/AzCore/IO/IOUtils.cpp b/Code/Framework/AzCore/AzCore/IO/IOUtils.cpp index 1e3a31f8ab..222a36ef70 100644 --- a/Code/Framework/AzCore/AzCore/IO/IOUtils.cpp +++ b/Code/Framework/AzCore/AzCore/IO/IOUtils.cpp @@ -10,69 +10,64 @@ #include #include /// this_thread sleep_for. -namespace AZ +namespace AZ::IO { - namespace IO - { - int TranslateOpenModeToSystemFileMode(const char* path, OpenMode mode) + int TranslateOpenModeToSystemFileMode(const char* path, OpenMode mode) + { + int systemFileMode = 0; + bool read = AnyFlag(mode & OpenMode::ModeRead) || AnyFlag(mode & OpenMode::ModeUpdate); + bool write = AnyFlag(mode & OpenMode::ModeWrite) || AnyFlag(mode & OpenMode::ModeUpdate) || AnyFlag(mode & OpenMode::ModeAppend); + if (write) { - int systemFileMode = 0; - bool read = AnyFlag(mode & OpenMode::ModeRead) || AnyFlag(mode & OpenMode::ModeUpdate); - bool write = AnyFlag(mode & OpenMode::ModeWrite) || AnyFlag(mode & OpenMode::ModeUpdate) || AnyFlag(mode & OpenMode::ModeAppend); - if (write) + // If writing the file, create the file in all cases (except r+) + if (!SystemFile::Exists(path) && !(AnyFlag(mode & OpenMode::ModeRead) && AnyFlag(mode & OpenMode::ModeUpdate))) { - // If writing the file, create the file in all cases (except r+) - if (!SystemFile::Exists(path) && !(AnyFlag(mode & OpenMode::ModeRead) && AnyFlag(mode & OpenMode::ModeUpdate))) - { - // LocalFileIO creates by default - systemFileMode |= SystemFile::SF_OPEN_CREATE; - } - - if (AnyFlag(mode & OpenMode::ModeCreatePath)) - { - systemFileMode |= SystemFile::SF_OPEN_CREATE_PATH; - } - - // If appending, append. - if (AnyFlag(mode & OpenMode::ModeAppend)) - { - systemFileMode |= SystemFile::SF_OPEN_APPEND; - } - // If writing and not appending, empty the file - else if (AnyFlag(mode & OpenMode::ModeWrite)) - { - systemFileMode |= SystemFile::SF_OPEN_TRUNCATE; - } - - // If reading, set read/write, otherwise just write - if (read) - { - systemFileMode |= SystemFile::SF_OPEN_READ_WRITE; - } - else - { - systemFileMode |= SystemFile::SF_OPEN_WRITE_ONLY; - } - } - else if (read) - { - systemFileMode |= SystemFile::SF_OPEN_READ_ONLY; + // LocalFileIO creates by default + systemFileMode |= SystemFile::SF_OPEN_CREATE; } - return systemFileMode; + if (AnyFlag(mode & OpenMode::ModeCreatePath)) + { + systemFileMode |= SystemFile::SF_OPEN_CREATE_PATH; + } + + // If appending, append. + if (AnyFlag(mode & OpenMode::ModeAppend)) + { + systemFileMode |= SystemFile::SF_OPEN_APPEND; + } + // If writing and not appending, empty the file + else if (AnyFlag(mode & OpenMode::ModeWrite)) + { + systemFileMode |= SystemFile::SF_OPEN_TRUNCATE; + } + + // If reading, set read/write, otherwise just write + if (read) + { + systemFileMode |= SystemFile::SF_OPEN_READ_WRITE; + } + else + { + systemFileMode |= SystemFile::SF_OPEN_WRITE_ONLY; + } + } + else if (read) + { + systemFileMode |= SystemFile::SF_OPEN_READ_ONLY; } - bool RetryOpenStream(FileIOStream& stream, int numRetries, int delayBetweenRetry) + return systemFileMode; + } + + bool RetryOpenStream(FileIOStream& stream, int numRetries, int delayBetweenRetry) + { + while ((!stream.IsOpen()) && (numRetries > 0)) { - while ((!stream.IsOpen()) && (numRetries > 0)) - { - numRetries--; - AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(delayBetweenRetry)); - stream.ReOpen(); - } - return stream.IsOpen(); + numRetries--; + AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(delayBetweenRetry)); + stream.ReOpen(); } - } // namespace IO -} // namespace AZ - - + return stream.IsOpen(); + } +} // namespace AZ::IO diff --git a/Code/Framework/AzCore/AzCore/IO/Path/Path.cpp b/Code/Framework/AzCore/AzCore/IO/Path/Path.cpp index 637945db35..6e4dfdaa63 100644 --- a/Code/Framework/AzCore/AzCore/IO/Path/Path.cpp +++ b/Code/Framework/AzCore/AzCore/IO/Path/Path.cpp @@ -50,13 +50,4 @@ namespace AZ::IO const PathIterator& rhs); template bool operator!=(const PathIterator& lhs, const PathIterator& rhs); - - void PathReflection::Reflect(AZ::ReflectContext* context) - { - if (auto* serializeContext = azrtti_cast(context)) - { - serializeContext->Class() - ->Field("m_path", &AZ::IO::Path::m_path); - } - } } diff --git a/Code/Framework/AzCore/AzCore/IO/Path/Path.h b/Code/Framework/AzCore/AzCore/IO/Path/Path.h index 3b5c224957..094dee16f2 100644 --- a/Code/Framework/AzCore/AzCore/IO/Path/Path.h +++ b/Code/Framework/AzCore/AzCore/IO/Path/Path.h @@ -78,7 +78,8 @@ namespace AZ::IO // native format observers //! Returns string_view stored within the PathView - constexpr AZStd::string_view Native() const noexcept; + constexpr const AZStd::string_view& Native() const noexcept; + constexpr AZStd::string_view& Native() noexcept; //! Conversion operator to retrieve string_view stored within the PathView constexpr explicit operator AZStd::string_view() const noexcept; @@ -321,7 +322,6 @@ namespace AZ::IO using const_iterator = const PathIterator; using iterator = const_iterator; friend PathIterator; - friend struct PathReflection; // constructors and destructor constexpr BasicPath() = default; @@ -665,6 +665,7 @@ namespace AZ::IO namespace AZ { AZ_TYPE_INFO_SPECIALIZE(AZ::IO::Path, "{88E0A40F-3085-4CAB-8B11-EF5A2659C71A}"); + AZ_TYPE_INFO_SPECIALIZE(AZ::IO::FixedMaxPath, "{FA6CA49F-376A-417C-9767-DD50744DF203}"); } namespace AZ::IO diff --git a/Code/Framework/AzCore/AzCore/IO/Path/Path.inl b/Code/Framework/AzCore/AzCore/IO/Path/Path.inl index 1d654c1502..5ac15fc728 100644 --- a/Code/Framework/AzCore/AzCore/IO/Path/Path.inl +++ b/Code/Framework/AzCore/AzCore/IO/Path/Path.inl @@ -101,7 +101,11 @@ namespace AZ::IO } // native format observers - constexpr auto PathView::Native() const noexcept -> AZStd::string_view + constexpr auto PathView::Native() const noexcept -> const AZStd::string_view& + { + return m_path; + } + constexpr auto PathView::Native() noexcept -> AZStd::string_view& { return m_path; } diff --git a/Code/Framework/AzCore/AzCore/IO/Path/PathReflect.cpp b/Code/Framework/AzCore/AzCore/IO/Path/PathReflect.cpp new file mode 100644 index 0000000000..55a991f19f --- /dev/null +++ b/Code/Framework/AzCore/AzCore/IO/Path/PathReflect.cpp @@ -0,0 +1,77 @@ +/* + * 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 +#include +#include + +namespace AZ::IO +{ + template + struct PathSerializer + : public SerializeContext::IDataSerializer + { + public: + /// Convert binary data to text + size_t DataToText(IO::GenericStream& in, IO::GenericStream& out, bool) override + { + PathType outPath; + outPath.Native().resize_no_construct(in.GetLength()); + in.Read(outPath.Native().size(), outPath.Native().data()); + + return static_cast(out.Write(outPath.Native().size(), outPath.Native().c_str())); + } + + size_t TextToData(const char* text, unsigned int, IO::GenericStream& stream, bool) override + { + return static_cast(stream.Write(strlen(text), reinterpret_cast(text))); + } + + size_t Save(const void* classPtr, IO::GenericStream& stream, bool) override + { + /// Save paths out using the PosixPathSeparator + PathType path(reinterpret_cast(classPtr)->Native(), AZ::IO::PosixPathSeparator); + path.MakePreferred(); + + return static_cast(stream.Write(path.Native().size(), path.c_str())); + } + + bool Load(void* classPtr, IO::GenericStream& stream, unsigned int, bool) override + { + // Normalize the path load + auto path = reinterpret_cast(classPtr); + + path->Native().resize_no_construct(stream.GetLength()); + stream.Read(path->Native().size(), path->Native().data()); + *path = path->LexicallyNormal(); + + return true; + } + + bool CompareValueData(const void* lhs, const void* rhs) override + { + return SerializeContext::EqualityCompareHelper::CompareValues(lhs, rhs); + } + }; + + void PathReflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context); serializeContext != nullptr) + { + serializeContext->Class() + ->Serializer(AZ::SerializeContext::IDataSerializerPtr{ new PathSerializer{}, + AZ::SerializeContext::IDataSerializer::CreateDefaultDeleteDeleter() }) + ; + + serializeContext->Class() + ->Serializer(AZ::SerializeContext::IDataSerializerPtr{ new PathSerializer{}, + AZ::SerializeContext::IDataSerializer::CreateDefaultDeleteDeleter() }) + ; + } + } +} diff --git a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Profiler/Aggregator.h b/Code/Framework/AzCore/AzCore/IO/Path/PathReflect.h similarity index 67% rename from Gems/ScriptCanvas/Code/Include/ScriptCanvas/Profiler/Aggregator.h rename to Code/Framework/AzCore/AzCore/IO/Path/PathReflect.h index 679a770bcc..3076895026 100644 --- a/Gems/ScriptCanvas/Code/Include/ScriptCanvas/Profiler/Aggregator.h +++ b/Code/Framework/AzCore/AzCore/IO/Path/PathReflect.h @@ -5,11 +5,15 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ -#pragma once -namespace ScriptCanvas -{ - namespace Profiler - { - } +#pragma once + +namespace AZ +{ + class ReflectContext; +} + +namespace AZ::IO +{ + void PathReflect(AZ::ReflectContext* context); } diff --git a/Code/Framework/AzCore/AzCore/IO/Path/Path_fwd.h b/Code/Framework/AzCore/AzCore/IO/Path/Path_fwd.h index cb61bd5887..c4fce5e1b7 100644 --- a/Code/Framework/AzCore/AzCore/IO/Path/Path_fwd.h +++ b/Code/Framework/AzCore/AzCore/IO/Path/Path_fwd.h @@ -57,11 +57,6 @@ namespace AZ::IO // It depends on the path type template class PathIterator; - - struct PathReflection - { - static void Reflect(AZ::ReflectContext* context); - }; } namespace AZStd diff --git a/Code/Framework/AzCore/AzCore/IO/Streamer/BlockCache.cpp b/Code/Framework/AzCore/AzCore/IO/Streamer/BlockCache.cpp index e838324408..6c873f3050 100644 --- a/Code/Framework/AzCore/AzCore/IO/Streamer/BlockCache.cpp +++ b/Code/Framework/AzCore/AzCore/IO/Streamer/BlockCache.cpp @@ -15,736 +15,733 @@ #include #include -namespace AZ +namespace AZ::IO { - namespace IO + AZStd::shared_ptr BlockCacheConfig::AddStreamStackEntry( + const HardwareInformation& hardware, AZStd::shared_ptr parent) { - AZStd::shared_ptr BlockCacheConfig::AddStreamStackEntry( - const HardwareInformation& hardware, AZStd::shared_ptr parent) + size_t blockSize; + switch (m_blockSize) { - size_t blockSize; - switch (m_blockSize) - { - case BlockSize::MaxTransfer: - blockSize = hardware.m_maxTransfer; - break; - case BlockSize::MemoryAlignment: - blockSize = hardware.m_maxPhysicalSectorSize; - break; - case BlockSize::SizeAlignment: - blockSize = hardware.m_maxLogicalSectorSize; - break; - default: - blockSize = m_blockSize; - break; - } - - u32 cacheSize = static_cast(m_cacheSizeMib * 1_mib); - if (blockSize * 2 > cacheSize) - { - AZ_Warning("Streamer", false, "Size (%u) for BlockCache isn't big enough to hold at least two cache blocks of size (%zu). " - "The cache size will be increased to fit 2 cache blocks.", cacheSize, blockSize); - cacheSize = aznumeric_caster(blockSize * 2); - } - - auto stackEntry = AZStd::make_shared( - cacheSize, aznumeric_cast(blockSize), aznumeric_cast(hardware.m_maxPhysicalSectorSize), false); - stackEntry->SetNext(AZStd::move(parent)); - return stackEntry; + case BlockSize::MaxTransfer: + blockSize = hardware.m_maxTransfer; + break; + case BlockSize::MemoryAlignment: + blockSize = hardware.m_maxPhysicalSectorSize; + break; + case BlockSize::SizeAlignment: + blockSize = hardware.m_maxLogicalSectorSize; + break; + default: + blockSize = m_blockSize; + break; } - void BlockCacheConfig::Reflect(AZ::ReflectContext* context) + u32 cacheSize = static_cast(m_cacheSizeMib * 1_mib); + if (blockSize * 2 > cacheSize) { - if (auto serializeContext = azrtti_cast(context); serializeContext != nullptr) - { - serializeContext->Enum() - ->Version(1) - ->Value("MaxTransfer", BlockSize::MaxTransfer) - ->Value("MemoryAlignment", BlockSize::MemoryAlignment) - ->Value("SizeAlignment", BlockSize::SizeAlignment); - - serializeContext->Class() - ->Version(1) - ->Field("CacheSizeMib", &BlockCacheConfig::m_cacheSizeMib) - ->Field("BlockSize", &BlockCacheConfig::m_blockSize); - } + AZ_Warning("Streamer", false, "Size (%u) for BlockCache isn't big enough to hold at least two cache blocks of size (%zu). " + "The cache size will be increased to fit 2 cache blocks.", cacheSize, blockSize); + cacheSize = aznumeric_caster(blockSize * 2); } - static constexpr char CacheHitRateName[] = "Cache hit rate"; - static constexpr char CacheableName[] = "Cacheable"; + auto stackEntry = AZStd::make_shared( + cacheSize, aznumeric_cast(blockSize), aznumeric_cast(hardware.m_maxPhysicalSectorSize), false); + stackEntry->SetNext(AZStd::move(parent)); + return stackEntry; + } - void BlockCache::Section::Prefix(const Section& section) + void BlockCacheConfig::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context); serializeContext != nullptr) { - AZ_Assert(section.m_used, "Trying to prefix an unused section"); - AZ_Assert(!m_wait && !section.m_wait, "Can't merge two section that are already waiting for data to be loaded."); + serializeContext->Enum() + ->Version(1) + ->Value("MaxTransfer", BlockSize::MaxTransfer) + ->Value("MemoryAlignment", BlockSize::MemoryAlignment) + ->Value("SizeAlignment", BlockSize::SizeAlignment); - if (m_used) + serializeContext->Class() + ->Version(1) + ->Field("CacheSizeMib", &BlockCacheConfig::m_cacheSizeMib) + ->Field("BlockSize", &BlockCacheConfig::m_blockSize); + } + } + + static constexpr char CacheHitRateName[] = "Cache hit rate"; + static constexpr char CacheableName[] = "Cacheable"; + + void BlockCache::Section::Prefix(const Section& section) + { + AZ_Assert(section.m_used, "Trying to prefix an unused section"); + AZ_Assert(!m_wait && !section.m_wait, "Can't merge two section that are already waiting for data to be loaded."); + + if (m_used) + { + AZ_Assert(m_blockOffset == 0, "Unable to add a block cache to this one as this block requires an offset upon completion."); + + AZ_Assert(section.m_readOffset < m_readOffset, "The block that's being merged needs to come before this block."); + m_readOffset = section.m_readOffset + section.m_blockOffset; // Remove any alignment that might have been added. + m_readSize += section.m_readSize - section.m_blockOffset; + + AZ_Assert(section.m_output < m_output, "The block that's being merged needs to come before this block."); + m_output = section.m_output; + m_copySize += section.m_copySize; + } + else + { + m_used = true; + m_readOffset = section.m_readOffset + section.m_blockOffset; + m_readSize = section.m_readSize - section.m_blockOffset; + m_output = section.m_output; + m_copySize = section.m_copySize; + } + m_blockOffset = 0; // Two merged sections do not support caching. + } + + BlockCache::BlockCache(u64 cacheSize, u32 blockSize, u32 alignment, bool onlyEpilogWrites) + : StreamStackEntry("Block cache") + , m_alignment(alignment) + , m_onlyEpilogWrites(onlyEpilogWrites) + { + AZ_Assert(IStreamerTypes::IsPowerOf2(alignment), "Alignment needs to be a power of 2."); + AZ_Assert(IStreamerTypes::IsAlignedTo(blockSize, alignment), "Block size needs to be a multiple of the alignment."); + + m_numBlocks = aznumeric_caster(cacheSize / blockSize); + m_cacheSize = cacheSize - (cacheSize % blockSize); // Only use the amount needed for the cache. + m_blockSize = blockSize; + if (m_numBlocks == 1) + { + m_onlyEpilogWrites = true; + } + + m_cache = reinterpret_cast(AZ::AllocatorInstance::Get().Allocate( + m_cacheSize, alignment, 0, "AZ::IO::Streamer BlockCache", __FILE__, __LINE__)); + m_cachedPaths = AZStd::unique_ptr(new RequestPath[m_numBlocks]); + m_cachedOffsets = AZStd::unique_ptr(new u64[m_numBlocks]); + m_blockLastTouched = AZStd::unique_ptr(new TimePoint[m_numBlocks]); + m_inFlightRequests = AZStd::unique_ptr(new FileRequest*[m_numBlocks]); + + ResetCache(); + } + + BlockCache::~BlockCache() + { + AZ::AllocatorInstance::Get().DeAllocate(m_cache, m_cacheSize, m_alignment); + } + + void BlockCache::QueueRequest(FileRequest* request) + { + AZ_Assert(request, "QueueRequest was provided a null request."); + + AZStd::visit([this, request](auto&& args) + { + using Command = AZStd::decay_t; + if constexpr (AZStd::is_same_v) { - AZ_Assert(m_blockOffset == 0, "Unable to add a block cache to this one as this block requires an offset upon completion."); - - AZ_Assert(section.m_readOffset < m_readOffset, "The block that's being merged needs to come before this block."); - m_readOffset = section.m_readOffset + section.m_blockOffset; // Remove any alignment that might have been added. - m_readSize += section.m_readSize - section.m_blockOffset; - - AZ_Assert(section.m_output < m_output, "The block that's being merged needs to come before this block."); - m_output = section.m_output; - m_copySize += section.m_copySize; + ReadFile(request, args); + return; } else { - m_used = true; - m_readOffset = section.m_readOffset + section.m_blockOffset; - m_readSize = section.m_readSize - section.m_blockOffset; - m_output = section.m_output; - m_copySize = section.m_copySize; - } - m_blockOffset = 0; // Two merged sections do not support caching. - } - - BlockCache::BlockCache(u64 cacheSize, u32 blockSize, u32 alignment, bool onlyEpilogWrites) - : StreamStackEntry("Block cache") - , m_alignment(alignment) - , m_onlyEpilogWrites(onlyEpilogWrites) - { - AZ_Assert(IStreamerTypes::IsPowerOf2(alignment), "Alignment needs to be a power of 2."); - AZ_Assert(IStreamerTypes::IsAlignedTo(blockSize, alignment), "Block size needs to be a multiple of the alignment."); - - m_numBlocks = aznumeric_caster(cacheSize / blockSize); - m_cacheSize = cacheSize - (cacheSize % blockSize); // Only use the amount needed for the cache. - m_blockSize = blockSize; - if (m_numBlocks == 1) - { - m_onlyEpilogWrites = true; - } - - m_cache = reinterpret_cast(AZ::AllocatorInstance::Get().Allocate( - m_cacheSize, alignment, 0, "AZ::IO::Streamer BlockCache", __FILE__, __LINE__)); - m_cachedPaths = AZStd::unique_ptr(new RequestPath[m_numBlocks]); - m_cachedOffsets = AZStd::unique_ptr(new u64[m_numBlocks]); - m_blockLastTouched = AZStd::unique_ptr(new TimePoint[m_numBlocks]); - m_inFlightRequests = AZStd::unique_ptr(new FileRequest*[m_numBlocks]); - - ResetCache(); - } - - BlockCache::~BlockCache() - { - AZ::AllocatorInstance::Get().DeAllocate(m_cache, m_cacheSize, m_alignment); - } - - void BlockCache::QueueRequest(FileRequest* request) - { - AZ_Assert(request, "QueueRequest was provided a null request."); - - AZStd::visit([this, request](auto&& args) - { - using Command = AZStd::decay_t; - if constexpr (AZStd::is_same_v) + if constexpr (AZStd::is_same_v) { - ReadFile(request, args); + FlushCache(args.m_path); + } + else if constexpr (AZStd::is_same_v) + { + FlushEntireCache(); + } + StreamStackEntry::QueueRequest(request); + } + }, request->GetCommand()); + } + + bool BlockCache::ExecuteRequests() + { + size_t delayedCount = m_delayedSections.size(); + + bool delayedRequestProcessed = false; + for (size_t i = 0; i < delayedCount; ++i) + { + Section& delayed = m_delayedSections.front(); + AZ_Assert(delayed.m_parent, "Delayed section doesn't have a reference to the original request."); + auto data = AZStd::get_if(&delayed.m_parent->GetCommand()); + AZ_Assert(data, "A request in the delayed queue of the BlockCache didn't have a parent with read data."); + // This call can add the same section to the back of the queue if there's not + // enough space. Because of this the entry needs to be removed from the delayed + // list no matter what the result is of ServiceFromCache. + if (ServiceFromCache(delayed.m_parent, delayed, data->m_path, data->m_sharedRead) != CacheResult::Delayed) + { + delayedRequestProcessed = true; + } + m_delayedSections.pop_front(); + } + bool nextResult = StreamStackEntry::ExecuteRequests(); + return nextResult || delayedRequestProcessed; + } + + void BlockCache::UpdateStatus(Status& status) const + { + StreamStackEntry::UpdateStatus(status); + s32 numAvailableSlots = CalculateAvailableRequestSlots(); + status.m_numAvailableSlots = AZStd::min(status.m_numAvailableSlots, numAvailableSlots); + status.m_isIdle = status.m_isIdle && + static_cast(numAvailableSlots) == m_numBlocks && + m_delayedSections.empty(); + } + + void BlockCache::UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector& internalPending, + StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) + { + // Have the stack downstream estimate the completion time for the requests that are waiting for a slot to execute in. + AddDelayedRequests(internalPending); + + StreamStackEntry::UpdateCompletionEstimates(now, internalPending, pendingBegin, pendingEnd); + + // The in-flight requests don't have to be updated because the subdivided request will bubble up in order so the final + // write will be the latest completion time. Requests that have a wait on another request though will need to be update + // as the estimation of the in-flight request needs to be copied to the wait request to get an accurate prediction. + UpdatePendingRequestEstimations(); + + // Technically here the wait commands for the delayed sections should be updated as well, but it's the parent that's interesting, + // not the wait so don't waste cycles updating the wait. + } + + void BlockCache::AddDelayedRequests(AZStd::vector& internalPending) + { + for (auto& section : m_delayedSections) + { + internalPending.push_back(section.m_parent); + } + } + + void BlockCache::UpdatePendingRequestEstimations() + { + for (auto it : m_pendingRequests) + { + Section& section = it.second; + AZ_Assert(section.m_cacheBlockIndex != s_fileNotCached, "An in-flight cache section doesn't have a cache block associated with it."); + AZ_Assert(m_inFlightRequests[section.m_cacheBlockIndex], + "Cache block %i is reported as being in-flight but has no request.", section.m_cacheBlockIndex); + if (section.m_wait) + { + AZ_Assert(section.m_parent, "A cache section with a wait request pending is missing a parent to wait on."); + auto largestTime = AZStd::max(section.m_parent->GetEstimatedCompletion(), it.first->GetEstimatedCompletion()); + section.m_wait->SetEstimatedCompletion(largestTime); + } + } + } + + void BlockCache::ReadFile(FileRequest* request, FileRequest::ReadData& data) + { + if (!m_next) + { + request->SetStatus(IStreamerTypes::RequestStatus::Failed); + m_context->MarkRequestAsCompleted(request); + return; + } + + auto continueReadFile = [this, request](FileRequest& fileSizeRequest) + { + AZ_PROFILE_FUNCTION(AzCore); + AZ_Assert(m_numMetaDataRetrievalInProgress > 0, + "More requests have completed meta data retrieval in the Block Cache than were requested."); + m_numMetaDataRetrievalInProgress--; + if (fileSizeRequest.GetStatus() == IStreamerTypes::RequestStatus::Completed) + { + auto& requestInfo = AZStd::get(fileSizeRequest.GetCommand()); + if (requestInfo.m_found) + { + ContinueReadFile(request, requestInfo.m_fileSize); return; } - else + } + // Couldn't find the file size so don't try to split and pass the request to the next entry in the stack. + StreamStackEntry::QueueRequest(request); + }; + m_numMetaDataRetrievalInProgress++; + FileRequest* fileSizeRequest = m_context->GetNewInternalRequest(); + fileSizeRequest->CreateFileMetaDataRetrieval(data.m_path); + fileSizeRequest->SetCompletionCallback(AZStd::move(continueReadFile)); + StreamStackEntry::QueueRequest(fileSizeRequest); + } + void BlockCache::ContinueReadFile(FileRequest* request, u64 fileLength) + { + Section prolog; + Section main; + Section epilog; + + auto& data = AZStd::get(request->GetCommand()); + + if (!SplitRequest(prolog, main, epilog, data.m_path, fileLength, data.m_offset, data.m_size, + reinterpret_cast(data.m_output))) + { + m_context->MarkRequestAsCompleted(request); + return; + } + + if (prolog.m_used || epilog.m_used) + { + m_cacheableStat.PushSample(1.0); + Statistic::PlotImmediate(m_name, CacheableName, m_cacheableStat.GetMostRecentSample()); + } + else + { + // Nothing to cache so simply forward the call to the next entry in the stack for direct reading. + m_cacheableStat.PushSample(0.0); + Statistic::PlotImmediate(m_name, CacheableName, m_cacheableStat.GetMostRecentSample()); + m_next->QueueRequest(request); + return; + } + + bool fullyCached = true; + if (prolog.m_used) + { + if (m_onlyEpilogWrites && (main.m_used || epilog.m_used)) + { + // Only the epilog is allowed to write to the cache, but a previous read could + // still have cached the prolog, so check the cache and use the data if it's there + // otherwise merge the section with the main section to have the data read. + if (ReadFromCache(request, prolog, data.m_path) == CacheResult::CacheMiss) { - if constexpr (AZStd::is_same_v) - { - FlushCache(args.m_path); - } - else if constexpr (AZStd::is_same_v) - { - FlushEntireCache(); - } - StreamStackEntry::QueueRequest(request); - } - }, request->GetCommand()); - } - - bool BlockCache::ExecuteRequests() - { - size_t delayedCount = m_delayedSections.size(); - - bool delayedRequestProcessed = false; - for (size_t i = 0; i < delayedCount; ++i) - { - Section& delayed = m_delayedSections.front(); - AZ_Assert(delayed.m_parent, "Delayed section doesn't have a reference to the original request."); - auto data = AZStd::get_if(&delayed.m_parent->GetCommand()); - AZ_Assert(data, "A request in the delayed queue of the BlockCache didn't have a parent with read data."); - // This call can add the same section to the back of the queue if there's not - // enough space. Because of this the entry needs to be removed from the delayed - // list no matter what the result is of ServiceFromCache. - if (ServiceFromCache(delayed.m_parent, delayed, data->m_path, data->m_sharedRead) != CacheResult::Delayed) - { - delayedRequestProcessed = true; - } - m_delayedSections.pop_front(); - } - bool nextResult = StreamStackEntry::ExecuteRequests(); - return nextResult || delayedRequestProcessed; - } - - void BlockCache::UpdateStatus(Status& status) const - { - StreamStackEntry::UpdateStatus(status); - s32 numAvailableSlots = CalculateAvailableRequestSlots(); - status.m_numAvailableSlots = AZStd::min(status.m_numAvailableSlots, numAvailableSlots); - status.m_isIdle = status.m_isIdle && - static_cast(numAvailableSlots) == m_numBlocks && - m_delayedSections.empty(); - } - - void BlockCache::UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector& internalPending, - StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) - { - // Have the stack downstream estimate the completion time for the requests that are waiting for a slot to execute in. - AddDelayedRequests(internalPending); - - StreamStackEntry::UpdateCompletionEstimates(now, internalPending, pendingBegin, pendingEnd); - - // The in-flight requests don't have to be updated because the subdivided request will bubble up in order so the final - // write will be the latest completion time. Requests that have a wait on another request though will need to be update - // as the estimation of the in-flight request needs to be copied to the wait request to get an accurate prediction. - UpdatePendingRequestEstimations(); - - // Technically here the wait commands for the delayed sections should be updated as well, but it's the parent that's interesting, - // not the wait so don't waste cycles updating the wait. - } - - void BlockCache::AddDelayedRequests(AZStd::vector& internalPending) - { - for (auto& section : m_delayedSections) - { - internalPending.push_back(section.m_parent); - } - } - - void BlockCache::UpdatePendingRequestEstimations() - { - for (auto it : m_pendingRequests) - { - Section& section = it.second; - AZ_Assert(section.m_cacheBlockIndex != s_fileNotCached, "An in-flight cache section doesn't have a cache block associated with it."); - AZ_Assert(m_inFlightRequests[section.m_cacheBlockIndex], - "Cache block %i is reported as being in-flight but has no request.", section.m_cacheBlockIndex); - if (section.m_wait) - { - AZ_Assert(section.m_parent, "A cache section with a wait request pending is missing a parent to wait on."); - auto largestTime = AZStd::max(section.m_parent->GetEstimatedCompletion(), it.first->GetEstimatedCompletion()); - section.m_wait->SetEstimatedCompletion(largestTime); - } - } - } - - void BlockCache::ReadFile(FileRequest* request, FileRequest::ReadData& data) - { - if (!m_next) - { - request->SetStatus(IStreamerTypes::RequestStatus::Failed); - m_context->MarkRequestAsCompleted(request); - return; - } - - auto continueReadFile = [this, request](FileRequest& fileSizeRequest) - { - AZ_PROFILE_FUNCTION(AzCore); - AZ_Assert(m_numMetaDataRetrievalInProgress > 0, - "More requests have completed meta data retrieval in the Block Cache than were requested."); - m_numMetaDataRetrievalInProgress--; - if (fileSizeRequest.GetStatus() == IStreamerTypes::RequestStatus::Completed) - { - auto& requestInfo = AZStd::get(fileSizeRequest.GetCommand()); - if (requestInfo.m_found) - { - ContinueReadFile(request, requestInfo.m_fileSize); - return; - } - } - // Couldn't find the file size so don't try to split and pass the request to the next entry in the stack. - StreamStackEntry::QueueRequest(request); - }; - m_numMetaDataRetrievalInProgress++; - FileRequest* fileSizeRequest = m_context->GetNewInternalRequest(); - fileSizeRequest->CreateFileMetaDataRetrieval(data.m_path); - fileSizeRequest->SetCompletionCallback(AZStd::move(continueReadFile)); - StreamStackEntry::QueueRequest(fileSizeRequest); - } - void BlockCache::ContinueReadFile(FileRequest* request, u64 fileLength) - { - Section prolog; - Section main; - Section epilog; - - auto& data = AZStd::get(request->GetCommand()); - - if (!SplitRequest(prolog, main, epilog, data.m_path, fileLength, data.m_offset, data.m_size, - reinterpret_cast(data.m_output))) - { - m_context->MarkRequestAsCompleted(request); - return; - } - - if (prolog.m_used || epilog.m_used) - { - m_cacheableStat.PushSample(1.0); - Statistic::PlotImmediate(m_name, CacheableName, m_cacheableStat.GetMostRecentSample()); - } - else - { - // Nothing to cache so simply forward the call to the next entry in the stack for direct reading. - m_cacheableStat.PushSample(0.0); - Statistic::PlotImmediate(m_name, CacheableName, m_cacheableStat.GetMostRecentSample()); - m_next->QueueRequest(request); - return; - } - - bool fullyCached = true; - if (prolog.m_used) - { - if (m_onlyEpilogWrites && (main.m_used || epilog.m_used)) - { - // Only the epilog is allowed to write to the cache, but a previous read could - // still have cached the prolog, so check the cache and use the data if it's there - // otherwise merge the section with the main section to have the data read. - if (ReadFromCache(request, prolog, data.m_path) == CacheResult::CacheMiss) - { - // The data isn't cached so put the prolog in front of the main section - // so it's read in one read request. If main wasn't used, prefixing the prolog - // will cause it to be filled in and used. - main.Prefix(prolog); - m_hitRateStat.PushSample(0.0); - Statistic::PlotImmediate(m_name, CacheHitRateName, m_hitRateStat.GetMostRecentSample()); - } - else - { - m_hitRateStat.PushSample(1.0); - Statistic::PlotImmediate(m_name, CacheHitRateName, m_hitRateStat.GetMostRecentSample()); - } + // The data isn't cached so put the prolog in front of the main section + // so it's read in one read request. If main wasn't used, prefixing the prolog + // will cause it to be filled in and used. + main.Prefix(prolog); + m_hitRateStat.PushSample(0.0); + Statistic::PlotImmediate(m_name, CacheHitRateName, m_hitRateStat.GetMostRecentSample()); } else { - // If m_onlyEpilogWrites is set but main and epilog are not filled in, it means that - // the request was so small it fits in one cache block, in which case the prolog and - // epilog are practically the same. Or this code is reached because both prolog and - // epilog are allowed to write. - bool readFromCache = (ServiceFromCache(request, prolog, data.m_path, data.m_sharedRead) == CacheResult::ReadFromCache); - fullyCached = readFromCache && fullyCached; - - m_hitRateStat.PushSample(readFromCache ? 1.0 : 0.0); + m_hitRateStat.PushSample(1.0); Statistic::PlotImmediate(m_name, CacheHitRateName, m_hitRateStat.GetMostRecentSample()); } } - - if (main.m_used) + else { - FileRequest* mainRequest = m_context->GetNewInternalRequest(); - // No need for a callback as there's nothing to do after the read has been completed. - mainRequest->CreateRead(request, main.m_output, main.m_readSize, data.m_path, - main.m_readOffset, main.m_readSize, data.m_sharedRead); - m_next->QueueRequest(mainRequest); - fullyCached = false; - } - - if (epilog.m_used) - { - bool readFromCache = (ServiceFromCache(request, epilog, data.m_path, data.m_sharedRead) == CacheResult::ReadFromCache); + // If m_onlyEpilogWrites is set but main and epilog are not filled in, it means that + // the request was so small it fits in one cache block, in which case the prolog and + // epilog are practically the same. Or this code is reached because both prolog and + // epilog are allowed to write. + bool readFromCache = (ServiceFromCache(request, prolog, data.m_path, data.m_sharedRead) == CacheResult::ReadFromCache); fullyCached = readFromCache && fullyCached; m_hitRateStat.PushSample(readFromCache ? 1.0 : 0.0); Statistic::PlotImmediate(m_name, CacheHitRateName, m_hitRateStat.GetMostRecentSample()); } + } - if (fullyCached) + if (main.m_used) + { + FileRequest* mainRequest = m_context->GetNewInternalRequest(); + // No need for a callback as there's nothing to do after the read has been completed. + mainRequest->CreateRead(request, main.m_output, main.m_readSize, data.m_path, + main.m_readOffset, main.m_readSize, data.m_sharedRead); + m_next->QueueRequest(mainRequest); + fullyCached = false; + } + + if (epilog.m_used) + { + bool readFromCache = (ServiceFromCache(request, epilog, data.m_path, data.m_sharedRead) == CacheResult::ReadFromCache); + fullyCached = readFromCache && fullyCached; + + m_hitRateStat.PushSample(readFromCache ? 1.0 : 0.0); + Statistic::PlotImmediate(m_name, CacheHitRateName, m_hitRateStat.GetMostRecentSample()); + } + + if (fullyCached) + { + request->SetStatus(IStreamerTypes::RequestStatus::Completed); + m_context->MarkRequestAsCompleted(request); + } + } + + void BlockCache::FlushCache(const RequestPath& filePath) + { + for (u32 i = 0; i < m_numBlocks; ++i) + { + if (m_cachedPaths[i] == filePath) { - request->SetStatus(IStreamerTypes::RequestStatus::Completed); - m_context->MarkRequestAsCompleted(request); + ResetCacheEntry(i); } } + } - void BlockCache::FlushCache(const RequestPath& filePath) + void BlockCache::FlushEntireCache() + { + ResetCache(); + } + + void BlockCache::CollectStatistics(AZStd::vector& statistics) const + { + statistics.push_back(Statistic::CreatePercentage(m_name, CacheHitRateName, CalculateHitRatePercentage())); + statistics.push_back(Statistic::CreatePercentage(m_name, CacheableName, CalculateCacheableRatePercentage())); + statistics.push_back(Statistic::CreateInteger(m_name, "Available slots", CalculateAvailableRequestSlots())); + + StreamStackEntry::CollectStatistics(statistics); + } + + double BlockCache::CalculateHitRatePercentage() const + { + return m_hitRateStat.GetAverage(); + } + + double BlockCache::CalculateCacheableRatePercentage() const + { + return m_cacheableStat.GetAverage(); + } + + s32 BlockCache::CalculateAvailableRequestSlots() const + { + return aznumeric_cast(m_numBlocks) - m_numInFlightRequests - m_numMetaDataRetrievalInProgress - + aznumeric_cast(m_delayedSections.size()); + } + + BlockCache::CacheResult BlockCache::ReadFromCache(FileRequest* request, Section& section, const RequestPath& filePath) + { + u32 cacheLocation = FindInCache(filePath, section.m_readOffset); + if (cacheLocation != s_fileNotCached) { - for (u32 i = 0; i < m_numBlocks; ++i) - { - if (m_cachedPaths[i] == filePath) - { - ResetCacheEntry(i); - } - } + return ReadFromCache(request, section, cacheLocation); } - - void BlockCache::FlushEntireCache() + else { - ResetCache(); + return CacheResult::CacheMiss; } + } - void BlockCache::CollectStatistics(AZStd::vector& statistics) const + BlockCache::CacheResult BlockCache::ReadFromCache(FileRequest* request, Section& section, u32 cacheBlock) + { + if (!IsCacheBlockInFlight(cacheBlock)) { - statistics.push_back(Statistic::CreatePercentage(m_name, CacheHitRateName, CalculateHitRatePercentage())); - statistics.push_back(Statistic::CreatePercentage(m_name, CacheableName, CalculateCacheableRatePercentage())); - statistics.push_back(Statistic::CreateInteger(m_name, "Available slots", CalculateAvailableRequestSlots())); - - StreamStackEntry::CollectStatistics(statistics); + TouchBlock(cacheBlock); + memcpy(section.m_output, GetCacheBlockData(cacheBlock) + section.m_blockOffset, section.m_copySize); + return CacheResult::ReadFromCache; } - - double BlockCache::CalculateHitRatePercentage() const + else { - return m_hitRateStat.GetAverage(); + AZ_Assert(section.m_wait == nullptr, "A wait request has to be set on a block cache section, but one has already been assigned."); + FileRequest* wait = m_context->GetNewInternalRequest(); + wait->CreateWait(request); + section.m_cacheBlockIndex = cacheBlock; + section.m_parent = request; + section.m_wait = wait; + m_pendingRequests.emplace(m_inFlightRequests[cacheBlock], section); + return CacheResult::Queued; } + } - double BlockCache::CalculateCacheableRatePercentage() const - { - return m_cacheableStat.GetAverage(); - } + BlockCache::CacheResult BlockCache::ServiceFromCache( + FileRequest* request, Section& section, const RequestPath& filePath, bool sharedRead) + { + AZ_Assert(m_next, "ServiceFromCache in BlockCache was called when the cache doesn't have a way to read files."); - s32 BlockCache::CalculateAvailableRequestSlots() const + u32 cacheLocation = FindInCache(filePath, section.m_readOffset); + if (cacheLocation == s_fileNotCached) { - return aznumeric_cast(m_numBlocks) - m_numInFlightRequests - m_numMetaDataRetrievalInProgress - - aznumeric_cast(m_delayedSections.size()); - } + m_hitRateStat.PushSample(0.0); + Statistic::PlotImmediate(m_name, CacheHitRateName, m_hitRateStat.GetMostRecentSample()); - BlockCache::CacheResult BlockCache::ReadFromCache(FileRequest* request, Section& section, const RequestPath& filePath) - { - u32 cacheLocation = FindInCache(filePath, section.m_readOffset); + section.m_parent = request; + cacheLocation = RecycleOldestBlock(filePath, section.m_readOffset); if (cacheLocation != s_fileNotCached) { - return ReadFromCache(request, section, cacheLocation); - } - else - { - return CacheResult::CacheMiss; - } - } + FileRequest* readRequest = m_context->GetNewInternalRequest(); + readRequest->CreateRead(request, GetCacheBlockData(cacheLocation), m_blockSize, filePath, section.m_readOffset, + section.m_readSize, sharedRead); + readRequest->SetCompletionCallback([this](FileRequest& request) + { + AZ_PROFILE_FUNCTION(AzCore); + CompleteRead(request); + }); + section.m_cacheBlockIndex = cacheLocation; + m_inFlightRequests[cacheLocation] = readRequest; + m_numInFlightRequests++; - BlockCache::CacheResult BlockCache::ReadFromCache(FileRequest* request, Section& section, u32 cacheBlock) - { - if (!IsCacheBlockInFlight(cacheBlock)) - { - TouchBlock(cacheBlock); - memcpy(section.m_output, GetCacheBlockData(cacheBlock) + section.m_blockOffset, section.m_copySize); - return CacheResult::ReadFromCache; - } - else - { - AZ_Assert(section.m_wait == nullptr, "A wait request has to be set on a block cache section, but one has already been assigned."); - FileRequest* wait = m_context->GetNewInternalRequest(); - wait->CreateWait(request); - section.m_cacheBlockIndex = cacheBlock; - section.m_parent = request; - section.m_wait = wait; - m_pendingRequests.emplace(m_inFlightRequests[cacheBlock], section); + // If set, this is the wait added by the delay. + if (section.m_wait) + { + m_context->MarkRequestAsCompleted(section.m_wait); + section.m_wait = nullptr; + } + + m_pendingRequests.emplace(readRequest, section); + m_next->QueueRequest(readRequest); return CacheResult::Queued; } - } - - BlockCache::CacheResult BlockCache::ServiceFromCache( - FileRequest* request, Section& section, const RequestPath& filePath, bool sharedRead) - { - AZ_Assert(m_next, "ServiceFromCache in BlockCache was called when the cache doesn't have a way to read files."); - - u32 cacheLocation = FindInCache(filePath, section.m_readOffset); - if (cacheLocation == s_fileNotCached) - { - m_hitRateStat.PushSample(0.0); - Statistic::PlotImmediate(m_name, CacheHitRateName, m_hitRateStat.GetMostRecentSample()); - - section.m_parent = request; - cacheLocation = RecycleOldestBlock(filePath, section.m_readOffset); - if (cacheLocation != s_fileNotCached) - { - FileRequest* readRequest = m_context->GetNewInternalRequest(); - readRequest->CreateRead(request, GetCacheBlockData(cacheLocation), m_blockSize, filePath, section.m_readOffset, - section.m_readSize, sharedRead); - readRequest->SetCompletionCallback([this](FileRequest& request) - { - AZ_PROFILE_FUNCTION(AzCore); - CompleteRead(request); - }); - section.m_cacheBlockIndex = cacheLocation; - m_inFlightRequests[cacheLocation] = readRequest; - m_numInFlightRequests++; - - // If set, this is the wait added by the delay. - if (section.m_wait) - { - m_context->MarkRequestAsCompleted(section.m_wait); - section.m_wait = nullptr; - } - - m_pendingRequests.emplace(readRequest, section); - m_next->QueueRequest(readRequest); - return CacheResult::Queued; - } - else - { - // There's no more space in the cache to store this request to. This is because there are more in-flight requests than - // there are slots in the cache. Delay the request until there's a slot available but add a wait for the section to - // make sure the request can't complete if some parts are read. - if (!section.m_wait) - { - section.m_wait = m_context->GetNewInternalRequest(); - section.m_wait->CreateWait(request); - } - m_delayedSections.push_back(section); - return CacheResult::Delayed; - } - } else { - // If set, this is the wait added by the delay when the cache was full. - if (section.m_wait) + // There's no more space in the cache to store this request to. This is because there are more in-flight requests than + // there are slots in the cache. Delay the request until there's a slot available but add a wait for the section to + // make sure the request can't complete if some parts are read. + if (!section.m_wait) { - m_context->MarkRequestAsCompleted(section.m_wait); - section.m_wait = nullptr; + section.m_wait = m_context->GetNewInternalRequest(); + section.m_wait->CreateWait(request); } - - m_hitRateStat.PushSample(1.0); - Statistic::PlotImmediate(m_name, CacheHitRateName, m_hitRateStat.GetMostRecentSample()); - - return ReadFromCache(request, section, cacheLocation); + m_delayedSections.push_back(section); + return CacheResult::Delayed; } } - - void BlockCache::CompleteRead(FileRequest& request) + else { - auto requestInfo = m_pendingRequests.equal_range(&request); - AZ_Assert(requestInfo.first != requestInfo.second, "Block cache was asked to complete a file request it never queued."); - - IStreamerTypes::RequestStatus requestStatus = request.GetStatus(); - bool requestWasSuccessful = requestStatus == IStreamerTypes::RequestStatus::Completed; - u32 cacheBlockIndex = requestInfo.first->second.m_cacheBlockIndex; - - for (auto it = requestInfo.first; it != requestInfo.second; ++it) + // If set, this is the wait added by the delay when the cache was full. + if (section.m_wait) { - Section& section = it->second; - AZ_Assert(section.m_cacheBlockIndex == cacheBlockIndex, - "Section associated with the file request is referencing the incorrect cache block (%u vs %u).", cacheBlockIndex, section.m_cacheBlockIndex); - if (section.m_wait) - { - section.m_wait->SetStatus(requestStatus); - m_context->MarkRequestAsCompleted(section.m_wait); - section.m_wait = nullptr; - } + m_context->MarkRequestAsCompleted(section.m_wait); + section.m_wait = nullptr; + } - if (requestWasSuccessful) - { - memcpy(section.m_output, GetCacheBlockData(cacheBlockIndex) + section.m_blockOffset, section.m_copySize); - } + m_hitRateStat.PushSample(1.0); + Statistic::PlotImmediate(m_name, CacheHitRateName, m_hitRateStat.GetMostRecentSample()); + + return ReadFromCache(request, section, cacheLocation); + } + } + + void BlockCache::CompleteRead(FileRequest& request) + { + auto requestInfo = m_pendingRequests.equal_range(&request); + AZ_Assert(requestInfo.first != requestInfo.second, "Block cache was asked to complete a file request it never queued."); + + IStreamerTypes::RequestStatus requestStatus = request.GetStatus(); + bool requestWasSuccessful = requestStatus == IStreamerTypes::RequestStatus::Completed; + u32 cacheBlockIndex = requestInfo.first->second.m_cacheBlockIndex; + + for (auto it = requestInfo.first; it != requestInfo.second; ++it) + { + Section& section = it->second; + AZ_Assert(section.m_cacheBlockIndex == cacheBlockIndex, + "Section associated with the file request is referencing the incorrect cache block (%u vs %u).", cacheBlockIndex, section.m_cacheBlockIndex); + if (section.m_wait) + { + section.m_wait->SetStatus(requestStatus); + m_context->MarkRequestAsCompleted(section.m_wait); + section.m_wait = nullptr; } if (requestWasSuccessful) { - TouchBlock(cacheBlockIndex); - m_inFlightRequests[cacheBlockIndex] = nullptr; + memcpy(section.m_output, GetCacheBlockData(cacheBlockIndex) + section.m_blockOffset, section.m_copySize); } - else - { - ResetCacheEntry(cacheBlockIndex); - } - AZ_Assert(m_numInFlightRequests > 0, "Clearing out an in-flight request, but there shouldn't be any in flight according to records."); - m_numInFlightRequests--; - m_pendingRequests.erase(&request); } - bool BlockCache::SplitRequest(Section& prolog, Section& main, Section& epilog, - [[maybe_unused]] const RequestPath& filePath, u64 fileLength, - u64 offset, u64 size, u8* buffer) const + if (requestWasSuccessful) { - AZ_Assert(offset + size <= fileLength, "File at path '%s' is being read past the end of the file.", filePath.GetRelativePath()); + TouchBlock(cacheBlockIndex); + m_inFlightRequests[cacheBlockIndex] = nullptr; + } + else + { + ResetCacheEntry(cacheBlockIndex); + } + AZ_Assert(m_numInFlightRequests > 0, "Clearing out an in-flight request, but there shouldn't be any in flight according to records."); + m_numInFlightRequests--; + m_pendingRequests.erase(&request); + } - // - // Prolog - // This looks at the request and sees if there's anything in front of the file that should be cached. This also - // deals with the situation where the entire file request fits inside the cache which could mean there's data - // left after the file as well that could be cached. - // - u64 roundedOffsetStart = AZ_SIZE_ALIGN_DOWN(offset, aznumeric_cast(m_blockSize)); - - u64 blockReadSizeStart = AZStd::min(fileLength - roundedOffsetStart, aznumeric_cast(m_blockSize)); - // Check if the request is on the left edge of the cache block, which means there's nothing in front of it - // that could be cached. - if (roundedOffsetStart == offset) - { - if (offset + size >= fileLength) - { - // The entire (remainder) of the file is read so there's nothing to cache - main.m_readOffset = offset; - main.m_readSize = size; - main.m_output = buffer; - main.m_used = true; - return true; - } - else if (size < blockReadSizeStart) - { - // The entire request fits inside a single cache block, but there's more file to read. - prolog.m_readOffset = offset; - prolog.m_readSize = blockReadSizeStart; - prolog.m_blockOffset = 0; - prolog.m_output = buffer; - prolog.m_copySize = size; - prolog.m_used = true; - return true; - } - // In any other case it means that the entire block would be read so caching has no effect. - } - else - { - // There is a portion of the file before that's not requested so always cache this block. - const u64 blockOffset = offset - roundedOffsetStart; - prolog.m_readOffset = roundedOffsetStart; - prolog.m_blockOffset = blockOffset; - prolog.m_output = buffer; - prolog.m_used = true; + bool BlockCache::SplitRequest(Section& prolog, Section& main, Section& epilog, + [[maybe_unused]] const RequestPath& filePath, u64 fileLength, + u64 offset, u64 size, u8* buffer) const + { + AZ_Assert(offset + size <= fileLength, "File at path '%s' is being read past the end of the file.", filePath.GetRelativePath()); - const bool isEntirelyInCache = blockOffset + size <= blockReadSizeStart; - if (isEntirelyInCache) - { - // The read size is already clamped to the file size above when blockReadSizeStart is set. - AZ_Assert(roundedOffsetStart + blockReadSizeStart <= fileLength, - "Read size in block cache was set to %llu but this is beyond the file length of %llu.", - roundedOffsetStart + blockReadSizeStart, fileLength); - prolog.m_readSize = blockReadSizeStart; - prolog.m_copySize = size; + // + // Prolog + // This looks at the request and sees if there's anything in front of the file that should be cached. This also + // deals with the situation where the entire file request fits inside the cache which could mean there's data + // left after the file as well that could be cached. + // + u64 roundedOffsetStart = AZ_SIZE_ALIGN_DOWN(offset, aznumeric_cast(m_blockSize)); - // There won't be anything else coming after this so continue reading. - return true; - } - else - { - prolog.m_readSize = blockReadSizeStart; - prolog.m_copySize = blockReadSizeStart - blockOffset; - } - } - - - // - // Epilog - // Since the prolog already takes care of the situation where the file fits entirely in the cache the epilog is - // much simpler as it only has to look at the case where there is more file after the request to read for caching. - // - u64 roundedOffsetEnd = AZ_SIZE_ALIGN_DOWN(offset + size, aznumeric_cast(m_blockSize)); - u64 copySize = offset + size - roundedOffsetEnd; - u64 blockReadSizeEnd = m_blockSize; - if ((roundedOffsetEnd + blockReadSizeEnd) > fileLength) + u64 blockReadSizeStart = AZStd::min(fileLength - roundedOffsetStart, aznumeric_cast(m_blockSize)); + // Check if the request is on the left edge of the cache block, which means there's nothing in front of it + // that could be cached. + if (roundedOffsetStart == offset) + { + if (offset + size >= fileLength) { - blockReadSizeEnd = fileLength - roundedOffsetEnd; - } - - // If the read doesn't align with the edge of the cache - if (copySize != 0 && copySize < blockReadSizeEnd) - { - epilog.m_readOffset = roundedOffsetEnd; - epilog.m_readSize = blockReadSizeEnd; - epilog.m_blockOffset = 0; - epilog.m_output = buffer + (roundedOffsetEnd - offset); - epilog.m_copySize = copySize; - epilog.m_used = true; - } - - // - // Main - // If this point is reached there's potentially a block between the prolog and epilog that can be directly read. - // - u64 adjustedOffset = offset; - if (prolog.m_used) - { - adjustedOffset += prolog.m_copySize; - size -= prolog.m_copySize; - } - if (epilog.m_used) - { - size -= epilog.m_copySize; - } - AZ_Assert(IStreamerTypes::IsAlignedTo(adjustedOffset, m_blockSize), - "The adjustments made by the prolog should guarantee the offset is aligned to a cache block."); - if (size != 0) - { - main.m_readOffset = adjustedOffset; + // The entire (remainder) of the file is read so there's nothing to cache + main.m_readOffset = offset; main.m_readSize = size; - main.m_output = buffer + (adjustedOffset - offset); + main.m_output = buffer; main.m_used = true; + return true; } - - return true; - } - - u8* BlockCache::GetCacheBlockData(u32 index) - { - AZ_Assert(index < m_numBlocks, "Index for touch a cache entry in the BlockCache is out of bounds."); - return m_cache + (index * m_blockSize); - } - - void BlockCache::TouchBlock(u32 index) - { - AZ_Assert(index < m_numBlocks, "Index for touch a cache entry in the BlockCache is out of bounds."); - m_blockLastTouched[index] = AZStd::chrono::high_resolution_clock::now(); - } - - u32 BlockCache::RecycleOldestBlock(const RequestPath& filePath, u64 offset) - { - AZ_Assert((offset & (m_blockSize - 1)) == 0, "The offset used to recycle a block cache needs to be a multiple of the block size."); - - // Find the oldest cache block. - TimePoint oldest = m_blockLastTouched[0]; - u32 oldestIndex = 0; - for (u32 i = 1; i < m_numBlocks; ++i) + else if (size < blockReadSizeStart) { - if (m_blockLastTouched[i] < oldest && !m_inFlightRequests[i]) - { - oldest = m_blockLastTouched[i]; - oldestIndex = i; - } + // The entire request fits inside a single cache block, but there's more file to read. + prolog.m_readOffset = offset; + prolog.m_readSize = blockReadSizeStart; + prolog.m_blockOffset = 0; + prolog.m_output = buffer; + prolog.m_copySize = size; + prolog.m_used = true; + return true; } + // In any other case it means that the entire block would be read so caching has no effect. + } + else + { + // There is a portion of the file before that's not requested so always cache this block. + const u64 blockOffset = offset - roundedOffsetStart; + prolog.m_readOffset = roundedOffsetStart; + prolog.m_blockOffset = blockOffset; + prolog.m_output = buffer; + prolog.m_used = true; - if (!IsCacheBlockInFlight(oldestIndex)) + const bool isEntirelyInCache = blockOffset + size <= blockReadSizeStart; + if (isEntirelyInCache) { - // Recycle the block. - m_cachedPaths[oldestIndex] = filePath; - m_cachedOffsets[oldestIndex] = offset; - TouchBlock(oldestIndex); - return oldestIndex; + // The read size is already clamped to the file size above when blockReadSizeStart is set. + AZ_Assert(roundedOffsetStart + blockReadSizeStart <= fileLength, + "Read size in block cache was set to %llu but this is beyond the file length of %llu.", + roundedOffsetStart + blockReadSizeStart, fileLength); + prolog.m_readSize = blockReadSizeStart; + prolog.m_copySize = size; + + // There won't be anything else coming after this so continue reading. + return true; } else { - return s_fileNotCached; + prolog.m_readSize = blockReadSizeStart; + prolog.m_copySize = blockReadSizeStart - blockOffset; } } - u32 BlockCache::FindInCache(const RequestPath& filePath, u64 offset) const - { - AZ_Assert((offset & (m_blockSize - 1)) == 0, "The offset used to find a block in the block cache needs to be a multiple of the block size."); - for (u32 i = 0; i < m_numBlocks; ++i) - { - if (m_cachedPaths[i] == filePath && m_cachedOffsets[i] == offset) - { - return i; - } - } + // + // Epilog + // Since the prolog already takes care of the situation where the file fits entirely in the cache the epilog is + // much simpler as it only has to look at the case where there is more file after the request to read for caching. + // + u64 roundedOffsetEnd = AZ_SIZE_ALIGN_DOWN(offset + size, aznumeric_cast(m_blockSize)); + u64 copySize = offset + size - roundedOffsetEnd; + u64 blockReadSizeEnd = m_blockSize; + if ((roundedOffsetEnd + blockReadSizeEnd) > fileLength) + { + blockReadSizeEnd = fileLength - roundedOffsetEnd; + } + + // If the read doesn't align with the edge of the cache + if (copySize != 0 && copySize < blockReadSizeEnd) + { + epilog.m_readOffset = roundedOffsetEnd; + epilog.m_readSize = blockReadSizeEnd; + epilog.m_blockOffset = 0; + epilog.m_output = buffer + (roundedOffsetEnd - offset); + epilog.m_copySize = copySize; + epilog.m_used = true; + } + + // + // Main + // If this point is reached there's potentially a block between the prolog and epilog that can be directly read. + // + u64 adjustedOffset = offset; + if (prolog.m_used) + { + adjustedOffset += prolog.m_copySize; + size -= prolog.m_copySize; + } + if (epilog.m_used) + { + size -= epilog.m_copySize; + } + AZ_Assert(IStreamerTypes::IsAlignedTo(adjustedOffset, m_blockSize), + "The adjustments made by the prolog should guarantee the offset is aligned to a cache block."); + if (size != 0) + { + main.m_readOffset = adjustedOffset; + main.m_readSize = size; + main.m_output = buffer + (adjustedOffset - offset); + main.m_used = true; + } + + return true; + } + + u8* BlockCache::GetCacheBlockData(u32 index) + { + AZ_Assert(index < m_numBlocks, "Index for touch a cache entry in the BlockCache is out of bounds."); + return m_cache + (index * m_blockSize); + } + + void BlockCache::TouchBlock(u32 index) + { + AZ_Assert(index < m_numBlocks, "Index for touch a cache entry in the BlockCache is out of bounds."); + m_blockLastTouched[index] = AZStd::chrono::high_resolution_clock::now(); + } + + u32 BlockCache::RecycleOldestBlock(const RequestPath& filePath, u64 offset) + { + AZ_Assert((offset & (m_blockSize - 1)) == 0, "The offset used to recycle a block cache needs to be a multiple of the block size."); + + // Find the oldest cache block. + TimePoint oldest = m_blockLastTouched[0]; + u32 oldestIndex = 0; + for (u32 i = 1; i < m_numBlocks; ++i) + { + if (m_blockLastTouched[i] < oldest && !m_inFlightRequests[i]) + { + oldest = m_blockLastTouched[i]; + oldestIndex = i; + } + } + + if (!IsCacheBlockInFlight(oldestIndex)) + { + // Recycle the block. + m_cachedPaths[oldestIndex] = filePath; + m_cachedOffsets[oldestIndex] = offset; + TouchBlock(oldestIndex); + return oldestIndex; + } + else + { return s_fileNotCached; } + } - bool BlockCache::IsCacheBlockInFlight(u32 index) const + u32 BlockCache::FindInCache(const RequestPath& filePath, u64 offset) const + { + AZ_Assert((offset & (m_blockSize - 1)) == 0, "The offset used to find a block in the block cache needs to be a multiple of the block size."); + for (u32 i = 0; i < m_numBlocks; ++i) { - AZ_Assert(index < m_numBlocks, "Index for checking if a cache block is in flight is out of bounds."); - return m_inFlightRequests[index] != nullptr; - } - - void BlockCache::ResetCacheEntry(u32 index) - { - AZ_Assert(index < m_numBlocks, "Index for resetting a cache entry in the BlockCache is out of bounds."); - - m_cachedPaths[index].Clear(); - m_cachedOffsets[index] = 0; - m_blockLastTouched[index] = TimePoint::min(); - m_inFlightRequests[index] = nullptr; - } - - void BlockCache::ResetCache() - { - for (u32 i = 0; i < m_numBlocks; ++i) + if (m_cachedPaths[i] == filePath && m_cachedOffsets[i] == offset) { - ResetCacheEntry(i); + return i; } - m_numInFlightRequests = 0; } - } // namespace IO -} // namespace AZ + + return s_fileNotCached; + } + + bool BlockCache::IsCacheBlockInFlight(u32 index) const + { + AZ_Assert(index < m_numBlocks, "Index for checking if a cache block is in flight is out of bounds."); + return m_inFlightRequests[index] != nullptr; + } + + void BlockCache::ResetCacheEntry(u32 index) + { + AZ_Assert(index < m_numBlocks, "Index for resetting a cache entry in the BlockCache is out of bounds."); + + m_cachedPaths[index].Clear(); + m_cachedOffsets[index] = 0; + m_blockLastTouched[index] = TimePoint::min(); + m_inFlightRequests[index] = nullptr; + } + + void BlockCache::ResetCache() + { + for (u32 i = 0; i < m_numBlocks; ++i) + { + ResetCacheEntry(i); + } + m_numInFlightRequests = 0; + } +} // namespace AZ::IO diff --git a/Code/Framework/AzCore/AzCore/IO/Streamer/DedicatedCache.cpp b/Code/Framework/AzCore/AzCore/IO/Streamer/DedicatedCache.cpp index e0e512e21f..b80a1ea724 100644 --- a/Code/Framework/AzCore/AzCore/IO/Streamer/DedicatedCache.cpp +++ b/Code/Framework/AzCore/AzCore/IO/Streamer/DedicatedCache.cpp @@ -12,320 +12,317 @@ #include #include -namespace AZ +namespace AZ::IO { - namespace IO + AZStd::shared_ptr DedicatedCacheConfig::AddStreamStackEntry( + const HardwareInformation& hardware, AZStd::shared_ptr parent) { - AZStd::shared_ptr DedicatedCacheConfig::AddStreamStackEntry( - const HardwareInformation& hardware, AZStd::shared_ptr parent) + size_t blockSize; + switch (m_blockSize) { - size_t blockSize; - switch (m_blockSize) + case BlockCacheConfig::BlockSize::MaxTransfer: + blockSize = hardware.m_maxTransfer; + break; + case BlockCacheConfig::BlockSize::MemoryAlignment: + blockSize = hardware.m_maxPhysicalSectorSize; + break; + case BlockCacheConfig::BlockSize::SizeAlignment: + blockSize = hardware.m_maxLogicalSectorSize; + break; + default: + blockSize = m_blockSize; + break; + } + + u32 cacheSize = static_cast(m_cacheSizeMib * 1_mib); + if (blockSize > cacheSize) + { + AZ_Warning("Streamer", false, "Size (%u) for DedicatedCache isn't big enough to hold at least one cache blocks of size (%zu). " + "The cache size will be increased to fit one cache block.", cacheSize, blockSize); + cacheSize = aznumeric_caster(blockSize); + } + + auto stackEntry = AZStd::make_shared( + cacheSize, aznumeric_cast(blockSize), aznumeric_cast(hardware.m_maxPhysicalSectorSize), m_writeOnlyEpilog); + stackEntry->SetNext(AZStd::move(parent)); + return stackEntry; + } + + void DedicatedCacheConfig::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context); serializeContext != nullptr) + { + serializeContext->Class() + ->Version(1) + ->Field("CacheSizeMib", &DedicatedCacheConfig::m_cacheSizeMib) + ->Field("BlockSize", &DedicatedCacheConfig::m_blockSize) + ->Field("WriteOnlyEpilog", &DedicatedCacheConfig::m_writeOnlyEpilog); + } + } + + + + // + // DedicatedCache + // + + DedicatedCache::DedicatedCache(u64 cacheSize, u32 blockSize, u32 alignment, bool onlyEpilogWrites) + : StreamStackEntry("Dedicated cache") + , m_cacheSize(cacheSize) + , m_alignment(alignment) + , m_blockSize(blockSize) + , m_onlyEpilogWrites(onlyEpilogWrites) + { + } + + void DedicatedCache::SetNext(AZStd::shared_ptr next) + { + m_next = AZStd::move(next); + for (AZStd::unique_ptr& cache : m_cachedFileCaches) + { + cache->SetNext(m_next); + } + } + + void DedicatedCache::SetContext(StreamerContext& context) + { + StreamStackEntry::SetContext(context); + for (AZStd::unique_ptr& cache : m_cachedFileCaches) + { + cache->SetContext(context); + } + } + + void DedicatedCache::PrepareRequest(FileRequest* request) + { + AZ_Assert(request, "PrepareRequest was provided a null request."); + + // Claim the requests so other entries can't claim it and make updates. + AZStd::visit([this, request](auto&& args) + { + using Command = AZStd::decay_t; + if constexpr (AZStd::is_same_v) { - case BlockCacheConfig::BlockSize::MaxTransfer: - blockSize = hardware.m_maxTransfer; - break; - case BlockCacheConfig::BlockSize::MemoryAlignment: - blockSize = hardware.m_maxPhysicalSectorSize; - break; - case BlockCacheConfig::BlockSize::SizeAlignment: - blockSize = hardware.m_maxLogicalSectorSize; - break; - default: - blockSize = m_blockSize; - break; + args.m_range = FileRange::CreateRangeForEntireFile(); + m_context->PushPreparedRequest(request); } - - u32 cacheSize = static_cast(m_cacheSizeMib * 1_mib); - if (blockSize > cacheSize) + else if constexpr (AZStd::is_same_v) { - AZ_Warning("Streamer", false, "Size (%u) for DedicatedCache isn't big enough to hold at least one cache blocks of size (%zu). " - "The cache size will be increased to fit one cache block.", cacheSize, blockSize); - cacheSize = aznumeric_caster(blockSize); - } - - auto stackEntry = AZStd::make_shared( - cacheSize, aznumeric_cast(blockSize), aznumeric_cast(hardware.m_maxPhysicalSectorSize), m_writeOnlyEpilog); - stackEntry->SetNext(AZStd::move(parent)); - return stackEntry; - } - - void DedicatedCacheConfig::Reflect(AZ::ReflectContext* context) - { - if (auto serializeContext = azrtti_cast(context); serializeContext != nullptr) - { - serializeContext->Class() - ->Version(1) - ->Field("CacheSizeMib", &DedicatedCacheConfig::m_cacheSizeMib) - ->Field("BlockSize", &DedicatedCacheConfig::m_blockSize) - ->Field("WriteOnlyEpilog", &DedicatedCacheConfig::m_writeOnlyEpilog); - } - } - - - - // - // DedicatedCache - // - - DedicatedCache::DedicatedCache(u64 cacheSize, u32 blockSize, u32 alignment, bool onlyEpilogWrites) - : StreamStackEntry("Dedicated cache") - , m_cacheSize(cacheSize) - , m_alignment(alignment) - , m_blockSize(blockSize) - , m_onlyEpilogWrites(onlyEpilogWrites) - { - } - - void DedicatedCache::SetNext(AZStd::shared_ptr next) - { - m_next = AZStd::move(next); - for (AZStd::unique_ptr& cache : m_cachedFileCaches) - { - cache->SetNext(m_next); - } - } - - void DedicatedCache::SetContext(StreamerContext& context) - { - StreamStackEntry::SetContext(context); - for (AZStd::unique_ptr& cache : m_cachedFileCaches) - { - cache->SetContext(context); - } - } - - void DedicatedCache::PrepareRequest(FileRequest* request) - { - AZ_Assert(request, "PrepareRequest was provided a null request."); - - // Claim the requests so other entries can't claim it and make updates. - AZStd::visit([this, request](auto&& args) - { - using Command = AZStd::decay_t; - if constexpr (AZStd::is_same_v) - { - args.m_range = FileRange::CreateRangeForEntireFile(); - m_context->PushPreparedRequest(request); - } - else if constexpr (AZStd::is_same_v) - { - args.m_range = FileRange::CreateRangeForEntireFile(); - m_context->PushPreparedRequest(request); - } - else - { - StreamStackEntry::PrepareRequest(request); - } - }, request->GetCommand()); - } - - void DedicatedCache::QueueRequest(FileRequest* request) - { - AZ_Assert(request, "QueueRequest was provided a null request."); - - AZStd::visit([this, request](auto&& args) - { - using Command = AZStd::decay_t; - if constexpr (AZStd::is_same_v) - { - ReadFile(request, args); - return; - } - else if constexpr (AZStd::is_same_v) - { - CreateDedicatedCache(request, args); - return; - } - else if constexpr (AZStd::is_same_v) - { - DestroyDedicatedCache(request, args); - return; - } - else - { - if constexpr (AZStd::is_same_v) - { - FlushCache(args.m_path); - } - else if constexpr (AZStd::is_same_v) - { - FlushEntireCache(); - } - StreamStackEntry::QueueRequest(request); - } - }, request->GetCommand()); - } - - bool DedicatedCache::ExecuteRequests() - { - bool hasProcessedRequest = false; - for (AZStd::unique_ptr& cache : m_cachedFileCaches) - { - hasProcessedRequest = cache->ExecuteRequests() || hasProcessedRequest; - } - return StreamStackEntry::ExecuteRequests() || hasProcessedRequest; - } - - void DedicatedCache::UpdateStatus(Status& status) const - { - // Available slots are not updated because the dedicated caches are often - // small and specific to a tiny subset of files that are loaded. It would therefore - // return a small number of slots that would needlessly hamper streaming as it doesn't - // apply to the majority of files. - - bool isIdle = true; - for (auto& cache : m_cachedFileCaches) - { - Status blockStatus; - cache->UpdateStatus(blockStatus); - isIdle = isIdle && blockStatus.m_isIdle; - } - status.m_isIdle = status.m_isIdle && isIdle; - StreamStackEntry::UpdateStatus(status); - } - - void DedicatedCache::UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, - AZStd::vector& internalPending, StreamerContext::PreparedQueue::iterator pendingBegin, - StreamerContext::PreparedQueue::iterator pendingEnd) - { - for (auto& cache : m_cachedFileCaches) - { - cache->AddDelayedRequests(internalPending); - } - - StreamStackEntry::UpdateCompletionEstimates(now, internalPending, pendingBegin, pendingEnd); - - for (auto& cache : m_cachedFileCaches) - { - cache->UpdatePendingRequestEstimations(); - } - } - - void DedicatedCache::ReadFile(FileRequest* request, FileRequest::ReadData& data) - { - size_t index = FindCache(data.m_path, data.m_offset); - if (index == s_fileNotFound) - { - m_usagePercentageStat.PushSample(0.0); - if (m_next) - { - m_next->QueueRequest(request); - } + args.m_range = FileRange::CreateRangeForEntireFile(); + m_context->PushPreparedRequest(request); } else { - m_usagePercentageStat.PushSample(1.0); - BlockCache& cache = *m_cachedFileCaches[index]; - cache.QueueRequest(request); -#if AZ_STREAMER_ADD_EXTRA_PROFILING_INFO - m_overallHitRateStat.PushSample(cache.CalculateHitRatePercentage()); - m_overallCacheableRateStat.PushSample(cache.CalculateCacheableRatePercentage()); -#endif + StreamStackEntry::PrepareRequest(request); } - } + }, request->GetCommand()); + } - void DedicatedCache::FlushCache(const RequestPath& filePath) + void DedicatedCache::QueueRequest(FileRequest* request) + { + AZ_Assert(request, "QueueRequest was provided a null request."); + + AZStd::visit([this, request](auto&& args) { - size_t count = m_cachedFileNames.size(); - for (size_t i = 0; i < count; ++i) + using Command = AZStd::decay_t; + if constexpr (AZStd::is_same_v) { - if (m_cachedFileNames[i] == filePath) - { - // Flush the entire block cache as it's entirely dedicated to the found file. - m_cachedFileCaches[i]->FlushEntireCache(); - } + ReadFile(request, args); + return; } - } - - void DedicatedCache::FlushEntireCache() - { - for (AZStd::unique_ptr& cache : m_cachedFileCaches) + else if constexpr (AZStd::is_same_v) { - cache->FlushEntireCache(); + CreateDedicatedCache(request, args); + return; } - } - - void DedicatedCache::CollectStatistics(AZStd::vector& statistics) const - { - statistics.push_back(Statistic::CreatePercentage(m_name, "Reads from dedicated cache", m_usagePercentageStat.GetAverage())); -#if AZ_STREAMER_ADD_EXTRA_PROFILING_INFO - statistics.push_back(Statistic::CreatePercentage(m_name, "Overall cacheable rate", m_overallCacheableRateStat.GetAverage())); - statistics.push_back(Statistic::CreatePercentage(m_name, "Overall hit rate", m_overallHitRateStat.GetAverage())); -#endif - statistics.push_back(Statistic::CreateInteger(m_name, "Num dedicated caches", aznumeric_caster(m_cachedFileNames.size()))); - StreamStackEntry::CollectStatistics(statistics); - } - - void DedicatedCache::CreateDedicatedCache(FileRequest* request, FileRequest::CreateDedicatedCacheData& data) - { - size_t index = FindCache(data.m_path, data.m_range); - if (index == s_fileNotFound) + else if constexpr (AZStd::is_same_v) { - index = m_cachedFileCaches.size(); - m_cachedFileNames.push_back(data.m_path); - m_cachedFileRanges.push_back(data.m_range); - m_cachedFileCaches.push_back(AZStd::make_unique(m_cacheSize, m_blockSize, m_alignment, m_onlyEpilogWrites)); - m_cachedFileCaches[index]->SetNext(m_next); - m_cachedFileCaches[index]->SetContext(*m_context); - m_cachedFileRefCounts.push_back(1); + DestroyDedicatedCache(request, args); + return; } else { - ++m_cachedFileRefCounts[index]; + if constexpr (AZStd::is_same_v) + { + FlushCache(args.m_path); + } + else if constexpr (AZStd::is_same_v) + { + FlushEntireCache(); + } + StreamStackEntry::QueueRequest(request); } - request->SetStatus(IStreamerTypes::RequestStatus::Completed); - m_context->MarkRequestAsCompleted(request); + }, request->GetCommand()); + } + + bool DedicatedCache::ExecuteRequests() + { + bool hasProcessedRequest = false; + for (AZStd::unique_ptr& cache : m_cachedFileCaches) + { + hasProcessedRequest = cache->ExecuteRequests() || hasProcessedRequest; + } + return StreamStackEntry::ExecuteRequests() || hasProcessedRequest; + } + + void DedicatedCache::UpdateStatus(Status& status) const + { + // Available slots are not updated because the dedicated caches are often + // small and specific to a tiny subset of files that are loaded. It would therefore + // return a small number of slots that would needlessly hamper streaming as it doesn't + // apply to the majority of files. + + bool isIdle = true; + for (auto& cache : m_cachedFileCaches) + { + Status blockStatus; + cache->UpdateStatus(blockStatus); + isIdle = isIdle && blockStatus.m_isIdle; + } + status.m_isIdle = status.m_isIdle && isIdle; + StreamStackEntry::UpdateStatus(status); + } + + void DedicatedCache::UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, + AZStd::vector& internalPending, StreamerContext::PreparedQueue::iterator pendingBegin, + StreamerContext::PreparedQueue::iterator pendingEnd) + { + for (auto& cache : m_cachedFileCaches) + { + cache->AddDelayedRequests(internalPending); } - void DedicatedCache::DestroyDedicatedCache(FileRequest* request, FileRequest::DestroyDedicatedCacheData& data) - { - size_t index = FindCache(data.m_path, data.m_range); - if (index != s_fileNotFound) - { - if (m_cachedFileRefCounts[index] > 0) - { - --m_cachedFileRefCounts[index]; - if (m_cachedFileRefCounts[index] == 0) - { - m_cachedFileNames.erase(m_cachedFileNames.begin() + index); - m_cachedFileRanges.erase(m_cachedFileRanges.begin() + index); - m_cachedFileCaches.erase(m_cachedFileCaches.begin() + index); - m_cachedFileRefCounts.erase(m_cachedFileRefCounts.begin() + index); - } - request->SetStatus(IStreamerTypes::RequestStatus::Completed); - m_context->MarkRequestAsCompleted(request); - return; - } - } - request->SetStatus(IStreamerTypes::RequestStatus::Failed); - m_context->MarkRequestAsCompleted(request); - } + StreamStackEntry::UpdateCompletionEstimates(now, internalPending, pendingBegin, pendingEnd); - size_t DedicatedCache::FindCache(const RequestPath& filename, FileRange range) + for (auto& cache : m_cachedFileCaches) { - size_t count = m_cachedFileNames.size(); - for (size_t i = 0; i < count; ++i) - { - if (m_cachedFileNames[i] == filename && m_cachedFileRanges[i] == range) - { - return i; - } - } - return s_fileNotFound; + cache->UpdatePendingRequestEstimations(); } + } - size_t DedicatedCache::FindCache(const RequestPath& filename, u64 offset) + void DedicatedCache::ReadFile(FileRequest* request, FileRequest::ReadData& data) + { + size_t index = FindCache(data.m_path, data.m_offset); + if (index == s_fileNotFound) { - size_t count = m_cachedFileNames.size(); - for (size_t i = 0; i < count; ++i) + m_usagePercentageStat.PushSample(0.0); + if (m_next) { - if (m_cachedFileNames[i] == filename && m_cachedFileRanges[i].IsInRange(offset)) - { - return i; - } + m_next->QueueRequest(request); } - return s_fileNotFound; } - } // namespace IO -} // namespace AZ + else + { + m_usagePercentageStat.PushSample(1.0); + BlockCache& cache = *m_cachedFileCaches[index]; + cache.QueueRequest(request); +#if AZ_STREAMER_ADD_EXTRA_PROFILING_INFO + m_overallHitRateStat.PushSample(cache.CalculateHitRatePercentage()); + m_overallCacheableRateStat.PushSample(cache.CalculateCacheableRatePercentage()); +#endif + } + } + + void DedicatedCache::FlushCache(const RequestPath& filePath) + { + size_t count = m_cachedFileNames.size(); + for (size_t i = 0; i < count; ++i) + { + if (m_cachedFileNames[i] == filePath) + { + // Flush the entire block cache as it's entirely dedicated to the found file. + m_cachedFileCaches[i]->FlushEntireCache(); + } + } + } + + void DedicatedCache::FlushEntireCache() + { + for (AZStd::unique_ptr& cache : m_cachedFileCaches) + { + cache->FlushEntireCache(); + } + } + + void DedicatedCache::CollectStatistics(AZStd::vector& statistics) const + { + statistics.push_back(Statistic::CreatePercentage(m_name, "Reads from dedicated cache", m_usagePercentageStat.GetAverage())); +#if AZ_STREAMER_ADD_EXTRA_PROFILING_INFO + statistics.push_back(Statistic::CreatePercentage(m_name, "Overall cacheable rate", m_overallCacheableRateStat.GetAverage())); + statistics.push_back(Statistic::CreatePercentage(m_name, "Overall hit rate", m_overallHitRateStat.GetAverage())); +#endif + statistics.push_back(Statistic::CreateInteger(m_name, "Num dedicated caches", aznumeric_caster(m_cachedFileNames.size()))); + StreamStackEntry::CollectStatistics(statistics); + } + + void DedicatedCache::CreateDedicatedCache(FileRequest* request, FileRequest::CreateDedicatedCacheData& data) + { + size_t index = FindCache(data.m_path, data.m_range); + if (index == s_fileNotFound) + { + index = m_cachedFileCaches.size(); + m_cachedFileNames.push_back(data.m_path); + m_cachedFileRanges.push_back(data.m_range); + m_cachedFileCaches.push_back(AZStd::make_unique(m_cacheSize, m_blockSize, m_alignment, m_onlyEpilogWrites)); + m_cachedFileCaches[index]->SetNext(m_next); + m_cachedFileCaches[index]->SetContext(*m_context); + m_cachedFileRefCounts.push_back(1); + } + else + { + ++m_cachedFileRefCounts[index]; + } + request->SetStatus(IStreamerTypes::RequestStatus::Completed); + m_context->MarkRequestAsCompleted(request); + } + + void DedicatedCache::DestroyDedicatedCache(FileRequest* request, FileRequest::DestroyDedicatedCacheData& data) + { + size_t index = FindCache(data.m_path, data.m_range); + if (index != s_fileNotFound) + { + if (m_cachedFileRefCounts[index] > 0) + { + --m_cachedFileRefCounts[index]; + if (m_cachedFileRefCounts[index] == 0) + { + m_cachedFileNames.erase(m_cachedFileNames.begin() + index); + m_cachedFileRanges.erase(m_cachedFileRanges.begin() + index); + m_cachedFileCaches.erase(m_cachedFileCaches.begin() + index); + m_cachedFileRefCounts.erase(m_cachedFileRefCounts.begin() + index); + } + request->SetStatus(IStreamerTypes::RequestStatus::Completed); + m_context->MarkRequestAsCompleted(request); + return; + } + } + request->SetStatus(IStreamerTypes::RequestStatus::Failed); + m_context->MarkRequestAsCompleted(request); + } + + size_t DedicatedCache::FindCache(const RequestPath& filename, FileRange range) + { + size_t count = m_cachedFileNames.size(); + for (size_t i = 0; i < count; ++i) + { + if (m_cachedFileNames[i] == filename && m_cachedFileRanges[i] == range) + { + return i; + } + } + return s_fileNotFound; + } + + size_t DedicatedCache::FindCache(const RequestPath& filename, u64 offset) + { + size_t count = m_cachedFileNames.size(); + for (size_t i = 0; i < count; ++i) + { + if (m_cachedFileNames[i] == filename && m_cachedFileRanges[i].IsInRange(offset)) + { + return i; + } + } + return s_fileNotFound; + } +} // namespace AZ::IO diff --git a/Code/Framework/AzCore/AzCore/IO/Streamer/FileRange.cpp b/Code/Framework/AzCore/AzCore/IO/Streamer/FileRange.cpp index df4f77b722..3a568d3f47 100644 --- a/Code/Framework/AzCore/AzCore/IO/Streamer/FileRange.cpp +++ b/Code/Framework/AzCore/AzCore/IO/Streamer/FileRange.cpp @@ -8,104 +8,101 @@ #include -namespace AZ +namespace AZ::IO { - namespace IO + FileRange FileRange::CreateRange(u64 offset, u64 size) { - FileRange FileRange::CreateRange(u64 offset, u64 size) - { - FileRange result; - result.m_hasOffsetEndSet = true; - result.m_isEntireFile = false; - result.m_offsetBegin = offset; - result.m_offsetEnd = offset + size; - return result; - } + FileRange result; + result.m_hasOffsetEndSet = true; + result.m_isEntireFile = false; + result.m_offsetBegin = offset; + result.m_offsetEnd = offset + size; + return result; + } - FileRange FileRange::CreateRangeForEntireFile() - { - FileRange result; - result.m_hasOffsetEndSet = false; - result.m_isEntireFile = true; - result.m_offsetBegin = 0; - result.m_offsetEnd = (static_cast(1) << 63) - 1; - return result; - } + FileRange FileRange::CreateRangeForEntireFile() + { + FileRange result; + result.m_hasOffsetEndSet = false; + result.m_isEntireFile = true; + result.m_offsetBegin = 0; + result.m_offsetEnd = (static_cast(1) << 63) - 1; + return result; + } - FileRange FileRange::CreateRangeForEntireFile(u64 fileSize) - { - FileRange result; - result.m_hasOffsetEndSet = true; - result.m_isEntireFile = true; - result.m_offsetBegin = 0; - result.m_offsetEnd = fileSize; - return result; - } + FileRange FileRange::CreateRangeForEntireFile(u64 fileSize) + { + FileRange result; + result.m_hasOffsetEndSet = true; + result.m_isEntireFile = true; + result.m_offsetBegin = 0; + result.m_offsetEnd = fileSize; + return result; + } - FileRange::FileRange() - : m_isEntireFile(false) - , m_offsetBegin(0) - , m_hasOffsetEndSet(false) - , m_offsetEnd(0) - { - } + FileRange::FileRange() + : m_isEntireFile(false) + , m_offsetBegin(0) + , m_hasOffsetEndSet(false) + , m_offsetEnd(0) + { + } - bool FileRange::operator==(const FileRange& rhs) const + bool FileRange::operator==(const FileRange& rhs) const + { + if (m_isEntireFile) { - if (m_isEntireFile) - { - return rhs.m_isEntireFile && m_offsetBegin == rhs.m_offsetBegin; - } - else - { - return m_offsetBegin == rhs.m_offsetBegin && m_offsetEnd == rhs.m_offsetEnd; - } + return rhs.m_isEntireFile && m_offsetBegin == rhs.m_offsetBegin; } + else + { + return m_offsetBegin == rhs.m_offsetBegin && m_offsetEnd == rhs.m_offsetEnd; + } + } - bool FileRange::operator!=(const FileRange& rhs) const + bool FileRange::operator!=(const FileRange& rhs) const + { + if (m_isEntireFile) { - if (m_isEntireFile) - { - return !rhs.m_isEntireFile || m_offsetBegin != rhs.m_offsetBegin; - } - else - { - return m_offsetBegin != rhs.m_offsetBegin || m_offsetEnd != rhs.m_offsetEnd; - } + return !rhs.m_isEntireFile || m_offsetBegin != rhs.m_offsetBegin; } + else + { + return m_offsetBegin != rhs.m_offsetBegin || m_offsetEnd != rhs.m_offsetEnd; + } + } - bool FileRange::IsEntireFile() const - { - return m_isEntireFile != 0; - } + bool FileRange::IsEntireFile() const + { + return m_isEntireFile != 0; + } - bool FileRange::IsSizeKnown() const - { - // m_hasOffsetEndSet being zero has the special meaning that the file size has not - // specifically been set yet. - return m_hasOffsetEndSet != 0; - } + bool FileRange::IsSizeKnown() const + { + // m_hasOffsetEndSet being zero has the special meaning that the file size has not + // specifically been set yet. + return m_hasOffsetEndSet != 0; + } - bool FileRange::IsInRange(u64 offset) const - { - return m_offsetBegin <= offset && offset < m_offsetEnd; - } + bool FileRange::IsInRange(u64 offset) const + { + return m_offsetBegin <= offset && offset < m_offsetEnd; + } - u64 FileRange::GetOffset() const - { - return m_offsetBegin; - } + u64 FileRange::GetOffset() const + { + return m_offsetBegin; + } - u64 FileRange::GetSize() const - { - AZ_Assert(m_hasOffsetEndSet, "Calling GetSize on a FileRange that doesn't have a size specified."); - return m_offsetEnd - m_offsetBegin; - } + u64 FileRange::GetSize() const + { + AZ_Assert(m_hasOffsetEndSet, "Calling GetSize on a FileRange that doesn't have a size specified."); + return m_offsetEnd - m_offsetBegin; + } - u64 FileRange::GetEndPoint() const - { - AZ_Assert(m_hasOffsetEndSet, "Calling GetEndPoint on a FileRange that doesn't have an end offset specified."); - return m_offsetEnd; - } - } // namespace IO -} // namesapce AZ + u64 FileRange::GetEndPoint() const + { + AZ_Assert(m_hasOffsetEndSet, "Calling GetEndPoint on a FileRange that doesn't have an end offset specified."); + return m_offsetEnd; + } +} // namespace AZ::IO diff --git a/Code/Framework/AzCore/AzCore/IO/Streamer/FileRequest.cpp b/Code/Framework/AzCore/AzCore/IO/Streamer/FileRequest.cpp index 7b9cde76d3..fc05b77b36 100644 --- a/Code/Framework/AzCore/AzCore/IO/Streamer/FileRequest.cpp +++ b/Code/Framework/AzCore/AzCore/IO/Streamer/FileRequest.cpp @@ -12,469 +12,466 @@ #include #include -namespace AZ +namespace AZ::IO { - namespace IO + // + // Command structures. + // + + FileRequest::ExternalRequestData::ExternalRequestData(FileRequestPtr&& request) + : m_request(AZStd::move(request)) + {} + + FileRequest::RequestPathStoreData::RequestPathStoreData(RequestPath path) + : m_path(AZStd::move(path)) + {} + + FileRequest::ReadRequestData::ReadRequestData(RequestPath path, void* output, u64 outputSize, u64 offset, u64 size, + AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority) + : m_path(AZStd::move(path)) + , m_allocator(nullptr) + , m_deadline(deadline) + , m_output(output) + , m_outputSize(outputSize) + , m_offset(offset) + , m_size(size) + , m_priority(priority) + , m_memoryType(IStreamerTypes::MemoryType::ReadWrite) // Only generic memory can be assigned externally. + {} + + FileRequest::ReadRequestData::ReadRequestData(RequestPath path, IStreamerTypes::RequestMemoryAllocator* allocator, + u64 offset, u64 size, AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority) + : m_path(AZStd::move(path)) + , m_allocator(allocator) + , m_deadline(deadline) + , m_output(nullptr) + , m_outputSize(0) + , m_offset(offset) + , m_size(size) + , m_priority(priority) + , m_memoryType(IStreamerTypes::MemoryType::ReadWrite) // Only generic memory can be assigned externally. + {} + + FileRequest::ReadRequestData::~ReadRequestData() { - // - // Command structures. - // - - FileRequest::ExternalRequestData::ExternalRequestData(FileRequestPtr&& request) - : m_request(AZStd::move(request)) - {} - - FileRequest::RequestPathStoreData::RequestPathStoreData(RequestPath path) - : m_path(AZStd::move(path)) - {} - - FileRequest::ReadRequestData::ReadRequestData(RequestPath path, void* output, u64 outputSize, u64 offset, u64 size, - AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority) - : m_path(AZStd::move(path)) - , m_allocator(nullptr) - , m_deadline(deadline) - , m_output(output) - , m_outputSize(outputSize) - , m_offset(offset) - , m_size(size) - , m_priority(priority) - , m_memoryType(IStreamerTypes::MemoryType::ReadWrite) // Only generic memory can be assigned externally. - {} - - FileRequest::ReadRequestData::ReadRequestData(RequestPath path, IStreamerTypes::RequestMemoryAllocator* allocator, - u64 offset, u64 size, AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority) - : m_path(AZStd::move(path)) - , m_allocator(allocator) - , m_deadline(deadline) - , m_output(nullptr) - , m_outputSize(0) - , m_offset(offset) - , m_size(size) - , m_priority(priority) - , m_memoryType(IStreamerTypes::MemoryType::ReadWrite) // Only generic memory can be assigned externally. - {} - - FileRequest::ReadRequestData::~ReadRequestData() + if (m_allocator != nullptr) { - if (m_allocator != nullptr) + if (m_output != nullptr) { - if (m_output != nullptr) - { - m_allocator->Release(m_output); - } - m_allocator->UnlockAllocator(); + m_allocator->Release(m_output); } + m_allocator->UnlockAllocator(); } + } - FileRequest::ReadData::ReadData(void* output, u64 outputSize, const RequestPath& path, u64 offset, u64 size, bool sharedRead) - : m_output(output) - , m_outputSize(outputSize) - , m_path(path) - , m_offset(offset) - , m_size(size) - , m_sharedRead(sharedRead) - {} + FileRequest::ReadData::ReadData(void* output, u64 outputSize, const RequestPath& path, u64 offset, u64 size, bool sharedRead) + : m_output(output) + , m_outputSize(outputSize) + , m_path(path) + , m_offset(offset) + , m_size(size) + , m_sharedRead(sharedRead) + {} - FileRequest::CompressedReadData::CompressedReadData(CompressionInfo&& compressionInfo, void* output, u64 readOffset, u64 readSize) - : m_compressionInfo(AZStd::move(compressionInfo)) - , m_output(output) - , m_readOffset(readOffset) - , m_readSize(readSize) - {} + FileRequest::CompressedReadData::CompressedReadData(CompressionInfo&& compressionInfo, void* output, u64 readOffset, u64 readSize) + : m_compressionInfo(AZStd::move(compressionInfo)) + , m_output(output) + , m_readOffset(readOffset) + , m_readSize(readSize) + {} - FileRequest::FileExistsCheckData::FileExistsCheckData(const RequestPath& path) - : m_path(path) - {} + FileRequest::FileExistsCheckData::FileExistsCheckData(const RequestPath& path) + : m_path(path) + {} - FileRequest::FileMetaDataRetrievalData::FileMetaDataRetrievalData(const RequestPath& path) - : m_path(path) - {} + FileRequest::FileMetaDataRetrievalData::FileMetaDataRetrievalData(const RequestPath& path) + : m_path(path) + {} - FileRequest::CancelData::CancelData(FileRequestPtr target) - : m_target(AZStd::move(target)) - {} + FileRequest::CancelData::CancelData(FileRequestPtr target) + : m_target(AZStd::move(target)) + {} - FileRequest::FlushData::FlushData(RequestPath path) - : m_path(AZStd::move(path)) - {} + FileRequest::FlushData::FlushData(RequestPath path) + : m_path(AZStd::move(path)) + {} - FileRequest::RescheduleData::RescheduleData(FileRequestPtr target, AZStd::chrono::system_clock::time_point newDeadline, - IStreamerTypes::Priority newPriority) - : m_target(AZStd::move(target)) - , m_newDeadline(newDeadline) - , m_newPriority(newPriority) - {} + FileRequest::RescheduleData::RescheduleData(FileRequestPtr target, AZStd::chrono::system_clock::time_point newDeadline, + IStreamerTypes::Priority newPriority) + : m_target(AZStd::move(target)) + , m_newDeadline(newDeadline) + , m_newPriority(newPriority) + {} - FileRequest::CreateDedicatedCacheData::CreateDedicatedCacheData(RequestPath path, const FileRange& range) - : m_path(AZStd::move(path)) - , m_range(range) - {} + FileRequest::CreateDedicatedCacheData::CreateDedicatedCacheData(RequestPath path, const FileRange& range) + : m_path(AZStd::move(path)) + , m_range(range) + {} - FileRequest::DestroyDedicatedCacheData::DestroyDedicatedCacheData(RequestPath path, const FileRange& range) - : m_path(AZStd::move(path)) - , m_range(range) - {} + FileRequest::DestroyDedicatedCacheData::DestroyDedicatedCacheData(RequestPath path, const FileRange& range) + : m_path(AZStd::move(path)) + , m_range(range) + {} - FileRequest::ReportData::ReportData(ReportType reportType) - : m_reportType(reportType) - {} + FileRequest::ReportData::ReportData(ReportType reportType) + : m_reportType(reportType) + {} - FileRequest::CustomData::CustomData(AZStd::any data, bool failWhenUnhandled) - : m_data(AZStd::move(data)) - , m_failWhenUnhandled(failWhenUnhandled) - {} + FileRequest::CustomData::CustomData(AZStd::any data, bool failWhenUnhandled) + : m_data(AZStd::move(data)) + , m_failWhenUnhandled(failWhenUnhandled) + {} - // - // FileRequest - // + // + // FileRequest + // - FileRequest::FileRequest(Usage usage) - : m_usage(usage) + FileRequest::FileRequest(Usage usage) + : m_usage(usage) + { + Reset(); + } + + FileRequest::~FileRequest() + { + Reset(); + } + + void FileRequest::CreateRequestLink(FileRequestPtr&& request) + { + AZ_Assert(AZStd::holds_alternative(m_command), + "Attempting to set FileRequest to 'RequestLink', but another task was already assigned."); + m_parent = request->m_request.m_parent; + request->m_request.m_parent = this; + m_dependencies++; + m_command.emplace(AZStd::move(request)); + } + + void FileRequest::CreateRequestPathStore(FileRequest* parent, RequestPath path) + { + AZ_Assert(AZStd::holds_alternative(m_command), + "Attempting to set FileRequest to 'CreateRequestPathStore', but another task was already assigned."); + m_command.emplace(AZStd::move(path)); + SetOptionalParent(parent); + } + + void FileRequest::CreateReadRequest(RequestPath path, void* output, u64 outputSize, u64 offset, u64 size, + AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority) + { + AZ_Assert(AZStd::holds_alternative(m_command), + "Attempting to set FileRequest to 'ReadRequest', but another task was already assigned."); + m_command.emplace(AZStd::move(path), output, outputSize, offset, size, deadline, priority); + } + + void FileRequest::CreateReadRequest(RequestPath path, IStreamerTypes::RequestMemoryAllocator* allocator, u64 offset, u64 size, + AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority) + { + AZ_Assert(AZStd::holds_alternative(m_command), + "Attempting to set FileRequest to 'ReadRequest', but another task was already assigned."); + m_command.emplace(AZStd::move(path), allocator, offset, size, deadline, priority); + } + + void FileRequest::CreateRead(FileRequest* parent, void* output, u64 outputSize, const RequestPath& path, + u64 offset, u64 size, bool sharedRead) + { + AZ_Assert(AZStd::holds_alternative(m_command), + "Attempting to set FileRequest to 'Read', but another task was already assigned."); + m_command.emplace(output, outputSize, AZStd::move(path), offset, size, sharedRead); + SetOptionalParent(parent); + } + + void FileRequest::CreateCompressedRead(FileRequest* parent, const CompressionInfo& compressionInfo, + void* output, u64 readOffset, u64 readSize) + { + CreateCompressedRead(parent, CompressionInfo(compressionInfo), output, readOffset, readSize); + } + + void FileRequest::CreateCompressedRead(FileRequest* parent, CompressionInfo&& compressionInfo, + void* output, u64 readOffset, u64 readSize) + { + AZ_Assert(AZStd::holds_alternative(m_command), + "Attempting to set FileRequest to 'CompressedRead', but another task was already assigned."); + m_command.emplace(AZStd::move(compressionInfo), output, readOffset, readSize); + SetOptionalParent(parent); + } + + void FileRequest::CreateWait(FileRequest* parent) + { + AZ_Assert(AZStd::holds_alternative(m_command), + "Attempting to set FileRequest to 'Wait', but another task was already assigned."); + m_command.emplace(); + SetOptionalParent(parent); + } + + void FileRequest::CreateFileExistsCheck(const RequestPath& path) + { + AZ_Assert(AZStd::holds_alternative(m_command), + "Attempting to set FileRequest to 'FileExistsCheck', but another task was already assigned."); + m_command.emplace(path); + } + + void FileRequest::CreateFileMetaDataRetrieval(const RequestPath& path) + { + AZ_Assert(AZStd::holds_alternative(m_command), + "Attempting to set FileRequest to 'FileMetaDataRetrieval', but another task was already assigned."); + m_command.emplace(path); + } + + void FileRequest::CreateCancel(FileRequestPtr target) + { + AZ_Assert(AZStd::holds_alternative(m_command), + "Attempting to set FileRequest to 'Cancel', but another task was already assigned."); + m_command.emplace(AZStd::move(target)); + } + + void FileRequest::CreateReschedule(FileRequestPtr target, AZStd::chrono::system_clock::time_point newDeadline, + IStreamerTypes::Priority newPriority) + { + AZ_Assert(AZStd::holds_alternative(m_command), + "Attempting to set FileRequest to 'Reschedule', but another task was already assigned."); + m_command.emplace(AZStd::move(target), newDeadline, newPriority); + } + + void FileRequest::CreateFlush(RequestPath path) + { + AZ_Assert(AZStd::holds_alternative(m_command), + "Attempting to set FileRequest to 'Flush', but another task was already assigned."); + m_command.emplace(AZStd::move(path)); + } + + void FileRequest::CreateFlushAll() + { + AZ_Assert(AZStd::holds_alternative(m_command), + "Attempting to set FileRequest to 'FlushAll', but another task was already assigned."); + m_command.emplace(); + } + + void FileRequest::CreateDedicatedCacheCreation(RequestPath path, const FileRange& range, FileRequest* parent) + { + AZ_Assert(AZStd::holds_alternative(m_command), + "Attempting to set FileRequest to 'CreateDedicateCache', but another task was already assigned."); + m_command.emplace(AZStd::move(path), range); + SetOptionalParent(parent); + } + + void FileRequest::CreateDedicatedCacheDestruction(RequestPath path, const FileRange& range, FileRequest* parent) + { + AZ_Assert(AZStd::holds_alternative(m_command), + "Attempting to set FileRequest to 'DestroyDedicateCache', but another task was already assigned."); + m_command.emplace(AZStd::move(path), range); + SetOptionalParent(parent); + } + + void FileRequest::CreateReport(ReportData::ReportType reportType) + { + AZ_Assert(AZStd::holds_alternative(m_command), + "Attempting to set FileRequest to 'Report', but another task was already assigned."); + m_command.emplace(reportType); + } + + void FileRequest::CreateCustom(AZStd::any data, bool failWhenUnhandled, FileRequest* parent) + { + AZ_Assert(AZStd::holds_alternative(m_command), + "Attempting to set FileRequest to 'Custom', but another task was already assigned."); + m_command.emplace(AZStd::move(data), failWhenUnhandled); + SetOptionalParent(parent); + } + + void FileRequest::SetCompletionCallback(OnCompletionCallback callback) + { + m_onCompletion = AZStd::move(callback); + } + + FileRequest::CommandVariant& FileRequest::GetCommand() + { + return m_command; + } + + const FileRequest::CommandVariant& FileRequest::GetCommand() const + { + return m_command; + } + + IStreamerTypes::RequestStatus FileRequest::GetStatus() const + { + return m_status; + } + + void FileRequest::SetStatus(IStreamerTypes::RequestStatus newStatus) + { + IStreamerTypes::RequestStatus currentStatus = m_status; + switch (newStatus) { - Reset(); - } - - FileRequest::~FileRequest() - { - Reset(); - } - - void FileRequest::CreateRequestLink(FileRequestPtr&& request) - { - AZ_Assert(AZStd::holds_alternative(m_command), - "Attempting to set FileRequest to 'RequestLink', but another task was already assigned."); - m_parent = request->m_request.m_parent; - request->m_request.m_parent = this; - m_dependencies++; - m_command.emplace(AZStd::move(request)); - } - - void FileRequest::CreateRequestPathStore(FileRequest* parent, RequestPath path) - { - AZ_Assert(AZStd::holds_alternative(m_command), - "Attempting to set FileRequest to 'CreateRequestPathStore', but another task was already assigned."); - m_command.emplace(AZStd::move(path)); - SetOptionalParent(parent); - } - - void FileRequest::CreateReadRequest(RequestPath path, void* output, u64 outputSize, u64 offset, u64 size, - AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority) - { - AZ_Assert(AZStd::holds_alternative(m_command), - "Attempting to set FileRequest to 'ReadRequest', but another task was already assigned."); - m_command.emplace(AZStd::move(path), output, outputSize, offset, size, deadline, priority); - } - - void FileRequest::CreateReadRequest(RequestPath path, IStreamerTypes::RequestMemoryAllocator* allocator, u64 offset, u64 size, - AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority) - { - AZ_Assert(AZStd::holds_alternative(m_command), - "Attempting to set FileRequest to 'ReadRequest', but another task was already assigned."); - m_command.emplace(AZStd::move(path), allocator, offset, size, deadline, priority); - } - - void FileRequest::CreateRead(FileRequest* parent, void* output, u64 outputSize, const RequestPath& path, - u64 offset, u64 size, bool sharedRead) - { - AZ_Assert(AZStd::holds_alternative(m_command), - "Attempting to set FileRequest to 'Read', but another task was already assigned."); - m_command.emplace(output, outputSize, AZStd::move(path), offset, size, sharedRead); - SetOptionalParent(parent); - } - - void FileRequest::CreateCompressedRead(FileRequest* parent, const CompressionInfo& compressionInfo, - void* output, u64 readOffset, u64 readSize) - { - CreateCompressedRead(parent, CompressionInfo(compressionInfo), output, readOffset, readSize); - } - - void FileRequest::CreateCompressedRead(FileRequest* parent, CompressionInfo&& compressionInfo, - void* output, u64 readOffset, u64 readSize) - { - AZ_Assert(AZStd::holds_alternative(m_command), - "Attempting to set FileRequest to 'CompressedRead', but another task was already assigned."); - m_command.emplace(AZStd::move(compressionInfo), output, readOffset, readSize); - SetOptionalParent(parent); - } - - void FileRequest::CreateWait(FileRequest* parent) - { - AZ_Assert(AZStd::holds_alternative(m_command), - "Attempting to set FileRequest to 'Wait', but another task was already assigned."); - m_command.emplace(); - SetOptionalParent(parent); - } - - void FileRequest::CreateFileExistsCheck(const RequestPath& path) - { - AZ_Assert(AZStd::holds_alternative(m_command), - "Attempting to set FileRequest to 'FileExistsCheck', but another task was already assigned."); - m_command.emplace(path); - } - - void FileRequest::CreateFileMetaDataRetrieval(const RequestPath& path) - { - AZ_Assert(AZStd::holds_alternative(m_command), - "Attempting to set FileRequest to 'FileMetaDataRetrieval', but another task was already assigned."); - m_command.emplace(path); - } - - void FileRequest::CreateCancel(FileRequestPtr target) - { - AZ_Assert(AZStd::holds_alternative(m_command), - "Attempting to set FileRequest to 'Cancel', but another task was already assigned."); - m_command.emplace(AZStd::move(target)); - } - - void FileRequest::CreateReschedule(FileRequestPtr target, AZStd::chrono::system_clock::time_point newDeadline, - IStreamerTypes::Priority newPriority) - { - AZ_Assert(AZStd::holds_alternative(m_command), - "Attempting to set FileRequest to 'Reschedule', but another task was already assigned."); - m_command.emplace(AZStd::move(target), newDeadline, newPriority); - } - - void FileRequest::CreateFlush(RequestPath path) - { - AZ_Assert(AZStd::holds_alternative(m_command), - "Attempting to set FileRequest to 'Flush', but another task was already assigned."); - m_command.emplace(AZStd::move(path)); - } - - void FileRequest::CreateFlushAll() - { - AZ_Assert(AZStd::holds_alternative(m_command), - "Attempting to set FileRequest to 'FlushAll', but another task was already assigned."); - m_command.emplace(); - } - - void FileRequest::CreateDedicatedCacheCreation(RequestPath path, const FileRange& range, FileRequest* parent) - { - AZ_Assert(AZStd::holds_alternative(m_command), - "Attempting to set FileRequest to 'CreateDedicateCache', but another task was already assigned."); - m_command.emplace(AZStd::move(path), range); - SetOptionalParent(parent); - } - - void FileRequest::CreateDedicatedCacheDestruction(RequestPath path, const FileRange& range, FileRequest* parent) - { - AZ_Assert(AZStd::holds_alternative(m_command), - "Attempting to set FileRequest to 'DestroyDedicateCache', but another task was already assigned."); - m_command.emplace(AZStd::move(path), range); - SetOptionalParent(parent); - } - - void FileRequest::CreateReport(ReportData::ReportType reportType) - { - AZ_Assert(AZStd::holds_alternative(m_command), - "Attempting to set FileRequest to 'Report', but another task was already assigned."); - m_command.emplace(reportType); - } - - void FileRequest::CreateCustom(AZStd::any data, bool failWhenUnhandled, FileRequest* parent) - { - AZ_Assert(AZStd::holds_alternative(m_command), - "Attempting to set FileRequest to 'Custom', but another task was already assigned."); - m_command.emplace(AZStd::move(data), failWhenUnhandled); - SetOptionalParent(parent); - } - - void FileRequest::SetCompletionCallback(OnCompletionCallback callback) - { - m_onCompletion = AZStd::move(callback); - } - - FileRequest::CommandVariant& FileRequest::GetCommand() - { - return m_command; - } - - const FileRequest::CommandVariant& FileRequest::GetCommand() const - { - return m_command; - } - - IStreamerTypes::RequestStatus FileRequest::GetStatus() const - { - return m_status; - } - - void FileRequest::SetStatus(IStreamerTypes::RequestStatus newStatus) - { - IStreamerTypes::RequestStatus currentStatus = m_status; - switch (newStatus) + case IStreamerTypes::RequestStatus::Pending: + [[fallthrough]]; + case IStreamerTypes::RequestStatus::Queued: + [[fallthrough]]; + case IStreamerTypes::RequestStatus::Processing: + if (currentStatus == IStreamerTypes::RequestStatus::Failed || + currentStatus == IStreamerTypes::RequestStatus::Canceled || + currentStatus == IStreamerTypes::RequestStatus::Completed) { - case IStreamerTypes::RequestStatus::Pending: - [[fallthrough]]; - case IStreamerTypes::RequestStatus::Queued: - [[fallthrough]]; - case IStreamerTypes::RequestStatus::Processing: - if (currentStatus == IStreamerTypes::RequestStatus::Failed || - currentStatus == IStreamerTypes::RequestStatus::Canceled || - currentStatus == IStreamerTypes::RequestStatus::Completed) - { - return; - } - break; - case IStreamerTypes::RequestStatus::Completed: - if (currentStatus == IStreamerTypes::RequestStatus::Failed || currentStatus == IStreamerTypes::RequestStatus::Canceled) - { - return; - } - break; - case IStreamerTypes::RequestStatus::Canceled: - if (currentStatus == IStreamerTypes::RequestStatus::Failed || currentStatus == IStreamerTypes::RequestStatus::Completed) - { - return; - } - break; - case IStreamerTypes::RequestStatus::Failed: - [[fallthrough]]; - default: - break; + return; } - m_status = newStatus; - } - - FileRequest* FileRequest::GetParent() - { - return m_parent; - } - - const FileRequest* FileRequest::GetParent() const - { - return m_parent; - } - - size_t FileRequest::GetNumDependencies() const - { - return m_dependencies; - } - - bool FileRequest::FailsWhenUnhandled() const - { - return AZStd::visit([](auto&& args) + break; + case IStreamerTypes::RequestStatus::Completed: + if (currentStatus == IStreamerTypes::RequestStatus::Failed || currentStatus == IStreamerTypes::RequestStatus::Canceled) { - using Command = AZStd::decay_t; - if constexpr (AZStd::is_same_v) - { - AZ_Assert(false, - "Request does not contain a valid command. It may have been reset already or was never assigned a command."); - return true; - } - else if constexpr (AZStd::is_same_v) - { - return args.m_failWhenUnhandled; - } - else - { - return Command::s_failWhenUnhandled; - } - }, m_command); - } - - void FileRequest::Reset() - { - m_command = AZStd::monostate{}; - m_onCompletion = &OnCompletionPlaceholder; - m_estimatedCompletion = AZStd::chrono::system_clock::time_point(); - m_parent = nullptr; - m_status = IStreamerTypes::RequestStatus::Pending; - m_dependencies = 0; - } - - void FileRequest::SetOptionalParent(FileRequest* parent) - { - if (parent) - { - m_parent = parent; - AZ_Assert(parent->m_dependencies < std::numeric_limitsm_dependencies)>::max(), - "A file request dependency was added, but the parent can't have any more dependencies."); - ++parent->m_dependencies; + return; } - } - - bool FileRequest::WorksOn(FileRequestPtr& request) const - { - const FileRequest* current = this; - while (current) + break; + case IStreamerTypes::RequestStatus::Canceled: + if (currentStatus == IStreamerTypes::RequestStatus::Failed || currentStatus == IStreamerTypes::RequestStatus::Completed) { - auto* link = AZStd::get_if(¤t->m_command); - if (!link) - { - current = current->m_parent; - } - else - { - return link->m_request == request; - } + return; } - return false; + break; + case IStreamerTypes::RequestStatus::Failed: + [[fallthrough]]; + default: + break; } + m_status = newStatus; + } - size_t FileRequest::GetPendingId() const - { - return m_pendingId; - } + FileRequest* FileRequest::GetParent() + { + return m_parent; + } - void FileRequest::SetEstimatedCompletion(AZStd::chrono::system_clock::time_point time) + const FileRequest* FileRequest::GetParent() const + { + return m_parent; + } + + size_t FileRequest::GetNumDependencies() const + { + return m_dependencies; + } + + bool FileRequest::FailsWhenUnhandled() const + { + return AZStd::visit([](auto&& args) { - FileRequest* current = this; - do + using Command = AZStd::decay_t; + if constexpr (AZStd::is_same_v) + { + AZ_Assert(false, + "Request does not contain a valid command. It may have been reset already or was never assigned a command."); + return true; + } + else if constexpr (AZStd::is_same_v) + { + return args.m_failWhenUnhandled; + } + else + { + return Command::s_failWhenUnhandled; + } + }, m_command); + } + + void FileRequest::Reset() + { + m_command = AZStd::monostate{}; + m_onCompletion = &OnCompletionPlaceholder; + m_estimatedCompletion = AZStd::chrono::system_clock::time_point(); + m_parent = nullptr; + m_status = IStreamerTypes::RequestStatus::Pending; + m_dependencies = 0; + } + + void FileRequest::SetOptionalParent(FileRequest* parent) + { + if (parent) + { + m_parent = parent; + AZ_Assert(parent->m_dependencies < std::numeric_limitsm_dependencies)>::max(), + "A file request dependency was added, but the parent can't have any more dependencies."); + ++parent->m_dependencies; + } + } + + bool FileRequest::WorksOn(FileRequestPtr& request) const + { + const FileRequest* current = this; + while (current) + { + auto* link = AZStd::get_if(¤t->m_command); + if (!link) { - current->m_estimatedCompletion = time; current = current->m_parent; - } while (current); - } - - AZStd::chrono::system_clock::time_point FileRequest::GetEstimatedCompletion() const - { - return m_estimatedCompletion; - } - - // - // ExternalFileRequest - // - - ExternalFileRequest::ExternalFileRequest(StreamerContext* owner) - : m_request(FileRequest::Usage::External) - , m_owner(owner) - { - } - - void ExternalFileRequest::add_ref() - { - m_refCount++; - } - - void ExternalFileRequest::release() - { - if (--m_refCount == 0) + } + else { - AZ_Assert(m_owner, "No owning context set for the file request."); - m_owner->RecycleRequest(this); + return link->m_request == request; } } + return false; + } - bool operator==(const FileRequestHandle& lhs, const FileRequestPtr& rhs) - { - return lhs.m_request == &rhs->m_request; - } + size_t FileRequest::GetPendingId() const + { + return m_pendingId; + } - bool operator==(const FileRequestPtr& lhs, const FileRequestHandle& rhs) + void FileRequest::SetEstimatedCompletion(AZStd::chrono::system_clock::time_point time) + { + FileRequest* current = this; + do { - return rhs == lhs; - } + current->m_estimatedCompletion = time; + current = current->m_parent; + } while (current); + } - bool operator!=(const FileRequestHandle& lhs, const FileRequestPtr& rhs) - { - return !(lhs == rhs); - } + AZStd::chrono::system_clock::time_point FileRequest::GetEstimatedCompletion() const + { + return m_estimatedCompletion; + } - bool operator!=(const FileRequestPtr& lhs, const FileRequestHandle& rhs) + // + // ExternalFileRequest + // + + ExternalFileRequest::ExternalFileRequest(StreamerContext* owner) + : m_request(FileRequest::Usage::External) + , m_owner(owner) + { + } + + void ExternalFileRequest::add_ref() + { + m_refCount++; + } + + void ExternalFileRequest::release() + { + if (--m_refCount == 0) { - return !(rhs == lhs); + AZ_Assert(m_owner, "No owning context set for the file request."); + m_owner->RecycleRequest(this); } - } // namespace IO -} // namespace AZ + } + + bool operator==(const FileRequestHandle& lhs, const FileRequestPtr& rhs) + { + return lhs.m_request == &rhs->m_request; + } + + bool operator==(const FileRequestPtr& lhs, const FileRequestHandle& rhs) + { + return rhs == lhs; + } + + bool operator!=(const FileRequestHandle& lhs, const FileRequestPtr& rhs) + { + return !(lhs == rhs); + } + + bool operator!=(const FileRequestPtr& lhs, const FileRequestHandle& rhs) + { + return !(rhs == lhs); + } +} // namespace AZ::IO diff --git a/Code/Framework/AzCore/AzCore/IO/Streamer/FullFileDecompressor.cpp b/Code/Framework/AzCore/AzCore/IO/Streamer/FullFileDecompressor.cpp index 6427571f10..723a5d62c8 100644 --- a/Code/Framework/AzCore/AzCore/IO/Streamer/FullFileDecompressor.cpp +++ b/Code/Framework/AzCore/AzCore/IO/Streamer/FullFileDecompressor.cpp @@ -21,719 +21,717 @@ #include #include -namespace AZ +namespace AZ::IO { - namespace IO + AZStd::shared_ptr FullFileDecompressorConfig::AddStreamStackEntry( + const HardwareInformation& hardware, AZStd::shared_ptr parent) { - AZStd::shared_ptr FullFileDecompressorConfig::AddStreamStackEntry( - const HardwareInformation& hardware, AZStd::shared_ptr parent) - { - auto stackEntry = AZStd::make_shared( - m_maxNumReads, m_maxNumJobs, aznumeric_caster(hardware.m_maxPhysicalSectorSize)); - stackEntry->SetNext(AZStd::move(parent)); - return stackEntry; - } + auto stackEntry = AZStd::make_shared( + m_maxNumReads, m_maxNumJobs, aznumeric_caster(hardware.m_maxPhysicalSectorSize)); + stackEntry->SetNext(AZStd::move(parent)); + return stackEntry; + } - void FullFileDecompressorConfig::Reflect(AZ::ReflectContext* context) + void FullFileDecompressorConfig::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context); serializeContext != nullptr) { - if (auto serializeContext = azrtti_cast(context); serializeContext != nullptr) - { - serializeContext->Class() - ->Version(1) - ->Field("MaxNumReads", &FullFileDecompressorConfig::m_maxNumReads) - ->Field("MaxNumJobs", &FullFileDecompressorConfig::m_maxNumJobs); - } + serializeContext->Class() + ->Version(1) + ->Field("MaxNumReads", &FullFileDecompressorConfig::m_maxNumReads) + ->Field("MaxNumJobs", &FullFileDecompressorConfig::m_maxNumJobs); } + } #if AZ_STREAMER_ADD_EXTRA_PROFILING_INFO - static constexpr char DecompBoundName[] = "Decompression bound"; - static constexpr char ReadBoundName[] = "Read bound"; + static constexpr char DecompBoundName[] = "Decompression bound"; + static constexpr char ReadBoundName[] = "Read bound"; #endif // AZ_STREAMER_ADD_EXTRA_PROFILING_INFO - bool FullFileDecompressor::DecompressionInformation::IsProcessing() const + bool FullFileDecompressor::DecompressionInformation::IsProcessing() const + { + return !!m_compressedData; + } + + FullFileDecompressor::FullFileDecompressor(u32 maxNumReads, u32 maxNumJobs, u32 alignment) + : StreamStackEntry("Full file decompressor") + , m_maxNumReads(maxNumReads) + , m_maxNumJobs(maxNumJobs) + , m_alignment(alignment) + { + JobManagerDesc jobDesc; + jobDesc.m_jobManagerName = "Full File Decompressor"; + u32 numThreads = AZ::GetMin(maxNumJobs, AZStd::thread::hardware_concurrency()); + for (u32 i = 0; i < numThreads; ++i) { - return !!m_compressedData; + jobDesc.m_workerThreads.push_back(JobManagerThreadDesc()); } - - FullFileDecompressor::FullFileDecompressor(u32 maxNumReads, u32 maxNumJobs, u32 alignment) - : StreamStackEntry("Full file decompressor") - , m_maxNumReads(maxNumReads) - , m_maxNumJobs(maxNumJobs) - , m_alignment(alignment) + m_decompressionJobManager = AZStd::make_unique(jobDesc); + m_decompressionjobContext = AZStd::make_unique(*m_decompressionJobManager); + + m_processingJobs = AZStd::make_unique(maxNumJobs); + + m_readBuffers = AZStd::make_unique(maxNumReads); + m_readRequests = AZStd::make_unique(maxNumReads); + m_readBufferStatus = AZStd::make_unique(maxNumReads); + for (u32 i = 0; i < maxNumReads; ++i) { - JobManagerDesc jobDesc; - u32 numThreads = AZ::GetMin(maxNumJobs, AZStd::thread::hardware_concurrency()); - for (u32 i = 0; i < numThreads; ++i) - { - jobDesc.m_workerThreads.push_back(JobManagerThreadDesc()); - } - m_decompressionJobManager = AZStd::make_unique(jobDesc); - m_decompressionjobContext = AZStd::make_unique(*m_decompressionJobManager); - - m_processingJobs = AZStd::make_unique(maxNumJobs); - - m_readBuffers = AZStd::make_unique(maxNumReads); - m_readRequests = AZStd::make_unique(maxNumReads); - m_readBufferStatus = AZStd::make_unique(maxNumReads); - for (u32 i = 0; i < maxNumReads; ++i) - { - m_readBufferStatus[i] = ReadBufferStatus::Unused; - } - - // Add initial dummy values to the stats to avoid division by zero later on and avoid needing branches. - m_bytesDecompressed.PushEntry(1); - m_decompressionDurationMicroSec.PushEntry(1); + m_readBufferStatus[i] = ReadBufferStatus::Unused; } - void FullFileDecompressor::PrepareRequest(FileRequest* request) - { - AZ_Assert(request, "PrepareRequest was provided a null request."); + // Add initial dummy values to the stats to avoid division by zero later on and avoid needing branches. + m_bytesDecompressed.PushEntry(1); + m_decompressionDurationMicroSec.PushEntry(1); + } - AZStd::visit([this, request](auto&& args) + void FullFileDecompressor::PrepareRequest(FileRequest* request) + { + AZ_Assert(request, "PrepareRequest was provided a null request."); + + AZStd::visit([this, request](auto&& args) + { + using Command = AZStd::decay_t; + if constexpr (AZStd::is_same_v) { - using Command = AZStd::decay_t; - if constexpr (AZStd::is_same_v) - { - PrepareReadRequest(request, args); - } - else if constexpr (AZStd::is_same_v || - AZStd::is_same_v) - { - PrepareDedicatedCache(request, args.m_path); - } - else - { - StreamStackEntry::PrepareRequest(request); - } - }, request->GetCommand()); + PrepareReadRequest(request, args); + } + else if constexpr (AZStd::is_same_v || + AZStd::is_same_v) + { + PrepareDedicatedCache(request, args.m_path); + } + else + { + StreamStackEntry::PrepareRequest(request); + } + }, request->GetCommand()); + } + + void FullFileDecompressor::QueueRequest(FileRequest* request) + { + AZ_Assert(request, "QueueRequest was provided a null request."); + + AZStd::visit([this, request](auto&& args) + { + using Command = AZStd::decay_t; + if constexpr (AZStd::is_same_v) + { + m_pendingReads.push_back(request); + } + else if constexpr (AZStd::is_same_v) + { + m_pendingFileExistChecks.push_back(request); + } + else + { + StreamStackEntry::QueueRequest(request); + } + }, request->GetCommand()); + } + + bool FullFileDecompressor::ExecuteRequests() + { + bool result = false; + // First queue jobs as this might open up new read slots. + if (m_numInFlightReads > 0 && m_numRunningJobs < m_maxNumJobs) + { + result = StartDecompressions(); } - void FullFileDecompressor::QueueRequest(FileRequest* request) + // Queue as many new reads as possible. + while (!m_pendingReads.empty() && m_numInFlightReads < m_maxNumReads) { - AZ_Assert(request, "QueueRequest was provided a null request."); - - AZStd::visit([this, request](auto&& args) - { - using Command = AZStd::decay_t; - if constexpr (AZStd::is_same_v) - { - m_pendingReads.push_back(request); - } - else if constexpr (AZStd::is_same_v) - { - m_pendingFileExistChecks.push_back(request); - } - else - { - StreamStackEntry::QueueRequest(request); - } - }, request->GetCommand()); + StartArchiveRead(m_pendingReads.front()); + m_pendingReads.pop_front(); + result = true; } - bool FullFileDecompressor::ExecuteRequests() + // If nothing else happened and there is at least one pending file exist check request, run one of those. + if (!result && !m_pendingFileExistChecks.empty()) { - bool result = false; - // First queue jobs as this might open up new read slots. - if (m_numInFlightReads > 0 && m_numRunningJobs < m_maxNumJobs) - { - result = StartDecompressions(); - } - - // Queue as many new reads as possible. - while (!m_pendingReads.empty() && m_numInFlightReads < m_maxNumReads) - { - StartArchiveRead(m_pendingReads.front()); - m_pendingReads.pop_front(); - result = true; - } - - // If nothing else happened and there is at least one pending file exist check request, run one of those. - if (!result && !m_pendingFileExistChecks.empty()) - { - FileExistsCheck(m_pendingFileExistChecks.front()); - m_pendingFileExistChecks.pop_front(); - result = true; - } + FileExistsCheck(m_pendingFileExistChecks.front()); + m_pendingFileExistChecks.pop_front(); + result = true; + } #if AZ_STREAMER_ADD_EXTRA_PROFILING_INFO - bool allPendingDecompression = true; - bool allReading = true; - for (u32 i = 0; i < m_maxNumReads; ++i) - { - allPendingDecompression = - allPendingDecompression && (m_readBufferStatus[i] == ReadBufferStatus::PendingDecompression); - allReading = - allReading && (m_readBufferStatus[i] == ReadBufferStatus::ReadInFlight); - } + bool allPendingDecompression = true; + bool allReading = true; + for (u32 i = 0; i < m_maxNumReads; ++i) + { + allPendingDecompression = + allPendingDecompression && (m_readBufferStatus[i] == ReadBufferStatus::PendingDecompression); + allReading = + allReading && (m_readBufferStatus[i] == ReadBufferStatus::ReadInFlight); + } - m_decompressionBoundStat.PushSample(allPendingDecompression ? 1.0 : 0.0); - Statistic::PlotImmediate(m_name, DecompBoundName, m_decompressionBoundStat.GetMostRecentSample()); + m_decompressionBoundStat.PushSample(allPendingDecompression ? 1.0 : 0.0); + Statistic::PlotImmediate(m_name, DecompBoundName, m_decompressionBoundStat.GetMostRecentSample()); - m_readBoundStat.PushSample(allReading && (m_numRunningJobs < m_maxNumJobs) ? 1.0 : 0.0); - Statistic::PlotImmediate(m_name, ReadBoundName, m_readBoundStat.GetMostRecentSample()); + m_readBoundStat.PushSample(allReading && (m_numRunningJobs < m_maxNumJobs) ? 1.0 : 0.0); + Statistic::PlotImmediate(m_name, ReadBoundName, m_readBoundStat.GetMostRecentSample()); #endif - return StreamStackEntry::ExecuteRequests() || result; - } + return StreamStackEntry::ExecuteRequests() || result; + } - void FullFileDecompressor::UpdateStatus(Status& status) const + void FullFileDecompressor::UpdateStatus(Status& status) const + { + StreamStackEntry::UpdateStatus(status); + s32 numAvailableSlots = aznumeric_cast(m_maxNumReads - m_numInFlightReads); + status.m_numAvailableSlots = AZStd::min(status.m_numAvailableSlots, numAvailableSlots); + status.m_isIdle = status.m_isIdle && IsIdle(); + } + + void FullFileDecompressor::UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector& internalPending, + StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) + { + // Create predictions for all pending requests. Some will be further processed after this. + AZStd::reverse_copy(m_pendingFileExistChecks.begin(), m_pendingFileExistChecks.end(), AZStd::back_inserter(internalPending)); + AZStd::reverse_copy(m_pendingReads.begin(), m_pendingReads.end(), AZStd::back_inserter(internalPending)); + + StreamStackEntry::UpdateCompletionEstimates(now, internalPending, pendingBegin, pendingEnd); + + double totalBytesDecompressed = aznumeric_caster(m_bytesDecompressed.GetTotal()); + double totalDecompressionDuration = aznumeric_caster(m_decompressionDurationMicroSec.GetTotal()); + AZStd::chrono::microseconds cumulativeDelay = AZStd::chrono::microseconds::max(); + + // Check the number of jobs that are processing. + for (u32 i = 0; i < m_maxNumJobs; ++i) { - StreamStackEntry::UpdateStatus(status); - s32 numAvailableSlots = aznumeric_cast(m_maxNumReads - m_numInFlightReads); - status.m_numAvailableSlots = AZStd::min(status.m_numAvailableSlots, numAvailableSlots); - status.m_isIdle = status.m_isIdle && IsIdle(); - } - - void FullFileDecompressor::UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector& internalPending, - StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) - { - // Create predictions for all pending requests. Some will be further processed after this. - AZStd::reverse_copy(m_pendingFileExistChecks.begin(), m_pendingFileExistChecks.end(), AZStd::back_inserter(internalPending)); - AZStd::reverse_copy(m_pendingReads.begin(), m_pendingReads.end(), AZStd::back_inserter(internalPending)); - - StreamStackEntry::UpdateCompletionEstimates(now, internalPending, pendingBegin, pendingEnd); - - double totalBytesDecompressed = aznumeric_caster(m_bytesDecompressed.GetTotal()); - double totalDecompressionDuration = aznumeric_caster(m_decompressionDurationMicroSec.GetTotal()); - AZStd::chrono::microseconds cumulativeDelay = AZStd::chrono::microseconds::max(); - - // Check the number of jobs that are processing. - for (u32 i = 0; i < m_maxNumJobs; ++i) + if (m_processingJobs[i].IsProcessing()) { - if (m_processingJobs[i].IsProcessing()) - { - FileRequest* compressedRequest = m_processingJobs[i].m_waitRequest->GetParent(); - AZ_Assert(compressedRequest, "A wait request attached to FullFileDecompressor was completed but didn't have a parent compressed request."); - auto data = AZStd::get_if(&compressedRequest->GetCommand()); - AZ_Assert(data, "Compressed request in the decompression queue in FullFileDecompressor didn't contain compression read data."); - - size_t bytesToDecompress = data->m_compressionInfo.m_compressedSize; - auto decompressionDuration = AZStd::chrono::microseconds( - aznumeric_cast((bytesToDecompress * totalDecompressionDuration) / totalBytesDecompressed)); - auto timeInProcessing = now - m_processingJobs[i].m_jobStartTime; - auto timeLeft = decompressionDuration > timeInProcessing ? decompressionDuration - timeInProcessing : AZStd::chrono::microseconds(0); - // Get the shortest time as this indicates the next decompression to become available. - cumulativeDelay = AZStd::min(timeLeft, cumulativeDelay); - m_processingJobs[i].m_waitRequest->SetEstimatedCompletion(now + timeLeft); - } - } - if (cumulativeDelay == AZStd::chrono::microseconds::max()) - { - cumulativeDelay = AZStd::chrono::microseconds(0); - } - - // Next update all reads that are in flight. These will have an estimation for the read to complete, but will then be queued - // for decompression, so add the time needed decompression. Assume that decompression happens in parallel. - AZStd::chrono::microseconds decompressionDelay = - AZStd::chrono::microseconds(aznumeric_cast(m_decompressionJobDelayMicroSec.CalculateAverage())); - AZStd::chrono::microseconds smallestDecompressionDuration = AZStd::chrono::microseconds::max(); - for (u32 i = 0; i < m_maxNumReads; ++i) - { - AZStd::chrono::system_clock::time_point baseTime; - switch (m_readBufferStatus[i]) - { - case ReadBufferStatus::Unused: - continue; - case ReadBufferStatus::ReadInFlight: - // Internal read requests can start and complete but pending finalization before they're ever scheduled in which case - // the estimated time is not set. - baseTime = m_readRequests[i]->GetEstimatedCompletion(); - if (baseTime == AZStd::chrono::system_clock::time_point()) - { - baseTime = now; - } - break; - case ReadBufferStatus::PendingDecompression: - baseTime = now; - break; - default: - AZ_Assert(false, "Unsupported buffer type: %i.", m_readBufferStatus[i]); - continue; - } - - baseTime += cumulativeDelay; // Delay until the first decompression slot becomes available. - baseTime += decompressionDelay; // The average time it takes for the job system to pick up the decompression job. - - // Calculate the amount of time it will take to decompress the data. - FileRequest* compressedRequest = m_readRequests[i]->GetParent(); + FileRequest* compressedRequest = m_processingJobs[i].m_waitRequest->GetParent(); + AZ_Assert(compressedRequest, "A wait request attached to FullFileDecompressor was completed but didn't have a parent compressed request."); auto data = AZStd::get_if(&compressedRequest->GetCommand()); - + AZ_Assert(data, "Compressed request in the decompression queue in FullFileDecompressor didn't contain compression read data."); + size_t bytesToDecompress = data->m_compressionInfo.m_compressedSize; auto decompressionDuration = AZStd::chrono::microseconds( aznumeric_cast((bytesToDecompress * totalDecompressionDuration) / totalBytesDecompressed)); - smallestDecompressionDuration = AZStd::min(smallestDecompressionDuration, decompressionDuration); - baseTime += decompressionDuration; - - m_readRequests[i]->SetEstimatedCompletion(baseTime); - } - if (smallestDecompressionDuration != AZStd::chrono::microseconds::max()) - { - cumulativeDelay += smallestDecompressionDuration; // Time after which the decompression jobs and pending reads have completed. - } - - // For all internally pending compressed reads add the decompression time. The read time will have already been added downstream. - // Because this call will go from the top of the stack to the bottom, but estimation is calculated from the bottom to the top, this - // list should be processed in reverse order. - for (auto pendingIt = internalPending.rbegin(); pendingIt != internalPending.rend(); ++pendingIt) - { - EstimateCompressedReadRequest(*pendingIt, cumulativeDelay, decompressionDelay, - totalDecompressionDuration, totalBytesDecompressed); - } - - // Finally add a prediction for all the requests that are waiting to be queued. - for (auto requestIt = pendingBegin; requestIt != pendingEnd; ++requestIt) - { - EstimateCompressedReadRequest(*requestIt, cumulativeDelay, decompressionDelay, - totalDecompressionDuration, totalBytesDecompressed); + auto timeInProcessing = now - m_processingJobs[i].m_jobStartTime; + auto timeLeft = decompressionDuration > timeInProcessing ? decompressionDuration - timeInProcessing : AZStd::chrono::microseconds(0); + // Get the shortest time as this indicates the next decompression to become available. + cumulativeDelay = AZStd::min(timeLeft, cumulativeDelay); + m_processingJobs[i].m_waitRequest->SetEstimatedCompletion(now + timeLeft); } } - - void FullFileDecompressor::EstimateCompressedReadRequest(FileRequest* request, AZStd::chrono::microseconds& cumulativeDelay, - AZStd::chrono::microseconds decompressionDelay, double totalDecompressionDurationUs, double totalBytesDecompressed) const + if (cumulativeDelay == AZStd::chrono::microseconds::max()) { - auto data = AZStd::get_if(&request->GetCommand()); - if (data) - { - AZStd::chrono::microseconds processingTime = decompressionDelay; - size_t bytesToDecompress = data->m_compressionInfo.m_compressedSize; - processingTime += AZStd::chrono::microseconds( - aznumeric_cast((bytesToDecompress * totalDecompressionDurationUs) / totalBytesDecompressed)); - - cumulativeDelay += processingTime; - request->SetEstimatedCompletion(request->GetEstimatedCompletion() + processingTime); - } + cumulativeDelay = AZStd::chrono::microseconds(0); } - void FullFileDecompressor::CollectStatistics(AZStd::vector& statistics) const + // Next update all reads that are in flight. These will have an estimation for the read to complete, but will then be queued + // for decompression, so add the time needed decompression. Assume that decompression happens in parallel. + AZStd::chrono::microseconds decompressionDelay = + AZStd::chrono::microseconds(aznumeric_cast(m_decompressionJobDelayMicroSec.CalculateAverage())); + AZStd::chrono::microseconds smallestDecompressionDuration = AZStd::chrono::microseconds::max(); + for (u32 i = 0; i < m_maxNumReads; ++i) { - constexpr double bytesToMB = 1.0 / (1024.0 * 1024.0); - constexpr double usToSec = 1.0 / (1000.0 * 1000.0); - constexpr double usToMs = 1.0 / 1000.0; - - if (m_bytesDecompressed.GetNumRecorded() > 1) // There's always a default added. + AZStd::chrono::system_clock::time_point baseTime; + switch (m_readBufferStatus[i]) { - //It only makes sense to add decompression statistics when reading from PAK files. - statistics.push_back(Statistic::CreateInteger(m_name, "Available decompression slots", m_maxNumJobs - m_numRunningJobs)); - statistics.push_back(Statistic::CreateInteger(m_name, "Available read slots", m_maxNumReads - m_numInFlightReads)); - statistics.push_back(Statistic::CreateInteger(m_name, "Pending decompression", m_numPendingDecompression)); - statistics.push_back(Statistic::CreateFloat(m_name, "Buffer memory (MB)", m_memoryUsage * bytesToMB)); + case ReadBufferStatus::Unused: + continue; + case ReadBufferStatus::ReadInFlight: + // Internal read requests can start and complete but pending finalization before they're ever scheduled in which case + // the estimated time is not set. + baseTime = m_readRequests[i]->GetEstimatedCompletion(); + if (baseTime == AZStd::chrono::system_clock::time_point()) + { + baseTime = now; + } + break; + case ReadBufferStatus::PendingDecompression: + baseTime = now; + break; + default: + AZ_Assert(false, "Unsupported buffer type: %i.", m_readBufferStatus[i]); + continue; + } - double averageJobStartDelay = m_decompressionJobDelayMicroSec.CalculateAverage() * usToMs; - statistics.push_back(Statistic::CreateFloat(m_name, "Decompression job delay (avg. ms)", averageJobStartDelay)); + baseTime += cumulativeDelay; // Delay until the first decompression slot becomes available. + baseTime += decompressionDelay; // The average time it takes for the job system to pick up the decompression job. - double totalBytesDecompressedMB = m_bytesDecompressed.GetTotal() * bytesToMB; - double totalDecompressionTimeSec = m_decompressionDurationMicroSec.GetTotal() * usToSec; - statistics.push_back(Statistic::CreateFloat(m_name, "Decompression Speed per job (avg. mbps)", totalBytesDecompressedMB / totalDecompressionTimeSec)); + // Calculate the amount of time it will take to decompress the data. + FileRequest* compressedRequest = m_readRequests[i]->GetParent(); + auto data = AZStd::get_if(&compressedRequest->GetCommand()); + + size_t bytesToDecompress = data->m_compressionInfo.m_compressedSize; + auto decompressionDuration = AZStd::chrono::microseconds( + aznumeric_cast((bytesToDecompress * totalDecompressionDuration) / totalBytesDecompressed)); + smallestDecompressionDuration = AZStd::min(smallestDecompressionDuration, decompressionDuration); + baseTime += decompressionDuration; + + m_readRequests[i]->SetEstimatedCompletion(baseTime); + } + if (smallestDecompressionDuration != AZStd::chrono::microseconds::max()) + { + cumulativeDelay += smallestDecompressionDuration; // Time after which the decompression jobs and pending reads have completed. + } + + // For all internally pending compressed reads add the decompression time. The read time will have already been added downstream. + // Because this call will go from the top of the stack to the bottom, but estimation is calculated from the bottom to the top, this + // list should be processed in reverse order. + for (auto pendingIt = internalPending.rbegin(); pendingIt != internalPending.rend(); ++pendingIt) + { + EstimateCompressedReadRequest(*pendingIt, cumulativeDelay, decompressionDelay, + totalDecompressionDuration, totalBytesDecompressed); + } + + // Finally add a prediction for all the requests that are waiting to be queued. + for (auto requestIt = pendingBegin; requestIt != pendingEnd; ++requestIt) + { + EstimateCompressedReadRequest(*requestIt, cumulativeDelay, decompressionDelay, + totalDecompressionDuration, totalBytesDecompressed); + } + } + + void FullFileDecompressor::EstimateCompressedReadRequest(FileRequest* request, AZStd::chrono::microseconds& cumulativeDelay, + AZStd::chrono::microseconds decompressionDelay, double totalDecompressionDurationUs, double totalBytesDecompressed) const + { + auto data = AZStd::get_if(&request->GetCommand()); + if (data) + { + AZStd::chrono::microseconds processingTime = decompressionDelay; + size_t bytesToDecompress = data->m_compressionInfo.m_compressedSize; + processingTime += AZStd::chrono::microseconds( + aznumeric_cast((bytesToDecompress * totalDecompressionDurationUs) / totalBytesDecompressed)); + + cumulativeDelay += processingTime; + request->SetEstimatedCompletion(request->GetEstimatedCompletion() + processingTime); + } + } + + void FullFileDecompressor::CollectStatistics(AZStd::vector& statistics) const + { + constexpr double bytesToMB = 1.0 / (1024.0 * 1024.0); + constexpr double usToSec = 1.0 / (1000.0 * 1000.0); + constexpr double usToMs = 1.0 / 1000.0; + + if (m_bytesDecompressed.GetNumRecorded() > 1) // There's always a default added. + { + //It only makes sense to add decompression statistics when reading from PAK files. + statistics.push_back(Statistic::CreateInteger(m_name, "Available decompression slots", m_maxNumJobs - m_numRunningJobs)); + statistics.push_back(Statistic::CreateInteger(m_name, "Available read slots", m_maxNumReads - m_numInFlightReads)); + statistics.push_back(Statistic::CreateInteger(m_name, "Pending decompression", m_numPendingDecompression)); + statistics.push_back(Statistic::CreateFloat(m_name, "Buffer memory (MB)", m_memoryUsage * bytesToMB)); + + double averageJobStartDelay = m_decompressionJobDelayMicroSec.CalculateAverage() * usToMs; + statistics.push_back(Statistic::CreateFloat(m_name, "Decompression job delay (avg. ms)", averageJobStartDelay)); + + double totalBytesDecompressedMB = m_bytesDecompressed.GetTotal() * bytesToMB; + double totalDecompressionTimeSec = m_decompressionDurationMicroSec.GetTotal() * usToSec; + statistics.push_back(Statistic::CreateFloat(m_name, "Decompression Speed per job (avg. mbps)", totalBytesDecompressedMB / totalDecompressionTimeSec)); #if AZ_STREAMER_ADD_EXTRA_PROFILING_INFO - statistics.push_back(Statistic::CreatePercentage(m_name, DecompBoundName, m_decompressionBoundStat.GetAverage())); - statistics.push_back(Statistic::CreatePercentage(m_name, ReadBoundName, m_readBoundStat.GetAverage())); + statistics.push_back(Statistic::CreatePercentage(m_name, DecompBoundName, m_decompressionBoundStat.GetAverage())); + statistics.push_back(Statistic::CreatePercentage(m_name, ReadBoundName, m_readBoundStat.GetAverage())); #endif - } - - StreamStackEntry::CollectStatistics(statistics); } - bool FullFileDecompressor::IsIdle() const - { - return - m_pendingReads.empty() && - m_pendingFileExistChecks.empty() && - m_numInFlightReads == 0 && - m_numPendingDecompression == 0 && - m_numRunningJobs == 0; - } + StreamStackEntry::CollectStatistics(statistics); + } - void FullFileDecompressor::PrepareReadRequest(FileRequest* request, FileRequest::ReadRequestData& data) + bool FullFileDecompressor::IsIdle() const + { + return + m_pendingReads.empty() && + m_pendingFileExistChecks.empty() && + m_numInFlightReads == 0 && + m_numPendingDecompression == 0 && + m_numRunningJobs == 0; + } + + void FullFileDecompressor::PrepareReadRequest(FileRequest* request, FileRequest::ReadRequestData& data) + { + CompressionInfo info; + if (CompressionUtils::FindCompressionInfo(info, data.m_path.GetRelativePath())) { - CompressionInfo info; - if (CompressionUtils::FindCompressionInfo(info, data.m_path.GetRelativePath())) + FileRequest* nextRequest = m_context->GetNewInternalRequest(); + if (info.m_isCompressed) { - FileRequest* nextRequest = m_context->GetNewInternalRequest(); - if (info.m_isCompressed) - { - AZ_Assert(info.m_decompressor, - "FullFileDecompressor::PrepareRequest found a compressed file, but no decompressor to decompress with."); - nextRequest->CreateCompressedRead(request, AZStd::move(info), data.m_output, data.m_offset, data.m_size); - } - else - { - FileRequest* pathStorageRequest = m_context->GetNewInternalRequest(); - pathStorageRequest->CreateRequestPathStore(request, AZStd::move(info.m_archiveFilename)); - auto& pathStorage = AZStd::get(pathStorageRequest->GetCommand()); - - nextRequest->CreateRead(pathStorageRequest, data.m_output, data.m_outputSize, pathStorage.m_path, - info.m_offset + data.m_offset, data.m_size, info.m_isSharedPak); - } - - if (info.m_conflictResolution == ConflictResolution::PreferFile) - { - auto callback = [this, nextRequest](const FileRequest& checkRequest) - { - AZ_PROFILE_FUNCTION(AzCore); - auto check = AZStd::get_if(&checkRequest.GetCommand()); - AZ_Assert(check, - "Callback in FullFileDecompressor::PrepareReadRequest expected FileExistsCheck but got another command."); - if (check->m_found) - { - FileRequest* originalRequest = m_context->RejectRequest(nextRequest); - if (AZStd::holds_alternative(originalRequest->GetCommand())) - { - originalRequest = m_context->RejectRequest(originalRequest); - } - StreamStackEntry::PrepareRequest(originalRequest); - } - else - { - m_context->PushPreparedRequest(nextRequest); - } - }; - FileRequest* fileCheckRequest = m_context->GetNewInternalRequest(); - fileCheckRequest->CreateFileExistsCheck(data.m_path); - fileCheckRequest->SetCompletionCallback(AZStd::move(callback)); - StreamStackEntry::QueueRequest(fileCheckRequest); - } - else - { - m_context->PushPreparedRequest(nextRequest); - } + AZ_Assert(info.m_decompressor, + "FullFileDecompressor::PrepareRequest found a compressed file, but no decompressor to decompress with."); + nextRequest->CreateCompressedRead(request, AZStd::move(info), data.m_output, data.m_offset, data.m_size); } else { - StreamStackEntry::PrepareRequest(request); - } - } + FileRequest* pathStorageRequest = m_context->GetNewInternalRequest(); + pathStorageRequest->CreateRequestPathStore(request, AZStd::move(info.m_archiveFilename)); + auto& pathStorage = AZStd::get(pathStorageRequest->GetCommand()); - void FullFileDecompressor::PrepareDedicatedCache(FileRequest* request, const RequestPath& path) - { - CompressionInfo info; - if (CompressionUtils::FindCompressionInfo(info, path.GetRelativePath())) + nextRequest->CreateRead(pathStorageRequest, data.m_output, data.m_outputSize, pathStorage.m_path, + info.m_offset + data.m_offset, data.m_size, info.m_isSharedPak); + } + + if (info.m_conflictResolution == ConflictResolution::PreferFile) { - FileRequest* nextRequest = m_context->GetNewInternalRequest(); - AZStd::visit([request, &info, nextRequest](auto&& args) + auto callback = [this, nextRequest](const FileRequest& checkRequest) { - using Command = AZStd::decay_t; - if constexpr (AZStd::is_same_v) + AZ_PROFILE_FUNCTION(AzCore); + auto check = AZStd::get_if(&checkRequest.GetCommand()); + AZ_Assert(check, + "Callback in FullFileDecompressor::PrepareReadRequest expected FileExistsCheck but got another command."); + if (check->m_found) { - nextRequest->CreateDedicatedCacheCreation(AZStd::move(info.m_archiveFilename), - FileRange::CreateRange(info.m_offset, info.m_compressedSize), request); - } - else if constexpr (AZStd::is_same_v) - { - nextRequest->CreateDedicatedCacheDestruction(AZStd::move(info.m_archiveFilename), - FileRange::CreateRange(info.m_offset, info.m_compressedSize), request); - } - }, request->GetCommand()); - - if (info.m_conflictResolution == ConflictResolution::PreferFile) - { - auto callback = [this, nextRequest](const FileRequest& checkRequest) - { - AZ_PROFILE_FUNCTION(AzCore); - auto check = AZStd::get_if(&checkRequest.GetCommand()); - AZ_Assert(check, - "Callback in FullFileDecompressor::PrepareDedicatedCache expected FileExistsCheck but got another command."); - if (check->m_found) + FileRequest* originalRequest = m_context->RejectRequest(nextRequest); + if (AZStd::holds_alternative(originalRequest->GetCommand())) { - FileRequest* originalRequest = nextRequest->GetParent(); - m_context->RejectRequest(nextRequest); - StreamStackEntry::PrepareRequest(originalRequest); + originalRequest = m_context->RejectRequest(originalRequest); } - else - { - m_context->PushPreparedRequest(nextRequest); - } - }; - FileRequest* fileCheckRequest = m_context->GetNewInternalRequest(); - fileCheckRequest->CreateFileExistsCheck(path); - fileCheckRequest->SetCompletionCallback(AZStd::move(callback)); - StreamStackEntry::QueueRequest(fileCheckRequest); - } - else - { - m_context->PushPreparedRequest(nextRequest); - } - } - else - { - StreamStackEntry::PrepareRequest(request); - } - } - - void FullFileDecompressor::FileExistsCheck(FileRequest* checkRequest) - { - auto& fileCheckRequest = AZStd::get(checkRequest->GetCommand()); - CompressionInfo info; - if (CompressionUtils::FindCompressionInfo(info, fileCheckRequest.m_path.GetRelativePath())) - { - fileCheckRequest.m_found = true; - } - else - { - // The file isn't in the archive but might still exist as a loose file, so let the next node have a shot. - StreamStackEntry::QueueRequest(checkRequest); - } - } - - void FullFileDecompressor::StartArchiveRead(FileRequest* compressedReadRequest) - { - if (!m_next) - { - compressedReadRequest->SetStatus(IStreamerTypes::RequestStatus::Failed); - m_context->MarkRequestAsCompleted(compressedReadRequest); - return; - } - - for (u32 i = 0; i < m_maxNumReads; ++i) - { - if (m_readBufferStatus[i] == ReadBufferStatus::Unused) - { - auto data = AZStd::get_if(&compressedReadRequest->GetCommand()); - AZ_Assert(data, "Compressed request that's starting a read in FullFileDecompressor didn't contain compression read data."); - AZ_Assert(data->m_compressionInfo.m_decompressor, - "FileRequest for FullFileDecompressor is missing a decompression callback."); - - CompressionInfo& info = data->m_compressionInfo; - AZ_Assert(info.m_decompressor, "FullFileDecompressor is planning to a queue a request for reading but couldn't find a decompressor."); - - // The buffer is aligned down but the offset is not corrected. If the offset was adjusted it would mean the same data is read - // multiple times and negates the block cache's ability to detect these cases. By still adjusting it means that the reads between - // the BlockCache's prolog and epilog are read into aligned buffers. - size_t offsetAdjustment = info.m_offset - AZ_SIZE_ALIGN_DOWN(info.m_offset, aznumeric_cast(m_alignment)); - size_t bufferSize = AZ_SIZE_ALIGN_UP((info.m_compressedSize + offsetAdjustment), aznumeric_cast(m_alignment)); - m_readBuffers[i] = reinterpret_cast(AZ::AllocatorInstance::Get().Allocate( - bufferSize, m_alignment, 0, "AZ::IO::Streamer FullFileDecompressor", __FILE__, __LINE__)); - m_memoryUsage += bufferSize; - - FileRequest* archiveReadRequest = m_context->GetNewInternalRequest(); - archiveReadRequest->CreateRead(compressedReadRequest, m_readBuffers[i] + offsetAdjustment, bufferSize, info.m_archiveFilename, - info.m_offset, info.m_compressedSize, info.m_isSharedPak); - archiveReadRequest->SetCompletionCallback( - [this, readSlot = i](FileRequest& request) - { - AZ_PROFILE_FUNCTION(AzCore); - FinishArchiveRead(&request, readSlot); - }); - m_next->QueueRequest(archiveReadRequest); - - m_readRequests[i] = archiveReadRequest; - m_readBufferStatus[i] = ReadBufferStatus::ReadInFlight; - - AZ_Assert(m_numInFlightReads < m_maxNumReads, - "A FileRequest was queued for reading in FullFileDecompressor, but there's no slots available."); - m_numInFlightReads++; - - return; - } - } - AZ_Assert(false, "%u of %u read slots are use in the FullFileDecompressor, but no empty slot was found.", m_numInFlightReads, m_maxNumReads); - } - - void FullFileDecompressor::FinishArchiveRead(FileRequest* readRequest, u32 readSlot) - { - AZ_Assert(m_readRequests[readSlot] == readRequest, - "Request in the archive read slot isn't the same as request that's being completed."); - - FileRequest* compressedRequest = readRequest->GetParent(); - AZ_Assert(compressedRequest, "Read requests started by FullFileDecompressor is missing a parent request."); - - if (readRequest->GetStatus() == IStreamerTypes::RequestStatus::Completed) - { - m_readBufferStatus[readSlot] = ReadBufferStatus::PendingDecompression; - ++m_numPendingDecompression; - - // Add this wait so the compressed request isn't fully completed yet as only the read part is done. The - // job thread will finish this wait, which in turn will trigger this function again on the main streaming thread. - FileRequest* waitRequest = m_context->GetNewInternalRequest(); - waitRequest->CreateWait(compressedRequest); - m_readRequests[readSlot] = waitRequest; - } - else - { - auto data = AZStd::get_if(&compressedRequest->GetCommand()); - AZ_Assert(data, "Compressed request in FullFileDecompressor that finished unsuccessfully didn't contain compression read data."); - CompressionInfo& info = data->m_compressionInfo; - size_t offsetAdjustment = info.m_offset - AZ_SIZE_ALIGN_DOWN(info.m_offset, aznumeric_cast(m_alignment)); - size_t bufferSize = AZ_SIZE_ALIGN_UP((info.m_compressedSize + offsetAdjustment), aznumeric_cast(m_alignment)); - m_memoryUsage -= bufferSize; - - if (m_readBuffers[readSlot] != nullptr) - { - AZ::AllocatorInstance::Get().DeAllocate(m_readBuffers[readSlot], bufferSize, m_alignment); - m_readBuffers[readSlot] = nullptr; - } - m_readRequests[readSlot] = nullptr; - m_readBufferStatus[readSlot] = ReadBufferStatus::Unused; - AZ_Assert(m_numInFlightReads > 0, - "Trying to decrement a read request after it was canceled or failed in FullFileDecompressor, " - "but no read requests are supposed to be queued."); - m_numInFlightReads--; - } - } - - bool FullFileDecompressor::StartDecompressions() - { - bool queuedJobs = false; - u32 jobSlot = 0; - for (u32 readSlot = 0; readSlot < m_maxNumReads; ++readSlot) - { - // Find completed read. - if (m_readBufferStatus[readSlot] != ReadBufferStatus::PendingDecompression) - { - continue; - } - - // Find decompression slot - for (; jobSlot < m_maxNumJobs; ++jobSlot) - { - if (m_processingJobs[jobSlot].IsProcessing()) - { - continue; - } - - FileRequest* waitRequest = m_readRequests[readSlot]; - AZ_Assert(AZStd::holds_alternative(waitRequest->GetCommand()), - "File request waiting for decompression wasn't marked as being a wait operation."); - FileRequest* compressedRequest = waitRequest->GetParent(); - AZ_Assert(compressedRequest, "Read requests started by FullFileDecompressor is missing a parent request."); - - waitRequest->SetCompletionCallback([this, jobSlot](FileRequest& request) - { - AZ_PROFILE_FUNCTION(AzCore); - FinishDecompression(&request, jobSlot); - }); - - DecompressionInformation& info = m_processingJobs[jobSlot]; - info.m_waitRequest = waitRequest; - info.m_queueStartTime = AZStd::chrono::high_resolution_clock::now(); - info.m_jobStartTime = info.m_queueStartTime; // Set these to the same in case the scheduler requests an update before the job has started. - info.m_compressedData = m_readBuffers[readSlot]; // Transfer ownership of the pointer. - m_readBuffers[readSlot] = nullptr; - - AZ::Job* decompressionJob; - auto data = AZStd::get_if(&compressedRequest->GetCommand()); - AZ_Assert(data, "Compressed request in FullFileDecompressor that's starting decompression didn't contain compression read data."); - AZ_Assert(data->m_compressionInfo.m_decompressor, "FullFileDecompressor is queuing a decompression job but couldn't find a decompressor."); - - info.m_alignmentOffset = aznumeric_caster(data->m_compressionInfo.m_offset - - AZ_SIZE_ALIGN_DOWN(data->m_compressionInfo.m_offset, aznumeric_cast(m_alignment))); - - if (data->m_readOffset == 0 && data->m_readSize == data->m_compressionInfo.m_uncompressedSize) - { - auto job = [this, &info]() - { - FullDecompression(m_context, info); - }; - decompressionJob = AZ::CreateJobFunction(job, true, m_decompressionjobContext.get()); + StreamStackEntry::PrepareRequest(originalRequest); } else { - m_memoryUsage += data->m_compressionInfo.m_uncompressedSize; - auto job = [this, &info]() - { - PartialDecompression(m_context, info); - }; - decompressionJob = AZ::CreateJobFunction(job, true, m_decompressionjobContext.get()); + m_context->PushPreparedRequest(nextRequest); } - --m_numPendingDecompression; - ++m_numRunningJobs; - decompressionJob->Start(); - - m_readRequests[readSlot] = nullptr; - m_readBufferStatus[readSlot] = ReadBufferStatus::Unused; - AZ_Assert(m_numInFlightReads > 0, "Trying to decrement a read request after it's queued for decompression in FullFileDecompressor, but no read requests are supposed to be queued."); - m_numInFlightReads--; - - queuedJobs = true; - break; - } - - if (m_numInFlightReads == 0 || m_numRunningJobs == m_maxNumJobs) - { - return queuedJobs; - } + }; + FileRequest* fileCheckRequest = m_context->GetNewInternalRequest(); + fileCheckRequest->CreateFileExistsCheck(data.m_path); + fileCheckRequest->SetCompletionCallback(AZStd::move(callback)); + StreamStackEntry::QueueRequest(fileCheckRequest); } - return queuedJobs; + else + { + m_context->PushPreparedRequest(nextRequest); + } + } + else + { + StreamStackEntry::PrepareRequest(request); + } + } + + void FullFileDecompressor::PrepareDedicatedCache(FileRequest* request, const RequestPath& path) + { + CompressionInfo info; + if (CompressionUtils::FindCompressionInfo(info, path.GetRelativePath())) + { + FileRequest* nextRequest = m_context->GetNewInternalRequest(); + AZStd::visit([request, &info, nextRequest](auto&& args) + { + using Command = AZStd::decay_t; + if constexpr (AZStd::is_same_v) + { + nextRequest->CreateDedicatedCacheCreation(AZStd::move(info.m_archiveFilename), + FileRange::CreateRange(info.m_offset, info.m_compressedSize), request); + } + else if constexpr (AZStd::is_same_v) + { + nextRequest->CreateDedicatedCacheDestruction(AZStd::move(info.m_archiveFilename), + FileRange::CreateRange(info.m_offset, info.m_compressedSize), request); + } + }, request->GetCommand()); + + if (info.m_conflictResolution == ConflictResolution::PreferFile) + { + auto callback = [this, nextRequest](const FileRequest& checkRequest) + { + AZ_PROFILE_FUNCTION(AzCore); + auto check = AZStd::get_if(&checkRequest.GetCommand()); + AZ_Assert(check, + "Callback in FullFileDecompressor::PrepareDedicatedCache expected FileExistsCheck but got another command."); + if (check->m_found) + { + FileRequest* originalRequest = nextRequest->GetParent(); + m_context->RejectRequest(nextRequest); + StreamStackEntry::PrepareRequest(originalRequest); + } + else + { + m_context->PushPreparedRequest(nextRequest); + } + }; + FileRequest* fileCheckRequest = m_context->GetNewInternalRequest(); + fileCheckRequest->CreateFileExistsCheck(path); + fileCheckRequest->SetCompletionCallback(AZStd::move(callback)); + StreamStackEntry::QueueRequest(fileCheckRequest); + } + else + { + m_context->PushPreparedRequest(nextRequest); + } + } + else + { + StreamStackEntry::PrepareRequest(request); + } + } + + void FullFileDecompressor::FileExistsCheck(FileRequest* checkRequest) + { + auto& fileCheckRequest = AZStd::get(checkRequest->GetCommand()); + CompressionInfo info; + if (CompressionUtils::FindCompressionInfo(info, fileCheckRequest.m_path.GetRelativePath())) + { + fileCheckRequest.m_found = true; + } + else + { + // The file isn't in the archive but might still exist as a loose file, so let the next node have a shot. + StreamStackEntry::QueueRequest(checkRequest); + } + } + + void FullFileDecompressor::StartArchiveRead(FileRequest* compressedReadRequest) + { + if (!m_next) + { + compressedReadRequest->SetStatus(IStreamerTypes::RequestStatus::Failed); + m_context->MarkRequestAsCompleted(compressedReadRequest); + return; } - void FullFileDecompressor::FinishDecompression([[maybe_unused]] FileRequest* waitRequest, u32 jobSlot) + for (u32 i = 0; i < m_maxNumReads; ++i) { - DecompressionInformation& jobInfo = m_processingJobs[jobSlot]; - AZ_Assert(jobInfo.m_waitRequest == waitRequest, "Job slot didn't contain the expected wait request."); + if (m_readBufferStatus[i] == ReadBufferStatus::Unused) + { + auto data = AZStd::get_if(&compressedReadRequest->GetCommand()); + AZ_Assert(data, "Compressed request that's starting a read in FullFileDecompressor didn't contain compression read data."); + AZ_Assert(data->m_compressionInfo.m_decompressor, + "FileRequest for FullFileDecompressor is missing a decompression callback."); - auto endTime = AZStd::chrono::high_resolution_clock::now(); + CompressionInfo& info = data->m_compressionInfo; + AZ_Assert(info.m_decompressor, "FullFileDecompressor is planning to a queue a request for reading but couldn't find a decompressor."); - FileRequest* compressedRequest = jobInfo.m_waitRequest->GetParent(); - AZ_Assert(compressedRequest, "A wait request attached to FullFileDecompressor was completed but didn't have a parent compressed request."); + // The buffer is aligned down but the offset is not corrected. If the offset was adjusted it would mean the same data is read + // multiple times and negates the block cache's ability to detect these cases. By still adjusting it means that the reads between + // the BlockCache's prolog and epilog are read into aligned buffers. + size_t offsetAdjustment = info.m_offset - AZ_SIZE_ALIGN_DOWN(info.m_offset, aznumeric_cast(m_alignment)); + size_t bufferSize = AZ_SIZE_ALIGN_UP((info.m_compressedSize + offsetAdjustment), aznumeric_cast(m_alignment)); + m_readBuffers[i] = reinterpret_cast(AZ::AllocatorInstance::Get().Allocate( + bufferSize, m_alignment, 0, "AZ::IO::Streamer FullFileDecompressor", __FILE__, __LINE__)); + m_memoryUsage += bufferSize; + + FileRequest* archiveReadRequest = m_context->GetNewInternalRequest(); + archiveReadRequest->CreateRead(compressedReadRequest, m_readBuffers[i] + offsetAdjustment, bufferSize, info.m_archiveFilename, + info.m_offset, info.m_compressedSize, info.m_isSharedPak); + archiveReadRequest->SetCompletionCallback( + [this, readSlot = i](FileRequest& request) + { + AZ_PROFILE_FUNCTION(AzCore); + FinishArchiveRead(&request, readSlot); + }); + m_next->QueueRequest(archiveReadRequest); + + m_readRequests[i] = archiveReadRequest; + m_readBufferStatus[i] = ReadBufferStatus::ReadInFlight; + + AZ_Assert(m_numInFlightReads < m_maxNumReads, + "A FileRequest was queued for reading in FullFileDecompressor, but there's no slots available."); + m_numInFlightReads++; + + return; + } + } + AZ_Assert(false, "%u of %u read slots are use in the FullFileDecompressor, but no empty slot was found.", m_numInFlightReads, m_maxNumReads); + } + + void FullFileDecompressor::FinishArchiveRead(FileRequest* readRequest, u32 readSlot) + { + AZ_Assert(m_readRequests[readSlot] == readRequest, + "Request in the archive read slot isn't the same as request that's being completed."); + + FileRequest* compressedRequest = readRequest->GetParent(); + AZ_Assert(compressedRequest, "Read requests started by FullFileDecompressor is missing a parent request."); + + if (readRequest->GetStatus() == IStreamerTypes::RequestStatus::Completed) + { + m_readBufferStatus[readSlot] = ReadBufferStatus::PendingDecompression; + ++m_numPendingDecompression; + + // Add this wait so the compressed request isn't fully completed yet as only the read part is done. The + // job thread will finish this wait, which in turn will trigger this function again on the main streaming thread. + FileRequest* waitRequest = m_context->GetNewInternalRequest(); + waitRequest->CreateWait(compressedRequest); + m_readRequests[readSlot] = waitRequest; + } + else + { auto data = AZStd::get_if(&compressedRequest->GetCommand()); - AZ_Assert(data, "Compressed request in FullFileDecompressor that completed decompression didn't contain compression read data."); + AZ_Assert(data, "Compressed request in FullFileDecompressor that finished unsuccessfully didn't contain compression read data."); CompressionInfo& info = data->m_compressionInfo; size_t offsetAdjustment = info.m_offset - AZ_SIZE_ALIGN_DOWN(info.m_offset, aznumeric_cast(m_alignment)); size_t bufferSize = AZ_SIZE_ALIGN_UP((info.m_compressedSize + offsetAdjustment), aznumeric_cast(m_alignment)); m_memoryUsage -= bufferSize; - if (data->m_readOffset != 0 || data->m_readSize != data->m_compressionInfo.m_uncompressedSize) + + if (m_readBuffers[readSlot] != nullptr) { - m_memoryUsage -= data->m_compressionInfo.m_uncompressedSize; + AZ::AllocatorInstance::Get().DeAllocate(m_readBuffers[readSlot], bufferSize, m_alignment); + m_readBuffers[readSlot] = nullptr; + } + m_readRequests[readSlot] = nullptr; + m_readBufferStatus[readSlot] = ReadBufferStatus::Unused; + AZ_Assert(m_numInFlightReads > 0, + "Trying to decrement a read request after it was canceled or failed in FullFileDecompressor, " + "but no read requests are supposed to be queued."); + m_numInFlightReads--; + } + } + + bool FullFileDecompressor::StartDecompressions() + { + bool queuedJobs = false; + u32 jobSlot = 0; + for (u32 readSlot = 0; readSlot < m_maxNumReads; ++readSlot) + { + // Find completed read. + if (m_readBufferStatus[readSlot] != ReadBufferStatus::PendingDecompression) + { + continue; } - m_decompressionJobDelayMicroSec.PushEntry(AZStd::chrono::duration_cast( - jobInfo.m_jobStartTime - jobInfo.m_queueStartTime).count()); - m_decompressionDurationMicroSec.PushEntry(AZStd::chrono::duration_cast( - endTime - jobInfo.m_jobStartTime).count()); - m_bytesDecompressed.PushEntry(data->m_compressionInfo.m_compressedSize); + // Find decompression slot + for (; jobSlot < m_maxNumJobs; ++jobSlot) + { + if (m_processingJobs[jobSlot].IsProcessing()) + { + continue; + } - AZ::AllocatorInstance::Get().DeAllocate(jobInfo.m_compressedData, bufferSize, m_alignment); - jobInfo.m_compressedData = nullptr; - AZ_Assert(m_numRunningJobs > 0, "About to complete a decompression job, but the internal count doesn't see a running job."); - --m_numRunningJobs; - return; + FileRequest* waitRequest = m_readRequests[readSlot]; + AZ_Assert(AZStd::holds_alternative(waitRequest->GetCommand()), + "File request waiting for decompression wasn't marked as being a wait operation."); + FileRequest* compressedRequest = waitRequest->GetParent(); + AZ_Assert(compressedRequest, "Read requests started by FullFileDecompressor is missing a parent request."); + + waitRequest->SetCompletionCallback([this, jobSlot](FileRequest& request) + { + AZ_PROFILE_FUNCTION(AzCore); + FinishDecompression(&request, jobSlot); + }); + + DecompressionInformation& info = m_processingJobs[jobSlot]; + info.m_waitRequest = waitRequest; + info.m_queueStartTime = AZStd::chrono::high_resolution_clock::now(); + info.m_jobStartTime = info.m_queueStartTime; // Set these to the same in case the scheduler requests an update before the job has started. + info.m_compressedData = m_readBuffers[readSlot]; // Transfer ownership of the pointer. + m_readBuffers[readSlot] = nullptr; + + AZ::Job* decompressionJob; + auto data = AZStd::get_if(&compressedRequest->GetCommand()); + AZ_Assert(data, "Compressed request in FullFileDecompressor that's starting decompression didn't contain compression read data."); + AZ_Assert(data->m_compressionInfo.m_decompressor, "FullFileDecompressor is queuing a decompression job but couldn't find a decompressor."); + + info.m_alignmentOffset = aznumeric_caster(data->m_compressionInfo.m_offset - + AZ_SIZE_ALIGN_DOWN(data->m_compressionInfo.m_offset, aznumeric_cast(m_alignment))); + + if (data->m_readOffset == 0 && data->m_readSize == data->m_compressionInfo.m_uncompressedSize) + { + auto job = [this, &info]() + { + FullDecompression(m_context, info); + }; + decompressionJob = AZ::CreateJobFunction(job, true, m_decompressionjobContext.get()); + } + else + { + m_memoryUsage += data->m_compressionInfo.m_uncompressedSize; + auto job = [this, &info]() + { + PartialDecompression(m_context, info); + }; + decompressionJob = AZ::CreateJobFunction(job, true, m_decompressionjobContext.get()); + } + --m_numPendingDecompression; + ++m_numRunningJobs; + decompressionJob->Start(); + + m_readRequests[readSlot] = nullptr; + m_readBufferStatus[readSlot] = ReadBufferStatus::Unused; + AZ_Assert(m_numInFlightReads > 0, "Trying to decrement a read request after it's queued for decompression in FullFileDecompressor, but no read requests are supposed to be queued."); + m_numInFlightReads--; + + queuedJobs = true; + break; + } + + if (m_numInFlightReads == 0 || m_numRunningJobs == m_maxNumJobs) + { + return queuedJobs; + } } + return queuedJobs; + } - void FullFileDecompressor::FullDecompression(StreamerContext* context, DecompressionInformation& info) + void FullFileDecompressor::FinishDecompression([[maybe_unused]] FileRequest* waitRequest, u32 jobSlot) + { + DecompressionInformation& jobInfo = m_processingJobs[jobSlot]; + AZ_Assert(jobInfo.m_waitRequest == waitRequest, "Job slot didn't contain the expected wait request."); + + auto endTime = AZStd::chrono::high_resolution_clock::now(); + + FileRequest* compressedRequest = jobInfo.m_waitRequest->GetParent(); + AZ_Assert(compressedRequest, "A wait request attached to FullFileDecompressor was completed but didn't have a parent compressed request."); + auto data = AZStd::get_if(&compressedRequest->GetCommand()); + AZ_Assert(data, "Compressed request in FullFileDecompressor that completed decompression didn't contain compression read data."); + CompressionInfo& info = data->m_compressionInfo; + size_t offsetAdjustment = info.m_offset - AZ_SIZE_ALIGN_DOWN(info.m_offset, aznumeric_cast(m_alignment)); + size_t bufferSize = AZ_SIZE_ALIGN_UP((info.m_compressedSize + offsetAdjustment), aznumeric_cast(m_alignment)); + m_memoryUsage -= bufferSize; + if (data->m_readOffset != 0 || data->m_readSize != data->m_compressionInfo.m_uncompressedSize) { - info.m_jobStartTime = AZStd::chrono::high_resolution_clock::now(); - - FileRequest* compressedRequest = info.m_waitRequest->GetParent(); - AZ_Assert(compressedRequest, "A wait request attached to FullFileDecompressor was completed but didn't have a parent compressed request."); - auto request = AZStd::get_if(&compressedRequest->GetCommand()); - AZ_Assert(request, "Compressed request in FullFileDecompressor that's running full decompression didn't contain compression read data."); - CompressionInfo& compressionInfo = request->m_compressionInfo; - AZ_Assert(compressionInfo.m_decompressor, "Full decompressor job started, but there's no decompressor callback assigned."); - - AZ_Assert(request->m_readOffset == 0, "FullFileDecompressor is doing a full decompression on a file request with an offset (%zu).", - request->m_readOffset); - AZ_Assert(compressionInfo.m_uncompressedSize == request->m_readSize, - "FullFileDecompressor is doing a full decompression, but the target buffer size (%llu) doesn't match the decompressed size (%zu).", - request->m_readSize, compressionInfo.m_uncompressedSize); - - bool success = compressionInfo.m_decompressor(compressionInfo, info.m_compressedData + info.m_alignmentOffset, - compressionInfo.m_compressedSize, request->m_output, compressionInfo.m_uncompressedSize); - info.m_waitRequest->SetStatus(success ? IStreamerTypes::RequestStatus::Completed : IStreamerTypes::RequestStatus::Failed); - - context->MarkRequestAsCompleted(info.m_waitRequest); - context->WakeUpSchedulingThread(); + m_memoryUsage -= data->m_compressionInfo.m_uncompressedSize; } - void FullFileDecompressor::PartialDecompression(StreamerContext* context, DecompressionInformation& info) - { - info.m_jobStartTime = AZStd::chrono::high_resolution_clock::now(); + m_decompressionJobDelayMicroSec.PushEntry(AZStd::chrono::duration_cast( + jobInfo.m_jobStartTime - jobInfo.m_queueStartTime).count()); + m_decompressionDurationMicroSec.PushEntry(AZStd::chrono::duration_cast( + endTime - jobInfo.m_jobStartTime).count()); + m_bytesDecompressed.PushEntry(data->m_compressionInfo.m_compressedSize); - FileRequest* compressedRequest = info.m_waitRequest->GetParent(); - AZ_Assert(compressedRequest, "A wait request attached to FullFileDecompressor was completed but didn't have a parent compressed request."); - auto request = AZStd::get_if(&compressedRequest->GetCommand()); - AZ_Assert(request, "Compressed request in FullFileDecompressor that's running partial decompression didn't contain compression read data."); - CompressionInfo& compressionInfo = request->m_compressionInfo; - AZ_Assert(compressionInfo.m_decompressor, "Partial decompressor job started, but there's no decompressor callback assigned."); + AZ::AllocatorInstance::Get().DeAllocate(jobInfo.m_compressedData, bufferSize, m_alignment); + jobInfo.m_compressedData = nullptr; + AZ_Assert(m_numRunningJobs > 0, "About to complete a decompression job, but the internal count doesn't see a running job."); + --m_numRunningJobs; + return; + } - AZStd::unique_ptr decompressionBuffer = AZStd::unique_ptr(new u8[compressionInfo.m_uncompressedSize]); - bool success = compressionInfo.m_decompressor(compressionInfo, info.m_compressedData + info.m_alignmentOffset, - compressionInfo.m_compressedSize, decompressionBuffer.get(), compressionInfo.m_uncompressedSize); - info.m_waitRequest->SetStatus(success ? IStreamerTypes::RequestStatus::Completed : IStreamerTypes::RequestStatus::Failed); - - memcpy(request->m_output, decompressionBuffer.get() + request->m_readOffset, request->m_readSize); + void FullFileDecompressor::FullDecompression(StreamerContext* context, DecompressionInformation& info) + { + info.m_jobStartTime = AZStd::chrono::high_resolution_clock::now(); - context->MarkRequestAsCompleted(info.m_waitRequest); - context->WakeUpSchedulingThread(); - } - } // namespace IO -} // namespace AZ + FileRequest* compressedRequest = info.m_waitRequest->GetParent(); + AZ_Assert(compressedRequest, "A wait request attached to FullFileDecompressor was completed but didn't have a parent compressed request."); + auto request = AZStd::get_if(&compressedRequest->GetCommand()); + AZ_Assert(request, "Compressed request in FullFileDecompressor that's running full decompression didn't contain compression read data."); + CompressionInfo& compressionInfo = request->m_compressionInfo; + AZ_Assert(compressionInfo.m_decompressor, "Full decompressor job started, but there's no decompressor callback assigned."); + + AZ_Assert(request->m_readOffset == 0, "FullFileDecompressor is doing a full decompression on a file request with an offset (%zu).", + request->m_readOffset); + AZ_Assert(compressionInfo.m_uncompressedSize == request->m_readSize, + "FullFileDecompressor is doing a full decompression, but the target buffer size (%llu) doesn't match the decompressed size (%zu).", + request->m_readSize, compressionInfo.m_uncompressedSize); + + bool success = compressionInfo.m_decompressor(compressionInfo, info.m_compressedData + info.m_alignmentOffset, + compressionInfo.m_compressedSize, request->m_output, compressionInfo.m_uncompressedSize); + info.m_waitRequest->SetStatus(success ? IStreamerTypes::RequestStatus::Completed : IStreamerTypes::RequestStatus::Failed); + + context->MarkRequestAsCompleted(info.m_waitRequest); + context->WakeUpSchedulingThread(); + } + + void FullFileDecompressor::PartialDecompression(StreamerContext* context, DecompressionInformation& info) + { + info.m_jobStartTime = AZStd::chrono::high_resolution_clock::now(); + + FileRequest* compressedRequest = info.m_waitRequest->GetParent(); + AZ_Assert(compressedRequest, "A wait request attached to FullFileDecompressor was completed but didn't have a parent compressed request."); + auto request = AZStd::get_if(&compressedRequest->GetCommand()); + AZ_Assert(request, "Compressed request in FullFileDecompressor that's running partial decompression didn't contain compression read data."); + CompressionInfo& compressionInfo = request->m_compressionInfo; + AZ_Assert(compressionInfo.m_decompressor, "Partial decompressor job started, but there's no decompressor callback assigned."); + + AZStd::unique_ptr decompressionBuffer = AZStd::unique_ptr(new u8[compressionInfo.m_uncompressedSize]); + bool success = compressionInfo.m_decompressor(compressionInfo, info.m_compressedData + info.m_alignmentOffset, + compressionInfo.m_compressedSize, decompressionBuffer.get(), compressionInfo.m_uncompressedSize); + info.m_waitRequest->SetStatus(success ? IStreamerTypes::RequestStatus::Completed : IStreamerTypes::RequestStatus::Failed); + + memcpy(request->m_output, decompressionBuffer.get() + request->m_readOffset, request->m_readSize); + + context->MarkRequestAsCompleted(info.m_waitRequest); + context->WakeUpSchedulingThread(); + } +} // namespace AZ::IO diff --git a/Code/Framework/AzCore/AzCore/IO/Streamer/ReadSplitter.cpp b/Code/Framework/AzCore/AzCore/IO/Streamer/ReadSplitter.cpp index 00c1c63933..a952e31a93 100644 --- a/Code/Framework/AzCore/AzCore/IO/Streamer/ReadSplitter.cpp +++ b/Code/Framework/AzCore/AzCore/IO/Streamer/ReadSplitter.cpp @@ -14,376 +14,373 @@ #include #include -namespace AZ +namespace AZ::IO { - namespace IO + AZStd::shared_ptr ReadSplitterConfig::AddStreamStackEntry( + const HardwareInformation& hardware, AZStd::shared_ptr parent) { - AZStd::shared_ptr ReadSplitterConfig::AddStreamStackEntry( - const HardwareInformation& hardware, AZStd::shared_ptr parent) + size_t splitSize; + switch (m_splitSize) { - size_t splitSize; - switch (m_splitSize) - { - case SplitSize::MaxTransfer: - splitSize = hardware.m_maxTransfer; - break; - case SplitSize::MemoryAlignment: - splitSize = hardware.m_maxPhysicalSectorSize; - break; - default: - splitSize = m_splitSize; - break; - } - - size_t bufferSize = m_bufferSizeMib * 1_mib; - if (bufferSize < splitSize) - { - AZ_Warning("Streamer", false, "The buffer size for the Read Splitter is smaller than the individual split size. " - "It will be increased to fit at least one split."); - bufferSize = splitSize; - } - - auto stackEntry = AZStd::make_shared( - splitSize, - aznumeric_caster(hardware.m_maxPhysicalSectorSize), - aznumeric_caster(hardware.m_maxLogicalSectorSize), - bufferSize, m_adjustOffset, m_splitAlignedRequests); - stackEntry->SetNext(AZStd::move(parent)); - return stackEntry; + case SplitSize::MaxTransfer: + splitSize = hardware.m_maxTransfer; + break; + case SplitSize::MemoryAlignment: + splitSize = hardware.m_maxPhysicalSectorSize; + break; + default: + splitSize = m_splitSize; + break; } - void ReadSplitterConfig::Reflect(AZ::ReflectContext* context) + size_t bufferSize = m_bufferSizeMib * 1_mib; + if (bufferSize < splitSize) { - if (auto serializeContext = azrtti_cast(context); serializeContext != nullptr) - { - serializeContext->Enum() - ->Version(1) - ->Value("MaxTransfer", SplitSize::MaxTransfer) - ->Value("MemoryAlignment", SplitSize::MemoryAlignment); - - serializeContext->Class() - ->Version(1) - ->Field("BufferSizeMib", &ReadSplitterConfig::m_bufferSizeMib) - ->Field("SplitSize", &ReadSplitterConfig::m_splitSize) - ->Field("AdjustOffset", &ReadSplitterConfig::m_adjustOffset) - ->Field("SplitAlignedRequests", &ReadSplitterConfig::m_splitAlignedRequests); - } + AZ_Warning("Streamer", false, "The buffer size for the Read Splitter is smaller than the individual split size. " + "It will be increased to fit at least one split."); + bufferSize = splitSize; } - static constexpr char AvgNumSubReadsName[] = "Avg. num sub reads"; - static constexpr char AlignedReadsName[] = "Aligned reads"; - static constexpr char NumAvailableBufferSlotsName[] = "Num available buffer slots"; - static constexpr char NumPendingReadsName[] = "Num pending reads"; + auto stackEntry = AZStd::make_shared( + splitSize, + aznumeric_caster(hardware.m_maxPhysicalSectorSize), + aznumeric_caster(hardware.m_maxLogicalSectorSize), + bufferSize, m_adjustOffset, m_splitAlignedRequests); + stackEntry->SetNext(AZStd::move(parent)); + return stackEntry; + } - ReadSplitter::ReadSplitter(u64 maxReadSize, u32 memoryAlignment, u32 sizeAlignment, size_t bufferSize, - bool adjustOffset, bool splitAlignedRequests) - : StreamStackEntry("Read splitter") - , m_buffer(nullptr) - , m_bufferSize(bufferSize) - , m_maxReadSize(maxReadSize) - , m_memoryAlignment(memoryAlignment) - , m_sizeAlignment(sizeAlignment) - , m_adjustOffset(adjustOffset) - , m_splitAlignedRequests(splitAlignedRequests) + void ReadSplitterConfig::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context); serializeContext != nullptr) { - AZ_Assert(IStreamerTypes::IsPowerOf2(memoryAlignment), "Memory alignment needs to be a power of 2"); - AZ_Assert(IStreamerTypes::IsPowerOf2(sizeAlignment), "Size alignment needs to be a power of 2"); - AZ_Assert(IStreamerTypes::IsAlignedTo(maxReadSize, sizeAlignment), - "Maximum read size isn't aligned to a multiple of the size alignment."); + serializeContext->Enum() + ->Version(1) + ->Value("MaxTransfer", SplitSize::MaxTransfer) + ->Value("MemoryAlignment", SplitSize::MemoryAlignment); - size_t numBufferSlots = bufferSize / maxReadSize; - // Don't divide the reads up in more sub-reads than there are dependencies available. - numBufferSlots = AZStd::min(numBufferSlots, FileRequest::GetMaxNumDependencies()); - m_bufferCopyInformation = AZStd::unique_ptr(new BufferCopyInformation[numBufferSlots]); - m_availableBufferSlots.reserve(numBufferSlots); - for (u32 i = aznumeric_caster(numBufferSlots); i > 0; --i) - { - m_availableBufferSlots.push_back(i - 1); - } + serializeContext->Class() + ->Version(1) + ->Field("BufferSizeMib", &ReadSplitterConfig::m_bufferSizeMib) + ->Field("SplitSize", &ReadSplitterConfig::m_splitSize) + ->Field("AdjustOffset", &ReadSplitterConfig::m_adjustOffset) + ->Field("SplitAlignedRequests", &ReadSplitterConfig::m_splitAlignedRequests); + } + } + + static constexpr char AvgNumSubReadsName[] = "Avg. num sub reads"; + static constexpr char AlignedReadsName[] = "Aligned reads"; + static constexpr char NumAvailableBufferSlotsName[] = "Num available buffer slots"; + static constexpr char NumPendingReadsName[] = "Num pending reads"; + + ReadSplitter::ReadSplitter(u64 maxReadSize, u32 memoryAlignment, u32 sizeAlignment, size_t bufferSize, + bool adjustOffset, bool splitAlignedRequests) + : StreamStackEntry("Read splitter") + , m_buffer(nullptr) + , m_bufferSize(bufferSize) + , m_maxReadSize(maxReadSize) + , m_memoryAlignment(memoryAlignment) + , m_sizeAlignment(sizeAlignment) + , m_adjustOffset(adjustOffset) + , m_splitAlignedRequests(splitAlignedRequests) + { + AZ_Assert(IStreamerTypes::IsPowerOf2(memoryAlignment), "Memory alignment needs to be a power of 2"); + AZ_Assert(IStreamerTypes::IsPowerOf2(sizeAlignment), "Size alignment needs to be a power of 2"); + AZ_Assert(IStreamerTypes::IsAlignedTo(maxReadSize, sizeAlignment), + "Maximum read size isn't aligned to a multiple of the size alignment."); + + size_t numBufferSlots = bufferSize / maxReadSize; + // Don't divide the reads up in more sub-reads than there are dependencies available. + numBufferSlots = AZStd::min(numBufferSlots, FileRequest::GetMaxNumDependencies()); + m_bufferCopyInformation = AZStd::unique_ptr(new BufferCopyInformation[numBufferSlots]); + m_availableBufferSlots.reserve(numBufferSlots); + for (u32 i = aznumeric_caster(numBufferSlots); i > 0; --i) + { + m_availableBufferSlots.push_back(i - 1); + } + } + + ReadSplitter::~ReadSplitter() + { + if (m_buffer) + { + AZ::AllocatorInstance::Get().DeAllocate(m_buffer, m_bufferSize, m_memoryAlignment); + } + } + + void ReadSplitter::QueueRequest(FileRequest* request) + { + AZ_Assert(request, "QueueRequest was provided a null request."); + if (!m_next) + { + request->SetStatus(IStreamerTypes::RequestStatus::Failed); + m_context->MarkRequestAsCompleted(request); + return; } - ReadSplitter::~ReadSplitter() + auto data = AZStd::get_if(&request->GetCommand()); + if (data == nullptr) { - if (m_buffer) - { - AZ::AllocatorInstance::Get().DeAllocate(m_buffer, m_bufferSize, m_memoryAlignment); - } + StreamStackEntry::QueueRequest(request); + return; } - void ReadSplitter::QueueRequest(FileRequest* request) - { - AZ_Assert(request, "QueueRequest was provided a null request."); - if (!m_next) - { - request->SetStatus(IStreamerTypes::RequestStatus::Failed); - m_context->MarkRequestAsCompleted(request); - return; - } + m_averageNumSubReadsStat.PushSample(aznumeric_cast((data->m_size / m_maxReadSize) + 1)); + Statistic::PlotImmediate(m_name, AvgNumSubReadsName, m_averageNumSubReadsStat.GetMostRecentSample()); - auto data = AZStd::get_if(&request->GetCommand()); - if (data == nullptr) + bool isAligned = IStreamerTypes::IsAlignedTo(data->m_output, m_memoryAlignment); + if (m_adjustOffset) + { + isAligned = isAligned && IStreamerTypes::IsAlignedTo(data->m_offset, m_sizeAlignment); + } + + if (isAligned || m_bufferSize == 0) + { + m_alignedReadsStat.PushSample(isAligned ? 1.0 : 0.0); + if (!m_splitAlignedRequests) { StreamStackEntry::QueueRequest(request); - return; - } - - m_averageNumSubReadsStat.PushSample(aznumeric_cast((data->m_size / m_maxReadSize) + 1)); - Statistic::PlotImmediate(m_name, AvgNumSubReadsName, m_averageNumSubReadsStat.GetMostRecentSample()); - - bool isAligned = IStreamerTypes::IsAlignedTo(data->m_output, m_memoryAlignment); - if (m_adjustOffset) - { - isAligned = isAligned && IStreamerTypes::IsAlignedTo(data->m_offset, m_sizeAlignment); - } - - if (isAligned || m_bufferSize == 0) - { - m_alignedReadsStat.PushSample(isAligned ? 1.0 : 0.0); - if (!m_splitAlignedRequests) - { - StreamStackEntry::QueueRequest(request); - } - else - { - QueueAlignedRead(request); - } } else { - m_alignedReadsStat.PushSample(0.0); - InitializeBuffer(); - QueueBufferedRead(request); + QueueAlignedRead(request); } } - - void ReadSplitter::QueueAlignedRead(FileRequest* request) + else { - auto data = AZStd::get_if(&request->GetCommand()); - AZ_Assert(data != nullptr, "Provided request to queue by the Read Splitter did not contain a read command."); + m_alignedReadsStat.PushSample(0.0); + InitializeBuffer(); + QueueBufferedRead(request); + } + } - if (data->m_size <= m_maxReadSize) - { - StreamStackEntry::QueueRequest(request); - return; - } + void ReadSplitter::QueueAlignedRead(FileRequest* request) + { + auto data = AZStd::get_if(&request->GetCommand()); + AZ_Assert(data != nullptr, "Provided request to queue by the Read Splitter did not contain a read command."); - PendingRead pendingRead; - pendingRead.m_request = request; - pendingRead.m_output = reinterpret_cast(data->m_output); - pendingRead.m_outputSize = data->m_outputSize; - pendingRead.m_readSize = data->m_size; - pendingRead.m_offset = data->m_offset; - pendingRead.m_isBuffered = false; - - if (!m_pendingReads.empty()) - { - m_pendingReads.push_back(pendingRead); - return; - } - - if (!QueueAlignedRead(pendingRead)) - { - m_pendingReads.push_back(pendingRead); - } + if (data->m_size <= m_maxReadSize) + { + StreamStackEntry::QueueRequest(request); + return; } - bool ReadSplitter::QueueAlignedRead(PendingRead& pending) + PendingRead pendingRead; + pendingRead.m_request = request; + pendingRead.m_output = reinterpret_cast(data->m_output); + pendingRead.m_outputSize = data->m_outputSize; + pendingRead.m_readSize = data->m_size; + pendingRead.m_offset = data->m_offset; + pendingRead.m_isBuffered = false; + + if (!m_pendingReads.empty()) { - auto data = AZStd::get_if(&pending.m_request->GetCommand()); - AZ_Assert(data != nullptr, "Provided request to queue by the Read Splitter did not contain a read command."); + m_pendingReads.push_back(pendingRead); + return; + } - while (pending.m_readSize > 0) + if (!QueueAlignedRead(pendingRead)) + { + m_pendingReads.push_back(pendingRead); + } + } + + bool ReadSplitter::QueueAlignedRead(PendingRead& pending) + { + auto data = AZStd::get_if(&pending.m_request->GetCommand()); + AZ_Assert(data != nullptr, "Provided request to queue by the Read Splitter did not contain a read command."); + + while (pending.m_readSize > 0) + { + if (pending.m_request->GetNumDependencies() >= FileRequest::GetMaxNumDependencies()) { - if (pending.m_request->GetNumDependencies() >= FileRequest::GetMaxNumDependencies()) + // Add a wait to make sure the read request isn't completed if all sub-reads completed before + // the ReadSplitter has had a chance to add new sub-reads to complete the read. + if (pending.m_wait == nullptr) { - // Add a wait to make sure the read request isn't completed if all sub-reads completed before - // the ReadSplitter has had a chance to add new sub-reads to complete the read. - if (pending.m_wait == nullptr) - { - pending.m_wait = m_context->GetNewInternalRequest(); - pending.m_wait->CreateWait(pending.m_request); - } - return false; + pending.m_wait = m_context->GetNewInternalRequest(); + pending.m_wait->CreateWait(pending.m_request); } + return false; + } - u64 readSize = m_maxReadSize; - size_t bufferSize = m_maxReadSize; - if (pending.m_readSize < m_maxReadSize) + u64 readSize = m_maxReadSize; + size_t bufferSize = m_maxReadSize; + if (pending.m_readSize < m_maxReadSize) + { + readSize = pending.m_readSize; + // This will be the last read so give the remainder of the output buffer to the final request. + bufferSize = pending.m_outputSize; + } + + FileRequest* subRequest = m_context->GetNewInternalRequest(); + subRequest->CreateRead(pending.m_request, pending.m_output, bufferSize, data->m_path, pending.m_offset, readSize, data->m_sharedRead); + subRequest->SetCompletionCallback([this](FileRequest&) { - readSize = pending.m_readSize; - // This will be the last read so give the remainder of the output buffer to the final request. - bufferSize = pending.m_outputSize; + AZ_PROFILE_FUNCTION(AzCore); + QueuePendingRequest(); + }); + m_next->QueueRequest(subRequest); + + pending.m_offset += readSize; + pending.m_readSize -= readSize; + pending.m_outputSize -= bufferSize; + pending.m_output += readSize; + } + if (pending.m_wait != nullptr) + { + m_context->MarkRequestAsCompleted(pending.m_wait); + pending.m_wait = nullptr; + } + return true; + } + + void ReadSplitter::QueueBufferedRead(FileRequest* request) + { + auto data = AZStd::get_if(&request->GetCommand()); + AZ_Assert(data != nullptr, "Provided request to queue by the Read Splitter did not contain a read command."); + + PendingRead pendingRead; + pendingRead.m_request = request; + pendingRead.m_output = reinterpret_cast(data->m_output); + pendingRead.m_outputSize = data->m_outputSize; + pendingRead.m_readSize = data->m_size; + pendingRead.m_offset = data->m_offset; + pendingRead.m_isBuffered = true; + + if (!m_pendingReads.empty()) + { + m_pendingReads.push_back(pendingRead); + return; + } + + if (!QueueBufferedRead(pendingRead)) + { + m_pendingReads.push_back(pendingRead); + } + } + + bool ReadSplitter::QueueBufferedRead(PendingRead& pending) + { + auto data = AZStd::get_if(&pending.m_request->GetCommand()); + AZ_Assert(data != nullptr, "Provided request to queue by the Read Splitter did not contain a read command."); + + while (pending.m_readSize > 0) + { + if (!m_availableBufferSlots.empty()) + { + u32 bufferSlot = m_availableBufferSlots.back(); + m_availableBufferSlots.pop_back(); + + u64 readSize; + u64 copySize; + u64 offset; + BufferCopyInformation& copyInfo = m_bufferCopyInformation[bufferSlot]; + copyInfo.m_target = pending.m_output; + + if (m_adjustOffset) + { + offset = AZ_SIZE_ALIGN_DOWN(pending.m_offset, aznumeric_cast(m_sizeAlignment)); + size_t bufferOffset = pending.m_offset - offset; + copyInfo.m_bufferOffset = bufferOffset; + readSize = AZStd::min(pending.m_readSize + bufferOffset, m_maxReadSize); + copySize = readSize - bufferOffset; } - + else + { + offset = pending.m_offset; + readSize = AZStd::min(pending.m_readSize, m_maxReadSize); + copySize = readSize; + } + AZ_Assert(readSize <= m_maxReadSize, "Read size %llu in read splitter exceeds the maximum split size of %llu.", + readSize, m_maxReadSize); + copyInfo.m_size = copySize; + FileRequest* subRequest = m_context->GetNewInternalRequest(); - subRequest->CreateRead(pending.m_request, pending.m_output, bufferSize, data->m_path, pending.m_offset, readSize, data->m_sharedRead); - subRequest->SetCompletionCallback([this](FileRequest&) + subRequest->CreateRead(pending.m_request, GetBufferSlot(bufferSlot), m_maxReadSize, data->m_path, + offset, readSize, data->m_sharedRead); + subRequest->SetCompletionCallback([this, bufferSlot]([[maybe_unused]] FileRequest& request) { AZ_PROFILE_FUNCTION(AzCore); + + BufferCopyInformation& copyInfo = m_bufferCopyInformation[bufferSlot]; + memcpy(copyInfo.m_target, GetBufferSlot(bufferSlot) + copyInfo.m_bufferOffset, copyInfo.m_size); + m_availableBufferSlots.push_back(bufferSlot); + QueuePendingRequest(); }); m_next->QueueRequest(subRequest); - pending.m_offset += readSize; - pending.m_readSize -= readSize; - pending.m_outputSize -= bufferSize; - pending.m_output += readSize; + pending.m_offset += copySize; + pending.m_readSize -= copySize; + pending.m_outputSize -= copySize; + pending.m_output += copySize; } - if (pending.m_wait != nullptr) + else { - m_context->MarkRequestAsCompleted(pending.m_wait); - pending.m_wait = nullptr; - } - return true; - } - - void ReadSplitter::QueueBufferedRead(FileRequest* request) - { - auto data = AZStd::get_if(&request->GetCommand()); - AZ_Assert(data != nullptr, "Provided request to queue by the Read Splitter did not contain a read command."); - - PendingRead pendingRead; - pendingRead.m_request = request; - pendingRead.m_output = reinterpret_cast(data->m_output); - pendingRead.m_outputSize = data->m_outputSize; - pendingRead.m_readSize = data->m_size; - pendingRead.m_offset = data->m_offset; - pendingRead.m_isBuffered = true; - - if (!m_pendingReads.empty()) - { - m_pendingReads.push_back(pendingRead); - return; - } - - if (!QueueBufferedRead(pendingRead)) - { - m_pendingReads.push_back(pendingRead); - } - } - - bool ReadSplitter::QueueBufferedRead(PendingRead& pending) - { - auto data = AZStd::get_if(&pending.m_request->GetCommand()); - AZ_Assert(data != nullptr, "Provided request to queue by the Read Splitter did not contain a read command."); - - while (pending.m_readSize > 0) - { - if (!m_availableBufferSlots.empty()) + // Add a wait to make sure the read request isn't completed if all sub-reads completed before + // the ReadSplitter has had a chance to add new sub-reads to complete the read. + if (pending.m_wait == nullptr) { - u32 bufferSlot = m_availableBufferSlots.back(); - m_availableBufferSlots.pop_back(); - - u64 readSize; - u64 copySize; - u64 offset; - BufferCopyInformation& copyInfo = m_bufferCopyInformation[bufferSlot]; - copyInfo.m_target = pending.m_output; - - if (m_adjustOffset) - { - offset = AZ_SIZE_ALIGN_DOWN(pending.m_offset, aznumeric_cast(m_sizeAlignment)); - size_t bufferOffset = pending.m_offset - offset; - copyInfo.m_bufferOffset = bufferOffset; - readSize = AZStd::min(pending.m_readSize + bufferOffset, m_maxReadSize); - copySize = readSize - bufferOffset; - } - else - { - offset = pending.m_offset; - readSize = AZStd::min(pending.m_readSize, m_maxReadSize); - copySize = readSize; - } - AZ_Assert(readSize <= m_maxReadSize, "Read size %llu in read splitter exceeds the maximum split size of %llu.", - readSize, m_maxReadSize); - copyInfo.m_size = copySize; - - FileRequest* subRequest = m_context->GetNewInternalRequest(); - subRequest->CreateRead(pending.m_request, GetBufferSlot(bufferSlot), m_maxReadSize, data->m_path, - offset, readSize, data->m_sharedRead); - subRequest->SetCompletionCallback([this, bufferSlot]([[maybe_unused]] FileRequest& request) - { - AZ_PROFILE_FUNCTION(AzCore); - - BufferCopyInformation& copyInfo = m_bufferCopyInformation[bufferSlot]; - memcpy(copyInfo.m_target, GetBufferSlot(bufferSlot) + copyInfo.m_bufferOffset, copyInfo.m_size); - m_availableBufferSlots.push_back(bufferSlot); - - QueuePendingRequest(); - }); - m_next->QueueRequest(subRequest); - - pending.m_offset += copySize; - pending.m_readSize -= copySize; - pending.m_outputSize -= copySize; - pending.m_output += copySize; - } - else - { - // Add a wait to make sure the read request isn't completed if all sub-reads completed before - // the ReadSplitter has had a chance to add new sub-reads to complete the read. - if (pending.m_wait == nullptr) - { - pending.m_wait = m_context->GetNewInternalRequest(); - pending.m_wait->CreateWait(pending.m_request); - } - return false; + pending.m_wait = m_context->GetNewInternalRequest(); + pending.m_wait->CreateWait(pending.m_request); } + return false; } - if (pending.m_wait != nullptr) + } + if (pending.m_wait != nullptr) + { + m_context->MarkRequestAsCompleted(pending.m_wait); + pending.m_wait = nullptr; + } + return true; + } + + void ReadSplitter::QueuePendingRequest() + { + if (!m_pendingReads.empty()) + { + PendingRead& pendingRead = m_pendingReads.front(); + if (pendingRead.m_isBuffered ? QueueBufferedRead(pendingRead) : QueueAlignedRead(pendingRead)) { - m_context->MarkRequestAsCompleted(pending.m_wait); - pending.m_wait = nullptr; - } - return true; - } - - void ReadSplitter::QueuePendingRequest() - { - if (!m_pendingReads.empty()) - { - PendingRead& pendingRead = m_pendingReads.front(); - if (pendingRead.m_isBuffered ? QueueBufferedRead(pendingRead) : QueueAlignedRead(pendingRead)) - { - m_pendingReads.pop_front(); - } + m_pendingReads.pop_front(); } } + } - void ReadSplitter::UpdateStatus(Status& status) const + void ReadSplitter::UpdateStatus(Status& status) const + { + StreamStackEntry::UpdateStatus(status); + if (m_bufferSize > 0) { - StreamStackEntry::UpdateStatus(status); - if (m_bufferSize > 0) - { - s32 numAvailableSlots = aznumeric_cast(m_availableBufferSlots.size()); - status.m_numAvailableSlots = AZStd::min(status.m_numAvailableSlots, numAvailableSlots); - status.m_isIdle = status.m_isIdle && m_pendingReads.empty(); - } + s32 numAvailableSlots = aznumeric_cast(m_availableBufferSlots.size()); + status.m_numAvailableSlots = AZStd::min(status.m_numAvailableSlots, numAvailableSlots); + status.m_isIdle = status.m_isIdle && m_pendingReads.empty(); } + } - void ReadSplitter::CollectStatistics(AZStd::vector& statistics) const - { - statistics.push_back(Statistic::CreateFloat(m_name, AvgNumSubReadsName, m_averageNumSubReadsStat.GetAverage())); - statistics.push_back(Statistic::CreatePercentage(m_name, AlignedReadsName, m_alignedReadsStat.GetAverage())); - statistics.push_back(Statistic::CreateInteger(m_name, NumAvailableBufferSlotsName, aznumeric_caster(m_availableBufferSlots.size()))); - statistics.push_back(Statistic::CreateInteger(m_name, NumPendingReadsName, aznumeric_caster(m_pendingReads.size()))); - StreamStackEntry::CollectStatistics(statistics); - } + void ReadSplitter::CollectStatistics(AZStd::vector& statistics) const + { + statistics.push_back(Statistic::CreateFloat(m_name, AvgNumSubReadsName, m_averageNumSubReadsStat.GetAverage())); + statistics.push_back(Statistic::CreatePercentage(m_name, AlignedReadsName, m_alignedReadsStat.GetAverage())); + statistics.push_back(Statistic::CreateInteger(m_name, NumAvailableBufferSlotsName, aznumeric_caster(m_availableBufferSlots.size()))); + statistics.push_back(Statistic::CreateInteger(m_name, NumPendingReadsName, aznumeric_caster(m_pendingReads.size()))); + StreamStackEntry::CollectStatistics(statistics); + } - void ReadSplitter::InitializeBuffer() + void ReadSplitter::InitializeBuffer() + { + // Lazy initialization to avoid allocating memory if it's not needed. + if (m_bufferSize != 0 && m_buffer == nullptr) { - // Lazy initialization to avoid allocating memory if it's not needed. - if (m_bufferSize != 0 && m_buffer == nullptr) - { - m_buffer = reinterpret_cast(AZ::AllocatorInstance::Get().Allocate( - m_bufferSize, m_memoryAlignment, 0, "AZ::IO::Streamer ReadSplitter", __FILE__, __LINE__)); - } + m_buffer = reinterpret_cast(AZ::AllocatorInstance::Get().Allocate( + m_bufferSize, m_memoryAlignment, 0, "AZ::IO::Streamer ReadSplitter", __FILE__, __LINE__)); } + } - u8* ReadSplitter::GetBufferSlot(size_t index) - { - AZ_Assert(m_buffer != nullptr, "A buffer slot was requested by the Read Splitter before the buffer was initialized."); - return m_buffer + (index * m_maxReadSize); - } - } // namespace IO -} // namesapce AZ + u8* ReadSplitter::GetBufferSlot(size_t index) + { + AZ_Assert(m_buffer != nullptr, "A buffer slot was requested by the Read Splitter before the buffer was initialized."); + return m_buffer + (index * m_maxReadSize); + } +} // namespace AZ::IO diff --git a/Code/Framework/AzCore/AzCore/IO/Streamer/StreamerComponent.cpp b/Code/Framework/AzCore/AzCore/IO/Streamer/StreamerComponent.cpp index 9728866fb6..9a465021c2 100644 --- a/Code/Framework/AzCore/AzCore/IO/Streamer/StreamerComponent.cpp +++ b/Code/Framework/AzCore/AzCore/IO/Streamer/StreamerComponent.cpp @@ -156,7 +156,10 @@ namespace AZ void StreamerComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) { bool isEnabled = false; - AZ::Debug::ProfilerRequestBus::BroadcastResult(isEnabled, &AZ::Debug::ProfilerRequests::IsActive); + if (auto profilerSystem = AZ::Debug::ProfilerSystemInterface::Get(); profilerSystem) + { + isEnabled = profilerSystem->IsActive(); + } if (isEnabled) { diff --git a/Code/Framework/AzCore/AzCore/IO/SystemFile.cpp b/Code/Framework/AzCore/AzCore/IO/SystemFile.cpp index ac12a59609..12f4003144 100644 --- a/Code/Framework/AzCore/AzCore/IO/SystemFile.cpp +++ b/Code/Framework/AzCore/AzCore/IO/SystemFile.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include @@ -101,7 +100,6 @@ bool SystemFile::Open(const char* fileName, int mode, int platformFlags) { if (strlen(fileName) > m_fileName.max_size()) { - EBUS_EVENT(FileIOEventBus, OnError, this, nullptr, 0); return false; } @@ -109,17 +107,6 @@ bool SystemFile::Open(const char* fileName, int mode, int platformFlags) m_fileName = fileName; } - if (FileIOBus::HasHandlers()) - { - bool isOpen = false; - bool isHandled = false; - EBUS_EVENT_RESULT(isHandled, FileIOBus, OnOpen, *this, m_fileName.c_str(), mode, platformFlags, isOpen); - if (isHandled) - { - return isOpen; - } - } - AZ_Assert(!IsOpen(), "This file (%s) is already open!", m_fileName.c_str()); return PlatformOpen(mode, platformFlags); @@ -133,31 +120,11 @@ bool SystemFile::ReOpen(int mode, int platformFlags) void SystemFile::Close() { - if (FileIOBus::HasHandlers()) - { - bool isHandled = false; - EBUS_EVENT_RESULT(isHandled, FileIOBus, OnClose, *this); - if (isHandled) - { - return; - } - } - PlatformClose(); } void SystemFile::Seek(SeekSizeType offset, SeekMode mode) { - if (FileIOBus::HasHandlers()) - { - bool isHandled = false; - EBUS_EVENT_RESULT(isHandled, FileIOBus, OnSeek, *this, offset, mode); - if (isHandled) - { - return; - } - } - Platform::Seek(m_handle, this, offset, mode); } @@ -178,33 +145,11 @@ AZ::u64 SystemFile::ModificationTime() SystemFile::SizeType SystemFile::Read(SizeType byteSize, void* buffer) { - if (FileIOBus::HasHandlers()) - { - SizeType numRead = 0; - bool isHandled = false; - EBUS_EVENT_RESULT(isHandled, FileIOBus, OnRead, *this, byteSize, buffer, numRead); - if (isHandled) - { - return numRead; - } - } - return Platform::Read(m_handle, this, byteSize, buffer); } SystemFile::SizeType SystemFile::Write(const void* buffer, SizeType byteSize) { - if (FileIOBus::HasHandlers()) - { - SizeType numWritten = 0; - bool isHandled = false; - EBUS_EVENT_RESULT(isHandled, FileIOBus, OnWrite, *this, buffer, byteSize, numWritten); - if (isHandled) - { - return numWritten; - } - } - return Platform::Write(m_handle, this, buffer, byteSize); } diff --git a/Code/Framework/AzCore/AzCore/IPC/SharedMemory.cpp b/Code/Framework/AzCore/AzCore/IPC/SharedMemory.cpp index af73ab4936..c3fa69a205 100644 --- a/Code/Framework/AzCore/AzCore/IPC/SharedMemory.cpp +++ b/Code/Framework/AzCore/AzCore/IPC/SharedMemory.cpp @@ -13,23 +13,18 @@ #include -namespace AZ +namespace AZ::Internal { - namespace Internal + struct RingData { - struct RingData - { - AZ::u32 m_readOffset; - AZ::u32 m_writeOffset; - AZ::u32 m_startOffset; - AZ::u32 m_endOffset; - AZ::u32 m_dataToRead; - AZ::u8 m_pad[32 - sizeof(AZStd::spin_mutex)]; - }; - } // namespace Internal -} // namespace AZ - - + AZ::u32 m_readOffset; + AZ::u32 m_writeOffset; + AZ::u32 m_startOffset; + AZ::u32 m_endOffset; + AZ::u32 m_dataToRead; + AZ::u8 m_pad[32 - sizeof(AZStd::spin_mutex)]; + }; +} // namespace AZ::Internal using namespace AZ; diff --git a/Code/Framework/AzCore/AzCore/Jobs/Internal/JobManagerWorkStealing.cpp b/Code/Framework/AzCore/AzCore/Jobs/Internal/JobManagerWorkStealing.cpp index 230bf959f6..ce8455d9a1 100644 --- a/Code/Framework/AzCore/AzCore/Jobs/Internal/JobManagerWorkStealing.cpp +++ b/Code/Framework/AzCore/AzCore/Jobs/Internal/JobManagerWorkStealing.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include @@ -83,7 +84,7 @@ AZ_THREAD_LOCAL JobManagerWorkStealing::ThreadInfo* JobManagerWorkStealing::m_cu JobManagerWorkStealing::JobManagerWorkStealing(const JobManagerDesc& desc) : m_isAsynchronous(!desc.m_workerThreads.empty()) - , m_workerThreads(AZStd::move(CreateWorkerThreads(desc.m_workerThreads))) + , m_workerThreads(AZStd::move(CreateWorkerThreads(desc))) { //allow workers to begin processing after they have all been created, needed to wait since they may access each others queues m_initSemaphore.release(static_cast(desc.m_workerThreads.size())); @@ -457,8 +458,6 @@ void JobManagerWorkStealing::ProcessJobsInternal(ThreadInfo* info, Job* suspende else { //attempt to steal a job from another thread's queue - AZ_PROFILE_SCOPE(AzCore, "JobManagerWorkStealing::ProcessJobsInternal:WorkStealing"); - unsigned int numStealAttempts = 0; const unsigned int maxStealAttempts = (unsigned int)m_workerThreads.size() * 3; //try every thread a few times before giving up while (!job) @@ -620,8 +619,9 @@ JobManagerWorkStealing::ThreadInfo* JobManagerWorkStealing::FindCurrentThreadInf return info; } -JobManagerWorkStealing::ThreadList JobManagerWorkStealing::CreateWorkerThreads(const JobManagerDesc::DescList& workerDescList) +JobManagerWorkStealing::ThreadList JobManagerWorkStealing::CreateWorkerThreads(const JobManagerDesc& jmDesc) { + const JobManagerDesc::DescList& workerDescList = jmDesc.m_workerThreads; ThreadList workerThreads(workerDescList.size()); m_threads.reserve(workerDescList.size()); @@ -634,8 +634,12 @@ JobManagerWorkStealing::ThreadList JobManagerWorkStealing::CreateWorkerThreads(c info->m_owningManager = this; info->m_workerId = iThread; + AZStd::fixed_string<128> threadName = AZStd::fixed_string<128>::format( + "%s worker thread %d", + jmDesc.m_jobManagerName[0] != '\0' ? jmDesc.m_jobManagerName : "AZ JobManager", + iThread); AZStd::thread_desc threadDesc; - threadDesc.m_name = "AZ JobManager worker thread"; + threadDesc.m_name = threadName.c_str(); threadDesc.m_cpuId = desc.m_cpuId; threadDesc.m_priority = desc.m_priority; if (desc.m_stackSize != 0) diff --git a/Code/Framework/AzCore/AzCore/Jobs/Internal/JobManagerWorkStealing.h b/Code/Framework/AzCore/AzCore/Jobs/Internal/JobManagerWorkStealing.h index 55de872d86..734c166d6a 100644 --- a/Code/Framework/AzCore/AzCore/Jobs/Internal/JobManagerWorkStealing.h +++ b/Code/Framework/AzCore/AzCore/Jobs/Internal/JobManagerWorkStealing.h @@ -115,7 +115,7 @@ namespace AZ void ProcessJobsAssist(ThreadInfo* info, Job* suspendedJob, AZStd::atomic* notifyFlag); void ProcessJobsSynchronous(ThreadInfo* info, Job* suspendedJob, AZStd::atomic* notifyFlag); void ProcessJobsInternal(ThreadInfo* info, Job* suspendedJob, AZStd::atomic* notifyFlag); - ThreadList CreateWorkerThreads(const JobManagerDesc::DescList& workerDescList); + ThreadList CreateWorkerThreads(const JobManagerDesc& jmDesc); #ifndef AZ_MONOLITHIC_BUILD ThreadInfo* CrossModuleFindAndSetWorkerThreadInfo() const; #endif diff --git a/Code/Framework/AzCore/AzCore/Jobs/JobManagerComponent.cpp b/Code/Framework/AzCore/AzCore/Jobs/JobManagerComponent.cpp index 2307a9372c..009b6b19dc 100644 --- a/Code/Framework/AzCore/AzCore/Jobs/JobManagerComponent.cpp +++ b/Code/Framework/AzCore/AzCore/Jobs/JobManagerComponent.cpp @@ -24,7 +24,7 @@ AZ_CVAR(float, cl_jobThreadsConcurrencyRatio, 0.6f, nullptr, AZ::ConsoleFunctorFlags::Null, "Legacy Job system multiplier on the number of hw threads the machine creates at initialization"); AZ_CVAR(uint32_t, cl_jobThreadsNumReserved, 2, nullptr, AZ::ConsoleFunctorFlags::Null, "Legacy Job system number of hardware threads that are reserved for O3DE system threads"); -AZ_CVAR(uint32_t, cl_jobThreadsMinNumber, 2, nullptr, AZ::ConsoleFunctorFlags::Null, "Legacy Job system minimum number of worker threads to create after scaling the number of hw threads"); +AZ_CVAR(uint32_t, cl_jobThreadsMinNumber, 3, nullptr, AZ::ConsoleFunctorFlags::Null, "Legacy Job system minimum number of worker threads to create after scaling the number of hw threads"); namespace AZ { @@ -51,6 +51,7 @@ namespace AZ JobManagerBus::Handler::BusConnect(); JobManagerDesc desc; + desc.m_jobManagerName = "Default JobManager"; JobManagerThreadDesc threadDesc; int numberOfWorkerThreads = m_numberOfWorkerThreads; diff --git a/Code/Framework/AzCore/AzCore/Jobs/JobManagerDesc.h b/Code/Framework/AzCore/AzCore/Jobs/JobManagerDesc.h index 94f84f27b3..b2156291bc 100644 --- a/Code/Framework/AzCore/AzCore/Jobs/JobManagerDesc.h +++ b/Code/Framework/AzCore/AzCore/Jobs/JobManagerDesc.h @@ -51,6 +51,8 @@ namespace AZ { JobManagerDesc() {} + const char* m_jobManagerName = ""; + using DescList = AZStd::fixed_vector; DescList m_workerThreads; ///< List of worker threads to create }; diff --git a/Code/Framework/AzCore/AzCore/Math/Aabb.cpp b/Code/Framework/AzCore/AzCore/Math/Aabb.cpp index 80b7b88659..f9786c312b 100644 --- a/Code/Framework/AzCore/AzCore/Math/Aabb.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Aabb.cpp @@ -94,7 +94,7 @@ namespace AZ behaviorContext->Class() ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) ->Attribute(AZ::Script::Attributes::Module, "math") - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly) ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) ->Attribute(AZ::Script::Attributes::GenericConstructorOverride, &AabbDefaultConstructor) ->Property("min", &Aabb::GetMin, &Aabb::SetMin) @@ -112,46 +112,46 @@ namespace AZ ->Method("GetCenter", &Aabb::GetCenter) ->Method("Set", &Aabb::Set) ->Attribute(AZ::Script::Attributes::MethodOverride, &AabbSetGeneric) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly) ->Method("CreateFromObb", &Aabb::CreateFromObb) ->Method("GetXExtent", &Aabb::GetXExtent) ->Method("GetYExtent", &Aabb::GetYExtent) ->Method("GetZExtent", &Aabb::GetZExtent) ->Method("GetAsSphere", &Aabb::GetAsSphere, nullptr, "() -> Vector3(center) and float(radius)") ->Attribute(AZ::Script::Attributes::MethodOverride, &AabbGetAsSphereMultipleReturn) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly) ->Method("Contains", &Aabb::Contains, nullptr, "const Vector3& or const Aabb&") ->Attribute(AZ::Script::Attributes::MethodOverride, &AabbContainsGeneric) ->Method("ContainsVector3", &Aabb::Contains, nullptr, "const Vector3&") ->Attribute(AZ::Script::Attributes::Ignore, 0) // ignore for script since we already got the generic contains above ->Method("Overlaps", &Aabb::Overlaps) ->Method("Expand", &Aabb::Expand) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly) ->Method("GetExpanded", &Aabb::GetExpanded) ->Method("AddPoint", &Aabb::AddPoint) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly) ->Method("AddAabb", &Aabb::AddAabb) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly) ->Method("GetDistance", &Aabb::GetDistance) ->Method("GetClamped", &Aabb::GetClamped) ->Method("Clamp", &Aabb::Clamp) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly) ->Method("SetNull", &Aabb::SetNull) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly) ->Method("Translate", &Aabb::Translate) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly) ->Method("GetTranslated", &Aabb::GetTranslated) ->Method("GetSurfaceArea", &Aabb::GetSurfaceArea) ->Method("GetTransformedObb", static_cast(&Aabb::GetTransformedObb)) ->Method("GetTransformedAabb", static_cast(&Aabb::GetTransformedAabb)) ->Method("ApplyTransform", &Aabb::ApplyTransform) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly) ->Method("Clone", [](const Aabb& rhs) -> Aabb { return rhs; }) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly) ->Method("IsFinite", &Aabb::IsFinite) ->Method("Equal", &Aabb::operator==) ->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Equal) - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All); + ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly); } } diff --git a/Code/Framework/AzCore/AzCore/Math/Color.cpp b/Code/Framework/AzCore/AzCore/Math/Color.cpp index 8a24b4ed7a..0339771076 100644 --- a/Code/Framework/AzCore/AzCore/Math/Color.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Color.cpp @@ -237,7 +237,7 @@ namespace AZ behaviorContext->Class()-> Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)-> Attribute(AZ::Script::Attributes::Module, "math")-> - Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)-> + Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)-> Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)-> Constructor()-> Constructor()-> diff --git a/Code/Framework/AzCore/AzCore/Math/Crc.h b/Code/Framework/AzCore/AzCore/Math/Crc.h index 9ba2a83139..c71339e92e 100644 --- a/Code/Framework/AzCore/AzCore/Math/Crc.h +++ b/Code/Framework/AzCore/AzCore/Math/Crc.h @@ -16,7 +16,7 @@ // // When AZ_CRC("My string") is used by default it will map to AZ::Crc32("My string"). // We do have a pro-processor program which will precompute the crc for you and -// transform that macro to AZ_CRC("My string",0xabcdef00) this will expand to just 0xabcdef00. +// transform that macro to AZ_CRC("My string", 0x18fbd270) this will expand to just 0x18fbd270. // This will remove completely the "My string" from your executable, it will add it to a database and so on. // WHen you want to update the string, just change the string. // If you don't run the precompile step the code should still run fine, except it will be slower, @@ -24,7 +24,7 @@ // a constant expression. // For example // switch(id) { -// case AZ_CRC("My string",0xabcdef00): {} break; // this will compile fine +// case AZ_CRC("My string",0x18fbd270): {} break; // this will compile fine // case AZ_CRC("My string"): {} break; // this will cause "error C2051: case expression not constant" // } // So it's you choice what you do, depending on your needs. diff --git a/Code/Framework/AzCore/AzCore/Math/Geometry2DUtils.cpp b/Code/Framework/AzCore/AzCore/Math/Geometry2DUtils.cpp index 47a7e0a2db..912794a518 100644 --- a/Code/Framework/AzCore/AzCore/Math/Geometry2DUtils.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Geometry2DUtils.cpp @@ -8,138 +8,135 @@ #include -namespace AZ +namespace AZ::Geometry2DUtils { - namespace Geometry2DUtils + float ShortestDistanceSqPointSegment(const Vector2& point, const Vector2& segmentStart, const Vector2& segmentEnd, + float epsilon) { - float ShortestDistanceSqPointSegment(const Vector2& point, const Vector2& segmentStart, const Vector2& segmentEnd, - float epsilon) + const AZ::Vector2 segmentVector = segmentEnd - segmentStart; + + // check if the line degenerates to a point + const float segmentLengthSq = segmentVector.GetLengthSq(); + if (segmentLengthSq < epsilon * epsilon) { - const AZ::Vector2 segmentVector = segmentEnd - segmentStart; - - // check if the line degenerates to a point - const float segmentLengthSq = segmentVector.GetLengthSq(); - if (segmentLengthSq < epsilon * epsilon) - { - return (point - segmentStart).GetLengthSq(); - } - - // if the point projects on to the line segment then the shortest distance is the perpendicular - const float projection = (point - segmentStart).Dot(segmentVector); - if (projection >= 0.0f && projection <= segmentLengthSq) - { - const Vector2 perpendicular = (point - segmentStart - projection / segmentLengthSq * segmentVector); - return perpendicular.GetLengthSq(); - } - - // otherwise the point must be closest to one of the end points of the segment - return GetMin( - (point - segmentStart).GetLengthSq(), - (point - segmentEnd).GetLengthSq()); + return (point - segmentStart).GetLengthSq(); } - float Signed2DTriangleArea(const Vector2& a, const Vector2& b, const Vector2& c) + // if the point projects on to the line segment then the shortest distance is the perpendicular + const float projection = (point - segmentStart).Dot(segmentVector); + if (projection >= 0.0f && projection <= segmentLengthSq) { - return 0.5f * ((a.GetX() - c.GetX()) * (b.GetY() - c.GetY()) - (a.GetY() - c.GetY()) * (b.GetX() - c.GetX())); + const Vector2 perpendicular = (point - segmentStart - projection / segmentLengthSq * segmentVector); + return perpendicular.GetLengthSq(); } - float ShortestDistanceSqSegmentSegment( - const Vector2& segment1Start, const Vector2& segment1End, - const Vector2& segment2Start, const Vector2& segment2End) + // otherwise the point must be closest to one of the end points of the segment + return GetMin( + (point - segmentStart).GetLengthSq(), + (point - segmentEnd).GetLengthSq()); + } + + float Signed2DTriangleArea(const Vector2& a, const Vector2& b, const Vector2& c) + { + return 0.5f * ((a.GetX() - c.GetX()) * (b.GetY() - c.GetY()) - (a.GetY() - c.GetY()) * (b.GetX() - c.GetX())); + } + + float ShortestDistanceSqSegmentSegment( + const Vector2& segment1Start, const Vector2& segment1End, + const Vector2& segment2Start, const Vector2& segment2End) + { + // if the segments cross, then the distance is zero + + // if the two ends of segment 2 are on different sides of segment 1, then these two triangles will have + // different winding orders (see Real-Time Collision Detection, Christer Ericson, ISBN 978-1558607323, + // Chapter 5.1.9.1) + const float area1 = Signed2DTriangleArea(segment1Start, segment1End, segment2End); + const float area2 = Signed2DTriangleArea(segment1Start, segment1End, segment2Start); + if (area1 * area2 < 0.0f) { - // if the segments cross, then the distance is zero - - // if the two ends of segment 2 are on different sides of segment 1, then these two triangles will have - // different winding orders (see Real-Time Collision Detection, Christer Ericson, ISBN 978-1558607323, - // Chapter 5.1.9.1) - const float area1 = Signed2DTriangleArea(segment1Start, segment1End, segment2End); - const float area2 = Signed2DTriangleArea(segment1Start, segment1End, segment2Start); - if (area1 * area2 < 0.0f) + // similarly we can check if the two ends of segment 1 are on different sides of segment 2 + const float area3 = Signed2DTriangleArea(segment2Start, segment2End, segment1Start); + const float area4 = area3 + area2 - area1; + if (area3 * area4 < 0.0f) { - // similarly we can check if the two ends of segment 1 are on different sides of segment 2 - const float area3 = Signed2DTriangleArea(segment2Start, segment2End, segment1Start); - const float area4 = area3 + area2 - area1; - if (area3 * area4 < 0.0f) - { - return 0.0f; - } + return 0.0f; } - - // otherwise the shortest distance must be between one of the segment end points and the other segment - return GetMin( - GetMin( - ShortestDistanceSqPointSegment(segment1Start, segment2Start, segment2End), - ShortestDistanceSqPointSegment(segment1End, segment2Start, segment2End)), - GetMin( - ShortestDistanceSqPointSegment(segment2Start, segment1Start, segment1End), - ShortestDistanceSqPointSegment(segment2End, segment1Start, segment1End)) - ); } - bool IsSimplePolygon(const AZStd::vector& vertices, float epsilon) + // otherwise the shortest distance must be between one of the segment end points and the other segment + return GetMin( + GetMin( + ShortestDistanceSqPointSegment(segment1Start, segment2Start, segment2End), + ShortestDistanceSqPointSegment(segment1End, segment2Start, segment2End)), + GetMin( + ShortestDistanceSqPointSegment(segment2Start, segment1Start, segment1End), + ShortestDistanceSqPointSegment(segment2End, segment1Start, segment1End)) + ); + } + + bool IsSimplePolygon(const AZStd::vector& vertices, float epsilon) + { + // note that this implementation is quadratic in the number of vertices + // if it becomes a bottleneck, there are approaches which are O(n log n), e.g. the Bentley-Ottmann algorithm + + const size_t vertexCount = vertices.size(); + + if (vertexCount < 3) { - // note that this implementation is quadratic in the number of vertices - // if it becomes a bottleneck, there are approaches which are O(n log n), e.g. the Bentley-Ottmann algorithm - - const size_t vertexCount = vertices.size(); - - if (vertexCount < 3) - { - return false; - } - - if (vertexCount == 3) - { - return true; - } - - const float epsilonSq = epsilon * epsilon; - - for (size_t i = 0; i < vertexCount; ++i) - { - // make it easy to nicely wrap indices - const size_t safeIndex = i + vertexCount; - - const size_t endIndex = (safeIndex - 1) % vertexCount; - const size_t beginIndex = (safeIndex + 2) % vertexCount; - - for (size_t j = beginIndex; j != endIndex; j = (j + 1) % vertexCount) - { - const float distSq = ShortestDistanceSqSegmentSegment( - vertices[i], - vertices[(i + 1) % vertexCount], - vertices[j], - vertices[(j + 1) % vertexCount] - ); - - if (distSq < epsilonSq) - { - return false; - } - } - } + return false; + } + if (vertexCount == 3) + { return true; } - bool IsConvex(const AZStd::vector& vertices) + const float epsilonSq = epsilon * epsilon; + + for (size_t i = 0; i < vertexCount; ++i) { - const size_t vertexCount = vertices.size(); + // make it easy to nicely wrap indices + const size_t safeIndex = i + vertexCount; - if (vertexCount < 3) - { - return false; - } + const size_t endIndex = (safeIndex - 1) % vertexCount; + const size_t beginIndex = (safeIndex + 2) % vertexCount; - for (size_t i = 0; i < vertexCount; ++i) + for (size_t j = beginIndex; j != endIndex; j = (j + 1) % vertexCount) { - if (Signed2DTriangleArea(vertices[i], vertices[(i + 1) % vertexCount], vertices[(i + 2) % vertexCount]) < 0.0f) + const float distSq = ShortestDistanceSqSegmentSegment( + vertices[i], + vertices[(i + 1) % vertexCount], + vertices[j], + vertices[(j + 1) % vertexCount] + ); + + if (distSq < epsilonSq) { return false; } } - - return true; } - } // namespace Geometry2DUtils -} // namespace AZ + + return true; + } + + bool IsConvex(const AZStd::vector& vertices) + { + const size_t vertexCount = vertices.size(); + + if (vertexCount < 3) + { + return false; + } + + for (size_t i = 0; i < vertexCount; ++i) + { + if (Signed2DTriangleArea(vertices[i], vertices[(i + 1) % vertexCount], vertices[(i + 2) % vertexCount]) < 0.0f) + { + return false; + } + } + + return true; + } +} // namespace AZ::Geometry2DUtils diff --git a/Code/Framework/AzCore/AzCore/Math/Matrix3x3.cpp b/Code/Framework/AzCore/AzCore/Math/Matrix3x3.cpp index 02afcab523..46d4843546 100644 --- a/Code/Framework/AzCore/AzCore/Math/Matrix3x3.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Matrix3x3.cpp @@ -260,7 +260,7 @@ namespace AZ behaviorContext->Class()-> Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Common)-> Attribute(Script::Attributes::Module, "math")-> - Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> + Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)-> Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)-> Attribute(AZ::Script::Attributes::ConstructorOverride, &Internal::Matrix3x3ScriptConstructor)-> Attribute(Script::Attributes::GenericConstructorOverride, &Internal::Matrix3x3DefaultConstructor)-> diff --git a/Code/Framework/AzCore/AzCore/Math/Matrix3x4.cpp b/Code/Framework/AzCore/AzCore/Math/Matrix3x4.cpp index 6d79c4c068..78da9e76d2 100644 --- a/Code/Framework/AzCore/AzCore/Math/Matrix3x4.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Matrix3x4.cpp @@ -351,7 +351,8 @@ namespace AZ ->Method("CreateFromMatrix3x3AndTranslation", &Matrix3x4::CreateFromMatrix3x3AndTranslation) ->Method("CreateScale", &Matrix3x4::CreateScale) ->Method("CreateDiagonal", &Matrix3x4::CreateDiagonal) - ->Method("CreateTranslation", &Matrix3x4::CreateTranslation); + ->Method("CreateTranslation", &Matrix3x4::CreateTranslation) + ->Method("UnsafeCreateFromMatrix4x4", &Matrix3x4::UnsafeCreateFromMatrix4x4); } } diff --git a/Code/Framework/AzCore/AzCore/Math/Matrix3x4.h b/Code/Framework/AzCore/AzCore/Math/Matrix3x4.h index 60a88df686..25653627ea 100644 --- a/Code/Framework/AzCore/AzCore/Math/Matrix3x4.h +++ b/Code/Framework/AzCore/AzCore/Math/Matrix3x4.h @@ -90,6 +90,9 @@ namespace AZ //! Constructs from a Matrix3x3 and a translation. static Matrix3x4 CreateFromMatrix3x3AndTranslation(const Matrix3x3& matrix3x3, const Vector3& translation); + //! Constructs from a Matrix4x4. + static Matrix3x4 UnsafeCreateFromMatrix4x4(const Matrix4x4& matrix4x4); + //! Constructs from a Transform. static Matrix3x4 CreateFromTransform(const Transform& transform); @@ -227,7 +230,7 @@ namespace AZ Matrix3x4& operator+=(const Matrix3x4& rhs); //! @} - //! Operator for matrix-matrix substraction. + //! Operator for matrix-matrix subtraction. //! @{ [[nodiscard]] Matrix3x4 operator-(const Matrix3x4& rhs) const; Matrix3x4& operator-=(const Matrix3x4& rhs); @@ -266,6 +269,9 @@ namespace AZ //! Post-multiplies the matrix by a vector, using only the 3x3 part of the matrix. [[nodiscard]] Vector3 TransformVector(const Vector3& rhs) const; + //! Post-multiplies the matrix by a point, using the rotation and translation part of the matrix. + [[nodiscard]] Vector3 TransformPoint(const Vector3& rhs) const; + //! Gets the result of transposing the 3x3 part of the matrix, setting the translation part to zero. [[nodiscard]] Matrix3x4 GetTranspose() const; diff --git a/Code/Framework/AzCore/AzCore/Math/Matrix3x4.inl b/Code/Framework/AzCore/AzCore/Math/Matrix3x4.inl index 2f127221ab..d1367cd26c 100644 --- a/Code/Framework/AzCore/AzCore/Math/Matrix3x4.inl +++ b/Code/Framework/AzCore/AzCore/Math/Matrix3x4.inl @@ -203,6 +203,16 @@ namespace AZ } + AZ_MATH_INLINE Matrix3x4 Matrix3x4::UnsafeCreateFromMatrix4x4(const Matrix4x4& matrix4x4) + { + Matrix3x4 result; + result.SetRow(0, matrix4x4.GetRow(0)); + result.SetRow(1, matrix4x4.GetRow(1)); + result.SetRow(2, matrix4x4.GetRow(2)); + return result; + } + + AZ_MATH_INLINE Matrix3x4 Matrix3x4::CreateScale(const Vector3& scale) { return CreateDiagonal(scale); @@ -609,6 +619,12 @@ namespace AZ } + AZ_MATH_INLINE Vector3 Matrix3x4::TransformPoint(const Vector3& rhs) const + { + return Multiply3x3(rhs) + GetTranslation(); + } + + AZ_MATH_INLINE Matrix3x4 Matrix3x4::GetTranspose() const { Matrix3x4 result; diff --git a/Code/Framework/AzCore/AzCore/Math/Matrix4x4.cpp b/Code/Framework/AzCore/AzCore/Math/Matrix4x4.cpp index ccfba0fc4e..6a5784690d 100644 --- a/Code/Framework/AzCore/AzCore/Math/Matrix4x4.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Matrix4x4.cpp @@ -280,7 +280,7 @@ namespace AZ behaviorContext->Class()-> Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Common)-> Attribute(Script::Attributes::Module, "math")-> - Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> + Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)-> Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)-> Attribute(Script::Attributes::GenericConstructorOverride, &Internal::Matrix4x4DefaultConstructor)-> Property("basisX", &Matrix4x4::GetBasisX, &Matrix4x4::SetBasisX)-> diff --git a/Code/Framework/AzCore/AzCore/Math/Obb.cpp b/Code/Framework/AzCore/AzCore/Math/Obb.cpp index 8b223a6e43..03e3722309 100644 --- a/Code/Framework/AzCore/AzCore/Math/Obb.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Obb.cpp @@ -69,7 +69,7 @@ namespace AZ if (behaviorContext) { behaviorContext->Class()-> - Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> + Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)-> Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)-> Attribute(Script::Attributes::GenericConstructorOverride, &Internal::ObbDefaultConstructor)-> Property("position", &Obb::GetPosition, &Obb::SetPosition)-> diff --git a/Code/Framework/AzCore/AzCore/Math/Plane.cpp b/Code/Framework/AzCore/AzCore/Math/Plane.cpp index cde82c4bed..dced975543 100644 --- a/Code/Framework/AzCore/AzCore/Math/Plane.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Plane.cpp @@ -142,7 +142,7 @@ namespace AZ if (behaviorContext) { behaviorContext->Class()-> - Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)-> + Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)-> Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)-> Attribute(AZ::Script::Attributes::GenericConstructorOverride, &Internal::PlaneDefaultConstructor)-> Method("ToString", &Internal::PlaneToString)-> diff --git a/Code/Framework/AzCore/AzCore/Math/Quaternion.cpp b/Code/Framework/AzCore/AzCore/Math/Quaternion.cpp index 8b5d9e9321..e06cd184ad 100644 --- a/Code/Framework/AzCore/AzCore/Math/Quaternion.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Quaternion.cpp @@ -170,7 +170,7 @@ namespace AZ behaviorContext->Class()-> Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)-> Attribute(AZ::Script::Attributes::Module, "math")-> - Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)-> + Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)-> Constructor()-> Constructor()-> Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)-> diff --git a/Code/Framework/AzCore/AzCore/Math/Sfmt.cpp b/Code/Framework/AzCore/AzCore/Math/Sfmt.cpp index 66404a9b3f..5f4dc9e5df 100644 --- a/Code/Framework/AzCore/AzCore/Math/Sfmt.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Sfmt.cpp @@ -14,28 +14,26 @@ #include // for memset -namespace AZ +namespace AZ::SfmtInternal { - namespace SfmtInternal - { - static const int N32 = N * 4; - static const int N64 = N * 2; - static const int POS1 = 122; - static const int SL1 = 18; - static const int SR1 = 11; - static const int SL2 = 1; - static const int SR2 = 1; - static const unsigned int MSK1 = 0xdfffffefU; - static const unsigned int MSK2 = 0xddfecb7fU; - static const unsigned int MSK3 = 0xbffaffffU; - static const unsigned int MSK4 = 0xbffffff6U; - static const unsigned int PARITY1 = 0x00000001U; - static const unsigned int PARITY2 = 0x00000000U; - static const unsigned int PARITY3 = 0x00000000U; - static const unsigned int PARITY4 = 0x13c9e684U; + static const int N32 = N * 4; + static const int N64 = N * 2; + static const int POS1 = 122; + static const int SL1 = 18; + static const int SR1 = 11; + static const int SL2 = 1; + static const int SR2 = 1; + static const unsigned int MSK1 = 0xdfffffefU; + static const unsigned int MSK2 = 0xddfecb7fU; + static const unsigned int MSK3 = 0xbffaffffU; + static const unsigned int MSK4 = 0xbffffff6U; + static const unsigned int PARITY1 = 0x00000001U; + static const unsigned int PARITY2 = 0x00000000U; + static const unsigned int PARITY3 = 0x00000000U; + static const unsigned int PARITY4 = 0x13c9e684U; - /** a parity check vector which certificate the period of 2^{MEXP} */ - static unsigned int parity[4] = {PARITY1, PARITY2, PARITY3, PARITY4}; + /** a parity check vector which certificate the period of 2^{MEXP} */ + static unsigned int parity[4] = {PARITY1, PARITY2, PARITY3, PARITY4}; #ifdef ONLY64 # define idxof(_i) (_i ^ 1) @@ -45,259 +43,257 @@ namespace AZ #if AZ_TRAIT_USE_PLATFORM_SIMD_SSE - /** - * This function represents the recursion formula. - * @param a a 128-bit part of the internal state array - * @param b a 128-bit part of the internal state array - * @param c a 128-bit part of the internal state array - * @param d a 128-bit part of the internal state array - * @param mask 128-bit mask - * @return output - */ - AZ_FORCE_INLINE static Simd::Vec4::Int32Type simd_recursion(Simd::Vec4::Int32Type* a, Simd::Vec4::Int32Type* b, Simd::Vec4::Int32Type c, Simd::Vec4::Int32Type d, Simd::Vec4::Int32Type mask) + /** + * This function represents the recursion formula. + * @param a a 128-bit part of the internal state array + * @param b a 128-bit part of the internal state array + * @param c a 128-bit part of the internal state array + * @param d a 128-bit part of the internal state array + * @param mask 128-bit mask + * @return output + */ + AZ_FORCE_INLINE static Simd::Vec4::Int32Type simd_recursion(Simd::Vec4::Int32Type* a, Simd::Vec4::Int32Type* b, Simd::Vec4::Int32Type c, Simd::Vec4::Int32Type d, Simd::Vec4::Int32Type mask) + { + Simd::Vec4::Int32Type v, x, y, z; + x = *a; + y = _mm_srli_epi32(*b, SR1); + z = _mm_srli_si128(c, SR2); + v = _mm_slli_epi32(d, SL1); + z = Simd::Vec4::Xor(z, x); + z = Simd::Vec4::Xor(z, v); + x = _mm_slli_si128(x, SL2); + y = Simd::Vec4::And(y, mask); + z = Simd::Vec4::Xor(z, x); + z = Simd::Vec4::Xor(z, y); + return z; + } + + /** + * This function fills the internal state array with pseudorandom + * integers. + */ + inline void gen_rand_all(Sfmt& g) + { + int i; + Simd::Vec4::Int32Type r, r1, r2, mask; + mask = Simd::Vec4::LoadImmediate((int32_t)MSK4, (int32_t)MSK3, (int32_t)MSK2, (int32_t)MSK1); + + r1 = Simd::Vec4::LoadAligned((const int32_t*)&g.m_sfmt[N - 2].si); + r2 = Simd::Vec4::LoadAligned((const int32_t*)&g.m_sfmt[N - 1].si); + for (i = 0; i < N - POS1; i++) { - Simd::Vec4::Int32Type v, x, y, z; - x = *a; - y = _mm_srli_epi32(*b, SR1); - z = _mm_srli_si128(c, SR2); - v = _mm_slli_epi32(d, SL1); - z = Simd::Vec4::Xor(z, x); - z = Simd::Vec4::Xor(z, v); - x = _mm_slli_si128(x, SL2); - y = Simd::Vec4::And(y, mask); - z = Simd::Vec4::Xor(z, x); - z = Simd::Vec4::Xor(z, y); - return z; + r = simd_recursion(&g.m_sfmt[i].si, &g.m_sfmt[i + POS1].si, r1, r2, mask); + Simd::Vec4::StoreAligned((int32_t*)&g.m_sfmt[i].si, r); + r1 = r2; + r2 = r; } - - /** - * This function fills the internal state array with pseudorandom - * integers. - */ - inline void gen_rand_all(Sfmt& g) + for (; i < N; i++) { - int i; - Simd::Vec4::Int32Type r, r1, r2, mask; - mask = Simd::Vec4::LoadImmediate((int32_t)MSK4, (int32_t)MSK3, (int32_t)MSK2, (int32_t)MSK1); - - r1 = Simd::Vec4::LoadAligned((const int32_t*)&g.m_sfmt[N - 2].si); - r2 = Simd::Vec4::LoadAligned((const int32_t*)&g.m_sfmt[N - 1].si); - for (i = 0; i < N - POS1; i++) - { - r = simd_recursion(&g.m_sfmt[i].si, &g.m_sfmt[i + POS1].si, r1, r2, mask); - Simd::Vec4::StoreAligned((int32_t*)&g.m_sfmt[i].si, r); - r1 = r2; - r2 = r; - } - for (; i < N; i++) - { - r = simd_recursion(&g.m_sfmt[i].si, &g.m_sfmt[i + POS1 - N].si, r1, r2, mask); - Simd::Vec4::StoreAligned((int32_t*)&g.m_sfmt[i].si, r); - r1 = r2; - r2 = r; - } + r = simd_recursion(&g.m_sfmt[i].si, &g.m_sfmt[i + POS1 - N].si, r1, r2, mask); + Simd::Vec4::StoreAligned((int32_t*)&g.m_sfmt[i].si, r); + r1 = r2; + r2 = r; } + } - /** - * This function fills the user-specified array with pseudorandom - * integers. - * - * @param array an 128-bit array to be filled by pseudorandom numbers. - * @param size number of 128-bit pesudorandom numbers to be generated. - */ - inline void gen_rand_array(Sfmt& g, w128_t* array, int size) + /** + * This function fills the user-specified array with pseudorandom + * integers. + * + * @param array an 128-bit array to be filled by pseudorandom numbers. + * @param size number of 128-bit pesudorandom numbers to be generated. + */ + inline void gen_rand_array(Sfmt& g, w128_t* array, int size) + { + int i, j; + Simd::Vec4::Int32Type r, r1, r2, mask; + mask = Simd::Vec4::LoadImmediate((int32_t)MSK4, (int32_t)MSK3, (int32_t)MSK2, (int32_t)MSK1); + + r1 = Simd::Vec4::LoadAligned((const int32_t*)&g.m_sfmt[N - 2].si); + r2 = Simd::Vec4::LoadAligned((const int32_t*)&g.m_sfmt[N - 1].si); + for (i = 0; i < N - POS1; i++) { - int i, j; - Simd::Vec4::Int32Type r, r1, r2, mask; - mask = Simd::Vec4::LoadImmediate((int32_t)MSK4, (int32_t)MSK3, (int32_t)MSK2, (int32_t)MSK1); - - r1 = Simd::Vec4::LoadAligned((const int32_t*)&g.m_sfmt[N - 2].si); - r2 = Simd::Vec4::LoadAligned((const int32_t*)&g.m_sfmt[N - 1].si); - for (i = 0; i < N - POS1; i++) - { - r = simd_recursion(&g.m_sfmt[i].si, &g.m_sfmt[i + POS1].si, r1, r2, mask); - Simd::Vec4::StoreAligned((int32_t*)&array[i].si, r); - r1 = r2; - r2 = r; - } - for (; i < N; i++) - { - r = simd_recursion(&g.m_sfmt[i].si, &array[i + POS1 - N].si, r1, r2, mask); - Simd::Vec4::StoreAligned((int32_t*)&array[i].si, r); - r1 = r2; - r2 = r; - } - /* main loop */ - for (; i < size - N; i++) - { - r = simd_recursion(&array[i - N].si, &array[i + POS1 - N].si, r1, r2, mask); - Simd::Vec4::StoreAligned((int32_t*)&array[i].si, r); - r1 = r2; - r2 = r; - } - for (j = 0; j < 2 * N - size; j++) - { - r = Simd::Vec4::LoadAligned((const int32_t*)&array[j + size - N].si); - Simd::Vec4::StoreAligned((int32_t*)&g.m_sfmt[j].si, r); - } - for (; i < size; i++) - { - r = simd_recursion(&array[i - N].si, &array[i + POS1 - N].si, r1, r2, mask); - Simd::Vec4::StoreAligned((int32_t*)&array[i].si, r); - Simd::Vec4::StoreAligned((int32_t*)&g.m_sfmt[j++].si, r); - r1 = r2; - r2 = r; - } + r = simd_recursion(&g.m_sfmt[i].si, &g.m_sfmt[i + POS1].si, r1, r2, mask); + Simd::Vec4::StoreAligned((int32_t*)&array[i].si, r); + r1 = r2; + r2 = r; } + for (; i < N; i++) + { + r = simd_recursion(&g.m_sfmt[i].si, &array[i + POS1 - N].si, r1, r2, mask); + Simd::Vec4::StoreAligned((int32_t*)&array[i].si, r); + r1 = r2; + r2 = r; + } + /* main loop */ + for (; i < size - N; i++) + { + r = simd_recursion(&array[i - N].si, &array[i + POS1 - N].si, r1, r2, mask); + Simd::Vec4::StoreAligned((int32_t*)&array[i].si, r); + r1 = r2; + r2 = r; + } + for (j = 0; j < 2 * N - size; j++) + { + r = Simd::Vec4::LoadAligned((const int32_t*)&array[j + size - N].si); + Simd::Vec4::StoreAligned((int32_t*)&g.m_sfmt[j].si, r); + } + for (; i < size; i++) + { + r = simd_recursion(&array[i - N].si, &array[i + POS1 - N].si, r1, r2, mask); + Simd::Vec4::StoreAligned((int32_t*)&array[i].si, r); + Simd::Vec4::StoreAligned((int32_t*)&g.m_sfmt[j++].si, r); + r1 = r2; + r2 = r; + } + } #else - inline void rshift128(w128_t* out, w128_t const* in, int shift) + inline void rshift128(w128_t* out, w128_t const* in, int shift) + { + AZ::u64 th, tl, oh, ol; + #ifdef ONLY64 + th = ((AZ::u64)in->u[2] << 32) | ((AZ::u64)in->u[3]); + tl = ((AZ::u64)in->u[0] << 32) | ((AZ::u64)in->u[1]); + + oh = th >> (shift * 8); + ol = tl >> (shift * 8); + ol |= th << (64 - shift * 8); + out->u[0] = (AZ::u32)(ol >> 32); + out->u[1] = (AZ::u32)ol; + out->u[2] = (AZ::u32)(oh >> 32); + out->u[3] = (AZ::u32)oh; +#else + th = ((AZ::u64)in->u[3] << 32) | ((AZ::u64)in->u[2]); + tl = ((AZ::u64)in->u[1] << 32) | ((AZ::u64)in->u[0]); + + oh = th >> (shift * 8); + ol = tl >> (shift * 8); + ol |= th << (64 - shift * 8); + out->u[1] = (AZ::u32)(ol >> 32); + out->u[0] = (AZ::u32)ol; + out->u[3] = (AZ::u32)(oh >> 32); + out->u[2] = (AZ::u32)oh; +#endif + } + + inline void lshift128(w128_t* out, w128_t const* in, int shift) + { + AZ::u64 th, tl, oh, ol; +#ifdef ONLY64 + th = ((AZ::u64)in->u[2] << 32) | ((AZ::u64)in->u[3]); + tl = ((AZ::u64)in->u[0] << 32) | ((AZ::u64)in->u[1]); + + oh = th << (shift * 8); + ol = tl << (shift * 8); + oh |= tl >> (64 - shift * 8); + out->u[0] = (AZ::u32)(ol >> 32); + out->u[1] = (AZ::u32)ol; + out->u[2] = (AZ::u32)(oh >> 32); + out->u[3] = (AZ::u32)oh; +#else + th = ((AZ::u64)in->u[3] << 32) | ((AZ::u64)in->u[2]); + tl = ((AZ::u64)in->u[1] << 32) | ((AZ::u64)in->u[0]); + + oh = th << (shift * 8); + ol = tl << (shift * 8); + oh |= tl >> (64 - shift * 8); + out->u[1] = (AZ::u32)(ol >> 32); + out->u[0] = (AZ::u32)ol; + out->u[3] = (AZ::u32)(oh >> 32); + out->u[2] = (AZ::u32)oh; +#endif + } + + inline void do_recursion(w128_t* r, w128_t* a, w128_t* b, w128_t* c, w128_t* d) + { + w128_t x; + w128_t y; + lshift128(&x, a, SL2); + rshift128(&y, c, SR2); +#ifdef ONLY64 + r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SR1) & MSK2) ^ y.u[0] ^ (d->u[0] << SL1); + r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SR1) & MSK1) ^ y.u[1] ^ (d->u[1] << SL1); + r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SR1) & MSK4) ^ y.u[2] ^ (d->u[2] << SL1); + r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SR1) & MSK3) ^ y.u[3] ^ (d->u[3] << SL1); +#else + r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SR1) & MSK1) ^ y.u[0] ^ (d->u[0] << SL1); + r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SR1) & MSK2) ^ y.u[1] ^ (d->u[1] << SL1); + r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SR1) & MSK3) ^ y.u[2] ^ (d->u[2] << SL1); + r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SR1) & MSK4) ^ y.u[3] ^ (d->u[3] << SL1); +#endif + } + /** + * This function fills the internal state array with pseudorandom + * integers. + */ + inline void gen_rand_all(Sfmt& g) + { + int i; + w128_t* r1, * r2; + + r1 = &g.m_sfmt[N - 2]; + r2 = &g.m_sfmt[N - 1]; + for (i = 0; i < N - POS1; i++) { - AZ::u64 th, tl, oh, ol; - #ifdef ONLY64 - th = ((AZ::u64)in->u[2] << 32) | ((AZ::u64)in->u[3]); - tl = ((AZ::u64)in->u[0] << 32) | ((AZ::u64)in->u[1]); - - oh = th >> (shift * 8); - ol = tl >> (shift * 8); - ol |= th << (64 - shift * 8); - out->u[0] = (AZ::u32)(ol >> 32); - out->u[1] = (AZ::u32)ol; - out->u[2] = (AZ::u32)(oh >> 32); - out->u[3] = (AZ::u32)oh; - #else - th = ((AZ::u64)in->u[3] << 32) | ((AZ::u64)in->u[2]); - tl = ((AZ::u64)in->u[1] << 32) | ((AZ::u64)in->u[0]); - - oh = th >> (shift * 8); - ol = tl >> (shift * 8); - ol |= th << (64 - shift * 8); - out->u[1] = (AZ::u32)(ol >> 32); - out->u[0] = (AZ::u32)ol; - out->u[3] = (AZ::u32)(oh >> 32); - out->u[2] = (AZ::u32)oh; - #endif + do_recursion(&g.m_sfmt[i], &g.m_sfmt[i], &g.m_sfmt[i + POS1], r1, r2); + r1 = r2; + r2 = &g.m_sfmt[i]; } - - inline void lshift128(w128_t* out, w128_t const* in, int shift) + for (; i < N; i++) { - AZ::u64 th, tl, oh, ol; - #ifdef ONLY64 - th = ((AZ::u64)in->u[2] << 32) | ((AZ::u64)in->u[3]); - tl = ((AZ::u64)in->u[0] << 32) | ((AZ::u64)in->u[1]); - - oh = th << (shift * 8); - ol = tl << (shift * 8); - oh |= tl >> (64 - shift * 8); - out->u[0] = (AZ::u32)(ol >> 32); - out->u[1] = (AZ::u32)ol; - out->u[2] = (AZ::u32)(oh >> 32); - out->u[3] = (AZ::u32)oh; - #else - th = ((AZ::u64)in->u[3] << 32) | ((AZ::u64)in->u[2]); - tl = ((AZ::u64)in->u[1] << 32) | ((AZ::u64)in->u[0]); - - oh = th << (shift * 8); - ol = tl << (shift * 8); - oh |= tl >> (64 - shift * 8); - out->u[1] = (AZ::u32)(ol >> 32); - out->u[0] = (AZ::u32)ol; - out->u[3] = (AZ::u32)(oh >> 32); - out->u[2] = (AZ::u32)oh; - #endif + do_recursion(&g.m_sfmt[i], &g.m_sfmt[i], &g.m_sfmt[i + POS1 - N], r1, r2); + r1 = r2; + r2 = &g.m_sfmt[i]; } + } - inline void do_recursion(w128_t* r, w128_t* a, w128_t* b, w128_t* c, w128_t* d) + /** + * This function fills the user-specified array with pseudorandom + * integers. + * + * @param array an 128-bit array to be filled by pseudorandom numbers. + * @param size number of 128-bit pseudorandom numbers to be generated. + */ + inline void gen_rand_array(Sfmt& g, w128_t* array, int size) + { + int i, j; + w128_t* r1, * r2; + + r1 = &g.m_sfmt[N - 2]; + r2 = &g.m_sfmt[N - 1]; + for (i = 0; i < N - POS1; i++) { - w128_t x; - w128_t y; - lshift128(&x, a, SL2); - rshift128(&y, c, SR2); - #ifdef ONLY64 - r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SR1) & MSK2) ^ y.u[0] ^ (d->u[0] << SL1); - r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SR1) & MSK1) ^ y.u[1] ^ (d->u[1] << SL1); - r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SR1) & MSK4) ^ y.u[2] ^ (d->u[2] << SL1); - r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SR1) & MSK3) ^ y.u[3] ^ (d->u[3] << SL1); - #else - r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SR1) & MSK1) ^ y.u[0] ^ (d->u[0] << SL1); - r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SR1) & MSK2) ^ y.u[1] ^ (d->u[1] << SL1); - r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SR1) & MSK3) ^ y.u[2] ^ (d->u[2] << SL1); - r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SR1) & MSK4) ^ y.u[3] ^ (d->u[3] << SL1); - #endif + do_recursion(&array[i], &g.m_sfmt[i], &g.m_sfmt[i + POS1], r1, r2); + r1 = r2; + r2 = &array[i]; } - /** - * This function fills the internal state array with pseudorandom - * integers. - */ - inline void gen_rand_all(Sfmt& g) + for (; i < N; i++) { - int i; - w128_t* r1, * r2; - - r1 = &g.m_sfmt[N - 2]; - r2 = &g.m_sfmt[N - 1]; - for (i = 0; i < N - POS1; i++) - { - do_recursion(&g.m_sfmt[i], &g.m_sfmt[i], &g.m_sfmt[i + POS1], r1, r2); - r1 = r2; - r2 = &g.m_sfmt[i]; - } - for (; i < N; i++) - { - do_recursion(&g.m_sfmt[i], &g.m_sfmt[i], &g.m_sfmt[i + POS1 - N], r1, r2); - r1 = r2; - r2 = &g.m_sfmt[i]; - } + do_recursion(&array[i], &g.m_sfmt[i], &array[i + POS1 - N], r1, r2); + r1 = r2; + r2 = &array[i]; } - - /** - * This function fills the user-specified array with pseudorandom - * integers. - * - * @param array an 128-bit array to be filled by pseudorandom numbers. - * @param size number of 128-bit pseudorandom numbers to be generated. - */ - inline void gen_rand_array(Sfmt& g, w128_t* array, int size) + for (; i < size - N; i++) { - int i, j; - w128_t* r1, * r2; - - r1 = &g.m_sfmt[N - 2]; - r2 = &g.m_sfmt[N - 1]; - for (i = 0; i < N - POS1; i++) - { - do_recursion(&array[i], &g.m_sfmt[i], &g.m_sfmt[i + POS1], r1, r2); - r1 = r2; - r2 = &array[i]; - } - for (; i < N; i++) - { - do_recursion(&array[i], &g.m_sfmt[i], &array[i + POS1 - N], r1, r2); - r1 = r2; - r2 = &array[i]; - } - for (; i < size - N; i++) - { - do_recursion(&array[i], &array[i - N], &array[i + POS1 - N], r1, r2); - r1 = r2; - r2 = &array[i]; - } - for (j = 0; j < 2 * N - size; j++) - { - g.m_sfmt[j] = array[j + size - N]; - } - for (; i < size; i++, j++) - { - do_recursion(&array[i], &array[i - N], &array[i + POS1 - N], r1, r2); - r1 = r2; - r2 = &array[i]; - g.m_sfmt[j] = array[i]; - } + do_recursion(&array[i], &array[i - N], &array[i + POS1 - N], r1, r2); + r1 = r2; + r2 = &array[i]; } + for (j = 0; j < 2 * N - size; j++) + { + g.m_sfmt[j] = array[j + size - N]; + } + for (; i < size; i++, j++) + { + do_recursion(&array[i], &array[i - N], &array[i + POS1 - N], r1, r2); + r1 = r2; + r2 = &array[i]; + g.m_sfmt[j] = array[i]; + } + } #endif - } // SmftInternal -} // AZ - +} // namespace AZ::SfmtInternal using namespace AZ; diff --git a/Code/Framework/AzCore/AzCore/Math/Spline.cpp b/Code/Framework/AzCore/AzCore/Math/Spline.cpp index b4fc6dc1f8..71f428fe99 100644 --- a/Code/Framework/AzCore/AzCore/Math/Spline.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Spline.cpp @@ -98,7 +98,7 @@ namespace AZ if (BehaviorContext* behaviorContext = azrtti_cast(context)) { behaviorContext->Class()-> - Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::List)-> + Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)-> Constructor()-> Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)-> Attribute(Script::Attributes::ConstructorOverride, &Internal::SplineAddressScriptConstructor)-> @@ -118,7 +118,7 @@ namespace AZ Property("rayDistance", [](RaySplineQueryResult* thisPtr) { return thisPtr->m_rayDistance; }, nullptr); behaviorContext->Class()-> - Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::List)-> + Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)-> Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::RuntimeOwn)-> Method("GetNearestAddressRay", &Spline::GetNearestAddressRay)-> Method("GetNearestAddressPosition", &Spline::GetNearestAddressPosition)-> diff --git a/Code/Framework/AzCore/AzCore/Math/Transform.cpp b/Code/Framework/AzCore/AzCore/Math/Transform.cpp index 1701820bae..4e80d0e29a 100644 --- a/Code/Framework/AzCore/AzCore/Math/Transform.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Transform.cpp @@ -293,7 +293,7 @@ namespace AZ behaviorContext->Class()-> Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Common)-> Attribute(Script::Attributes::Module, "math")-> - Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> + Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)-> Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)-> Attribute(Script::Attributes::GenericConstructorOverride, &Internal::TransformDefaultConstructor)-> Constructor()-> @@ -312,35 +312,35 @@ namespace AZ Attribute(Script::Attributes::Ignore, 0)-> // ignore for script since we already got the generic multiply above Method("MultiplyTransform", &Transform::operator*)-> Attribute(Script::Attributes::Ignore, 0)-> // ignore for script since we already got the generic multiply above - Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> + Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)-> Method("Equal", &Transform::operator==)-> Attribute(Script::Attributes::Operator, Script::Attributes::OperatorType::Equal)-> - Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> + Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)-> Method("Clone", [](const Transform& rhs) -> Transform { return rhs; })-> - Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> + Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)-> Method("GetTranslation", &Transform::GetTranslation)-> Method("GetBasisAndTranslation", &Transform::GetBasisAndTranslation)-> Attribute(Script::Attributes::MethodOverride, &Internal::TransformGetBasisAndTranslationMultipleReturn)-> - Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> + Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)-> Method("TransformVector", &Transform::TransformVector)-> - Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> + Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)-> Method("SetTranslation", &Transform::SetTranslation)-> Attribute(Script::Attributes::MethodOverride, &Internal::TransformSetTranslationGeneric)-> - Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> + Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)-> Method("GetRotation", &Transform::GetRotation)-> Method("SetRotation", &Transform::SetRotation)-> Method("GetUniformScale", &Transform::GetUniformScale)-> Method("SetUniformScale", &Transform::SetUniformScale)-> Method("ExtractUniformScale", &Transform::ExtractUniformScale)-> - Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> + Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)-> Method("MultiplyByUniformScale", &Transform::MultiplyByUniformScale)-> Method("GetInverse", &Transform::GetInverse)-> Method("Invert", &Transform::Invert)-> - Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> + Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)-> Method("IsOrthogonal", &Transform::IsOrthogonal, behaviorContext->MakeDefaultValues(Constants::Tolerance))-> Method("GetOrthogonalized", &Transform::GetOrthogonalized)-> Method("Orthogonalize", &Transform::Orthogonalize)-> - Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> + Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)-> Method("IsClose", &Transform::IsClose, behaviorContext->MakeDefaultValues(Constants::Tolerance))-> Method("IsFinite", &Transform::IsFinite)-> Method("CreateIdentity", &Transform::CreateIdentity)-> diff --git a/Code/Framework/AzCore/AzCore/Math/Uuid.h b/Code/Framework/AzCore/AzCore/Math/Uuid.h index 6b77e7ec3e..eeacb04877 100644 --- a/Code/Framework/AzCore/AzCore/Math/Uuid.h +++ b/Code/Framework/AzCore/AzCore/Math/Uuid.h @@ -45,7 +45,7 @@ namespace AZ static constexpr int ValidUuidStringLength = 32; /// Number of characters (data only, no extra formatting) in a valid UUID string static const size_t MaxStringBuffer = 39; /// 32 Uuid + 4 dashes + 2 brackets + 1 terminate - Uuid() {} + Uuid() = default; Uuid(const char* string, size_t stringLength = 0) { *this = CreateString(string, stringLength); } static Uuid CreateNull(); diff --git a/Code/Framework/AzCore/AzCore/Math/Vector2.cpp b/Code/Framework/AzCore/AzCore/Math/Vector2.cpp index 0e236b8b1f..d2d3d2a58d 100644 --- a/Code/Framework/AzCore/AzCore/Math/Vector2.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Vector2.cpp @@ -191,7 +191,7 @@ namespace AZ behaviorContext->Class()-> Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Common)-> Attribute(Script::Attributes::Module, "math")-> - Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> + Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)-> Constructor()-> Constructor()-> Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)-> diff --git a/Code/Framework/AzCore/AzCore/Math/Vector2.h b/Code/Framework/AzCore/AzCore/Math/Vector2.h index 7c37d74135..91eb61d6c8 100644 --- a/Code/Framework/AzCore/AzCore/Math/Vector2.h +++ b/Code/Framework/AzCore/AzCore/Math/Vector2.h @@ -30,7 +30,7 @@ namespace AZ Vector2() = default; - Vector2(const Vector2& v); + Vector2(const Vector2& v) = default; //! Constructs vector with all components set to the same specified value. explicit Vector2(float x); diff --git a/Code/Framework/AzCore/AzCore/Math/Vector2.inl b/Code/Framework/AzCore/AzCore/Math/Vector2.inl index 086be2bbc3..9691dd3a5c 100644 --- a/Code/Framework/AzCore/AzCore/Math/Vector2.inl +++ b/Code/Framework/AzCore/AzCore/Math/Vector2.inl @@ -8,13 +8,6 @@ namespace AZ { - AZ_MATH_INLINE Vector2::Vector2(const Vector2& v) - : m_value(v.m_value) - { - ; - } - - AZ_MATH_INLINE Vector2::Vector2(float x) : m_value(Simd::Vec2::Splat(x)) { diff --git a/Code/Framework/AzCore/AzCore/Math/Vector3.cpp b/Code/Framework/AzCore/AzCore/Math/Vector3.cpp index d82aa32f7d..04041c6604 100644 --- a/Code/Framework/AzCore/AzCore/Math/Vector3.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Vector3.cpp @@ -206,7 +206,7 @@ namespace AZ behaviorContext->Class()-> Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Common)-> Attribute(Script::Attributes::Module, "math")-> - Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> + Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)-> Constructor()-> Constructor()-> Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)-> diff --git a/Code/Framework/AzCore/AzCore/Math/Vector4.cpp b/Code/Framework/AzCore/AzCore/Math/Vector4.cpp index 20126a8b64..1dc6d5d1fe 100644 --- a/Code/Framework/AzCore/AzCore/Math/Vector4.cpp +++ b/Code/Framework/AzCore/AzCore/Math/Vector4.cpp @@ -215,7 +215,7 @@ namespace AZ behaviorContext->Class()-> Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Common)-> Attribute(Script::Attributes::Module, "math")-> - Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)-> + Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)-> Constructor()-> Constructor()-> Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)-> diff --git a/Code/Framework/AzCore/AzCore/Memory/AllocationRecords.cpp b/Code/Framework/AzCore/AzCore/Memory/AllocationRecords.cpp index b55b2db768..680d93501a 100644 --- a/Code/Framework/AzCore/AzCore/Memory/AllocationRecords.cpp +++ b/Code/Framework/AzCore/AzCore/Memory/AllocationRecords.cpp @@ -9,10 +9,10 @@ #include #include #include -#include #include #include +#include #include @@ -26,30 +26,25 @@ using namespace AZ::Debug; // AllocationRecords // [9/16/2009] //========================================================================= -AllocationRecords::AllocationRecords(unsigned char stackRecordLevels, bool isMemoryGuard, bool isMarkUnallocatedMemory, const char* allocatorName) +AllocationRecords::AllocationRecords(unsigned char stackRecordLevels, [[maybe_unused]] bool isMemoryGuard, bool isMarkUnallocatedMemory, const char* allocatorName) : m_mode(AllocatorManager::Instance().m_defaultTrackingRecordMode) , m_isAutoIntegrityCheck(false) , m_isMarkUnallocatedMemory(isMarkUnallocatedMemory) , m_saveNames(false) , m_decodeImmediately(false) , m_numStackLevels(stackRecordLevels) +#if defined(ENABLE_MEMORY_GUARD) + , m_memoryGuardSize(isMemoryGuard ? sizeof(Debug::GuardValue) : 0) +#else + , m_memoryGuardSize(0) +#endif , m_requestedAllocs(0) , m_requestedBytes(0) , m_requestedBytesPeak(0) , m_allocatorName(allocatorName) { -#if defined(ENABLE_MEMORY_GUARD) - m_memoryGuardSize = isMemoryGuard ? sizeof(Debug::GuardValue) : 0; -#else - (void)isMemoryGuard; - m_memoryGuardSize = 0; -#endif -#if AZ_TRAIT_OS_HAS_CRITICAL_SECTION_SPIN_COUNT - SetCriticalSectionSpinCount(DrillerEBusMutex::GetMutex().native_handle(), 4000); -#endif - // preallocate some buckets - //m_records.rehash(20000); -}; + +} //========================================================================= // ~AllocationRecords @@ -73,7 +68,7 @@ AllocationRecords::~AllocationRecords() void AllocationRecords::lock() { - DrillerEBusMutex::GetMutex().lock(); + m_recordsMutex.lock(); } //========================================================================= @@ -82,7 +77,7 @@ AllocationRecords::lock() //========================================================================= bool AllocationRecords::try_lock() { - return DrillerEBusMutex::GetMutex().try_lock(); + return m_recordsMutex.try_lock(); } //========================================================================= @@ -92,7 +87,7 @@ bool AllocationRecords::try_lock() void AllocationRecords::unlock() { - DrillerEBusMutex::GetMutex().unlock(); + m_recordsMutex.unlock(); } //========================================================================= @@ -117,7 +112,7 @@ AllocationRecords::RegisterAllocation(void* address, size_t byteSize, size_t ali { if (m_isAutoIntegrityCheck) { - IntegrityCheckNoLock(); + IntegrityCheck(); } AZ_Assert(byteSize>sizeof(Debug::GuardValue), "Did you forget to add the extra MemoryGuardSize() bytes?"); @@ -125,7 +120,11 @@ AllocationRecords::RegisterAllocation(void* address, size_t byteSize, size_t ali new(reinterpret_cast(address)+byteSize) Debug::GuardValue(); } - Debug::AllocationRecordsType::pair_iter_bool iterBool = m_records.insert_key(address); + Debug::AllocationRecordsType::pair_iter_bool iterBool; + { + AZStd::scoped_lock lock(m_recordsMutex); + iterBool = m_records.insert_key(address); + } if (!iterBool.second) { @@ -210,7 +209,15 @@ AllocationRecords::RegisterAllocation(void* address, size_t byteSize, size_t ali // statistics m_requestedBytes += byteSize; - m_requestedBytesPeak = AZStd::GetMax(m_requestedBytesPeak, m_requestedBytes); + + size_t currentRequestedBytePeak; + size_t newRequestedBytePeak; + do + { + currentRequestedBytePeak = m_requestedBytesPeak.load(std::memory_order::memory_order_relaxed); + newRequestedBytePeak = AZStd::GetMax(currentRequestedBytePeak, m_requestedBytes.load(std::memory_order::memory_order_relaxed)); + } while (!m_requestedBytesPeak.compare_exchange_weak(currentRequestedBytePeak, newRequestedBytePeak)); + ++m_requestedAllocs; return &ai; @@ -220,8 +227,7 @@ AllocationRecords::RegisterAllocation(void* address, size_t byteSize, size_t ali // UnregisterAllocation // [9/11/2009] //========================================================================= -void -AllocationRecords::UnregisterAllocation(void* address, size_t byteSize, size_t alignment, AllocationInfo* info) +void AllocationRecords::UnregisterAllocation(void* address, size_t byteSize, size_t alignment, AllocationInfo* info) { if (m_mode == RECORD_NO_RECORDS) { @@ -232,24 +238,38 @@ AllocationRecords::UnregisterAllocation(void* address, size_t byteSize, size_t a return; } - Debug::AllocationRecordsType::iterator iter = m_records.find(address); - - // We cannot assert if an allocation does not exist because our allocators start up way before the driller is started and the Allocator Records would be created. - // It is currently impossible to actually track all allocations that happen before a certain point - //AZ_Assert(iter!=m_records.end(), "Could not find address 0x%p in the allocator!", address); - if (iter == m_records.end()) + AllocationInfo allocationInfo; { - return; + AZStd::scoped_lock lock(m_recordsMutex); + Debug::AllocationRecordsType::iterator iter = m_records.find(address); + // We cannot assert if an allocation does not exist because allocations may have been made before tracking was enabled. + // It is currently impossible to actually track all allocations that happen before a certain point + // AZ_Assert(iter!=m_records.end(), "Could not find address 0x%p in the allocator!", address); + if (iter == m_records.end()) + { + return; + } + allocationInfo = iter->second; + m_records.erase(iter); + + // try to be more aggressive and keep the memory footprint low. + // \todo store the load factor at the last rehash to avoid unnecessary rehash + if (m_records.load_factor() < 0.9f) + { + m_records.rehash(0); + } } - AllocatorManager::Instance().DebugBreak(address, iter->second); + + + AllocatorManager::Instance().DebugBreak(address, allocationInfo); (void)byteSize; (void)alignment; - AZ_Assert(byteSize==0||byteSize==iter->second.m_byteSize, "Mismatched byteSize at deallocation! You supplied an invalid value!"); - AZ_Assert(alignment==0||alignment==iter->second.m_alignment, "Mismatched alignment at deallocation! You supplied an invalid value!"); + AZ_Assert(byteSize==0||byteSize==allocationInfo.m_byteSize, "Mismatched byteSize at deallocation! You supplied an invalid value!"); + AZ_Assert(alignment==0||alignment==allocationInfo.m_alignment, "Mismatched alignment at deallocation! You supplied an invalid value!"); // statistics - m_requestedBytes -= iter->second.m_byteSize; + m_requestedBytes -= allocationInfo.m_byteSize; #if defined(ENABLE_MEMORY_GUARD) // memory guard @@ -258,18 +278,18 @@ AllocationRecords::UnregisterAllocation(void* address, size_t byteSize, size_t a if (m_isAutoIntegrityCheck) { // full integrity check - IntegrityCheckNoLock(); + IntegrityCheck(); } else { // check current allocation - char* guardAddress = reinterpret_cast(address)+iter->second.m_byteSize; + char* guardAddress = reinterpret_cast(address)+allocationInfo.m_byteSize; Debug::GuardValue* guard = reinterpret_cast(guardAddress); if (!guard->Validate()) { AZ_Printf("Memory", "Memory stomp located at address %p, part of allocation:", guardAddress); PrintAllocationsCB printAlloc(true); - printAlloc(address, iter->second, m_numStackLevels); + printAlloc(address, allocationInfo, m_numStackLevels); AZ_Assert(false, "MEMORY STOMP DETECTED!!!"); } guard->~GuardValue(); @@ -278,33 +298,26 @@ AllocationRecords::UnregisterAllocation(void* address, size_t byteSize, size_t a #endif // delete allocation record - if (iter->second.m_namesBlock) + if (allocationInfo.m_namesBlock) { - m_records.get_allocator().deallocate(iter->second.m_namesBlock, iter->second.m_namesBlockSize, 1); - iter->second.m_namesBlock = nullptr; - iter->second.m_namesBlockSize = 0; - iter->second.m_name = nullptr; - iter->second.m_fileName = nullptr; + m_records.get_allocator().deallocate(allocationInfo.m_namesBlock, allocationInfo.m_namesBlockSize, 1); + allocationInfo.m_namesBlock = nullptr; + allocationInfo.m_namesBlockSize = 0; + allocationInfo.m_name = nullptr; + allocationInfo.m_fileName = nullptr; } - if (iter->second.m_stackFrames) + if (allocationInfo.m_stackFrames) { - m_records.get_allocator().deallocate(iter->second.m_stackFrames, sizeof(AZ::Debug::StackFrame)*m_numStackLevels, 1); - iter->second.m_stackFrames = nullptr; + m_records.get_allocator().deallocate(allocationInfo.m_stackFrames, sizeof(AZ::Debug::StackFrame)*m_numStackLevels, 1); + allocationInfo.m_stackFrames = nullptr; } if (info) { - *info = iter->second; + *info = allocationInfo; } - m_records.erase(iter); - // try to be more aggressive and keep the memory footprint low. - // \todo store the load factor at the last rehash to avoid unnecessary rehash - if (m_records.load_factor()<0.9f) - { - m_records.rehash(0); - } // if requested set memory to a specific value. if (m_isMarkUnallocatedMemory) @@ -325,9 +338,14 @@ AllocationRecords::ResizeAllocation(void* address, size_t newSize) return; } - Debug::AllocationRecordsType::iterator iter = m_records.find(address); - AZ_Assert(iter!=m_records.end(), "Could not find address 0x%p in the allocator!", address); - AllocatorManager::Instance().DebugBreak(address, iter->second); + AllocationInfo* allocationInfo; + { + AZStd::scoped_lock lock(m_recordsMutex); + Debug::AllocationRecordsType::iterator iter = m_records.find(address); + AZ_Assert(iter != m_records.end(), "Could not find address 0x%p in the allocator!", address); + allocationInfo = &iter->second; + } + AllocatorManager::Instance().DebugBreak(address, *allocationInfo); #if defined(ENABLE_MEMORY_GUARD) if (m_memoryGuardSize == sizeof(Debug::GuardValue)) @@ -335,12 +353,12 @@ AllocationRecords::ResizeAllocation(void* address, size_t newSize) if (m_isAutoIntegrityCheck) { // full integrity check - IntegrityCheckNoLock(); + IntegrityCheck(); } else { // check memory guard - char* guardAddress = reinterpret_cast(address)+iter->second.m_byteSize; + char* guardAddress = reinterpret_cast(address) + allocationInfo->m_byteSize; Debug::GuardValue* guard = reinterpret_cast(guardAddress); if (!guard->Validate()) { @@ -358,13 +376,19 @@ AllocationRecords::ResizeAllocation(void* address, size_t newSize) #endif // statistics - m_requestedBytes -= iter->second.m_byteSize; + m_requestedBytes -= allocationInfo->m_byteSize; m_requestedBytes += newSize; - m_requestedBytesPeak = AZStd::GetMax(m_requestedBytesPeak, m_requestedBytes); + size_t currentRequestedBytePeak; + size_t newRequestedBytePeak; + do + { + currentRequestedBytePeak = m_requestedBytesPeak.load(std::memory_order::memory_order_relaxed); + newRequestedBytePeak = AZStd::GetMax(currentRequestedBytePeak, m_requestedBytes.load(std::memory_order::memory_order_relaxed)); + } while (!m_requestedBytesPeak.compare_exchange_weak(currentRequestedBytePeak, newRequestedBytePeak)); ++m_requestedAllocs; // update allocation size - iter->second.m_byteSize = newSize; + allocationInfo->m_byteSize = newSize; } //========================================================================= @@ -374,21 +398,20 @@ AllocationRecords::ResizeAllocation(void* address, size_t newSize) void AllocationRecords::SetMode(Mode mode) { - DrillerEBusMutex::GetMutex().lock(); - - if (mode==RECORD_NO_RECORDS) + if (mode == RECORD_NO_RECORDS) { - m_records.clear(); + { + AZStd::scoped_lock lock(m_recordsMutex); + m_records.clear(); + } m_requestedBytes = 0; m_requestedBytesPeak = 0; m_requestedAllocs = 0; } - AZ_Warning("Memory", m_mode!=RECORD_NO_RECORDS||mode==RECORD_NO_RECORDS, "Records recording was disabled and now it's enabled! You might get assert when you free memory, if a you have allocations which were not recorded!"); + AZ_Warning("Memory", m_mode != RECORD_NO_RECORDS || mode == RECORD_NO_RECORDS, "Records recording was disabled and now it's enabled! You might get assert when you free memory, if a you have allocations which were not recorded!"); m_mode = mode; - - DrillerEBusMutex::GetMutex().unlock(); } //========================================================================= @@ -398,11 +421,14 @@ AllocationRecords::SetMode(Mode mode) void AllocationRecords::EnumerateAllocations(AllocationInfoCBType cb) { - DrillerEBusMutex::GetMutex().lock(); // enumerate all allocations and stop if requested. // Since allocations can change during the iteration (code that prints out the records could allocate, which will // mutate m_records), we are going to make a copy and iterate the copy. - const Debug::AllocationRecordsType recordsCopy = m_records; + Debug::AllocationRecordsType recordsCopy; + { + AZStd::scoped_lock lock(m_recordsMutex); + recordsCopy = m_records; + } for (Debug::AllocationRecordsType::const_iterator iter = recordsCopy.begin(); iter != recordsCopy.end(); ++iter) { if (!cb(iter->first, iter->second, m_numStackLevels)) @@ -410,7 +436,6 @@ AllocationRecords::EnumerateAllocations(AllocationInfoCBType cb) break; } } - DrillerEBusMutex::GetMutex().unlock(); } //========================================================================= @@ -420,38 +445,29 @@ AllocationRecords::EnumerateAllocations(AllocationInfoCBType cb) void AllocationRecords::IntegrityCheck() const { +#if defined(ENABLE_MEMORY_GUARD) if (m_memoryGuardSize == sizeof(Debug::GuardValue)) { - DrillerEBusMutex::GetMutex().lock(); - - IntegrityCheckNoLock(); - - DrillerEBusMutex::GetMutex().unlock(); - } -} - -//========================================================================= -// IntegrityCheckNoLock -// [9/13/2011] -//========================================================================= -void -AllocationRecords::IntegrityCheckNoLock() const -{ -#if defined(ENABLE_MEMORY_GUARD) - for (Debug::AllocationRecordsType::const_iterator iter = m_records.begin(); iter != m_records.end(); ++iter) - { - // check memory guard - const char* guardAddress = reinterpret_cast(iter->first)+ iter->second.m_byteSize; - if (!reinterpret_cast(guardAddress)->Validate()) + Debug::AllocationRecordsType recordsCopy; { - // We have to turn off the integrity check at this point if we want to succesfully report the memory - // stomp we just found. If we don't turn this off, the printf just winds off the stack as each memory - // allocation done therein recurses this same code. - *const_cast(&m_isAutoIntegrityCheck) = false; - AZ_Printf("Memory", "Memory stomp located at address %p, part of allocation:", guardAddress); - PrintAllocationsCB printAlloc(true); - printAlloc(iter->first, iter->second, m_numStackLevels); - AZ_Error("Memory", false, "MEMORY STOMP DETECTED!!!"); + AZStd::scoped_lock lock(m_recordsMutex); + recordsCopy = m_records; + } + for (Debug::AllocationRecordsType::const_iterator iter = recordsCopy.begin(); iter != recordsCopy.end(); ++iter) + { + // check memory guard + const char* guardAddress = reinterpret_cast(iter->first)+ iter->second.m_byteSize; + if (!reinterpret_cast(guardAddress)->Validate()) + { + // We have to turn off the integrity check at this point if we want to succesfully report the memory + // stomp we just found. If we don't turn this off, the printf just winds off the stack as each memory + // allocation done therein recurses this same code. + *const_cast(&m_isAutoIntegrityCheck) = false; + AZ_Printf("Memory", "Memory stomp located at address %p, part of allocation:", guardAddress); + PrintAllocationsCB printAlloc(true); + printAlloc(iter->first, iter->second, m_numStackLevels); + AZ_Error("Memory", false, "MEMORY STOMP DETECTED!!!"); + } } } #endif diff --git a/Code/Framework/AzCore/AzCore/Memory/AllocationRecords.h b/Code/Framework/AzCore/AzCore/Memory/AllocationRecords.h index 709e16174d..3f998b0bbd 100644 --- a/Code/Framework/AzCore/AzCore/Memory/AllocationRecords.h +++ b/Code/Framework/AzCore/AzCore/Memory/AllocationRecords.h @@ -120,10 +120,9 @@ namespace AZ */ class AllocationRecords { - friend class MemoryDriller; + public: AZ_CLASS_ALLOCATOR(AllocationRecords, OSAllocator, 0); - public: enum Mode : int { RECORD_NO_RECORDS, ///< Never record any information. @@ -178,7 +177,7 @@ namespace AZ /// Returns peak of requested memory. IMPORTANT: This is user requested memory! Any allocator overhead is NOT included. size_t RequestedBytesPeak() const { return m_requestedBytesPeak; } /// Reset the peak allocation to the current requested memory. - void ResetPeakBytes() { m_requestedBytesPeak = m_requestedBytes; } + void ResetPeakBytes() { m_requestedBytesPeak.store(m_requestedBytes); } /// Return requested user bytes. IMPORTANT: This is user requested memory! Any allocator overhead is NOT included. size_t RequestedBytes() const { return m_requestedBytes; } /// Returns total number of requested allocations. @@ -186,8 +185,6 @@ namespace AZ const char* GetAllocatorName() const { return m_allocatorName; } - protected: - // @{ Allocation tracking management - we assume this functions are called with the lock locked. const AllocationInfo* RegisterAllocation(void* address, size_t byteSize, size_t alignment, const char* name, const char* fileName, int lineNum, unsigned int stackSuppressCount); void UnregisterAllocation(void* address, size_t byteSize, size_t alignment, AllocationInfo* info); @@ -195,9 +192,9 @@ namespace AZ void ResizeAllocation(void* address, size_t newSize); // @} - void IntegrityCheckNoLock() const; - + protected: Debug::AllocationRecordsType m_records; + AZStd::spin_mutex m_recordsMutex; Mode m_mode; bool m_isAutoIntegrityCheck; bool m_isMarkUnallocatedMemory; ///< True if we want to set value 0xcd in unallocated memory. @@ -205,9 +202,9 @@ namespace AZ bool m_decodeImmediately; unsigned char m_numStackLevels; unsigned int m_memoryGuardSize; - size_t m_requestedAllocs; - size_t m_requestedBytes; - size_t m_requestedBytesPeak; + AZStd::atomic m_requestedAllocs; + AZStd::atomic m_requestedBytes; + AZStd::atomic m_requestedBytesPeak; const char* m_allocatorName; }; diff --git a/Code/Framework/AzCore/AzCore/Memory/AllocatorBase.cpp b/Code/Framework/AzCore/AzCore/Memory/AllocatorBase.cpp index e450f12bcf..61e883bb4e 100644 --- a/Code/Framework/AzCore/AzCore/Memory/AllocatorBase.cpp +++ b/Code/Framework/AzCore/AzCore/Memory/AllocatorBase.cpp @@ -8,7 +8,6 @@ #include #include -#include using namespace AZ; @@ -74,16 +73,24 @@ void AllocatorBase::PostCreate() } } -#if PLATFORM_MEMORY_INSTRUMENTATION_ENABLED - m_platformMemoryInstrumentationGroupId = AZ::PlatformMemoryInstrumentation::GetNextGroupId(); - AZ::PlatformMemoryInstrumentation::RegisterGroup(m_platformMemoryInstrumentationGroupId, GetDescription(), AZ::PlatformMemoryInstrumentation::m_groupRoot); -#endif + const auto debugConfig = GetDebugConfig(); + if (!debugConfig.m_excludeFromDebugging) + { + SetRecords(aznew Debug::AllocationRecords((unsigned char)debugConfig.m_stackRecordLevels, debugConfig.m_usesMemoryGuards, debugConfig.m_marksUnallocatedMemory, GetName())); + } m_isReady = true; } void AllocatorBase::PreDestroy() { + Debug::AllocationRecords* allocatorRecords = GetRecords(); + if(allocatorRecords) + { + delete allocatorRecords; + SetRecords(nullptr); + } + if (m_registrationEnabled && AZ::AllocatorManager::IsReady()) { AllocatorManager::Instance().UnRegisterAllocator(this); @@ -130,11 +137,11 @@ void AllocatorBase::ProfileAllocation(void* ptr, size_t byteSize, size_t alignme if (m_isProfilingActive) { -#if PLATFORM_MEMORY_INSTRUMENTATION_ENABLED - AZ::PlatformMemoryInstrumentation::Alloc(ptr, byteSize, 0, m_platformMemoryInstrumentationGroupId); -#else - EBUS_EVENT(AZ::Debug::MemoryDrillerBus, RegisterAllocation, this, ptr, byteSize, alignment, name, fileName, lineNum, suppressStackRecord); -#endif + auto records = GetRecords(); + if (records) + { + records->RegisterAllocation(ptr, byteSize, alignment, name, fileName, lineNum, suppressStackRecord + 1); + } } } @@ -142,37 +149,25 @@ void AllocatorBase::ProfileDeallocation(void* ptr, size_t byteSize, size_t align { if (m_isProfilingActive) { -#if PLATFORM_MEMORY_INSTRUMENTATION_ENABLED - AZ::PlatformMemoryInstrumentation::Free(ptr); -#else - EBUS_EVENT(AZ::Debug::MemoryDrillerBus, UnregisterAllocation, this, ptr, byteSize, alignment, info); -#endif + auto records = GetRecords(); + if (records) + { + records->UnregisterAllocation(ptr, byteSize, alignment, info); + } } } -void AllocatorBase::ProfileReallocationBegin(void* ptr, size_t newSize) +void AllocatorBase::ProfileReallocationBegin([[maybe_unused]] void* ptr, [[maybe_unused]] size_t newSize) { - if (m_isProfilingActive) - { -#if PLATFORM_MEMORY_INSTRUMENTATION_ENABLED - AZ::PlatformMemoryInstrumentation::ReallocBegin(ptr, newSize, m_platformMemoryInstrumentationGroupId); -#else - // Driller API intensionally not called, only End is required. - AZ_UNUSED(ptr); - AZ_UNUSED(newSize); -#endif - } } void AllocatorBase::ProfileReallocationEnd(void* ptr, void* newPtr, size_t newSize, size_t newAlignment) { if (m_isProfilingActive) { -#if PLATFORM_MEMORY_INSTRUMENTATION_ENABLED - AZ::PlatformMemoryInstrumentation::ReallocEnd(newPtr, newSize, 0); -#else - EBUS_EVENT(AZ::Debug::MemoryDrillerBus, ReallocateAllocation, this, ptr, newPtr, newSize, newAlignment); -#endif + Debug::AllocationInfo info; + ProfileDeallocation(ptr, 0, 0, &info); + ProfileAllocation(newPtr, newSize, newAlignment, info.m_name, info.m_fileName, info.m_lineNum, 0); } } @@ -185,7 +180,11 @@ void AllocatorBase::ProfileResize(void* ptr, size_t newSize) { if (newSize && m_isProfilingActive) { - EBUS_EVENT(AZ::Debug::MemoryDrillerBus, ResizeAllocation, this, ptr, newSize); + auto records = GetRecords(); + if (records) + { + records->ResizeAllocation(ptr, newSize); + } } } diff --git a/Code/Framework/AzCore/AzCore/Memory/AllocatorBase.h b/Code/Framework/AzCore/AzCore/Memory/AllocatorBase.h index 0502c25e5d..8f8a17e470 100644 --- a/Code/Framework/AzCore/AzCore/Memory/AllocatorBase.h +++ b/Code/Framework/AzCore/AzCore/Memory/AllocatorBase.h @@ -8,7 +8,6 @@ #pragma once #include -#include namespace AZ { @@ -103,16 +102,13 @@ namespace AZ const char* m_name = nullptr; const char* m_desc = nullptr; - Debug::AllocationRecords* m_records = nullptr; // Cached pointer to allocation records. Works together with the MemoryDriller. + Debug::AllocationRecords* m_records = nullptr; // Cached pointer to allocation records size_t m_memoryGuardSize = 0; bool m_isLazilyCreated = false; bool m_isProfilingActive = false; bool m_isReady = false; bool m_canBeOverridden = true; bool m_registrationEnabled = true; -#if PLATFORM_MEMORY_INSTRUMENTATION_ENABLED - uint16_t m_platformMemoryInstrumentationGroupId = 0; -#endif }; namespace Internal { diff --git a/Code/Framework/AzCore/AzCore/Memory/AllocatorManager.cpp b/Code/Framework/AzCore/AzCore/Memory/AllocatorManager.cpp index 3c09b4cae6..70ac813972 100644 --- a/Code/Framework/AzCore/AzCore/Memory/AllocatorManager.cpp +++ b/Code/Framework/AzCore/AzCore/Memory/AllocatorManager.cpp @@ -14,50 +14,47 @@ #include #include #include -#include #include #include #include -using namespace AZ; - #if !defined(RELEASE) && !defined(AZCORE_MEMORY_ENABLE_OVERRIDES) # define AZCORE_MEMORY_ENABLE_OVERRIDES #endif -namespace AZ +namespace AZ::Internal { - namespace Internal + struct AMStringHasher { - struct AMStringHasher + using is_transparent = void; + template + size_t operator()(const ConvertibleToStringView& key) { - using is_transparent = void; - template - size_t operator()(const ConvertibleToStringView& key) - { - return AZStd::hash{}(key); - } - }; - using AMString = AZStd::basic_string, AZStdIAllocator>; - using AllocatorNameMap = AZStd::unordered_map, AZStdIAllocator>; - using AllocatorRemappings = AZStd::unordered_map, AZStdIAllocator>; + return AZStd::hash{}(key); + } + }; + using AMString = AZStd::basic_string, AZStdIAllocator>; + using AllocatorNameMap = AZStd::unordered_map, AZStdIAllocator>; + using AllocatorRemappings = AZStd::unordered_map, AZStdIAllocator>; - // For allocators that are created before we have an environment, we keep some module-local data for them so that we can register them - // properly once the environment is attached. - struct PreEnvironmentAttachData - { - static const int MAX_UNREGISTERED_ALLOCATORS = 8; - AZStd::mutex m_mutex; - MallocSchema m_mallocSchema; - IAllocator* m_unregisteredAllocators[MAX_UNREGISTERED_ALLOCATORS]; - int m_unregisteredAllocatorCount = 0; - }; + // For allocators that are created before we have an environment, we keep some module-local data for them so that we can register them + // properly once the environment is attached. + struct PreEnvironmentAttachData + { + static const int MAX_UNREGISTERED_ALLOCATORS = 8; + AZStd::mutex m_mutex; + MallocSchema m_mallocSchema; + IAllocator* m_unregisteredAllocators[MAX_UNREGISTERED_ALLOCATORS]; + int m_unregisteredAllocatorCount = 0; + }; - } } -struct AZ::AllocatorManager::InternalData +namespace AZ +{ + +struct AllocatorManager::InternalData { explicit InternalData(const AZStdIAllocator& alloc) : m_allocatorMap(alloc) @@ -69,13 +66,13 @@ struct AZ::AllocatorManager::InternalData Internal::AllocatorRemappings m_remappingsReverse; }; -static AZ::EnvironmentVariable s_allocManager = nullptr; +static EnvironmentVariable s_allocManager = nullptr; static AllocatorManager* s_allocManagerDebug = nullptr; // For easier viewing in crash dumps /// Returns a module-local instance of data to use for allocators that are created before the environment is attached. -static AZ::Internal::PreEnvironmentAttachData& GetPreEnvironmentAttachData() +static Internal::PreEnvironmentAttachData& GetPreEnvironmentAttachData() { - static AZ::Internal::PreEnvironmentAttachData s_data; + static Internal::PreEnvironmentAttachData s_data; return s_data; } @@ -131,7 +128,7 @@ AllocatorManager& AllocatorManager::Instance() if (!s_allocManager) { AZ_Assert(Environment::IsReady(), "Environment must be ready before calling Instance()"); - s_allocManager = AZ::Environment::CreateVariable(AZ_CRC("AZ::AllocatorManager::s_allocManager", 0x6bdd908c)); + s_allocManager = Environment::CreateVariable(AZ_CRC_CE("AZ::AllocatorManager::s_allocManager")); // Register any allocators that were created in this module before we attached to the environment auto& data = GetPreEnvironmentAttachData(); @@ -156,9 +153,9 @@ AllocatorManager& AllocatorManager::Instance() ////////////////////////////////////////////////////////////////////////// // Create malloc schema using custom AZ_OS_MALLOC allocator. -AZ::MallocSchema* AllocatorManager::CreateMallocSchema() +MallocSchema* AllocatorManager::CreateMallocSchema() { - return static_cast(new(AZ_OS_MALLOC(sizeof(AZ::MallocSchema), alignof(AZ::MallocSchema))) AZ::MallocSchema()); + return static_cast(new(AZ_OS_MALLOC(sizeof(MallocSchema), alignof(MallocSchema))) MallocSchema()); } @@ -168,7 +165,7 @@ AZ::MallocSchema* AllocatorManager::CreateMallocSchema() //========================================================================= AllocatorManager::AllocatorManager() : m_profilingRefcount(0) - , m_mallocSchema(CreateMallocSchema(), [](AZ::MallocSchema* schema) + , m_mallocSchema(CreateMallocSchema(), [](MallocSchema* schema) { if (schema) { @@ -182,7 +179,7 @@ AllocatorManager::AllocatorManager() m_numAllocators = 0; m_isAllocatorLeaking = false; m_configurationFinalized = false; - m_defaultTrackingRecordMode = AZ::Debug::AllocationRecords::RECORD_NO_RECORDS; + m_defaultTrackingRecordMode = Debug::AllocationRecords::RECORD_NO_RECORDS; m_data = new (m_mallocSchema->Allocate(sizeof(InternalData), AZStd::alignment_of::value, 0)) InternalData(AZStdIAllocator(m_mallocSchema.get())); } @@ -217,8 +214,6 @@ AllocatorManager::RegisterAllocator(class IAllocator* alloc) #ifdef AZCORE_MEMORY_ENABLE_OVERRIDES ConfigureAllocatorOverrides(alloc); #endif - - EBUS_EVENT(Debug::MemoryDrillerBus, RegisterAllocator, alloc); } //========================================================================= @@ -321,11 +316,6 @@ AllocatorManager::UnRegisterAllocator(class IAllocator* alloc) { AZStd::lock_guard lock(m_allocatorListMutex); - if (alloc->GetRecords()) - { - EBUS_EVENT(Debug::MemoryDrillerBus, UnregisterAllocator, alloc); - } - for (int i = 0; i < m_numAllocators; ++i) { if (m_allocators[i] == alloc) @@ -411,12 +401,12 @@ AllocatorManager::RemoveOutOfMemoryListener() // [9/16/2011] //========================================================================= void -AllocatorManager::SetTrackingMode(AZ::Debug::AllocationRecords::Mode mode) +AllocatorManager::SetTrackingMode(Debug::AllocationRecords::Mode mode) { AZStd::lock_guard lock(m_allocatorListMutex); for (int i = 0; i < m_numAllocators; ++i) { - AZ::Debug::AllocationRecords* records = m_allocators[i]->GetRecords(); + Debug::AllocationRecords* records = m_allocators[i]->GetRecords(); if (records) { records->SetMode(mode); @@ -595,31 +585,31 @@ void AllocatorManager::GetAllocatorStats(size_t& allocatedBytes, size_t& capacit AZStd::lock_guard lock(m_allocatorListMutex); const int allocatorCount = GetNumAllocators(); - AZStd::unordered_map existingAllocators; - AZStd::unordered_map sourcesToAllocators; + AZStd::unordered_map existingAllocators; + AZStd::unordered_map sourcesToAllocators; // Build a mapping of original allocator sources to their allocators for (int i = 0; i < allocatorCount; ++i) { - AZ::IAllocator* allocator = GetAllocator(i); + IAllocator* allocator = GetAllocator(i); sourcesToAllocators.emplace(allocator->GetOriginalAllocationSource(), allocator); } for (int i = 0; i < allocatorCount; ++i) { - AZ::IAllocator* allocator = GetAllocator(i); - AZ::IAllocatorAllocate* source = allocator->GetAllocationSource(); - AZ::IAllocatorAllocate* originalSource = allocator->GetOriginalAllocationSource(); - AZ::IAllocatorAllocate* schema = allocator->GetSchema(); - AZ::IAllocator* alias = (source != originalSource) ? sourcesToAllocators[source] : nullptr; + IAllocator* allocator = GetAllocator(i); + IAllocatorAllocate* source = allocator->GetAllocationSource(); + IAllocatorAllocate* originalSource = allocator->GetOriginalAllocationSource(); + IAllocatorAllocate* schema = allocator->GetSchema(); + IAllocator* alias = (source != originalSource) ? sourcesToAllocators[source] : nullptr; if (schema && !alias) { // Check to see if this allocator's source maps to another allocator // Need to check both the schema and the allocator itself, as either one might be used as the alias depending on how it's implemented - AZStd::array checkAllocators = { { schema, allocator->GetAllocationSource() } }; + AZStd::array checkAllocators = { { schema, allocator->GetAllocationSource() } }; - for (AZ::IAllocatorAllocate* check : checkAllocators) + for (IAllocatorAllocate* check : checkAllocators) { auto existing = existingAllocators.emplace(check, allocator); @@ -631,7 +621,7 @@ void AllocatorManager::GetAllocatorStats(size_t& allocatedBytes, size_t& capacit } } - static const AZ::IAllocator* OS_ALLOCATOR = &AZ::AllocatorInstance::GetAllocator(); + static const IAllocator* OS_ALLOCATOR = &AllocatorInstance::GetAllocator(); size_t sourceAllocatedBytes = source->NumAllocatedBytes(); size_t sourceCapacityBytes = source->Capacity(); @@ -742,3 +732,5 @@ AllocatorManager::DebugBreak(void* address, const Debug::AllocationInfo& info) } } } + +} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Memory/AllocatorOverrideShim.cpp b/Code/Framework/AzCore/AzCore/Memory/AllocatorOverrideShim.cpp index 154d59edd3..e4928e83c5 100644 --- a/Code/Framework/AzCore/AzCore/Memory/AllocatorOverrideShim.cpp +++ b/Code/Framework/AzCore/AzCore/Memory/AllocatorOverrideShim.cpp @@ -8,223 +8,220 @@ #include -namespace AZ +namespace AZ::Internal { - namespace Internal + AllocatorOverrideShim* AllocatorOverrideShim::Create(IAllocator* owningAllocator, IAllocatorAllocate* shimAllocationSource) { - AllocatorOverrideShim* AllocatorOverrideShim::Create(IAllocator* owningAllocator, IAllocatorAllocate* shimAllocationSource) + void* memory = shimAllocationSource->Allocate(sizeof(AllocatorOverrideShim), AZStd::alignment_of::value, 0); + auto result = new (memory) AllocatorOverrideShim(owningAllocator, shimAllocationSource); + return result; + } + + void AllocatorOverrideShim::Destroy(AllocatorOverrideShim* source) + { + auto shimAllocationSource = source->m_shimAllocationSource; + source->~AllocatorOverrideShim(); + shimAllocationSource->DeAllocate(source); + } + + AllocatorOverrideShim::AllocatorOverrideShim(IAllocator* owningAllocator, IAllocatorAllocate* shimAllocationSource) + : m_owningAllocator(owningAllocator) + , m_source(owningAllocator->GetOriginalAllocationSource()) + , m_overridingSource(owningAllocator->GetOriginalAllocationSource()) + , m_shimAllocationSource(shimAllocationSource) + , m_records(typename AllocationSet::hasher(), typename AllocationSet::key_eq(), StdAllocationSrc(shimAllocationSource)) + { + } + + void AllocatorOverrideShim::SetOverride(IAllocatorAllocate* source) + { + m_overridingSource = source; + } + + IAllocatorAllocate* AllocatorOverrideShim::GetOverride() const + { + return m_overridingSource; + } + + bool AllocatorOverrideShim::IsOverridden() const + { + return m_source != m_overridingSource; + } + + bool AllocatorOverrideShim::HasOrphanedAllocations() const + { + return !m_records.empty(); + } + + void AllocatorOverrideShim::SetFinalizedConfiguration() + { + m_finalizedConfiguration = true; + } + + typename AllocatorOverrideShim::pointer_type AllocatorOverrideShim::Allocate(size_type byteSize, size_type alignment, int flags, const char* name, const char* fileName, int lineNum, unsigned int suppressStackRecord) + { + pointer_type ptr = m_overridingSource->Allocate(byteSize, alignment, flags, name, fileName, lineNum, suppressStackRecord); + + if (!IsOverridden()) { - void* memory = shimAllocationSource->Allocate(sizeof(AllocatorOverrideShim), AZStd::alignment_of::value, 0); - auto result = new (memory) AllocatorOverrideShim(owningAllocator, shimAllocationSource); - return result; + lock_type lock(m_mutex); + m_records.insert(ptr); // Record in case we need to orphan this allocation later } - void AllocatorOverrideShim::Destroy(AllocatorOverrideShim* source) + return ptr; + } + + void AllocatorOverrideShim::DeAllocate(pointer_type ptr, size_type byteSize, size_type alignment) + { + IAllocatorAllocate* source = m_overridingSource; + bool destroy = false; + { - auto shimAllocationSource = source->m_shimAllocationSource; - source->~AllocatorOverrideShim(); - shimAllocationSource->DeAllocate(source); + lock_type lock(m_mutex); + + // Check to see if this came from a prior allocation source + if (m_records.erase(ptr) && IsOverridden()) + { + source = m_source; + + if (m_records.empty() && m_finalizedConfiguration) + { + // All orphaned records are gone; we are no longer needed + m_owningAllocator->SetAllocationSource(m_overridingSource); + destroy = true; // Must destroy outside the lock + } + } } - AllocatorOverrideShim::AllocatorOverrideShim(IAllocator* owningAllocator, IAllocatorAllocate* shimAllocationSource) - : m_owningAllocator(owningAllocator) - , m_source(owningAllocator->GetOriginalAllocationSource()) - , m_overridingSource(owningAllocator->GetOriginalAllocationSource()) - , m_shimAllocationSource(shimAllocationSource) - , m_records(typename AllocationSet::hasher(), typename AllocationSet::key_eq(), StdAllocationSrc(shimAllocationSource)) + source->DeAllocate(ptr, byteSize, alignment); + + if (destroy) { + Destroy(this); + } + } + + typename AllocatorOverrideShim::size_type AllocatorOverrideShim::Resize(pointer_type ptr, size_type newSize) + { + IAllocatorAllocate* source = m_overridingSource; + + if (IsOverridden()) + { + // Determine who owns the allocation + lock_type lock(m_mutex); + + if (m_records.count(ptr)) + { + source = m_source; + } } - void AllocatorOverrideShim::SetOverride(IAllocatorAllocate* source) + size_t result = source->Resize(ptr, newSize); + + return result; + } + + typename AllocatorOverrideShim::pointer_type AllocatorOverrideShim::ReAllocate(pointer_type ptr, size_type newSize, size_type newAlignment) + { + pointer_type newPtr = nullptr; + bool useOverride = true; + bool destroy = false; + + if (IsOverridden()) { - m_overridingSource = source; + lock_type lock(m_mutex); + + if (m_records.erase(ptr)) + { + // An old allocation needs to be transferred to the new, overriding allocator. + useOverride = false; // We'll do the reallocation here + size_t oldSize = m_source->AllocationSize(ptr); + + if (newSize) + { + newPtr = m_overridingSource->Allocate(newSize, newAlignment, 0); + memcpy(newPtr, ptr, AZStd::min(newSize, oldSize)); + } + + m_source->DeAllocate(ptr, oldSize); + + if (m_records.empty() && m_finalizedConfiguration) + { + // All orphaned records are gone; we are no longer needed + m_owningAllocator->SetAllocationSource(m_overridingSource); + destroy = true; // Must destroy outside the lock + } + } } - IAllocatorAllocate* AllocatorOverrideShim::GetOverride() const + if (useOverride) { - return m_overridingSource; - } - - bool AllocatorOverrideShim::IsOverridden() const - { - return m_source != m_overridingSource; - } - - bool AllocatorOverrideShim::HasOrphanedAllocations() const - { - return !m_records.empty(); - } - - void AllocatorOverrideShim::SetFinalizedConfiguration() - { - m_finalizedConfiguration = true; - } - - typename AllocatorOverrideShim::pointer_type AllocatorOverrideShim::Allocate(size_type byteSize, size_type alignment, int flags, const char* name, const char* fileName, int lineNum, unsigned int suppressStackRecord) - { - pointer_type ptr = m_overridingSource->Allocate(byteSize, alignment, flags, name, fileName, lineNum, suppressStackRecord); + // Default behavior, we weren't deleting an old allocation + newPtr = m_overridingSource->ReAllocate(ptr, newSize, newAlignment); if (!IsOverridden()) { + // Still need to do bookkeeping if we haven't been overridden yet lock_type lock(m_mutex); - m_records.insert(ptr); // Record in case we need to orphan this allocation later - } - - return ptr; - } - - void AllocatorOverrideShim::DeAllocate(pointer_type ptr, size_type byteSize, size_type alignment) - { - IAllocatorAllocate* source = m_overridingSource; - bool destroy = false; - - { - lock_type lock(m_mutex); - - // Check to see if this came from a prior allocation source - if (m_records.erase(ptr) && IsOverridden()) - { - source = m_source; - - if (m_records.empty() && m_finalizedConfiguration) - { - // All orphaned records are gone; we are no longer needed - m_owningAllocator->SetAllocationSource(m_overridingSource); - destroy = true; // Must destroy outside the lock - } - } - } - - source->DeAllocate(ptr, byteSize, alignment); - - if (destroy) - { - Destroy(this); + m_records.erase(ptr); + m_records.insert(newPtr); } } - typename AllocatorOverrideShim::size_type AllocatorOverrideShim::Resize(pointer_type ptr, size_type newSize) + if (destroy) { - IAllocatorAllocate* source = m_overridingSource; - - if (IsOverridden()) - { - // Determine who owns the allocation - lock_type lock(m_mutex); - - if (m_records.count(ptr)) - { - source = m_source; - } - } - - size_t result = source->Resize(ptr, newSize); - - return result; - } - - typename AllocatorOverrideShim::pointer_type AllocatorOverrideShim::ReAllocate(pointer_type ptr, size_type newSize, size_type newAlignment) - { - pointer_type newPtr = nullptr; - bool useOverride = true; - bool destroy = false; - - if (IsOverridden()) - { - lock_type lock(m_mutex); - - if (m_records.erase(ptr)) - { - // An old allocation needs to be transferred to the new, overriding allocator. - useOverride = false; // We'll do the reallocation here - size_t oldSize = m_source->AllocationSize(ptr); - - if (newSize) - { - newPtr = m_overridingSource->Allocate(newSize, newAlignment, 0); - memcpy(newPtr, ptr, AZStd::min(newSize, oldSize)); - } - - m_source->DeAllocate(ptr, oldSize); - - if (m_records.empty() && m_finalizedConfiguration) - { - // All orphaned records are gone; we are no longer needed - m_owningAllocator->SetAllocationSource(m_overridingSource); - destroy = true; // Must destroy outside the lock - } - } - } - - if (useOverride) - { - // Default behavior, we weren't deleting an old allocation - newPtr = m_overridingSource->ReAllocate(ptr, newSize, newAlignment); - - if (!IsOverridden()) - { - // Still need to do bookkeeping if we haven't been overridden yet - lock_type lock(m_mutex); - m_records.erase(ptr); - m_records.insert(newPtr); - } - } - - if (destroy) - { - Destroy(this); - } - - return newPtr; - } - - typename AllocatorOverrideShim::size_type AllocatorOverrideShim::AllocationSize(pointer_type ptr) - { - IAllocatorAllocate* source = m_overridingSource; - - if (IsOverridden()) - { - // Determine who owns the allocation - lock_type lock(m_mutex); - - if (m_records.count(ptr)) - { - source = m_source; - } - } - - return source->AllocationSize(ptr); - } - - void AllocatorOverrideShim::GarbageCollect() - { - m_source->GarbageCollect(); - } - - typename AllocatorOverrideShim::size_type AllocatorOverrideShim::NumAllocatedBytes() const - { - return m_source->NumAllocatedBytes(); - } - - typename AllocatorOverrideShim::size_type AllocatorOverrideShim::Capacity() const - { - return m_source->Capacity(); - } - - typename AllocatorOverrideShim::size_type AllocatorOverrideShim::GetMaxAllocationSize() const - { - return m_source->GetMaxAllocationSize(); - } - - auto AllocatorOverrideShim::GetMaxContiguousAllocationSize() const -> size_type - { - return m_source->GetMaxContiguousAllocationSize(); - } - - IAllocatorAllocate* AllocatorOverrideShim::GetSubAllocator() - { - return m_source->GetSubAllocator(); + Destroy(this); } + return newPtr; } -} + + typename AllocatorOverrideShim::size_type AllocatorOverrideShim::AllocationSize(pointer_type ptr) + { + IAllocatorAllocate* source = m_overridingSource; + + if (IsOverridden()) + { + // Determine who owns the allocation + lock_type lock(m_mutex); + + if (m_records.count(ptr)) + { + source = m_source; + } + } + + return source->AllocationSize(ptr); + } + + void AllocatorOverrideShim::GarbageCollect() + { + m_source->GarbageCollect(); + } + + typename AllocatorOverrideShim::size_type AllocatorOverrideShim::NumAllocatedBytes() const + { + return m_source->NumAllocatedBytes(); + } + + typename AllocatorOverrideShim::size_type AllocatorOverrideShim::Capacity() const + { + return m_source->Capacity(); + } + + typename AllocatorOverrideShim::size_type AllocatorOverrideShim::GetMaxAllocationSize() const + { + return m_source->GetMaxAllocationSize(); + } + + auto AllocatorOverrideShim::GetMaxContiguousAllocationSize() const -> size_type + { + return m_source->GetMaxContiguousAllocationSize(); + } + + IAllocatorAllocate* AllocatorOverrideShim::GetSubAllocator() + { + return m_source->GetSubAllocator(); + } + +} // namespace AZ::Internal diff --git a/Code/Framework/AzCore/AzCore/Memory/BestFitExternalMapAllocator.cpp b/Code/Framework/AzCore/AzCore/Memory/BestFitExternalMapAllocator.cpp index ca414df4b5..30b0b78fe5 100644 --- a/Code/Framework/AzCore/AzCore/Memory/BestFitExternalMapAllocator.cpp +++ b/Code/Framework/AzCore/AzCore/Memory/BestFitExternalMapAllocator.cpp @@ -10,7 +10,6 @@ #include #include -#include #include @@ -79,8 +78,14 @@ AllocatorDebugConfig BestFitExternalMapAllocator::GetDebugConfig() // Allocate // [1/28/2011] //========================================================================= -BestFitExternalMapAllocator::pointer_type -BestFitExternalMapAllocator::Allocate(size_type byteSize, size_type alignment, int flags, const char* name, const char* fileName, int lineNum, unsigned int suppressStackRecord) +BestFitExternalMapAllocator::pointer_type BestFitExternalMapAllocator::Allocate( + size_type byteSize, + size_type alignment, + int flags, + [[maybe_unused]] const char* name, + [[maybe_unused]] const char* fileName, + [[maybe_unused]] int lineNum, + unsigned int suppressStackRecord) { (void)suppressStackRecord; @@ -89,17 +94,6 @@ BestFitExternalMapAllocator::Allocate(size_type byteSize, size_type alignment, i byteSize = MemorySizeAdjustedUp(byteSize); BestFitExternalMapAllocator::pointer_type address = m_schema->Allocate(byteSize, alignment, flags); - if (address == nullptr) - { - if (!OnOutOfMemory(byteSize, alignment, flags, name, fileName, lineNum)) - { - if (GetRecords()) - { - EBUS_EVENT(Debug::MemoryDrillerBus, DumpAllAllocations); - } - } - } - AZ_Assert(address != nullptr, "BestFitExternalMapAllocator: Failed to allocate %d bytes aligned on %d (flags: 0x%08x) %s : %s (%d)!", byteSize, alignment, flags, name ? name : "(no name)", fileName ? fileName : "(no file name)", lineNum); AZ_MEMORY_PROFILE(ProfileAllocation(address, byteSize, alignment, name, fileName, lineNum, suppressStackRecord + 1)); diff --git a/Code/Framework/AzCore/AzCore/Memory/HphaSchema.cpp b/Code/Framework/AzCore/AzCore/Memory/HphaSchema.cpp index 6af8f201c2..6e40ccd8cd 100644 --- a/Code/Framework/AzCore/AzCore/Memory/HphaSchema.cpp +++ b/Code/Framework/AzCore/AzCore/Memory/HphaSchema.cpp @@ -140,8 +140,8 @@ namespace AZ { class const_iterator; class iterator { - typedef T& reference; - typedef T* pointer; + using reference = T&; + using pointer = T*; friend class const_iterator; T* mPtr; public: @@ -171,8 +171,8 @@ namespace AZ { class const_iterator { - typedef const T& reference; - typedef const T* pointer; + using reference = const T &; + using pointer = const T *; const T* mPtr; public: const_iterator() @@ -327,7 +327,7 @@ namespace AZ { uint64_t mSizeAndFlags; public: - typedef block_header* block_ptr; + using block_ptr = block_header *; size_t size() const { return mSizeAndFlags & ~BL_FLAG_MASK; } block_ptr next() const {return (block_ptr)((char*)mem() + size()); } block_ptr prev() const {return mPrev; } @@ -415,7 +415,7 @@ namespace AZ { void dec_ref() { HPPA_ASSERT(mUseCount > 0); mUseCount--; } bool check_marker(size_t marker) const { return mMarker == (marker ^ ((size_t)this)); } }; - typedef intrusive_list page_list; + using page_list = intrusive_list; class bucket { page_list mPageList; diff --git a/Code/Framework/AzCore/AzCore/Memory/IAllocator.h b/Code/Framework/AzCore/AzCore/Memory/IAllocator.h index 1aa4cf70f5..532335e50b 100644 --- a/Code/Framework/AzCore/AzCore/Memory/IAllocator.h +++ b/Code/Framework/AzCore/AzCore/Memory/IAllocator.h @@ -14,7 +14,6 @@ namespace AZ namespace Debug { class AllocationRecords; - class MemoryDriller; } namespace AllocatorStorage @@ -83,7 +82,7 @@ namespace AZ /// Sets the number of entries to omit from the top of the callstack when recording stack traces. AllocatorDebugConfig& StackRecordLevels(int levels) { m_stackRecordLevels = levels; return *this; } - /// Set to true if this allocator should not have its records recorded and analyzed by systems like the MemoryDriller. + /// Set to true if this allocator should not have its records recorded and analyzed. AllocatorDebugConfig& ExcludeFromDebugging(bool exclude = true) { m_excludeFromDebugging = exclude; return *this; } /// Set to true if this allocator expands allocations with guard sections to detect overruns. @@ -207,8 +206,6 @@ namespace AZ template friend class AllocatorWrapper; - - friend class Debug::MemoryDriller; }; } diff --git a/Code/Framework/AzCore/AzCore/Memory/MallocSchema.cpp b/Code/Framework/AzCore/AzCore/Memory/MallocSchema.cpp index 76a71e0f08..9aa31cd8b6 100644 --- a/Code/Framework/AzCore/AzCore/Memory/MallocSchema.cpp +++ b/Code/Framework/AzCore/AzCore/Memory/MallocSchema.cpp @@ -11,149 +11,168 @@ #include #include +namespace AZ::Internal +{ + struct Header + { + uint32_t offset; + uint32_t size; + }; +} // namespace AZ::Internal + namespace AZ { - namespace Internal + //--------------------------------------------------------------------- + // MallocSchema methods + //--------------------------------------------------------------------- + + MallocSchema::MallocSchema(const Descriptor& desc) + : m_bytesAllocated(0) { - struct Header + if (desc.m_useAZMalloc) { - uint32_t offset; - uint32_t size; - }; + static const int DEFAULT_ALIGNMENT = sizeof(void*) * 2; // Default malloc alignment + + m_mallocFn = [](size_t byteSize) + { + return AZ_OS_MALLOC(byteSize, DEFAULT_ALIGNMENT); + }; + m_freeFn = [](void* ptr) + { + AZ_OS_FREE(ptr); + }; + } + else + { + m_mallocFn = &malloc; + m_freeFn = &free; + } } -} -//--------------------------------------------------------------------- -// MallocSchema methods -//--------------------------------------------------------------------- - -AZ::MallocSchema::MallocSchema(const Descriptor& desc) : - m_bytesAllocated(0) -{ - if (desc.m_useAZMalloc) + MallocSchema::~MallocSchema() { - static const int DEFAULT_ALIGNMENT = sizeof(void*) * 2; // Default malloc alignment - - m_mallocFn = [](size_t byteSize) { return AZ_OS_MALLOC(byteSize, DEFAULT_ALIGNMENT); }; - m_freeFn = [](void* ptr) { AZ_OS_FREE(ptr); }; } - else + + MallocSchema::pointer_type MallocSchema::Allocate( + size_type byteSize, + size_type alignment, + int flags, + const char* name, + const char* fileName, + int lineNum, + unsigned int suppressStackRecord) { - m_mallocFn = &malloc; - m_freeFn = &free; + (void)flags; + (void)name; + (void)fileName; + (void)lineNum; + (void)suppressStackRecord; + + if (!byteSize) + { + return nullptr; + } + + if (alignment == 0) + { + alignment = sizeof(void*) * 2; // Default malloc alignment + } + + AZ_Assert(byteSize < 0x100000000ull, "Malloc allocator only allocates up to 4GB"); + + size_type required = byteSize + sizeof(Internal::Header) + + ((alignment > sizeof(double)) + ? alignment + : 0); // Malloc will align to a minimum boundary for native objects, so we only pad if aligning to a large value + void* data = (*m_mallocFn)(required); + void* result = PointerAlignUp(reinterpret_cast(reinterpret_cast(data) + sizeof(Internal::Header)), alignment); + Internal::Header* header = PointerAlignDown( + (Internal::Header*)(reinterpret_cast(result) - sizeof(Internal::Header)), AZStd::alignment_of::value); + + header->offset = static_cast(reinterpret_cast(result) - reinterpret_cast(data)); + header->size = static_cast(byteSize); + m_bytesAllocated += byteSize; + + return result; } -} -AZ::MallocSchema::~MallocSchema() -{ -} + void MallocSchema::DeAllocate(pointer_type ptr, size_type byteSize, size_type alignment) + { + (void)byteSize; + (void)alignment; -AZ::MallocSchema::pointer_type AZ::MallocSchema::Allocate(size_type byteSize, size_type alignment, int flags, const char* name, const char* fileName, int lineNum, unsigned int suppressStackRecord) -{ - (void)flags; - (void)name; - (void)fileName; - (void)lineNum; - (void)suppressStackRecord; + if (!ptr) + { + return; + } - if (!byteSize) + Internal::Header* header = PointerAlignDown( + reinterpret_cast(reinterpret_cast(ptr) - sizeof(Internal::Header)), + AZStd::alignment_of::value); + void* freePtr = reinterpret_cast(reinterpret_cast(ptr) - static_cast(header->offset)); + + m_bytesAllocated -= header->size; + (*m_freeFn)(freePtr); + } + + MallocSchema::pointer_type MallocSchema::ReAllocate(pointer_type ptr, size_type newSize, size_type newAlignment) + { + void* newPtr = Allocate(newSize, newAlignment, 0); + size_t oldSize = AllocationSize(ptr); + + memcpy(newPtr, ptr, AZStd::min(oldSize, newSize)); + DeAllocate(ptr, 0, 0); + + return newPtr; + } + + MallocSchema::size_type MallocSchema::Resize(pointer_type ptr, size_type newSize) + { + (void)ptr; + (void)newSize; + + return 0; + } + + MallocSchema::size_type MallocSchema::AllocationSize(pointer_type ptr) + { + if (!ptr) + { + return 0; + } + Internal::Header* header = PointerAlignDown( + reinterpret_cast(reinterpret_cast(ptr) - sizeof(Internal::Header)), + AZStd::alignment_of::value); + return header->size; + } + + MallocSchema::size_type MallocSchema::NumAllocatedBytes() const + { + return m_bytesAllocated; + } + + MallocSchema::size_type MallocSchema::Capacity() const + { + return 0; + } + + MallocSchema::size_type MallocSchema::GetMaxAllocationSize() const + { + return 0xFFFFFFFFull; + } + + MallocSchema::size_type MallocSchema::GetMaxContiguousAllocationSize() const + { + return AZ_CORE_MAX_ALLOCATOR_SIZE; + } + + IAllocatorAllocate* MallocSchema::GetSubAllocator() { return nullptr; } - if (alignment == 0) + void MallocSchema::GarbageCollect() { - alignment = sizeof(void*) * 2; // Default malloc alignment } - AZ_Assert(byteSize < 0x100000000ull, "Malloc allocator only allocates up to 4GB"); - - size_type required = byteSize + sizeof(Internal::Header) + ((alignment > sizeof(double)) ? alignment : 0); // Malloc will align to a minimum boundary for native objects, so we only pad if aligning to a large value - void* data = (*m_mallocFn)(required); - void* result = PointerAlignUp(reinterpret_cast(reinterpret_cast(data) + sizeof(Internal::Header)), alignment); - Internal::Header* header = PointerAlignDown((Internal::Header*)(reinterpret_cast(result) - sizeof(Internal::Header)), AZStd::alignment_of::value); - - header->offset = static_cast(reinterpret_cast(result) - reinterpret_cast(data)); - header->size = static_cast(byteSize); - m_bytesAllocated += byteSize; - - return result; -} - -void AZ::MallocSchema::DeAllocate(pointer_type ptr, size_type byteSize, size_type alignment) -{ - (void)byteSize; - (void)alignment; - - if (!ptr) - { - return; - } - - Internal::Header* header = PointerAlignDown(reinterpret_cast(reinterpret_cast(ptr) - sizeof(Internal::Header)), AZStd::alignment_of::value); - void* freePtr = reinterpret_cast(reinterpret_cast(ptr) - static_cast(header->offset)); - - m_bytesAllocated -= header->size; - (*m_freeFn)(freePtr); -} - -AZ::MallocSchema::pointer_type AZ::MallocSchema::ReAllocate(pointer_type ptr, size_type newSize, size_type newAlignment) -{ - void* newPtr = Allocate(newSize, newAlignment, 0); - size_t oldSize = AllocationSize(ptr); - - memcpy(newPtr, ptr, AZStd::min(oldSize, newSize)); - DeAllocate(ptr, 0, 0); - - return newPtr; -} - -AZ::MallocSchema::size_type AZ::MallocSchema::Resize(pointer_type ptr, size_type newSize) -{ - (void)ptr; - (void)newSize; - - return 0; -} - -AZ::MallocSchema::size_type AZ::MallocSchema::AllocationSize(pointer_type ptr) -{ - size_type result = 0; - - if (ptr) - { - Internal::Header* header = PointerAlignDown(reinterpret_cast(reinterpret_cast(ptr) - sizeof(Internal::Header)), AZStd::alignment_of::value); - result = header->size; - } - - return result; -} - -AZ::MallocSchema::size_type AZ::MallocSchema::NumAllocatedBytes() const -{ - return m_bytesAllocated; -} - -AZ::MallocSchema::size_type AZ::MallocSchema::Capacity() const -{ - return 0; -} - -AZ::MallocSchema::size_type AZ::MallocSchema::GetMaxAllocationSize() const -{ - return 0xFFFFFFFFull; -} - -AZ::MallocSchema::size_type AZ::MallocSchema::GetMaxContiguousAllocationSize() const -{ - return AZ_CORE_MAX_ALLOCATOR_SIZE; -} - -AZ::IAllocatorAllocate* AZ::MallocSchema::GetSubAllocator() -{ - return nullptr; -} - -void AZ::MallocSchema::GarbageCollect() -{ -} +} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Memory/MemoryDriller.cpp b/Code/Framework/AzCore/AzCore/Memory/MemoryDriller.cpp deleted file mode 100644 index 2ea25c3397..0000000000 --- a/Code/Framework/AzCore/AzCore/Memory/MemoryDriller.cpp +++ /dev/null @@ -1,294 +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 -#include - -#include -#include - -#include -#include - -namespace AZ -{ - namespace Debug - { - //========================================================================= - // MemoryDriller - // [2/6/2013] - //========================================================================= - MemoryDriller::MemoryDriller(const Descriptor& desc) - { - (void)desc; - BusConnect(); - - AllocatorManager::Instance().EnterProfilingMode(); - - { - // Register all allocators that were created before the driller existed - auto allocatorLock = AllocatorManager::Instance().LockAllocators(); - - for (int i = 0; i < AllocatorManager::Instance().GetNumAllocators(); ++i) - { - IAllocator* allocator = AllocatorManager::Instance().GetAllocator(i); - RegisterAllocator(allocator); - } - } - } - - //========================================================================= - // ~MemoryDriller - // [2/6/2013] - //========================================================================= - MemoryDriller::~MemoryDriller() - { - BusDisconnect(); - AllocatorManager::Instance().ExitProfilingMode(); - } - - //========================================================================= - // Start - // [2/6/2013] - //========================================================================= - void MemoryDriller::Start(const Param* params, int numParams) - { - (void)params; - (void)numParams; - - // dump current allocations for all allocators with tracking - auto allocatorLock = AllocatorManager::Instance().LockAllocators(); - for (int i = 0; i < AllocatorManager::Instance().GetNumAllocators(); ++i) - { - IAllocator* allocator = AllocatorManager::Instance().GetAllocator(i); - if (auto records = allocator->GetRecords()) - { - RegisterAllocatorOutput(allocator); - const AllocationRecordsType& allocMap = records->GetMap(); - for (AllocationRecordsType::const_iterator allocIt = allocMap.begin(); allocIt != allocMap.end(); ++allocIt) - { - RegisterAllocationOutput(allocator, allocIt->first, &allocIt->second); - } - } - } - } - - //========================================================================= - // Stop - // [2/6/2013] - //========================================================================= - void MemoryDriller::Stop() - { - } - - //========================================================================= - // RegisterAllocator - // [2/6/2013] - //========================================================================= - void MemoryDriller::RegisterAllocator(IAllocator* allocator) - { - // Ignore if our allocator is already registered - if (allocator->GetRecords() != nullptr) - { - return; - } - - auto debugConfig = allocator->GetDebugConfig(); - - if (!debugConfig.m_excludeFromDebugging) - { - allocator->SetRecords(aznew Debug::AllocationRecords((unsigned char)debugConfig.m_stackRecordLevels, debugConfig.m_usesMemoryGuards, debugConfig.m_marksUnallocatedMemory, allocator->GetName())); - - m_allAllocatorRecords.push_back(allocator->GetRecords()); - - if (m_output == nullptr) - { - return; // we have no active output - } - RegisterAllocatorOutput(allocator); - } - } - //========================================================================= - // RegisterAllocatorOutput - // [2/6/2013] - //========================================================================= - void MemoryDriller::RegisterAllocatorOutput(IAllocator* allocator) - { - auto records = allocator->GetRecords(); - m_output->BeginTag(AZ_CRC("MemoryDriller", 0x1b31269d)); - m_output->BeginTag(AZ_CRC("RegisterAllocator", 0x19f08114)); - m_output->Write(AZ_CRC("Name", 0x5e237e06), allocator->GetName()); - m_output->Write(AZ_CRC("Id", 0xbf396750), allocator); - m_output->Write(AZ_CRC("Capacity", 0xb5e8b174), allocator->GetAllocationSource()->Capacity()); - m_output->Write(AZ_CRC("RecordsId", 0x7caaca88), records); - if (records) - { - m_output->Write(AZ_CRC("RecordsMode", 0x764c147a), (char)records->GetMode()); - m_output->Write(AZ_CRC("NumStackLevels", 0xad9cff15), records->GetNumStackLevels()); - } - m_output->EndTag(AZ_CRC("RegisterAllocator", 0x19f08114)); - m_output->EndTag(AZ_CRC("MemoryDriller", 0x1b31269d)); - } - - //========================================================================= - // UnregisterAllocator - // [2/6/2013] - //========================================================================= - void MemoryDriller::UnregisterAllocator(IAllocator* allocator) - { - auto allocatorRecords = allocator->GetRecords(); - AZ_Assert(allocatorRecords, "This allocator is not registered with the memory driller!"); - for (auto records : m_allAllocatorRecords) - { - if (records == allocatorRecords) - { - m_allAllocatorRecords.remove(records); - break; - } - } - delete allocatorRecords; - allocator->SetRecords(nullptr); - - if (m_output == nullptr) - { - return; // we have no active output - } - m_output->BeginTag(AZ_CRC("MemoryDriller", 0x1b31269d)); - m_output->Write(AZ_CRC("UnregisterAllocator", 0xb2b54f93), allocator); - m_output->EndTag(AZ_CRC("MemoryDriller", 0x1b31269d)); - } - - //========================================================================= - // RegisterAllocation - // [2/6/2013] - //========================================================================= - void MemoryDriller::RegisterAllocation(IAllocator* allocator, void* address, size_t byteSize, size_t alignment, const char* name, const char* fileName, int lineNum, unsigned int stackSuppressCount) - { - auto records = allocator->GetRecords(); - if (records) - { - const AllocationInfo* info = records->RegisterAllocation(address, byteSize, alignment, name, fileName, lineNum, stackSuppressCount + 1); - if (m_output == nullptr) - { - return; // we have no active output - } - RegisterAllocationOutput(allocator, address, info); - } - } - - //========================================================================= - // RegisterAllocationOutput - // [2/6/2013] - //========================================================================= - void MemoryDriller::RegisterAllocationOutput(IAllocator* allocator, void* address, const AllocationInfo* info) - { - auto records = allocator->GetRecords(); - m_output->BeginTag(AZ_CRC("MemoryDriller", 0x1b31269d)); - m_output->BeginTag(AZ_CRC("RegisterAllocation", 0x992a9780)); - m_output->Write(AZ_CRC("RecordsId", 0x7caaca88), records); - m_output->Write(AZ_CRC("Address", 0x0d4e6f81), address); - if (info) - { - if (info->m_name) - { - m_output->Write(AZ_CRC("Name", 0x5e237e06), info->m_name); - } - m_output->Write(AZ_CRC("Alignment", 0x2cce1e5c), info->m_alignment); - m_output->Write(AZ_CRC("Size", 0xf7c0246a), info->m_byteSize); - if (info->m_fileName) - { - m_output->Write(AZ_CRC("FileName", 0x3c0be965), info->m_fileName); - m_output->Write(AZ_CRC("FileLine", 0xb33c2395), info->m_lineNum); - } - // copy the stack frames directly, resolving the stack should happen later as this is a SLOW procedure. - if (info->m_stackFrames) - { - m_output->Write(AZ_CRC("Stack", 0x41a87b6a), info->m_stackFrames, info->m_stackFrames + records->GetNumStackLevels()); - } - } - m_output->EndTag(AZ_CRC("RegisterAllocation", 0x992a9780)); - m_output->EndTag(AZ_CRC("MemoryDriller", 0x1b31269d)); - } - - //========================================================================= - // UnRegisterAllocation - // [2/6/2013] - //========================================================================= - void MemoryDriller::UnregisterAllocation(IAllocator* allocator, void* address, size_t byteSize, size_t alignment, AllocationInfo* info) - { - auto records = allocator->GetRecords(); - if (records) - { - records->UnregisterAllocation(address, byteSize, alignment, info); - - if (m_output == nullptr) - { - return; // we have no active output - } - m_output->BeginTag(AZ_CRC("MemoryDriller", 0x1b31269d)); - m_output->BeginTag(AZ_CRC("UnRegisterAllocation", 0xea5dc4cd)); - m_output->Write(AZ_CRC("RecordsId", 0x7caaca88), records); - m_output->Write(AZ_CRC("Address", 0x0d4e6f81), address); - m_output->EndTag(AZ_CRC("UnRegisterAllocation", 0xea5dc4cd)); - m_output->EndTag(AZ_CRC("MemoryDriller", 0x1b31269d)); - } - } - - //========================================================================= - // ReallocateAllocation - // [10/1/2018] - //========================================================================= - void MemoryDriller::ReallocateAllocation(IAllocator* allocator, void* prevAddress, void* newAddress, size_t newByteSize, size_t newAlignment) - { - AllocationInfo info; - UnregisterAllocation(allocator, prevAddress, 0, 0, &info); - RegisterAllocation(allocator, newAddress, newByteSize, newAlignment, info.m_name, info.m_fileName, info.m_lineNum, 0); - } - - //========================================================================= - // ResizeAllocation - // [2/6/2013] - //========================================================================= - void MemoryDriller::ResizeAllocation(IAllocator* allocator, void* address, size_t newSize) - { - auto records = allocator->GetRecords(); - if (records) - { - records->ResizeAllocation(address, newSize); - - if (m_output == nullptr) - { - return; // we have no active output - } - m_output->BeginTag(AZ_CRC("MemoryDriller", 0x1b31269d)); - m_output->BeginTag(AZ_CRC("ResizeAllocation", 0x8a9c78dc)); - m_output->Write(AZ_CRC("RecordsId", 0x7caaca88), records); - m_output->Write(AZ_CRC("Address", 0x0d4e6f81), address); - m_output->Write(AZ_CRC("Size", 0xf7c0246a), newSize); - m_output->EndTag(AZ_CRC("ResizeAllocation", 0x8a9c78dc)); - m_output->EndTag(AZ_CRC("MemoryDriller", 0x1b31269d)); - } - } - - void MemoryDriller::DumpAllAllocations() - { - // Create a copy so allocations done during the printing dont end up affecting the container - const AZStd::list allocationRecords = m_allAllocatorRecords; - - for (auto records : allocationRecords) - { - // Skip if we have had no allocations made - if (records->RequestedAllocs()) - { - records->EnumerateAllocations(AZ::Debug::PrintAllocationsCB(true, true)); - } - } - } - - }// namespace Debug -} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Memory/MemoryDriller.h b/Code/Framework/AzCore/AzCore/Memory/MemoryDriller.h deleted file mode 100644 index 9bcbc85217..0000000000 --- a/Code/Framework/AzCore/AzCore/Memory/MemoryDriller.h +++ /dev/null @@ -1,71 +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 - * - */ -#ifndef AZCORE_MEMORY_DRILLER_H -#define AZCORE_MEMORY_DRILLER_H 1 - -#include -#include - -namespace AZ -{ - namespace Debug - { - struct StackFrame; - - /** - * Trace messages driller class - */ - class MemoryDriller - : public Driller - , public MemoryDrillerBus::Handler - { - public: - AZ_CLASS_ALLOCATOR(MemoryDriller, OSAllocator, 0) - - // TODO: Centralized settings for memory tracking. - struct Descriptor - { - }; - - MemoryDriller(const Descriptor& desc = Descriptor()); - ~MemoryDriller(); - - protected: - ////////////////////////////////////////////////////////////////////////// - // Driller - const char* GroupName() const override { return "SystemDrillers"; } - const char* GetName() const override { return "MemoryDriller"; } - const char* GetDescription() const override { return "Reports all allocators and memory allocations."; } - void Start(const Param* params = NULL, int numParams = 0) override; - void Stop() override; - ////////////////////////////////////////////////////////////////////////// - - ////////////////////////////////////////////////////////////////////////// - // MemoryDrillerBus - void RegisterAllocator(IAllocator* allocator) override; - void UnregisterAllocator(IAllocator* allocator) override; - - void RegisterAllocation(IAllocator* allocator, void* address, size_t byteSize, size_t alignment, const char* name, const char* fileName, int lineNum, unsigned int stackSuppressCount) override; - void UnregisterAllocation(IAllocator* allocator, void* address, size_t byteSize, size_t alignment, AllocationInfo* info) override; - void ReallocateAllocation(IAllocator* allocator, void* prevAddress, void* newAddress, size_t newByteSize, size_t newAlignment) override; - void ResizeAllocation(IAllocator* allocator, void* address, size_t newSize) override; - - void DumpAllAllocations() override; - ////////////////////////////////////////////////////////////////////////// - - void RegisterAllocatorOutput(IAllocator* allocator); - void RegisterAllocationOutput(IAllocator* allocator, void* address, const AllocationInfo* info); - private: - // Store a list of all of our allocator records so we can dump them all without having to know about the allocators - AZStd::list m_allAllocatorRecords; - }; - } // namespace Debug -} // namespace AZ - -#endif // AZCORE_MEMORY_DRILLER_H -#pragma once diff --git a/Code/Framework/AzCore/AzCore/Memory/MemoryDrillerBus.h b/Code/Framework/AzCore/AzCore/Memory/MemoryDrillerBus.h deleted file mode 100644 index 43a31af006..0000000000 --- a/Code/Framework/AzCore/AzCore/Memory/MemoryDrillerBus.h +++ /dev/null @@ -1,52 +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 - * - */ -#ifndef AZCORE_MEMORY_DRILLER_BUS_H -#define AZCORE_MEMORY_DRILLER_BUS_H 1 - -#include - -namespace AZ -{ - class IAllocator; - namespace Debug - { - //class AllocationRecords; - struct AllocationInfo; - - /** - * Memory allocations driller message. - * - * We use a driller bus so all messages are sending in exclusive matter no other driller messages - * can be triggered at that moment, so we already preserve the calling order. You can assume - * all access code in the driller framework in guarded. You can manually lock the driller mutex are you - * use by using \ref AZ::Debug::DrillerEBusMutex. - */ - class MemoryDrillerMessages - : public AZ::Debug::DrillerEBusTraits - { - public: - virtual ~MemoryDrillerMessages() {} - - /// Register allocation (with customizable tracking settings - TODO: we should centralize this settings and remove them from here) - virtual void RegisterAllocator(IAllocator* allocator) = 0; - virtual void UnregisterAllocator(IAllocator* allocator) = 0; - - virtual void RegisterAllocation(IAllocator* allocator, void* address, size_t byteSize, size_t alignment, const char* name, const char* fileName, int lineNum, unsigned int stackSuppressCount) = 0; - virtual void UnregisterAllocation(IAllocator* allocator, void* address, size_t byteSize, size_t alignment, AllocationInfo* info) = 0; - virtual void ReallocateAllocation(IAllocator* allocator, void* prevAddress, void* newAddress, size_t newByteSize, size_t newAlignment) = 0; - virtual void ResizeAllocation(IAllocator* allocator, void* address, size_t newSize) = 0; - - virtual void DumpAllAllocations() = 0; - }; - - typedef AZ::EBus MemoryDrillerBus; - } // namespace Debug -} // namespace AZ - -#endif // AZCORE_MEMORY_DRILLER_BUS_H -#pragma once diff --git a/Code/Framework/AzCore/AzCore/Memory/OSAllocator.h b/Code/Framework/AzCore/AzCore/Memory/OSAllocator.h index b327cf6349..3a8080483b 100644 --- a/Code/Framework/AzCore/AzCore/Memory/OSAllocator.h +++ b/Code/Framework/AzCore/AzCore/Memory/OSAllocator.h @@ -20,7 +20,7 @@ namespace AZ * OS allocator should be used for direct OS allocations (C heap) * It's memory usage is NOT tracked. If you don't create this allocator, it will be implicitly * created by the SystemAllocator when it is needed. In addition this allocator is used for - * debug data (like drillers, memory trackng, etc.) + * debug data (like memory tracking, etc.) */ class OSAllocator : public AllocatorBase diff --git a/Code/Framework/AzCore/AzCore/Memory/PlatformMemoryInstrumentation.h b/Code/Framework/AzCore/AzCore/Memory/PlatformMemoryInstrumentation.h deleted file mode 100644 index bc6499566d..0000000000 --- a/Code/Framework/AzCore/AzCore/Memory/PlatformMemoryInstrumentation.h +++ /dev/null @@ -1,42 +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 - -#if AZ_TRAIT_OS_MEMORY_INSTRUMENTATION && !defined(_RELEASE) -#define PLATFORM_MEMORY_INSTRUMENTATION_ENABLED 1 -#else -#define PLATFORM_MEMORY_INSTRUMENTATION_ENABLED 0 -#endif - -#if PLATFORM_MEMORY_INSTRUMENTATION_ENABLED - -#include -#include - -namespace AZ -{ - /** - * PlatformMemoryInstrumentation - Abstraction layer for platform specific memory instrumentation. - */ - class PlatformMemoryInstrumentation - { - public: - static uint16_t GetNextGroupId() { return m_nextGroupId++; }; - static void RegisterGroup(uint16_t id, const char* name, uint16_t parentGroup); - static void Alloc(const void* ptr, uint64_t size, uint32_t padding, uint16_t group); - static void Free(const void* ptr); - static void ReallocBegin(const void* origPtr, uint64_t size, uint16_t group); - static void ReallocEnd(const void* newPtr, uint64_t size, uint32_t padding); - static const uint16_t m_groupRoot; - static uint16_t m_nextGroupId; - }; -} - -#endif // PLATFORM_MEMORY_INSTRUMENTATION_ENABLED diff --git a/Code/Framework/AzCore/AzCore/Memory/PoolAllocator.h b/Code/Framework/AzCore/AzCore/Memory/PoolAllocator.h index 72418b3d6e..a03f7b5b92 100644 --- a/Code/Framework/AzCore/AzCore/Memory/PoolAllocator.h +++ b/Code/Framework/AzCore/AzCore/Memory/PoolAllocator.h @@ -12,9 +12,6 @@ #include #include -#include - - namespace AZ { template diff --git a/Code/Framework/AzCore/AzCore/Memory/PoolSchema.cpp b/Code/Framework/AzCore/AzCore/Memory/PoolSchema.cpp index 0bef6b7d28..9167864450 100644 --- a/Code/Framework/AzCore/AzCore/Memory/PoolSchema.cpp +++ b/Code/Framework/AzCore/AzCore/Memory/PoolSchema.cpp @@ -35,8 +35,8 @@ namespace AZ public: AZ_CLASS_ALLOCATOR(PoolAllocation, SystemAllocator, 0) - typedef typename Allocator::Page PageType; - typedef typename Allocator::Bucket BucketType; + using PageType = typename Allocator::Page; + using BucketType = typename Allocator::Bucket; PoolAllocation(Allocator* alloc, size_t pageSize, size_t minAllocationSize, size_t maxAllocationSize); virtual ~PoolAllocation(); @@ -89,7 +89,7 @@ namespace AZ void SetupFreeList(size_t elementSize, size_t pageDataBlockSize); /// We just use a free list of nodes which we cast to the pool type. - typedef AZStd::intrusive_slist > FreeListType; + using FreeListType = AZStd::intrusive_slist>; FreeListType m_freeList; u32 m_bin; @@ -103,7 +103,7 @@ namespace AZ */ struct Bucket { - typedef AZStd::intrusive_list > PageListType; + using PageListType = AZStd::intrusive_list>; PageListType m_pages; }; @@ -162,7 +162,7 @@ namespace AZ return page; } - typedef PoolAllocation AllocatorType; + using AllocatorType = PoolAllocation; IAllocatorAllocate* m_pageAllocator; AllocatorType m_allocator; void* m_staticDataBlock; @@ -199,7 +199,7 @@ namespace AZ void SetupFreeList(size_t elementSize, size_t pageDataBlockSize); /// We just use a free list of nodes which we cast to the pool type. - typedef AZStd::intrusive_slist > FreeListType; + using FreeListType = AZStd::intrusive_slist>; FreeListType m_freeList; AZStd::lock_free_intrusive_stack_node m_lfStack; ///< Lock Free stack node @@ -215,7 +215,7 @@ namespace AZ */ struct Bucket { - typedef AZStd::intrusive_list > PageListType; + using PageListType = AZStd::intrusive_list>; PageListType m_pages; }; @@ -291,7 +291,7 @@ namespace AZ ThreadPoolSchema::SetThreadPoolData m_threadPoolSetter; // Fox X64 we push/pop pages using the m_mutex to sync. Pages are - typedef Bucket::PageListType FreePagesType; + using FreePagesType = Bucket::PageListType; FreePagesType m_freePages; AZStd::vector m_threads; ///< Array with all separate thread data. Used to traverse end free elements. @@ -313,12 +313,12 @@ namespace AZ ThreadPoolData(ThreadPoolSchemaImpl* alloc, size_t pageSize, size_t minAllocationSize, size_t maxAllocationSize); ~ThreadPoolData(); - typedef PoolAllocation AllocatorType; + using AllocatorType = PoolAllocation; /** * Stack with freed elements from other threads. We don't need stamped stack since the ABA problem can not * happen here. We push from many threads and pop from only one (we don't push from it). */ - typedef AZStd::lock_free_intrusive_stack > FreedElementsStack; + using FreedElementsStack = AZStd::lock_free_intrusive_stack>; AllocatorType m_allocator; FreedElementsStack m_freedElements; diff --git a/Code/Framework/AzCore/AzCore/Memory/SystemAllocator.cpp b/Code/Framework/AzCore/AzCore/Memory/SystemAllocator.cpp index 8c84338fd0..ddcc046b8e 100644 --- a/Code/Framework/AzCore/AzCore/Memory/SystemAllocator.cpp +++ b/Code/Framework/AzCore/AzCore/Memory/SystemAllocator.cpp @@ -11,7 +11,6 @@ #include #include -#include #include @@ -248,14 +247,6 @@ SystemAllocator::Allocate(size_type byteSize, size_type alignment, int flags, co if (address == nullptr) { byteSize = MemorySizeAdjustedDown(byteSize); // restore original size - - if (!OnOutOfMemory(byteSize, alignment, flags, name, fileName, lineNum)) - { - if (GetRecords()) - { - EBUS_EVENT(Debug::MemoryDrillerBus, DumpAllAllocations); - } - } } AZ_Assert(address != nullptr, "SystemAllocator: Failed to allocate %d bytes aligned on %d (flags: 0x%08x) %s : %s (%d)!", byteSize, alignment, flags, name ? name : "(no name)", fileName ? fileName : "(no file name)", lineNum); diff --git a/Code/Framework/AzCore/AzCore/Module/Environment.cpp b/Code/Framework/AzCore/AzCore/Module/Environment.cpp index 71da948a35..790af730bf 100644 --- a/Code/Framework/AzCore/AzCore/Module/Environment.cpp +++ b/Code/Framework/AzCore/AzCore/Module/Environment.cpp @@ -24,10 +24,10 @@ namespace AZ class OSStdAllocator { public: - typedef void* pointer_type; - typedef AZStd::size_t size_type; - typedef AZStd::ptrdiff_t difference_type; - typedef AZStd::false_type allow_memory_leaks; ///< Regular allocators should not leak. + using pointer_type = void *; + using size_type = AZStd::size_t; + using difference_type = AZStd::ptrdiff_t; + using allow_memory_leaks = AZStd::false_type; ///< Regular allocators should not leak. OSStdAllocator(Environment::AllocatorInterface* allocator) : m_name("GlobalEnvironmentAllocator") @@ -122,7 +122,7 @@ namespace AZ : public EnvironmentInterface { public: - typedef AZStd::unordered_map, AZStd::equal_to, OSStdAllocator> MapType; + using MapType = AZStd::unordered_map, AZStd::equal_to, OSStdAllocator>; static EnvironmentInterface* Get(); static void Attach(EnvironmentInstance sourceEnvironment, bool useAsGetFallback); diff --git a/Code/Framework/AzCore/AzCore/Module/Internal/ModuleManagerSearchPathTool.cpp b/Code/Framework/AzCore/AzCore/Module/Internal/ModuleManagerSearchPathTool.cpp index 10aa9904a3..0797fef93f 100644 --- a/Code/Framework/AzCore/AzCore/Module/Internal/ModuleManagerSearchPathTool.cpp +++ b/Code/Framework/AzCore/AzCore/Module/Internal/ModuleManagerSearchPathTool.cpp @@ -10,21 +10,18 @@ #include #include -namespace AZ +namespace AZ::Internal { - namespace Internal + AZ::OSString ModuleManagerSearchPathTool::GetModuleDirectory(const AZ::DynamicModuleDescriptor& moduleDesc) { - AZ::OSString ModuleManagerSearchPathTool::GetModuleDirectory(const AZ::DynamicModuleDescriptor& moduleDesc) + // For each module that is loaded, attempt to set the module's folder as a path for dependent module resolution + AZ::OSString modulePath = moduleDesc.m_dynamicLibraryPath; + AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationBus::Events::ResolveModulePath, modulePath); + auto lastPathSep = modulePath.find_last_of(AZ_TRAIT_OS_PATH_SEPARATOR); + if (lastPathSep != modulePath.npos) { - // For each module that is loaded, attempt to set the module's folder as a path for dependent module resolution - AZ::OSString modulePath = moduleDesc.m_dynamicLibraryPath; - AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationBus::Events::ResolveModulePath, modulePath); - auto lastPathSep = modulePath.find_last_of(AZ_TRAIT_OS_PATH_SEPARATOR); - if (lastPathSep != modulePath.npos) - { - modulePath = modulePath.substr(0, lastPathSep); - } - return modulePath; + modulePath = modulePath.substr(0, lastPathSep); } - } // namespace Internal -} // namespace AZ + return modulePath; + } +} // namespace AZ::Internal diff --git a/Code/Framework/AzCore/AzCore/Name/Internal/NameData.cpp b/Code/Framework/AzCore/AzCore/Name/Internal/NameData.cpp index 574b0bcc7e..69bead11ac 100644 --- a/Code/Framework/AzCore/AzCore/Name/Internal/NameData.cpp +++ b/Code/Framework/AzCore/AzCore/Name/Internal/NameData.cpp @@ -9,45 +9,41 @@ #include #include -namespace AZ +namespace AZ::Internal { - namespace Internal + NameData::NameData(AZStd::string&& name, Hash hash) + : m_name{AZStd::move(name)} + , m_hash{hash} + {} + + AZStd::string_view NameData::GetName() const { - NameData::NameData(AZStd::string&& name, Hash hash) - : m_name{AZStd::move(name)} - , m_hash{hash} - {} + return m_name; + } - AZStd::string_view NameData::GetName() const - { - return m_name; - } + NameData::Hash NameData::GetHash() const + { + return m_hash; + } - NameData::Hash NameData::GetHash() const - { - return m_hash; - } + void NameData::add_ref() + { + AZ_Assert(m_useCount >= 0, "NameData has been deleted"); + ++m_useCount; + } - void NameData::add_ref() + void NameData::release() + { + // this could be released after we decrement the counter, therefore we will + // base the release on the hash which is stable + Hash hash = m_hash; + AZ_Assert(m_useCount > 0, "m_useCount is already 0!"); + if (m_useCount.fetch_sub(1) == 1) { - AZ_Assert(m_useCount >= 0, "NameData has been deleted"); - ++m_useCount; - } - - void NameData::release() - { - // this could be released after we decrement the counter, therefore we will - // base the release on the hash which is stable - Hash hash = m_hash; - AZ_Assert(m_useCount > 0, "m_useCount is already 0!"); - if (m_useCount.fetch_sub(1) == 1) + if (AZ::NameDictionary::IsReady()) { - if (AZ::NameDictionary::IsReady()) - { - AZ::NameDictionary::Instance().TryReleaseName(hash); - } + AZ::NameDictionary::Instance().TryReleaseName(hash); } } } -} - +} // namespace AZ::Internal diff --git a/Code/Framework/AzCore/AzCore/Platform.cpp b/Code/Framework/AzCore/AzCore/Platform.cpp index ad345f65e8..0defef826e 100644 --- a/Code/Framework/AzCore/AzCore/Platform.cpp +++ b/Code/Framework/AzCore/AzCore/Platform.cpp @@ -8,19 +8,16 @@ #include -namespace AZ +namespace AZ::Platform { - namespace Platform - { - MachineId s_machineId = MachineId(0); + MachineId s_machineId = MachineId(0); - void SetLocalMachineId(AZ::u32 machineId) + void SetLocalMachineId(AZ::u32 machineId) + { + AZ_Assert(machineId != 0, "0 machine ID is reserved!"); + if (s_machineId != 0) { - AZ_Assert(machineId != 0, "0 machine ID is reserved!"); - if (s_machineId != 0) - { - s_machineId = machineId; - } + s_machineId = machineId; } } -} +} // namespace AZ::Platform diff --git a/Code/Framework/AzCore/AzCore/RTTI/BehaviorContextUtilities.cpp b/Code/Framework/AzCore/AzCore/RTTI/BehaviorContextUtilities.cpp index 6fb03073b0..5632c258c3 100644 --- a/Code/Framework/AzCore/AzCore/RTTI/BehaviorContextUtilities.cpp +++ b/Code/Framework/AzCore/AzCore/RTTI/BehaviorContextUtilities.cpp @@ -117,9 +117,9 @@ namespace AZ if (!explicitOverloads.m_overloads.empty()) { - for (auto methodAndClass : explicitOverloads.m_overloads) + for (const auto& methodAndClass : explicitOverloads.m_overloads) { - overloads.push_back({ methodAndClass.first, methodAndClass.second }); + overloads.emplace_back(methodAndClass.first, methodAndClass.second); } } else @@ -128,7 +128,7 @@ namespace AZ do { - overloads.push_back({ overload, behaviorClass }); + overloads.emplace_back(overload, behaviorClass); overload = overload->m_overload; } while (overload); diff --git a/Code/Framework/AzCore/AzCore/RTTI/BehaviorInterfaceProxy.h b/Code/Framework/AzCore/AzCore/RTTI/BehaviorInterfaceProxy.h new file mode 100644 index 0000000000..0e7a4ac356 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/RTTI/BehaviorInterfaceProxy.h @@ -0,0 +1,126 @@ +/* + * 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 +#include +#include +#include +#include + +namespace AZ +{ + /** + * Utility class for reflecting an AZ::Interface through the BehaviorContext + * + * Example: + * + * class MyInterface + * { + * public: + * AZ_RTTI(MyInterface, "{BADDF000D-CDCD-CDCD-CDCD-BAAAADF0000D}"); + * virtual ~MyInterface() = default; + * + * virtual AZStd::string Foo() = 0; + * virtual void Bar(float x, float y) = 0; + * }; + * + * class MySystemProxy + * : public BehaviorInterfaceProxy + * { + * public: + * AZ_RTTI(MySystemProxy, "{CDCDCDCD-BAAD-BADD-F00D-CDCDCDCDCDCD}", BehaviorInterfaceProxy); + * AZ_BEHAVIOR_INTERFACE(MySystemProxy, MyInterface); + * }; + * + * void Reflect(AZ::ReflectContext* context) + * { + * if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) + * { + * behaviorContext->ConstantProperty("g_MySystem", MySystemProxy::GetProxy) + * ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + * ->Attribute(AZ::Script::Attributes::Module, "MyModule"); + * + * behaviorContext->Class("MySystemInterface") + * ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + * ->Attribute(AZ::Script::Attributes::Module, "MyModule") + * + * ->Method("Foo", MySystemProxy::WrapMethod<&MyInterface::Foo>()) + * ->Method("Bar", MySystemProxy::WrapMethod<&MyInterface::Bar>()); + * } + * } + */ + template + class BehaviorInterfaceProxy + { + public: + AZ_CLASS_ALLOCATOR(BehaviorInterfaceProxy, AZ::SystemAllocator, 0); + AZ_RTTI(BehaviorInterfaceProxy, "{E7CC8D27-4499-454E-A7DF-3F72FBECD30D}"); + + BehaviorInterfaceProxy() = default; + virtual ~BehaviorInterfaceProxy() = default; + + //! Stores the instance which will use the provided shared_ptr deleter when the reference count hits zero + BehaviorInterfaceProxy(AZStd::shared_ptr sharedInstance) + : m_instance(AZStd::move(sharedInstance)) + { + } + + //! Stores the instance which will perform a no-op deleter when the reference count hits zero + BehaviorInterfaceProxy(T* rawIntance) + : m_instance(rawIntance, [](T*) {}) + { + } + + //! Returns if the m_instance shared pointer is non-nullptr + bool IsValid() const { return m_instance; } + + protected: + //! Internal access for use in the derived GetProxy function + static T* GetInstance() + { + T* interfacePtr = AZ::Interface::Get(); + AZ_Warning("BehaviorInterfaceProxy", interfacePtr, + "There is currently no global %s registered with an AZ Interface", + AzTypeInfo::Name() + ); + // Don't delete the global instance, it is not owned by the behavior context + return interfacePtr; + } + + template + struct MethodWrapper + { + template + static auto WrapMethod() + { + using ReturnType = AZStd::function_traits_get_result_t>; + return [](Proxy* proxy, Args... params) -> ReturnType + { + if (proxy && proxy->IsValid()) + { + return AZStd::invoke(Method, proxy->m_instance, AZStd::forward(params)...); + } + return ReturnType(); + }; + } + }; + + AZStd::shared_ptr m_instance; + }; + + #define AZ_BEHAVIOR_INTERFACE(ProxyType, InterfaceType) \ + static ProxyType GetProxy() { return GetInstance(); } \ + template \ + static auto WrapMethod() { \ + using FuncTraits = AZStd::function_traits>; \ + return FuncTraits::template expand_args::template WrapMethod(); \ + } \ + ProxyType() = default; \ + ProxyType(AZStd::shared_ptr sharedInstance) : BehaviorInterfaceProxy(sharedInstance) {} \ + ProxyType(InterfaceType* rawIntance) : BehaviorInterfaceProxy(rawIntance) {} +} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Script/ScriptContext.cpp b/Code/Framework/AzCore/AzCore/Script/ScriptContext.cpp index b03d413507..841387f14d 100644 --- a/Code/Framework/AzCore/AzCore/Script/ScriptContext.cpp +++ b/Code/Framework/AzCore/AzCore/Script/ScriptContext.cpp @@ -3408,7 +3408,14 @@ LUA_API const Node* lua_getDummyNode() const BehaviorParameter* arg = method->GetArgument(iArg); BehaviorClass* argClass = nullptr; LuaLoadFromStack fromStack = FromLuaStack(context, arg, argClass); - AZ_Assert(fromStack, "Argument %s for Method %s doesn't have support to be converted to Lua!", arg->m_name, method->m_name.c_str()); + AZ_Assert(fromStack, + "The argument type: %s for method: %s is not serialized and/or reflected for scripting.\n" + "Make sure %s is added to the SerializeContext and reflected to the BehaviorContext\n" + "For example, verify these two exist and are being called in a Reflect function:\n" + "serializeContext->Class<%s>();\n" + "behaviorContext->Class<%s>();\n" + "%s will not be available for scripting unless these requirements are met." + , arg->m_name, method->m_name.c_str(), arg->m_name, arg->m_name, arg->m_name, method->m_name.c_str()); m_fromLua.push_back(AZStd::make_pair(fromStack, argClass)); } @@ -5066,13 +5073,26 @@ LUA_API const Node* lua_getDummyNode() // Check all constructors if they have use ScriptDataContext and if so choose this one if (!customConstructorMethod) { + int overrideIndex = -1; + AZ::AttributeReader(nullptr, FindAttribute + ( Script::Attributes::DefaultConstructorOverrideIndex, behaviorClass->m_attributes)).Read(overrideIndex); + + int methodIndex = 0; for (BehaviorMethod* method : behaviorClass->m_constructors) { + if (methodIndex == overrideIndex) + { + customConstructorMethod = method; + break; + } + if (method->GetNumArguments() && method->GetArgument(method->GetNumArguments() - 1)->m_typeId == AZ::AzTypeInfo::Uuid()) { customConstructorMethod = method; break; } + + ++methodIndex; } } diff --git a/Code/Framework/AzCore/AzCore/Script/ScriptContextAttributes.h b/Code/Framework/AzCore/AzCore/Script/ScriptContextAttributes.h index 9807f0af39..e238289ef5 100644 --- a/Code/Framework/AzCore/AzCore/Script/ScriptContextAttributes.h +++ b/Code/Framework/AzCore/AzCore/Script/ScriptContextAttributes.h @@ -21,6 +21,7 @@ namespace AZ static constexpr AZ::Crc32 ClassNameOverride = AZ_CRC_CE("ScriptClassNameOverride"); ///< Provide a custom name for script reflection, that doesn't match the behavior Context name static constexpr AZ::Crc32 MethodOverride = AZ_CRC_CE("ScriptFunctionOverride"); ///< Use a custom function in the attribute instead of the function static constexpr AZ::Crc32 ConstructorOverride = AZ_CRC_CE("ConstructorOverride"); ///< You can provide a custom constructor to be called when created from Lua script + static constexpr AZ::Crc32 DefaultConstructorOverrideIndex = AZ_CRC_CE("DefaultConstructorOverrideIndex"); ///< Use a different class constructor as the default constructor in Lua static constexpr AZ::Crc32 EventHandlerCreationFunction = AZ_CRC_CE("EventHandlerCreationFunction"); ///< helps create a handler for any script target so that script functions can be used for AZ::Event signals static constexpr AZ::Crc32 GenericConstructorOverride = AZ_CRC_CE("GenericConstructorOverride"); ///< You can provide a custom constructor to be called when creating a script static constexpr AZ::Crc32 ReaderWriterOverride = AZ_CRC_CE("ReaderWriterOverride"); ///< paired with \ref ScriptContext::CustomReaderWriter allows you to customize read/write to Lua VM diff --git a/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp b/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp index d34f43e433..4b71c57682 100644 --- a/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp +++ b/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -169,61 +170,77 @@ ScriptContext* ScriptSystemComponent::AddContext(ScriptContext* context, int ga ScriptContext* ScriptSystemComponent::AddContextWithId(ScriptContextId id) { AZ_Assert(m_contexts.empty() || id != ScriptContextIds::DefaultScriptContextId, "Default script context ID is reserved! Please provide a Unique context ID for you ScriptContext!"); - if (GetContext(id) == nullptr) + if (GetContext(id) != nullptr) { - m_contexts.emplace_back(); - ContextContainer& cc = m_contexts.back(); - cc.m_context = aznew ScriptContext(id); - cc.m_isOwner = true; - cc.m_garbageCollectorSteps = m_defaultGarbageCollectorSteps; - - cc.m_context->SetRequireHook(AZStd::bind(&ScriptSystemComponent::DefaultRequireHook, this, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3)); - - if (id != ScriptContextIds::CryScriptContextId) + return nullptr; + } + m_contexts.emplace_back(); + ContextContainer& cc = m_contexts.back(); + cc.m_context = aznew ScriptContext(id); + cc.m_isOwner = true; + cc.m_garbageCollectorSteps = m_defaultGarbageCollectorSteps; + cc.m_context->SetRequireHook( + [this](lua_State* lua, ScriptContext* context, const char* module) -> int { - // Reflect script classes - ComponentApplication* app = nullptr; - EBUS_EVENT_RESULT(app, ComponentApplicationBus, GetApplication); - if (app && app->GetDescriptor().m_enableScriptReflection) + return DefaultRequireHook(lua, context, module); + }); + + if (id != ScriptContextIds::CryScriptContextId) + { + // Reflect script classes + ComponentApplication* app = nullptr; + EBUS_EVENT_RESULT(app, ComponentApplicationBus, GetApplication); + if (app && app->GetDescriptor().m_enableScriptReflection) + { + if (app->GetBehaviorContext()) { - if (app->GetBehaviorContext()) - { - cc.m_context->BindTo(app->GetBehaviorContext()); - } - else - { - AZ_Error("Script", false, "We are asked to enabled scripting, but the Applicaion has no BehaviorContext! Scripting relies on BehaviorContext!"); - } + cc.m_context->BindTo(app->GetBehaviorContext()); + } + else + { + AZ_Error("Script", false, "We are asked to enabled scripting, but the Applicaion has no BehaviorContext! Scripting relies on BehaviorContext!"); } } - - return cc.m_context; } - return nullptr; + return cc.m_context; } void ScriptSystemComponent::RestoreDefaultRequireHook(ScriptContextId id) { - if (auto context = GetContext(id)) + auto context = GetContext(id); + if (!context) { - for (auto& inMemoryModule : m_inMemoryModules) - { - ClearAssetReferences(inMemoryModule.second->GetId()); - } - - m_inMemoryModules.clear(); - context->SetRequireHook(AZStd::bind(&ScriptSystemComponent::DefaultRequireHook, this, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3)); + return; } + + for (auto& inMemoryModule : m_inMemoryModules) + { + ClearAssetReferences(inMemoryModule.second->GetId()); + } + + m_inMemoryModules.clear(); + context->SetRequireHook( + [this](lua_State* lua, ScriptContext* context, const char* module) -> int + { + return DefaultRequireHook(lua, context, module); + }); } void ScriptSystemComponent::UseInMemoryRequireHook(const InMemoryScriptModules& modules, ScriptContextId id) { - if (auto context = GetContext(id)) + auto context = GetContext(id); + if (nullptr == context) { - m_inMemoryModules = modules; - context->SetRequireHook(AZStd::bind(&ScriptSystemComponent::InMemoryRequireHook, this, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3)); + return; } + + m_inMemoryModules = modules; + context->SetRequireHook( + [this](lua_State* lua, ScriptContext* context, const char* module) -> int + { + return InMemoryRequireHook(lua, context, module); + }); } //========================================================================= @@ -927,6 +944,7 @@ void ScriptSystemComponent::Reflect(ReflectContext* reflection) // reflect default entity MathReflect(behaviorContext); ScriptDebug::Reflect(behaviorContext); + Debug::ProfilerReflect(behaviorContext); Debug::TraceReflect(behaviorContext); behaviorContext->Class("Platform") diff --git a/Code/Framework/AzCore/AzCore/ScriptCanvas/ScriptCanvasOnDemandNames.cpp b/Code/Framework/AzCore/AzCore/ScriptCanvas/ScriptCanvasOnDemandNames.cpp index b0d141bba9..d0bea11432 100644 --- a/Code/Framework/AzCore/AzCore/ScriptCanvas/ScriptCanvasOnDemandNames.cpp +++ b/Code/Framework/AzCore/AzCore/ScriptCanvas/ScriptCanvasOnDemandNames.cpp @@ -28,133 +28,130 @@ #include #include -namespace AZ +namespace AZ::ScriptCanvasOnDemandReflection { - namespace ScriptCanvasOnDemandReflection + // the use of this might have to come at the end of on demand reflection...instead of instantly + // basically, it required that dependent classes are reflected first, I'm not sure they are yet. + AZStd::string GetPrettyNameForAZTypeId(AZ::BehaviorContext& context, AZ::Uuid typeId) { - // the use of this might have to come at the end of on demand reflection...instead of instantly - // basically, it required that dependent classes are reflected first, I'm not sure they are yet. - AZStd::string GetPrettyNameForAZTypeId(AZ::BehaviorContext& context, AZ::Uuid typeId) + // return capitalized versions of what we need, otherwise just the regular name + // then strip all the stuff + if (typeId == azrtti_typeid()) { - // return capitalized versions of what we need, otherwise just the regular name - // then strip all the stuff - if (typeId == azrtti_typeid()) + return "AABB"; + } + else if (typeId == azrtti_typeid()) + { + return "Boolean"; + } + else if (typeId == azrtti_typeid()) + { + return "Color"; + } + else if (typeId == azrtti_typeid()) + { + return "CRC"; + } + else if (typeId == azrtti_typeid()) + { + return "EntityId"; + } + else if (typeId == azrtti_typeid()) + { + return "Matrix3x3"; + } + else if (typeId == azrtti_typeid()) + { + return "Matrix4x4"; + } + else if (typeId == azrtti_typeid()) + { + return "Number:s8"; + } + else if (typeId == azrtti_typeid()) + { + return "Number:s16"; + } + else if (typeId == azrtti_typeid()) + { + return "Number:s32"; + } + else if (typeId == azrtti_typeid()) + { + return "Number:s64"; + } + else if (typeId == azrtti_typeid()) + { + return "Number:u8"; + } + else if (typeId == azrtti_typeid()) + { + return "Number:u16"; + } + else if (typeId == azrtti_typeid()) + { + return "Number:u32"; + } + else if (typeId == azrtti_typeid()) + { + return "Number:u64"; + } + else if (typeId == azrtti_typeid()) + { + return "Number:float"; + } + else if (typeId == azrtti_typeid()) + { + return "Number"; + } + else if (typeId == azrtti_typeid()) + { + return "OBB"; + } + else if (typeId == azrtti_typeid()) + { + return "Plane"; + } + else if (typeId == azrtti_typeid()) + { + return "Quaternion"; + } + else if (typeId == azrtti_typeid() || typeId == azrtti_typeid()) + { + return "String"; + } + else if (typeId == azrtti_typeid()) + { + return "Transform"; + } + else if (typeId == azrtti_typeid()) + { + return "Vector2"; + } + else if (typeId == azrtti_typeid()) + { + return "Vector3"; + } + else if (typeId == azrtti_typeid()) + { + return "Vector4"; + } + else + { + auto bcClassIter = context.m_typeToClassMap.find(typeId); + if (bcClassIter != context.m_typeToClassMap.end()) { - return "AABB"; - } - else if (typeId == azrtti_typeid()) - { - return "Boolean"; - } - else if (typeId == azrtti_typeid()) - { - return "Color"; - } - else if (typeId == azrtti_typeid()) - { - return "CRC"; - } - else if (typeId == azrtti_typeid()) - { - return "EntityId"; - } - else if (typeId == azrtti_typeid()) - { - return "Matrix3x3"; - } - else if (typeId == azrtti_typeid()) - { - return "Matrix4x4"; - } - else if (typeId == azrtti_typeid()) - { - return "Number:s8"; - } - else if (typeId == azrtti_typeid()) - { - return "Number:s16"; - } - else if (typeId == azrtti_typeid()) - { - return "Number:s32"; - } - else if (typeId == azrtti_typeid()) - { - return "Number:s64"; - } - else if (typeId == azrtti_typeid()) - { - return "Number:u8"; - } - else if (typeId == azrtti_typeid()) - { - return "Number:u16"; - } - else if (typeId == azrtti_typeid()) - { - return "Number:u32"; - } - else if (typeId == azrtti_typeid()) - { - return "Number:u64"; - } - else if (typeId == azrtti_typeid()) - { - return "Number:float"; - } - else if (typeId == azrtti_typeid()) - { - return "Number"; - } - else if (typeId == azrtti_typeid()) - { - return "OBB"; - } - else if (typeId == azrtti_typeid()) - { - return "Plane"; - } - else if (typeId == azrtti_typeid()) - { - return "Quaternion"; - } - else if (typeId == azrtti_typeid() || typeId == azrtti_typeid()) - { - return "String"; - } - else if (typeId == azrtti_typeid()) - { - return "Transform"; - } - else if (typeId == azrtti_typeid()) - { - return "Vector2"; - } - else if (typeId == azrtti_typeid()) - { - return "Vector3"; - } - else if (typeId == azrtti_typeid()) - { - return "Vector4"; + const AZ::BehaviorClass& bcClass = *(bcClassIter->second); + AZStd::string uglyName = bcClass.m_name; + AZ::StringFunc::Replace(uglyName, "AZStd::", "", true); + AZ::StringFunc::Replace(uglyName, "AZ::", "", true); + AZ::StringFunc::Replace(uglyName, "::", ".", true); + return uglyName; } else { - auto bcClassIter = context.m_typeToClassMap.find(typeId); - if (bcClassIter != context.m_typeToClassMap.end()) - { - const AZ::BehaviorClass& bcClass = *(bcClassIter->second); - AZStd::string uglyName = bcClass.m_name; - AZ::StringFunc::Replace(uglyName, "AZStd::", "", true); - AZ::StringFunc::Replace(uglyName, "AZ::", "", true); - AZ::StringFunc::Replace(uglyName, "::", ".", true); - return uglyName; - } - else - { - return "Invalid"; - } + return "Invalid"; } } - } // namespace ScriptCanvasOnDemandReflection -} // namespace AZ + } +} // namespace AZ::ScriptCanvasOnDemandReflection diff --git a/Code/Framework/AzCore/AzCore/Serialization/AZStdContainers.inl b/Code/Framework/AzCore/AzCore/Serialization/AZStdContainers.inl index 2a00652488..a633af22da 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/AZStdContainers.inl +++ b/Code/Framework/AzCore/AzCore/Serialization/AZStdContainers.inl @@ -54,12 +54,8 @@ namespace AZStd namespace AZ { - //template - //class ScriptProperty; - namespace Internal { - template void SetupClassElementFromType(SerializeContext::ClassElement& classElement) { @@ -86,8 +82,7 @@ namespace AZ { auto uuid = AzTypeInfo::Uuid(); - using ContainerType = AttributeContainerType; - classElement.m_attributes.emplace_back(AZ_CRC("EnumType", 0xb177e1b5), CreateModuleAttribute(AZStd::move(uuid))); + classElement.m_attributes.emplace_back(AZ_CRC("EnumType", 0xb177e1b5), CreateModuleAttribute(AZStd::move(uuid))); } } @@ -648,7 +643,6 @@ namespace AZ // Register our key type within an lvalue to rvalue wrapper as an attribute AZ::TypeId uuid = azrtti_typeid(); - using ContainerType = AttributeContainerType; /** * This should technically bind the reference value from the GetCurrentSerializeContextModule() call @@ -658,7 +652,7 @@ namespace AZ */ m_classElement.m_attributes.set_allocator(AZStdFunctorAllocator([]() -> IAllocatorAllocate& { return GetCurrentSerializeContextModule().GetAllocator(); })); - m_classElement.m_attributes.emplace_back(AZ_CRC("KeyType", 0x15bc5303), CreateModuleAttribute(AZStd::move(uuid))); + m_classElement.m_attributes.emplace_back(AZ_CRC("KeyType", 0x15bc5303), CreateModuleAttribute(AZStd::move(uuid))); } // Reflect our wrapped key and value types to serializeContext so that may later be used diff --git a/Code/Framework/AzCore/AzCore/Serialization/DataOverlayProviderMsgs.cpp b/Code/Framework/AzCore/AzCore/Serialization/DataOverlayProviderMsgs.cpp index 37f4623301..58f86bf831 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/DataOverlayProviderMsgs.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/DataOverlayProviderMsgs.cpp @@ -19,8 +19,11 @@ namespace AZ nodeStack.push_back(m_dataContainer); SerializeContext::EnumerateInstanceCallContext callContext( - AZStd::bind(&DataOverlayTarget::ElementBegin, this, &nodeStack, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3), - AZStd::bind(&DataOverlayTarget::ElementEnd, this, &nodeStack), + [this, &nodeStack](void* instancePointer, const SerializeContext::ClassData* classData, const SerializeContext::ClassElement* classElement)->bool + { + return ElementBegin(&nodeStack, instancePointer, classData, classElement); + }, + [this, &nodeStack]()->bool { return ElementEnd(&nodeStack); }, m_sc, SerializeContext::ENUM_ACCESS_FOR_READ, m_errorLogger diff --git a/Code/Framework/AzCore/AzCore/Serialization/DataPatch.cpp b/Code/Framework/AzCore/AzCore/Serialization/DataPatch.cpp index 6f1635148c..d27a5005b4 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/DataPatch.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/DataPatch.cpp @@ -36,7 +36,7 @@ namespace AZ class DataNode { public: - typedef AZStd::list ChildDataNodes; + using ChildDataNodes = AZStd::list; DataNode() { @@ -148,25 +148,28 @@ namespace AZ m_root.Reset(); m_currentNode = nullptr; - if (m_context && rootClassPtr) + if (!m_context || !rootClassPtr) { - SerializeContext::EnumerateInstanceCallContext callContext( - AZStd::bind(&DataNodeTree::BeginNode, this, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3), - AZStd::bind(&DataNodeTree::EndNode, this), - m_context, - SerializeContext::ENUM_ACCESS_FOR_READ, - nullptr - ); - - m_context->EnumerateInstanceConst( - &callContext, - rootClassPtr, - rootClassId, - nullptr, - nullptr - ); + return; } + SerializeContext::EnumerateInstanceCallContext callContext( + [this](void* instancePointer, const SerializeContext::ClassData* classData, const SerializeContext::ClassElement* classElement)->bool + { + return BeginNode(instancePointer, classData, classElement); + }, + [this]()->bool { return EndNode(); }, + m_context, + SerializeContext::ENUM_ACCESS_FOR_READ, + nullptr + ); + m_context->EnumerateInstanceConst( + &callContext, + rootClassPtr, + rootClassId, + nullptr, + nullptr + ); m_currentNode = nullptr; } diff --git a/Code/Framework/AzCore/AzCore/Serialization/EditContext.cpp b/Code/Framework/AzCore/AzCore/Serialization/EditContext.cpp index d0c433df16..8a4688d748 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/EditContext.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/EditContext.cpp @@ -34,7 +34,7 @@ namespace AZ } classIt->ClearElements(); } - for (auto enumIt : m_enumData) + for (auto& enumIt : m_enumData) { enumIt.second.ClearAttributes(); } @@ -103,7 +103,7 @@ namespace AZ //========================================================================= void ElementData::ClearAttributes() { - for (auto attrib : m_attributes) + for (auto& attrib : m_attributes) { delete attrib.second; } diff --git a/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl b/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl index 4f8c67f058..27f27dd6dd 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl +++ b/Code/Framework/AzCore/AzCore/Serialization/EditContextConstants.inl @@ -133,6 +133,8 @@ namespace AZ const static AZ::Crc32 AllowClearAsset = AZ_CRC("AllowClearAsset", 0x24827182); // Show the name of the asset that was produced from the source asset const static AZ::Crc32 ShowProductAssetFileName = AZ_CRC("ShowProductAssetFileName"); + //! Regular expression pattern filter for source files + const static AZ::Crc32 SourceAssetFilterPattern = AZ_CRC_CE("SourceAssetFilterPattern"); //! Component icon attributes const static AZ::Crc32 Icon = AZ_CRC("Icon", 0x659429db); diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/BaseJsonSerializer.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/BaseJsonSerializer.cpp index 7309955a1c..4f5657a328 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/BaseJsonSerializer.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/BaseJsonSerializer.cpp @@ -204,6 +204,22 @@ namespace AZ // BaseJsonSerializer // + JsonSerializationResult::Result BaseJsonSerializer::Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue, + JsonDeserializerContext& context) + { + JsonSerializationResult::ResultCode result(JsonSerializationResult::Tasks::ReadField); + result.Combine(ContinueLoading(outputValue, outputValueTypeId, inputValue, context, ContinuationFlags::IgnoreTypeSerializer)); + return context.Report(result, "Ignoring custom serialization during load"); + } + + JsonSerializationResult::Result BaseJsonSerializer::Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, + const Uuid& valueTypeId, JsonSerializerContext& context) + { + JsonSerializationResult::ResultCode result(JsonSerializationResult::Tasks::WriteValue); + result.Combine(ContinueStoring(outputValue, inputValue, defaultValue, valueTypeId, context, ContinuationFlags::IgnoreTypeSerializer)); + return context.Report(result, "Ignoring custom serialization during store"); + } + BaseJsonSerializer::OperationFlags BaseJsonSerializer::GetOperationsFlags() const { return OperationFlags::None; diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/BaseJsonSerializer.h b/Code/Framework/AzCore/AzCore/Serialization/Json/BaseJsonSerializer.h index 7d2af01c16..4e8f545367 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/BaseJsonSerializer.h +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/BaseJsonSerializer.h @@ -180,13 +180,16 @@ namespace AZ //! Transforms the data from the rapidjson Value to outputValue, if the conversion is possible and supported. //! The serializer is responsible for casting to the proper type and safely writing to the outputValue memory. + //! \note The default implementation is to load the object ignoring a custom serializers for the type, which allows for custom serializers + //! to modify the object after all default loading has occurred. virtual JsonSerializationResult::Result Load(void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue, - JsonDeserializerContext& context) = 0; + JsonDeserializerContext& context); //! Write the input value to a rapidjson value if the default value is not null and doesn't match the input value, otherwise //! an error is returned and sets the rapidjson value to a null value. + //! \note The default implementation is to store the object ignoring custom serializers. virtual JsonSerializationResult::Result Store(rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, - const Uuid& valueTypeId, JsonSerializerContext& context) = 0; + const Uuid& valueTypeId, JsonSerializerContext& context); //! Returns the operation flags which tells the Json Serialization how this custom json serializer can be used. virtual OperationFlags GetOperationsFlags() const; diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonDeserializer.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonDeserializer.cpp index a741294544..4e80d9ba69 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonDeserializer.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonDeserializer.cpp @@ -759,6 +759,7 @@ namespace AZ else { typeIdResult.m_determination = JsonDeserializer::TypeIdDetermination::FailedToDetermine; + typeIdResult.m_typeId = Uuid::CreateNull(); } } else if (input.IsString()) @@ -768,6 +769,7 @@ namespace AZ else { typeIdResult.m_determination = JsonDeserializer::TypeIdDetermination::FailedToDetermine; + typeIdResult.m_typeId = Uuid::CreateNull(); } switch (typeIdResult.m_determination) diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.cpp index 822c1c43d5..16bad5a466 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonSerializationResult.cpp @@ -10,265 +10,264 @@ #include #include -namespace AZ +namespace AZ::JsonSerializationResult::Internal { - namespace JsonSerializationResult + template + void AppendToString(AZ::JsonSerializationResult::ResultCode code, + StringType& target, AZStd::string_view path) { - namespace Internal + if (code.GetTask() == static_cast(0)) { - template - void AppendToString(AZ::JsonSerializationResult::ResultCode code, - StringType& target, AZStd::string_view path) - { - if (code.GetTask() == static_cast(0)) - { - target.append("The result code wasn't initialized"); - return; - } - - target.append("The operation "); - switch (code.GetProcessing()) - { - case Processing::Halted: - target.append("has halted during "); - break; - case Processing::Altered: - target.append("has taken an alternative approach for "); - break; - case Processing::PartialAlter: - target.append("has taken a partially alternative approach for "); - break; - case Processing::Completed: - target.append("has completed "); - break; - default: - target.append("has unknown processing status for "); - break; - } - - switch (code.GetTask()) - { - case Tasks::RetrieveInfo: - target.append("a retrieve info operation "); - break; - case Tasks::CreateDefault: - target.append("a create default operation "); - break; - case Tasks::Convert: - target.append("a convert operation "); - break; - case Tasks::ReadField: - target.append("a read field operation "); - break; - case Tasks::WriteValue: - target.append("a write value operation "); - break; - case Tasks::Merge: - target.append("a merge operation "); - break; - case Tasks::CreatePatch: - target.append("a create patch operation "); - break; - case Tasks::Import: - target.append("an import operation"); - break; - default: - target.append("an unknown operation "); - break; - } - - if (!path.empty()) - { - target.append("for '"); - target.append(path.begin(), path.end()); - target.append("' "); - } - - switch (code.GetOutcome()) - { - case Outcomes::Success: - target.append("which resulted in success"); - break; - case Outcomes::DefaultsUsed: - target.append("by using only default values"); - break; - case Outcomes::PartialDefaults: - target.append("by using one or more default values"); - break; - case Outcomes::Skipped: - target.append("because a field or value was skipped"); - break; - case Outcomes::PartialSkip: - target.append("because one or more fields or values were skipped"); - break; - case Outcomes::Unavailable: - target.append("because the target was unavailable"); - break; - case Outcomes::Unsupported: - target.append("because the action was unsupported"); - break; - case Outcomes::TypeMismatch: - target.append("because the source and target are unrelated types"); - break; - case Outcomes::TestFailed: - target.append("because a test against a value failed"); - break; - case Outcomes::Missing: - target.append("because a required field or value was missing"); - break; - case Outcomes::Invalid: - target.append("because a field or element has an invalid value"); - break; - case Outcomes::Unknown: - target.append("because information was missing"); - break; - case Outcomes::Catastrophic: - target.append("because a catastrophic issue was encountered"); - break; - default: - break; - } - } - } // namespace JsonSerializationResultInternal - - ResultCode::ResultCode(Tasks task) - : m_code(0) - { - m_options.m_task = task; + target.append("The result code wasn't initialized"); + return; } - ResultCode::ResultCode(uint32_t code) - : m_code(code) - {} - - ResultCode::ResultCode(Tasks task, Outcomes outcome) + target.append("The operation "); + switch (code.GetProcessing()) { - m_options.m_task = task; - switch (outcome) - { - case Outcomes::Success: // fall through - case Outcomes::Skipped: // fall through - case Outcomes::PartialSkip: // fall through - case Outcomes::DefaultsUsed: // fall through - case Outcomes::PartialDefaults: - m_options.m_processing = Processing::Completed; - break; - case Outcomes::Unavailable: // fall through - case Outcomes::Unsupported: - m_options.m_processing = Processing::Altered; - break; - case Outcomes::TypeMismatch: // fall through - case Outcomes::TestFailed: // fall through - case Outcomes::Missing: // fall through - case Outcomes::Invalid: // fall through - case Outcomes::Unknown: // fall through - case Outcomes::Catastrophic: // fall through - default: - m_options.m_processing = Processing::Halted; - break; - } - m_options.m_outcome = outcome; + case Processing::Halted: + target.append("has halted during "); + break; + case Processing::Altered: + target.append("has taken an alternative approach for "); + break; + case Processing::PartialAlter: + target.append("has taken a partially alternative approach for "); + break; + case Processing::Completed: + target.append("has completed "); + break; + default: + target.append("has unknown processing status for "); + break; } - bool ResultCode::HasDoneWork() const + switch (code.GetTask()) { - return m_options.m_outcome != static_cast(0); + case Tasks::RetrieveInfo: + target.append("a retrieve info operation "); + break; + case Tasks::CreateDefault: + target.append("a create default operation "); + break; + case Tasks::Convert: + target.append("a convert operation "); + break; + case Tasks::ReadField: + target.append("a read field operation "); + break; + case Tasks::WriteValue: + target.append("a write value operation "); + break; + case Tasks::Merge: + target.append("a merge operation "); + break; + case Tasks::CreatePatch: + target.append("a create patch operation "); + break; + case Tasks::Import: + target.append("an import operation"); + break; + default: + target.append("an unknown operation "); + break; } - ResultCode& ResultCode::Combine(ResultCode other) + if (!path.empty()) { - *this = Combine(*this, other); - return *this; + target.append("for '"); + target.append(path.begin(), path.end()); + target.append("' "); } - ResultCode& ResultCode::Combine(const Result& other) + switch (code.GetOutcome()) { - *this = Combine(*this, other.GetResultCode()); - return *this; + case Outcomes::Success: + target.append("which resulted in success"); + break; + case Outcomes::DefaultsUsed: + target.append("by using only default values"); + break; + case Outcomes::PartialDefaults: + target.append("by using one or more default values"); + break; + case Outcomes::Skipped: + target.append("because a field or value was skipped"); + break; + case Outcomes::PartialSkip: + target.append("because one or more fields or values were skipped"); + break; + case Outcomes::Unavailable: + target.append("because the target was unavailable"); + break; + case Outcomes::Unsupported: + target.append("because the action was unsupported"); + break; + case Outcomes::TypeMismatch: + target.append("because the source and target are unrelated types"); + break; + case Outcomes::TestFailed: + target.append("because a test against a value failed"); + break; + case Outcomes::Missing: + target.append("because a required field or value was missing"); + break; + case Outcomes::Invalid: + target.append("because a field or element has an invalid value"); + break; + case Outcomes::Unknown: + target.append("because information was missing"); + break; + case Outcomes::Catastrophic: + target.append("because a catastrophic issue was encountered"); + break; + default: + break; } + } +} // namespace AZ::JsonSerializationResult::Internal - ResultCode ResultCode::Combine(ResultCode lhs, ResultCode rhs) +namespace AZ::JsonSerializationResult +{ + + ResultCode::ResultCode(Tasks task) + : m_code(0) + { + m_options.m_task = task; + } + + ResultCode::ResultCode(uint32_t code) + : m_code(code) + {} + + ResultCode::ResultCode(Tasks task, Outcomes outcome) + { + m_options.m_task = task; + switch (outcome) { - ResultCode result = ResultCode(AZStd::max(lhs.m_code, rhs.m_code)); - - if ((lhs.m_options.m_outcome == Outcomes::Success && rhs.m_options.m_outcome == Outcomes::DefaultsUsed) || - (lhs.m_options.m_outcome == Outcomes::DefaultsUsed && rhs.m_options.m_outcome == Outcomes::Success)) - { - result.m_options.m_outcome = Outcomes::PartialDefaults; - } - else if ((lhs.m_options.m_outcome == Outcomes::Success && rhs.m_options.m_outcome == Outcomes::Skipped) || - (lhs.m_options.m_outcome == Outcomes::Skipped && rhs.m_options.m_outcome == Outcomes::Success)) - { - result.m_options.m_outcome = Outcomes::PartialSkip; - } - - if ((lhs.m_options.m_processing == Processing::Completed && rhs.m_options.m_processing == Processing::Altered) || - (lhs.m_options.m_processing == Processing::Altered && rhs.m_options.m_processing == Processing::Completed)) - { - result.m_options.m_processing = Processing::PartialAlter; - } - - return result; + case Outcomes::Success: // fall through + case Outcomes::Skipped: // fall through + case Outcomes::PartialSkip: // fall through + case Outcomes::DefaultsUsed: // fall through + case Outcomes::PartialDefaults: + m_options.m_processing = Processing::Completed; + break; + case Outcomes::Unavailable: // fall through + case Outcomes::Unsupported: + m_options.m_processing = Processing::Altered; + break; + case Outcomes::TypeMismatch: // fall through + case Outcomes::TestFailed: // fall through + case Outcomes::Missing: // fall through + case Outcomes::Invalid: // fall through + case Outcomes::Unknown: // fall through + case Outcomes::Catastrophic: // fall through + default: + m_options.m_processing = Processing::Halted; + break; } + m_options.m_outcome = outcome; + } - Tasks ResultCode::GetTask() const + bool ResultCode::HasDoneWork() const + { + return m_options.m_outcome != static_cast(0); + } + + ResultCode& ResultCode::Combine(ResultCode other) + { + *this = Combine(*this, other); + return *this; + } + + ResultCode& ResultCode::Combine(const Result& other) + { + *this = Combine(*this, other.GetResultCode()); + return *this; + } + + ResultCode ResultCode::Combine(ResultCode lhs, ResultCode rhs) + { + ResultCode result = ResultCode(AZStd::max(lhs.m_code, rhs.m_code)); + + if ((lhs.m_options.m_outcome == Outcomes::Success && rhs.m_options.m_outcome == Outcomes::DefaultsUsed) || + (lhs.m_options.m_outcome == Outcomes::DefaultsUsed && rhs.m_options.m_outcome == Outcomes::Success)) { - return m_options.m_task; + result.m_options.m_outcome = Outcomes::PartialDefaults; } - - Processing ResultCode::GetProcessing() const + else if ((lhs.m_options.m_outcome == Outcomes::Success && rhs.m_options.m_outcome == Outcomes::Skipped) || + (lhs.m_options.m_outcome == Outcomes::Skipped && rhs.m_options.m_outcome == Outcomes::Success)) { - return m_options.m_processing == static_cast(0) ? - Processing::Completed : m_options.m_processing; + result.m_options.m_outcome = Outcomes::PartialSkip; } - Outcomes ResultCode::GetOutcome() const + if ((lhs.m_options.m_processing == Processing::Completed && rhs.m_options.m_processing == Processing::Altered) || + (lhs.m_options.m_processing == Processing::Altered && rhs.m_options.m_processing == Processing::Completed)) { - return m_options.m_outcome == static_cast(0) ? - Outcomes::DefaultsUsed : m_options.m_outcome; + result.m_options.m_processing = Processing::PartialAlter; } - void ResultCode::AppendToString(AZ::OSString& target, AZStd::string_view path) const - { - Internal::AppendToString(*this, target, path); - } + return result; + } - void ResultCode::AppendToString(AZStd::string& target, AZStd::string_view path) const - { - Internal::AppendToString(*this, target, path); - } + Tasks ResultCode::GetTask() const + { + return m_options.m_task; + } - AZStd::string ResultCode::ToString(AZStd::string_view path) const - { - AZStd::string result; - AppendToString(result, path); - return result; - } + Processing ResultCode::GetProcessing() const + { + return m_options.m_processing == static_cast(0) ? + Processing::Completed : m_options.m_processing; + } - AZ::OSString ResultCode::ToOSString(AZStd::string_view path) const - { - AZ::OSString result; - AppendToString(result, path); - return result; - } + Outcomes ResultCode::GetOutcome() const + { + return m_options.m_outcome == static_cast(0) ? + Outcomes::DefaultsUsed : m_options.m_outcome; + } + + void ResultCode::AppendToString(AZ::OSString& target, AZStd::string_view path) const + { + Internal::AppendToString(*this, target, path); + } + + void ResultCode::AppendToString(AZStd::string& target, AZStd::string_view path) const + { + Internal::AppendToString(*this, target, path); + } + + AZStd::string ResultCode::ToString(AZStd::string_view path) const + { + AZStd::string result; + AppendToString(result, path); + return result; + } + + AZ::OSString ResultCode::ToOSString(AZStd::string_view path) const + { + AZ::OSString result; + AppendToString(result, path); + return result; + } - Result::Result(const JsonIssueCallback& callback, AZStd::string_view message, ResultCode result, AZStd::string_view path) - : m_result(callback(message, result, path)) - {} + Result::Result(const JsonIssueCallback& callback, AZStd::string_view message, ResultCode result, AZStd::string_view path) + : m_result(callback(message, result, path)) + {} - Result::Result(const JsonIssueCallback& callback, AZStd::string_view message, Tasks task, Outcomes outcome, AZStd::string_view path) - : m_result(callback(message, ResultCode(task, outcome), path)) - {} + Result::Result(const JsonIssueCallback& callback, AZStd::string_view message, Tasks task, Outcomes outcome, AZStd::string_view path) + : m_result(callback(message, ResultCode(task, outcome), path)) + {} - Result::operator ResultCode() const - { - return m_result; - } + Result::operator ResultCode() const + { + return m_result; + } - ResultCode Result::GetResultCode() const - { - return m_result; - } - } // namespace JsonSerializationResult -} // namespace AZ + ResultCode Result::GetResultCode() const + { + return m_result; + } + +} // namespace AZ::JsonSerializationResult diff --git a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonUtils.cpp b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonUtils.cpp index 1a4e7b3e80..6d7edb0716 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/Json/JsonUtils.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/Json/JsonUtils.cpp @@ -24,438 +24,435 @@ #include -namespace AZ +namespace AZ::JsonSerializationUtils { - namespace JsonSerializationUtils + static const char* FileTypeTag = "Type"; + static const char* FileType = "JsonSerialization"; + static const char* VersionTag = "Version"; + static const char* ClassNameTag = "ClassName"; + static const char* ClassDataTag = "ClassData"; + + AZ::Outcome WriteJsonString(const rapidjson::Document& document, AZStd::string& jsonText, WriteJsonSettings settings) { - static const char* FileTypeTag = "Type"; - static const char* FileType = "JsonSerialization"; - static const char* VersionTag = "Version"; - static const char* ClassNameTag = "ClassName"; - static const char* ClassDataTag = "ClassData"; + AZ::IO::ByteContainerStream stream{&jsonText}; + return WriteJsonStream(document, stream, settings); + } - AZ::Outcome WriteJsonString(const rapidjson::Document& document, AZStd::string& jsonText, WriteJsonSettings settings) + AZ::Outcome WriteJsonFile(const rapidjson::Document& document, AZStd::string_view filePath, WriteJsonSettings settings) + { + // Write the json into memory first and then write the file, rather than passing a file stream to rapidjson. + // This should avoid creating a large number of micro-writes to the file. + AZStd::string fileContent; + auto outcome = WriteJsonString(document, fileContent, settings); + if (!outcome.IsSuccess()) { - AZ::IO::ByteContainerStream stream{&jsonText}; - return WriteJsonStream(document, stream, settings); + return outcome; } - AZ::Outcome WriteJsonFile(const rapidjson::Document& document, AZStd::string_view filePath, WriteJsonSettings settings) - { - // Write the json into memory first and then write the file, rather than passing a file stream to rapidjson. - // This should avoid creating a large number of micro-writes to the file. - AZStd::string fileContent; - auto outcome = WriteJsonString(document, fileContent, settings); - if (!outcome.IsSuccess()) - { - return outcome; - } + return AZ::Utils::WriteFile(fileContent, filePath); + } - return AZ::Utils::WriteFile(fileContent, filePath); + AZ::Outcome WriteJsonStream(const rapidjson::Document& document, IO::GenericStream& stream, WriteJsonSettings settings) + { + AZ::IO::RapidJSONStreamWriter jsonStreamWriter(&stream); + + rapidjson::PrettyWriter writer(jsonStreamWriter); + + if (settings.m_maxDecimalPlaces >= 0) + { + writer.SetMaxDecimalPlaces(settings.m_maxDecimalPlaces); } - AZ::Outcome WriteJsonStream(const rapidjson::Document& document, IO::GenericStream& stream, WriteJsonSettings settings) + if (document.Accept(writer)) { - AZ::IO::RapidJSONStreamWriter jsonStreamWriter(&stream); + return AZ::Success(); + } + else + { + return AZ::Failure(AZStd::string{"Json Writer failed"}); + } + } - rapidjson::PrettyWriter writer(jsonStreamWriter); - - if (settings.m_maxDecimalPlaces >= 0) - { - writer.SetMaxDecimalPlaces(settings.m_maxDecimalPlaces); - } - - if (document.Accept(writer)) - { - return AZ::Success(); - } - else - { - return AZ::Failure(AZStd::string{"Json Writer failed"}); - } + AZ::Outcome SaveObjectToStreamByType(const void* objectPtr, const Uuid& classId, IO::GenericStream& stream, + const void* defaultObjectPtr, const JsonSerializerSettings* settings) + { + if (!stream.CanWrite()) + { + return AZ::Failure(AZStd::string("The GenericStream can't be written to")); } - AZ::Outcome SaveObjectToStreamByType(const void* objectPtr, const Uuid& classId, IO::GenericStream& stream, - const void* defaultObjectPtr, const JsonSerializerSettings* settings) + JsonSerializerSettings saveSettings; + if (settings) { - if (!stream.CanWrite()) - { - return AZ::Failure(AZStd::string("The GenericStream can't be written to")); - } + saveSettings = *settings; + } - JsonSerializerSettings saveSettings; - if (settings) - { - saveSettings = *settings; - } - - AZ::SerializeContext* serializeContext = saveSettings.m_serializeContext; + AZ::SerializeContext* serializeContext = saveSettings.m_serializeContext; + if (!serializeContext) + { + AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext); if (!serializeContext) { - AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext); - if (!serializeContext) - { - return AZ::Failure(AZStd::string::format("Need SerializeContext for saving")); - } - saveSettings.m_serializeContext = serializeContext; + return AZ::Failure(AZStd::string::format("Need SerializeContext for saving")); } - - rapidjson::Document jsonDocument; - jsonDocument.SetObject(); - jsonDocument.AddMember(rapidjson::StringRef(FileTypeTag), rapidjson::StringRef(FileType), jsonDocument.GetAllocator()); - - rapidjson::Value serializedObject; - - JsonSerializationResult::ResultCode jsonResult = JsonSerialization::Store(serializedObject, jsonDocument.GetAllocator(), - objectPtr, defaultObjectPtr, classId, saveSettings); - - if (jsonResult.GetProcessing() != JsonSerializationResult::Processing::Completed) - { - return AZ::Failure(jsonResult.ToString("")); - } - - const SerializeContext::ClassData* classData = serializeContext->FindClassData(classId); - - jsonDocument.AddMember(rapidjson::StringRef(VersionTag), 1, jsonDocument.GetAllocator()); - jsonDocument.AddMember(rapidjson::StringRef(ClassNameTag), rapidjson::StringRef(classData->m_name), jsonDocument.GetAllocator()); - jsonDocument.AddMember(rapidjson::StringRef(ClassDataTag), AZStd::move(serializedObject), jsonDocument.GetAllocator()); - - AZ::IO::RapidJSONStreamWriter jsonStreamWriter(&stream); - rapidjson::PrettyWriter writer(jsonStreamWriter); - bool jsonWriteResult = jsonDocument.Accept(writer); - if (!jsonWriteResult) - { - return AZ::Failure(AZStd::string::format("Unable to write class %s with json serialization format'", - classId.ToString().data())); - } - - return AZ::Success(); + saveSettings.m_serializeContext = serializeContext; } - AZ::Outcome SaveObjectToFileByType(const void* classPtr, const Uuid& classId, const AZStd::string& filePath, - const void* defaultClassPtr, const JsonSerializerSettings* settings) + rapidjson::Document jsonDocument; + jsonDocument.SetObject(); + jsonDocument.AddMember(rapidjson::StringRef(FileTypeTag), rapidjson::StringRef(FileType), jsonDocument.GetAllocator()); + + rapidjson::Value serializedObject; + + JsonSerializationResult::ResultCode jsonResult = JsonSerialization::Store(serializedObject, jsonDocument.GetAllocator(), + objectPtr, defaultObjectPtr, classId, saveSettings); + + if (jsonResult.GetProcessing() != JsonSerializationResult::Processing::Completed) { - AZStd::vector buffer; - buffer.reserve(1024); - AZ::IO::ByteContainerStream > byteStream(&buffer); - auto saveResult = SaveObjectToStreamByType(classPtr, classId, byteStream, defaultClassPtr, settings); - if (saveResult.IsSuccess()) - { - AZ::IO::FileIOStream outputFileStream; - if (!outputFileStream.Open(filePath.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath | AZ::IO::OpenMode::ModeText)) - { - return AZ::Failure(AZStd::string::format("Error opening file '%s' for writing", filePath.c_str())); - } - outputFileStream.Write(buffer.size(), buffer.data()); - } - return saveResult; + return AZ::Failure(jsonResult.ToString("")); } - // Helper function to check whether the load outcome was success (for loading json serialization file) - bool WasLoadSuccess(JsonSerializationResult::Outcomes outcome) - { - return (outcome == JsonSerializationResult::Outcomes::Success - || outcome == JsonSerializationResult::Outcomes::DefaultsUsed - || outcome == JsonSerializationResult::Outcomes::PartialDefaults); - } - - AZ::Outcome PrepareDeserializerSettings(const JsonDeserializerSettings* inputSettings, JsonDeserializerSettings& returnSettings - , AZStd::string& deserializeError) - { - if (inputSettings) - { - returnSettings = *inputSettings; - } + const SerializeContext::ClassData* classData = serializeContext->FindClassData(classId); + jsonDocument.AddMember(rapidjson::StringRef(VersionTag), 1, jsonDocument.GetAllocator()); + jsonDocument.AddMember(rapidjson::StringRef(ClassNameTag), rapidjson::StringRef(classData->m_name), jsonDocument.GetAllocator()); + jsonDocument.AddMember(rapidjson::StringRef(ClassDataTag), AZStd::move(serializedObject), jsonDocument.GetAllocator()); + + AZ::IO::RapidJSONStreamWriter jsonStreamWriter(&stream); + rapidjson::PrettyWriter writer(jsonStreamWriter); + bool jsonWriteResult = jsonDocument.Accept(writer); + if (!jsonWriteResult) + { + return AZ::Failure(AZStd::string::format("Unable to write class %s with json serialization format'", + classId.ToString().data())); + } + + return AZ::Success(); + } + + AZ::Outcome SaveObjectToFileByType(const void* classPtr, const Uuid& classId, const AZStd::string& filePath, + const void* defaultClassPtr, const JsonSerializerSettings* settings) + { + AZStd::vector buffer; + buffer.reserve(1024); + AZ::IO::ByteContainerStream > byteStream(&buffer); + auto saveResult = SaveObjectToStreamByType(classPtr, classId, byteStream, defaultClassPtr, settings); + if (saveResult.IsSuccess()) + { + AZ::IO::FileIOStream outputFileStream; + if (!outputFileStream.Open(filePath.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath | AZ::IO::OpenMode::ModeText)) + { + return AZ::Failure(AZStd::string::format("Error opening file '%s' for writing", filePath.c_str())); + } + outputFileStream.Write(buffer.size(), buffer.data()); + } + return saveResult; + } + + // Helper function to check whether the load outcome was success (for loading json serialization file) + bool WasLoadSuccess(JsonSerializationResult::Outcomes outcome) + { + return (outcome == JsonSerializationResult::Outcomes::Success + || outcome == JsonSerializationResult::Outcomes::DefaultsUsed + || outcome == JsonSerializationResult::Outcomes::PartialDefaults); + } + + AZ::Outcome PrepareDeserializerSettings(const JsonDeserializerSettings* inputSettings, JsonDeserializerSettings& returnSettings + , AZStd::string& deserializeError) + { + if (inputSettings) + { + returnSettings = *inputSettings; + } + + if (!returnSettings.m_serializeContext) + { + AZ::ComponentApplicationBus::BroadcastResult(returnSettings.m_serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext); if (!returnSettings.m_serializeContext) { - AZ::ComponentApplicationBus::BroadcastResult(returnSettings.m_serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext); - if (!returnSettings.m_serializeContext) + return AZ::Failure(AZStd::string("Need SerializeContext for loading")); + } + } + + // Report unused data field as error by default + auto reporting = returnSettings.m_reporting; + auto issueReportingCallback = [&deserializeError, reporting](AZStd::string_view message, JsonSerializationResult::ResultCode result, AZStd::string_view target) -> JsonSerializationResult::ResultCode + { + using namespace JsonSerializationResult; + + if (!WasLoadSuccess(result.GetOutcome())) + { + // This if is a hack around fault in the JSON serialization system + // Jira: LY-106587 + if (message != "No part of the string could be interpreted as a uuid.") { - return AZ::Failure(AZStd::string("Need SerializeContext for loading")); + deserializeError.append(message); + deserializeError.append(AZStd::string::format(" '%s' \n", target.data())); } } - // Report unused data field as error by default - auto reporting = returnSettings.m_reporting; - auto issueReportingCallback = [&deserializeError, reporting](AZStd::string_view message, JsonSerializationResult::ResultCode result, AZStd::string_view target) -> JsonSerializationResult::ResultCode + if (reporting) { - using namespace JsonSerializationResult; + result = reporting(message, result, target); + } - if (!WasLoadSuccess(result.GetOutcome())) - { - // This if is a hack around fault in the JSON serialization system - // Jira: LY-106587 - if (message != "No part of the string could be interpreted as a uuid.") - { - deserializeError.append(message); - deserializeError.append(AZStd::string::format(" '%s' \n", target.data())); - } - } + return result; + }; - if (reporting) - { - result = reporting(message, result, target); - } + returnSettings.m_reporting = issueReportingCallback; - return result; - }; + return AZ::Success(); + } - returnSettings.m_reporting = issueReportingCallback; - return AZ::Success(); + AZ::Outcome ReadJsonString(AZStd::string_view jsonText) + { + if (jsonText.empty()) + { + return AZ::Failure(AZStd::string("Failed to parse JSON: input string is empty.")); } - - AZ::Outcome ReadJsonString(AZStd::string_view jsonText) + rapidjson::Document jsonDocument; + jsonDocument.Parse(jsonText.data(), jsonText.size()); + if (jsonDocument.HasParseError()) { - if (jsonText.empty()) + size_t lineNumber = 1; + + const size_t errorOffset = jsonDocument.GetErrorOffset(); + for (size_t searchOffset = jsonText.find('\n'); + searchOffset < errorOffset && searchOffset < AZStd::string::npos; + searchOffset = jsonText.find('\n', searchOffset + 1)) { - return AZ::Failure(AZStd::string("Failed to parse JSON: input string is empty.")); + lineNumber++; } - rapidjson::Document jsonDocument; - jsonDocument.Parse(jsonText.data(), jsonText.size()); - if (jsonDocument.HasParseError()) - { - size_t lineNumber = 1; + return AZ::Failure(AZStd::string::format("JSON parse error at line %zu: %s", lineNumber, rapidjson::GetParseError_En(jsonDocument.GetParseError()))); + } + else + { + return AZ::Success(AZStd::move(jsonDocument)); + } + } - const size_t errorOffset = jsonDocument.GetErrorOffset(); - for (size_t searchOffset = jsonText.find('\n'); - searchOffset < errorOffset && searchOffset < AZStd::string::npos; - searchOffset = jsonText.find('\n', searchOffset + 1)) - { - lineNumber++; - } - - return AZ::Failure(AZStd::string::format("JSON parse error at line %zu: %s", lineNumber, rapidjson::GetParseError_En(jsonDocument.GetParseError()))); - } - else - { - return AZ::Success(AZStd::move(jsonDocument)); - } + AZ::Outcome ReadJsonStream(IO::GenericStream& stream) + { + IO::SizeType length = stream.GetLength(); + + AZStd::vector memoryBuffer; + memoryBuffer.resize_no_construct(static_cast::size_type>(static_cast::size_type>(length) + 1)); + + IO::SizeType bytesRead = stream.Read(length, memoryBuffer.data()); + if (bytesRead != length) + { + return AZ::Failure(AZStd::string{"Cannot to read input stream."}); } - AZ::Outcome ReadJsonStream(IO::GenericStream& stream) + memoryBuffer.back() = 0; + + return ReadJsonString(AZStd::string_view{memoryBuffer.data(), memoryBuffer.size()}); + } + + AZ::Outcome ReadJsonFile(AZStd::string_view filePath, size_t maxFileSize) + { + // Read into memory first and then parse the json, rather than passing a file stream to rapidjson. + // This should avoid creating a large number of micro-reads from the file. + + auto readResult = AZ::Utils::ReadFile(filePath, maxFileSize); + if(!readResult.IsSuccess()) { - IO::SizeType length = stream.GetLength(); - - AZStd::vector memoryBuffer; - memoryBuffer.resize_no_construct(static_cast::size_type>(static_cast::size_type>(length) + 1)); - - IO::SizeType bytesRead = stream.Read(length, memoryBuffer.data()); - if (bytesRead != length) - { - return AZ::Failure(AZStd::string{"Cannot to read input stream."}); - } - - memoryBuffer.back() = 0; - - return ReadJsonString(AZStd::string_view{memoryBuffer.data(), memoryBuffer.size()}); + return AZ::Failure(readResult.GetError()); } - AZ::Outcome ReadJsonFile(AZStd::string_view filePath, size_t maxFileSize) + AZStd::string jsonContent = readResult.TakeValue(); + + auto result = ReadJsonString(jsonContent); + if (!result.IsSuccess()) { - // Read into memory first and then parse the json, rather than passing a file stream to rapidjson. - // This should avoid creating a large number of micro-reads from the file. + return AZ::Failure(AZStd::string::format("Failed to load '%.*s'. %s", AZ_STRING_ARG(filePath), result.GetError().c_str())); + } + else + { + return result; + } + } - auto readResult = AZ::Utils::ReadFile(filePath, maxFileSize); - if(!readResult.IsSuccess()) - { - return AZ::Failure(readResult.GetError()); - } - - AZStd::string jsonContent = readResult.TakeValue(); - - auto result = ReadJsonString(jsonContent); - if (!result.IsSuccess()) - { - return AZ::Failure(AZStd::string::format("Failed to load '%.*s'. %s", AZ_STRING_ARG(filePath), result.GetError().c_str())); - } - else - { - return result; - } + // Helper function to validate the JSON is structured with the standard header for a generic class + AZ::Outcome ValidateJsonClassHeader(const rapidjson::Document& jsonDocument) + { + auto typeItr = jsonDocument.FindMember(FileTypeTag); + if (typeItr == jsonDocument.MemberEnd() || !typeItr->value.IsString() || azstricmp(typeItr->value.GetString(), FileType) != 0) + { + return AZ::Failure(AZStd::string::format("Not a valid JsonSerialization file")); } - // Helper function to validate the JSON is structured with the standard header for a generic class - AZ::Outcome ValidateJsonClassHeader(const rapidjson::Document& jsonDocument) + auto nameItr = jsonDocument.FindMember(ClassNameTag); + if (nameItr == jsonDocument.MemberEnd() || !nameItr->value.IsString()) { - auto typeItr = jsonDocument.FindMember(FileTypeTag); - if (typeItr == jsonDocument.MemberEnd() || !typeItr->value.IsString() || azstricmp(typeItr->value.GetString(), FileType) != 0) - { - return AZ::Failure(AZStd::string::format("Not a valid JsonSerialization file")); - } - - auto nameItr = jsonDocument.FindMember(ClassNameTag); - if (nameItr == jsonDocument.MemberEnd() || !nameItr->value.IsString()) - { - return AZ::Failure(AZStd::string::format("File should contain ClassName")); - } - - auto dataItr = jsonDocument.FindMember(ClassDataTag); - // data can be empty but it should be an object - if (dataItr != jsonDocument.MemberEnd() && !dataItr->value.IsObject()) - { - return AZ::Failure(AZStd::string::format("ClassData should be an object")); - } - - return AZ::Success(); + return AZ::Failure(AZStd::string::format("File should contain ClassName")); } - AZ::Outcome LoadObjectFromStringByType(void* objectToLoad, const Uuid& classId, AZStd::string_view stream, - const JsonDeserializerSettings* settings) + auto dataItr = jsonDocument.FindMember(ClassDataTag); + // data can be empty but it should be an object + if (dataItr != jsonDocument.MemberEnd() && !dataItr->value.IsObject()) { - JsonDeserializerSettings loadSettings; - AZStd::string deserializeErrors; - auto prepare = PrepareDeserializerSettings(settings, loadSettings, deserializeErrors); - if (!prepare.IsSuccess()) - { - return AZ::Failure(prepare.GetError()); - } + return AZ::Failure(AZStd::string::format("ClassData should be an object")); + } - auto parseResult = ReadJsonString(stream); - if (!parseResult.IsSuccess()) - { - return AZ::Failure(parseResult.GetError()); - } + return AZ::Success(); + } - const rapidjson::Document& jsonDocument = parseResult.GetValue(); + AZ::Outcome LoadObjectFromStringByType(void* objectToLoad, const Uuid& classId, AZStd::string_view stream, + const JsonDeserializerSettings* settings) + { + JsonDeserializerSettings loadSettings; + AZStd::string deserializeErrors; + auto prepare = PrepareDeserializerSettings(settings, loadSettings, deserializeErrors); + if (!prepare.IsSuccess()) + { + return AZ::Failure(prepare.GetError()); + } - auto validateResult = ValidateJsonClassHeader(jsonDocument); - if (!validateResult.IsSuccess()) - { - return AZ::Failure(validateResult.GetError()); - } + auto parseResult = ReadJsonString(stream); + if (!parseResult.IsSuccess()) + { + return AZ::Failure(parseResult.GetError()); + } - const char* className = jsonDocument.FindMember(ClassNameTag)->value.GetString(); + const rapidjson::Document& jsonDocument = parseResult.GetValue(); - // validate class name - auto classData = loadSettings.m_serializeContext->FindClassData(classId); - if (!classData) - { - return AZ::Failure(AZStd::string::format("Try to load class from Id %s", classId.ToString().c_str())); - } + auto validateResult = ValidateJsonClassHeader(jsonDocument); + if (!validateResult.IsSuccess()) + { + return AZ::Failure(validateResult.GetError()); + } - if (azstricmp(classData->m_name, className) != 0) - { - return AZ::Failure(AZStd::string::format("Try to load class %s from class %s data", classData->m_name, className)); - } + const char* className = jsonDocument.FindMember(ClassNameTag)->value.GetString(); - JsonSerializationResult::ResultCode result = JsonSerialization::Load(objectToLoad, classId, jsonDocument.FindMember(ClassDataTag)->value, loadSettings); + // validate class name + auto classData = loadSettings.m_serializeContext->FindClassData(classId); + if (!classData) + { + return AZ::Failure(AZStd::string::format("Try to load class from Id %s", classId.ToString().c_str())); + } + + if (azstricmp(classData->m_name, className) != 0) + { + return AZ::Failure(AZStd::string::format("Try to load class %s from class %s data", classData->m_name, className)); + } + + JsonSerializationResult::ResultCode result = JsonSerialization::Load(objectToLoad, classId, jsonDocument.FindMember(ClassDataTag)->value, loadSettings); + + if (!WasLoadSuccess(result.GetOutcome()) || !deserializeErrors.empty()) + { + return AZ::Failure(deserializeErrors); + } + + return AZ::Success(); + } + + AZ::Outcome LoadObjectFromStreamByType(void* objectToLoad, const Uuid& classId, IO::GenericStream& stream, + const JsonDeserializerSettings* settings) + { + JsonDeserializerSettings loadSettings; + AZStd::string deserializeErrors; + auto prepare = PrepareDeserializerSettings(settings, loadSettings, deserializeErrors); + if (!prepare.IsSuccess()) + { + return AZ::Failure(prepare.GetError()); + } + + auto parseResult = ReadJsonStream(stream); + if (!parseResult.IsSuccess()) + { + return AZ::Failure(parseResult.GetError()); + } + + const rapidjson::Document& jsonDocument = parseResult.GetValue(); + + auto validateResult = ValidateJsonClassHeader(jsonDocument); + if (!validateResult.IsSuccess()) + { + return AZ::Failure(validateResult.GetError()); + } + + const char* className = jsonDocument.FindMember(ClassNameTag)->value.GetString(); + + // validate class name + auto classData = loadSettings.m_serializeContext->FindClassData(classId); + if (!classData) + { + return AZ::Failure(AZStd::string::format("Try to load class from Id %s", classId.ToString().c_str())); + } + + if (azstricmp(classData->m_name, className) != 0) + { + return AZ::Failure(AZStd::string::format("Try to load class %s from class %s data", classData->m_name, className)); + } + + JsonSerializationResult::ResultCode result = JsonSerialization::Load(objectToLoad, classId, jsonDocument.FindMember(ClassDataTag)->value, loadSettings); + + if (!WasLoadSuccess(result.GetOutcome()) || !deserializeErrors.empty()) + { + return AZ::Failure(deserializeErrors); + } + + return AZ::Success(); + } + + AZ::Outcome LoadAnyObjectFromStream(IO::GenericStream& stream, const JsonDeserializerSettings* settings) + { + JsonDeserializerSettings loadSettings; + AZStd::string deserializeErrors; + auto prepare = PrepareDeserializerSettings(settings, loadSettings, deserializeErrors); + if (!prepare.IsSuccess()) + { + return AZ::Failure(prepare.GetError()); + } + + auto parseResult = ReadJsonStream(stream); + if (!parseResult.IsSuccess()) + { + return AZ::Failure(parseResult.GetError()); + } + + const rapidjson::Document& jsonDocument = parseResult.GetValue(); + + auto validateResult = ValidateJsonClassHeader(jsonDocument); + if (!parseResult.IsSuccess()) + { + return AZ::Failure(parseResult.GetError()); + } + + const char* className = jsonDocument.FindMember(ClassNameTag)->value.GetString(); + AZStd::vector ids = loadSettings.m_serializeContext->FindClassId(AZ::Crc32(className)); + + // Load with first found class id + if (ids.size() >= 1) + { + auto classId = ids[0]; + AZStd::any anyData = loadSettings.m_serializeContext->CreateAny(classId); + auto& objectData = jsonDocument.FindMember(ClassDataTag)->value; + JsonSerializationResult::ResultCode result = JsonSerialization::Load(AZStd::any_cast(&anyData), classId, objectData, loadSettings); if (!WasLoadSuccess(result.GetOutcome()) || !deserializeErrors.empty()) { return AZ::Failure(deserializeErrors); } - return AZ::Success(); + return AZ::Success(anyData); } - AZ::Outcome LoadObjectFromStreamByType(void* objectToLoad, const Uuid& classId, IO::GenericStream& stream, - const JsonDeserializerSettings* settings) + return AZ::Failure(AZStd::string::format("Can't find serialize context for class %s", className)); + } + + AZ::Outcome LoadAnyObjectFromFile(const AZStd::string& filePath, const JsonDeserializerSettings* settings) + { + AZ::IO::FileIOStream inputFileStream; + if (!inputFileStream.Open(filePath.c_str(), AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeText)) { - JsonDeserializerSettings loadSettings; - AZStd::string deserializeErrors; - auto prepare = PrepareDeserializerSettings(settings, loadSettings, deserializeErrors); - if (!prepare.IsSuccess()) - { - return AZ::Failure(prepare.GetError()); - } - - auto parseResult = ReadJsonStream(stream); - if (!parseResult.IsSuccess()) - { - return AZ::Failure(parseResult.GetError()); - } - - const rapidjson::Document& jsonDocument = parseResult.GetValue(); - - auto validateResult = ValidateJsonClassHeader(jsonDocument); - if (!validateResult.IsSuccess()) - { - return AZ::Failure(validateResult.GetError()); - } - - const char* className = jsonDocument.FindMember(ClassNameTag)->value.GetString(); - - // validate class name - auto classData = loadSettings.m_serializeContext->FindClassData(classId); - if (!classData) - { - return AZ::Failure(AZStd::string::format("Try to load class from Id %s", classId.ToString().c_str())); - } - - if (azstricmp(classData->m_name, className) != 0) - { - return AZ::Failure(AZStd::string::format("Try to load class %s from class %s data", classData->m_name, className)); - } - - JsonSerializationResult::ResultCode result = JsonSerialization::Load(objectToLoad, classId, jsonDocument.FindMember(ClassDataTag)->value, loadSettings); - - if (!WasLoadSuccess(result.GetOutcome()) || !deserializeErrors.empty()) - { - return AZ::Failure(deserializeErrors); - } - - return AZ::Success(); - } - - AZ::Outcome LoadAnyObjectFromStream(IO::GenericStream& stream, const JsonDeserializerSettings* settings) - { - JsonDeserializerSettings loadSettings; - AZStd::string deserializeErrors; - auto prepare = PrepareDeserializerSettings(settings, loadSettings, deserializeErrors); - if (!prepare.IsSuccess()) - { - return AZ::Failure(prepare.GetError()); - } - - auto parseResult = ReadJsonStream(stream); - if (!parseResult.IsSuccess()) - { - return AZ::Failure(parseResult.GetError()); - } - - const rapidjson::Document& jsonDocument = parseResult.GetValue(); - - auto validateResult = ValidateJsonClassHeader(jsonDocument); - if (!parseResult.IsSuccess()) - { - return AZ::Failure(parseResult.GetError()); - } - - const char* className = jsonDocument.FindMember(ClassNameTag)->value.GetString(); - AZStd::vector ids = loadSettings.m_serializeContext->FindClassId(AZ::Crc32(className)); - - // Load with first found class id - if (ids.size() >= 1) - { - auto classId = ids[0]; - AZStd::any anyData = loadSettings.m_serializeContext->CreateAny(classId); - auto& objectData = jsonDocument.FindMember(ClassDataTag)->value; - JsonSerializationResult::ResultCode result = JsonSerialization::Load(AZStd::any_cast(&anyData), classId, objectData, loadSettings); - - if (!WasLoadSuccess(result.GetOutcome()) || !deserializeErrors.empty()) - { - return AZ::Failure(deserializeErrors); - } - - return AZ::Success(anyData); - } - - return AZ::Failure(AZStd::string::format("Can't find serialize context for class %s", className)); + return AZ::Failure(AZStd::string::format("Error opening file '%s' for reading", filePath.c_str())); } + return LoadAnyObjectFromStream(inputFileStream, settings); + } - AZ::Outcome LoadAnyObjectFromFile(const AZStd::string& filePath, const JsonDeserializerSettings* settings) - { - AZ::IO::FileIOStream inputFileStream; - if (!inputFileStream.Open(filePath.c_str(), AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeText)) - { - return AZ::Failure(AZStd::string::format("Error opening file '%s' for reading", filePath.c_str())); - } - return LoadAnyObjectFromStream(inputFileStream, settings); - } - - } // namespace JsonSerializationUtils -} // namespace AZ +} // namespace AZ::JsonSerializationUtils diff --git a/Code/Framework/AzCore/AzCore/Serialization/ObjectStream.cpp b/Code/Framework/AzCore/AzCore/Serialization/ObjectStream.cpp index 746c80f3ea..ac44ac150c 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/ObjectStream.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/ObjectStream.cpp @@ -1050,7 +1050,7 @@ namespace AZ } m_xmlNode = next; - Uuid specializedId; + Uuid specializedId = Uuid::CreateNull(); // now parse the node rapidxml::xml_attribute* attr = m_xmlNode->first_attribute(); while (attr) @@ -1643,12 +1643,15 @@ namespace AZ m_writeElementResultStack.push_back(WriteElement(ptr, classData, classElement)); return m_writeElementResultStack.back(); }; - auto closeElementCB = [this, classData]() + auto closeElementCB = [this, classTypeId = classData->m_typeId]() { if (m_writeElementResultStack.empty()) { - AZ_UNUSED(classData); // Prevent unused warning in release builds - AZ_Error("Serialize", false, "CloseElement is attempted to be called without a corresponding WriteElement when writing class %s", classData->m_name); + // ClassData could be dangling pointer if it was unreflected by the ObjectStreamWriteOverrideCB + // So use the classTypeId instead + AZ_UNUSED(classTypeId); + AZ_Error("Serialize", false, "CloseElement is attempted to be called without a corresponding WriteElement when writing class %s", + classTypeId.ToString>().c_str()); return true; } if (m_writeElementResultStack.back()) @@ -1665,16 +1668,14 @@ namespace AZ SerializeContext::ENUM_ACCESS_FOR_READ, &m_errorLogger ); - ObjectStreamWriteOverrideCB writeCB; - if (objectStreamWriteOverrideCB.Read(writeCB)) + if (objectStreamWriteOverrideCB.Invoke(callContext, objectPtr, *classData, classElement)) { - writeCB(callContext, objectPtr, *classData, classElement); return false; } else { auto objectStreamError = AZStd::string::format("Unable to invoke ObjectStream Write Element Override for class element %s of class data %s", - classElement->m_name ? classElement->m_name : "", classData->m_name); + classElement && classElement->m_name ? classElement->m_name : "", classData->m_name); m_errorLogger.ReportError(objectStreamError.c_str()); } } diff --git a/Code/Framework/AzCore/AzCore/Serialization/SerializationUtils.cpp b/Code/Framework/AzCore/AzCore/Serialization/SerializationUtils.cpp index 37eacd940e..8439ff8e2c 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/SerializationUtils.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/SerializationUtils.cpp @@ -18,502 +18,499 @@ #include -namespace AZ +namespace AZ::Utils { - namespace Utils + bool LoadObjectFromStreamInPlace(IO::GenericStream& stream, AZ::SerializeContext* context, const SerializeContext::ClassData* objectClassData, void* targetPointer, const FilterDescriptor& filterDesc) { - bool LoadObjectFromStreamInPlace(IO::GenericStream& stream, AZ::SerializeContext* context, const SerializeContext::ClassData* objectClassData, void* targetPointer, const FilterDescriptor& filterDesc) + AZ_PROFILE_FUNCTION(AzCore); + + AZ_Assert(objectClassData, "Class data is required."); + + if (!context) { - AZ_PROFILE_FUNCTION(AzCore); - - AZ_Assert(objectClassData, "Class data is required."); - - if (!context) - { - EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext); - AZ_Assert(context, "No serialize context"); - } - - if (!context) - { - return false; - } - - AZ_Assert(targetPointer, "You must provide a target pointer"); - - bool foundSuccess = false; - using CreationCallback = AZStd::function; - auto handler = [&targetPointer, objectClassData, &foundSuccess](void** instance, const SerializeContext::ClassData** classData, const Uuid& classId, SerializeContext* context) - { - void* convertibleInstance{}; - if (objectClassData->ConvertFromType(convertibleInstance, classId, targetPointer, *context)) - { - foundSuccess = true; - if (instance) - { - // The ObjectStream will ask us for the address of the target to load into, so provide it. - *instance = convertibleInstance; - } - if (classData) - { - // The ObjectStream will ask us for the class data of the target being loaded into, so provide it if needed. - // This allows us to load directly into a generic object (templated containers, strings, etc). - *classData = objectClassData; - } - } - }; - bool readSuccess = ObjectStream::LoadBlocking(&stream, *context, ObjectStream::ClassReadyCB(), filterDesc, CreationCallback(handler, AZ::OSStdAllocator())); - - AZ_Warning("Serialization", readSuccess, "LoadObjectFromStreamInPlace: Stream did not deserialize correctly"); - AZ_Warning("Serialization", foundSuccess, "LoadObjectFromStreamInPlace: Did not find the expected type in the stream"); - - return readSuccess && foundSuccess; + EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext); + AZ_Assert(context, "No serialize context"); } - bool LoadObjectFromStreamInPlace(IO::GenericStream& stream, AZ::SerializeContext* context, const Uuid& targetClassId, void* targetPointer, const FilterDescriptor& filterDesc) + if (!context) { - AZ_PROFILE_FUNCTION(AzCore); - - if (!context) - { - EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext); - AZ_Assert(context, "No serialize context"); - } - - if (!context) - { - return false; - } - - const SerializeContext::ClassData* classData = context->FindClassData(targetClassId); - if (!classData) - { - AZ_Error("Serialization", false, - "Unable to locate class data for uuid \"%s\". This object cannot be serialized as a root element. " - "Make sure the Uuid is valid, or if this is a generic type, use the override that takes a ClassData pointer instead.", - targetClassId.ToString().c_str()); - return false; - } - - return LoadObjectFromStreamInPlace(stream, context, classData, targetPointer, filterDesc); - } - - bool LoadObjectFromFileInPlace(const AZStd::string& filePath, const Uuid& targetClassId, void* destination, AZ::SerializeContext* context /*= nullptr*/, const FilterDescriptor& filterDesc /*= FilterDescriptor()*/) - { - AZ::IO::FileIOStream fileStream; - if (!fileStream.Open(filePath.c_str(), IO::OpenMode::ModeRead | IO::OpenMode::ModeBinary)) - { - return false; - } - - return LoadObjectFromStreamInPlace(fileStream, context, targetClassId, destination, filterDesc); - } - - void* LoadObjectFromStream(IO::GenericStream& stream, AZ::SerializeContext* context, const Uuid* targetClassId, const FilterDescriptor& filterDesc) - { - AZ_PROFILE_FUNCTION(AzCore); - - if (!context) - { - EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext); - AZ_Assert(context, "No serialize context"); - } - - if (!context) - { - return nullptr; - } - - void* loadedInstance = nullptr; - bool success = ObjectStream::LoadBlocking(&stream, *context, - [&loadedInstance, targetClassId](void* classPtr, const Uuid& classId, const SerializeContext* serializeContext) - { - if (targetClassId) - { - void* instance = serializeContext->DownCast(classPtr, classId, *targetClassId); - - // Given a valid object - if (instance) - { - AZ_Assert(!loadedInstance, "loadedInstance must be NULL, otherwise we are being invoked with multiple valid objects"); - loadedInstance = instance; - return; - } - } - else - { - if (!loadedInstance) - { - loadedInstance = classPtr; - return; - } - } - - auto classData = serializeContext->FindClassData(classId); - if (classData && classData->m_factory) - { - classData->m_factory->Destroy(classPtr); - } - }, - filterDesc, - ObjectStream::InplaceLoadRootInfoCB() - ); - - if (!success) - { - return nullptr; - } - - return loadedInstance; - } - - void* LoadObjectFromFile(const AZStd::string& filePath, const Uuid& targetClassId, SerializeContext* context, const FilterDescriptor& filterDesc, int /*platformFlags*/) - { - AZ_PROFILE_FUNCTION(AzCore); - - AZ::IO::FileIOStream fileStream; - if (!fileStream.Open(filePath.c_str(), IO::OpenMode::ModeRead | IO::OpenMode::ModeBinary)) - { - return nullptr; - } - - void* loadedObject = LoadObjectFromStream(fileStream, context, &targetClassId, filterDesc); - return loadedObject; - } - - bool SaveObjectToStream(IO::GenericStream& stream, DataStream::StreamType streamType, const void* classPtr, const Uuid& classId, SerializeContext* context, const SerializeContext::ClassData* classData) - { - AZ_PROFILE_FUNCTION(AzCore); - - if (!context) - { - EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext); - - if(!context) - { - AZ_Assert(false, "No serialize context"); - return false; - } - } - - if (!classPtr) - { - AZ_Assert(false, "SaveObjectToStream: classPtr is null, object cannot be serialized."); - return false; - } - - AZ::ObjectStream* objectStream = AZ::ObjectStream::Create(&stream, *context, streamType); - if (!objectStream) - { - return false; - } - - if (!objectStream->WriteClass(classPtr, classId, classData)) - { - objectStream->Finalize(); - return false; - } - - if (!objectStream->Finalize()) - { - return false; - } - - return true; - } - - bool SaveStreamToFile(const AZStd::string& filePath, const AZStd::vector& streamData, int platformFlags) - { - AZ::IO::FileIOBase* fileIo = AZ::IO::FileIOBase::GetInstance(); - AZ::IO::FixedMaxPathString resolvedPath; - if (fileIo == nullptr || !fileIo->ResolvePath(filePath.c_str(), resolvedPath.data(), resolvedPath.capacity() + 1)) - { - resolvedPath = filePath; - } - if (AZ::IO::SystemFile fileHandle; fileHandle.Open(resolvedPath.c_str(), - AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY, - platformFlags)) - { - AZ::IO::SizeType bytesWritten = fileHandle.Write(streamData.data(), streamData.size()); - return bytesWritten == streamData.size(); - } - return false; } - bool SaveObjectToFile(const AZStd::string& filePath, DataStream::StreamType fileType, const void* classPtr, const Uuid& classId, SerializeContext* context, int platformFlags) - { - AZ_PROFILE_FUNCTION(AzCore); + AZ_Assert(targetPointer, "You must provide a target pointer"); - // \note This is ok for tools, but we should use the streamer to write objects directly (no memory store) - AZStd::vector dstData; - AZ::IO::ByteContainerStream > dstByteStream(&dstData); - - if (!SaveObjectToStream(dstByteStream, fileType, classPtr, classId, context)) + bool foundSuccess = false; + using CreationCallback = AZStd::function; + auto handler = [&targetPointer, objectClassData, &foundSuccess](void** instance, const SerializeContext::ClassData** classData, const Uuid& classId, SerializeContext* context) { - return false; - } - - return SaveStreamToFile(filePath, dstData, platformFlags); - } - - /*! - \brief Finds any descendant DataElementNodes of the @classElement which match each Crc32 values in the supplied elementCrcQueue - \param context SerializeContext used for looking up ClassData - \param classElement Top level DataElementNode to begin comparison each the Crc32 queue - \param elementCrcQueue Container of Crc32 values in the order in which DataElementNodes should be matched as the DataElementNode tree is traversed - \return Vector of valid pointers to DataElementNodes which match the entire element Crc32 queue - */ - AZStd::vector FindDescendantElements(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement, - const AZStd::vector& elementCrcQueue) - { - AZStd::vector dataElementNodes; - FindDescendantElements(context, classElement, dataElementNodes, elementCrcQueue.begin(), elementCrcQueue.end()); - - return dataElementNodes; - } - - /*! - \brief Finds any descendant DataElementNodes of the @classElement which match each Crc32 values in the supplied elementCrcQueue - \param context SerializeContext used for looking up ClassData - \param classElement The current DataElementNode which will be compared against be to current top Crc32 value in the Crc32 queue - \param dataElementNodes[out] Array to populate with a DataElementNode which was found by matching all Crc32 values in the Crc32 queue - \param first The current front of the Crc32 queue - \param last The end of the Crc32 queue - */ - void FindDescendantElements(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement, - AZStd::vector& dataElementNodes, AZStd::vector::const_iterator first, AZStd::vector::const_iterator last) - { - if (first == last) - { - return; - } - - for (int i = 0; i < classElement.GetNumSubElements(); ++i) - { - auto& childElement = classElement.GetSubElement(i); - if (*first == AZ::Crc32(childElement.GetName())) + void* convertibleInstance{}; + if (objectClassData->ConvertFromType(convertibleInstance, classId, targetPointer, *context)) { - if (AZStd::distance(first, last) == 1) + foundSuccess = true; + if (instance) { - dataElementNodes.push_back(&childElement); + // The ObjectStream will ask us for the address of the target to load into, so provide it. + *instance = convertibleInstance; + } + if (classData) + { + // The ObjectStream will ask us for the class data of the target being loaded into, so provide it if needed. + // This allows us to load directly into a generic object (templated containers, strings, etc). + *classData = objectClassData; + } + } + }; + bool readSuccess = ObjectStream::LoadBlocking(&stream, *context, ObjectStream::ClassReadyCB(), filterDesc, CreationCallback(handler, AZ::OSStdAllocator())); + + AZ_Warning("Serialization", readSuccess, "LoadObjectFromStreamInPlace: Stream did not deserialize correctly"); + AZ_Warning("Serialization", foundSuccess, "LoadObjectFromStreamInPlace: Did not find the expected type in the stream"); + + return readSuccess && foundSuccess; + } + + bool LoadObjectFromStreamInPlace(IO::GenericStream& stream, AZ::SerializeContext* context, const Uuid& targetClassId, void* targetPointer, const FilterDescriptor& filterDesc) + { + AZ_PROFILE_FUNCTION(AzCore); + + if (!context) + { + EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext); + AZ_Assert(context, "No serialize context"); + } + + if (!context) + { + return false; + } + + const SerializeContext::ClassData* classData = context->FindClassData(targetClassId); + if (!classData) + { + AZ_Error("Serialization", false, + "Unable to locate class data for uuid \"%s\". This object cannot be serialized as a root element. " + "Make sure the Uuid is valid, or if this is a generic type, use the override that takes a ClassData pointer instead.", + targetClassId.ToString().c_str()); + return false; + } + + return LoadObjectFromStreamInPlace(stream, context, classData, targetPointer, filterDesc); + } + + bool LoadObjectFromFileInPlace(const AZStd::string& filePath, const Uuid& targetClassId, void* destination, AZ::SerializeContext* context /*= nullptr*/, const FilterDescriptor& filterDesc /*= FilterDescriptor()*/) + { + AZ::IO::FileIOStream fileStream; + if (!fileStream.Open(filePath.c_str(), IO::OpenMode::ModeRead | IO::OpenMode::ModeBinary)) + { + return false; + } + + return LoadObjectFromStreamInPlace(fileStream, context, targetClassId, destination, filterDesc); + } + + void* LoadObjectFromStream(IO::GenericStream& stream, AZ::SerializeContext* context, const Uuid* targetClassId, const FilterDescriptor& filterDesc) + { + AZ_PROFILE_FUNCTION(AzCore); + + if (!context) + { + EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext); + AZ_Assert(context, "No serialize context"); + } + + if (!context) + { + return nullptr; + } + + void* loadedInstance = nullptr; + bool success = ObjectStream::LoadBlocking(&stream, *context, + [&loadedInstance, targetClassId](void* classPtr, const Uuid& classId, const SerializeContext* serializeContext) + { + if (targetClassId) + { + void* instance = serializeContext->DownCast(classPtr, classId, *targetClassId); + + // Given a valid object + if (instance) + { + AZ_Assert(!loadedInstance, "loadedInstance must be NULL, otherwise we are being invoked with multiple valid objects"); + loadedInstance = instance; + return; + } } else { - FindDescendantElements(context, childElement, dataElementNodes, AZStd::next(first), last); - } - } - } - } - - bool IsVectorContainerType(const AZ::Uuid& type) - { - AZ::SerializeContext* serializeContext = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); - - if (serializeContext) - { - AZ::TypeId containerTypeId = type; - - if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type)) - { - // This type is a container - containerTypeId = classInfo->GetGenericTypeId(); - } - - if (containerTypeId == AZ::GetGenericClassInfoVectorTypeId() - || containerTypeId == AZ::GetGenericClassInfoFixedVectorTypeId() - || containerTypeId == AZ::GetGenericClassInfoArrayTypeId() - ) - { - return true; - } - } - - return false; - } - - bool IsSetContainerType(const AZ::Uuid& type) - { - AZ::SerializeContext* serializeContext = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); - - if (serializeContext) - { - AZ::TypeId containerTypeId = type; - - if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type)) - { - // This type is a container - containerTypeId = classInfo->GetGenericTypeId(); - } - - if (containerTypeId == AZ::GetGenericClassSetTypeId() - || containerTypeId == AZ::GetGenericClassUnorderedSetTypeId() - ) - { - return true; - } - } - - return false; - } - - - bool IsMapContainerType(const AZ::Uuid& type) - { - AZ::SerializeContext* serializeContext = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); - - if (serializeContext) - { - AZ::Uuid containerTypeId = type; - - if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type)) - { - containerTypeId = classInfo->GetGenericTypeId(); - } - - if (containerTypeId == AZ::GetGenericClassMapTypeId() - || containerTypeId == AZ::GetGenericClassUnorderedMapTypeId() - ) - { - return true; - } - } - - return false; - } - - bool IsContainerType(const AZ::Uuid& type) - { - return IsVectorContainerType(type) || IsSetContainerType(type) || IsMapContainerType(type); - } - - AZStd::vector GetContainedTypes(const AZ::Uuid& type) - { - AZStd::vector types; - - AZ::SerializeContext* serializeContext = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); - - if (serializeContext) - { - if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type)) - { - for (int i = 0; i < classInfo->GetNumTemplatedArguments(); ++i) - { - types.push_back(classInfo->GetTemplatedTypeId(i)); - } - } - } - - return types; - } - - bool IsOutcomeType(const AZ::Uuid& type) - { - AZ::SerializeContext* serializeContext = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); - - if (serializeContext) - { - GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type); - return classInfo && classInfo->GetGenericTypeId() == AZ::GetGenericOutcomeTypeId(); - } - - return false; - } - - bool IsPairContainerType(const AZ::Uuid& type) - { - AZ::SerializeContext* serializeContext = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); - - if (serializeContext) - { - AZ::Uuid containerTypeId = type; - - if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type)) - { - containerTypeId = classInfo->GetGenericTypeId(); - } - - if (containerTypeId == AZ::GetGenericClassPairTypeId()) - { - return true; - } - } - - return false; - } - - AZ::TypeId GetGenericContainerType(const AZ::TypeId& type) - { - AZ::SerializeContext* serializeContext = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); - - if (serializeContext) - { - if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type)) - { - // This type is a container - return classInfo->GetGenericTypeId(); - } - } - - return azrtti_typeid(); - } - - bool IsGenericContainerType(const AZ::TypeId& type) - { - return IsContainerType(type) && GetGenericContainerType(type) == azrtti_typeid(); - } - - AZStd::pair GetOutcomeTypes(const AZ::Uuid& type) - { - AZStd::vector types; - - AZ::SerializeContext* serializeContext = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); - - if (serializeContext) - { - if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type)) - { - AZ_Assert(classInfo->GetNumTemplatedArguments() == 2, "Outcome template arguments must be 2, even if void, void"); - return AZStd::make_pair(classInfo->GetTemplatedTypeId(0), classInfo->GetTemplatedTypeId(1)); - } - } - - return AZStd::make_pair(azrtti_typeid(), azrtti_typeid()); - } - - void* ResolvePointer(void* ptr, const SerializeContext::ClassElement& classElement, const SerializeContext& context) - { - if (classElement.m_flags & SerializeContext::ClassElement::FLG_POINTER) - { - // In the case of pointer-to-pointer, we'll deference. - ptr = *(void**)(ptr); - - // Pointer-to-pointer fields may be base class / polymorphic, so cast pointer to actual type, - // safe for passing as 'this' to member functions. - if (ptr && classElement.m_azRtti) - { - Uuid actualClassId = classElement.m_azRtti->GetActualUuid(ptr); - if (actualClassId != classElement.m_typeId) - { - const SerializeContext::ClassData* classData = context.FindClassData(actualClassId); - if (classData) + if (!loadedInstance) { - ptr = classElement.m_azRtti->Cast(ptr, classData->m_azRtti->GetTypeId()); + loadedInstance = classPtr; + return; } } - } - } - return ptr; + auto classData = serializeContext->FindClassData(classId); + if (classData && classData->m_factory) + { + classData->m_factory->Destroy(classPtr); + } + }, + filterDesc, + ObjectStream::InplaceLoadRootInfoCB() + ); + + if (!success) + { + return nullptr; } - } // namespace Utils -} // namespace AZ + return loadedInstance; + } + + void* LoadObjectFromFile(const AZStd::string& filePath, const Uuid& targetClassId, SerializeContext* context, const FilterDescriptor& filterDesc, int /*platformFlags*/) + { + AZ_PROFILE_FUNCTION(AzCore); + + AZ::IO::FileIOStream fileStream; + if (!fileStream.Open(filePath.c_str(), IO::OpenMode::ModeRead | IO::OpenMode::ModeBinary)) + { + return nullptr; + } + + void* loadedObject = LoadObjectFromStream(fileStream, context, &targetClassId, filterDesc); + return loadedObject; + } + + bool SaveObjectToStream(IO::GenericStream& stream, DataStream::StreamType streamType, const void* classPtr, const Uuid& classId, SerializeContext* context, const SerializeContext::ClassData* classData) + { + AZ_PROFILE_FUNCTION(AzCore); + + if (!context) + { + EBUS_EVENT_RESULT(context, ComponentApplicationBus, GetSerializeContext); + + if(!context) + { + AZ_Assert(false, "No serialize context"); + return false; + } + } + + if (!classPtr) + { + AZ_Assert(false, "SaveObjectToStream: classPtr is null, object cannot be serialized."); + return false; + } + + AZ::ObjectStream* objectStream = AZ::ObjectStream::Create(&stream, *context, streamType); + if (!objectStream) + { + return false; + } + + if (!objectStream->WriteClass(classPtr, classId, classData)) + { + objectStream->Finalize(); + return false; + } + + if (!objectStream->Finalize()) + { + return false; + } + + return true; + } + + bool SaveStreamToFile(const AZStd::string& filePath, const AZStd::vector& streamData, int platformFlags) + { + AZ::IO::FileIOBase* fileIo = AZ::IO::FileIOBase::GetInstance(); + AZ::IO::FixedMaxPathString resolvedPath; + if (fileIo == nullptr || !fileIo->ResolvePath(filePath.c_str(), resolvedPath.data(), resolvedPath.capacity() + 1)) + { + resolvedPath = filePath; + } + if (AZ::IO::SystemFile fileHandle; fileHandle.Open(resolvedPath.c_str(), + AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY, + platformFlags)) + { + AZ::IO::SizeType bytesWritten = fileHandle.Write(streamData.data(), streamData.size()); + return bytesWritten == streamData.size(); + } + + return false; + } + + bool SaveObjectToFile(const AZStd::string& filePath, DataStream::StreamType fileType, const void* classPtr, const Uuid& classId, SerializeContext* context, int platformFlags) + { + AZ_PROFILE_FUNCTION(AzCore); + + // \note This is ok for tools, but we should use the streamer to write objects directly (no memory store) + AZStd::vector dstData; + AZ::IO::ByteContainerStream > dstByteStream(&dstData); + + if (!SaveObjectToStream(dstByteStream, fileType, classPtr, classId, context)) + { + return false; + } + + return SaveStreamToFile(filePath, dstData, platformFlags); + } + + /*! + \brief Finds any descendant DataElementNodes of the @classElement which match each Crc32 values in the supplied elementCrcQueue + \param context SerializeContext used for looking up ClassData + \param classElement Top level DataElementNode to begin comparison each the Crc32 queue + \param elementCrcQueue Container of Crc32 values in the order in which DataElementNodes should be matched as the DataElementNode tree is traversed + \return Vector of valid pointers to DataElementNodes which match the entire element Crc32 queue + */ + AZStd::vector FindDescendantElements(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement, + const AZStd::vector& elementCrcQueue) + { + AZStd::vector dataElementNodes; + FindDescendantElements(context, classElement, dataElementNodes, elementCrcQueue.begin(), elementCrcQueue.end()); + + return dataElementNodes; + } + + /*! + \brief Finds any descendant DataElementNodes of the @classElement which match each Crc32 values in the supplied elementCrcQueue + \param context SerializeContext used for looking up ClassData + \param classElement The current DataElementNode which will be compared against be to current top Crc32 value in the Crc32 queue + \param dataElementNodes[out] Array to populate with a DataElementNode which was found by matching all Crc32 values in the Crc32 queue + \param first The current front of the Crc32 queue + \param last The end of the Crc32 queue + */ + void FindDescendantElements(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement, + AZStd::vector& dataElementNodes, AZStd::vector::const_iterator first, AZStd::vector::const_iterator last) + { + if (first == last) + { + return; + } + + for (int i = 0; i < classElement.GetNumSubElements(); ++i) + { + auto& childElement = classElement.GetSubElement(i); + if (*first == AZ::Crc32(childElement.GetName())) + { + if (AZStd::distance(first, last) == 1) + { + dataElementNodes.push_back(&childElement); + } + else + { + FindDescendantElements(context, childElement, dataElementNodes, AZStd::next(first), last); + } + } + } + } + + bool IsVectorContainerType(const AZ::Uuid& type) + { + AZ::SerializeContext* serializeContext = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); + + if (serializeContext) + { + AZ::TypeId containerTypeId = type; + + if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type)) + { + // This type is a container + containerTypeId = classInfo->GetGenericTypeId(); + } + + if (containerTypeId == AZ::GetGenericClassInfoVectorTypeId() + || containerTypeId == AZ::GetGenericClassInfoFixedVectorTypeId() + || containerTypeId == AZ::GetGenericClassInfoArrayTypeId() + ) + { + return true; + } + } + + return false; + } + + bool IsSetContainerType(const AZ::Uuid& type) + { + AZ::SerializeContext* serializeContext = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); + + if (serializeContext) + { + AZ::TypeId containerTypeId = type; + + if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type)) + { + // This type is a container + containerTypeId = classInfo->GetGenericTypeId(); + } + + if (containerTypeId == AZ::GetGenericClassSetTypeId() + || containerTypeId == AZ::GetGenericClassUnorderedSetTypeId() + ) + { + return true; + } + } + + return false; + } + + + bool IsMapContainerType(const AZ::Uuid& type) + { + AZ::SerializeContext* serializeContext = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); + + if (serializeContext) + { + AZ::Uuid containerTypeId = type; + + if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type)) + { + containerTypeId = classInfo->GetGenericTypeId(); + } + + if (containerTypeId == AZ::GetGenericClassMapTypeId() + || containerTypeId == AZ::GetGenericClassUnorderedMapTypeId() + ) + { + return true; + } + } + + return false; + } + + bool IsContainerType(const AZ::Uuid& type) + { + return IsVectorContainerType(type) || IsSetContainerType(type) || IsMapContainerType(type); + } + + AZStd::vector GetContainedTypes(const AZ::Uuid& type) + { + AZStd::vector types; + + AZ::SerializeContext* serializeContext = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); + + if (serializeContext) + { + if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type)) + { + for (int i = 0; i < classInfo->GetNumTemplatedArguments(); ++i) + { + types.push_back(classInfo->GetTemplatedTypeId(i)); + } + } + } + + return types; + } + + bool IsOutcomeType(const AZ::Uuid& type) + { + AZ::SerializeContext* serializeContext = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); + + if (serializeContext) + { + GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type); + return classInfo && classInfo->GetGenericTypeId() == AZ::GetGenericOutcomeTypeId(); + } + + return false; + } + + bool IsPairContainerType(const AZ::Uuid& type) + { + AZ::SerializeContext* serializeContext = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); + + if (serializeContext) + { + AZ::Uuid containerTypeId = type; + + if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type)) + { + containerTypeId = classInfo->GetGenericTypeId(); + } + + if (containerTypeId == AZ::GetGenericClassPairTypeId()) + { + return true; + } + } + + return false; + } + + AZ::TypeId GetGenericContainerType(const AZ::TypeId& type) + { + AZ::SerializeContext* serializeContext = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); + + if (serializeContext) + { + if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type)) + { + // This type is a container + return classInfo->GetGenericTypeId(); + } + } + + return azrtti_typeid(); + } + + bool IsGenericContainerType(const AZ::TypeId& type) + { + return IsContainerType(type) && GetGenericContainerType(type) == azrtti_typeid(); + } + + AZStd::pair GetOutcomeTypes(const AZ::Uuid& type) + { + AZStd::vector types; + + AZ::SerializeContext* serializeContext = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); + + if (serializeContext) + { + if (GenericClassInfo* classInfo = serializeContext->FindGenericClassInfo(type)) + { + AZ_Assert(classInfo->GetNumTemplatedArguments() == 2, "Outcome template arguments must be 2, even if void, void"); + return AZStd::make_pair(classInfo->GetTemplatedTypeId(0), classInfo->GetTemplatedTypeId(1)); + } + } + + return AZStd::make_pair(azrtti_typeid(), azrtti_typeid()); + } + + void* ResolvePointer(void* ptr, const SerializeContext::ClassElement& classElement, const SerializeContext& context) + { + if (classElement.m_flags & SerializeContext::ClassElement::FLG_POINTER) + { + // In the case of pointer-to-pointer, we'll deference. + ptr = *(void**)(ptr); + + // Pointer-to-pointer fields may be base class / polymorphic, so cast pointer to actual type, + // safe for passing as 'this' to member functions. + if (ptr && classElement.m_azRtti) + { + Uuid actualClassId = classElement.m_azRtti->GetActualUuid(ptr); + if (actualClassId != classElement.m_typeId) + { + const SerializeContext::ClassData* classData = context.FindClassData(actualClassId); + if (classData) + { + ptr = classElement.m_azRtti->Cast(ptr, classData->m_azRtti->GetTypeId()); + } + } + } + } + + return ptr; + } + +} // namespace AZ::Utils diff --git a/Code/Framework/AzCore/AzCore/Serialization/SerializeContext.cpp b/Code/Framework/AzCore/AzCore/Serialization/SerializeContext.cpp index 81546f4f28..ff0ad571f7 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/SerializeContext.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/SerializeContext.cpp @@ -2065,11 +2065,15 @@ namespace AZ } EnumerateInstanceCallContext callContext( - AZStd::bind(&SerializeContext::BeginCloneElement, this, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3, &cloneData, &m_errorLogger, &scratchBuffer), - AZStd::bind(&SerializeContext::EndCloneElement, this, &cloneData), - this, - SerializeContext::ENUM_ACCESS_FOR_READ, - &m_errorLogger); + [&](void* ptr, const ClassData* classData, const ClassElement* elementData) -> bool + { + return BeginCloneElement(ptr, classData, elementData, &cloneData, &m_errorLogger, &scratchBuffer); + }, + [&]() -> bool + { + return EndCloneElement(&cloneData); + }, + this, SerializeContext::ENUM_ACCESS_FOR_READ, &m_errorLogger); EnumerateInstance( &callContext @@ -2098,19 +2102,17 @@ namespace AZ if (ptr) { EnumerateInstanceCallContext callContext( - AZStd::bind(&SerializeContext::BeginCloneElementInplace, this, dest, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3, &cloneData, &m_errorLogger, &scratchBuffer), - AZStd::bind(&SerializeContext::EndCloneElement, this, &cloneData), - this, - SerializeContext::ENUM_ACCESS_FOR_READ, - &m_errorLogger); + [&](void* ptr, const ClassData* classData, const ClassElement* elementData) -> bool + { + return BeginCloneElementInplace(dest, ptr, classData, elementData, &cloneData, &m_errorLogger, &scratchBuffer); + }, + [&]() -> bool + { + return EndCloneElement(&cloneData); + }, + this, SerializeContext::ENUM_ACCESS_FOR_READ, &m_errorLogger); - EnumerateInstance( - &callContext - , const_cast(ptr) - , classId - , nullptr - , nullptr - ); + EnumerateInstance(&callContext, const_cast(ptr), classId, nullptr, nullptr); } } @@ -2941,14 +2943,10 @@ namespace AZ { m_errorHandler = errorHandler ? errorHandler : &m_defaultErrorHandler; - m_elementCallback = AZStd::bind(static_cast(&SerializeContext::EnumerateInstance) - , m_context - , this - , AZStd::placeholders::_1 - , AZStd::placeholders::_2 - , AZStd::placeholders::_3 - , AZStd::placeholders::_4 - ); + m_elementCallback = [this](void* ptr, const Uuid& classId, const ClassData* classData, const ClassElement* classElement)->bool + { + return m_context->EnumerateInstance(this, ptr, classId, classData, classElement); + }; } //========================================================================= diff --git a/Code/Framework/AzCore/AzCore/Serialization/SerializeContext.h b/Code/Framework/AzCore/AzCore/Serialization/SerializeContext.h index a37780029e..bf96bcdb9a 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/SerializeContext.h +++ b/Code/Framework/AzCore/AzCore/Serialization/SerializeContext.h @@ -90,7 +90,7 @@ namespace AZ using AttributePtr = AZStd::shared_ptr; using AttributeSharedPair = AZStd::pair; - template + template > AttributePtr CreateModuleAttribute(T&& attrValue); /** @@ -540,6 +540,7 @@ namespace AZ */ struct ClassElement { + AZ_TYPE_INFO(ClassElement, "{7D386902-A1D9-4525-8284-F68435FE1D05}"); enum Flags { FLG_POINTER = (1 << 0), ///< Element is stored as pointer (it's not a value). @@ -563,22 +564,22 @@ namespace AZ void ClearAttributes(); Attribute* FindAttribute(AttributeId attributeId) const; - const char* m_name; ///< Used in XML output and debugging purposes - u32 m_nameCrc; ///< CRC32 of m_name - Uuid m_typeId; - size_t m_dataSize; - size_t m_offset; + const char* m_name{ "" }; ///< Used in XML output and debugging purposes + u32 m_nameCrc{}; ///< CRC32 of m_name + Uuid m_typeId = AZ::TypeId::CreateNull(); + size_t m_dataSize{}; + size_t m_offset{}; - IRttiHelper* m_azRtti; ///< Interface used to support RTTI. + IRttiHelper* m_azRtti{}; ///< Interface used to support RTTI. GenericClassInfo* m_genericClassInfo = nullptr; ///< Valid when the generic class is set. So you don't search for the actual type in the class register. - Edit::ElementData* m_editData; ///< Pointer to edit data (generated by EditContext). + Edit::ElementData* m_editData{}; ///< Pointer to edit data (generated by EditContext). AZStd::vector m_attributes{ AZStdFunctorAllocator([]() -> IAllocatorAllocate& { return AZ::AllocatorInstance::Get(); }) }; ///< Attributes attached to ClassElement. Lambda is required here as AZStdFunctorAllocator expects a function pointer ///< that returns an IAllocatorAllocate& and the AZ::AllocatorInstance::Get returns an AZ::SystemAllocator& /// which while it inherits from IAllocatorAllocate, does not work as function pointers do not support covariant return types AttributeOwnership m_attributeOwnership = AttributeOwnership::Parent; - int m_flags; ///< + int m_flags{}; ///< }; typedef AZStd::vector ClassElementArray; @@ -589,6 +590,8 @@ namespace AZ class ClassData { public: + AZ_TYPE_INFO(ClassData, "{20EB8E2E-D807-4039-84E2-CE37D7647CD4}"); + ClassData(); ~ClassData() { ClearAttributes(); } ClassData(ClassData&&) = default; @@ -1040,6 +1043,7 @@ namespace AZ */ struct EnumerateInstanceCallContext { + AZ_TYPE_INFO(EnumerateInstanceCallContext, "{FCC1DB4B-72BD-4D78-9C23-C84B91589D33}"); EnumerateInstanceCallContext(const BeginElemEnumCB& beginElemCB, const EndElemEnumCB& endElemCB, const SerializeContext* context, unsigned int accessflags, ErrorHandler* errorHandler); BeginElemEnumCB m_beginElemCB; ///< Optional callback when entering an element's hierarchy. @@ -2539,7 +2543,7 @@ namespace AZ /// associated with current module /// @param attrValue value to store within the attribute /// @param ContainerType second parameter which is used for function parameter deduction - template + template AttributePtr CreateModuleAttribute(T&& attrValue) { IAllocatorAllocate& moduleAllocator = GetCurrentSerializeContextModule().GetAllocator(); diff --git a/Code/Framework/AzCore/AzCore/Serialization/std/VariantReflection.inl b/Code/Framework/AzCore/AzCore/Serialization/std/VariantReflection.inl index 70972bb846..06d4f76c80 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/std/VariantReflection.inl +++ b/Code/Framework/AzCore/AzCore/Serialization/std/VariantReflection.inl @@ -433,9 +433,7 @@ namespace AZ m_classData.m_attributes.set_allocator(AZStd::move(dllAllocator)); // Create the ObjectStreamWriteOverrideCB in the current module - using ContainerType = AttributeData>; - m_classData.m_attributes.emplace_back(AZ_CRC("ObjectStreamWriteElementOverride", 0x35eb659f), CreateModuleAttribute(&ObjectStreamWriter)); + m_classData.m_attributes.emplace_back(AZ_CRC("ObjectStreamWriteElementOverride", 0x35eb659f), CreateModuleAttribute(&ObjectStreamWriter)); } SerializeContext::ClassData* GetClassData() override diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryConsoleUtils.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryConsoleUtils.cpp index 7a8ab85ce3..e5f87f4e7f 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryConsoleUtils.cpp +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryConsoleUtils.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -36,7 +37,7 @@ namespace AZ::SettingsRegistryConsoleUtils combinedKeyValueCommand.c_str()); AZ::Debug::Trace::Output("SettingsRegistry", setOutput.c_str()); } - }; + } static void ConsoleRemoveSettingsRegistryValue(SettingsRegistryInterface& settingsRegistry, const ConsoleCommandContainer& commandArgs) { @@ -57,7 +58,7 @@ namespace AZ::SettingsRegistryConsoleUtils AZ::Debug::Trace::Output("SettingsRegistry", removeOutput.c_str()); } } - }; + } static void ConsoleDumpSettingsRegistryValue(SettingsRegistryInterface& settingsRegistry, const ConsoleCommandContainer& commandArgs) { @@ -88,13 +89,39 @@ namespace AZ::SettingsRegistryConsoleUtils } AZ::Debug::Trace::Output("SettingsRegistry", outputString.c_str()); - }; + } static void ConsoleDumpAllSettingsRegistryValues(SettingsRegistryInterface& settingsRegistry, [[maybe_unused]] const ConsoleCommandContainer& commandArgs) { ConsoleDumpSettingsRegistryValue(settingsRegistry, { "" }); - }; + } + + static void ConsoleMergeFileToSettingsRegistry(SettingsRegistryInterface& settingsRegistry, const ConsoleCommandContainer& commandArgs) + { + if (commandArgs.empty()) + { + AZ_Error("SettingsRegistryConsoleUtils", false, "Command %s requires a argument to locate json file to merge", + SettingsRegistryMergeFile); + return; + } + + auto commandArgumentsIter = commandArgs.begin(); + // Extract the JSON pointer path from the argument list + AZStd::string_view filePath{ *commandArgumentsIter++ }; + AZ::SettingsRegistryInterface::FixedValueString jsonAnchorPath; + AZ::StringFunc::Join(jsonAnchorPath, commandArgumentsIter, commandArgs.end(), ' '); + + const auto mergeFormat = AZ::IO::PathView(filePath).Extension() != ".setregpatch" ? AZ::SettingsRegistryInterface::Format::JsonMergePatch : AZ::SettingsRegistryInterface::Format::JsonPatch; + if (settingsRegistry.MergeSettingsFile(filePath, mergeFormat, jsonAnchorPath)) + { + const auto mergeFileOutput = AZ::SettingsRegistryInterface::FixedValueString::format( + R"(Merged json file "%*.s" anchored to json path "%s" into the global settings registry)" "\n", + AZ_STRING_ARG(filePath), jsonAnchorPath.c_str()); + AZ::Debug::Trace::Output("SettingsRegistry", mergeFileOutput.c_str()); + } + } + [[nodiscard]] ConsoleFunctorHandle RegisterAzConsoleCommands(SettingsRegistryInterface& registry, AZ::IConsole& azConsole) { @@ -115,6 +142,11 @@ namespace AZ::SettingsRegistryConsoleUtils resultHandle.m_consoleFunctors.emplace_back(azConsole, SettingsRegistryDumpAll, R"(Dumps all values from the global settings registry)" "\n", ConsoleFunctorFlags::Null, AZ::TypeId::CreateNull(), registry, &ConsoleDumpAllSettingsRegistryValues); + resultHandle.m_consoleFunctors.emplace_back(azConsole, SettingsRegistryMergeFile, + R"(Merges File into the global settings registry)" "\n" + R"(@param file-path - path to JSON formatted file to merge)" "\n" + R"(@param anchor-path - JSON path to anchor merge operation. Defaults to "")" "\n", + ConsoleFunctorFlags::Null, AZ::TypeId::CreateNull(), registry, &ConsoleMergeFileToSettingsRegistry); return resultHandle; } diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryConsoleUtils.h b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryConsoleUtils.h index ba0d552dde..2e37431839 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryConsoleUtils.h +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryConsoleUtils.h @@ -14,15 +14,16 @@ namespace AZ::SettingsRegistryConsoleUtils { - //! Only 4 console command are registered for the settings registry - //! "regset", "regremove", "regdump", "regdumpall" + //! The following console command are registered for the settings registry + //! "regset", "regremove", "regdump", "regdumpall", "regset-file" //! The value should be increased if more commands are needed - inline constexpr size_t MaxSettingsRegistryConsoleFunctors = 4; + inline constexpr size_t MaxSettingsRegistryConsoleFunctors = 5; inline constexpr const char* SettingsRegistrySet = "sr_regset"; inline constexpr const char* SettingsRegistryRemove = "sr_regremove"; inline constexpr const char* SettingsRegistryDump = "sr_regdump"; inline constexpr const char* SettingsRegistryDumpAll = "sr_regdumpall"; + inline constexpr const char* SettingsRegistryMergeFile = "sr_regset_file"; // RAII structure which owns the instances of the Settings Registry Console commands // registered with an AZ Console @@ -51,6 +52,10 @@ namespace AZ::SettingsRegistryConsoleUtils //! //! "sr_regdumpall" accepts 0 arguments and dumps the entire settings registry //! NOTE: this might result in a large amount of output to the console + //! + //! "sr_regset_file" accepts 1 or 2 arguments - [] + //! Merges the json formatted file into the settings registry underneath the root anchor "" + //! or if supplied [[nodiscard]] ConsoleFunctorHandle RegisterAzConsoleCommands(SettingsRegistryInterface& registry, AZ::IConsole& azConsole); } diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp index cbf25c29d4..43c8a64b93 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryImpl.cpp @@ -327,7 +327,7 @@ namespace AZ while (!localNotifierQueue.empty()) { - for (SignalNotifierArgs notifierArgs : localNotifierQueue) + for (const SignalNotifierArgs& notifierArgs : localNotifierQueue) { localNotifierEvent.Signal(notifierArgs.m_jsonPath, notifierArgs.m_type); } diff --git a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp index 975217a131..eed0112ad1 100644 --- a/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp +++ b/Code/Framework/AzCore/AzCore/Settings/SettingsRegistryMergeUtils.cpp @@ -19,9 +19,6 @@ #include #include #include -#include -#include -#include #include #include @@ -983,7 +980,7 @@ namespace AZ::SettingsRegistryMergeUtils // code in the loop makes calls that mutates the `commandLine` instance, invalidating the iterators. Making a copy // ensures that the iterators remain valid. // NOLINTNEXTLINE(performance-unnecessary-value-param) - void MergeSettingsToRegistry_CommandLine(SettingsRegistryInterface& registry, AZ::CommandLine commandLine, bool executeCommands) + void MergeSettingsToRegistry_CommandLine(SettingsRegistryInterface& registry, AZ::CommandLine commandLine, bool executeRegdumpCommands) { // Iterate over all the command line options in order to parse the --regset and --regremove // arguments in the order they were supplied @@ -998,18 +995,44 @@ namespace AZ::SettingsRegistryMergeUtils continue; } } + else if (commandArgument.m_option == "regset-file") + { + AZStd::string_view fileArg(commandArgument.m_value); + AZStd::string_view jsonAnchorPath; + // double colons is treated as the separator for an anchor path + // single colon cannot be used as it is used in Windows paths + if (auto anchorPathIndex = AZ::StringFunc::Find(fileArg, "::"); + anchorPathIndex != AZStd::string_view::npos) + { + jsonAnchorPath = fileArg.substr(anchorPathIndex + 2); + fileArg = fileArg.substr(0, anchorPathIndex); + } + if (!fileArg.empty()) + { + AZ::IO::PathView filePath(fileArg); + const auto mergeFormat = filePath.Extension() != ".setregpatch" + ? AZ::SettingsRegistryInterface::Format::JsonMergePatch + : AZ::SettingsRegistryInterface::Format::JsonPatch; + if (!registry.MergeSettingsFile(filePath.Native(), mergeFormat, jsonAnchorPath)) + { + AZ_Warning("SettingsRegistryMergeUtils", false, R"(Merging of file "%.*s" to the Settings Registry has failed at anchor "%.*s".)", + AZ_STRING_ARG(filePath.Native()), AZ_STRING_ARG(jsonAnchorPath)); + continue; + } + } + } else if (commandArgument.m_option == "regremove") { if (!registry.Remove(commandArgument.m_value)) { AZ_Warning("SettingsRegistryMergeUtils", false, "Unable to remove value at JSON Pointer %s for --regremove.", - commandArgument.m_value.data()); + commandArgument.m_value.c_str()); continue; } } } - if (executeCommands) + if (executeRegdumpCommands) { constexpr bool prettifyOutput = true; const size_t regdumpSwitchValues = commandLine.GetNumSwitchValues("regdump"); diff --git a/Code/Framework/AzCore/AzCore/Slice/SliceAssetHandler.cpp b/Code/Framework/AzCore/AzCore/Slice/SliceAssetHandler.cpp index 551ca1258d..a942d0c9f1 100644 --- a/Code/Framework/AzCore/AzCore/Slice/SliceAssetHandler.cpp +++ b/Code/Framework/AzCore/AzCore/Slice/SliceAssetHandler.cpp @@ -13,7 +13,6 @@ #include #include #include -#include namespace AZ { diff --git a/Code/Framework/AzCore/AzCore/Slice/SliceComponent.cpp b/Code/Framework/AzCore/AzCore/Slice/SliceComponent.cpp index 0b410369f0..ca89e95162 100644 --- a/Code/Framework/AzCore/AzCore/Slice/SliceComponent.cpp +++ b/Code/Framework/AzCore/AzCore/Slice/SliceComponent.cpp @@ -840,7 +840,7 @@ namespace AZ } SliceComponent::SliceInstance* SliceComponent::SliceReference::CreateInstanceFromExistingEntities(AZStd::vector& entities, - const EntityIdToEntityIdMap assetToLiveIdMap, + const EntityIdToEntityIdMap& assetToLiveIdMap, SliceInstanceId sliceInstanceId) { AZ_PROFILE_FUNCTION(AzCore); diff --git a/Code/Framework/AzCore/AzCore/Slice/SliceComponent.h b/Code/Framework/AzCore/AzCore/Slice/SliceComponent.h index 7a66167a96..ae4ec155c7 100644 --- a/Code/Framework/AzCore/AzCore/Slice/SliceComponent.h +++ b/Code/Framework/AzCore/AzCore/Slice/SliceComponent.h @@ -443,7 +443,7 @@ namespace AZ * @return A pointer to the newly created slice instance. Returns nullptr on error or if the SliceComponent is not instantiated. */ SliceInstance* CreateInstanceFromExistingEntities(AZStd::vector& entities, - const EntityIdToEntityIdMap assetToLiveIdMap, + const EntityIdToEntityIdMap& assetToLiveIdMap, SliceInstanceId sliceInstanceId = SliceInstanceId::CreateRandom()); /** diff --git a/Code/Framework/AzCore/AzCore/Socket/AzSocket.cpp b/Code/Framework/AzCore/AzCore/Socket/AzSocket.cpp index f15c288ad8..71c0aeea6d 100644 --- a/Code/Framework/AzCore/AzCore/Socket/AzSocket.cpp +++ b/Code/Framework/AzCore/AzCore/Socket/AzSocket.cpp @@ -8,77 +8,74 @@ #include -namespace AZ +namespace AZ::AzSock { - namespace AzSock + AzSocketAddress::AzSocketAddress() { - AzSocketAddress::AzSocketAddress() - { - Reset(); - } - - AzSocketAddress& AzSocketAddress::operator=(const AZSOCKADDR& addr) - { - m_sockAddr = *reinterpret_cast(&addr); - return *this; - } - - bool AzSocketAddress::operator==(const AzSocketAddress& rhs) const - { - return m_sockAddr.sin_family == rhs.m_sockAddr.sin_family - && m_sockAddr.sin_addr.s_addr == rhs.m_sockAddr.sin_addr.s_addr - && m_sockAddr.sin_port == rhs.m_sockAddr.sin_port; - } - - const AZSOCKADDR* AzSocketAddress::GetTargetAddress() const - { - return reinterpret_cast(&m_sockAddr); - } - - AZStd::string AzSocketAddress::GetIP() const - { - char ip[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, const_cast(&m_sockAddr.sin_addr), ip, AZ_ARRAY_SIZE(ip)); - return AZStd::string(ip); - } - - AZStd::string AzSocketAddress::GetAddress() const - { - char ip[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, const_cast(&m_sockAddr.sin_addr), ip, AZ_ARRAY_SIZE(ip)); - return AZStd::string::format("%s:%d", ip, AZ::AzSock::NetToHostShort(m_sockAddr.sin_port)); - } - - AZ::u16 AzSocketAddress::GetAddrPort() const - { - return AZ::AzSock::NetToHostShort(m_sockAddr.sin_port); - } - - void AzSocketAddress::SetAddrPort(AZ::u16 port) - { - m_sockAddr.sin_port = AZ::AzSock::HostToNetShort(port); - } - - bool AzSocketAddress::SetAddress(const AZStd::string& ip, AZ::u16 port) - { - AZ_Assert(!ip.empty(), "Invalid address string!"); - Reset(); - return AZ::AzSock::ResolveAddress(ip, port, m_sockAddr); - } - - bool AzSocketAddress::SetAddress(AZ::u32 ip, AZ::u16 port) - { - Reset(); - m_sockAddr.sin_addr.s_addr = AZ::AzSock::HostToNetLong(ip); - m_sockAddr.sin_port = AZ::AzSock::HostToNetShort(port); - return true; - } - - void AzSocketAddress::Reset() - { - memset(&m_sockAddr, 0, sizeof(m_sockAddr)); - m_sockAddr.sin_family = AF_INET; - m_sockAddr.sin_addr.s_addr = INADDR_ANY; - } + Reset(); } -} + + AzSocketAddress& AzSocketAddress::operator=(const AZSOCKADDR& addr) + { + m_sockAddr = *reinterpret_cast(&addr); + return *this; + } + + bool AzSocketAddress::operator==(const AzSocketAddress& rhs) const + { + return m_sockAddr.sin_family == rhs.m_sockAddr.sin_family + && m_sockAddr.sin_addr.s_addr == rhs.m_sockAddr.sin_addr.s_addr + && m_sockAddr.sin_port == rhs.m_sockAddr.sin_port; + } + + const AZSOCKADDR* AzSocketAddress::GetTargetAddress() const + { + return reinterpret_cast(&m_sockAddr); + } + + AZStd::string AzSocketAddress::GetIP() const + { + char ip[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, const_cast(&m_sockAddr.sin_addr), ip, AZ_ARRAY_SIZE(ip)); + return AZStd::string(ip); + } + + AZStd::string AzSocketAddress::GetAddress() const + { + char ip[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, const_cast(&m_sockAddr.sin_addr), ip, AZ_ARRAY_SIZE(ip)); + return AZStd::string::format("%s:%d", ip, AZ::AzSock::NetToHostShort(m_sockAddr.sin_port)); + } + + AZ::u16 AzSocketAddress::GetAddrPort() const + { + return AZ::AzSock::NetToHostShort(m_sockAddr.sin_port); + } + + void AzSocketAddress::SetAddrPort(AZ::u16 port) + { + m_sockAddr.sin_port = AZ::AzSock::HostToNetShort(port); + } + + bool AzSocketAddress::SetAddress(const AZStd::string& ip, AZ::u16 port) + { + AZ_Assert(!ip.empty(), "Invalid address string!"); + Reset(); + return AZ::AzSock::ResolveAddress(ip, port, m_sockAddr); + } + + bool AzSocketAddress::SetAddress(AZ::u32 ip, AZ::u16 port) + { + Reset(); + m_sockAddr.sin_addr.s_addr = AZ::AzSock::HostToNetLong(ip); + m_sockAddr.sin_port = AZ::AzSock::HostToNetShort(port); + return true; + } + + void AzSocketAddress::Reset() + { + memset(&m_sockAddr, 0, sizeof(m_sockAddr)); + m_sockAddr.sin_family = AF_INET; + m_sockAddr.sin_addr.s_addr = INADDR_ANY; + } +} // namespace AZ::AzSock diff --git a/Code/Framework/AzCore/AzCore/Statistics/RunningStatistic.cpp b/Code/Framework/AzCore/AzCore/Statistics/RunningStatistic.cpp index 47f85c8309..3093d38a10 100644 --- a/Code/Framework/AzCore/AzCore/Statistics/RunningStatistic.cpp +++ b/Code/Framework/AzCore/AzCore/Statistics/RunningStatistic.cpp @@ -10,66 +10,63 @@ #include "RunningStatistic.h" -namespace AZ +namespace AZ::Statistics { - namespace Statistics + void RunningStatistic::Reset() { - void RunningStatistic::Reset() + m_numSamples = 0; + m_mostRecentSample = 0.0; + m_minimum = 0.0; + m_maximum = 0.0; + m_sum = 0.0; + m_average = 0.0; + m_varianceTracking = 0.0; + } + + void RunningStatistic::PushSample(double value) + { + m_numSamples++; + m_mostRecentSample = value; + m_sum += value; + + if (m_numSamples == 1) { - m_numSamples = 0; - m_mostRecentSample = 0.0; - m_minimum = 0.0; - m_maximum = 0.0; - m_sum = 0.0; - m_average = 0.0; - m_varianceTracking = 0.0; + m_minimum = value; + m_maximum = value; + m_average = value; + return; } - void RunningStatistic::PushSample(double value) + if (value < m_minimum) { - m_numSamples++; - m_mostRecentSample = value; - m_sum += value; - - if (m_numSamples == 1) - { - m_minimum = value; - m_maximum = value; - m_average = value; - return; - } - - if (value < m_minimum) - { - m_minimum = value; - } - else if (value > m_maximum) - { - m_maximum = value; - } - - //See header notes and references to understand this way of calculating - //running average & variance. - const double newAverage = m_average + (value - m_average) / m_numSamples; - m_varianceTracking = m_varianceTracking + (value - m_average)*(value - newAverage); - - m_average = newAverage; + m_minimum = value; + } + else if (value > m_maximum) + { + m_maximum = value; } - double RunningStatistic::GetVariance(VarianceType varianceType) const - { - if (m_numSamples > 1) - { - const AZ::u64 varianceDivisor = (varianceType == VarianceType::S) ? m_numSamples - 1 : m_numSamples; - return m_varianceTracking / varianceDivisor; - } - return 0.0; - } + //See header notes and references to understand this way of calculating + //running average & variance. + const double newAverage = m_average + (value - m_average) / m_numSamples; + m_varianceTracking = m_varianceTracking + (value - m_average)*(value - newAverage); - double RunningStatistic::GetStdev(VarianceType varianceType) const + m_average = newAverage; + } + + double RunningStatistic::GetVariance(VarianceType varianceType) const + { + if (m_numSamples > 1) { - return sqrt(GetVariance(varianceType)); + const AZ::u64 varianceDivisor = (varianceType == VarianceType::S) ? m_numSamples - 1 : m_numSamples; + return m_varianceTracking / varianceDivisor; } - - }//namespace Statistics -}//namespace AZ + return 0.0; + } + + double RunningStatistic::GetStdev(VarianceType varianceType) const + { + return sqrt(GetVariance(varianceType)); + } + +} // namespace AZ::Statistics diff --git a/Code/Framework/AzCore/AzCore/Statistics/StatisticalProfilerProxy.h b/Code/Framework/AzCore/AzCore/Statistics/StatisticalProfilerProxy.h index 278b2ecc98..7fae0ca354 100644 --- a/Code/Framework/AzCore/AzCore/Statistics/StatisticalProfilerProxy.h +++ b/Code/Framework/AzCore/AzCore/Statistics/StatisticalProfilerProxy.h @@ -157,6 +157,22 @@ namespace AZ::Statistics } } + void GetAllStatistics(AZStd::vector& stats) + { + for (auto& iter : m_profilers) + { + iter.second.m_profiler.GetStatsManager().GetAllStatistics(stats); + } + } + + void GetAllStatisticsOfUnits(AZStd::vector& stats, const char* units) + { + for (auto& iter : m_profilers) + { + iter.second.m_profiler.GetStatsManager().GetAllStatisticsOfUnits(stats, units); + } + } + private: struct ProfilerInfo { diff --git a/Code/Framework/AzCore/AzCore/Statistics/StatisticalProfilerProxySystemComponent.cpp b/Code/Framework/AzCore/AzCore/Statistics/StatisticalProfilerProxySystemComponent.cpp index 00bb97b745..ef87307624 100644 --- a/Code/Framework/AzCore/AzCore/Statistics/StatisticalProfilerProxySystemComponent.cpp +++ b/Code/Framework/AzCore/AzCore/Statistics/StatisticalProfilerProxySystemComponent.cpp @@ -12,55 +12,52 @@ #include "StatisticalProfilerProxySystemComponent.h" //////////////////////////////////////////////////////////////////////////////////////////////////// -namespace AZ +namespace AZ::Statistics { - namespace Statistics + StatisticalProfilerProxy* StatisticalProfilerProxy::TimedScope::m_profilerProxy = nullptr; + + //////////////////////////////////////////////////////////////////////////////////////////////// + void StatisticalProfilerProxySystemComponent::Reflect(AZ::ReflectContext* context) { - StatisticalProfilerProxy* StatisticalProfilerProxy::TimedScope::m_profilerProxy = nullptr; - - //////////////////////////////////////////////////////////////////////////////////////////////// - void StatisticalProfilerProxySystemComponent::Reflect(AZ::ReflectContext* context) + if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) { - if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) - { - serializeContext->Class() - ->Version(1); - } + serializeContext->Class() + ->Version(1); } + } - //////////////////////////////////////////////////////////////////////////////////////////////// - void StatisticalProfilerProxySystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) - { - provided.push_back(AZ_CRC("StatisticalProfilerService", 0x20066f73)); - } + //////////////////////////////////////////////////////////////////////////////////////////////// + void StatisticalProfilerProxySystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC("StatisticalProfilerService", 0x20066f73)); + } - //////////////////////////////////////////////////////////////////////////////////////////////// - void StatisticalProfilerProxySystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) - { - incompatible.push_back(AZ_CRC("StatisticalProfilerService", 0x20066f73)); - } + //////////////////////////////////////////////////////////////////////////////////////////////// + void StatisticalProfilerProxySystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC("StatisticalProfilerService", 0x20066f73)); + } - //////////////////////////////////////////////////////////////////////////////////////////////// - StatisticalProfilerProxySystemComponent::StatisticalProfilerProxySystemComponent() - : m_StatisticalProfilerProxy(nullptr) - { - } + //////////////////////////////////////////////////////////////////////////////////////////////// + StatisticalProfilerProxySystemComponent::StatisticalProfilerProxySystemComponent() + : m_StatisticalProfilerProxy(nullptr) + { + } - //////////////////////////////////////////////////////////////////////////////////////////////// - StatisticalProfilerProxySystemComponent::~StatisticalProfilerProxySystemComponent() - { - } + //////////////////////////////////////////////////////////////////////////////////////////////// + StatisticalProfilerProxySystemComponent::~StatisticalProfilerProxySystemComponent() + { + } - //////////////////////////////////////////////////////////////////////////////////////////////// - void StatisticalProfilerProxySystemComponent::Activate() - { - m_StatisticalProfilerProxy = new StatisticalProfilerProxy; - } + //////////////////////////////////////////////////////////////////////////////////////////////// + void StatisticalProfilerProxySystemComponent::Activate() + { + m_StatisticalProfilerProxy = new StatisticalProfilerProxy; + } - //////////////////////////////////////////////////////////////////////////////////////////////// - void StatisticalProfilerProxySystemComponent::Deactivate() - { - delete m_StatisticalProfilerProxy; - } - } //namespace Statistics -} // namespace AZ + //////////////////////////////////////////////////////////////////////////////////////////////// + void StatisticalProfilerProxySystemComponent::Deactivate() + { + delete m_StatisticalProfilerProxy; + } +} // namespace AZ::Statistics diff --git a/Code/Framework/AzCore/AzCore/Statistics/StatisticsManager.h b/Code/Framework/AzCore/AzCore/Statistics/StatisticsManager.h index 5984701f4e..dc97de40c1 100644 --- a/Code/Framework/AzCore/AzCore/Statistics/StatisticsManager.h +++ b/Code/Framework/AzCore/AzCore/Statistics/StatisticsManager.h @@ -56,13 +56,25 @@ namespace AZ void GetAllStatistics(AZStd::vector& vector) { - for (auto const& it : m_statistics) + for (const auto& it : m_statistics) { NamedRunningStatistic* stat = it.second; vector.push_back(stat); } } + void GetAllStatisticsOfUnits(AZStd::vector& vector, const char* units) + { + for (const auto& it : m_statistics) + { + NamedRunningStatistic* stat = it.second; + if (stat->GetUnits() == units) + { + vector.push_back(stat); + } + } + } + //! Helper method to apply units to statistics with empty units string. AZ::u32 ApplyUnits(const AZStd::string& units) { diff --git a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp index a953d53c33..8405424f7d 100644 --- a/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp +++ b/Code/Framework/AzCore/AzCore/StringFunc/StringFunc.cpp @@ -416,2157 +416,2155 @@ namespace AZ::StringFunc::Internal } -namespace AZ +namespace AZ::StringFunc { - namespace StringFunc + AZStd::string_view LStrip(AZStd::string_view in, AZStd::string_view stripCharacters) { - AZStd::string_view LStrip(AZStd::string_view in, AZStd::string_view stripCharacters) + if (size_t pos = in.find_first_not_of(stripCharacters); pos != AZStd::string_view::npos) { - if (size_t pos = in.find_first_not_of(stripCharacters); pos != AZStd::string_view::npos) - { - return in.substr(pos); - } + return in.substr(pos); + } - return {}; - }; + return {}; + }; - AZStd::string_view RStrip(AZStd::string_view in, AZStd::string_view stripCharacters) + AZStd::string_view RStrip(AZStd::string_view in, AZStd::string_view stripCharacters) + { + if (size_t pos = in.find_last_not_of(stripCharacters); pos != AZStd::string_view::npos) { - if (size_t pos = in.find_last_not_of(stripCharacters); pos != AZStd::string_view::npos) - { - return in.substr(0, pos < in.size() ? pos + 1 : pos); - } + return in.substr(0, pos < in.size() ? pos + 1 : pos); + } - return {}; - }; + return {}; + }; - AZStd::string_view StripEnds(AZStd::string_view in, AZStd::string_view stripCharacters) + AZStd::string_view StripEnds(AZStd::string_view in, AZStd::string_view stripCharacters) + { + return LStrip(RStrip(in, stripCharacters), stripCharacters); + }; + + bool Equal(const char* inA, const char* inB, bool bCaseSensitive /*= false*/, size_t n /*= 0*/) + { + if (!inA || !inB) { - return LStrip(RStrip(in, stripCharacters), stripCharacters); - }; + return false; + } - bool Equal(const char* inA, const char* inB, bool bCaseSensitive /*= false*/, size_t n /*= 0*/) + if (inA == inB) { - if (!inA || !inB) - { - return false; - } + return true; + } - if (inA == inB) + if (bCaseSensitive) + { + if (n) { - return true; - } - - if (bCaseSensitive) - { - if (n) - { - return !strncmp(inA, inB, n); - } - else - { - return !strcmp(inA, inB); - } + return !strncmp(inA, inB, n); } else { - if (n) - { - return !azstrnicmp(inA, inB, n); - } - else - { - return !azstricmp(inA, inB); - } + return !strcmp(inA, inB); } } - bool Equal(AZStd::string_view inA, AZStd::string_view inB, bool bCaseSensitive) + else { - const size_t maxCharsToCompare = inA.size(); - - return inA.size() == inB.size() && (bCaseSensitive - ? strncmp(inA.data(), inB.data(), maxCharsToCompare) == 0 - : azstrnicmp(inA.data(), inB.data(), maxCharsToCompare) == 0); - } - - bool StartsWith(AZStd::string_view sourceValue, AZStd::string_view prefixValue, bool bCaseSensitive) - { - return sourceValue.size() >= prefixValue.size() - && Equal(sourceValue.data(), prefixValue.data(), bCaseSensitive, prefixValue.size()); - } - - bool EndsWith(AZStd::string_view sourceValue, AZStd::string_view suffixValue, bool bCaseSensitive) - { - return sourceValue.size() >= suffixValue.size() - && Equal(sourceValue.substr(sourceValue.size() - suffixValue.size(), AZStd::string_view::npos).data(), suffixValue.data(), bCaseSensitive, suffixValue.size()); - } - - bool Contains(AZStd::string_view in, char ch, bool bCaseSensitive) - { - return Find(in, ch, 0, false, bCaseSensitive) != AZStd::string_view::npos; - } - bool Contains(AZStd::string_view in, AZStd::string_view sv, bool bCaseSensitive) - { - return Find(in, sv, 0, false, bCaseSensitive) != AZStd::string_view::npos; - } - - size_t Find(AZStd::string_view in, char c, size_t pos /*= 0*/, bool bReverse /*= false*/, bool bCaseSensitive /*= false*/) - { - if (in.empty()) + if (n) { - return AZStd::string::npos; + return !azstrnicmp(inA, inB, n); } - - if (pos == AZStd::string::npos) + else { - pos = 0; + return !azstricmp(inA, inB); } + } + } + bool Equal(AZStd::string_view inA, AZStd::string_view inB, bool bCaseSensitive) + { + const size_t maxCharsToCompare = inA.size(); - size_t inLen = in.size(); - if (inLen < pos) - { - return AZStd::string::npos; - } + return inA.size() == inB.size() && (bCaseSensitive + ? strncmp(inA.data(), inB.data(), maxCharsToCompare) == 0 + : azstrnicmp(inA.data(), inB.data(), maxCharsToCompare) == 0); + } + bool StartsWith(AZStd::string_view sourceValue, AZStd::string_view prefixValue, bool bCaseSensitive) + { + return sourceValue.size() >= prefixValue.size() + && Equal(sourceValue.data(), prefixValue.data(), bCaseSensitive, prefixValue.size()); + } + + bool EndsWith(AZStd::string_view sourceValue, AZStd::string_view suffixValue, bool bCaseSensitive) + { + return sourceValue.size() >= suffixValue.size() + && Equal(sourceValue.substr(sourceValue.size() - suffixValue.size(), AZStd::string_view::npos).data(), suffixValue.data(), bCaseSensitive, suffixValue.size()); + } + + bool Contains(AZStd::string_view in, char ch, bool bCaseSensitive) + { + return Find(in, ch, 0, false, bCaseSensitive) != AZStd::string_view::npos; + } + bool Contains(AZStd::string_view in, AZStd::string_view sv, bool bCaseSensitive) + { + return Find(in, sv, 0, false, bCaseSensitive) != AZStd::string_view::npos; + } + + size_t Find(AZStd::string_view in, char c, size_t pos /*= 0*/, bool bReverse /*= false*/, bool bCaseSensitive /*= false*/) + { + if (in.empty()) + { + return AZStd::string::npos; + } + + if (pos == AZStd::string::npos) + { + pos = 0; + } + + size_t inLen = in.size(); + if (inLen < pos) + { + return AZStd::string::npos; + } + + if (!bCaseSensitive) + { + c = (char)tolower(c); + } + + if (bReverse) + { + pos = inLen - pos - 1; + } + + char character; + + do + { if (!bCaseSensitive) { - c = (char)tolower(c); + character = (char)tolower(in[pos]); + } + else + { + character = in[pos]; + } + + if (character == c) + { + return pos; } if (bReverse) { - pos = inLen - pos - 1; + pos = pos > 0 ? pos-1 : pos; } - - char character; - - do + else { - if (!bCaseSensitive) - { - character = (char)tolower(in[pos]); - } - else - { - character = in[pos]; - } + pos++; + } + } while (bReverse ? pos : character != '\0'); - if (character == c) - { - return pos; - } + return AZStd::string::npos; + } - if (bReverse) - { - pos = pos > 0 ? pos-1 : pos; - } - else - { - pos++; - } - } while (bReverse ? pos : character != '\0'); + size_t Find(AZStd::string_view in, AZStd::string_view s, size_t offset /*= 0*/, bool bReverse /*= false*/, bool bCaseSensitive /*= false*/) + { + // Formally an empty string matches at the offset if it is <= to the size of the input string + if (s.empty() && offset <= in.size()) + { + return offset; + } + if (in.empty()) + { return AZStd::string::npos; } - size_t Find(AZStd::string_view in, AZStd::string_view s, size_t offset /*= 0*/, bool bReverse /*= false*/, bool bCaseSensitive /*= false*/) + const size_t inlen = in.size(); + const size_t slen = s.size(); + + if (offset == AZStd::string::npos) { - // Formally an empty string matches at the offset if it is <= to the size of the input string - if (s.empty() && offset <= in.size()) + offset = 0; + } + + if (offset + slen > inlen) + { + return AZStd::string::npos; + } + + const char* pCur; + + if (bReverse) + { + // Start at the end (- pos) + pCur = in.data() + inlen - slen - offset; + } + else + { + // Start at the beginning (+ pos) + pCur = in.data() + offset; + } + + do + { + if (bCaseSensitive) { - return offset; + if (!strncmp(pCur, s.data(), slen)) + { + return static_cast(pCur - in.data()); + } } - - if (in.empty()) + else { - return AZStd::string::npos; + if (!azstrnicmp(pCur, s.data(), slen)) + { + return static_cast(pCur - in.data()); + } } - const size_t inlen = in.size(); - const size_t slen = s.size(); - - if (offset == AZStd::string::npos) - { - offset = 0; - } - - if (offset + slen > inlen) - { - return AZStd::string::npos; - } - - const char* pCur; - if (bReverse) { - // Start at the end (- pos) - pCur = in.data() + inlen - slen - offset; + pCur--; } else { - // Start at the beginning (+ pos) - pCur = in.data() + offset; + pCur++; + } + } while (bReverse ? pCur >= in.data() : pCur - in.data() <= static_cast(inlen)); + + return AZStd::string::npos; + } + + char FirstCharacter(const char* in) + { + if (!in) + { + return '\0'; + } + if (in[0] == '\n') + { + return '\0'; + } + return in[0]; + } + + char LastCharacter(const char* in) + { + if (!in) + { + return '\0'; + } + size_t len = strlen(in); + if (!len) + { + return '\0'; + } + return in[len - 1]; + } + + AZStd::string& Append(AZStd::string& inout, const char s) + { + return inout.append(1, s); + } + + AZStd::string& Append(AZStd::string& inout, const char* str) + { + if (!str) + { + return inout; + } + return inout.append(str); + } + + AZStd::string& Prepend(AZStd::string& inout, const char s) + { + return inout.insert((size_t)0, 1, s); + } + + AZStd::string& Prepend(AZStd::string& inout, const char* str) + { + if (!str) + { + return inout; + } + return inout.insert(0, str); + } + + AZStd::string& LChop(AZStd::string& inout, size_t num) + { + return Internal::LChop(inout, num); + } + + AZStd::string_view LChop(AZStd::string_view in, size_t num) + { + return Internal::LChop(in, num); + } + + AZStd::string& RChop(AZStd::string& inout, size_t num) + { + return Internal::RChop(inout, num); + } + + AZStd::string_view RChop(AZStd::string_view in, size_t num) + { + return Internal::RChop(in, num); + } + + AZStd::string& LKeep(AZStd::string& inout, size_t pos, bool bKeepPosCharacter) + { + return Internal::LKeep(inout, pos, bKeepPosCharacter); + } + + AZStd::string& RKeep(AZStd::string& inout, size_t pos, bool bKeepPosCharacter) + { + return Internal::RKeep(inout, pos, bKeepPosCharacter); + } + + bool Replace(AZStd::string& inout, const char replaceA, const char withB, bool bCaseSensitive, bool bReplaceFirst, bool bReplaceLast) + { + return Internal::Replace(inout, replaceA, withB, bCaseSensitive, bReplaceFirst, bReplaceLast); + } + + bool Replace(AZStd::string& inout, const char* replaceA, const char* withB, bool bCaseSensitive, bool bReplaceFirst, bool bReplaceLast) + { + return Internal::Replace(inout, replaceA, withB, bCaseSensitive, bReplaceFirst, bReplaceLast); + } + + bool Strip(AZStd::string& inout, const char stripCharacter, bool bCaseSensitive, bool bStripBeginning, bool bStripEnding) + { + return Internal::Strip(inout, stripCharacter, bCaseSensitive, bStripBeginning, bStripEnding); + } + + bool Strip(AZStd::string& inout, const char* stripCharacters, bool bCaseSensitive, bool bStripBeginning, bool bStripEnding) + { + return Internal::Strip(inout, stripCharacters, bCaseSensitive, bStripBeginning, bStripEnding); + } + + AZStd::string& TrimWhiteSpace(AZStd::string& value, bool leading, bool trailing) + { + static const char* trimmable = " \t\r\n"; + if (value.length() > 0) + { + if (leading) + { + value.erase(0, value.find_first_not_of(trimmable)); + } + if (trailing) + { + value.erase(value.find_last_not_of(trimmable) + 1); + } + } + return value; + } + + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings) + { + return Tokenize(in, tokens, { &delimiter, 1 }, keepEmptyStrings, keepSpaceStrings); + } + + void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters, bool keepEmptyStrings, bool keepSpaceStrings) + { + auto insertVisitor = [&tokens](AZStd::string_view token) + { + tokens.push_back(token); + }; + return TokenizeVisitor(in, insertVisitor, delimiters, keepEmptyStrings, keepSpaceStrings); + } + + void TokenizeVisitor(AZStd::string_view in, const TokenVisitor& tokenVisitor, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings) + { + return TokenizeVisitor(in, tokenVisitor, { &delimiter, 1 }, keepEmptyStrings, keepSpaceStrings); + } + + void TokenizeVisitor(AZStd::string_view in, const TokenVisitor& tokenVisitor, AZStd::string_view delimiters, + bool keepEmptyStrings, bool keepSpaceStrings) + { + if (delimiters.empty() || in.empty()) + { + return; + } + + while (AZStd::optional nextToken = TokenizeNext(in, delimiters)) + { + bool bIsEmpty = nextToken->empty(); + bool bIsSpaces = false; + if (!bIsEmpty) + { + AZStd::string_view strippedNextToken = StripEnds(*nextToken, " "); + bIsSpaces = strippedNextToken.empty(); } - do + if ((bIsEmpty && keepEmptyStrings) || + (bIsSpaces && keepSpaceStrings) || + (!bIsSpaces && !bIsEmpty)) { - if (bCaseSensitive) + tokenVisitor(*nextToken); + } + } + } + + void TokenizeVisitorReverse(AZStd::string_view in, const TokenVisitor& tokenVisitor, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings) + { + return TokenizeVisitorReverse(in, tokenVisitor, { &delimiter, 1 }, keepEmptyStrings, keepSpaceStrings); + } + + void TokenizeVisitorReverse(AZStd::string_view in, const TokenVisitor& tokenVisitor, AZStd::string_view delimiters, + bool keepEmptyStrings, bool keepSpaceStrings) + { + if (delimiters.empty() || in.empty()) + { + return; + } + + while (AZStd::optional nextToken = TokenizeLast(in, delimiters)) + { + bool bIsEmpty = nextToken->empty(); + bool bIsSpaces = false; + if (!bIsEmpty) + { + AZStd::string_view strippedNextToken = StripEnds(*nextToken, " "); + bIsSpaces = strippedNextToken.empty(); + } + + if ((bIsEmpty && keepEmptyStrings) || + (bIsSpaces && keepSpaceStrings) || + (!bIsSpaces && !bIsEmpty)) + { + tokenVisitor(*nextToken); + } + } + } + + AZStd::optional TokenizeNext(AZStd::string_view& inout, const char delimiter) + { + return TokenizeNext(inout, { &delimiter, 1 }); + } + AZStd::optional TokenizeNext(AZStd::string_view& inout, AZStd::string_view delimiters) + { + if (delimiters.empty() || inout.empty()) + { + return AZStd::nullopt; + } + + AZStd::string_view resultToken; + if (size_t pos = inout.find_first_of(delimiters); pos == AZStd::string_view::npos) + { + // The delimiter has not been found, a new view containing the entire + // string will be returned and the input parameter will be set to empty + resultToken.swap(inout); + } + else + { + resultToken = { inout.data(), pos }; + // Strip off all previous characters before the delimiter plus + // the delimiter itself from the input view + inout.remove_prefix(pos + 1); + } + + return resultToken; + } + + AZStd::optional TokenizeLast(AZStd::string_view& inout, const char delimiter) + { + return TokenizeLast(inout, { &delimiter, 1 }); + } + AZStd::optional TokenizeLast(AZStd::string_view& inout, AZStd::string_view delimiters) + { + if (delimiters.empty() || inout.empty()) + { + return AZStd::nullopt; + } + + AZStd::string_view resultToken; + if (size_t pos = inout.find_last_of(delimiters); pos == AZStd::string_view::npos) + { + // The delimiter has not been found, a new view containing the entire + // string will be returned and the input parameter will be set to empty + resultToken.swap(inout); + } + else + { + resultToken = inout.substr(pos + 1); + // Strip off all previous characters before the delimiter plus + // the delimiter itself from the input view + inout = inout.substr(0, pos); + } + + return resultToken; + } + + bool FindFirstOf(AZStd::string_view inString, size_t offset, const AZStd::vector& searchStrings, uint32_t& outIndex, size_t& outOffset) + { + bool found = false; + + outIndex = 0; + outOffset = AZStd::string::npos; + for (int32_t i = 0; i < searchStrings.size(); ++i) + { + const AZStd::string& search = searchStrings[i]; + + size_t entry = inString.find(search, offset); + if (entry != AZStd::string::npos) + { + if (!found || (entry < outOffset)) { - if (!strncmp(pCur, s.data(), slen)) - { - return static_cast(pCur - in.data()); - } - } - else - { - if (!azstrnicmp(pCur, s.data(), slen)) - { - return static_cast(pCur - in.data()); - } - } - - if (bReverse) - { - pCur--; - } - else - { - pCur++; - } - } while (bReverse ? pCur >= in.data() : pCur - in.data() <= static_cast(inlen)); - - return AZStd::string::npos; - } - - char FirstCharacter(const char* in) - { - if (!in) - { - return '\0'; - } - if (in[0] == '\n') - { - return '\0'; - } - return in[0]; - } - - char LastCharacter(const char* in) - { - if (!in) - { - return '\0'; - } - size_t len = strlen(in); - if (!len) - { - return '\0'; - } - return in[len - 1]; - } - - AZStd::string& Append(AZStd::string& inout, const char s) - { - return inout.append(1, s); - } - - AZStd::string& Append(AZStd::string& inout, const char* str) - { - if (!str) - { - return inout; - } - return inout.append(str); - } - - AZStd::string& Prepend(AZStd::string& inout, const char s) - { - return inout.insert((size_t)0, 1, s); - } - - AZStd::string& Prepend(AZStd::string& inout, const char* str) - { - if (!str) - { - return inout; - } - return inout.insert(0, str); - } - - AZStd::string& LChop(AZStd::string& inout, size_t num) - { - return Internal::LChop(inout, num); - } - - AZStd::string_view LChop(AZStd::string_view in, size_t num) - { - return Internal::LChop(in, num); - } - - AZStd::string& RChop(AZStd::string& inout, size_t num) - { - return Internal::RChop(inout, num); - } - - AZStd::string_view RChop(AZStd::string_view in, size_t num) - { - return Internal::RChop(in, num); - } - - AZStd::string& LKeep(AZStd::string& inout, size_t pos, bool bKeepPosCharacter) - { - return Internal::LKeep(inout, pos, bKeepPosCharacter); - } - - AZStd::string& RKeep(AZStd::string& inout, size_t pos, bool bKeepPosCharacter) - { - return Internal::RKeep(inout, pos, bKeepPosCharacter); - } - - bool Replace(AZStd::string& inout, const char replaceA, const char withB, bool bCaseSensitive, bool bReplaceFirst, bool bReplaceLast) - { - return Internal::Replace(inout, replaceA, withB, bCaseSensitive, bReplaceFirst, bReplaceLast); - } - - bool Replace(AZStd::string& inout, const char* replaceA, const char* withB, bool bCaseSensitive, bool bReplaceFirst, bool bReplaceLast) - { - return Internal::Replace(inout, replaceA, withB, bCaseSensitive, bReplaceFirst, bReplaceLast); - } - - bool Strip(AZStd::string& inout, const char stripCharacter, bool bCaseSensitive, bool bStripBeginning, bool bStripEnding) - { - return Internal::Strip(inout, stripCharacter, bCaseSensitive, bStripBeginning, bStripEnding); - } - - bool Strip(AZStd::string& inout, const char* stripCharacters, bool bCaseSensitive, bool bStripBeginning, bool bStripEnding) - { - return Internal::Strip(inout, stripCharacters, bCaseSensitive, bStripBeginning, bStripEnding); - } - - AZStd::string& TrimWhiteSpace(AZStd::string& value, bool leading, bool trailing) - { - static const char* trimmable = " \t\r\n"; - if (value.length() > 0) - { - if (leading) - { - value.erase(0, value.find_first_not_of(trimmable)); - } - if (trailing) - { - value.erase(value.find_last_not_of(trimmable) + 1); - } - } - return value; - } - - void Tokenize(AZStd::string_view in, AZStd::vector& tokens, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings) - { - return Tokenize(in, tokens, { &delimiter, 1 }, keepEmptyStrings, keepSpaceStrings); - } - - void Tokenize(AZStd::string_view in, AZStd::vector& tokens, AZStd::string_view delimiters, bool keepEmptyStrings, bool keepSpaceStrings) - { - auto insertVisitor = [&tokens](AZStd::string_view token) - { - tokens.push_back(token); - }; - return TokenizeVisitor(in, insertVisitor, delimiters, keepEmptyStrings, keepSpaceStrings); - } - - void TokenizeVisitor(AZStd::string_view in, const TokenVisitor& tokenVisitor, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings) - { - return TokenizeVisitor(in, tokenVisitor, { &delimiter, 1 }, keepEmptyStrings, keepSpaceStrings); - } - - void TokenizeVisitor(AZStd::string_view in, const TokenVisitor& tokenVisitor, AZStd::string_view delimiters, - bool keepEmptyStrings, bool keepSpaceStrings) - { - if (delimiters.empty() || in.empty()) - { - return; - } - - while (AZStd::optional nextToken = TokenizeNext(in, delimiters)) - { - bool bIsEmpty = nextToken->empty(); - bool bIsSpaces = false; - if (!bIsEmpty) - { - AZStd::string_view strippedNextToken = StripEnds(*nextToken, " "); - bIsSpaces = strippedNextToken.empty(); - } - - if ((bIsEmpty && keepEmptyStrings) || - (bIsSpaces && keepSpaceStrings) || - (!bIsSpaces && !bIsEmpty)) - { - tokenVisitor(*nextToken); + found = true; + outIndex = i; + outOffset = entry; } } } - void TokenizeVisitorReverse(AZStd::string_view in, const TokenVisitor& tokenVisitor, const char delimiter, bool keepEmptyStrings, bool keepSpaceStrings) + return found; + } + + void Tokenize(AZStd::string_view input, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings /*= false*/, bool keepSpaceStrings /*= false*/) + { + if (input.empty()) { - return TokenizeVisitorReverse(in, tokenVisitor, { &delimiter, 1 }, keepEmptyStrings, keepSpaceStrings); + return; } - void TokenizeVisitorReverse(AZStd::string_view in, const TokenVisitor& tokenVisitor, AZStd::string_view delimiters, - bool keepEmptyStrings, bool keepSpaceStrings) + size_t offset = 0; + for (;;) { - if (delimiters.empty() || in.empty()) + uint32_t nextMatch = 0; + size_t nextOffset = offset; + if (!FindFirstOf(input, offset, delimiters, nextMatch, nextOffset)) { - return; + // No more occurrences of a separator, consume whatever is left and exit + tokens.push_back(input.substr(offset)); + break; } - while (AZStd::optional nextToken = TokenizeLast(in, delimiters)) + // Take the substring, not including the separator, and increment our offset + AZStd::string nextSubstring = input.substr(offset, nextOffset - offset); + if (keepEmptyStrings || keepSpaceStrings || !nextSubstring.empty()) { - bool bIsEmpty = nextToken->empty(); - bool bIsSpaces = false; - if (!bIsEmpty) - { - AZStd::string_view strippedNextToken = StripEnds(*nextToken, " "); - bIsSpaces = strippedNextToken.empty(); - } - - if ((bIsEmpty && keepEmptyStrings) || - (bIsSpaces && keepSpaceStrings) || - (!bIsSpaces && !bIsEmpty)) - { - tokenVisitor(*nextToken); - } + tokens.push_back(nextSubstring); } + + offset = nextOffset + delimiters[nextMatch].size(); } + } - AZStd::optional TokenizeNext(AZStd::string_view& inout, const char delimiter) + int ToInt(const char* in) + { + if (!in) { - return TokenizeNext(inout, { &delimiter, 1 }); + return 0; } - AZStd::optional TokenizeNext(AZStd::string_view& inout, AZStd::string_view delimiters) + return atoi(in); + } + + bool LooksLikeInt(const char* in, int* pInt /*=nullptr*/) + { + if (!in) { - if (delimiters.empty() || inout.empty()) - { - return AZStd::nullopt; - } - - AZStd::string_view resultToken; - if (size_t pos = inout.find_first_of(delimiters); pos == AZStd::string_view::npos) - { - // The delimiter has not been found, a new view containing the entire - // string will be returned and the input parameter will be set to empty - resultToken.swap(inout); - } - else - { - resultToken = { inout.data(), pos }; - // Strip off all previous characters before the delimiter plus - // the delimiter itself from the input view - inout.remove_prefix(pos + 1); - } - - return resultToken; - } - - AZStd::optional TokenizeLast(AZStd::string_view& inout, const char delimiter) - { - return TokenizeLast(inout, { &delimiter, 1 }); - } - AZStd::optional TokenizeLast(AZStd::string_view& inout, AZStd::string_view delimiters) - { - if (delimiters.empty() || inout.empty()) - { - return AZStd::nullopt; - } - - AZStd::string_view resultToken; - if (size_t pos = inout.find_last_of(delimiters); pos == AZStd::string_view::npos) - { - // The delimiter has not been found, a new view containing the entire - // string will be returned and the input parameter will be set to empty - resultToken.swap(inout); - } - else - { - resultToken = inout.substr(pos + 1); - // Strip off all previous characters before the delimiter plus - // the delimiter itself from the input view - inout = inout.substr(0, pos); - } - - return resultToken; - } - - bool FindFirstOf(AZStd::string_view inString, size_t offset, const AZStd::vector& searchStrings, uint32_t& outIndex, size_t& outOffset) - { - bool found = false; - - outIndex = 0; - outOffset = AZStd::string::npos; - for (int32_t i = 0; i < searchStrings.size(); ++i) - { - const AZStd::string& search = searchStrings[i]; - - size_t entry = inString.find(search, offset); - if (entry != AZStd::string::npos) - { - if (!found || (entry < outOffset)) - { - found = true; - outIndex = i; - outOffset = entry; - } - } - } - - return found; - } - - void Tokenize(AZStd::string_view input, AZStd::vector& tokens, const AZStd::vector& delimiters, bool keepEmptyStrings /*= false*/, bool keepSpaceStrings /*= false*/) - { - if (input.empty()) - { - return; - } - - size_t offset = 0; - for (;;) - { - uint32_t nextMatch = 0; - size_t nextOffset = offset; - if (!FindFirstOf(input, offset, delimiters, nextMatch, nextOffset)) - { - // No more occurrences of a separator, consume whatever is left and exit - tokens.push_back(input.substr(offset)); - break; - } - - // Take the substring, not including the separator, and increment our offset - AZStd::string nextSubstring = input.substr(offset, nextOffset - offset); - if (keepEmptyStrings || keepSpaceStrings || !nextSubstring.empty()) - { - tokens.push_back(nextSubstring); - } - - offset = nextOffset + delimiters[nextMatch].size(); - } - } - - int ToInt(const char* in) - { - if (!in) - { - return 0; - } - return atoi(in); - } - - bool LooksLikeInt(const char* in, int* pInt /*=nullptr*/) - { - if (!in) - { - return false; - } - - //if pos is past then end of the string false - size_t len = strlen(in); - if (!len)//must at least 1 characters to work with "1" - { - return false; - } - - const char* pStr = in; - - size_t countNeg = 0; - while (*pStr != '\0' && - (isdigit(*pStr) || - *pStr == '-')) - { - if (*pStr == '-') - { - countNeg++; - } - pStr++; - } - - if (*pStr == '\0' && - countNeg < 2) - { - if (pInt) - { - *pInt = ToInt(in); - } - - return true; - } return false; } - double ToDouble(const char* in) + //if pos is past then end of the string false + size_t len = strlen(in); + if (!len)//must at least 1 characters to work with "1" { - if (!in) - { - return 0.; - } - return atof(in); - } - - bool LooksLikeDouble(const char* in, double* pDouble) - { - if (!in) - { - return false; - } - - size_t len = strlen(in); - if (len < 2)//must have at least 2 characters to work with "1." - { - return false; - } - - const char* pStr = in; - - size_t countDot = 0; - size_t countNeg = 0; - while (*pStr != '\0' && - (isdigit(*pStr) || - (*pStr == '-' || - *pStr == '.'))) - { - if (*pStr == '.') - { - countDot++; - } - if (*pStr == '-') - { - countNeg++; - } - pStr++; - } - - if (*pStr == '\0' && - countDot == 1 && - countNeg < 2) - { - if (pDouble) - { - *pDouble = ToDouble(in); - } - - return true; - } - return false; } - float ToFloat(const char* in) + const char* pStr = in; + + size_t countNeg = 0; + while (*pStr != '\0' && + (isdigit(*pStr) || + *pStr == '-')) { - if (!in) + if (*pStr == '-') { - return 0.f; + countNeg++; } - return (float)atof(in); + pStr++; } - bool LooksLikeFloat(const char* in, float* pFloat /* = nullptr */) + if (*pStr == '\0' && + countNeg < 2) { - bool result = false; - - if (pFloat) + if (pInt) { - double doubleValue = 0.0; - result = LooksLikeDouble(in, &doubleValue); - - (*pFloat) = aznumeric_cast(doubleValue); - } - else - { - result = LooksLikeDouble(in); + *pInt = ToInt(in); } - return result; + return true; } + return false; + } - bool ToBool(const char* in) + double ToDouble(const char* in) + { + if (!in) + { + return 0.; + } + return atof(in); + } + + bool LooksLikeDouble(const char* in, double* pDouble) + { + if (!in) { - bool boolValue = false; - if (LooksLikeBool(in, &boolValue)) - { - return boolValue; - } return false; } - bool LooksLikeBool(const char* in, bool* pBool /* = nullptr */) + size_t len = strlen(in); + if (len < 2)//must have at least 2 characters to work with "1." { - if (!in) - { - return false; - } - - if (!azstricmp(in, "true") || !azstricmp(in, "1")) - { - if (pBool) - { - *pBool = true; - } - return true; - } - - if (!azstricmp(in, "false") || !azstricmp(in, "0")) - { - if (pBool) - { - *pBool = false; - } - return true; - } - return false; } - template - bool LooksLikeVectorHelper(const char* in, VECTOR_TYPE* outVector) - { - AZStd::vector tokens; - Tokenize(in, tokens, ',', false, true); - if (tokens.size() == ELEMENT_COUNT) - { - float vectorValues[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + const char* pStr = in; + size_t countDot = 0; + size_t countNeg = 0; + while (*pStr != '\0' && + (isdigit(*pStr) || + (*pStr == '-' || + *pStr == '.'))) + { + if (*pStr == '.') + { + countDot++; + } + if (*pStr == '-') + { + countNeg++; + } + pStr++; + } + + if (*pStr == '\0' && + countDot == 1 && + countNeg < 2) + { + if (pDouble) + { + *pDouble = ToDouble(in); + } + + return true; + } + + return false; + } + + float ToFloat(const char* in) + { + if (!in) + { + return 0.f; + } + return (float)atof(in); + } + + bool LooksLikeFloat(const char* in, float* pFloat /* = nullptr */) + { + bool result = false; + + if (pFloat) + { + double doubleValue = 0.0; + result = LooksLikeDouble(in, &doubleValue); + + (*pFloat) = aznumeric_cast(doubleValue); + } + else + { + result = LooksLikeDouble(in); + } + + return result; + } + + bool ToBool(const char* in) + { + bool boolValue = false; + if (LooksLikeBool(in, &boolValue)) + { + return boolValue; + } + return false; + } + + bool LooksLikeBool(const char* in, bool* pBool /* = nullptr */) + { + if (!in) + { + return false; + } + + if (!azstricmp(in, "true") || !azstricmp(in, "1")) + { + if (pBool) + { + *pBool = true; + } + return true; + } + + if (!azstricmp(in, "false") || !azstricmp(in, "0")) + { + if (pBool) + { + *pBool = false; + } + return true; + } + + return false; + } + + template + bool LooksLikeVectorHelper(const char* in, VECTOR_TYPE* outVector) + { + AZStd::vector tokens; + Tokenize(in, tokens, ',', false, true); + if (tokens.size() == ELEMENT_COUNT) + { + float vectorValues[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + for (uint32_t element = 0; element < ELEMENT_COUNT; ++element) + { + if (!LooksLikeFloat(tokens[element].c_str(), outVector ? &vectorValues[element] : nullptr)) + { + return false; + } + } + + if (outVector) + { for (uint32_t element = 0; element < ELEMENT_COUNT; ++element) { - if (!LooksLikeFloat(tokens[element].c_str(), outVector ? &vectorValues[element] : nullptr)) - { - return false; - } + outVector->SetElement(element, vectorValues[element]); } - - if (outVector) - { - for (uint32_t element = 0; element < ELEMENT_COUNT; ++element) - { - outVector->SetElement(element, vectorValues[element]); - } - } - - return true; } - return false; - } - - bool LooksLikeVector2(const char* in, AZ::Vector2* outVector) - { - return LooksLikeVectorHelper(in, outVector); - } - - AZ::Vector2 ToVector2(const char* in) - { - AZ::Vector2 vector; - LooksLikeVector2(in, &vector); - return vector; - } - - bool LooksLikeVector3(const char* in, AZ::Vector3* outVector) - { - return LooksLikeVectorHelper(in, outVector); - } - - AZ::Vector3 ToVector3(const char* in) - { - AZ::Vector3 vector; - LooksLikeVector3(in, &vector); - return vector; - } - - bool LooksLikeVector4(const char* in, AZ::Vector4* outVector) - { - return LooksLikeVectorHelper(in, outVector); - } - - AZ::Vector4 ToVector4(const char* in) - { - AZ::Vector4 vector; - LooksLikeVector4(in, &vector); - return vector; - } - - bool ToHexDump(const char* in, AZStd::string& out) - { - struct TInline - { - static void ByteToHex(char* pszHex, unsigned char bValue) - { - pszHex[0] = bValue / 16; - - if (pszHex[0] < 10) - { - pszHex[0] += '0'; - } - else - { - pszHex[0] -= 10; - pszHex[0] += 'A'; - } - - pszHex[1] = bValue % 16; - - if (pszHex[1] < 10) - { - pszHex[1] += '0'; - } - else - { - pszHex[1] -= 10; - pszHex[1] += 'A'; - } - } - }; - - size_t len = strlen(in); - if (len < 1) //must be at least 1 character to work with - { - return false; - } - - size_t nBytes = len; - - char* pszData = reinterpret_cast(azmalloc((nBytes * 2) + 1)); - - for (size_t ii = 0; ii < nBytes; ++ii) - { - TInline::ByteToHex(&pszData[ii * 2], in[ii]); - } - - pszData[nBytes * 2] = 0x00; - out = pszData; - azfree(pszData); - return true; } - bool FromHexDump(const char* in, AZStd::string& out) + return false; + } + + bool LooksLikeVector2(const char* in, AZ::Vector2* outVector) + { + return LooksLikeVectorHelper(in, outVector); + } + + AZ::Vector2 ToVector2(const char* in) + { + AZ::Vector2 vector; + LooksLikeVector2(in, &vector); + return vector; + } + + bool LooksLikeVector3(const char* in, AZ::Vector3* outVector) + { + return LooksLikeVectorHelper(in, outVector); + } + + AZ::Vector3 ToVector3(const char* in) + { + AZ::Vector3 vector; + LooksLikeVector3(in, &vector); + return vector; + } + + bool LooksLikeVector4(const char* in, AZ::Vector4* outVector) + { + return LooksLikeVectorHelper(in, outVector); + } + + AZ::Vector4 ToVector4(const char* in) + { + AZ::Vector4 vector; + LooksLikeVector4(in, &vector); + return vector; + } + + bool ToHexDump(const char* in, AZStd::string& out) + { + struct TInline { - struct TInline + static void ByteToHex(char* pszHex, unsigned char bValue) { - static unsigned char HexToByte(const char* pszHex) + pszHex[0] = bValue / 16; + + if (pszHex[0] < 10) { - unsigned char bHigh = 0; - unsigned char bLow = 0; - - if ((pszHex[0] >= '0') && (pszHex[0] <= '9')) - { - bHigh = pszHex[0] - '0'; - } - else if ((pszHex[0] >= 'A') && (pszHex[0] <= 'F')) - { - bHigh = (pszHex[0] - 'A') + 10; - } - - bHigh = bHigh << 4; - - if ((pszHex[1] >= '0') && (pszHex[1] <= '9')) - { - bLow = pszHex[1] - '0'; - } - else if ((pszHex[1] >= 'A') && (pszHex[1] <= 'F')) - { - bLow = (pszHex[1] - 'A') + 10; - } - - return bHigh | bLow; - } - }; - - size_t len = strlen(in); - if (len < 2) //must be at least 2 characters to work with - { - return false; - } - - size_t nBytes = len / 2; - char* pszData = reinterpret_cast(azmalloc(nBytes + 1)); - - for (size_t ii = 0; ii < nBytes; ++ii) - { - pszData[ii] = TInline::HexToByte(&in[ii * 2]); - } - - pszData[nBytes] = 0x00; - out = pszData; - azfree(pszData); - - return true; - } - - namespace NumberFormatting - { - int GroupDigits(char* buffer, size_t bufferSize, size_t decimalPosHint, char digitSeparator, char decimalSeparator, int groupingSize, int firstGroupingSize) - { - static const int MAX_SEPARATORS = 16; - - AZ_Assert(buffer, "Null string buffer"); - AZ_Assert(bufferSize > decimalPosHint, "Decimal position %lu cannot be located beyond bufferSize %lu", decimalPosHint, bufferSize); - AZ_Assert(groupingSize > 0, "Grouping size must be a positive integer"); - - int numberEndPos = 0; - int stringEndPos = 0; - - if (decimalPosHint > 0 && decimalPosHint < (bufferSize - 1) && buffer[decimalPosHint] == decimalSeparator) - { - // Assume the number ends at the supplied location - numberEndPos = (int)decimalPosHint; - stringEndPos = numberEndPos + (int)strnlen(buffer + numberEndPos, bufferSize - numberEndPos); + pszHex[0] += '0'; } else { - // Search for the final digit or separator while obtaining the string length - int lastDigitSeenPos = 0; - - while (stringEndPos < bufferSize) - { - char c = buffer[stringEndPos]; - - if (!c) - { - break; - } - else if (c == decimalSeparator) - { - // End the number if there's a decimal - numberEndPos = stringEndPos; - } - else if (numberEndPos <= 0 && c >= '0' && c <= '9') - { - // Otherwise keep track of where the last digit we've seen is - lastDigitSeenPos = stringEndPos; - } - - stringEndPos++; - } - - if (numberEndPos <= 0) - { - if (lastDigitSeenPos > 0) - { - // No decimal, so use the last seen digit as the end of the number - numberEndPos = lastDigitSeenPos + 1; - } - else - { - // No digits, no decimals, therefore no change in the string - return stringEndPos; - } - } + pszHex[0] -= 10; + pszHex[0] += 'A'; } - if (firstGroupingSize <= 0) + pszHex[1] = bValue % 16; + + if (pszHex[1] < 10) { - firstGroupingSize = groupingSize; + pszHex[1] += '0'; + } + else + { + pszHex[1] -= 10; + pszHex[1] += 'A'; + } + } + }; + + size_t len = strlen(in); + if (len < 1) //must be at least 1 character to work with + { + return false; + } + + size_t nBytes = len; + + char* pszData = reinterpret_cast(azmalloc((nBytes * 2) + 1)); + + for (size_t ii = 0; ii < nBytes; ++ii) + { + TInline::ByteToHex(&pszData[ii * 2], in[ii]); + } + + pszData[nBytes * 2] = 0x00; + out = pszData; + azfree(pszData); + + return true; + } + + bool FromHexDump(const char* in, AZStd::string& out) + { + struct TInline + { + static unsigned char HexToByte(const char* pszHex) + { + unsigned char bHigh = 0; + unsigned char bLow = 0; + + if ((pszHex[0] >= '0') && (pszHex[0] <= '9')) + { + bHigh = pszHex[0] - '0'; + } + else if ((pszHex[0] >= 'A') && (pszHex[0] <= 'F')) + { + bHigh = (pszHex[0] - 'A') + 10; } - // Determine where to place the separators - int groupingSizes[] = { firstGroupingSize + 1, groupingSize }; // First group gets +1 since we begin all subsequent groups at the second digit - int groupingOffsetsToNext[] = { 1, 0 }; // We will offset from the first entry to the second, then stay at the second for remaining iterations - const int* currentGroupingSize = groupingSizes; - const int* currentGroupingOffsetToNext = groupingOffsetsToNext; - AZStd::fixed_vector separatorLocations; - int groupCounter = 0; - int digitPosition = numberEndPos - 1; + bHigh = bHigh << 4; - while (digitPosition >= 0) + if ((pszHex[1] >= '0') && (pszHex[1] <= '9')) { - // Walk backwards in the string from the least significant digit to the most significant, demarcating consecutive groups of digits - char c = buffer[digitPosition]; + bLow = pszHex[1] - '0'; + } + else if ((pszHex[1] >= 'A') && (pszHex[1] <= 'F')) + { + bLow = (pszHex[1] - 'A') + 10; + } - if (c >= '0' && c <= '9') - { - if (++groupCounter == *currentGroupingSize) - { - // Demarcate a new group of digits at this location - separatorLocations.push_back(buffer + digitPosition); - currentGroupingSize += *currentGroupingOffsetToNext; - currentGroupingOffsetToNext += *currentGroupingOffsetToNext; - groupCounter = 0; - } + return bHigh | bLow; + } + }; - digitPosition--; - } - else + size_t len = strlen(in); + if (len < 2) //must be at least 2 characters to work with + { + return false; + } + + size_t nBytes = len / 2; + char* pszData = reinterpret_cast(azmalloc(nBytes + 1)); + + for (size_t ii = 0; ii < nBytes; ++ii) + { + pszData[ii] = TInline::HexToByte(&in[ii * 2]); + } + + pszData[nBytes] = 0x00; + out = pszData; + azfree(pszData); + + return true; + } + + namespace NumberFormatting + { + int GroupDigits(char* buffer, size_t bufferSize, size_t decimalPosHint, char digitSeparator, char decimalSeparator, int groupingSize, int firstGroupingSize) + { + static const int MAX_SEPARATORS = 16; + + AZ_Assert(buffer, "Null string buffer"); + AZ_Assert(bufferSize > decimalPosHint, "Decimal position %lu cannot be located beyond bufferSize %lu", decimalPosHint, bufferSize); + AZ_Assert(groupingSize > 0, "Grouping size must be a positive integer"); + + int numberEndPos = 0; + int stringEndPos = 0; + + if (decimalPosHint > 0 && decimalPosHint < (bufferSize - 1) && buffer[decimalPosHint] == decimalSeparator) + { + // Assume the number ends at the supplied location + numberEndPos = (int)decimalPosHint; + stringEndPos = numberEndPos + (int)strnlen(buffer + numberEndPos, bufferSize - numberEndPos); + } + else + { + // Search for the final digit or separator while obtaining the string length + int lastDigitSeenPos = 0; + + while (stringEndPos < bufferSize) + { + char c = buffer[stringEndPos]; + + if (!c) { break; } - } - - if (stringEndPos + separatorLocations.size() >= bufferSize) - { - // Won't fit into buffer, so return unchanged - return stringEndPos; - } - - // Insert the separators by shifting characters forward in the string, starting at the end and working backwards - const char* src = buffer + stringEndPos; - char* dest = buffer + stringEndPos + separatorLocations.size(); - auto separatorItr = separatorLocations.begin(); - - while (separatorItr != separatorLocations.end()) - { - while (src > *separatorItr) + else if (c == decimalSeparator) { - *dest-- = *src--; + // End the number if there's a decimal + numberEndPos = stringEndPos; + } + else if (numberEndPos <= 0 && c >= '0' && c <= '9') + { + // Otherwise keep track of where the last digit we've seen is + lastDigitSeenPos = stringEndPos; } - // Insert the separator and reduce the distance between our destination and source by one - *dest-- = digitSeparator; - ++separatorItr; + stringEndPos++; } - return (int)(stringEndPos + separatorLocations.size()); - } - } - - namespace AssetPath - { - namespace Internal - { - AZ::u32 CalculateBranchTokenHash(AZStd::string_view engineRootPath) + if (numberEndPos <= 0) { - // Normalize the token to prepare for CRC32 calculation - auto NormalizeEnginePath = [](const char element) -> char + if (lastDigitSeenPos > 0) { - // Substitute path separators with '_' and lower case - return element == AZ::IO::WindowsPathSeparator || element == AZ::IO::PosixPathSeparator - ? '_' : static_cast(std::tolower(element)); - }; - - // Trim off trailing path separators - engineRootPath = RStrip(engineRootPath, AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR); - AZ::IO::FixedMaxPathString enginePath; - AZStd::transform(engineRootPath.begin(), engineRootPath.end(), - AZStd::back_inserter(enginePath), AZStd::move(NormalizeEnginePath)); - - // Perform the CRC32 calculation - constexpr bool forceLowercase = true; - return static_cast(AZ::Crc32(enginePath.c_str(), enginePath.size(), forceLowercase)); + // No decimal, so use the last seen digit as the end of the number + numberEndPos = lastDigitSeenPos + 1; + } + else + { + // No digits, no decimals, therefore no change in the string + return stringEndPos; + } } } - void CalculateBranchToken(AZStd::string_view engineRootPath, AZStd::string& token) + + if (firstGroupingSize <= 0) { - token = AZStd::string::format("0x%08X", Internal::CalculateBranchTokenHash(engineRootPath)); + firstGroupingSize = groupingSize; } - void CalculateBranchToken(AZStd::string_view engineRootPath, AZ::IO::FixedMaxPathString& token) + + // Determine where to place the separators + int groupingSizes[] = { firstGroupingSize + 1, groupingSize }; // First group gets +1 since we begin all subsequent groups at the second digit + int groupingOffsetsToNext[] = { 1, 0 }; // We will offset from the first entry to the second, then stay at the second for remaining iterations + const int* currentGroupingSize = groupingSizes; + const int* currentGroupingOffsetToNext = groupingOffsetsToNext; + AZStd::fixed_vector separatorLocations; + int groupCounter = 0; + int digitPosition = numberEndPos - 1; + + while (digitPosition >= 0) { - token = AZ::IO::FixedMaxPathString::format("0x%08X", Internal::CalculateBranchTokenHash(engineRootPath)); + // Walk backwards in the string from the least significant digit to the most significant, demarcating consecutive groups of digits + char c = buffer[digitPosition]; + + if (c >= '0' && c <= '9') + { + if (++groupCounter == *currentGroupingSize) + { + // Demarcate a new group of digits at this location + separatorLocations.push_back(buffer + digitPosition); + currentGroupingSize += *currentGroupingOffsetToNext; + currentGroupingOffsetToNext += *currentGroupingOffsetToNext; + groupCounter = 0; + } + + digitPosition--; + } + else + { + break; + } } + + if (stringEndPos + separatorLocations.size() >= bufferSize) + { + // Won't fit into buffer, so return unchanged + return stringEndPos; + } + + // Insert the separators by shifting characters forward in the string, starting at the end and working backwards + const char* src = buffer + stringEndPos; + char* dest = buffer + stringEndPos + separatorLocations.size(); + auto separatorItr = separatorLocations.begin(); + + while (separatorItr != separatorLocations.end()) + { + while (src > *separatorItr) + { + *dest-- = *src--; + } + + // Insert the separator and reduce the distance between our destination and source by one + *dest-- = digitSeparator; + ++separatorItr; + } + + return (int)(stringEndPos + separatorLocations.size()); + } + } + + namespace AssetPath + { + namespace Internal + { + AZ::u32 CalculateBranchTokenHash(AZStd::string_view engineRootPath) + { + // Normalize the token to prepare for CRC32 calculation + auto NormalizeEnginePath = [](const char element) -> char + { + // Substitute path separators with '_' and lower case + return element == AZ::IO::WindowsPathSeparator || element == AZ::IO::PosixPathSeparator + ? '_' + : static_cast(std::tolower(element)); + }; + + // Trim off trailing path separators + engineRootPath = RStrip(engineRootPath, AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR); + AZ::IO::FixedMaxPathString enginePath; + AZStd::transform( + engineRootPath.begin(), engineRootPath.end(), AZStd::back_inserter(enginePath), AZStd::move(NormalizeEnginePath)); + + // Perform the CRC32 calculation + constexpr bool forceLowercase = true; + return static_cast(AZ::Crc32(enginePath.c_str(), enginePath.size(), forceLowercase)); + } + } // namespace Internal + void CalculateBranchToken(AZStd::string_view engineRootPath, AZStd::string& token) + { + token = AZStd::string::format("0x%08X", Internal::CalculateBranchTokenHash(engineRootPath)); + } + void CalculateBranchToken(AZStd::string_view engineRootPath, AZ::IO::FixedMaxPathString& token) + { + token = AZ::IO::FixedMaxPathString::format("0x%08X", Internal::CalculateBranchTokenHash(engineRootPath)); + } + } // namespace AssetPath + + namespace AssetDatabasePath + { + bool Normalize(AZStd::string& inout) + { + // Asset Paths uses the forward slash for the database separator + AZ::IO::Path path(AZStd::move(inout), AZ_CORRECT_DATABASE_SEPARATOR); + bool appendTrailingSlash = (path.Native().ends_with(AZ_CORRECT_DATABASE_SEPARATOR) || path.Native().ends_with(AZ_WRONG_DATABASE_SEPARATOR)) + && path.HasRelativePath(); + inout = AZStd::move(path.LexicallyNormal().Native()); + if (appendTrailingSlash) + { + inout.push_back(AZ_CORRECT_DATABASE_SEPARATOR); + } + return IsValid(inout.c_str()); } - namespace AssetDatabasePath + bool IsValid(const char* in) { - bool Normalize(AZStd::string& inout) + if (!in) { - // Asset Paths uses the forward slash for the database separator - AZ::IO::Path path(AZStd::move(inout), AZ_CORRECT_DATABASE_SEPARATOR); - bool appendTrailingSlash = (path.Native().ends_with(AZ_CORRECT_DATABASE_SEPARATOR) || path.Native().ends_with(AZ_WRONG_DATABASE_SEPARATOR)) - && path.HasRelativePath(); - inout = AZStd::move(path.LexicallyNormal().Native()); - if (appendTrailingSlash) - { - inout.push_back(AZ_CORRECT_DATABASE_SEPARATOR); - } - return IsValid(inout.c_str()); + return false; } - bool IsValid(const char* in) + if (!strlen(in)) { - if (!in) - { - return false; - } + return false; + } - if (!strlen(in)) - { - return false; - } + if (Find(in, AZ_DATABASE_INVALID_CHARACTERS) != AZStd::string::npos) + { + return false; + } - if (Find(in, AZ_DATABASE_INVALID_CHARACTERS) != AZStd::string::npos) - { - return false; - } - - if (Find(in, AZ_WRONG_DATABASE_SEPARATOR) != AZStd::string::npos) - { - return false; - } + if (Find(in, AZ_WRONG_DATABASE_SEPARATOR) != AZStd::string::npos) + { + return false; + } #ifndef AZ_FILENAME_ALLOW_SPACES - if (Find(in, AZ_SPACE_CHARACTERS) != AZStd::string::npos) - { - return false; - } + if (Find(in, AZ_SPACE_CHARACTERS) != AZStd::string::npos) + { + return false; + } #endif // AZ_FILENAME_ALLOW_SPACES - if (LastCharacter(in) == AZ_CORRECT_DATABASE_SEPARATOR) - { - return false; - } - - return true; - } - - bool Split(const char* in, [[maybe_unused]] AZStd::string* pDstProjectRootOut, AZStd::string* pDstDatabaseRootOut, - AZStd::string* pDstDatabasePathOut , AZStd::string* pDstFileOut, AZStd::string* pDstFileExtensionOut) + if (LastCharacter(in) == AZ_CORRECT_DATABASE_SEPARATOR) { - AZStd::string_view path{ in }; - if (path.empty()) - { - return false; - } - - AZ::IO::PathView pathView(path, AZ_CORRECT_DATABASE_SEPARATOR); - if (pDstDatabaseRootOut) - { - AZStd::string_view rootNameView = pathView.RootName().Native(); - if (rootNameView.size() > pDstDatabaseRootOut->max_size()) - { - return false; - } - *pDstDatabaseRootOut = rootNameView; - } - if (pDstDatabasePathOut) - { - AZStd::string_view rootPathView = pathView.RootPath().Native(); - AZStd::string_view relPathParentView = pathView.ParentPath().RelativePath().Native(); - if (rootPathView.size() + relPathParentView.size() > pDstDatabasePathOut->max_size()) - { - return false; - } - // Append the root directory if there is one - *pDstDatabasePathOut = rootPathView; - // Append the relative path portion of the split path excluding the filename - *pDstDatabasePathOut += relPathParentView; - } - if (pDstFileOut) - { - AZStd::string_view stemView = pathView.Stem().Native(); - if (stemView.size() > pDstFileOut->max_size()) - { - return false; - } - *pDstFileOut = stemView; - } - if (pDstFileExtensionOut) - { - AZStd::string_view extensionView = pathView.Extension().Native(); - if (extensionView.size() > pDstFileExtensionOut->max_size()) - { - return false; - } - *pDstFileExtensionOut = extensionView; - } - - return true; + return false; } - bool Join(const char* pFirstPart, const char* pSecondPart, AZStd::string& out, [[maybe_unused]] bool bCaseInsensitive /*= true*/, bool bNormalize /*= true*/) - { - // both paths cannot be empty - if (!pFirstPart || !pSecondPart) - { - return false; - } + return true; + } - AZ::IO::PathView secondPath(pSecondPart); - AZ_Warning("StringFunc", secondPath.IsRelative(), "The second join parameter %s is an absolute path," - " this will replace the first part of the path resulting in an output of just the second part", - pSecondPart); - - AZ::IO::Path resultPath(pFirstPart, AZ_CORRECT_DATABASE_SEPARATOR); - resultPath /= secondPath; - out = bNormalize ? AZStd::move(resultPath.LexicallyNormal().Native()) : AZStd::move(resultPath.Native()); - return true; - } - } //namespace AssetDatabasePath - - namespace Root + bool Split(const char* in, [[maybe_unused]] AZStd::string* pDstProjectRootOut, AZStd::string* pDstDatabaseRootOut, + AZStd::string* pDstDatabasePathOut , AZStd::string* pDstFileOut, AZStd::string* pDstFileExtensionOut) { - bool Normalize(AZStd::string& inout) + AZStd::string_view path{ in }; + if (path.empty()) { - AZ::IO::Path path(AZStd::move(inout)); - path = path.LexicallyNormal(); - // After normalization check if the path contains a relative path - bool appendTrailingSlash = path.HasRelativePath(); - inout = AZStd::move(path.Native()); - if (appendTrailingSlash) - { - inout.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR); // Append a trailing separator for Root path normalization - } - return IsValid(inout.c_str()); + return false; } - bool IsValid(const char* in) + AZ::IO::PathView pathView(path, AZ_CORRECT_DATABASE_SEPARATOR); + if (pDstDatabaseRootOut) { - if (!in) + AZStd::string_view rootNameView = pathView.RootName().Native(); + if (rootNameView.size() > pDstDatabaseRootOut->max_size()) { return false; } - - if (!strlen(in)) + *pDstDatabaseRootOut = rootNameView; + } + if (pDstDatabasePathOut) + { + AZStd::string_view rootPathView = pathView.RootPath().Native(); + AZStd::string_view relPathParentView = pathView.ParentPath().RelativePath().Native(); + if (rootPathView.size() + relPathParentView.size() > pDstDatabasePathOut->max_size()) { return false; } - - if (Find(in, AZ_FILESYSTEM_INVALID_CHARACTERS) != AZStd::string::npos) + // Append the root directory if there is one + *pDstDatabasePathOut = rootPathView; + // Append the relative path portion of the split path excluding the filename + *pDstDatabasePathOut += relPathParentView; + } + if (pDstFileOut) + { + AZStd::string_view stemView = pathView.Stem().Native(); + if (stemView.size() > pDstFileOut->max_size()) { return false; } - - if (Find(in, AZ_WRONG_FILESYSTEM_SEPARATOR) != AZStd::string::npos) + *pDstFileOut = stemView; + } + if (pDstFileExtensionOut) + { + AZStd::string_view extensionView = pathView.Extension().Native(); + if (extensionView.size() > pDstFileExtensionOut->max_size()) { return false; } + *pDstFileExtensionOut = extensionView; + } - #ifndef AZ_FILENAME_ALLOW_SPACES - if (Find(in, AZ_SPACE_CHARACTERS) != AZStd::string::npos) - { - return false; - } - #endif // AZ_FILENAME_ALLOW_SPACES + return true; + } - AZ::IO::PathView pathView(in); - if (!pathView.HasRootPath()) - { - return false; - } + bool Join(const char* pFirstPart, const char* pSecondPart, AZStd::string& out, [[maybe_unused]] bool bCaseInsensitive /*= true*/, bool bNormalize /*= true*/) + { + // both paths cannot be empty + if (!pFirstPart || !pSecondPart) + { + return false; + } - if (LastCharacter(in) != AZ_CORRECT_FILESYSTEM_SEPARATOR) - { - return false; - } + AZ::IO::PathView secondPath(pSecondPart); + AZ_Warning("StringFunc", secondPath.IsRelative(), "The second join parameter %s is an absolute path," + " this will replace the first part of the path resulting in an output of just the second part", + pSecondPart); + AZ::IO::Path resultPath(pFirstPart, AZ_CORRECT_DATABASE_SEPARATOR); + resultPath /= secondPath; + out = bNormalize ? AZStd::move(resultPath.LexicallyNormal().Native()) : AZStd::move(resultPath.Native()); + return true; + } + } //namespace AssetDatabasePath + + namespace Root + { + bool Normalize(AZStd::string& inout) + { + AZ::IO::Path path(AZStd::move(inout)); + path = path.LexicallyNormal(); + // After normalization check if the path contains a relative path + bool appendTrailingSlash = path.HasRelativePath(); + inout = AZStd::move(path.Native()); + if (appendTrailingSlash) + { + inout.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR); // Append a trailing separator for Root path normalization + } + return IsValid(inout.c_str()); + } + + bool IsValid(const char* in) + { + if (!in) + { + return false; + } + + if (!strlen(in)) + { + return false; + } + + if (Find(in, AZ_FILESYSTEM_INVALID_CHARACTERS) != AZStd::string::npos) + { + return false; + } + + if (Find(in, AZ_WRONG_FILESYSTEM_SEPARATOR) != AZStd::string::npos) + { + return false; + } + +#ifndef AZ_FILENAME_ALLOW_SPACES + if (Find(in, AZ_SPACE_CHARACTERS) != AZStd::string::npos) + { + return false; + } +#endif // AZ_FILENAME_ALLOW_SPACES + + AZ::IO::PathView pathView(in); + if (!pathView.HasRootPath()) + { + return false; + } + + if (LastCharacter(in) != AZ_CORRECT_FILESYSTEM_SEPARATOR) + { + return false; + } + + return true; + } + }//namespace Root + + namespace RelativePath + { + bool Normalize(AZStd::string& inout) + { + AZ::IO::Path path(AZStd::move(inout)); + path = path.LexicallyNormal(); + // After normalization check if the path contains a relative path + bool appendTrailingSlash = path.HasRelativePath(); + inout = AZStd::move(path.Native()); + if (appendTrailingSlash) + { + inout.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR); // Append trailing separator for Relative path normalization if it it is not empty + } + return IsValid(inout.c_str()); + } + + bool IsValid(const char* in) + { + if (!in) + { + return false; + } + + if (!strlen(in)) + { return true; } - }//namespace Root - namespace RelativePath + if (Find(in, AZ_FILESYSTEM_INVALID_CHARACTERS) != AZStd::string::npos) + { + return false; + } + + if (Find(in, AZ_WRONG_FILESYSTEM_SEPARATOR) != AZStd::string::npos) + { + return false; + } + +#ifndef AZ_FILENAME_ALLOW_SPACES + if (Find(in, AZ_SPACE_CHARACTERS) != AZStd::string::npos) + { + return false; + } +#endif // AZ_FILENAME_ALLOW_SPACES + + if (Path::HasDrive(in)) + { + return false; + } + + if (FirstCharacter(in) == AZ_CORRECT_FILESYSTEM_SEPARATOR) + { + return false; + } + + if (LastCharacter(in) != AZ_CORRECT_FILESYSTEM_SEPARATOR) + { + return false; + } + + return true; + } + }//namespace RelativePath + + namespace Path + { + bool Normalize(AZStd::string& inout) { - bool Normalize(AZStd::string& inout) + AZ::IO::Path path(AZStd::move(inout)); + bool appendTrailingSlash = (path.Native().ends_with(AZ_CORRECT_FILESYSTEM_SEPARATOR) || path.Native().ends_with(AZ_WRONG_FILESYSTEM_SEPARATOR)); + path = path.LexicallyNormal(); + // After normalization check if the path contains a relative path and addition to ending with a path separator before + appendTrailingSlash = appendTrailingSlash && path.HasRelativePath(); + inout = AZStd::move(path.Native()); + if (appendTrailingSlash) { - AZ::IO::Path path(AZStd::move(inout)); - path = path.LexicallyNormal(); - // After normalization check if the path contains a relative path - bool appendTrailingSlash = path.HasRelativePath(); - inout = AZStd::move(path.Native()); - if (appendTrailingSlash) - { - inout.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR); // Append trailing separator for Relative path normalization if it it is not empty - } - return IsValid(inout.c_str()); + inout.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR); } + return IsValid(inout.c_str()); + } - bool IsValid(const char* in) - { - if (!in) - { - return false; - } - - if (!strlen(in)) - { - return true; - } - - if (Find(in, AZ_FILESYSTEM_INVALID_CHARACTERS) != AZStd::string::npos) - { - return false; - } - - if (Find(in, AZ_WRONG_FILESYSTEM_SEPARATOR) != AZStd::string::npos) - { - return false; - } - - #ifndef AZ_FILENAME_ALLOW_SPACES - if (Find(in, AZ_SPACE_CHARACTERS) != AZStd::string::npos) - { - return false; - } - #endif // AZ_FILENAME_ALLOW_SPACES - - if (Path::HasDrive(in)) - { - return false; - } - - if (FirstCharacter(in) == AZ_CORRECT_FILESYSTEM_SEPARATOR) - { - return false; - } - - if (LastCharacter(in) != AZ_CORRECT_FILESYSTEM_SEPARATOR) - { - return false; - } - - return true; - } - }//namespace RelativePath - - namespace Path + bool Normalize(FixedString& inout) { - bool Normalize(AZStd::string& inout) + AZ::IO::FixedMaxPath path(AZStd::move(inout)); + bool appendTrailingSlash = (path.Native().ends_with(AZ_CORRECT_FILESYSTEM_SEPARATOR) || path.Native().ends_with(AZ_WRONG_FILESYSTEM_SEPARATOR)); + path = path.LexicallyNormal(); + // After normalization check if the path contains a relative path and addition to ending with a path separator before + appendTrailingSlash = appendTrailingSlash && path.HasRelativePath(); + inout = AZStd::move(path.Native()); + if (appendTrailingSlash) { - AZ::IO::Path path(AZStd::move(inout)); - bool appendTrailingSlash = (path.Native().ends_with(AZ_CORRECT_FILESYSTEM_SEPARATOR) || path.Native().ends_with(AZ_WRONG_FILESYSTEM_SEPARATOR)); - path = path.LexicallyNormal(); - // After normalization check if the path contains a relative path and addition to ending with a path separator before - appendTrailingSlash = appendTrailingSlash && path.HasRelativePath(); - inout = AZStd::move(path.Native()); - if (appendTrailingSlash) - { - inout.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR); - } - return IsValid(inout.c_str()); + inout.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR); + } + return IsValid(inout.c_str()); + } + + bool IsValid(const char* in, bool bHasDrive /*= false*/, bool bHasExtension /*= false*/, AZStd::string* errors /*= nullptr*/) + { + //if they gave us a error reporting string empty it. + if (errors) + { + errors->clear(); } - bool Normalize(FixedString& inout) + //empty is not a valid path + if (!in) { - AZ::IO::FixedMaxPath path(AZStd::move(inout)); - bool appendTrailingSlash = (path.Native().ends_with(AZ_CORRECT_FILESYSTEM_SEPARATOR) || path.Native().ends_with(AZ_WRONG_FILESYSTEM_SEPARATOR)); - path = path.LexicallyNormal(); - // After normalization check if the path contains a relative path and addition to ending with a path separator before - appendTrailingSlash = appendTrailingSlash && path.HasRelativePath(); - inout = AZStd::move(path.Native()); - if (appendTrailingSlash) - { - inout.push_back(AZ_CORRECT_FILESYSTEM_SEPARATOR); - } - return IsValid(inout.c_str()); - } - - bool IsValid(const char* in, bool bHasDrive /*= false*/, bool bHasExtension /*= false*/, AZStd::string* errors /*= nullptr*/) - { - //if they gave us a error reporting string empty it. if (errors) { - errors->clear(); + *errors += "The path is Empty."; } + return false; + } - //empty is not a valid path - if (!in) + //empty is not a valid path + size_t length = strlen(in); + if (!length) + { + if (errors) { - if (errors) - { - *errors += "The path is Empty."; - } - return false; + *errors += "The path is Empty."; } + return false; + } - //empty is not a valid path - size_t length = strlen(in); - if (!length) + //invalid characters + const char* inEnd = in + length; + const char* invalidCharactersBegin = AZ_FILESYSTEM_INVALID_CHARACTERS; + const char* invalidCharactersEnd = invalidCharactersBegin + AZ_ARRAY_SIZE(AZ_FILESYSTEM_INVALID_CHARACTERS); + if (AZStd::find_first_of(in, inEnd, invalidCharactersBegin, invalidCharactersEnd) != inEnd) + { + if (errors) { - if (errors) - { - *errors += "The path is Empty."; - } - return false; + *errors += "The path has invalid characters."; } + return false; + } - //invalid characters - const char* inEnd = in + length; - const char* invalidCharactersBegin = AZ_FILESYSTEM_INVALID_CHARACTERS; - const char* invalidCharactersEnd = invalidCharactersBegin + AZ_ARRAY_SIZE(AZ_FILESYSTEM_INVALID_CHARACTERS); - if (AZStd::find_first_of(in, inEnd, invalidCharactersBegin, invalidCharactersEnd) != inEnd) + //invalid characters + if (Find(in, AZ_WRONG_FILESYSTEM_SEPARATOR) != AZStd::string::npos) + { + if (errors) { - if (errors) - { - *errors += "The path has invalid characters."; - } - return false; + *errors += "The path has wrong separator."; } + return false; + } - //invalid characters - if (Find(in, AZ_WRONG_FILESYSTEM_SEPARATOR) != AZStd::string::npos) +#ifndef AZ_FILENAME_ALLOW_SPACES + const char* spaceCharactersBegin = AZ_SPACE_CHARACTERS; + const char* spaceCharactersEnd = spaceCharactersBegin + AZ_ARRAY_SIZE(AZ_SPACE_CHARACTERS); + if (AZStd::find_first_of(in, inEnd, spaceCharactersBegin, spaceCharactersEnd) != inEnd) + { + if (errors) { - if (errors) - { - *errors += "The path has wrong separator."; - } - return false; + *errors += "The path has space characters."; } + return false; + } +#endif // AZ_FILENAME_ALLOW_SPACES - #ifndef AZ_FILENAME_ALLOW_SPACES - const char* spaceCharactersBegin = AZ_SPACE_CHARACTERS; - const char* spaceCharactersEnd = spaceCharactersBegin + AZ_ARRAY_SIZE(AZ_SPACE_CHARACTERS); - if (AZStd::find_first_of(in, inEnd, spaceCharactersBegin, spaceCharactersEnd) != inEnd) + //does it have a drive if specified + if (bHasDrive && !HasDrive(in)) + { + if (errors) { - if (errors) - { - *errors += "The path has space characters."; - } - return false; + *errors += "The path should have a drive. The path ["; + *errors += in; + *errors += "] is invalid."; } - #endif // AZ_FILENAME_ALLOW_SPACES + return false; + } - //does it have a drive if specified - if (bHasDrive && !HasDrive(in)) + //does it have and extension if specified + if (bHasExtension && !HasExtension(in)) + { + if (errors) { - if (errors) - { - *errors += "The path should have a drive. The path ["; - *errors += in; - *errors += "] is invalid."; - } - return false; + *errors += "The path should have the a file extension. The path ["; + *errors += in; + *errors += "] is invalid."; } + return false; + } - //does it have and extension if specified - if (bHasExtension && !HasExtension(in)) + //start at the beginning and walk down the characters of the path + const char* elementStart = in; + const char* walk = elementStart; + while (*walk) + { + if (*walk == AZ_CORRECT_FILESYSTEM_SEPARATOR) //is this the correct separator { - if (errors) - { - *errors += "The path should have the a file extension. The path ["; - *errors += in; - *errors += "] is invalid."; - } - return false; + elementStart = walk; } - - //start at the beginning and walk down the characters of the path - const char* elementStart = in; - const char* walk = elementStart; - while (*walk) +#if AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS + else if (*walk == AZ_FILESYSTEM_DRIVE_SEPARATOR) //is this the drive separator { - if (*walk == AZ_CORRECT_FILESYSTEM_SEPARATOR) //is this the correct separator - { - elementStart = walk; - } - #if AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS - else if (*walk == AZ_FILESYSTEM_DRIVE_SEPARATOR) //is this the drive separator - { - //A AZ_FILESYSTEM_DRIVE_SEPARATOR character con only occur in the first - //component of a valid path. If the elementStart is not GetBufferPtr() - //then we have past the first component - if (elementStart != in) - { - if (errors) - { - *errors += "There is a stray AZ_FILESYSTEM_DRIVE_SEPARATOR = "; - *errors += AZ_FILESYSTEM_DRIVE_SEPARATOR; - *errors += " found after the first component. The path ["; - *errors += in; - *errors += "] is invalid."; - } - return false; - } - } - #endif - #ifndef AZ_FILENAME_ALLOW_SPACES - else if (*walk == ' ') //is this a space + //A AZ_FILESYSTEM_DRIVE_SEPARATOR character con only occur in the first + //component of a valid path. If the elementStart is not GetBufferPtr() + //then we have past the first component + if (elementStart != in) { if (errors) { - *errors += "The component ["; - for (const char* c = elementStart + 1; c != walk; ++c) - { - *errors += *c; - } - *errors += "] has a SPACE character. The path ["; + *errors += "There is a stray AZ_FILESYSTEM_DRIVE_SEPARATOR = "; + *errors += AZ_FILESYSTEM_DRIVE_SEPARATOR; + *errors += " found after the first component. The path ["; *errors += in; *errors += "] is invalid."; } return false; } - #endif - - ++walk; } - - #if !AZ_TRAIT_OS_ALLOW_UNLIMITED_PATH_COMPONENT_LENGTH - //is this full path longer than AZ::IO::MaxPathLength (The longest a path with all components can possibly be)? - if (walk - in > AZ::IO::MaxPathLength) +#endif +#ifndef AZ_FILENAME_ALLOW_SPACES + else if (*walk == ' ') //is this a space { - if (errors != 0) + if (errors) { - *errors += "The path ["; + *errors += "The component ["; + for (const char* c = elementStart + 1; c != walk; ++c) + { + *errors += *c; + } + *errors += "] has a SPACE character. The path ["; *errors += in; - *errors += "] is over the AZ::IO::MaxPathLength = "; - char buf[64]; - _itoa_s(AZ::IO::MaxPathLength, buf, 10); - *errors += buf; - *errors += " characters total length limit."; + *errors += "] is invalid."; } return false; } - #endif +#endif - return true; + ++walk; } - bool ConstructFull(const char* pRootPath, const char* pFileName, AZStd::string& out, bool bNormalize /* = false*/) +#if !AZ_TRAIT_OS_ALLOW_UNLIMITED_PATH_COMPONENT_LENGTH + //is this full path longer than AZ::IO::MaxPathLength (The longest a path with all components can possibly be)? + if (walk - in > AZ::IO::MaxPathLength) { - if (!pRootPath || AZ::IO::PathView(pRootPath).IsRelative() - || !pFileName || AZ::IO::PathView(pFileName).IsAbsolute()) + if (errors != 0) { - return false; - } - AZ::IO::Path path(pRootPath); - path /= pFileName; - if (bNormalize) - { - out = AZStd::move(path.LexicallyNormal().Native()); - } - else - { - out = AZStd::move(path.Native()); - } - return IsValid(out.c_str()); - } - - bool ConstructFull(const char* pRootPath, const char* pFileName, const char* pFileExtension, AZStd::string& out, bool bNormalize /* = false*/) - { - if (!pRootPath || AZ::IO::PathView(pRootPath).IsRelative() - || !pFileName || AZ::IO::PathView(pFileName).IsAbsolute()) - { - return false; - } - AZ::IO::Path path(pRootPath); - path /= pFileName; - if (pFileExtension) - { - path.ReplaceExtension(pFileExtension); - } - if (bNormalize) - { - out = AZStd::move(path.LexicallyNormal().Native()); - } - else - { - out = AZStd::move(path.Native()); - } - return IsValid(out.c_str()); - } - - bool ConstructFull(const char* pRoot, const char* pRelativePath, const char* pFileName, const char* pFileExtension, AZStd::string& out, bool bNormalize /* = false*/) - { - if (!pRoot || AZ::IO::PathView(pRoot).IsRelative() - || !pRelativePath || AZ::IO::PathView(pRelativePath).IsAbsolute() - || !pFileName || AZ::IO::PathView(pFileName).IsAbsolute()) - { - return false; - } - AZ::IO::Path path(pRoot); - path /= pRelativePath; - path /= pFileName; - if (pFileExtension) - { - path.ReplaceExtension(pFileExtension); - } - if (bNormalize) - { - out = AZStd::move(path.LexicallyNormal().Native()); - } - else - { - out = AZStd::move(path.Native()); - } - return IsValid(out.c_str()); - } - - bool Split(const char* in, AZStd::string* pDstDrive, AZStd::string* pDstPath, AZStd::string* pDstName, AZStd::string* pDstExtension) - { - AZStd::string_view path{ in }; - if (path.empty()) - { - return false; - } - - AZ::IO::PathView pathView(path); - if (pDstDrive) - { - AZStd::string_view rootNameView = pathView.RootName().Native(); - if (rootNameView.size() > pDstDrive->max_size()) - { - return false; - } - *pDstDrive = rootNameView; - } - if (pDstPath) - { - AZStd::string_view rootDirectoryView = pathView.RootDirectory().Native(); - AZStd::string_view relPathParentView = pathView.ParentPath().RelativePath().Native(); - if (rootDirectoryView.size() + relPathParentView.size() > pDstPath->max_size()) - { - return false; - } - // Append the root directory if there is one - *pDstPath = rootDirectoryView; - // Append the relative path portion of the split path excluding the filename - *pDstPath += relPathParentView; - } - if (pDstName) - { - AZStd::string_view stemView = pathView.Stem().Native(); - if (stemView.size() > pDstName->max_size()) - { - return false; - } - *pDstName = stemView; - } - if (pDstExtension) - { - AZStd::string_view extensionView = pathView.Extension().Native(); - if (extensionView.size() > pDstExtension->max_size()) - { - return false; - } - *pDstExtension = extensionView; - } - - return true; - } - - bool Join(const char* pFirstPart, const char* pSecondPart, AZStd::string& out, [[maybe_unused]] bool bCaseInsensitive, bool bNormalize) - { - if (!pFirstPart || !pSecondPart) - { - return false; - } - - AZ::IO::PathView secondPath(pSecondPart); - AZ_Warning("StringFunc", secondPath.IsRelative(), "The second join parameter %s is an absolute path," - " this will replace the first part of the path resulting in an output of just the second part", - pSecondPart); - - AZ::IO::Path resultPath(pFirstPart); - resultPath /= secondPath; - out = bNormalize ? AZStd::move(resultPath.LexicallyNormal().Native()) : AZStd::move(resultPath.Native()); - return true; - } - - bool Join(const char* pFirstPart, const char* pSecondPart, FixedString& out, [[maybe_unused]] bool bCaseInsensitive, bool bNormalize) - { - if (!pFirstPart || !pSecondPart) - { - return false; - } - - AZ::IO::PathView secondPath(pSecondPart); - AZ_Warning("StringFunc", secondPath.IsRelative(), "The second join parameter %s is an absolute path," - " this will replace the first part of the path resulting in an output of just the second part", - pSecondPart); - - AZ::IO::FixedMaxPath resultPath(pFirstPart); - resultPath /= secondPath; - out = bNormalize ? AZStd::move(resultPath.LexicallyNormal().Native()) : AZStd::move(resultPath.Native()); - return true; - } - - bool HasDrive(const char* in, bool bCheckAllFileSystemFormats /*= false*/) - { - // no drive if empty - if (!in || in[0] == '\0') - { - return false; - } - AZ::IO::PathView pathView(in); - return pathView.HasRootName() || (bCheckAllFileSystemFormats && pathView.HasRootDirectory()); - } - - bool HasExtension(const char* in) - { - //it doesn't have an extension if it's empty - if (!in || in[0] == '\0') - { - return false; - } - - return AZ::IO::PathView(in).HasExtension(); - } - - bool IsExtension(const char* in, const char* pExtension, bool bCaseInsenitive /*= false*/) - { - //it doesn't have an extension if it's empty - if (!in || in[0] == '\0' || !pExtension || pExtension[0] == '\0') - { - return false; - } - - AZStd::string_view pathExtension = AZ::IO::PathView(in).Extension().Native(); - if (pathExtension.starts_with(AZ_FILESYSTEM_EXTENSION_SEPARATOR)) - { - pathExtension.remove_prefix(1); - } - AZStd::string_view extensionView(pExtension); - if (extensionView.starts_with(AZ_FILESYSTEM_EXTENSION_SEPARATOR)) - { - extensionView.remove_prefix(1); - } - - return AZStd::equal(pathExtension.begin(), pathExtension.end(), extensionView.begin(), extensionView.end(), - [bCaseInsenitive](const char lhs, const char rhs) - { - return !bCaseInsenitive ? lhs == rhs : tolower(lhs) == tolower(rhs); - }); - } - - bool IsRelative(const char* in) - { - //not relative if empty - if (!in || in[0] == '\0') - { - return false; - } - - return AZ::IO::PathView(in).IsRelative(); - } - - bool StripDrive(AZStd::string& inout) - { - AZ::IO::PathView pathView(inout); - AZ::IO::PathView rootNameView(pathView.RootName()); - if (!rootNameView.empty()) - { - inout.replace(0, rootNameView.Native().size(), ""); - return true; + *errors += "The path ["; + *errors += in; + *errors += "] is over the AZ::IO::MaxPathLength = "; + char buf[64]; + _itoa_s(AZ::IO::MaxPathLength, buf, 10); + *errors += buf; + *errors += " characters total length limit."; } return false; } +#endif - void StripPath(AZStd::string& inout) + return true; + } + + bool ConstructFull(const char* pRootPath, const char* pFileName, AZStd::string& out, bool bNormalize /* = false*/) + { + if (!pRootPath || AZ::IO::PathView(pRootPath).IsRelative() + || !pFileName || AZ::IO::PathView(pFileName).IsAbsolute()) { - inout = AZ::IO::PathView(inout).Filename().Native(); + return false; + } + AZ::IO::Path path(pRootPath); + path /= pFileName; + if (bNormalize) + { + out = AZStd::move(path.LexicallyNormal().Native()); + } + else + { + out = AZStd::move(path.Native()); + } + return IsValid(out.c_str()); + } + + bool ConstructFull(const char* pRootPath, const char* pFileName, const char* pFileExtension, AZStd::string& out, bool bNormalize /* = false*/) + { + if (!pRootPath || AZ::IO::PathView(pRootPath).IsRelative() + || !pFileName || AZ::IO::PathView(pFileName).IsAbsolute()) + { + return false; + } + AZ::IO::Path path(pRootPath); + path /= pFileName; + if (pFileExtension) + { + path.ReplaceExtension(pFileExtension); + } + if (bNormalize) + { + out = AZStd::move(path.LexicallyNormal().Native()); + } + else + { + out = AZStd::move(path.Native()); + } + return IsValid(out.c_str()); + } + + bool ConstructFull(const char* pRoot, const char* pRelativePath, const char* pFileName, const char* pFileExtension, AZStd::string& out, bool bNormalize /* = false*/) + { + if (!pRoot || AZ::IO::PathView(pRoot).IsRelative() + || !pRelativePath || AZ::IO::PathView(pRelativePath).IsAbsolute() + || !pFileName || AZ::IO::PathView(pFileName).IsAbsolute()) + { + return false; + } + AZ::IO::Path path(pRoot); + path /= pRelativePath; + path /= pFileName; + if (pFileExtension) + { + path.ReplaceExtension(pFileExtension); + } + if (bNormalize) + { + out = AZStd::move(path.LexicallyNormal().Native()); + } + else + { + out = AZStd::move(path.Native()); + } + return IsValid(out.c_str()); + } + + bool Split(const char* in, AZStd::string* pDstDrive, AZStd::string* pDstPath, AZStd::string* pDstName, AZStd::string* pDstExtension) + { + AZStd::string_view path{ in }; + if (path.empty()) + { + return false; } - void StripFullName(AZStd::string& inout) + AZ::IO::PathView pathView(path); + if (pDstDrive) { - inout = AZ::IO::Path(AZStd::move(inout)).RemoveFilename().Native(); - } - - void StripExtension(AZStd::string& inout) - { - AZ::IO::Path path(AZStd::move(inout)); - path.ReplaceExtension(); - inout = AZStd::move(path.Native()); - } - - bool StripComponent(AZStd::string& inout, bool bLastComponent /* = false*/) - { - AZ::IO::PathView pathView(inout); - auto pathBeginIter = pathView.begin(); - auto pathEndIter = pathView.end(); - if (pathBeginIter == pathEndIter) + AZStd::string_view rootNameView = pathView.RootName().Native(); + if (rootNameView.size() > pDstDrive->max_size()) { return false; } - AZ::IO::Path resultPath; - if (!bLastComponent) - { - // Removing leading path component - AZStd::advance(pathBeginIter, 1); - } - else - { - // Remove trailing path component - AZStd::advance(pathEndIter, -1); - } - for (; pathBeginIter != pathEndIter; ++pathBeginIter) - { - resultPath /= *pathBeginIter; - } - if (resultPath.empty()) + *pDstDrive = rootNameView; + } + if (pDstPath) + { + AZStd::string_view rootDirectoryView = pathView.RootDirectory().Native(); + AZStd::string_view relPathParentView = pathView.ParentPath().RelativePath().Native(); + if (rootDirectoryView.size() + relPathParentView.size() > pDstPath->max_size()) { return false; } - inout = AZStd::move(resultPath.Native()); + // Append the root directory if there is one + *pDstPath = rootDirectoryView; + // Append the relative path portion of the split path excluding the filename + *pDstPath += relPathParentView; + } + if (pDstName) + { + AZStd::string_view stemView = pathView.Stem().Native(); + if (stemView.size() > pDstName->max_size()) + { + return false; + } + *pDstName = stemView; + } + if (pDstExtension) + { + AZStd::string_view extensionView = pathView.Extension().Native(); + if (extensionView.size() > pDstExtension->max_size()) + { + return false; + } + *pDstExtension = extensionView; + } + + return true; + } + + bool Join(const char* pFirstPart, const char* pSecondPart, AZStd::string& out, [[maybe_unused]] bool bCaseInsensitive, bool bNormalize) + { + if (!pFirstPart || !pSecondPart) + { + return false; + } + + AZ::IO::PathView secondPath(pSecondPart); + AZ_Warning("StringFunc", secondPath.IsRelative(), "The second join parameter %s is an absolute path," + " this will replace the first part of the path resulting in an output of just the second part", + pSecondPart); + + AZ::IO::Path resultPath(pFirstPart); + resultPath /= secondPath; + out = bNormalize ? AZStd::move(resultPath.LexicallyNormal().Native()) : AZStd::move(resultPath.Native()); + return true; + } + + bool Join(const char* pFirstPart, const char* pSecondPart, FixedString& out, [[maybe_unused]] bool bCaseInsensitive, bool bNormalize) + { + if (!pFirstPart || !pSecondPart) + { + return false; + } + + AZ::IO::PathView secondPath(pSecondPart); + AZ_Warning("StringFunc", secondPath.IsRelative(), "The second join parameter %s is an absolute path," + " this will replace the first part of the path resulting in an output of just the second part", + pSecondPart); + + AZ::IO::FixedMaxPath resultPath(pFirstPart); + resultPath /= secondPath; + out = bNormalize ? AZStd::move(resultPath.LexicallyNormal().Native()) : AZStd::move(resultPath.Native()); + return true; + } + + bool HasDrive(const char* in, bool bCheckAllFileSystemFormats /*= false*/) + { + // no drive if empty + if (!in || in[0] == '\0') + { + return false; + } + AZ::IO::PathView pathView(in); + return pathView.HasRootName() || (bCheckAllFileSystemFormats && pathView.HasRootDirectory()); + } + + bool HasExtension(const char* in) + { + //it doesn't have an extension if it's empty + if (!in || in[0] == '\0') + { + return false; + } + + return AZ::IO::PathView(in).HasExtension(); + } + + bool IsExtension(const char* in, const char* pExtension, bool bCaseInsenitive /*= false*/) + { + //it doesn't have an extension if it's empty + if (!in || in[0] == '\0' || !pExtension || pExtension[0] == '\0') + { + return false; + } + + AZStd::string_view pathExtension = AZ::IO::PathView(in).Extension().Native(); + if (pathExtension.starts_with(AZ_FILESYSTEM_EXTENSION_SEPARATOR)) + { + pathExtension.remove_prefix(1); + } + AZStd::string_view extensionView(pExtension); + if (extensionView.starts_with(AZ_FILESYSTEM_EXTENSION_SEPARATOR)) + { + extensionView.remove_prefix(1); + } + + return AZStd::equal(pathExtension.begin(), pathExtension.end(), extensionView.begin(), extensionView.end(), + [bCaseInsenitive](const char lhs, const char rhs) + { + return !bCaseInsenitive ? lhs == rhs : tolower(lhs) == tolower(rhs); + }); + } + + bool IsRelative(const char* in) + { + //not relative if empty + if (!in || in[0] == '\0') + { + return false; + } + + return AZ::IO::PathView(in).IsRelative(); + } + + bool StripDrive(AZStd::string& inout) + { + AZ::IO::PathView pathView(inout); + AZ::IO::PathView rootNameView(pathView.RootName()); + if (!rootNameView.empty()) + { + inout.replace(0, rootNameView.Native().size(), ""); return true; } + return false; + } - bool GetDrive(const char* in, AZStd::string& out) + void StripPath(AZStd::string& inout) + { + inout = AZ::IO::PathView(inout).Filename().Native(); + } + + void StripFullName(AZStd::string& inout) + { + inout = AZ::IO::Path(AZStd::move(inout)).RemoveFilename().Native(); + } + + void StripExtension(AZStd::string& inout) + { + AZ::IO::Path path(AZStd::move(inout)); + path.ReplaceExtension(); + inout = AZStd::move(path.Native()); + } + + bool StripComponent(AZStd::string& inout, bool bLastComponent /* = false*/) + { + AZ::IO::PathView pathView(inout); + auto pathBeginIter = pathView.begin(); + auto pathEndIter = pathView.end(); + if (pathBeginIter == pathEndIter) { - if (!in || in[0] == '\0') - { - return false; - } + return false; + } + AZ::IO::Path resultPath; + if (!bLastComponent) + { + // Removing leading path component + AZStd::advance(pathBeginIter, 1); + } + else + { + // Remove trailing path component + AZStd::advance(pathEndIter, -1); + } + for (; pathBeginIter != pathEndIter; ++pathBeginIter) + { + resultPath /= *pathBeginIter; + } + if (resultPath.empty()) + { + return false; + } + inout = AZStd::move(resultPath.Native()); + return true; + } - out = AZ::IO::PathView(in).RootName().Native(); + bool GetDrive(const char* in, AZStd::string& out) + { + if (!in || in[0] == '\0') + { + return false; + } + + out = AZ::IO::PathView(in).RootName().Native(); + return !out.empty(); + } + + AZStd::optional GetParentDir(AZStd::string_view path) + { + if (path.empty()) + { + return {}; + } + + AZStd::string_view parentDir = AZ::IO::PathView(path).ParentPath().Native(); + return !parentDir.empty() ? AZStd::make_optional(parentDir) : AZStd::nullopt; + } + + bool GetFullPath(const char* in, AZStd::string& out) + { + if (!in || in[0] == '\0') + { + return false; + } + + out = AZ::IO::PathView(in).ParentPath().Native(); + return !out.empty(); + } + + bool GetFolderPath(const char* in, AZStd::string& out) + { + return GetFullPath(in, out); + } + + bool GetFolder(const char* in, AZStd::string& out, bool bFirst /* = false*/) + { + if (!in || in[0] == '\0') + { + return false; + } + + if (!bFirst) + { + out = AZ::IO::PathView(in).ParentPath().Filename().Native(); return !out.empty(); } - - AZStd::optional GetParentDir(AZStd::string_view path) + else { - if (path.empty()) - { - return {}; - } - - AZStd::string_view parentDir = AZ::IO::PathView(path).ParentPath().Native(); - return !parentDir.empty() ? AZStd::make_optional(parentDir) : AZStd::nullopt; - } - - bool GetFullPath(const char* in, AZStd::string& out) - { - if (!in || in[0] == '\0') - { - return false; - } - - out = AZ::IO::PathView(in).ParentPath().Native(); + AZStd::string_view relativePath = AZ::IO::PathView(in).RelativePath().Native(); + size_t nextSeparator = relativePath.find_first_of(AZ_CORRECT_FILESYSTEM_SEPARATOR); + out = nextSeparator != AZStd::string_view::npos ? relativePath.substr(0, nextSeparator) : relativePath; return !out.empty(); } + } - bool GetFolderPath(const char* in, AZStd::string& out) + bool GetFullFileName(const char* in, AZStd::string& out) + { + if (!in || in[0] == '\0') { - return GetFullPath(in, out); + return false; } - bool GetFolder(const char* in, AZStd::string& out, bool bFirst /* = false*/) - { - if (!in || in[0] == '\0') - { - return false; - } + out = AZ::IO::PathView(in).Filename().Native(); + return !out.empty(); + } - if (!bFirst) - { - out = AZ::IO::PathView(in).ParentPath().Filename().Native(); - return !out.empty(); - } - else - { - AZStd::string_view relativePath = AZ::IO::PathView(in).RelativePath().Native(); - size_t nextSeparator = relativePath.find_first_of(AZ_CORRECT_FILESYSTEM_SEPARATOR); - out = nextSeparator != AZStd::string_view::npos ? relativePath.substr(0, nextSeparator) : relativePath; - return !out.empty(); - } + bool GetFileName(const char* in, AZStd::string& out) + { + if (!in || in[0] == '\0') + { + return false; } - bool GetFullFileName(const char* in, AZStd::string& out) - { - if (!in || in[0] == '\0') - { - return false; - } + out = AZ::IO::PathView(in).Stem().Native(); + return !out.empty(); + } - out = AZ::IO::PathView(in).Filename().Native(); - return !out.empty(); + bool GetExtension(const char* in, AZStd::string& out, bool includeDot) + { + if (!in || in[0] == '\0') + { + return false; } - bool GetFileName(const char* in, AZStd::string& out) + AZStd::string_view extensionView = AZ::IO::PathView(in).Extension().Native(); + // PathView returns extensions with the character, so remove the + // if it is not included + if (!includeDot && extensionView.starts_with(AZ_FILESYSTEM_EXTENSION_SEPARATOR)) { - if (!in || in[0] == '\0') + extensionView.remove_prefix(1); + } + out = extensionView; + return !out.empty(); + } + + void ReplaceFullName(AZStd::string& inout, const char* pFileName /* = nullptr*/, const char* pFileExtension /* = nullptr*/) + { + //strip the full file name if it has one + AZ::IO::Path path(AZStd::move(inout)); + path.RemoveFilename(); + if (pFileName) + { + path /= pFileName; + } + if (pFileExtension) + { + path.ReplaceExtension(pFileExtension); + } + inout = AZStd::move(path.Native()); + } + + void ReplaceExtension(AZStd::string& inout, const char* newExtension /* = nullptr*/) + { + //treat this as a strip + if (!newExtension || newExtension[0] == '\0') + { + return; + } + AZ::IO::Path path(AZStd::move(inout)); + path.ReplaceExtension(newExtension); + inout = AZStd::move(path.Native()); + } + + AZStd::string& AppendSeparator(AZStd::string& inout) + { + if (inout.ends_with(AZ_WRONG_FILESYSTEM_SEPARATOR)) + { + inout.replace(inout.end() - 1, inout.end(), 1, AZ_CORRECT_FILESYSTEM_SEPARATOR); + } + else if (!inout.ends_with(AZ_CORRECT_FILESYSTEM_SEPARATOR)) + { + inout.append(1, AZ_CORRECT_FILESYSTEM_SEPARATOR); + } + return inout; + } + } // namespace Path + + namespace Json + { + /* + According to http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf: + A string is a sequence of Unicode code points wrapped with quotation marks (U+0022). All characters may be + placed within the quotation marks except for the characters that must be escaped: quotation mark (U+0022), + reverse solidus (U+005C), and the control characters U+0000 to U+001F. + */ + AZStd::string& ToEscapedString(AZStd::string& inout) + { + size_t strSize = inout.size(); + + for (size_t i = 0; i < strSize; ++i) + { + char character = inout[i]; + + // defaults to 1 if it hits any cases except default + size_t jumpChar = 1; + switch (character) { - return false; + case '"': + inout.insert(i, "\\"); + break; + + case '\\': + inout.insert(i, "\\"); + break; + + case '/': + inout.insert(i, "\\"); + break; + + case '\b': + inout.replace(i, i + 1, "\\b"); + break; + + case '\f': + inout.replace(i, i + 1, "\\f"); + break; + + case '\n': + inout.replace(i, i + 1, "\\n"); + break; + + case '\r': + inout.replace(i, i + 1, "\\r"); + break; + + case '\t': + inout.replace(i, i + 1, "\\t"); + break; + + default: + /* + Control characters U+0000 to U+001F may be represented as a six - character sequence : a reverse solidus, + followed by the lowercase letter u, followed by four hexadecimal digits that encode the code point. + */ + if (character >= '\x0000' && character <= '\x001f') + { + // jumping "\uXXXX" characters + jumpChar = 6; + + AZStd::string hexStr = AZStd::string::format("\\u%04x", static_cast(character)); + inout.replace(i, i + 1, hexStr); + } + else + { + jumpChar = 0; + } } - out = AZ::IO::PathView(in).Stem().Native(); - return !out.empty(); + i += jumpChar; + strSize += jumpChar; } - bool GetExtension(const char* in, AZStd::string& out, bool includeDot) - { - if (!in || in[0] == '\0') - { - return false; - } + return inout; + } + } // namespace Json - AZStd::string_view extensionView = AZ::IO::PathView(in).Extension().Native(); - // PathView returns extensions with the character, so remove the - // if it is not included - if (!includeDot && extensionView.starts_with(AZ_FILESYSTEM_EXTENSION_SEPARATOR)) - { - extensionView.remove_prefix(1); - } - out = extensionView; - return !out.empty(); - } + namespace Base64 + { + static const char base64pad = '='; - void ReplaceFullName(AZStd::string& inout, const char* pFileName /* = nullptr*/, const char* pFileExtension /* = nullptr*/) - { - //strip the full file name if it has one - AZ::IO::Path path(AZStd::move(inout)); - path.RemoveFilename(); - if (pFileName) - { - path /= pFileName; - } - if (pFileExtension) - { - path.ReplaceExtension(pFileExtension); - } - inout = AZStd::move(path.Native()); - } + static const char c_base64Table[] = + { + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/" + }; - void ReplaceExtension(AZStd::string& inout, const char* newExtension /* = nullptr*/) - { - //treat this as a strip - if (!newExtension || newExtension[0] == '\0') - { - return; - } - AZ::IO::Path path(AZStd::move(inout)); - path.ReplaceExtension(newExtension); - inout = AZStd::move(path.Native()); - } + static const AZ::u8 c_inverseBase64Table[] = + { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; - AZStd::string& AppendSeparator(AZStd::string& inout) - { - if (inout.ends_with(AZ_WRONG_FILESYSTEM_SEPARATOR)) - { - inout.replace(inout.end() - 1, inout.end(), 1, AZ_CORRECT_FILESYSTEM_SEPARATOR); - } - else if (!inout.ends_with(AZ_CORRECT_FILESYSTEM_SEPARATOR)) - { - inout.append(1, AZ_CORRECT_FILESYSTEM_SEPARATOR); - } - return inout; - } - } // namespace Path + bool IsValidEncodedChar(const char encodedChar) + { + return c_inverseBase64Table[static_cast(encodedChar)] != 0xff; + } - namespace Json + AZStd::string Encode(const AZ::u8* in, const size_t size) { /* - According to http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf: - A string is a sequence of Unicode code points wrapped with quotation marks (U+0022). All characters may be - placed within the quotation marks except for the characters that must be escaped: quotation mark (U+0022), - reverse solidus (U+005C), and the control characters U+0000 to U+001F. + figure retrieved from the Base encoding rfc https://tools.ietf.org/html/rfc4648 + +--first octet--+-second octet--+--third octet--+ + |7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0| + +-----------+---+-------+-------+---+-----------+ + |5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0| + +--1.index--+--2.index--+--3.index--+--4.index--+ */ - AZStd::string& ToEscapedString(AZStd::string& inout) + AZStd::string result; + + const size_t remainder = size % 3; + const size_t alignEndSize = size - remainder; + const AZ::u8* encodeBuf = in; + size_t encodeIndex = 0; + for (; encodeIndex < alignEndSize; encodeIndex += 3) { - size_t strSize = inout.size(); - - for (size_t i = 0; i < strSize; ++i) - { - char character = inout[i]; - - // defaults to 1 if it hits any cases except default - size_t jumpChar = 1; - switch (character) - { - case '"': - inout.insert(i, "\\"); - break; - - case '\\': - inout.insert(i, "\\"); - break; - - case '/': - inout.insert(i, "\\"); - break; - - case '\b': - inout.replace(i, i + 1, "\\b"); - break; - - case '\f': - inout.replace(i, i + 1, "\\f"); - break; - - case '\n': - inout.replace(i, i + 1, "\\n"); - break; - - case '\r': - inout.replace(i, i + 1, "\\r"); - break; - - case '\t': - inout.replace(i, i + 1, "\\t"); - break; - - default: - /* - Control characters U+0000 to U+001F may be represented as a six - character sequence : a reverse solidus, - followed by the lowercase letter u, followed by four hexadecimal digits that encode the code point. - */ - if (character >= '\x0000' && character <= '\x001f') - { - // jumping "\uXXXX" characters - jumpChar = 6; - - AZStd::string hexStr = AZStd::string::format("\\u%04x", static_cast(character)); - inout.replace(i, i + 1, hexStr); - } - else - { - jumpChar = 0; - } - } - - i += jumpChar; - strSize += jumpChar; - } - - return inout; - } - } // namespace Json - - namespace Base64 - { - static const char base64pad = '='; - - static const char c_base64Table[] = - { - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/" - }; - - static const AZ::u8 c_inverseBase64Table[] = - { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, - 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff - }; - - bool IsValidEncodedChar(const char encodedChar) - { - return c_inverseBase64Table[static_cast(encodedChar)] != 0xff; - } - - AZStd::string Encode(const AZ::u8* in, const size_t size) - { - /* - figure retrieved from the Base encoding rfc https://tools.ietf.org/html/rfc4648 - +--first octet--+-second octet--+--third octet--+ - |7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0| - +-----------+---+-------+-------+---+-----------+ - |5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0| - +--1.index--+--2.index--+--3.index--+--4.index--+ - */ - AZStd::string result; - - const size_t remainder = size % 3; - const size_t alignEndSize = size - remainder; - const AZ::u8* encodeBuf = in; - size_t encodeIndex = 0; - for (; encodeIndex < alignEndSize; encodeIndex += 3) - { - encodeBuf = &in[encodeIndex]; - - result.push_back(c_base64Table[(encodeBuf[0] & 0xfc) >> 2]); - result.push_back(c_base64Table[((encodeBuf[0] & 0x03) << 4) | ((encodeBuf[1] & 0xf0) >> 4)]); - result.push_back(c_base64Table[((encodeBuf[1] & 0x0f) << 2) | ((encodeBuf[2] & 0xc0) >> 6)]); - result.push_back(c_base64Table[encodeBuf[2] & 0x3f]); - } - encodeBuf = &in[encodeIndex]; - if (remainder == 2) - { - result.push_back(c_base64Table[(encodeBuf[0] & 0xfc) >> 2]); - result.push_back(c_base64Table[((encodeBuf[0] & 0x03) << 4) | ((encodeBuf[1] & 0xf0) >> 4)]); - result.push_back(c_base64Table[((encodeBuf[1] & 0x0f) << 2)]); - result.push_back(base64pad); - } - else if (remainder == 1) - { - result.push_back(c_base64Table[(encodeBuf[0] & 0xfc) >> 2]); - result.push_back(c_base64Table[(encodeBuf[0] & 0x03) << 4]); - result.push_back(base64pad); - result.push_back(base64pad); - } - return result; + result.push_back(c_base64Table[(encodeBuf[0] & 0xfc) >> 2]); + result.push_back(c_base64Table[((encodeBuf[0] & 0x03) << 4) | ((encodeBuf[1] & 0xf0) >> 4)]); + result.push_back(c_base64Table[((encodeBuf[1] & 0x0f) << 2) | ((encodeBuf[2] & 0xc0) >> 6)]); + result.push_back(c_base64Table[encodeBuf[2] & 0x3f]); } - bool Decode(AZStd::vector& out, const char* in, const size_t size) + encodeBuf = &in[encodeIndex]; + if (remainder == 2) { - if (size % 4 != 0) - { - AZ_Warning("StringFunc", size % 4 == 0, "Base 64 encoded data length must be multiple of 4"); - return false; - } - - AZStd::vector result; - result.reserve(size * 3 / 4); - const char* decodeBuf = in; - size_t decodeIndex = 0; - for (; decodeIndex < size; decodeIndex += 4) - { - decodeBuf = &in[decodeIndex]; - //Check if each character is a valid Base64 encoded character - { - // First Octet - if (!IsValidEncodedChar(decodeBuf[0])) - { - AZ_Warning("StringFunc", false, "Invalid Base64 encoded text at offset %tu", AZStd::distance(in, &decodeBuf[0])); - return false; - } - if (!IsValidEncodedChar(decodeBuf[1])) - { - AZ_Warning("StringFunc", false, "Invalid Base64 encoded text at offset %tu", AZStd::distance(in, &decodeBuf[1])); - return false; - } - - result.push_back((c_inverseBase64Table[static_cast(decodeBuf[0])] << 2) | ((c_inverseBase64Table[static_cast(decodeBuf[1])] & 0x30) >> 4)); - } - - { - // Second Octet - if (decodeBuf[2] == base64pad) - { - break; - } - - if (!IsValidEncodedChar(decodeBuf[2])) - { - AZ_Warning("StringFunc", false, "Invalid Base64 encoded text at offset %tu", AZStd::distance(in, &decodeBuf[2])); - return false; - } - - result.push_back(((c_inverseBase64Table[static_cast(decodeBuf[1])] & 0x0f) << 4) | ((c_inverseBase64Table[static_cast(decodeBuf[2])] & 0x3c) >> 2)); - } - - { - // Third Octet - if (decodeBuf[3] == base64pad) - { - break; - } - - if (!IsValidEncodedChar(decodeBuf[3])) - { - AZ_Warning("StringFunc", false, "Invalid Base64 encoded text at offset %tu", AZStd::distance(in, &decodeBuf[3])); - return false; - } - - result.push_back(((c_inverseBase64Table[static_cast(decodeBuf[2])] & 0x03) << 6) | (c_inverseBase64Table[static_cast(decodeBuf[3])] & 0x3f)); - } - } - - out = AZStd::move(result); - return true; + result.push_back(c_base64Table[(encodeBuf[0] & 0xfc) >> 2]); + result.push_back(c_base64Table[((encodeBuf[0] & 0x03) << 4) | ((encodeBuf[1] & 0xf0) >> 4)]); + result.push_back(c_base64Table[((encodeBuf[1] & 0x0f) << 2)]); + result.push_back(base64pad); } + else if (remainder == 1) + { + result.push_back(c_base64Table[(encodeBuf[0] & 0xfc) >> 2]); + result.push_back(c_base64Table[(encodeBuf[0] & 0x03) << 4]); + result.push_back(base64pad); + result.push_back(base64pad); + } + + return result; } - namespace Utf8 + bool Decode(AZStd::vector& out, const char* in, const size_t size) { - bool CheckNonAsciiChar(const AZStd::string& in) + if (size % 4 != 0) { - for (int i = 0; i < in.length(); ++i) - { - char byte = in[i]; - if (byte & 0x80) - { - return true; - } - } + AZ_Warning("StringFunc", size % 4 == 0, "Base 64 encoded data length must be multiple of 4"); return false; } + + AZStd::vector result; + result.reserve(size * 3 / 4); + const char* decodeBuf = in; + size_t decodeIndex = 0; + for (; decodeIndex < size; decodeIndex += 4) + { + decodeBuf = &in[decodeIndex]; + //Check if each character is a valid Base64 encoded character + { + // First Octet + if (!IsValidEncodedChar(decodeBuf[0])) + { + AZ_Warning("StringFunc", false, "Invalid Base64 encoded text at offset %tu", AZStd::distance(in, &decodeBuf[0])); + return false; + } + if (!IsValidEncodedChar(decodeBuf[1])) + { + AZ_Warning("StringFunc", false, "Invalid Base64 encoded text at offset %tu", AZStd::distance(in, &decodeBuf[1])); + return false; + } + + result.push_back((c_inverseBase64Table[static_cast(decodeBuf[0])] << 2) | ((c_inverseBase64Table[static_cast(decodeBuf[1])] & 0x30) >> 4)); + } + + { + // Second Octet + if (decodeBuf[2] == base64pad) + { + break; + } + + if (!IsValidEncodedChar(decodeBuf[2])) + { + AZ_Warning("StringFunc", false, "Invalid Base64 encoded text at offset %tu", AZStd::distance(in, &decodeBuf[2])); + return false; + } + + result.push_back(((c_inverseBase64Table[static_cast(decodeBuf[1])] & 0x0f) << 4) | ((c_inverseBase64Table[static_cast(decodeBuf[2])] & 0x3c) >> 2)); + } + + { + // Third Octet + if (decodeBuf[3] == base64pad) + { + break; + } + + if (!IsValidEncodedChar(decodeBuf[3])) + { + AZ_Warning("StringFunc", false, "Invalid Base64 encoded text at offset %tu", AZStd::distance(in, &decodeBuf[3])); + return false; + } + + result.push_back(((c_inverseBase64Table[static_cast(decodeBuf[2])] & 0x03) << 6) | (c_inverseBase64Table[static_cast(decodeBuf[3])] & 0x3f)); + } + } + + out = AZStd::move(result); + return true; } - } // namespace StringFunc -} // namespace AZ + } + + namespace Utf8 + { + bool CheckNonAsciiChar(const AZStd::string& in) + { + for (int i = 0; i < in.length(); ++i) + { + char byte = in[i]; + if (byte & 0x80) + { + return true; + } + } + return false; + } + } +} // namespace AZ::StringFunc diff --git a/Code/Framework/AzCore/AzCore/Task/TaskExecutor.cpp b/Code/Framework/AzCore/AzCore/Task/TaskExecutor.cpp index 4097348798..08cfb11d34 100644 --- a/Code/Framework/AzCore/AzCore/Task/TaskExecutor.cpp +++ b/Code/Framework/AzCore/AzCore/Task/TaskExecutor.cpp @@ -363,7 +363,11 @@ namespace AZ { ++m_graphsRemaining; - event->m_executor = this; // Used to validate event is not waited for inside a job + if (event) + { + event->IncWaitCount(); + event->m_executor = this; // Used to validate event is not waited for inside a job + } // Submit all tasks that have no inbound edges for (Internal::Task& task : graph.Tasks()) diff --git a/Code/Framework/AzCore/AzCore/Task/TaskGraph.cpp b/Code/Framework/AzCore/AzCore/Task/TaskGraph.cpp index f57b06890a..eeb46d6887 100644 --- a/Code/Framework/AzCore/AzCore/Task/TaskGraph.cpp +++ b/Code/Framework/AzCore/AzCore/Task/TaskGraph.cpp @@ -20,6 +20,46 @@ namespace AZ m_semaphore.acquire(); } + void TaskGraphEvent::IncWaitCount() + { + // guess zero to optimize for single task graph using an event, if multiple are using it then this will take 2+ comp_exch calls + int expectedValue = 0; + while(!m_waitCount.compare_exchange_weak(expectedValue, expectedValue + 1)) + { + // value will be negative once event is ready to signal or has been signaled. Shouldn't happen. + AZ_Assert(expectedValue >= 0, "Called TaskGraphEvent::IncWaitCount on a signalled event"); + if (expectedValue < 0) // event already signaled, skip + { + return; + } + }; + } + + void TaskGraphEvent::Signal() + { + // guess one to optimize for single task graph using an event, if multiple are using it then this will take 2+ comp_exch calls + int expectedValue = 1; + while(!m_waitCount.compare_exchange_weak(expectedValue, expectedValue - 1)) + { + // It's an error for Signal to be called if no one is waiting, or the event has already been signaled + AZ_Assert(expectedValue > 0, "Called TaskGraphEvent::Signal when event is either signaled or unused"); + if (expectedValue < 0) // return if already signaled + { + return; + } + }; + + if (expectedValue == 1) // This call to Signal decremented the value to 0. + { + expectedValue = 0; + // validate no one incremented the wait count and mark signalling state + if (m_waitCount.compare_exchange_strong(expectedValue, -1)) + { + m_semaphore.release(); + } + } + } + void TaskToken::PrecedesInternal(TaskToken& comesAfter) { AZ_Assert(!m_parent.m_submitted, "Cannot mutate a TaskGraph that was previously submitted."); diff --git a/Code/Framework/AzCore/AzCore/Task/TaskGraph.h b/Code/Framework/AzCore/AzCore/Task/TaskGraph.h index 9553013a4b..ffe3ea6caf 100644 --- a/Code/Framework/AzCore/AzCore/Task/TaskGraph.h +++ b/Code/Framework/AzCore/AzCore/Task/TaskGraph.h @@ -16,6 +16,7 @@ #include #include #include +#include namespace AZ { @@ -61,14 +62,14 @@ namespace AZ uint32_t m_index; }; - // A TaskGraphEvent may be used to block until a task graph has finished executing. Usage + // A TaskGraphEvent may be used to block until one or more task graphs has finished executing. Usage // is NOT recommended for the majority of tasks (prefer to simply containing expanding/contracting // the graph without synchronization over the course of the frame). However, the event // is useful for the edges of the computation graph. // // You are responsible for ensuring the event object lifetime exceeds the task graph lifetime. // - // After the TaskGraphEvent is signaled, you are allowed to reuse the same TaskGraphEvent + // After the TaskGraphEvent is signaled, you are NOT allowed to reuse the same TaskGraphEvent // for a future submission. class TaskGraphEvent { @@ -81,10 +82,12 @@ namespace AZ friend class TaskGraph; friend class TaskExecutor; + void IncWaitCount(); void Signal(); AZStd::binary_semaphore m_semaphore; - TaskExecutor* m_executor = nullptr; + AZStd::atomic_int m_waitCount = 0; + TaskExecutor* m_executor = nullptr; }; // The TaskGraph encapsulates a set of tasks and their interdependencies. After adding diff --git a/Code/Framework/AzCore/AzCore/Task/TaskGraph.inl b/Code/Framework/AzCore/AzCore/Task/TaskGraph.inl index 7b2f0cefdc..9a5289eb82 100644 --- a/Code/Framework/AzCore/AzCore/Task/TaskGraph.inl +++ b/Code/Framework/AzCore/AzCore/Task/TaskGraph.inl @@ -33,11 +33,6 @@ namespace AZ return m_semaphore.try_acquire_for(AZStd::chrono::milliseconds{ 0 }); } - inline void TaskGraphEvent::Signal() - { - m_semaphore.release(); - } - template TaskToken TaskGraph::AddTask(TaskDescriptor const& desc, Lambda&& lambda) { diff --git a/Code/Framework/AzCore/AzCore/Time/ITime.h b/Code/Framework/AzCore/AzCore/Time/ITime.h index a97ba2319a..a61b629f35 100644 --- a/Code/Framework/AzCore/AzCore/Time/ITime.h +++ b/Code/Framework/AzCore/AzCore/Time/ITime.h @@ -12,8 +12,8 @@ #include #include #include -#include #include +#include namespace AZ { @@ -24,15 +24,21 @@ namespace AZ //! Using int64_t as the underlying type, this is good to represent approximately 292,471 years AZ_TYPE_SAFE_INTEGRAL(TimeUs, int64_t); + namespace Time + { + static const AZ::TimeMs ZeroTimeMs = AZ::TimeMs{ 0 }; + static const AZ::TimeUs ZeroTimeUs = AZ::TimeUs{ 0 }; + } + //! @class ITime //! @brief This is an AZ::Interface<> for managing time related operations. //! AZ::ITime and associated types may not operate in realtime. These abstractions are to allow our application //! simulation to operate both slower and faster than realtime in a well defined and user controllable manner - //! The rate at which time passes for AZ::ITime is controlled by the cvar t_scale - //! t_scale == 0 means simulation time should halt - //! 0 < t_scale < 1 will cause time to pass slower than realtime, with t_scale 0.1 being roughly 1/10th realtime - //! t_scale == 1 will cause time to pass at roughly realtime - //! t_scale > 1 will cause time to pass faster than normal, with t_scale 10 being roughly 10x realtime + //! The rate at which time passes for AZ::ITime is controlled by the cvar t_simulationTickScale + //! t_simulationTickScale == 0 means simulation time should halt + //! 0 < t_simulationTickScale < 1 will cause time to pass slower than realtime, with t_simulationTickScale 0.1 being roughly 1/10th realtime + //! t_simulationTickScale == 1 will cause time to pass at roughly realtime + //! t_simulationTickScale > 1 will cause time to pass faster than normal, with t_simulationTickScale 10 being roughly 10x realtime class ITime { public: @@ -41,15 +47,72 @@ namespace AZ ITime() = default; virtual ~ITime() = default; - //! Returns the number of milliseconds since application start. - //! @return the number of milliseconds that have elapsed since application start + //! Returns the number of milliseconds since application start scaled by t_simulationTickScale. + //! @return The number of milliseconds that have elapsed since application start. virtual TimeMs GetElapsedTimeMs() const = 0; - //! Returns the number of microseconds since application start. + //! Returns the number of microseconds since application start scaled by t_simulationTickScale. //! @return the number of microseconds that have elapsed since application start virtual TimeUs GetElapsedTimeUs() const = 0; + + //! Returns the number of milliseconds since application start. + //! This value is not affected by the t_simulationTickScale cvar. + //! @return The number of milliseconds that have elapsed since application start. + virtual TimeMs GetRealElapsedTimeMs() const = 0; + + //! Returns the number of microseconds since application start. + //! This value is not affected by the t_simulationTickScale cvar. + //! @return The number of microseconds that have elapsed since application start. + virtual TimeUs GetRealElapsedTimeUs() const = 0; + + //! Returns the current simulation tick delta time. + //! This is affected by the cvars t_simulationTickScale, t_simulationTickDeltaOverride, and t_maxGameTickDelta. + //! @return The number of microseconds elapsed since the last game tick. + virtual TimeUs GetSimulationTickDeltaTimeUs() const = 0; + + //! Returns the non-manipulated tick time. + //! @return The number of microseconds elapsed since the last game tick. + virtual TimeUs GetRealTickDeltaTimeUs() const = 0; + + //! Returns the time since application start of when the last simulation tick was updated. + virtual TimeUs GetLastSimulationTickTime() const = 0; + + //! If > 0 this will override the simulation tick delta time with the provided value. + //! When enabled this will ignore any set simulation tick scale. + //! Setting to 0 disables the override. + //! @param timeMs The time in milliseconds to use for the tick delta. + virtual void SetSimulationTickDeltaOverride(TimeMs timeMs) = 0; + + //! Returns the current simulation tick override. + //! 0 means disabled. + //! @returns The current simulation tick override in milliseconds. + virtual TimeMs GetSimulationTickDeltaOverride() const = 0; + + //! A scalar amount to adjust the passage of time by, 1.0 == realtime, 0.5 == half realtime, 2.0 == doubletime. + //! @param scale The scalar value to apply to the simulation time. + virtual void SetSimulationTickScale(float scale) = 0; + + //! Returns the current simulation tick scale. + //! 1.0 == realtime, 0.5 == half realtime, 2.0 == doubletime. + //! @returns The simulation tick scale value. + virtual float GetSimulationTickScale() const = 0; + + //! The minimum rate to force the simulation tick to run. + //! 0 for as fast as possible. 30 = ~33ms, 60 = ~16ms. + //! Setting to 0 will disable rate limiting. + //! @note It is not guaranteed to hit the requested tick rate exactly. + //! @param rate The rate in frames per second. + virtual void SetSimulationTickRate(int rate) = 0; + + //! Return the current simulation tick rate. + //! 0 means disabled. + //! @return The rate in frames per second. + virtual int32_t GetSimulationTickRate() const = 0; AZ_DISABLE_COPY_MOVE(ITime); + + static const AZ::TimeMs ZeroTimeMs = AZ::TimeMs{ 0 }; + static const AZ::TimeUs ZeroTimeUs = AZ::TimeUs{ 0 }; }; // EBus wrapper for ScriptCanvas @@ -74,6 +137,36 @@ namespace AZ return AZ::Interface::Get()->GetElapsedTimeUs(); } + //! This is a simple convenience wrapper + inline TimeMs GetRealElapsedTimeMs() + { + return AZ::Interface::Get()->GetRealElapsedTimeMs(); + } + + //! This is a simple convenience wrapper + inline TimeUs GetRealElapsedTimeUs() + { + return AZ::Interface::Get()->GetRealElapsedTimeUs(); + } + + //! This is a simple convenience wrapper + inline TimeUs GetSimulationTickDeltaTimeUs() + { + return AZ::Interface::Get()->GetSimulationTickDeltaTimeUs(); + } + + //! This is a simple convenience wrapper + inline TimeUs GetRealTickDeltaTimeUs() + { + return AZ::Interface::Get()->GetRealTickDeltaTimeUs(); + } + + //! This is a simple convenience wrapper + inline TimeUs GetLastSimulationTickTime() + { + return AZ::Interface::Get()->GetLastSimulationTickTime(); + } + //! Converts from milliseconds to microseconds inline TimeUs TimeMsToUs(TimeMs value) { @@ -92,12 +185,24 @@ namespace AZ return static_cast(value) / 1000.0f; } + //! Converts from milliseconds to seconds + inline double TimeMsToSecondsDouble(TimeMs value) + { + return static_cast(value) / 1000.0; + } + //! Converts from microseconds to seconds inline float TimeUsToSeconds(TimeUs value) { return static_cast(value) / 1000000.0f; } + //! Converts from microseconds to seconds + inline double TimeUsToSecondsDouble(TimeUs value) + { + return static_cast(value) / 1000000.0; + } + //! Converts from milliseconds to AZStd::chrono::time_point inline auto TimeMsToChrono(TimeMs value) { @@ -113,6 +218,20 @@ namespace AZ auto chronoValue = AZStd::chrono::microseconds(aznumeric_cast(value)); return epoch + chronoValue; } + + //! A utility function to convert from seconds to TimeMs + inline TimeMs SecondsToTimeMs(const double value) + { + const double valueMs = value * 1000.0; + return static_cast(static_cast(valueMs)); + } + + //! A utility function to convert from seconds to TimeUs + inline TimeUs SecondsToTimeUs(const double value) + { + const double valueMs = value * 1000000.0; + return static_cast(static_cast(valueMs)); + } } // namespace AZ AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(AZ::TimeMs); diff --git a/Code/Framework/AzCore/AzCore/Time/TimeSystem.cpp b/Code/Framework/AzCore/AzCore/Time/TimeSystem.cpp new file mode 100644 index 0000000000..a5e0908ac4 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Time/TimeSystem.cpp @@ -0,0 +1,213 @@ +/* + * 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 +#include +#include +#include + +namespace AZ +{ + namespace + { + void cvar_t_simulationTickScale_Changed(const float& value) + { + if (auto* timeSystem = AZ::Interface::Get()) + { + timeSystem->SetSimulationTickScale(value); + } + } + + void cvar_t_simulationTickDeltaOverride_Changed(const float& value) + { + if (auto* timeSystem = AZ::Interface::Get()) + { + timeSystem->SetSimulationTickDeltaOverride(AZ::SecondsToTimeMs(value)); + } + } + + void cvar_t_simulationTickRate_Changed(const int& rate) + { + AZ_Warning("tick", false, "Simulation tick rate limiting is currently disabled. Setting will not be applied."); + if (auto* timeSystem = AZ::Interface::Get()) + { + timeSystem->SetSimulationTickRate(rate); + } + } + } // namespace + + AZ_CVAR(float, t_simulationTickScale, 1.0f, cvar_t_simulationTickScale_Changed, AZ::ConsoleFunctorFlags::Null, + "A scalar amount to adjust time passage by, 1.0 == realtime, 0.5 == half realtime, 2.0 == doubletime"); + + AZ_CVAR(float, t_simulationTickDeltaOverride, 0.0f, cvar_t_simulationTickDeltaOverride_Changed, AZ::ConsoleFunctorFlags::Null, + "If > 0, overrides the simulation tick delta time with the provided value (Seconds) and ignores any t_simulationTickScale value."); + + AZ_CVAR(int, t_simulationTickRate, 0, cvar_t_simulationTickRate_Changed, AZ::ConsoleFunctorFlags::Null, + "The minimum rate to force the game simulation tick to run. 0 for as fast as possible. 30 = ~33ms, 60 = ~16ms"); + + void TimeSystem::Reflect(AZ::ReflectContext* context) + { + if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->Version(1); + } + } + + TimeSystem::TimeSystem() + { + m_lastInvokedTimeUs = static_cast(AZStd::GetTimeNowMicroSecond()); + AZ::Interface::Register(this); + ITimeRequestBus::Handler::BusConnect(); + } + + TimeSystem::~TimeSystem() + { + AZ::Interface::Unregister(this); + ITimeRequestBus::Handler::BusDisconnect(); + } + + TimeMs TimeSystem::GetElapsedTimeMs() const + { + return AZ::TimeUsToMs(GetElapsedTimeUs()); + } + + TimeUs TimeSystem::GetElapsedTimeUs() const + { + TimeUs currentTime = static_cast(AZStd::GetTimeNowMicroSecond()); + TimeUs deltaTime = currentTime - m_lastInvokedTimeUs; + + if (t_simulationTickScale != 1.0f) + { + const float floatDelta = AZStd::GetMax(static_cast(deltaTime) * t_simulationTickScale, 1.0f); + deltaTime = static_cast(static_cast(floatDelta)); + } + + m_accumulatedTimeUs += deltaTime; + m_lastInvokedTimeUs = currentTime; + + return m_accumulatedTimeUs; + } + + TimeMs TimeSystem::GetRealElapsedTimeMs() const + { + return AZ::TimeUsToMs(GetRealElapsedTimeUs()); + } + + TimeUs TimeSystem::GetRealElapsedTimeUs() const + { + return static_cast(AZStd::GetTimeNowMicroSecond()); + } + + TimeUs TimeSystem::GetSimulationTickDeltaTimeUs() const + { + return m_simulationTickDeltaTimeUs; + } + + TimeUs TimeSystem::GetRealTickDeltaTimeUs() const + { + return m_realTickDeltaTimeUs; + } + + TimeUs TimeSystem::GetLastSimulationTickTime() const + { + return m_lastSimulationTickTimeUs; + } + + TimeUs TimeSystem::AdvanceTickDeltaTimes() + { + const TimeUs currentTimeUs = static_cast(AZStd::GetTimeNowMicroSecond()); + + //real time + m_realTickDeltaTimeUs = currentTimeUs - m_lastSimulationTickTimeUs; + + //game time + if (m_simulationTickDeltaOverride > AZ::Time::ZeroTimeUs) + { + m_simulationTickDeltaTimeUs = m_simulationTickDeltaOverride; + m_lastSimulationTickTimeUs = currentTimeUs; + return m_simulationTickDeltaTimeUs; + } + + m_simulationTickDeltaTimeUs = currentTimeUs - m_lastSimulationTickTimeUs; + + if (!AZ::IsClose(t_simulationTickScale, 1.0f)) + { + const double floatDelta = AZStd::GetMax(static_cast(m_simulationTickDeltaTimeUs) * static_cast(t_simulationTickScale), 1.0); + m_simulationTickDeltaTimeUs = static_cast(static_cast(floatDelta)); + } + m_lastSimulationTickTimeUs = currentTimeUs; + + return m_simulationTickDeltaTimeUs; + } + + void TimeSystem::ApplyTickRateLimiterIfNeeded() + { + // Currently disabling the Tick rate limiter as there are some reported issues when using it. + #ifdef ENABLE_TICK_RATE_LIMITER + // If tick rate limiting is on, ensure (1 / t_simulationTickRate) ms has elapsed since the last frame, + // sleeping if there's still time remaining. + if (t_simulationTickRate > 0) + { + const TimeUs currentTimeUs = AZ::GetRealElapsedTimeUs(); + const TimeUs timeUntilNextTick = (m_lastSimulationTickTimeUs + m_simulationTickLimitTimeUs) - currentTimeUs; + if (timeUntilNextTick > AZ::Time::ZeroTimeUs) + { + AZ_TracePrintf("tick", "Sleeping for %.2f", AZ::TimeUsToSecondsDouble(timeUntilNextTick)); + AZStd::this_thread::sleep_for(AZStd::chrono::microseconds(static_cast(timeUntilNextTick))); + } + } + #endif // #ifdef ENABLE_TICK_RATE_LIMITER + } + + void TimeSystem::SetSimulationTickDeltaOverride(TimeMs timeMs) + { + const TimeUs timeUs = AZ::TimeMsToUs(timeMs); + if (timeUs != m_simulationTickDeltaOverride) + { + m_simulationTickDeltaOverride = timeUs; + t_simulationTickDeltaOverride = AZ::TimeUsToSeconds(timeUs); //update the cvar + } + } + + TimeMs TimeSystem::GetSimulationTickDeltaOverride() const + { + return AZ::TimeUsToMs(m_simulationTickDeltaOverride); + } + + void TimeSystem::SetSimulationTickScale(float scale) + { + if (!AZ::IsClose(scale, t_simulationTickScale)) + { + t_simulationTickScale = scale; + } + } + + float TimeSystem::GetSimulationTickScale() const + { + return t_simulationTickScale; + } + + void TimeSystem::SetSimulationTickRate(int rate) + { + m_simulationTickLimitRate = AZStd::abs(rate); + if (m_simulationTickLimitRate != 0) + { + m_simulationTickLimitTimeUs = AZ::SecondsToTimeUs(1.0f / m_simulationTickLimitRate); + } + else + { + m_simulationTickLimitTimeUs = AZ::Time::ZeroTimeUs; + } + } + + int32_t TimeSystem::GetSimulationTickRate() const + { + return m_simulationTickLimitRate; + } +} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/Time/TimeSystem.h b/Code/Framework/AzCore/AzCore/Time/TimeSystem.h new file mode 100644 index 0000000000..ded70c9be5 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Time/TimeSystem.h @@ -0,0 +1,83 @@ +/* + * 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 +#include + +namespace AZ +{ + class ReflectContext; + + //! Implementation of the ITime system interface. + class TimeSystem + : public ITimeRequestBus::Handler + { + public: + AZ_RTTI(AZ::TimeSystem, "{CE1C5E4F-7DC1-4248-B10C-AC55E8924A48}", AZ::ITime); + + static void Reflect(AZ::ReflectContext* context); + + TimeSystem(); + virtual ~TimeSystem(); + + //! ITime overrides. + //! @{ + TimeMs GetElapsedTimeMs() const override; + TimeUs GetElapsedTimeUs() const override; + TimeMs GetRealElapsedTimeMs() const override; + TimeUs GetRealElapsedTimeUs() const override; + TimeUs GetSimulationTickDeltaTimeUs() const override; + TimeUs GetRealTickDeltaTimeUs() const override; + TimeUs GetLastSimulationTickTime() const override; + void SetSimulationTickDeltaOverride(TimeMs timeMs) override; + TimeMs GetSimulationTickDeltaOverride() const override; + void SetSimulationTickScale(float scale) override; + float GetSimulationTickScale() const override; + void SetSimulationTickRate(int rate) override; + int32_t GetSimulationTickRate() const override; + //! @} + + //! Advances the Simulation and Real tick delta time counters. + //! This is called from the owner of the TimeSystem, ComponentApplication in Tick(). + //! @return The delta in microseconds from the last call to AdvanceTickDeltaTimes(). Value will be the same as GetSimulationTickDeltaTimeUs(). + TimeUs AdvanceTickDeltaTimes(); + + //! If t_simulationTickRate is >0 this will try to have the game delta time run at a maximum of the rate set. + //! This is called from the owner of the TimeSystem, ComponentApplication in Tick(). + //! example. If t_simulationTickRate is set to 60Fps, and the game tick delta is <17ms(60fps), this will add a sleep for the remaining time. + //! example. If t_simulationTickRate is set to 60Fps, and the game tick delta is >=17ms(60fps), this will not sleep at all. + //! @note It is not guaranteed to hit the requested tick rate exactly. + void ApplyTickRateLimiterIfNeeded(); + private: + //! Used to calculate the delta time between calls to GetElapsedTimeMs/TimeUs(). + //! Mutable to allow GetElapsedTimeMs/TimeUs() to be a const functions. + mutable TimeUs m_lastInvokedTimeUs = AZ::Time::ZeroTimeUs; + + //! Accumulates the delta time of GetElapsedTimeMs/TimeUs() calls. + //! Mutable to allow GetElapsedTimeMs/TimeUs() to be a const functions. + mutable TimeUs m_accumulatedTimeUs = AZ::Time::ZeroTimeUs; + + //! The current game tick delta time. + //! Can be affected by time system cvars. + //! Updated in AdvanceTickDeltaTimes(). + TimeUs m_simulationTickDeltaTimeUs = AZ::Time::ZeroTimeUs; + + //! The current real tick delta time. + //! Will not be affected by time system cvars. + //! Updated in AdvanceTickDeltaTimes(). + TimeUs m_realTickDeltaTimeUs = AZ::Time::ZeroTimeUs; + + TimeUs m_lastSimulationTickTimeUs = AZ::Time::ZeroTimeUs; //!< Used to determine the game tick delta time. + + TimeUs m_simulationTickDeltaOverride = AZ::Time::ZeroTimeUs; // -#include -#include - -namespace AZ -{ - AZ_CVAR(float, t_scale, 1.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "A scalar amount to adjust time passage by, 1.0 == realtime, 0.5 == half realtime, 2.0 == doubletime"); - - void TimeSystemComponent::Reflect(AZ::ReflectContext* context) - { - if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) - { - serializeContext->Class() - ->Version(1); - } - } - - void TimeSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) - { - provided.push_back(AZ_CRC_CE("TimeService")); - } - - void TimeSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) - { - incompatible.push_back(AZ_CRC_CE("TimeService")); - } - - TimeSystemComponent::TimeSystemComponent() - { - m_lastInvokedTimeUs = static_cast(AZStd::GetTimeNowMicroSecond()); - AZ::Interface::Register(this); - ITimeRequestBus::Handler::BusConnect(); - } - - TimeSystemComponent::~TimeSystemComponent() - { - ITimeRequestBus::Handler::BusDisconnect(); - AZ::Interface::Unregister(this); - } - - void TimeSystemComponent::Activate() - { - ; - } - - void TimeSystemComponent::Deactivate() - { - ; - } - - TimeMs TimeSystemComponent::GetElapsedTimeMs() const - { - return TimeUsToMs(GetElapsedTimeUs()); - } - - TimeUs TimeSystemComponent::GetElapsedTimeUs() const - { - TimeUs currentTime = static_cast(AZStd::GetTimeNowMicroSecond()); - TimeUs deltaTime = currentTime - m_lastInvokedTimeUs; - - if (t_scale != 1.0f) - { - float floatDelta = static_cast(deltaTime) * t_scale; - deltaTime = static_cast(static_cast(floatDelta)); - } - - m_accumulatedTimeUs += deltaTime; - m_lastInvokedTimeUs = currentTime; - - return m_accumulatedTimeUs; - } -} diff --git a/Code/Framework/AzCore/AzCore/Time/TimeSystemComponent.h b/Code/Framework/AzCore/AzCore/Time/TimeSystemComponent.h deleted file mode 100644 index 3ab3dbc234..0000000000 --- a/Code/Framework/AzCore/AzCore/Time/TimeSystemComponent.h +++ /dev/null @@ -1,50 +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 -#include -#include - -namespace AZ -{ - //! Implementation of the ITime system interface. - class TimeSystemComponent - : public AZ::Component - , public ITimeRequestBus::Handler - { - public: - - AZ_COMPONENT(TimeSystemComponent, "{CE1C5E4F-7DC1-4248-B10C-AC55E8924A48}"); - - static void Reflect(AZ::ReflectContext* context); - static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); - static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); - - TimeSystemComponent(); - virtual ~TimeSystemComponent(); - - //! AZ::Component overrides. - //! @{ - void Activate() override; - void Deactivate() override; - //! @} - - //! ITime overrides. - //! @{ - TimeMs GetElapsedTimeMs() const override; - TimeUs GetElapsedTimeUs() const override; - //! @} - - private: - - mutable TimeUs m_lastInvokedTimeUs = TimeUs{0}; - mutable TimeUs m_accumulatedTimeUs = TimeUs{0}; - }; -} diff --git a/Code/Framework/AzCore/AzCore/UnitTest/Mocks/MockITime.h b/Code/Framework/AzCore/AzCore/UnitTest/Mocks/MockITime.h new file mode 100644 index 0000000000..3d31056e27 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/UnitTest/Mocks/MockITime.h @@ -0,0 +1,119 @@ +/* + * 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 +#include + +namespace AZ +{ + class MockTimeSystem; + using NiceTimeSystemMock =::testing::NiceMock; + + //used if you wish to mock any of the Get time functions. + class MockTimeSystem + : public ITimeRequestBus::Handler + { + public: + MockTimeSystem() + { + AZ::Interface::Register(this); + ITimeRequestBus::Handler::BusConnect(); + } + virtual ~MockTimeSystem() + { + AZ::Interface::Unregister(this); + ITimeRequestBus::Handler::BusDisconnect(); + } + + MOCK_CONST_METHOD0(GetElapsedTimeMs, TimeMs()); + MOCK_CONST_METHOD0(GetElapsedTimeUs, TimeUs()); + MOCK_CONST_METHOD0(GetRealElapsedTimeMs, TimeMs()); + MOCK_CONST_METHOD0(GetRealElapsedTimeUs, TimeUs()); + MOCK_CONST_METHOD0(GetSimulationTickDeltaTimeUs, TimeUs()); + MOCK_CONST_METHOD0(GetRealTickDeltaTimeUs, TimeUs()); + MOCK_CONST_METHOD0(GetLastSimulationTickTime, TimeUs()); + MOCK_METHOD1(SetSimulationTickDeltaOverride, void(TimeMs)); + MOCK_CONST_METHOD0(GetSimulationTickDeltaOverride, TimeMs()); + MOCK_METHOD1(SetSimulationTickScale, void(float)); + MOCK_CONST_METHOD0(GetSimulationTickScale, float()); + MOCK_METHOD1(SetSimulationTickRate, void(int)); + MOCK_CONST_METHOD0(GetSimulationTickRate, int32_t()); + }; + + //used if you wish to override any of the Get time functions with specific functionality. + class StubTimeSystem + : public AZ::TimeSystem + { + public: + AZ_RTTI(AZ::StubTimeSystem, "{DD5D5A6A-345F-49FD-A61E-A40E63C49CFA}", AZ::TimeSystem); + + virtual AZ::TimeMs GetElapsedTimeMs() const override + { + return AZ::Time::ZeroTimeMs; + } + + virtual AZ::TimeUs GetElapsedTimeUs() const override + { + return AZ::Time::ZeroTimeUs; + } + + virtual AZ::TimeMs GetRealElapsedTimeMs() const override + { + return AZ::Time::ZeroTimeMs; + } + + virtual AZ::TimeUs GetRealElapsedTimeUs() const override + { + return AZ::Time::ZeroTimeUs; + } + + virtual AZ::TimeUs GetSimulationTickDeltaTimeUs() const override + { + return AZ::Time::ZeroTimeUs; + } + + virtual AZ::TimeUs GetRealTickDeltaTimeUs() const override + { + return AZ::Time::ZeroTimeUs; + } + + virtual AZ::TimeUs GetLastSimulationTickTime() const override + { + return AZ::Time::ZeroTimeUs; + } + + virtual void SetSimulationTickDeltaOverride([[maybe_unused]]TimeMs timeMs) override + { + } + + virtual TimeMs GetSimulationTickDeltaOverride() const override + { + return AZ::Time::ZeroTimeMs; + } + + virtual void SetSimulationTickScale([[maybe_unused]] float scale) override + { + } + + virtual float GetSimulationTickScale() const override + { + return 1.0f; + } + + virtual void SetSimulationTickRate([[maybe_unused]] int rate) override + { + } + + virtual int32_t GetSimulationTickRate() const override + { + return 0; + } + }; + +} // namespace AZ diff --git a/Code/Framework/AzCore/AzCore/UnitTest/TestTypes.h b/Code/Framework/AzCore/AzCore/UnitTest/TestTypes.h index e5dcb32d9d..afa6898944 100644 --- a/Code/Framework/AzCore/AzCore/UnitTest/TestTypes.h +++ b/Code/Framework/AzCore/AzCore/UnitTest/TestTypes.h @@ -13,8 +13,6 @@ #include #include -#include -#include #include #if defined(HAVE_BENCHMARK) @@ -39,7 +37,6 @@ namespace UnitTest */ class AllocatorsBase { - AZ::Debug::DrillerManager* m_drillerManager; bool m_ownsAllocator{}; public: @@ -47,8 +44,7 @@ namespace UnitTest void SetupAllocator(const AZ::SystemAllocator::Descriptor& allocatorDesc = {}) { - m_drillerManager = AZ::Debug::DrillerManager::Create(); - m_drillerManager->Register(aznew AZ::Debug::MemoryDriller); + AZ::AllocatorManager::Instance().EnterProfilingMode(); AZ::AllocatorManager::Instance().SetDefaultTrackingMode(AZ::Debug::AllocationRecords::RECORD_FULL); // Only create the SystemAllocator if it s not ready @@ -68,9 +64,9 @@ namespace UnitTest AZ::AllocatorInstance::Destroy(); } m_ownsAllocator = false; - AZ::Debug::DrillerManager::Destroy(m_drillerManager); AZ::AllocatorManager::Instance().SetDefaultTrackingMode(AZ::Debug::AllocationRecords::RECORD_NO_RECORDS); + AZ::AllocatorManager::Instance().ExitProfilingMode(); } }; @@ -93,8 +89,7 @@ namespace UnitTest * Helper class to handle the boiler plate of setting up a test fixture that uses the system allocators * If you wish to do additional setup and tear down be sure to call the base class SetUp first and TearDown * last. - * By default memory tracking through driller is enabled. - * Defaults to a heap size of 15 MB + * By default memory tracking is enabled. */ class AllocatorsTestFixture @@ -123,8 +118,7 @@ namespace UnitTest * Helper class to handle the boiler plate of setting up a benchmark fixture that uses the system allocators * If you wish to do additional setup and tear down be sure to call the base class SetUp first and TearDown * last. - * By default memory tracking through driller is disabled. - * Defaults to a heap size of 15 MB + * By default memory tracking is enabled. */ class AllocatorsBenchmarkFixture : public ::benchmark::Fixture diff --git a/Code/Framework/AzCore/AzCore/UnitTest/UnitTest.h b/Code/Framework/AzCore/AzCore/UnitTest/UnitTest.h index 5244493c78..be9199b6c4 100644 --- a/Code/Framework/AzCore/AzCore/UnitTest/UnitTest.h +++ b/Code/Framework/AzCore/AzCore/UnitTest/UnitTest.h @@ -60,6 +60,7 @@ namespace UnitTest m_isAssertTest = true; m_numAssertsFailed = 0; } + int StopAssertTests() { m_isAssertTest = false; @@ -69,6 +70,11 @@ namespace UnitTest } bool m_isAssertTest; + bool m_suppressErrors = true; + bool m_suppressWarnings = true; + bool m_suppressAsserts = true; + bool m_suppressOutput = true; + bool m_suppressPrintf = true; int m_numAssertsFailed; }; @@ -114,7 +120,7 @@ namespace UnitTest // utility classes that you can derive from or contain, which suppress AZ_Asserts // and AZ_Errors to the below macros (processAssert, etc) - // If TraceBusHook or TraceBusRedirector have been started in your unit tests, + // If TraceBusHook or TraceBusRedirector have been started in your unit tests, // use AZ_TEST_START_TRACE_SUPPRESSION and AZ_TEST_STOP_TRACE_SUPPRESSION(numExpectedAsserts) macros to perform AZ_Assert and AZ_Error suppression class TraceBusRedirector : public AZ::Debug::TraceMessageBus::Handler @@ -124,16 +130,19 @@ namespace UnitTest if (UnitTest::TestRunner::Instance().m_isAssertTest) { UnitTest::TestRunner::Instance().ProcessAssert(message, file, line, false); + return true; } - else + else if (UnitTest::TestRunner::Instance().m_suppressAsserts) { GTEST_MESSAGE_AT_(file, line, message, ::testing::TestPartResult::kNonFatalFailure); + return true; } - return true; + + return false; } bool OnAssert(const char* /*message*/) override { - return true; // stop processing + return UnitTest::TestRunner::Instance().m_suppressAsserts; // stop processing } bool OnPreError(const char* /*window*/, const char* file, int line, const char* /*func*/, const char* message) override { @@ -142,6 +151,7 @@ namespace UnitTest UnitTest::TestRunner::Instance().ProcessAssert(message, file, line, false); return true; } + return false; } bool OnError(const char* /*window*/, const char* message) override @@ -149,12 +159,15 @@ namespace UnitTest if (UnitTest::TestRunner::Instance().m_isAssertTest) { UnitTest::TestRunner::Instance().ProcessAssert(message, __FILE__, __LINE__, UnitTest::AssertionExpr(false)); + return true; } - else + else if (UnitTest::TestRunner::Instance().m_suppressErrors) { GTEST_MESSAGE_(message, ::testing::TestPartResult::kNonFatalFailure); + return true; } - return true; // stop processing + + return false; } bool OnPreWarning(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override { @@ -163,21 +176,21 @@ namespace UnitTest } bool OnWarning(const char* /*window*/, const char* /*message*/) override { - return true; + return UnitTest::TestRunner::Instance().m_suppressWarnings; } bool OnOutput(const char* /*window*/, const char* /*message*/) override { - return true; + return UnitTest::TestRunner::Instance().m_suppressOutput; } bool OnPrintf(const char* window, const char* message) override { if (AZStd::string_view(window) == "Memory") // We want to print out the memory leak's stack traces { - ColoredPrintf(COLOR_RED, "[ MEMORY ] %s", message); + ColoredPrintf(COLOR_RED, "[ MEMORY ] %s", message); } - return true; + return UnitTest::TestRunner::Instance().m_suppressPrintf; } }; @@ -259,7 +272,6 @@ namespace UnitTest bool m_environmentSetup = false; bool m_createdAllocator = false; }; - } diff --git a/Code/Framework/AzCore/AzCore/Utils/Utils.cpp b/Code/Framework/AzCore/AzCore/Utils/Utils.cpp index 12c6473905..1623aca56b 100644 --- a/Code/Framework/AzCore/AzCore/Utils/Utils.cpp +++ b/Code/Framework/AzCore/AzCore/Utils/Utils.cpp @@ -165,13 +165,12 @@ namespace AZ::Utils } Container fileContent; - fileContent.resize(length); + fileContent.resize_no_construct(length); AZ::IO::SizeType bytesRead = file.Read(length, fileContent.data()); file.Close(); // Resize again just in case bytesRead is less than length for some reason - fileContent.resize(bytesRead); - + fileContent.resize_no_construct(bytesRead); return AZ::Success(AZStd::move(fileContent)); } diff --git a/Code/Framework/AzCore/AzCore/azcore_files.cmake b/Code/Framework/AzCore/AzCore/azcore_files.cmake index 0c5a360844..e8001f7215 100644 --- a/Code/Framework/AzCore/AzCore/azcore_files.cmake +++ b/Code/Framework/AzCore/AzCore/azcore_files.cmake @@ -92,10 +92,6 @@ set(FILES Compression/Compression.h Compression/zstd_compression.cpp Compression/zstd_compression.h - Debug/AssetTracking.cpp - Debug/AssetTracking.h - Debug/AssetTrackingTypesImpl.h - Debug/AssetTrackingTypes.h Debug/Budget.h Debug/Budget.cpp Debug/BudgetTracker.h @@ -108,29 +104,24 @@ set(FILES Debug/Profiler.inl Debug/Profiler.h Debug/ProfilerBus.h + Debug/ProfilerReflection.cpp + Debug/ProfilerReflection.h Debug/StackTracer.h - Debug/EventTrace.h - Debug/EventTrace.cpp - Debug/EventTraceDriller.h - Debug/EventTraceDriller.cpp - Debug/EventTraceDrillerBus.h Debug/Timer.h Debug/Trace.cpp Debug/Trace.h Debug/TraceMessageBus.h - Debug/TraceMessagesDriller.cpp - Debug/TraceMessagesDriller.h - Debug/TraceMessagesDrillerBus.h Debug/TraceReflection.cpp Debug/TraceReflection.h - Driller/DefaultStringPool.h - Driller/Driller.cpp - Driller/Driller.h - Driller/DrillerBus.cpp - Driller/DrillerBus.h - Driller/DrillerRootHandler.h - Driller/Stream.cpp - Driller/Stream.h + DOM/DomBackend.cpp + DOM/DomBackend.h + DOM/DomUtils.cpp + DOM/DomUtils.h + DOM/DomVisitor.cpp + DOM/DomVisitor.h + DOM/Backends/JSON/JsonBackend.h + DOM/Backends/JSON/JsonSerializationUtils.cpp + DOM/Backends/JSON/JsonSerializationUtils.h EBus/BusImpl.h EBus/EBus.h EBus/EBusEnvironment.cpp @@ -167,7 +158,6 @@ set(FILES IO/CompressorZStd.h IO/FileIO.cpp IO/FileIO.h - IO/FileIOEventBus.h IO/FileReader.cpp IO/FileReader.h IO/IOUtils.h @@ -183,6 +173,8 @@ set(FILES IO/Path/Path.inl IO/Path/PathIterable.inl IO/Path/PathParser.inl + IO/Path/PathReflect.cpp + IO/Path/PathReflect.h IO/Path/Path_fwd.h IO/SystemFile.cpp IO/SystemFile.h @@ -391,16 +383,12 @@ set(FILES Memory/Memory.h Memory/MemoryComponent.cpp Memory/MemoryComponent.h - Memory/MemoryDriller.cpp - Memory/MemoryDriller.h - Memory/MemoryDrillerBus.h Memory/nedmalloc.inl Memory/NewAndDelete.inl Memory/OSAllocator.cpp Memory/OSAllocator.h Memory/OverrunDetectionAllocator.cpp Memory/OverrunDetectionAllocator.h - Memory/PlatformMemoryInstrumentation.h Memory/PoolAllocator.h Memory/PoolSchema.cpp Memory/PoolSchema.h @@ -454,6 +442,7 @@ set(FILES RTTI/BehaviorContext.h RTTI/BehaviorContextUtilities.h RTTI/BehaviorContextUtilities.cpp + RTTI/BehaviorInterfaceProxy.h RTTI/BehaviorObjectSignals.h RTTI/TypeSafeIntegral.h Script/ScriptAsset.cpp @@ -649,8 +638,8 @@ set(FILES Threading/ThreadUtils.h Threading/ThreadUtils.cpp Time/ITime.h - Time/TimeSystemComponent.cpp - Time/TimeSystemComponent.h + Time/TimeSystem.cpp + Time/TimeSystem.h ) # Prevent the following files from being grouped in UNITY builds diff --git a/Code/Framework/AzCore/AzCore/azcoretestcommon_files.cmake b/Code/Framework/AzCore/AzCore/azcoretestcommon_files.cmake index 6c25641f9f..e31dc803b9 100644 --- a/Code/Framework/AzCore/AzCore/azcoretestcommon_files.cmake +++ b/Code/Framework/AzCore/AzCore/azcoretestcommon_files.cmake @@ -12,5 +12,6 @@ set(FILES UnitTest/UnitTest.h UnitTest/TestTypes.h UnitTest/Mocks/MockFileIOBase.h + UnitTest/Mocks/MockITime.h UnitTest/Mocks/MockSettingsRegistry.h ) diff --git a/Code/Framework/AzCore/AzCore/std/containers/deque.h b/Code/Framework/AzCore/AzCore/std/containers/deque.h index db585e0ec9..9eed66aeb3 100644 --- a/Code/Framework/AzCore/AzCore/std/containers/deque.h +++ b/Code/Framework/AzCore/AzCore/std/containers/deque.h @@ -1233,6 +1233,14 @@ namespace AZStd right.swap(AZStd::forward(left)); } + template + decltype(auto) erase(deque& container, const U& value) + { + auto iter = AZStd::remove(container.begin(), container.end(), value); + auto removedCount = AZStd::distance(iter, container.end()); + container.erase(iter, container.end()); + return removedCount; + } template decltype(auto) erase_if(deque& container, Predicate predicate) { diff --git a/Code/Framework/AzCore/AzCore/std/containers/fixed_vector.h b/Code/Framework/AzCore/AzCore/std/containers/fixed_vector.h index 7c0cca0308..82faaab2d8 100644 --- a/Code/Framework/AzCore/AzCore/std/containers/fixed_vector.h +++ b/Code/Framework/AzCore/AzCore/std/containers/fixed_vector.h @@ -130,10 +130,11 @@ namespace AZStd::Internal //! Constructors - constexpr fixed_trivial_storage() = default; + fixed_trivial_storage() = default; template >> - constexpr fixed_trivial_storage(AZStd::initializer_list ilist) noexcept + fixed_trivial_storage(AZStd::initializer_list ilist) noexcept + : m_size(aznumeric_caster(ilist.size())) { AZSTD_CONTAINER_ASSERT(ilist.size() <= capacity(), "Initializer list cannot be larger than storage capacity"); size_t index{}; @@ -141,20 +142,19 @@ namespace AZStd::Internal { m_data[index++] = element; } - resize_no_construct(ilist.size()); } - constexpr pointer data() noexcept + pointer data() noexcept { return m_data; } - constexpr const_pointer data() const noexcept + const_pointer data() const noexcept { return m_data; } //! Number of elements currently stored. - constexpr size_type size() const noexcept + size_type size() const noexcept { return m_size; } @@ -164,12 +164,12 @@ namespace AZStd::Internal return Capacity; } //! Is the storage empty? - constexpr bool empty() const noexcept + bool empty() const noexcept { return size() == 0; } //! Is the storage full? - constexpr bool full() const noexcept + bool full() const noexcept { return size() == capacity(); } @@ -186,7 +186,7 @@ namespace AZStd::Internal //! Increases size of the storage by one. //! Always fails for empty storage. template >> - constexpr reference emplace_back(Args&&... args) noexcept + reference emplace_back(Args&&... args) noexcept { AZSTD_CONTAINER_ASSERT(!full(), "emplace_back cannot be invoked on full storage"); reference new_element = *(data() + size()); @@ -196,7 +196,7 @@ namespace AZStd::Internal } //! Removes the last element of the storage. //! Precondition: size is not empty - constexpr void pop_back() noexcept + void pop_back() noexcept { AZSTD_CONTAINER_ASSERT(!empty(), "pop_back cannot be invoked on empty storage"); resize_no_construct(size() - 1); @@ -205,7 +205,7 @@ namespace AZStd::Internal //! removing elements (unsafe). //! //! Updates the size of the container while checking that the new size is less than capacity - constexpr void resize_no_construct(size_t new_size) noexcept + void resize_no_construct(size_t new_size) noexcept { AZSTD_CONTAINER_ASSERT(new_size <= capacity(), "New size cannot be larger than capacity"); m_size = aznumeric_cast(new_size); @@ -215,19 +215,19 @@ namespace AZStd::Internal //! This does not modify the size of the storage //! This is a no-op for trivial types template >> - constexpr void unsafe_destroy(InputIt, InputIt) noexcept + void unsafe_destroy(InputIt, InputIt) noexcept { } //! Destructs all elements of the storage. //! This does not modify the size of the storage //! This is a no-op for trivial types - constexpr void unsafe_destroy_all() noexcept + void unsafe_destroy_all() noexcept { } private: - T m_data[Capacity]{}; + T m_data[Capacity]; size_type m_size{}; }; @@ -245,7 +245,7 @@ namespace AZStd::Internal using reference = T&; using const_reference = const T&; - constexpr fixed_non_trivial_storage() = default; + fixed_non_trivial_storage() = default; ~fixed_non_trivial_storage() noexcept { @@ -253,7 +253,7 @@ namespace AZStd::Internal } template >> - constexpr fixed_non_trivial_storage(AZStd::initializer_list ilist) noexcept(noexcept(emplace_back(AZStd::declval()))) + fixed_non_trivial_storage(AZStd::initializer_list ilist) noexcept(noexcept(emplace_back(AZStd::declval()))) { AZSTD_CONTAINER_ASSERT(ilist.size() <= capacity(), "Initializer list cannot be larger than storage capacity"); for (const U& element : ilist) @@ -272,7 +272,7 @@ namespace AZStd::Internal } //! Number of elements currently stored. - constexpr size_type size() const noexcept + size_type size() const noexcept { return m_size; } @@ -282,12 +282,12 @@ namespace AZStd::Internal return Capacity; } //! Is the storage empty? - constexpr bool empty() const noexcept + bool empty() const noexcept { return size() == 0; } //! Is the storage full? - constexpr bool full() const noexcept + bool full() const noexcept { return size() == capacity(); } @@ -325,7 +325,7 @@ namespace AZStd::Internal //! removing elements (unsafe). //! //! Updates the size of the container while checking that the new size is less than capacity - constexpr void resize_no_construct(size_t new_size) noexcept + void resize_no_construct(size_t new_size) noexcept { AZSTD_CONTAINER_ASSERT(new_size <= capacity(), "New size cannot be larger than capacity"); m_size = aznumeric_cast(new_size); @@ -402,23 +402,23 @@ namespace AZStd ////////////////////////////////////////////////////////////////////////// // 23.2.4.1 construct/copy/destroy - constexpr fixed_vector() = default; + fixed_vector() = default; - constexpr explicit fixed_vector(size_type numElements, const_reference value = value_type()) + explicit fixed_vector(size_type numElements, const_reference value = value_type()) { resize_no_construct(numElements); AZStd::uninitialized_fill_n(data(), numElements, value); } template >> - constexpr fixed_vector(InputIt first, InputIt last) + fixed_vector(InputIt first, InputIt last) { resize_no_construct(AZStd::distance(first, last)); AZStd::uninitialized_copy(first, last, data()); } - constexpr fixed_vector(const fixed_vector& rhs) + fixed_vector(const fixed_vector& rhs) { resize_no_construct(rhs.size()); AZStd::uninitialized_copy(rhs.data(), rhs.data() + rhs.size(), data()); @@ -428,7 +428,7 @@ namespace AZStd // It performs an AZStd::move on each of the fixed_vector elements instead // of swapping pointers to the allocted memory address // as it is unable to perform that operations due to the storage being baked into the container - constexpr fixed_vector(fixed_vector&& rhs) + fixed_vector(fixed_vector&& rhs) { resize_no_construct(rhs.size()); AZStd::uninitialized_move(rhs.data(), rhs.data() + rhs.size(), data()); @@ -440,7 +440,7 @@ namespace AZStd // into a fixed_vector given that the type in question isn't the same type as this fixed_vector type template && !AZStd::is_convertible_v>> - constexpr fixed_vector(VectorContainer&& rhs) + fixed_vector(VectorContainer&& rhs) { constexpr bool is_const_or_lvalue_reference = AZStd::is_lvalue_reference_v || AZStd::is_const_v; @@ -459,12 +459,12 @@ namespace AZStd } } - constexpr fixed_vector(AZStd::initializer_list ilist) + fixed_vector(AZStd::initializer_list ilist) : base_type(ilist) { } - constexpr fixed_vector& operator=(const fixed_vector& rhs) + fixed_vector& operator=(const fixed_vector& rhs) { if (this == &rhs) { @@ -475,7 +475,7 @@ namespace AZStd return assign_helper(rhs); } - constexpr fixed_vector& operator=(fixed_vector&& rhs) + fixed_vector& operator=(fixed_vector&& rhs) { if (this == &rhs) { @@ -487,23 +487,23 @@ namespace AZStd } template - constexpr AZStd::enable_if_t, fixed_vector>, fixed_vector>& operator=(VectorContainer&& rhs) + AZStd::enable_if_t, fixed_vector>, fixed_vector>& operator=(VectorContainer&& rhs) { return assign_helper(AZStd::forward(rhs)); } - constexpr iterator begin() { return iterator(data()); } - constexpr const_iterator begin() const { return const_iterator(data()); } - constexpr const_iterator cbegin() const { return const_iterator(data()); } - constexpr iterator end() { return iterator(data() + size()); } - constexpr const_iterator end() const { return const_iterator(data() + size()); } - constexpr const_iterator cend() const { return const_iterator(data() + size()); } - constexpr reverse_iterator rbegin() { return reverse_iterator(end()); } - constexpr const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } - constexpr const_reverse_iterator crbegin() const { return const_reverse_iterator(end()); } - constexpr reverse_iterator rend() { return reverse_iterator(begin()); } - constexpr const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } - constexpr const_reverse_iterator crend() const { return const_reverse_iterator(begin()); } + iterator begin() { return iterator(data()); } + const_iterator begin() const { return const_iterator(data()); } + const_iterator cbegin() const { return const_iterator(data()); } + iterator end() { return iterator(data() + size()); } + const_iterator end() const { return const_iterator(data() + size()); } + const_iterator cend() const { return const_iterator(data() + size()); } + reverse_iterator rbegin() { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } + const_reverse_iterator crbegin() const { return const_reverse_iterator(end()); } + reverse_iterator rend() { return reverse_iterator(begin()); } + const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } + const_reverse_iterator crend() const { return const_reverse_iterator(begin()); } // bring in fixed_vector_storage functions into scope using base_type::data; @@ -514,7 +514,7 @@ namespace AZStd // extension method using base_type::resize_no_construct; - constexpr size_type size() const noexcept + size_type size() const noexcept { return base_type::size(); } @@ -527,12 +527,12 @@ namespace AZStd return base_type::max_size(); } - constexpr void resize(size_type newSize) + void resize(size_type newSize) { return resize(newSize, value_type{}); } - constexpr void resize(size_type newSize, const_reference value) + void resize(size_type newSize, const_reference value) { size_type dataSize = size(); if (dataSize < newSize) @@ -547,7 +547,7 @@ namespace AZStd // Removes unused capacity - For fixed_vector this only asserts // that the supplied capacity is not longer than the fixed_vector capacity - constexpr void reserve(size_type newCapacity) + void reserve(size_type newCapacity) { // No-op - Implemented to provide consistent std::vector AZSTD_CONTAINER_ASSERT(newCapacity <= capacity(), @@ -556,79 +556,79 @@ namespace AZStd } // Removes unused capacity - For fixed_vector this does nothing - constexpr void shrink_to_fit() + void shrink_to_fit() { // No-op - Implemented to provide consistent std::vector } - constexpr reference at(size_type position) + reference at(size_type position) { AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range"); return *(data() + position); } - constexpr const_reference at(size_type position) const + const_reference at(size_type position) const { AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range"); return *(data() + position); } - constexpr reference operator[](size_type position) + reference operator[](size_type position) { AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range"); return *(data() + position); } - constexpr const_reference operator[](size_type position) const + const_reference operator[](size_type position) const { AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range"); return *(data() + position); } - constexpr reference front() + reference front() { AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::front - container is empty!"); return *data(); } - constexpr const_reference front() const + const_reference front() const { AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::front - container is empty!"); return *data(); } - constexpr reference back() + reference back() { AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::back - container is empty!"); return *(data() + size() - 1); } - constexpr const_reference back() const + const_reference back() const { AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::back - container is empty!"); return *(data() + size() - 1); } - constexpr void push_back(const_reference value) + void push_back(const_reference value) { emplace_back(value); } - constexpr void assign(size_type numElements, const_reference value) + void assign(size_type numElements, const_reference value) { clear(); insert(end(), numElements, value); } template >> - constexpr void assign(InputIt first, InputIt last) + void assign(InputIt first, InputIt last) { clear(); insert(end(), first, last); } - constexpr void assign(AZStd::initializer_list ilist) + void assign(AZStd::initializer_list ilist) { assign(ilist.begin(), ilist.end()); } template >> - constexpr iterator emplace(const_iterator insertPos, Args&&... args) + iterator emplace(const_iterator insertPos, Args&&... args) { AZSTD_CONTAINER_ASSERT(!full(), "Cannot emplace on a full fixed_vector"); AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container"); @@ -645,18 +645,18 @@ namespace AZStd AZStd::construct_at(insertPosPtr, AZStd::forward(args)...); return iterator(insertPosPtr); } - constexpr iterator insert(const_iterator insertPos, const_reference value) + iterator insert(const_iterator insertPos, const_reference value) { AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container"); return emplace(insertPos, value); } - constexpr iterator insert(const_iterator insertPos, value_type&& value) + iterator insert(const_iterator insertPos, value_type&& value) { AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container"); return emplace(insertPos, AZStd::move(value)); } - constexpr void insert(const_iterator insertPos, size_type numElements, const_reference value) + void insert(const_iterator insertPos, size_type numElements, const_reference value) { if (numElements == 0) { @@ -708,24 +708,24 @@ namespace AZStd } template>> - constexpr void insert(const_iterator insertPos, InputIt first, InputIt last) + void insert(const_iterator insertPos, InputIt first, InputIt last) { // specialize for iterator categories. AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container"); insert_iter(insertPos, first, last, typename iterator_traits::iterator_category()); }; - constexpr void insert(const_iterator insertPos, AZStd::initializer_list ilist) + void insert(const_iterator insertPos, AZStd::initializer_list ilist) { insert(insertPos, ilist.begin(), ilist.end()); } - constexpr iterator erase(const_iterator elementIter) + iterator erase(const_iterator elementIter) { return erase(elementIter, elementIter + 1); } - constexpr iterator erase(const_iterator first, const_iterator last) + iterator erase(const_iterator first, const_iterator last) { AZSTD_CONTAINER_ASSERT(first >= cbegin() && last <= cend(), "erase iterator must be inside the range of fixed_vector container"); iterator dataStart = begin(); @@ -741,12 +741,12 @@ namespace AZStd return dataStart + offset; } - constexpr void clear() + void clear() { base_type::unsafe_destroy_all(); resize_no_construct(0); } - constexpr void swap(fixed_vector& rhs) + void swap(fixed_vector& rhs) { // Fixed containers cannot swap pointers, they need to do full copies. // The strategy is to extend the smaller fixed_vector to be the size @@ -776,12 +776,12 @@ namespace AZStd } // Validate container status. - constexpr bool validate() const + bool validate() const { return size() <= max_size(); } // Validate iterator. - constexpr int validate_iterator(const_iterator iter) const + int validate_iterator(const_iterator iter) const { const_pointer start = data(); const_pointer end = data() + size(); @@ -799,19 +799,19 @@ namespace AZStd } // pushes back an empty without a provided instance. - constexpr void push_back() + void push_back() { emplace_back(); } - constexpr void leak_and_reset() + void leak_and_reset() { resize_no_construct(0); } private: template - constexpr fixed_vector& assign_helper(VectorContainer&& rhs) + fixed_vector& assign_helper(VectorContainer&& rhs) { constexpr bool is_const_or_lvalue_reference = AZStd::is_lvalue_reference_v || AZStd::is_const_v; @@ -872,7 +872,7 @@ namespace AZStd } template - constexpr void insert_iter(const_iterator insertPos, Iterator first, Iterator last, const forward_iterator_tag&) + void insert_iter(const_iterator insertPos, Iterator first, Iterator last, const forward_iterator_tag&) { size_type numElements = AZStd::distance(first, last); if (numElements == 0) @@ -923,7 +923,7 @@ namespace AZStd } template - constexpr void insert_iter(const_iterator insertPos, Iterator first, Iterator last, const input_iterator_tag&) + void insert_iter(const_iterator insertPos, Iterator first, Iterator last, const input_iterator_tag&) { iterator dataStart = data(); size_type offset = AZStd::distance(dataStart, insertPos); @@ -974,4 +974,22 @@ namespace AZStd { return !operator<(a, b); } + + // C++20 erase free functions + template + constexpr decltype(auto) erase(fixed_vector& container, const U& value) + { + auto iter = AZStd::remove(container.begin(), container.end(), value); + auto removedCount = AZStd::distance(iter, container.end()); + container.erase(iter, container.end()); + return removedCount; + } + template + constexpr decltype(auto) erase_if(fixed_vector& container, Predicate predicate) + { + auto iter = AZStd::remove_if(container.begin(), container.end(), predicate); + auto removedCount = AZStd::distance(iter, container.end()); + container.erase(iter, container.end()); + return removedCount; + } } diff --git a/Code/Framework/AzCore/AzCore/std/containers/forward_list.h b/Code/Framework/AzCore/AzCore/std/containers/forward_list.h index 407d65ffa9..9d7eebbe0e 100644 --- a/Code/Framework/AzCore/AzCore/std/containers/forward_list.h +++ b/Code/Framework/AzCore/AzCore/std/containers/forward_list.h @@ -1275,6 +1275,11 @@ namespace AZStd return !(left == right); } + template + decltype(auto) erase(forward_list& container, const U& value) + { + return container.remove(value); + } template decltype(auto) erase_if(forward_list& container, Predicate predicate) { diff --git a/Code/Framework/AzCore/AzCore/std/containers/list.h b/Code/Framework/AzCore/AzCore/std/containers/list.h index 60d2977749..51bc62f444 100644 --- a/Code/Framework/AzCore/AzCore/std/containers/list.h +++ b/Code/Framework/AzCore/AzCore/std/containers/list.h @@ -1340,6 +1340,11 @@ namespace AZStd return !(left == right); } + template + decltype(auto) erase(list& container, const U& value) + { + return container.remove(value); + } template decltype(auto) erase_if(list& container, Predicate predicate) { diff --git a/Code/Framework/AzCore/AzCore/std/containers/vector.h b/Code/Framework/AzCore/AzCore/std/containers/vector.h index 48de12a5b4..255e1c4de4 100644 --- a/Code/Framework/AzCore/AzCore/std/containers/vector.h +++ b/Code/Framework/AzCore/AzCore/std/containers/vector.h @@ -1387,6 +1387,14 @@ namespace AZStd } //#pragma endregion + template + decltype(auto) erase(vector& container, const U& value) + { + auto iter = AZStd::remove(container.begin(), container.end(), value); + auto removedCount = AZStd::distance(iter, container.end()); + container.erase(iter, container.end()); + return removedCount; + } template decltype(auto) erase_if(vector& container, Predicate predicate) { diff --git a/Code/Framework/AzCore/AzCore/std/parallel/threadbus.h b/Code/Framework/AzCore/AzCore/std/parallel/threadbus.h index 26c2f92edb..85b716a6fa 100644 --- a/Code/Framework/AzCore/AzCore/std/parallel/threadbus.h +++ b/Code/Framework/AzCore/AzCore/std/parallel/threadbus.h @@ -8,9 +8,9 @@ #ifndef AZSTD_THREAD_BUS_H #define AZSTD_THREAD_BUS_H 1 -#include #include #include +#include namespace AZStd { @@ -32,24 +32,7 @@ namespace AZStd virtual void OnThreadExit(const AZStd::thread::id& id) = 0; }; - //! Thread events driller bus - only "drillers" (profilers) should connect to this. - //! A global mutex that includes a lock on the memory manager and other driller busses - //! is held during dispatch, and listeners are expected to do no allocation - //! or thread workloads or blocking or mutex operations of their own - only dump the data to - //! network or file ASAP. - //! DO NOT USE this bus unless you are a profiler capture system, use the ThreadEvents / ThreadBus instead - class ThreadDrillerEvents - : public AZ::Debug::DrillerEBusTraits - { - public: - /// Called when we enter a thread, optional thread_desc is provided when the use provides one. - virtual void OnThreadEnter(const AZStd::thread::id& id, const AZStd::thread_desc* desc) = 0; - /// Called when we exit a thread. - virtual void OnThreadExit(const AZStd::thread::id& id) = 0; - }; - typedef AZ::EBus ThreadEventBus; - typedef AZ::EBus ThreadDrillerEventBus; } #endif // AZSTD_THREAD_BUS_H diff --git a/Code/Framework/AzCore/AzCore/std/string/fixed_string.h b/Code/Framework/AzCore/AzCore/std/string/fixed_string.h index b68f21784b..a8e0f96988 100644 --- a/Code/Framework/AzCore/AzCore/std/string/fixed_string.h +++ b/Code/Framework/AzCore/AzCore/std/string/fixed_string.h @@ -465,6 +465,15 @@ namespace AZStd template constexpr bool operator>=(const Element* lhs, const basic_fixed_string& rhs); + // C++20 erase helpers + template + constexpr auto erase(basic_fixed_string& container, const U& element) + -> typename basic_fixed_string::size_type; + + template + constexpr auto erase_if(basic_fixed_string& container, Predicate predicate) + -> typename basic_fixed_string::size_type; + template struct hash; diff --git a/Code/Framework/AzCore/AzCore/std/string/fixed_string.inl b/Code/Framework/AzCore/AzCore/std/string/fixed_string.inl index 9a7e38aca0..8b973d3b2c 100644 --- a/Code/Framework/AzCore/AzCore/std/string/fixed_string.inl +++ b/Code/Framework/AzCore/AzCore/std/string/fixed_string.inl @@ -11,7 +11,7 @@ #include #include -#include +#include #include @@ -1680,6 +1680,26 @@ namespace AZStd return !operator<(lhs, rhs); } + template + inline constexpr auto erase(basic_fixed_string& container, const U& element) + -> typename basic_fixed_string::size_type + { + auto iter = AZStd::remove(container.begin(), container.end(), element); + auto removedCount = AZStd::distance(iter, container.end()); + container.erase(iter, container.end()); + return removedCount; + } + + template + inline constexpr auto erase_if(basic_fixed_string& container, Predicate predicate) + -> typename basic_fixed_string::size_type + { + auto iter = AZStd::remove_if(container.begin(), container.end(), predicate); + auto removedCount = AZStd::distance(iter, container.end()); + container.erase(iter, container.end()); + return removedCount; + } + template struct hash> { diff --git a/Code/Framework/AzCore/AzCore/std/string/memorytoascii.cpp b/Code/Framework/AzCore/AzCore/std/string/memorytoascii.cpp index 8f2d5ddf25..21f3a1c5f4 100644 --- a/Code/Framework/AzCore/AzCore/std/string/memorytoascii.cpp +++ b/Code/Framework/AzCore/AzCore/std/string/memorytoascii.cpp @@ -8,179 +8,176 @@ #include -namespace AZStd +namespace AZStd::MemoryToASCII { - namespace MemoryToASCII + AZStd::string ToString(const void* memoryAddrs, AZStd::size_t dataSize, AZStd::size_t maxShowSize, AZStd::size_t dataWidth/*=16*/, Options format/*=Options::Default*/) { - AZStd::string ToString(const void* memoryAddrs, AZStd::size_t dataSize, AZStd::size_t maxShowSize, AZStd::size_t dataWidth/*=16*/, Options format/*=Options::Default*/) + AZStd::string output; + + if ((memoryAddrs != nullptr) && (dataSize > 0)) { - AZStd::string output; + const AZ::u8 *data = reinterpret_cast(memoryAddrs); - if ((memoryAddrs != nullptr) && (dataSize > 0)) + if (static_cast(format) != 0) { - const AZ::u8 *data = reinterpret_cast(memoryAddrs); + output.reserve(8162); - if (static_cast(format) != 0) + bool showHeader = static_cast(format) & static_cast(Options::Header) ? true : false; + bool showOffset = static_cast(format) & static_cast(Options::Offset) ? true : false; + bool showBinary = static_cast(format) & static_cast(Options::Binary) ? true : false; + bool showASCII = static_cast(format) & static_cast(Options::ASCII) ? true : false; + bool showInfo = static_cast(format) & static_cast(Options::Info) ? true : false; + + // Because of the auto formatting for the headers, the min width is 3 + if (dataWidth < 3) { - output.reserve(8162); + dataWidth = 3; + } - bool showHeader = static_cast(format) & static_cast(Options::Header) ? true : false; - bool showOffset = static_cast(format) & static_cast(Options::Offset) ? true : false; - bool showBinary = static_cast(format) & static_cast(Options::Binary) ? true : false; - bool showASCII = static_cast(format) & static_cast(Options::ASCII) ? true : false; - bool showInfo = static_cast(format) & static_cast(Options::Info) ? true : false; + if (showHeader) + { + AZStd::string line1; + AZStd::string line2; + line1.reserve(1024); + line2.reserve(1024); - // Because of the auto formatting for the headers, the min width is 3 - if (dataWidth < 3) + if (showOffset) { - dataWidth = 3; + line1 += "Offset"; + line2 += "------"; + + if (showBinary || showASCII) + { + line1 += " "; + line2 += " "; + } } - if (showHeader) + if (showBinary) { - AZStd::string line1; - AZStd::string line2; - line1.reserve(1024); - line2.reserve(1024); + static const char *kHeaderName = "Data"; + static AZStd::size_t kHeaderNameSize = 4; - if (showOffset) + AZStd::size_t lineLength = (dataWidth * 3) - 1; + AZStd::size_t numPreSpaces = (lineLength - kHeaderNameSize) / 2; + AZStd::size_t numPostSpaces = lineLength - numPreSpaces - kHeaderNameSize; + + line1 += AZStd::string(numPreSpaces, ' ') + kHeaderName + AZStd::string(numPostSpaces, ' '); + //line2 += AZStd::string(lineLength, '-'); + for(size_t i=0; i 0) { - line1 += " "; - line2 += " "; - } - } - - if (showBinary) - { - static const char *kHeaderName = "Data"; - static AZStd::size_t kHeaderNameSize = 4; - - AZStd::size_t lineLength = (dataWidth * 3) - 1; - AZStd::size_t numPreSpaces = (lineLength - kHeaderNameSize) / 2; - AZStd::size_t numPostSpaces = lineLength - numPreSpaces - kHeaderNameSize; - - line1 += AZStd::string(numPreSpaces, ' ') + kHeaderName + AZStd::string(numPostSpaces, ' '); - //line2 += AZStd::string(lineLength, '-'); - for(size_t i=0; i 0) - { - line2 += "-"; - } - - line2 += AZStd::string::format("%02zx", i); + line2 += "-"; } - if (showASCII) - { - line1 += " "; - line2 += " "; - } + line2 += AZStd::string::format("%02zx", i); } if (showASCII) { - static const char *kHeaderName = "ASCII"; - static AZStd::size_t kHeaderNameSize = 5; - - AZStd::size_t numPreSpaces = (dataWidth - kHeaderNameSize) / 2; - AZStd::size_t numPostSpaces = dataWidth - numPreSpaces - kHeaderNameSize; - - line1 += AZStd::string(numPreSpaces, ' ') + kHeaderName + AZStd::string(numPostSpaces, ' '); - line2 += AZStd::string(dataWidth, '-'); + line1 += " "; + line2 += " "; } - - if (showInfo) - { - output += AZStd::string::format("Address: 0x%p Data Size:%zu Max Size:%zu\n", data, dataSize, maxShowSize); - } - - output += line1 + "\n"; - output += line2 + "\n"; } - AZStd::size_t offset = 0; - AZStd::size_t maxSize = dataSize > maxShowSize ? maxShowSize : dataSize; - - while (offset < maxSize) + if (showASCII) { - if (showOffset) - { - output += AZStd::string::format("%06zx", offset); + static const char *kHeaderName = "ASCII"; + static AZStd::size_t kHeaderNameSize = 5; - if (showBinary || showASCII) + AZStd::size_t numPreSpaces = (dataWidth - kHeaderNameSize) / 2; + AZStd::size_t numPostSpaces = dataWidth - numPreSpaces - kHeaderNameSize; + + line1 += AZStd::string(numPreSpaces, ' ') + kHeaderName + AZStd::string(numPostSpaces, ' '); + line2 += AZStd::string(dataWidth, '-'); + } + + if (showInfo) + { + output += AZStd::string::format("Address: 0x%p Data Size:%zu Max Size:%zu\n", data, dataSize, maxShowSize); + } + + output += line1 + "\n"; + output += line2 + "\n"; + } + + AZStd::size_t offset = 0; + AZStd::size_t maxSize = dataSize > maxShowSize ? maxShowSize : dataSize; + + while (offset < maxSize) + { + if (showOffset) + { + output += AZStd::string::format("%06zx", offset); + + if (showBinary || showASCII) + { + output += " "; + } + } + + if (showBinary) + { + AZStd::string binLine; + binLine.reserve((dataWidth * 3) * 2); + + for (AZStd::size_t index = 0; index < dataWidth; index++) + { + if (!binLine.empty()) { - output += " "; + binLine += " "; + } + + if ((offset + index) < maxSize) + { + binLine += AZStd::string::format("%02x", data[offset + index]); + } + else + { + binLine += " "; } } - if (showBinary) - { - AZStd::string binLine; - binLine.reserve((dataWidth * 3) * 2); - - for (AZStd::size_t index = 0; index < dataWidth; index++) - { - if (!binLine.empty()) - { - binLine += " "; - } - - if ((offset + index) < maxSize) - { - binLine += AZStd::string::format("%02x", data[offset + index]); - } - else - { - binLine += " "; - } - } - - output += binLine; - - if (showASCII) - { - output += " "; - } - } + output += binLine; if (showASCII) { - AZStd::string asciiLine; - asciiLine.reserve(dataWidth * 2); + output += " "; + } + } - for (AZStd::size_t index = 0; index < dataWidth; index++) + if (showASCII) + { + AZStd::string asciiLine; + asciiLine.reserve(dataWidth * 2); + + for (AZStd::size_t index = 0; index < dataWidth; index++) + { + if ((offset + index) > maxSize) { - if ((offset + index) > maxSize) - { - break; - } - else - { - char value = static_cast(data[offset + index]); - - if ((value < 32) || (value > 127)) - value = ' '; - - asciiLine += value; - } + break; } + else + { + char value = static_cast(data[offset + index]); - output += asciiLine; + if ((value < 32) || (value > 127)) + value = ' '; + + asciiLine += value; + } } - output += "\n"; - offset += dataWidth; + output += asciiLine; } + + output += "\n"; + offset += dataWidth; } } - - return output; } - } // namespace MemoryToASCII -} // namespace AZStd + + return output; + } +} // namespace AZStd::MemoryToASCII diff --git a/Code/Framework/AzCore/AzCore/std/string/string.h b/Code/Framework/AzCore/AzCore/std/string/string.h index 9ef7ea3086..e107c4657d 100644 --- a/Code/Framework/AzCore/AzCore/std/string/string.h +++ b/Code/Framework/AzCore/AzCore/std/string/string.h @@ -1955,6 +1955,16 @@ namespace AZStd { return basic_string(lhs).compare(rhs) >= 0; } + + template + decltype(auto) erase(basic_string& container, const U& element) + { + auto iter = AZStd::remove(container.begin(), container.end(), element); + auto removedCount = AZStd::distance(iter, container.end()); + container.erase(iter, container.end()); + return removedCount; + } + template decltype(auto) erase_if(basic_string& container, Predicate predicate) { diff --git a/Code/Framework/AzCore/Platform/Android/AzCore/AzCore_Traits_Android.h b/Code/Framework/AzCore/Platform/Android/AzCore/AzCore_Traits_Android.h index e05e000a4e..e8efce1133 100644 --- a/Code/Framework/AzCore/Platform/Android/AzCore/AzCore_Traits_Android.h +++ b/Code/Framework/AzCore/Platform/Android/AzCore/AzCore_Traits_Android.h @@ -53,10 +53,8 @@ // Compiler traits ... #define AZ_TRAIT_COMPILER_DEFINE_AZSWNPRINTF_AS_SWPRINTF 1 #define AZ_TRAIT_COMPILER_DEFINE_FS_ERRNO_TYPE 1 -#define AZ_TRAIT_COMPILER_DEFINE_FS_STAT_TYPE 0 #define AZ_TRAIT_COMPILER_DEFINE_GETCURRENTPROCESSID 1 #define AZ_TRAIT_COMPILER_DEFINE_REFGUID 0 -#define AZ_TRAIT_COMPILER_DEFINE_SASSERTDATA_TYPE 1 #define AZ_TRAIT_COMPILER_DEFINE_WCSICMP 1 #define AZ_TRAIT_COMPILER_INT64_T_IS_LONG 1 #define AZ_TRAIT_COMPILER_OPTIMIZE_MISSING_DEFAULT_SWITCH_CASE 0 diff --git a/Code/Framework/AzCore/Platform/Android/AzCore/IO/SystemFile_Android.cpp b/Code/Framework/AzCore/Platform/Android/AzCore/IO/SystemFile_Android.cpp index b9a6cfef9a..a0b95960ed 100644 --- a/Code/Framework/AzCore/Platform/Android/AzCore/IO/SystemFile_Android.cpp +++ b/Code/Framework/AzCore/Platform/Android/AzCore/IO/SystemFile_Android.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include @@ -87,7 +86,6 @@ bool SystemFile::PlatformOpen(int mode, int platformFlags) } else { - EBUS_EVENT(FileIOEventBus, OnError, this, nullptr, EINVAL); return false; } @@ -103,7 +101,6 @@ bool SystemFile::PlatformOpen(int mode, int platformFlags) { if (isApkFile) { - EBUS_EVENT(FileIOEventBus, OnError, this, nullptr, ENOSPC); return false; } @@ -125,7 +122,6 @@ bool SystemFile::PlatformOpen(int mode, int platformFlags) if (m_handle == PlatformSpecificInvalidHandle) { - EBUS_EVENT(FileIOEventBus, OnError, this, nullptr, errorCode); return false; } @@ -168,22 +164,8 @@ namespace Platform::Internal entry = readdir(dir); } - int lastError = errno; - if (lastError != 0) - { - EBUS_EVENT(FileIOEventBus, OnError, nullptr, filter, lastError); - } - closedir(dir); } - else - { - int lastError = errno; - if (lastError != ENOENT) - { - EBUS_EVENT(FileIOEventBus, OnError, nullptr, filter, 0); - } - } } void FindFilesInApk(const char* filter, const SystemFile::FindFileCB& cb) @@ -233,11 +215,7 @@ namespace Platform { if (handle != PlatformSpecificInvalidHandle) { - off_t result = fseeko(handle, static_cast(offset), mode); - if (result != 0) - { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, errno); - } + fseeko(handle, static_cast(offset), mode); } } @@ -248,7 +226,6 @@ namespace Platform off_t result = ftello(handle); if (result == (off_t)-1) { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, errno); return 0; } return aznumeric_cast(result); @@ -292,7 +269,6 @@ namespace Platform if (bytesRead != bytesToRead && ferror(handle)) { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, errno); return 0; } @@ -311,7 +287,6 @@ namespace Platform if (bytesWritten != bytesToWrite && ferror(handle)) { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, errno); return 0; } @@ -325,10 +300,7 @@ namespace Platform { if (handle != PlatformSpecificInvalidHandle) { - if (fflush(handle) != 0) - { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, errno); - } + fflush(handle); } } @@ -347,7 +319,6 @@ namespace Platform struct stat fileStat; if (stat(fileName, &fileStat) < 0) { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, 0); return 0; } return static_cast(fileStat.st_size); diff --git a/Code/Framework/AzCore/Platform/Common/Apple/AzCore/IO/SystemFile_Apple.cpp b/Code/Framework/AzCore/Platform/Common/Apple/AzCore/IO/SystemFile_Apple.cpp index be5cbc3859..deb2d49516 100644 --- a/Code/Framework/AzCore/Platform/Common/Apple/AzCore/IO/SystemFile_Apple.cpp +++ b/Code/Framework/AzCore/Platform/Common/Apple/AzCore/IO/SystemFile_Apple.cpp @@ -7,9 +7,9 @@ */ #include -#include #include #include +#include #include #include <../Common/UnixLike/AzCore/IO/Internal/SystemFileUtils_UnixLike.h> @@ -44,21 +44,7 @@ namespace AZ::IO::Platform entry = readdir(dir); } - int lastError = errno; - if (lastError != 0) - { - EBUS_EVENT(FileIOEventBus, OnError, nullptr, filter, lastError); - } - closedir(dir); } - else - { - int lastError = errno; - if (lastError != ENOENT) - { - EBUS_EVENT(FileIOEventBus, OnError, nullptr, filter, 0); - } - } } } diff --git a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Debug/Trace_UnixLike.cpp b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Debug/Trace_UnixLike.cpp index f4d7db802b..92a80b0d9a 100644 --- a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Debug/Trace_UnixLike.cpp +++ b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Debug/Trace_UnixLike.cpp @@ -13,73 +13,67 @@ #include #include -namespace AZ +namespace AZ::Debug::Platform { - namespace Debug - { - namespace Platform - { #if defined(AZ_ENABLE_DEBUG_TOOLS) - bool performDebuggerDetection() + bool performDebuggerDetection() + { + AZ::IO::SystemFile processStatusFile; + if (!processStatusFile.Open("/proc/self/status", AZ::IO::SystemFile::SF_OPEN_READ_ONLY)) + { + return false; + } + + char buffer[4096]; + AZ::IO::SystemFile::SizeType numRead = processStatusFile.Read(sizeof(buffer), buffer); + + const AZStd::string_view processStatusView(buffer, buffer + numRead); + constexpr AZStd::string_view tracerPidString = "TracerPid:"; + const size_t tracerPidOffset = processStatusView.find(tracerPidString); + if (tracerPidOffset == AZStd::string_view::npos) + { + return false; + } + for (size_t i = tracerPidOffset + tracerPidString.length(); i < numRead; ++i) + { + if (!::isspace(processStatusView[i])) { - AZ::IO::SystemFile processStatusFile; - if (!processStatusFile.Open("/proc/self/status", AZ::IO::SystemFile::SF_OPEN_READ_ONLY)) - { - return false; - } - - char buffer[4096]; - AZ::IO::SystemFile::SizeType numRead = processStatusFile.Read(sizeof(buffer), buffer); - - const AZStd::string_view processStatusView(buffer, buffer + numRead); - constexpr AZStd::string_view tracerPidString = "TracerPid:"; - const size_t tracerPidOffset = processStatusView.find(tracerPidString); - if (tracerPidOffset == AZStd::string_view::npos) - { - return false; - } - for (size_t i = tracerPidOffset + tracerPidString.length(); i < numRead; ++i) - { - if (!::isspace(processStatusView[i])) - { - return processStatusView[i] != '0'; - } - } - return false; - } - - bool IsDebuggerPresent() - { - static bool s_detectionPerformed = false; - static bool s_debuggerDetected = false; - if (!s_detectionPerformed) - { - s_debuggerDetected = performDebuggerDetection(); - s_detectionPerformed = true; - } - return s_debuggerDetected; - } - - bool AttachDebugger() - { - // Not supported yet - AZ_Assert(false, "AttachDebugger() is not supported for Unix platform yet"); - return false; - } - - void HandleExceptions(bool) - {} - - void DebugBreak() - { - raise(SIGINT); - } -#endif // AZ_ENABLE_DEBUG_TOOLS - - void Terminate(int exitCode) - { - _exit(exitCode); + return processStatusView[i] != '0'; } } + return false; } -} + + bool IsDebuggerPresent() + { + static bool s_detectionPerformed = false; + static bool s_debuggerDetected = false; + if (!s_detectionPerformed) + { + s_debuggerDetected = performDebuggerDetection(); + s_detectionPerformed = true; + } + return s_debuggerDetected; + } + + bool AttachDebugger() + { + // Not supported yet + AZ_Assert(false, "AttachDebugger() is not supported for Unix platform yet"); + return false; + } + + void HandleExceptions(bool) + {} + + void DebugBreak() + { + raise(SIGINT); + } +#endif // AZ_ENABLE_DEBUG_TOOLS + + void Terminate(int exitCode) + { + _exit(exitCode); + } +} // namespace AZ::Debug::Platform diff --git a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/IO/Internal/SystemFileUtils_UnixLike.cpp b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/IO/Internal/SystemFileUtils_UnixLike.cpp index 07614ad56b..4c605f6154 100644 --- a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/IO/Internal/SystemFileUtils_UnixLike.cpp +++ b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/IO/Internal/SystemFileUtils_UnixLike.cpp @@ -8,82 +8,76 @@ #include "SystemFileUtils_UnixLike.h" -namespace AZ +namespace AZ::IO::Internal { - namespace IO + bool FormatAndPeelOffWildCardExtension(const char* sourcePath, char* filePath, size_t filePathSize, char* extensionPath, size_t extensionSize, bool keepWildcard) { - namespace Internal + if (sourcePath == nullptr || filePath == nullptr || extensionPath == nullptr || filePathSize == 0 || extensionSize == 0) { - bool FormatAndPeelOffWildCardExtension(const char* sourcePath, char* filePath, size_t filePathSize, char* extensionPath, size_t extensionSize, bool keepWildcard) + AZ_Error("AZ::IO::Internal", false, "FormatAndPeelOffWildCardExtension: One or more parameters was invalid."); + return false; + } + const char* pSrcPath = sourcePath; + char* pDestPath = filePath; + size_t destinationSize = filePathSize; + unsigned numFileChars = 0; + unsigned numExtensionChars = 0; + unsigned* pNumDestChars = &numFileChars; + bool bIsWildcardExtension = false; + while (*pSrcPath) + { + char srcChar = *pSrcPath++; + + // Skip '*' and '.' + if ((!bIsWildcardExtension && srcChar != '*') || (bIsWildcardExtension && srcChar != '.' && (keepWildcard || srcChar != '*'))) { - if (sourcePath == nullptr || filePath == nullptr || extensionPath == nullptr || filePathSize == 0 || extensionSize == 0) + unsigned numChars = *pNumDestChars; + pDestPath[numChars++] = srcChar; + *pNumDestChars = numChars; + + --destinationSize; + if (destinationSize == 0) { - AZ_Error("AZ::IO::Internal", false, "FormatAndPeelOffWildCardExtension: One or more parameters was invalid."); + AZ_Error( + "AZ::IO::Internal", + false, + "Error splitting sourcePath '%s' into filePath and extension, %s length is larger than storage size %d.", + sourcePath, + bIsWildcardExtension ? "extensionPath" : "filePath", + bIsWildcardExtension ? extensionSize : filePathSize); return false; } - const char* pSrcPath = sourcePath; - char* pDestPath = filePath; - size_t destinationSize = filePathSize; - unsigned numFileChars = 0; - unsigned numExtensionChars = 0; - unsigned* pNumDestChars = &numFileChars; - bool bIsWildcardExtension = false; - while (*pSrcPath) + } + // Wild-card extension is separate + if (srcChar == '*') + { + bIsWildcardExtension = true; + pDestPath = extensionPath; + destinationSize = extensionSize; + pNumDestChars = &numExtensionChars; + if (keepWildcard) { - char srcChar = *pSrcPath++; + unsigned numChars = *pNumDestChars; + pDestPath[numChars++] = srcChar; + *pNumDestChars = numChars; - // Skip '*' and '.' - if ((!bIsWildcardExtension && srcChar != '*') || (bIsWildcardExtension && srcChar != '.' && (keepWildcard || srcChar != '*'))) + --destinationSize; + if (destinationSize == 0) { - unsigned numChars = *pNumDestChars; - pDestPath[numChars++] = srcChar; - *pNumDestChars = numChars; - - --destinationSize; - if (destinationSize == 0) - { - AZ_Error( - "AZ::IO::Internal", - false, - "Error splitting sourcePath '%s' into filePath and extension, %s length is larger than storage size %d.", - sourcePath, - bIsWildcardExtension ? "extensionPath" : "filePath", - bIsWildcardExtension ? extensionSize : filePathSize); - return false; - } - } - // Wild-card extension is separate - if (srcChar == '*') - { - bIsWildcardExtension = true; - pDestPath = extensionPath; - destinationSize = extensionSize; - pNumDestChars = &numExtensionChars; - if (keepWildcard) - { - unsigned numChars = *pNumDestChars; - pDestPath[numChars++] = srcChar; - *pNumDestChars = numChars; - - --destinationSize; - if (destinationSize == 0) - { - AZ_Error( - "AZ::IO::Internal", - false, - "Error splitting sourcePath '%s' into filePath and extension, extensionPath length is larger than storage size %d.", - sourcePath, - extensionSize); - return false; - } - } + AZ_Error( + "AZ::IO::Internal", + false, + "Error splitting sourcePath '%s' into filePath and extension, extensionPath length is larger than storage size %d.", + sourcePath, + extensionSize); + return false; } } - // Close strings - filePath[numFileChars] = 0; - extensionPath[numExtensionChars] = 0; - return true; } } + // Close strings + filePath[numFileChars] = 0; + extensionPath[numExtensionChars] = 0; + return true; } -} +} // namespace AZ::IO::Internal diff --git a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/IO/SystemFile_UnixLike.cpp b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/IO/SystemFile_UnixLike.cpp index 8f651a9559..2bf7fdc5f2 100644 --- a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/IO/SystemFile_UnixLike.cpp +++ b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/IO/SystemFile_UnixLike.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include @@ -130,7 +129,6 @@ namespace Platform int result = remove(fileName); if (result != 0) { - EBUS_EVENT(FileIOEventBus, OnError, nullptr, fileName, result); return false; } @@ -142,7 +140,6 @@ namespace Platform int result = rename(sourceFileName, targetFileName); if (result) { - EBUS_EVENT(FileIOEventBus, OnError, nullptr, sourceFileName, result); return false; } @@ -198,10 +195,6 @@ namespace Platform } azstrcpy(dirPath, AZ_MAX_PATH_LEN, dirName); bool success = CreateDirRecursive(dirPath); - if (!success) - { - EBUS_EVENT(FileIOEventBus, OnError, nullptr, dirName, errno); - } return success; } return false; diff --git a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Platform_UnixLike.cpp b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Platform_UnixLike.cpp index bdc2e753be..4a316bcde6 100644 --- a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Platform_UnixLike.cpp +++ b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Platform_UnixLike.cpp @@ -14,31 +14,28 @@ #include #include -namespace AZ +namespace AZ::Platform { - namespace Platform + ProcessId GetCurrentProcessId() { - ProcessId GetCurrentProcessId() - { - return static_cast(::getpid()); - } + return static_cast(::getpid()); + } - MachineId GetLocalMachineId() + MachineId GetLocalMachineId() + { + if (s_machineId == 0) { + // In specialized server situations, SetLocalMachineId() should be used, with whatever criteria works best in that environment + // A proper implementation for each supported system will be needed instead of this temporary measure to avoid collision in the small scale. + // On a larger scale, the odds of two people getting in here at the same millisecond will go up drastically, and we'll have the same issue again, + // though far less reproducible, for duplicated EntityId's across a network. + s_machineId = static_cast(AZStd::GetTimeUTCMilliSecond() & 0xffffffff); if (s_machineId == 0) { - // In specialized server situations, SetLocalMachineId() should be used, with whatever criteria works best in that environment - // A proper implementation for each supported system will be needed instead of this temporary measure to avoid collision in the small scale. - // On a larger scale, the odds of two people getting in here at the same millisecond will go up drastically, and we'll have the same issue again, - // though far less reproducible, for duplicated EntityId's across a network. - s_machineId = static_cast(AZStd::GetTimeUTCMilliSecond() & 0xffffffff); - if (s_machineId == 0) - { - s_machineId = 1; - AZ_Warning("System", false, "0 machine ID is reserved!"); - } + s_machineId = 1; + AZ_Warning("System", false, "0 machine ID is reserved!"); } - return s_machineId; } - } // namespace Platform -} + return s_machineId; + } +} // namespace AZ::Platform diff --git a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Socket/AzSocket_UnixLike.cpp b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Socket/AzSocket_UnixLike.cpp index 5b35b13d8b..555d70f1e6 100644 --- a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Socket/AzSocket_UnixLike.cpp +++ b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Socket/AzSocket_UnixLike.cpp @@ -18,365 +18,362 @@ #define INVALID_SOCKET (-1) #define closesocket(_s) close(_s) #define GetInternalSocketError errno -typedef int SOCKET; -typedef AZ::u32 AZSOCKLEN; +using SOCKET = int; +using AZSOCKLEN = AZ::u32; -namespace AZ +namespace AZ::AzSock { - namespace AzSock + AZ::s32 TranslateOSError(AZ::s32 oserror) { - AZ::s32 TranslateOSError(AZ::s32 oserror) - { - AZ::s32 error; + AZ::s32 error; #define TRANSLATE(_from, _to) case (_from): error = static_cast(_to); break; - switch (oserror) - { - TRANSLATE(0, AzSockError::eASE_NO_ERROR); - TRANSLATE(EACCES, AzSockError::eASE_EACCES); - TRANSLATE(EADDRINUSE, AzSockError::eASE_EADDRINUSE); - TRANSLATE(EADDRNOTAVAIL, AzSockError::eASE_EADDRNOTAVAIL); - TRANSLATE(EAFNOSUPPORT, AzSockError::eASE_EAFNOSUPPORT); - TRANSLATE(EALREADY, AzSockError::eASE_EALREADY); - TRANSLATE(EBADF, AzSockError::eASE_EBADF); - TRANSLATE(ECONNABORTED, AzSockError::eASE_ECONNABORTED); - TRANSLATE(ECONNREFUSED, AzSockError::eASE_ECONNREFUSED); - TRANSLATE(ECONNRESET, AzSockError::eASE_ECONNRESET); - TRANSLATE(EFAULT, AzSockError::eASE_EFAULT); - TRANSLATE(EHOSTDOWN, AzSockError::eASE_EHOSTDOWN); - TRANSLATE(EINPROGRESS, AzSockError::eASE_EINPROGRESS); - TRANSLATE(EINTR, AzSockError::eASE_EINTR); - TRANSLATE(EINVAL, AzSockError::eASE_EINVAL); - TRANSLATE(EISCONN, AzSockError::eASE_EISCONN); - TRANSLATE(EMFILE, AzSockError::eASE_EMFILE); - TRANSLATE(EMSGSIZE, AzSockError::eASE_EMSGSIZE); - TRANSLATE(ENETUNREACH, AzSockError::eASE_ENETUNREACH); - TRANSLATE(ENOBUFS, AzSockError::eASE_ENOBUFS); - TRANSLATE(ENOPROTOOPT, AzSockError::eASE_ENOPROTOOPT); - TRANSLATE(ENOTCONN, AzSockError::eASE_ENOTCONN); - TRANSLATE(EOPNOTSUPP, AzSockError::eASE_EOPNOTSUPP); - TRANSLATE(EPROTONOSUPPORT, AzSockError::eASE_EAFNOSUPPORT); - TRANSLATE(ETIMEDOUT, AzSockError::eASE_ETIMEDOUT); - TRANSLATE(ETOOMANYREFS, AzSockError::eASE_ETOOMANYREFS); - TRANSLATE(EWOULDBLOCK, AzSockError::eASE_EWOULDBLOCK); + switch (oserror) + { + TRANSLATE(0, AzSockError::eASE_NO_ERROR); + TRANSLATE(EACCES, AzSockError::eASE_EACCES); + TRANSLATE(EADDRINUSE, AzSockError::eASE_EADDRINUSE); + TRANSLATE(EADDRNOTAVAIL, AzSockError::eASE_EADDRNOTAVAIL); + TRANSLATE(EAFNOSUPPORT, AzSockError::eASE_EAFNOSUPPORT); + TRANSLATE(EALREADY, AzSockError::eASE_EALREADY); + TRANSLATE(EBADF, AzSockError::eASE_EBADF); + TRANSLATE(ECONNABORTED, AzSockError::eASE_ECONNABORTED); + TRANSLATE(ECONNREFUSED, AzSockError::eASE_ECONNREFUSED); + TRANSLATE(ECONNRESET, AzSockError::eASE_ECONNRESET); + TRANSLATE(EFAULT, AzSockError::eASE_EFAULT); + TRANSLATE(EHOSTDOWN, AzSockError::eASE_EHOSTDOWN); + TRANSLATE(EINPROGRESS, AzSockError::eASE_EINPROGRESS); + TRANSLATE(EINTR, AzSockError::eASE_EINTR); + TRANSLATE(EINVAL, AzSockError::eASE_EINVAL); + TRANSLATE(EISCONN, AzSockError::eASE_EISCONN); + TRANSLATE(EMFILE, AzSockError::eASE_EMFILE); + TRANSLATE(EMSGSIZE, AzSockError::eASE_EMSGSIZE); + TRANSLATE(ENETUNREACH, AzSockError::eASE_ENETUNREACH); + TRANSLATE(ENOBUFS, AzSockError::eASE_ENOBUFS); + TRANSLATE(ENOPROTOOPT, AzSockError::eASE_ENOPROTOOPT); + TRANSLATE(ENOTCONN, AzSockError::eASE_ENOTCONN); + TRANSLATE(EOPNOTSUPP, AzSockError::eASE_EOPNOTSUPP); + TRANSLATE(EPROTONOSUPPORT, AzSockError::eASE_EAFNOSUPPORT); + TRANSLATE(ETIMEDOUT, AzSockError::eASE_ETIMEDOUT); + TRANSLATE(ETOOMANYREFS, AzSockError::eASE_ETOOMANYREFS); + TRANSLATE(EWOULDBLOCK, AzSockError::eASE_EWOULDBLOCK); - default: - AZ_TracePrintf("AzSock", "AzSocket could not translate OS error code %x, treating as miscellaneous.\n", oserror); - error = static_cast(AzSockError::eASE_MISC_ERROR); - break; - } + default: + AZ_TracePrintf("AzSock", "AzSocket could not translate OS error code %x, treating as miscellaneous.\n", oserror); + error = static_cast(AzSockError::eASE_MISC_ERROR); + break; + } #undef TRANSLATE - return error; - } + return error; + } - AZ::s32 TranslateSocketOption(AzSocketOption opt) - { - AZ::s32 value; + AZ::s32 TranslateSocketOption(AzSocketOption opt) + { + AZ::s32 value; #define TRANSLATE(_from, _to) case (_from): value = (_to); break; - switch (opt) - { - TRANSLATE(AzSocketOption::REUSEADDR, SO_REUSEADDR); - TRANSLATE(AzSocketOption::KEEPALIVE, SO_KEEPALIVE); - TRANSLATE(AzSocketOption::LINGER, SO_LINGER); + switch (opt) + { + TRANSLATE(AzSocketOption::REUSEADDR, SO_REUSEADDR); + TRANSLATE(AzSocketOption::KEEPALIVE, SO_KEEPALIVE); + TRANSLATE(AzSocketOption::LINGER, SO_LINGER); - default: - AZ_TracePrintf("AzSock", "AzSocket option %x not yet supported", opt); - value = 0; - break; - } + default: + AZ_TracePrintf("AzSock", "AzSocket option %x not yet supported", opt); + value = 0; + break; + } #undef TRANSLATE - return value; - } + return value; + } - AZSOCKET HandleInvalidSocket(SOCKET sock) + AZSOCKET HandleInvalidSocket(SOCKET sock) + { + AZSOCKET azsock = static_cast(sock); + if (sock == INVALID_SOCKET) { - AZSOCKET azsock = static_cast(sock); - if (sock == INVALID_SOCKET) - { - azsock = TranslateOSError(GetInternalSocketError); - } - return azsock; + azsock = TranslateOSError(GetInternalSocketError); } + return azsock; + } - AZ::s32 HandleSocketError(AZ::s32 socketError) + AZ::s32 HandleSocketError(AZ::s32 socketError) + { + if (socketError == SOCKET_ERROR) { - if (socketError == SOCKET_ERROR) - { - socketError = TranslateOSError(GetInternalSocketError); - } - return socketError; + socketError = TranslateOSError(GetInternalSocketError); } + return socketError; + } - const char* GetStringForError(AZ::s32 errorNumber) - { - AzSockError errorCode = AzSockError(errorNumber); + const char* GetStringForError(AZ::s32 errorNumber) + { + AzSockError errorCode = AzSockError(errorNumber); #define CASE_RETSTRING(errorEnum) case errorEnum: { return #errorEnum; } - switch (errorCode) - { - CASE_RETSTRING(AzSockError::eASE_NO_ERROR); - CASE_RETSTRING(AzSockError::eASE_SOCKET_INVALID); - CASE_RETSTRING(AzSockError::eASE_EACCES); - CASE_RETSTRING(AzSockError::eASE_EADDRINUSE); - CASE_RETSTRING(AzSockError::eASE_EADDRNOTAVAIL); - CASE_RETSTRING(AzSockError::eASE_EAFNOSUPPORT); - CASE_RETSTRING(AzSockError::eASE_EALREADY); - CASE_RETSTRING(AzSockError::eASE_EBADF); - CASE_RETSTRING(AzSockError::eASE_ECONNABORTED); - CASE_RETSTRING(AzSockError::eASE_ECONNREFUSED); - CASE_RETSTRING(AzSockError::eASE_ECONNRESET); - CASE_RETSTRING(AzSockError::eASE_EFAULT); - CASE_RETSTRING(AzSockError::eASE_EHOSTDOWN); - CASE_RETSTRING(AzSockError::eASE_EINPROGRESS); - CASE_RETSTRING(AzSockError::eASE_EINTR); - CASE_RETSTRING(AzSockError::eASE_EINVAL); - CASE_RETSTRING(AzSockError::eASE_EISCONN); - CASE_RETSTRING(AzSockError::eASE_EMFILE); - CASE_RETSTRING(AzSockError::eASE_EMSGSIZE); - CASE_RETSTRING(AzSockError::eASE_ENETUNREACH); - CASE_RETSTRING(AzSockError::eASE_ENOBUFS); - CASE_RETSTRING(AzSockError::eASE_ENOPROTOOPT); - CASE_RETSTRING(AzSockError::eASE_ENOTCONN); - CASE_RETSTRING(AzSockError::eASE_ENOTINITIALISED); - CASE_RETSTRING(AzSockError::eASE_EOPNOTSUPP); - CASE_RETSTRING(AzSockError::eASE_EPIPE); - CASE_RETSTRING(AzSockError::eASE_EPROTONOSUPPORT); - CASE_RETSTRING(AzSockError::eASE_ETIMEDOUT); - CASE_RETSTRING(AzSockError::eASE_ETOOMANYREFS); - CASE_RETSTRING(AzSockError::eASE_EWOULDBLOCK); - CASE_RETSTRING(AzSockError::eASE_EWOULDBLOCK_CONN); - CASE_RETSTRING(AzSockError::eASE_MISC_ERROR); - } + switch (errorCode) + { + CASE_RETSTRING(AzSockError::eASE_NO_ERROR); + CASE_RETSTRING(AzSockError::eASE_SOCKET_INVALID); + CASE_RETSTRING(AzSockError::eASE_EACCES); + CASE_RETSTRING(AzSockError::eASE_EADDRINUSE); + CASE_RETSTRING(AzSockError::eASE_EADDRNOTAVAIL); + CASE_RETSTRING(AzSockError::eASE_EAFNOSUPPORT); + CASE_RETSTRING(AzSockError::eASE_EALREADY); + CASE_RETSTRING(AzSockError::eASE_EBADF); + CASE_RETSTRING(AzSockError::eASE_ECONNABORTED); + CASE_RETSTRING(AzSockError::eASE_ECONNREFUSED); + CASE_RETSTRING(AzSockError::eASE_ECONNRESET); + CASE_RETSTRING(AzSockError::eASE_EFAULT); + CASE_RETSTRING(AzSockError::eASE_EHOSTDOWN); + CASE_RETSTRING(AzSockError::eASE_EINPROGRESS); + CASE_RETSTRING(AzSockError::eASE_EINTR); + CASE_RETSTRING(AzSockError::eASE_EINVAL); + CASE_RETSTRING(AzSockError::eASE_EISCONN); + CASE_RETSTRING(AzSockError::eASE_EMFILE); + CASE_RETSTRING(AzSockError::eASE_EMSGSIZE); + CASE_RETSTRING(AzSockError::eASE_ENETUNREACH); + CASE_RETSTRING(AzSockError::eASE_ENOBUFS); + CASE_RETSTRING(AzSockError::eASE_ENOPROTOOPT); + CASE_RETSTRING(AzSockError::eASE_ENOTCONN); + CASE_RETSTRING(AzSockError::eASE_ENOTINITIALISED); + CASE_RETSTRING(AzSockError::eASE_EOPNOTSUPP); + CASE_RETSTRING(AzSockError::eASE_EPIPE); + CASE_RETSTRING(AzSockError::eASE_EPROTONOSUPPORT); + CASE_RETSTRING(AzSockError::eASE_ETIMEDOUT); + CASE_RETSTRING(AzSockError::eASE_ETOOMANYREFS); + CASE_RETSTRING(AzSockError::eASE_EWOULDBLOCK); + CASE_RETSTRING(AzSockError::eASE_EWOULDBLOCK_CONN); + CASE_RETSTRING(AzSockError::eASE_MISC_ERROR); + } #undef CASE_RETSTRING - return "(invalid)"; - } - - AZ::u32 HostToNetLong(AZ::u32 hstLong) - { - return htonl(hstLong); - } - - AZ::u32 NetToHostLong(AZ::u32 netLong) - { - return ntohl(netLong); - } - - AZ::u16 HostToNetShort(AZ::u16 hstShort) - { - return htons(hstShort); - } - - AZ::u16 NetToHostShort(AZ::u16 netShort) - { - return ntohs(netShort); - } - - AZ::s32 GetHostName(AZStd::string& hostname) - { - AZ::s32 result = 0; - hostname.clear(); - char name[256]; - result = HandleSocketError(gethostname(name, AZ_ARRAY_SIZE(name))); - if (result == static_cast(AzSockError::eASE_NO_ERROR)) - { - hostname = name; - } - return result; - } - - AZSOCKET Socket() - { - return Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - } - - AZSOCKET Socket(AZ::s32 af, AZ::s32 type, AZ::s32 protocol) - { - return HandleInvalidSocket(socket(af, type, protocol)); - } - - AZ::s32 SetSockOpt(AZSOCKET sock, AZ::s32 level, AZ::s32 optname, const char* optval, AZ::s32 optlen) - { - AZSOCKLEN length(optlen); - return HandleSocketError(setsockopt(sock, level, optname, optval, length)); - } - - AZ::s32 SetSocketOption(AZSOCKET sock, AzSocketOption opt, bool enable) - { - AZ::u32 val = enable ? 1 : 0; - return SetSockOpt(sock, SOL_SOCKET, TranslateSocketOption(opt), reinterpret_cast(&val), sizeof(val)); - } - - AZ::s32 EnableTCPNoDelay(AZSOCKET sock, bool enable) - { - AZ::u32 val = enable ? 1 : 0; - return SetSockOpt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&val), sizeof(val)); - } - - AZ::s32 SetSocketBlockingMode(AZSOCKET sock, bool blocking) - { - AZ::s32 flags = ::fcntl(sock, F_GETFL); - flags &= ~O_NONBLOCK; - flags |= (blocking ? 0 : O_NONBLOCK); - return ::fcntl(sock, F_SETFL, flags); - } - - AZ::s32 CloseSocket(AZSOCKET sock) - { - return HandleSocketError(closesocket(sock)); - } - - AZ::s32 Shutdown(AZSOCKET sock, AZ::s32 how) - { - return HandleSocketError(shutdown(sock, how)); - } - - AZ::s32 GetSockName(AZSOCKET sock, AzSocketAddress& addr) - { - AZSOCKADDR sAddr; - AZSOCKLEN sAddrLen = sizeof(AZSOCKADDR); - memset(&sAddr, 0, sAddrLen); - AZ::s32 result = HandleSocketError(getsockname(sock, &sAddr, &sAddrLen)); - addr = sAddr; - return result; - } - - AZ::s32 Connect(AZSOCKET sock, const AzSocketAddress& addr) - { - AZ::s32 err = HandleSocketError(connect(sock, addr.GetTargetAddress(), sizeof(AZSOCKADDR_IN))); - if (err == static_cast(AzSockError::eASE_EINPROGRESS)) - { - err = static_cast(AzSockError::eASE_EWOULDBLOCK_CONN); - } - return err; - } - - AZ::s32 Listen(AZSOCKET sock, AZ::s32 backlog) - { - return HandleSocketError(listen(sock, backlog)); - } - - AZSOCKET Accept(AZSOCKET sock, AzSocketAddress& addr) - { - AZSOCKADDR sAddr; - AZSOCKLEN sAddrLen = sizeof(AZSOCKADDR); - memset(&sAddr, 0, sAddrLen); - AZSOCKET outSock = HandleInvalidSocket(accept(sock, &sAddr, &sAddrLen)); - addr = sAddr; - return outSock; - } - - AZ::s32 Send(AZSOCKET sock, const char* buf, AZ::s32 len, AZ::s32 flags) - { - AZ::s32 msgNoSignal = MSG_NOSIGNAL; - return HandleSocketError(send(sock, buf, len, flags | msgNoSignal)); - } - - AZ::s32 Recv(AZSOCKET sock, char* buf, AZ::s32 len, AZ::s32 flags) - { - return HandleSocketError(recv(sock, buf, len, flags)); - } - - AZ::s32 Bind(AZSOCKET sock, const AzSocketAddress& addr) - { - return HandleSocketError(bind(sock, addr.GetTargetAddress(), sizeof(AZSOCKADDR_IN))); - } - - AZ::s32 Select(AZSOCKET sock, AZFD_SET* readfdsock, AZFD_SET* writefdsock, AZFD_SET* exceptfdsock, AZTIMEVAL* timeout) - { - return HandleSocketError(::select(sock + 1, readfdsock, writefdsock, exceptfdsock, timeout)); - } - - AZ::s32 IsRecvPending(AZSOCKET sock, AZTIMEVAL* timeout) - { - AZFD_SET readSet; - FD_ZERO(&readSet); - FD_SET(sock, &readSet); - - AZ::s32 ret = Select(sock, &readSet, nullptr, nullptr, timeout); - if (ret >= 0) - { - ret = FD_ISSET(sock, &readSet); - if (ret != 0) - { - ret = 1; - } - } - - return ret; - } - - AZ::s32 WaitForWritableSocket(AZSOCKET sock, AZTIMEVAL* timeout) - { - AZFD_SET writeSet; - FD_ZERO(&writeSet); - FD_SET(sock, &writeSet); - - AZ::s32 ret = Select(sock, nullptr, &writeSet, nullptr, timeout); - if (ret >= 0) - { - ret = FD_ISSET(sock, &writeSet); - if (ret != 0) - { - ret = 1; - } - } - - return ret; - } - - AZ::s32 Startup() - { - return static_cast(AzSockError::eASE_NO_ERROR); - } - - AZ::s32 Cleanup() - { - return static_cast(AzSockError::eASE_NO_ERROR); - } - - bool ResolveAddress(const AZStd::string& ip, AZ::u16 port, AZSOCKADDR_IN& socketAddress) - { - bool foundAddr = false; - addrinfo hints; - memset(&hints, 0, sizeof(addrinfo)); - addrinfo* addrInfo; - hints.ai_family = AF_INET; - hints.ai_flags = AI_CANONNAME; - char strPort[8]; - azsnprintf(strPort, AZ_ARRAY_SIZE(strPort), "%d", port); - - const char* address = ip.c_str(); - if (address && strlen(address) == 0) // getaddrinfo doesn't accept empty string - { - address = nullptr; - } - - AZ::s32 err = HandleSocketError(getaddrinfo(address, strPort, &hints, &addrInfo)); - if (err == 0) // eASE_NO_ERROR - { - if (addrInfo->ai_family == AF_INET) - { - socketAddress = *reinterpret_cast(addrInfo->ai_addr); - foundAddr = true; - } - - freeaddrinfo(addrInfo); - } - else - { - AZ_Assert(false, "AzSocketAddress could not resolve address %s with port %d. (reason - %s)", ip.c_str(), port, GetStringForError(err)); - } - return foundAddr; - } + return "(invalid)"; } -} + + AZ::u32 HostToNetLong(AZ::u32 hstLong) + { + return htonl(hstLong); + } + + AZ::u32 NetToHostLong(AZ::u32 netLong) + { + return ntohl(netLong); + } + + AZ::u16 HostToNetShort(AZ::u16 hstShort) + { + return htons(hstShort); + } + + AZ::u16 NetToHostShort(AZ::u16 netShort) + { + return ntohs(netShort); + } + + AZ::s32 GetHostName(AZStd::string& hostname) + { + AZ::s32 result = 0; + hostname.clear(); + char name[256]; + result = HandleSocketError(gethostname(name, AZ_ARRAY_SIZE(name))); + if (result == static_cast(AzSockError::eASE_NO_ERROR)) + { + hostname = name; + } + return result; + } + + AZSOCKET Socket() + { + return Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + } + + AZSOCKET Socket(AZ::s32 af, AZ::s32 type, AZ::s32 protocol) + { + return HandleInvalidSocket(socket(af, type, protocol)); + } + + AZ::s32 SetSockOpt(AZSOCKET sock, AZ::s32 level, AZ::s32 optname, const char* optval, AZ::s32 optlen) + { + AZSOCKLEN length(optlen); + return HandleSocketError(setsockopt(sock, level, optname, optval, length)); + } + + AZ::s32 SetSocketOption(AZSOCKET sock, AzSocketOption opt, bool enable) + { + AZ::u32 val = enable ? 1 : 0; + return SetSockOpt(sock, SOL_SOCKET, TranslateSocketOption(opt), reinterpret_cast(&val), sizeof(val)); + } + + AZ::s32 EnableTCPNoDelay(AZSOCKET sock, bool enable) + { + AZ::u32 val = enable ? 1 : 0; + return SetSockOpt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&val), sizeof(val)); + } + + AZ::s32 SetSocketBlockingMode(AZSOCKET sock, bool blocking) + { + AZ::s32 flags = ::fcntl(sock, F_GETFL); + flags &= ~O_NONBLOCK; + flags |= (blocking ? 0 : O_NONBLOCK); + return ::fcntl(sock, F_SETFL, flags); + } + + AZ::s32 CloseSocket(AZSOCKET sock) + { + return HandleSocketError(closesocket(sock)); + } + + AZ::s32 Shutdown(AZSOCKET sock, AZ::s32 how) + { + return HandleSocketError(shutdown(sock, how)); + } + + AZ::s32 GetSockName(AZSOCKET sock, AzSocketAddress& addr) + { + AZSOCKADDR sAddr; + AZSOCKLEN sAddrLen = sizeof(AZSOCKADDR); + memset(&sAddr, 0, sAddrLen); + AZ::s32 result = HandleSocketError(getsockname(sock, &sAddr, &sAddrLen)); + addr = sAddr; + return result; + } + + AZ::s32 Connect(AZSOCKET sock, const AzSocketAddress& addr) + { + AZ::s32 err = HandleSocketError(connect(sock, addr.GetTargetAddress(), sizeof(AZSOCKADDR_IN))); + if (err == static_cast(AzSockError::eASE_EINPROGRESS)) + { + err = static_cast(AzSockError::eASE_EWOULDBLOCK_CONN); + } + return err; + } + + AZ::s32 Listen(AZSOCKET sock, AZ::s32 backlog) + { + return HandleSocketError(listen(sock, backlog)); + } + + AZSOCKET Accept(AZSOCKET sock, AzSocketAddress& addr) + { + AZSOCKADDR sAddr; + AZSOCKLEN sAddrLen = sizeof(AZSOCKADDR); + memset(&sAddr, 0, sAddrLen); + AZSOCKET outSock = HandleInvalidSocket(accept(sock, &sAddr, &sAddrLen)); + addr = sAddr; + return outSock; + } + + AZ::s32 Send(AZSOCKET sock, const char* buf, AZ::s32 len, AZ::s32 flags) + { + AZ::s32 msgNoSignal = MSG_NOSIGNAL; + return HandleSocketError(send(sock, buf, len, flags | msgNoSignal)); + } + + AZ::s32 Recv(AZSOCKET sock, char* buf, AZ::s32 len, AZ::s32 flags) + { + return HandleSocketError(recv(sock, buf, len, flags)); + } + + AZ::s32 Bind(AZSOCKET sock, const AzSocketAddress& addr) + { + return HandleSocketError(bind(sock, addr.GetTargetAddress(), sizeof(AZSOCKADDR_IN))); + } + + AZ::s32 Select(AZSOCKET sock, AZFD_SET* readfdsock, AZFD_SET* writefdsock, AZFD_SET* exceptfdsock, AZTIMEVAL* timeout) + { + return HandleSocketError(::select(sock + 1, readfdsock, writefdsock, exceptfdsock, timeout)); + } + + AZ::s32 IsRecvPending(AZSOCKET sock, AZTIMEVAL* timeout) + { + AZFD_SET readSet; + FD_ZERO(&readSet); + FD_SET(sock, &readSet); + + AZ::s32 ret = Select(sock, &readSet, nullptr, nullptr, timeout); + if (ret >= 0) + { + ret = FD_ISSET(sock, &readSet); + if (ret != 0) + { + ret = 1; + } + } + + return ret; + } + + AZ::s32 WaitForWritableSocket(AZSOCKET sock, AZTIMEVAL* timeout) + { + AZFD_SET writeSet; + FD_ZERO(&writeSet); + FD_SET(sock, &writeSet); + + AZ::s32 ret = Select(sock, nullptr, &writeSet, nullptr, timeout); + if (ret >= 0) + { + ret = FD_ISSET(sock, &writeSet); + if (ret != 0) + { + ret = 1; + } + } + + return ret; + } + + AZ::s32 Startup() + { + return static_cast(AzSockError::eASE_NO_ERROR); + } + + AZ::s32 Cleanup() + { + return static_cast(AzSockError::eASE_NO_ERROR); + } + + bool ResolveAddress(const AZStd::string& ip, AZ::u16 port, AZSOCKADDR_IN& socketAddress) + { + bool foundAddr = false; + addrinfo hints; + memset(&hints, 0, sizeof(addrinfo)); + addrinfo* addrInfo; + hints.ai_family = AF_INET; + hints.ai_flags = AI_CANONNAME; + char strPort[8]; + azsnprintf(strPort, AZ_ARRAY_SIZE(strPort), "%d", port); + + const char* address = ip.c_str(); + if (address && strlen(address) == 0) // getaddrinfo doesn't accept empty string + { + address = nullptr; + } + + AZ::s32 err = HandleSocketError(getaddrinfo(address, strPort, &hints, &addrInfo)); + if (err == 0) // eASE_NO_ERROR + { + if (addrInfo->ai_family == AF_INET) + { + socketAddress = *reinterpret_cast(addrInfo->ai_addr); + foundAddr = true; + } + + freeaddrinfo(addrInfo); + } + else + { + AZ_Assert(false, "AzSocketAddress could not resolve address %s with port %d. (reason - %s)", ip.c_str(), port, GetStringForError(err)); + } + return foundAddr; + } +} // namespace AZ::AzSock diff --git a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Utils/Utils_UnixLike.cpp b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Utils/Utils_UnixLike.cpp index 7327c8f152..8a93f88ac2 100644 --- a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Utils/Utils_UnixLike.cpp +++ b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Utils/Utils_UnixLike.cpp @@ -11,68 +11,65 @@ #include #include -namespace AZ +namespace AZ::Utils { - namespace Utils + void RequestAbnormalTermination() { - void RequestAbnormalTermination() + abort(); + } + + void NativeErrorMessageBox(const char*, const char*) {} + + AZ::IO::FixedMaxPathString GetHomeDirectory() + { + constexpr AZStd::string_view overrideHomeDirKey = "/Amazon/Settings/override_home_dir"; + AZ::IO::FixedMaxPathString overrideHomeDir; + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) { - abort(); - } - - void NativeErrorMessageBox(const char*, const char*) {} - - AZ::IO::FixedMaxPathString GetHomeDirectory() - { - constexpr AZStd::string_view overrideHomeDirKey = "/Amazon/Settings/override_home_dir"; - AZ::IO::FixedMaxPathString overrideHomeDir; - if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + if (settingsRegistry->Get(overrideHomeDir, overrideHomeDirKey)) { - if (settingsRegistry->Get(overrideHomeDir, overrideHomeDirKey)) - { - AZ::IO::FixedMaxPath path{overrideHomeDir}; - return path.Native(); - } - } - - if (const char* homePath = std::getenv("HOME"); homePath != nullptr) - { - AZ::IO::FixedMaxPath path{homePath}; + AZ::IO::FixedMaxPath path{overrideHomeDir}; return path.Native(); } - - struct passwd* pass = getpwuid(getuid()); - if (pass) - { - AZ::IO::FixedMaxPath path{pass->pw_dir}; - return path.Native(); - } - - return {}; } - bool ConvertToAbsolutePath(const char* path, char* absolutePath, AZ::u64 maxLength) + if (const char* homePath = std::getenv("HOME"); homePath != nullptr) { + AZ::IO::FixedMaxPath path{homePath}; + return path.Native(); + } + + struct passwd* pass = getpwuid(getuid()); + if (pass) + { + AZ::IO::FixedMaxPath path{pass->pw_dir}; + return path.Native(); + } + + return {}; + } + + bool ConvertToAbsolutePath(const char* path, char* absolutePath, AZ::u64 maxLength) + { #ifdef PATH_MAX - static constexpr size_t UnixMaxPathLength = PATH_MAX; + static constexpr size_t UnixMaxPathLength = PATH_MAX; #else - // Fallback to 4096 if the PATH_MAX macro isn't defined on the Unix System - static constexpr size_t UnixMaxPathLength = 4096; + // Fallback to 4096 if the PATH_MAX macro isn't defined on the Unix System + static constexpr size_t UnixMaxPathLength = 4096; #endif - if (!AZ::IO::PathView(path).IsAbsolute()) + if (!AZ::IO::PathView(path).IsAbsolute()) + { + // note that realpath fails if the path does not exist and actually changes the return value + // to be the actual place that FAILED, which we don't want. + // if we fail, we'd prefer to fall through and at least use the original path. + char absolutePathBuffer[UnixMaxPathLength]; + if (const char* result = realpath(path, absolutePathBuffer); result != nullptr) { - // note that realpath fails if the path does not exist and actually changes the return value - // to be the actual place that FAILED, which we don't want. - // if we fail, we'd prefer to fall through and at least use the original path. - char absolutePathBuffer[UnixMaxPathLength]; - if (const char* result = realpath(path, absolutePathBuffer); result != nullptr) - { - azstrcpy(absolutePath, maxLength, absolutePathBuffer); - return true; - } + azstrcpy(absolutePath, maxLength, absolutePathBuffer); + return true; } - azstrcpy(absolutePath, maxLength, path); - return AZ::IO::PathView(absolutePath).IsAbsolute(); } - } // namespace Utils -} // namespace AZ + azstrcpy(absolutePath, maxLength, path); + return AZ::IO::PathView(absolutePath).IsAbsolute(); + } +} // namespace AZ::Utils diff --git a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/std/parallel/internal/thread_UnixLike.cpp b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/std/parallel/internal/thread_UnixLike.cpp index b615f677f1..da5d95b9a4 100644 --- a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/std/parallel/internal/thread_UnixLike.cpp +++ b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/std/parallel/internal/thread_UnixLike.cpp @@ -35,7 +35,6 @@ namespace AZStd destroy_thread_info(ti); ThreadEventBus::Broadcast(&ThreadEventBus::Events::OnThreadExit, this_thread::get_id()); - ThreadDrillerEventBus::Broadcast(&ThreadDrillerEventBus::Events::OnThreadExit, this_thread::get_id()); pthread_exit(nullptr); return nullptr; } @@ -88,7 +87,6 @@ namespace AZStd Platform::PostCreateThread(tId, name, cpuId); ThreadEventBus::Broadcast(&ThreadEventBus::Events::OnThreadEnter, thread::id(tId), desc); - ThreadDrillerEventBus::Broadcast(&ThreadDrillerEventBus::Events::OnThreadEnter, thread::id(tId), desc); return tId; } } diff --git a/Code/Framework/AzCore/Platform/Common/UnixLikeDefault/AzCore/IO/SystemFile_UnixLikeDefault.cpp b/Code/Framework/AzCore/Platform/Common/UnixLikeDefault/AzCore/IO/SystemFile_UnixLikeDefault.cpp index 88c47ed142..7302232a49 100644 --- a/Code/Framework/AzCore/Platform/Common/UnixLikeDefault/AzCore/IO/SystemFile_UnixLikeDefault.cpp +++ b/Code/Framework/AzCore/Platform/Common/UnixLikeDefault/AzCore/IO/SystemFile_UnixLikeDefault.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include @@ -61,7 +60,6 @@ bool SystemFile::PlatformOpen(int mode, int platformFlags) } else { - EBUS_EVENT(FileIOEventBus, OnError, this, nullptr, 0); return false; } @@ -88,7 +86,6 @@ bool SystemFile::PlatformOpen(int mode, int platformFlags) if (m_handle == PlatformSpecificInvalidHandle) { - EBUS_EVENT(FileIOEventBus, OnError, this, nullptr, errno); return false; } else @@ -119,11 +116,7 @@ namespace Platform { if (handle != PlatformSpecificInvalidHandle) { - int result = lseek(handle, offset, mode); - if (result == -1) - { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, errno); - } + lseek(handle, offset, mode); } } @@ -132,10 +125,6 @@ namespace Platform if (handle != PlatformSpecificInvalidHandle) { off_t result = lseek(handle, 0, SEEK_CUR); - if (result == (off_t)-1) - { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, errno); - } return aznumeric_cast(result); } @@ -149,14 +138,12 @@ namespace Platform off_t current = lseek(handle, 0, SEEK_CUR); if (current == (off_t)-1) { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, current); return false; } off_t end = lseek(handle, 0, SEEK_END); if (end == (off_t)-1) { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, end); return false; } @@ -191,7 +178,6 @@ namespace Platform ssize_t bytesRead = read(handle, buffer, byteSize); if (bytesRead == -1) { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, errno); return 0; } return bytesRead; @@ -207,7 +193,6 @@ namespace Platform ssize_t result = write(handle, buffer, byteSize); if (result == -1) { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, errno); return 0; } return result; @@ -221,10 +206,7 @@ namespace Platform if (handle != PlatformSpecificInvalidHandle) { #if AZ_TRAIT_SYSTEMFILE_FSYNC_IS_DEFINED - if (fsync(handle) != 0) - { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, errno); - } + fsync(handle); #endif } } @@ -236,7 +218,6 @@ namespace Platform struct stat stat; if (fstat(handle, &stat) < 0) { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, 0); return 0; } return stat.st_size; diff --git a/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/Debug/Trace_WinAPI.cpp b/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/Debug/Trace_WinAPI.cpp index 28d3459ba3..d090971359 100644 --- a/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/Debug/Trace_WinAPI.cpp +++ b/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/Debug/Trace_WinAPI.cpp @@ -11,13 +11,12 @@ #include #include #include -#include #include #include #include -namespace AZ +namespace AZ::Debug { #if defined(AZ_ENABLE_DEBUG_TOOLS) LONG WINAPI ExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo); @@ -26,94 +25,91 @@ namespace AZ constexpr int g_maxMessageLength = 4096; - namespace Debug + namespace Platform { - namespace Platform - { #if defined(AZ_ENABLE_DEBUG_TOOLS) - bool IsDebuggerPresent() + bool IsDebuggerPresent() + { + return ::IsDebuggerPresent() ? true : false; + } + + void HandleExceptions(bool isEnabled) + { + if (isEnabled) { - return ::IsDebuggerPresent() ? true : false; + g_previousExceptionHandler = ::SetUnhandledExceptionFilter(&ExceptionHandler); } - - void HandleExceptions(bool isEnabled) + else { - if (isEnabled) - { - g_previousExceptionHandler = ::SetUnhandledExceptionFilter(&ExceptionHandler); - } - else - { - ::SetUnhandledExceptionFilter(g_previousExceptionHandler); - g_previousExceptionHandler = NULL; - } - } - - bool AttachDebugger() - { - if (IsDebuggerPresent()) - { - return true; - } - - // Launch vsjitdebugger.exe, this app is always present in System32 folder - // with an installation of any version of visual studio. - // It will open a debugging dialog asking the user what debugger to use - - STARTUPINFOW startupInfo = {0}; - startupInfo.cb = sizeof(startupInfo); - PROCESS_INFORMATION processInfo = {0}; - - wchar_t cmdline[MAX_PATH]; - swprintf_s(cmdline, L"vsjitdebugger.exe -p %li", ::GetCurrentProcessId()); - bool success = ::CreateProcessW( - NULL, // No module name (use command line) - cmdline, // Command line - NULL, // Process handle not inheritable - NULL, // Thread handle not inheritable - FALSE, // No handle inheritance - 0, // No creation flags - NULL, // Use parent's environment block - NULL, // Use parent's starting directory - &startupInfo, // Pointer to STARTUPINFO structure - &processInfo); // Pointer to PROCESS_INFORMATION structure - - if (success) - { - ::WaitForSingleObject(processInfo.hProcess, INFINITE); - ::CloseHandle(processInfo.hProcess); - ::CloseHandle(processInfo.hThread); - return true; - } - return false; - } - - void DebugBreak() - { - __debugbreak(); - } -#endif // AZ_ENABLE_DEBUG_TOOLS - - void Terminate(int exitCode) - { - TerminateProcess(GetCurrentProcess(), exitCode); - } - - void OutputToDebugger([[maybe_unused]] const char* window, const char* message) - { - AZStd::fixed_wstring tmpW; - if(window) - { - AZStd::to_wstring(tmpW, window); - tmpW += L": "; - OutputDebugStringW(tmpW.c_str()); - tmpW.clear(); - } - AZStd::to_wstring(tmpW, message); - OutputDebugStringW(tmpW.c_str()); + ::SetUnhandledExceptionFilter(g_previousExceptionHandler); + g_previousExceptionHandler = NULL; } } - } + + bool AttachDebugger() + { + if (IsDebuggerPresent()) + { + return true; + } + + // Launch vsjitdebugger.exe, this app is always present in System32 folder + // with an installation of any version of visual studio. + // It will open a debugging dialog asking the user what debugger to use + + STARTUPINFOW startupInfo = {0}; + startupInfo.cb = sizeof(startupInfo); + PROCESS_INFORMATION processInfo = {0}; + + wchar_t cmdline[MAX_PATH]; + swprintf_s(cmdline, L"vsjitdebugger.exe -p %li", ::GetCurrentProcessId()); + bool success = ::CreateProcessW( + NULL, // No module name (use command line) + cmdline, // Command line + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + FALSE, // No handle inheritance + 0, // No creation flags + NULL, // Use parent's environment block + NULL, // Use parent's starting directory + &startupInfo, // Pointer to STARTUPINFO structure + &processInfo); // Pointer to PROCESS_INFORMATION structure + + if (success) + { + ::WaitForSingleObject(processInfo.hProcess, INFINITE); + ::CloseHandle(processInfo.hProcess); + ::CloseHandle(processInfo.hThread); + return true; + } + return false; + } + + void DebugBreak() + { + __debugbreak(); + } +#endif // AZ_ENABLE_DEBUG_TOOLS + + void Terminate(int exitCode) + { + TerminateProcess(GetCurrentProcess(), exitCode); + } + + void OutputToDebugger([[maybe_unused]] const char* window, const char* message) + { + AZStd::fixed_wstring tmpW; + if(window) + { + AZStd::to_wstring(tmpW, window); + tmpW += L": "; + OutputDebugStringW(tmpW.c_str()); + tmpW.clear(); + } + AZStd::to_wstring(tmpW, message); + OutputDebugStringW(tmpW.c_str()); + } + } // namespace Platform #if defined(AZ_ENABLE_DEBUG_TOOLS) @@ -187,7 +183,7 @@ namespace AZ azsnprintf(message, g_maxMessageLength, "Exception : 0x%lX - '%s' [%p]\n", ExceptionInfo->ExceptionRecord->ExceptionCode, GetExeptionName(ExceptionInfo->ExceptionRecord->ExceptionCode), ExceptionInfo->ExceptionRecord->ExceptionAddress); Debug::Trace::Instance().Output(nullptr, message); - EBUS_EVENT(Debug::TraceMessageDrillerBus, OnException, message); + Debug::Trace::Instance().PrintCallstack(nullptr, 0, ExceptionInfo->ContextRecord); bool result = false; EBUS_EVENT_RESULT(result, Debug::TraceMessageBus, OnException, message); @@ -198,7 +194,7 @@ namespace AZ // if someone ever returns TRUE we assume that they somehow handled this exception and continue. return EXCEPTION_CONTINUE_EXECUTION; } - Debug::Trace::Instance().PrintCallstack(nullptr, 0, ExceptionInfo->ContextRecord); + Debug::Trace::Instance().Output(nullptr, "==================================================================\n"); // allowing continue of execution is not valid here. This handler gets called for serious exceptions. @@ -211,4 +207,4 @@ namespace AZ } #endif -} +} // namspace AZ::Debug diff --git a/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/IO/SystemFile_WinAPI.cpp b/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/IO/SystemFile_WinAPI.cpp index 0fe32dcd4a..d608d67a85 100644 --- a/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/IO/SystemFile_WinAPI.cpp +++ b/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/IO/SystemFile_WinAPI.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include @@ -142,7 +141,6 @@ bool SystemFile::PlatformOpen(int mode, int platformFlags) if (m_handle == INVALID_HANDLE_VALUE) { - EBUS_EVENT(FileIOEventBus, OnError, this, nullptr, (int)GetLastError()); return false; } else @@ -160,10 +158,7 @@ void SystemFile::PlatformClose() { if (m_handle != PlatformSpecificInvalidHandle) { - if (!CloseHandle(m_handle)) - { - EBUS_EVENT(FileIOEventBus, OnError, this, nullptr, (int)GetLastError()); - } + CloseHandle(m_handle); m_handle = INVALID_HANDLE_VALUE; } } @@ -177,7 +172,7 @@ namespace Platform { using FileHandleType = AZ::IO::SystemFile::FileHandleType; - void Seek(FileHandleType handle, const SystemFile* systemFile, SystemFile::SeekSizeType offset, SystemFile::SeekMode mode) + void Seek(FileHandleType handle, [[maybe_unused]] const SystemFile* systemFile, SystemFile::SeekSizeType offset, SystemFile::SeekMode mode) { if (handle != PlatformSpecificInvalidHandle) { @@ -185,14 +180,11 @@ namespace Platform LARGE_INTEGER distToMove; distToMove.QuadPart = offset; - if (!SetFilePointerEx(handle, distToMove, 0, dwMoveMethod)) - { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, (int)GetLastError()); - } + SetFilePointerEx(handle, distToMove, 0, dwMoveMethod); } } - SystemFile::SizeType Tell(FileHandleType handle, const SystemFile* systemFile) + SystemFile::SizeType Tell(FileHandleType handle, [[maybe_unused]] const SystemFile* systemFile) { if (handle != PlatformSpecificInvalidHandle) { @@ -202,7 +194,6 @@ namespace Platform LARGE_INTEGER newFilePtr; if (!SetFilePointerEx(handle, distToMove, &newFilePtr, FILE_CURRENT)) { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, (int)GetLastError()); return 0; } @@ -212,7 +203,7 @@ namespace Platform return 0; } - bool Eof(FileHandleType handle, const SystemFile* systemFile) + bool Eof(FileHandleType handle, [[maybe_unused]] const SystemFile* systemFile) { if (handle != PlatformSpecificInvalidHandle) { @@ -222,14 +213,12 @@ namespace Platform LARGE_INTEGER currentFilePtr; if (!SetFilePointerEx(handle, zero, ¤tFilePtr, FILE_CURRENT)) { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, (int)GetLastError()); return false; } FILE_STANDARD_INFO fileInfo; if (!GetFileInformationByHandleEx(handle, FileStandardInfo, &fileInfo, sizeof(fileInfo))) { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, (int)GetLastError()); return false; } @@ -239,14 +228,13 @@ namespace Platform return false; } - AZ::u64 ModificationTime(FileHandleType handle, const SystemFile* systemFile) + AZ::u64 ModificationTime(FileHandleType handle, [[maybe_unused]] const SystemFile* systemFile) { if (handle != PlatformSpecificInvalidHandle) { FILE_BASIC_INFO fileInfo; if (!GetFileInformationByHandleEx(handle, FileBasicInfo, &fileInfo, sizeof(fileInfo))) { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, (int)GetLastError()); return 0; } @@ -263,7 +251,7 @@ namespace Platform return 0; } - SystemFile::SizeType Read(FileHandleType handle, const SystemFile* systemFile, SizeType byteSize, void* buffer) + SystemFile::SizeType Read(FileHandleType handle, [[maybe_unused]] const SystemFile* systemFile, SizeType byteSize, void* buffer) { if (handle != PlatformSpecificInvalidHandle) { @@ -271,7 +259,6 @@ namespace Platform DWORD nNumberOfBytesToRead = (DWORD)byteSize; if (!ReadFile(handle, buffer, nNumberOfBytesToRead, &dwNumBytesRead, 0)) { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, (int)GetLastError()); return 0; } return static_cast(dwNumBytesRead); @@ -280,7 +267,7 @@ namespace Platform return 0; } - SystemFile::SizeType Write(FileHandleType handle, const SystemFile* systemFile, const void* buffer, SizeType byteSize) + SystemFile::SizeType Write(FileHandleType handle, [[maybe_unused]] const SystemFile* systemFile, const void* buffer, SizeType byteSize) { if (handle != PlatformSpecificInvalidHandle) { @@ -288,7 +275,6 @@ namespace Platform DWORD nNumberOfBytesToWrite = (DWORD)byteSize; if (!WriteFile(handle, buffer, nNumberOfBytesToWrite, &dwNumBytesWritten, 0)) { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, (int)GetLastError()); return 0; } return static_cast(dwNumBytesWritten); @@ -297,25 +283,21 @@ namespace Platform return 0; } - void Flush(FileHandleType handle, const SystemFile* systemFile) + void Flush(FileHandleType handle, [[maybe_unused]] const SystemFile* systemFile) { if (handle != PlatformSpecificInvalidHandle) { - if (!FlushFileBuffers(handle)) - { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, (int)GetLastError()); - } + FlushFileBuffers(handle); } } - SystemFile::SizeType Length(FileHandleType handle, const SystemFile* systemFile) + SystemFile::SizeType Length(FileHandleType handle, [[maybe_unused]] const SystemFile* systemFile) { if (handle != PlatformSpecificInvalidHandle) { LARGE_INTEGER size; if (!GetFileSizeEx(handle, &size)) { - EBUS_EVENT(FileIOEventBus, OnError, systemFile, nullptr, (int)GetLastError()); return 0; } @@ -341,7 +323,6 @@ namespace Platform { WIN32_FIND_DATA fd; HANDLE hFile; - int lastError; AZ::IO::FixedMaxPathWString filterW; AZStd::to_wstring(filterW, filter); @@ -367,20 +348,7 @@ namespace Platform cb(fileName, (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0); } - lastError = (int)GetLastError(); FindClose(hFile); - if (lastError != ERROR_NO_MORE_FILES) - { - EBUS_EVENT(FileIOEventBus, OnError, nullptr, fileName, lastError); - } - } - else - { - lastError = (int)GetLastError(); - if (lastError != ERROR_FILE_NOT_FOUND) - { - EBUS_EVENT(FileIOEventBus, OnError, nullptr, filter, lastError); - } } } @@ -394,15 +362,11 @@ namespace Platform if (handle == INVALID_HANDLE_VALUE) { - EBUS_EVENT(FileIOEventBus, OnError, nullptr, fileName, (int)GetLastError()); return 0; } FILE_BASIC_INFO fileInfo{}; - if (!GetFileInformationByHandleEx(handle, FileBasicInfo, &fileInfo, sizeof(fileInfo))) - { - EBUS_EVENT(FileIOEventBus, OnError, nullptr, fileName, (int)GetLastError()); - } + GetFileInformationByHandleEx(handle, FileBasicInfo, &fileInfo, sizeof(fileInfo)); CloseHandle(handle); @@ -434,10 +398,6 @@ namespace Platform fileSize.HighPart = data.nFileSizeHigh; len = aznumeric_cast(fileSize.QuadPart); } - else - { - EBUS_EVENT(FileIOEventBus, OnError, nullptr, fileName, (int)GetLastError()); - } return len; } @@ -448,7 +408,6 @@ namespace Platform AZStd::to_wstring(fileNameW, fileName); if (DeleteFileW(fileNameW.c_str()) == 0) { - EBUS_EVENT(FileIOEventBus, OnError, nullptr, fileName, (int)GetLastError()); return false; } @@ -463,7 +422,6 @@ namespace Platform AZStd::to_wstring(targetFileNameW, targetFileName); if (MoveFileExW(sourceFileNameW.c_str(), targetFileNameW.c_str(), overwrite ? MOVEFILE_REPLACE_EXISTING : 0) == 0) { - EBUS_EVENT(FileIOEventBus, OnError, nullptr, sourceFileName, (int)GetLastError()); return false; } @@ -503,10 +461,6 @@ namespace Platform AZ::IO::FixedMaxPathWString dirNameW; AZStd::to_wstring(dirNameW, dirName); bool success = CreateDirRecursive(dirNameW); - if (!success) - { - EBUS_EVENT(FileIOEventBus, OnError, nullptr, dirName, (int)GetLastError()); - } return success; } return false; diff --git a/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/std/parallel/internal/thread_WinAPI.cpp b/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/std/parallel/internal/thread_WinAPI.cpp index c84b4ecd98..cd638a34fa 100644 --- a/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/std/parallel/internal/thread_WinAPI.cpp +++ b/Code/Framework/AzCore/Platform/Common/WinAPI/AzCore/std/parallel/internal/thread_WinAPI.cpp @@ -38,7 +38,6 @@ namespace AZStd destroy_thread_info(ti); ThreadEventBus::Broadcast(&ThreadEventBus::Events::OnThreadExit, this_thread::get_id()); // goes to client listeners - ThreadDrillerEventBus::Broadcast(&ThreadDrillerEventBus::Events::OnThreadExit, this_thread::get_id()); // goes to the profiler. return Platform::PostThreadRun(); } @@ -73,7 +72,6 @@ namespace AZStd } ThreadEventBus::Broadcast(&ThreadEventBus::Events::OnThreadEnter, thread::id(*id), desc); - ThreadDrillerEventBus::Broadcast(&ThreadDrillerEventBus::Events::OnThreadEnter, thread::id(*id), desc); ::ResumeThread(hThread); diff --git a/Code/Framework/AzCore/Platform/Linux/AzCore/AzCore_Traits_Linux.h b/Code/Framework/AzCore/Platform/Linux/AzCore/AzCore_Traits_Linux.h index 2c0d554078..59d5f3c5ed 100644 --- a/Code/Framework/AzCore/Platform/Linux/AzCore/AzCore_Traits_Linux.h +++ b/Code/Framework/AzCore/Platform/Linux/AzCore/AzCore_Traits_Linux.h @@ -53,10 +53,8 @@ // Compiler traits ... #define AZ_TRAIT_COMPILER_DEFINE_AZSWNPRINTF_AS_SWPRINTF 1 #define AZ_TRAIT_COMPILER_DEFINE_FS_ERRNO_TYPE 1 -#define AZ_TRAIT_COMPILER_DEFINE_FS_STAT_TYPE 0 #define AZ_TRAIT_COMPILER_DEFINE_GETCURRENTPROCESSID 1 #define AZ_TRAIT_COMPILER_DEFINE_REFGUID 1 -#define AZ_TRAIT_COMPILER_DEFINE_SASSERTDATA_TYPE 1 #define AZ_TRAIT_COMPILER_DEFINE_WCSICMP 0 #define AZ_TRAIT_COMPILER_INT64_T_IS_LONG 1 #define AZ_TRAIT_COMPILER_OPTIMIZE_MISSING_DEFAULT_SWITCH_CASE 1 diff --git a/Code/Framework/AzCore/Platform/Linux/AzCore/Debug/Trace_Linux.cpp b/Code/Framework/AzCore/Platform/Linux/AzCore/Debug/Trace_Linux.cpp index 1ca06fcf7f..e4cd0d0db4 100644 --- a/Code/Framework/AzCore/Platform/Linux/AzCore/Debug/Trace_Linux.cpp +++ b/Code/Framework/AzCore/Platform/Linux/AzCore/Debug/Trace_Linux.cpp @@ -9,16 +9,10 @@ #include #include -namespace AZ +namespace AZ::Debug::Platform { - namespace Debug + void OutputToDebugger([[maybe_unused]] const char* title, [[maybe_unused]] const char* message) { - namespace Platform - { - void OutputToDebugger([[maybe_unused]] const char* title, [[maybe_unused]] const char* message) - { - // std::cout << title << ": " << message; - } - } + // std::cout << title << ": " << message; } -} +} // namespace AZ::Debug::Platform diff --git a/Code/Framework/AzCore/Platform/Linux/AzCore/IO/SystemFile_Linux.cpp b/Code/Framework/AzCore/Platform/Linux/AzCore/IO/SystemFile_Linux.cpp index be5cbc3859..deb2d49516 100644 --- a/Code/Framework/AzCore/Platform/Linux/AzCore/IO/SystemFile_Linux.cpp +++ b/Code/Framework/AzCore/Platform/Linux/AzCore/IO/SystemFile_Linux.cpp @@ -7,9 +7,9 @@ */ #include -#include #include #include +#include #include #include <../Common/UnixLike/AzCore/IO/Internal/SystemFileUtils_UnixLike.h> @@ -44,21 +44,7 @@ namespace AZ::IO::Platform entry = readdir(dir); } - int lastError = errno; - if (lastError != 0) - { - EBUS_EVENT(FileIOEventBus, OnError, nullptr, filter, lastError); - } - closedir(dir); } - else - { - int lastError = errno; - if (lastError != ENOENT) - { - EBUS_EVENT(FileIOEventBus, OnError, nullptr, filter, 0); - } - } } } diff --git a/Code/Framework/AzCore/Platform/Linux/AzCore/Memory/HeapSchema_Linux.cpp b/Code/Framework/AzCore/Platform/Linux/AzCore/Memory/HeapSchema_Linux.cpp index e28832cf61..d9defd5acc 100644 --- a/Code/Framework/AzCore/Platform/Linux/AzCore/Memory/HeapSchema_Linux.cpp +++ b/Code/Framework/AzCore/Platform/Linux/AzCore/Memory/HeapSchema_Linux.cpp @@ -8,13 +8,10 @@ #include -namespace AZ +namespace AZ::Platform { - namespace Platform + size_t GetHeapCapacity() { - size_t GetHeapCapacity() - { - return 0; - } + return 0; } -} +} // namespace AZ::Platform diff --git a/Code/Framework/AzCore/Platform/Linux/AzCore/Module/DynamicModuleHandle_Linux.cpp b/Code/Framework/AzCore/Platform/Linux/AzCore/Module/DynamicModuleHandle_Linux.cpp index 62718c2d46..aee2bdb622 100644 --- a/Code/Framework/AzCore/Platform/Linux/AzCore/Module/DynamicModuleHandle_Linux.cpp +++ b/Code/Framework/AzCore/Platform/Linux/AzCore/Module/DynamicModuleHandle_Linux.cpp @@ -10,28 +10,25 @@ #include #include -namespace AZ +namespace AZ::Platform { - namespace Platform + AZ::IO::FixedMaxPath GetModulePath() { - AZ::IO::FixedMaxPath GetModulePath() - { - return AZ::Utils::GetExecutableDirectory(); - } - - void* OpenModule(const AZ::OSString& fileName, bool& alreadyOpen) - { - void* handle = dlopen(fileName.c_str(), RTLD_NOLOAD); - alreadyOpen = (handle != nullptr); - if (!alreadyOpen) - { - handle = dlopen(fileName.c_str(), RTLD_NOW); - } - return handle; - } - - void ConstructModuleFullFileName(AZ::IO::FixedMaxPath&) - { - } + return AZ::Utils::GetExecutableDirectory(); } -} + + void* OpenModule(const AZ::OSString& fileName, bool& alreadyOpen) + { + void* handle = dlopen(fileName.c_str(), RTLD_NOLOAD); + alreadyOpen = (handle != nullptr); + if (!alreadyOpen) + { + handle = dlopen(fileName.c_str(), RTLD_NOW); + } + return handle; + } + + void ConstructModuleFullFileName(AZ::IO::FixedMaxPath&) + { + } +} // namespace AZ::Platform diff --git a/Code/Framework/AzCore/Platform/Linux/AzCore/Module/Internal/ModuleManagerSearchPathTool_Linux.cpp b/Code/Framework/AzCore/Platform/Linux/AzCore/Module/Internal/ModuleManagerSearchPathTool_Linux.cpp index 39b4cbd4ea..9dfa08e554 100644 --- a/Code/Framework/AzCore/Platform/Linux/AzCore/Module/Internal/ModuleManagerSearchPathTool_Linux.cpp +++ b/Code/Framework/AzCore/Platform/Linux/AzCore/Module/Internal/ModuleManagerSearchPathTool_Linux.cpp @@ -8,20 +8,17 @@ #include -namespace AZ +namespace AZ::Internal { - namespace Internal + ModuleManagerSearchPathTool::ModuleManagerSearchPathTool() { - ModuleManagerSearchPathTool::ModuleManagerSearchPathTool() - { - } + } - ModuleManagerSearchPathTool::~ModuleManagerSearchPathTool() - { - } + ModuleManagerSearchPathTool::~ModuleManagerSearchPathTool() + { + } - void ModuleManagerSearchPathTool::SetModuleSearchPath(const AZ::DynamicModuleDescriptor&) - { - } - } // namespace Internal -} // namespace AZ + void ModuleManagerSearchPathTool::SetModuleSearchPath(const AZ::DynamicModuleDescriptor&) + { + } +} // namespace AZ::Internal diff --git a/Code/Framework/AzCore/Platform/Linux/AzCore/Utils/Utils_Linux.cpp b/Code/Framework/AzCore/Platform/Linux/AzCore/Utils/Utils_Linux.cpp index d75eab0139..ed4e2674c5 100644 --- a/Code/Framework/AzCore/Platform/Linux/AzCore/Utils/Utils_Linux.cpp +++ b/Code/Framework/AzCore/Platform/Linux/AzCore/Utils/Utils_Linux.cpp @@ -13,42 +13,39 @@ #include -namespace AZ +namespace AZ::Utils { - namespace Utils + GetExecutablePathReturnType GetExecutablePath(char* exeStorageBuffer, size_t exeStorageSize) { - GetExecutablePathReturnType GetExecutablePath(char* exeStorageBuffer, size_t exeStorageSize) + GetExecutablePathReturnType result; + result.m_pathIncludesFilename = true; + + // http://man7.org/linux/man-pages/man5/proc.5.html + const ssize_t bytesWritten = readlink("/proc/self/exe", exeStorageBuffer, exeStorageSize); + if (bytesWritten == -1) { - GetExecutablePathReturnType result; - result.m_pathIncludesFilename = true; - - // http://man7.org/linux/man-pages/man5/proc.5.html - const ssize_t bytesWritten = readlink("/proc/self/exe", exeStorageBuffer, exeStorageSize); - if (bytesWritten == -1) - { - result.m_pathStored = ExecutablePathResult::GeneralError; - } - else if (bytesWritten == exeStorageSize) - { - result.m_pathStored = ExecutablePathResult::BufferSizeNotLargeEnough; - } - else - { - // readlink doesn't null terminate - exeStorageBuffer[bytesWritten] = '\0'; - } - - return result; + result.m_pathStored = ExecutablePathResult::GeneralError; + } + else if (bytesWritten == exeStorageSize) + { + result.m_pathStored = ExecutablePathResult::BufferSizeNotLargeEnough; + } + else + { + // readlink doesn't null terminate + exeStorageBuffer[bytesWritten] = '\0'; } - AZStd::optional GetDefaultAppRootPath() - { - return AZStd::nullopt; - } - - AZStd::optional GetDevWriteStoragePath() - { - return AZStd::nullopt; - } + return result; } -} + + AZStd::optional GetDefaultAppRootPath() + { + return AZStd::nullopt; + } + + AZStd::optional GetDevWriteStoragePath() + { + return AZStd::nullopt; + } +} // namespace AZ::Utils diff --git a/Code/Framework/AzCore/Platform/Mac/AzCore/AzCore_Traits_Mac.h b/Code/Framework/AzCore/Platform/Mac/AzCore/AzCore_Traits_Mac.h index 816a28b728..1a3d0663e1 100644 --- a/Code/Framework/AzCore/Platform/Mac/AzCore/AzCore_Traits_Mac.h +++ b/Code/Framework/AzCore/Platform/Mac/AzCore/AzCore_Traits_Mac.h @@ -53,10 +53,8 @@ // Compiler traits ... #define AZ_TRAIT_COMPILER_DEFINE_AZSWNPRINTF_AS_SWPRINTF 1 #define AZ_TRAIT_COMPILER_DEFINE_FS_ERRNO_TYPE 1 -#define AZ_TRAIT_COMPILER_DEFINE_FS_STAT_TYPE 1 #define AZ_TRAIT_COMPILER_DEFINE_GETCURRENTPROCESSID 1 #define AZ_TRAIT_COMPILER_DEFINE_REFGUID 1 -#define AZ_TRAIT_COMPILER_DEFINE_SASSERTDATA_TYPE 1 #define AZ_TRAIT_COMPILER_DEFINE_WCSICMP 0 #define AZ_TRAIT_COMPILER_INT64_T_IS_LONG 0 #define AZ_TRAIT_COMPILER_OPTIMIZE_MISSING_DEFAULT_SWITCH_CASE 1 diff --git a/Code/Framework/AzCore/Platform/Windows/AzCore/AzCore_Traits_Windows.h b/Code/Framework/AzCore/Platform/Windows/AzCore/AzCore_Traits_Windows.h index 83cff9b54f..1a83aba267 100644 --- a/Code/Framework/AzCore/Platform/Windows/AzCore/AzCore_Traits_Windows.h +++ b/Code/Framework/AzCore/Platform/Windows/AzCore/AzCore_Traits_Windows.h @@ -53,10 +53,8 @@ // Compiler traits ... #define AZ_TRAIT_COMPILER_DEFINE_AZSWNPRINTF_AS_SWPRINTF 0 #define AZ_TRAIT_COMPILER_DEFINE_FS_ERRNO_TYPE 0 -#define AZ_TRAIT_COMPILER_DEFINE_FS_STAT_TYPE 0 #define AZ_TRAIT_COMPILER_DEFINE_GETCURRENTPROCESSID 0 #define AZ_TRAIT_COMPILER_DEFINE_REFGUID 0 -#define AZ_TRAIT_COMPILER_DEFINE_SASSERTDATA_TYPE 0 #define AZ_TRAIT_COMPILER_DEFINE_WCSICMP 0 #define AZ_TRAIT_COMPILER_INT64_T_IS_LONG 0 #define AZ_TRAIT_COMPILER_OPTIMIZE_MISSING_DEFAULT_SWITCH_CASE 1 diff --git a/Code/Framework/AzCore/Platform/iOS/AzCore/AzCore_Traits_iOS.h b/Code/Framework/AzCore/Platform/iOS/AzCore/AzCore_Traits_iOS.h index b0832c54f6..7a75af71fb 100644 --- a/Code/Framework/AzCore/Platform/iOS/AzCore/AzCore_Traits_iOS.h +++ b/Code/Framework/AzCore/Platform/iOS/AzCore/AzCore_Traits_iOS.h @@ -53,10 +53,8 @@ // Compiler traits ... #define AZ_TRAIT_COMPILER_DEFINE_AZSWNPRINTF_AS_SWPRINTF 1 #define AZ_TRAIT_COMPILER_DEFINE_FS_ERRNO_TYPE 1 -#define AZ_TRAIT_COMPILER_DEFINE_FS_STAT_TYPE 1 #define AZ_TRAIT_COMPILER_DEFINE_GETCURRENTPROCESSID 1 #define AZ_TRAIT_COMPILER_DEFINE_REFGUID 1 -#define AZ_TRAIT_COMPILER_DEFINE_SASSERTDATA_TYPE 1 #define AZ_TRAIT_COMPILER_DEFINE_WCSICMP 0 #define AZ_TRAIT_COMPILER_INT64_T_IS_LONG 0 #define AZ_TRAIT_COMPILER_OPTIMIZE_MISSING_DEFAULT_SWITCH_CASE 1 diff --git a/Code/Framework/AzCore/Tests/AZStd/Parallel.cpp b/Code/Framework/AzCore/Tests/AZStd/Parallel.cpp index 6b9202133c..362560c7be 100644 --- a/Code/Framework/AzCore/Tests/AZStd/Parallel.cpp +++ b/Code/Framework/AzCore/Tests/AZStd/Parallel.cpp @@ -1392,7 +1392,7 @@ namespace UnitTest EXPECT_TRUE(done); } - // Fixture for thread-driller-bus related calls + // Fixture for thread-event-bus related calls // exists only to categorize the tests. class ThreadEventsBus : public AllocatorsFixture @@ -1438,44 +1438,33 @@ namespace UnitTest TEST_F(ThreadEventsBus, Broadcasts_BothBusses) { ThreadEventCounter eventBusCounter; - ThreadEventCounter drillerBusCounter; auto thread_function = [&]() { ; // intentionally left blank }; eventBusCounter.Connect(); - drillerBusCounter.Connect(); AZStd::thread starter = AZStd::thread(thread_function); starter.join(); - EXPECT_EQ(drillerBusCounter.m_enterCount, 1); - EXPECT_EQ(drillerBusCounter.m_exitCount, 1); EXPECT_EQ(eventBusCounter.m_enterCount, 1); EXPECT_EQ(eventBusCounter.m_exitCount, 1); eventBusCounter.Disconnect(); - drillerBusCounter.Disconnect(); } - // this class tests for deadlocks caused by interactions between the thread - // driller bus and the other driller busses. - // Client code (ie, not part of the driller system) can connec to the - // ThreadEventBus and be told when threads are started and stopped - // However, if they instead listen to the ThreadDrillerEventBus, a deadlock condition - // could be caused if they lock a mutex that another thread needs in order to proceed. - // This test makes sure that using the ThreadEventBus (ie, the one meant for client code) - // instead of the ThreadDrillerEventBus (the one meant only for profilers) does NOT cause - // a deadlock. + // This class tests for deadlocks caused by multiple threads interacting with the ThreadEventBus. + // Client code can connect to the ThreadEventBus and be told when threads are started and stopped. + // A deadlock condition could be caused if they lock a mutex that another thread needs in order to proceed. + // This test makes sure that using the ThreadEventBus does NOT cause a deadlock. // We will simulate this series of events by doing the following // 1. Main thread listens on the ThreadEventBus // 2. OnThreadExit will lock a mutex, perform an allocation, unlock a mutex // 3. The thread itself will lock the mutex, perform an allocation, unlock the mutex. - // As long as there is no cross talk between the client and the driller busses, the - // above operation should not deadlock. - // but if there is, then a deadlock can occur where one thread will be unable to perform - // its allocation because the other is in OnThreadExit() - // and the other will not be able to perform OnThreadExit() because it cannot lock the mutex. + // As long as there is no cross talk between threads, the above operation should not deadlock. + // If there is, then a deadlock can occur where one thread will be unable to perform + // its allocation because the other is in OnThreadExit() and the other will not be able to perform + // OnThreadExit() because it cannot lock the mutex. class ThreadEventsDeathTest : public AllocatorsFixture diff --git a/Code/Framework/AzCore/Tests/AZStd/String.cpp b/Code/Framework/AzCore/Tests/AZStd/String.cpp index 68bdd311d5..356310a94d 100644 --- a/Code/Framework/AzCore/Tests/AZStd/String.cpp +++ b/Code/Framework/AzCore/Tests/AZStd/String.cpp @@ -2376,17 +2376,52 @@ namespace UnitTest static_assert(AZStd::wildcard_match_case(filter1, blahValue)); } - TEST_F(String, StringEraseIf_Succeeds) + TEST_F(String, StringCXX20Erase_Succeeds) { - AZStd::string eraseIfTest = "ABC CBA"; - auto eraseCount = AZStd::erase_if(eraseIfTest, [](AZStd::string::value_type ch) - { - return ch == 'C'; - }); + auto erasePredicate = [](AZStd::string::value_type ch) + { + return ch == 'C'; + }; + auto eraseCount = AZStd::erase_if(eraseIfTest, erasePredicate); EXPECT_EQ(2, eraseCount); EXPECT_EQ(5, eraseIfTest.size()); EXPECT_STREQ("AB BA", eraseIfTest.c_str()); + + // Now erase the letter 'A'; + eraseCount = AZStd::erase(eraseIfTest, 'A'); + EXPECT_EQ(2, eraseCount); + EXPECT_EQ(3, eraseIfTest.size()); + EXPECT_EQ("B B", eraseIfTest); + } + + TEST_F(String, FixedStringCXX20Erase_Succeeds) + { + // Erase 'l' from the phrase "Hello" World" + constexpr auto eraseTest = [](const char* testString) constexpr + { + AZStd::fixed_string<16> testResult{ testString }; + AZStd::erase(testResult, 'l'); + return testResult; + }("HelloWorld"); + + static_assert(eraseTest == "HeoWord"); + EXPECT_EQ("HeoWord", eraseTest); + + // Use erase_if to erase both 'H' and 'e' from the remaining eraseTest string + constexpr auto eraseIfTest = [](AZStd::string_view testString) constexpr + { + AZStd::fixed_string<16> testResult{ testString }; + auto erasePredicate = [](char ch) + { + return ch == 'H' || ch == 'e'; + }; + AZStd::erase_if(testResult, erasePredicate); + return testResult; + }(eraseTest); + + static_assert(eraseIfTest == "oWord"); + EXPECT_EQ("oWord", eraseIfTest); } template diff --git a/Code/Framework/AzCore/Tests/AZStd/TypeTraits.cpp b/Code/Framework/AzCore/Tests/AZStd/TypeTraits.cpp index da3aae1387..aeba048d69 100644 --- a/Code/Framework/AzCore/Tests/AZStd/TypeTraits.cpp +++ b/Code/Framework/AzCore/Tests/AZStd/TypeTraits.cpp @@ -8,6 +8,7 @@ #include "UserTypes.h" #include +#include #include #include #include diff --git a/Code/Framework/AzCore/Tests/AZStd/VectorAndArray.cpp b/Code/Framework/AzCore/Tests/AZStd/VectorAndArray.cpp index 47c3630091..851a7a8609 100644 --- a/Code/Framework/AzCore/Tests/AZStd/VectorAndArray.cpp +++ b/Code/Framework/AzCore/Tests/AZStd/VectorAndArray.cpp @@ -753,7 +753,7 @@ namespace UnitTest TEST_F(Arrays, FixedVectorCanCopyAndMoveWithDifferentCapacity) { - constexpr AZStd::fixed_vector sourceVector{ 1,2,3,4,5 }; + AZStd::fixed_vector sourceVector{ 1,2,3,4,5 }; AZStd::fixed_vector copyConstructVector{ sourceVector }; EXPECT_EQ(sourceVector, copyConstructVector); @@ -768,32 +768,63 @@ namespace UnitTest AZStd::fixed_vector moveAssignVector = AZStd::move(moveConstructVector); - constexpr AZStd::fixed_vector expectedVector{ 1,2,3,4,5,6 }; + AZStd::fixed_vector expectedVector{ 1,2,3,4,5,6 }; EXPECT_EQ(expectedVector, moveAssignVector); } TEST_F(Arrays, FixedVectorComparisonOperatorsSucceedAsExpected) { - constexpr AZStd::fixed_vector testVector{ 1,2,3,4,5 }; - constexpr AZStd::fixed_vector equalVector{ 1,2,3,4,5 }; - constexpr AZStd::fixed_vector notEqualVectorDifferentSize{ 1,2,3,4,5,6 }; - constexpr AZStd::fixed_vector lessVector{ 1,2,3,4,4 }; - constexpr AZStd::fixed_vector greaterVectorDifferentSize{ 1,2,3,4,5, 1 }; + AZStd::fixed_vector testVector{ 1,2,3,4,5 }; + AZStd::fixed_vector equalVector{ 1,2,3,4,5 }; + AZStd::fixed_vector notEqualVectorDifferentSize{ 1,2,3,4,5,6 }; + AZStd::fixed_vector lessVector{ 1,2,3,4,4 }; + AZStd::fixed_vector greaterVectorDifferentSize{ 1,2,3,4,5, 1 }; - static_assert(testVector == equalVector); - static_assert(testVector != notEqualVectorDifferentSize); - static_assert(testVector != lessVector); - static_assert(lessVector < testVector); - static_assert(lessVector < greaterVectorDifferentSize); - static_assert(lessVector <= lessVector); - static_assert(lessVector <= testVector); - static_assert(lessVector <= greaterVectorDifferentSize); - static_assert(testVector > lessVector); - static_assert(testVector > lessVector); - static_assert(notEqualVectorDifferentSize > testVector); - static_assert(testVector >= testVector); - static_assert(testVector >= lessVector); - static_assert(greaterVectorDifferentSize > lessVector); + EXPECT_EQ(testVector, equalVector); + EXPECT_NE(testVector, notEqualVectorDifferentSize); + EXPECT_NE(testVector, lessVector); + EXPECT_LT(lessVector, testVector); + EXPECT_LT(lessVector, greaterVectorDifferentSize); + EXPECT_LE(lessVector, lessVector); + EXPECT_LE(lessVector, testVector); + EXPECT_LE(lessVector, greaterVectorDifferentSize); + EXPECT_GT(testVector, lessVector); + EXPECT_GT(testVector, lessVector); + EXPECT_GT(notEqualVectorDifferentSize, testVector); + EXPECT_GE(testVector, testVector); + EXPECT_GE(testVector, lessVector); + EXPECT_GT(greaterVectorDifferentSize, lessVector); + } + + TEST_F(Arrays, FixedVectorCXX20Erase_Succeeds) + { + // Erase 'l' from the phrase "Hello" World" + auto eraseTest = [](AZStd::initializer_list testInit) + { + AZStd::fixed_vector testResult{ testInit }; + AZStd::erase(testResult, 'l'); + return testResult; + }({ 'H', 'e', 'l', 'l', 'o', 'W', 'o', 'r', 'l', 'd' }); + + constexpr AZStd::string_view expectedEraseString = "HeoWord"; + AZStd::string_view testEraseString{ eraseTest.begin(), eraseTest.end() }; + EXPECT_EQ(expectedEraseString, testEraseString); + + // Use erase_if to erase both 'H' and 'e' from the remaining eraseTest string + auto eraseIfTest = [](const AZStd::fixed_vector& testVector) + { + AZStd::fixed_vector testResult{ testVector }; + auto erasePredicate = [](char ch) + { + return ch == 'H' || ch == 'e'; + }; + AZStd::erase_if(testResult, erasePredicate); + return testResult; + }(testEraseString); + + constexpr AZStd::string_view expectedEraseIfString = "oWord"; + AZStd::string_view testEraseIfString{ eraseIfTest.begin(), eraseIfTest.end() }; + EXPECT_EQ(expectedEraseIfString, testEraseIfString); } TEST_F(Arrays, VectorSwap) diff --git a/Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp b/Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp index fdacd80542..c3be9b886a 100644 --- a/Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp +++ b/Code/Framework/AzCore/Tests/Asset/AssetManagerLoadingTests.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -131,8 +132,8 @@ namespace UnitTest * This will test the aspect of the system where ObjectStreams and asset jobs loading dependent * assets will do the work in their own thread. */ - class AssetJobsFloodTest - : public BaseAssetManagerTest + + class AssetJobsFloodTest : public DisklessAssetManagerBase { public: TestAssetManager* m_testAssetManager{ nullptr }; @@ -183,15 +184,14 @@ namespace UnitTest void SetUp() override { - BaseAssetManagerTest::SetUp(); + DisklessAssetManagerBase::SetUp(); SetupTest(); } void TearDown() override { - TearDownTest(); AssetManager::Destroy(); - BaseAssetManagerTest::TearDown(); + DisklessAssetManagerBase::TearDown(); } void SetupAssets() @@ -257,9 +257,9 @@ namespace UnitTest AssetWithSerializedData ap2; AssetWithSerializedData ap3; - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset4.txt", AZ::DataStream::ST_XML, &ap1, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset5.txt", AZ::DataStream::ST_XML, &ap2, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset6.txt", AZ::DataStream::ST_XML, &ap3, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset4.txt", &ap1, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset5.txt", &ap2, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset6.txt", &ap3, m_serializeContext)); AssetWithAssetReference assetWithPreload1; AssetWithAssetReference assetWithPreload2; @@ -273,11 +273,11 @@ namespace UnitTest noLoadAsset.m_asset = m_testAssetManager->CreateAsset(MyAsset2Id, AssetLoadBehavior::NoLoad); EXPECT_EQ(m_assetHandlerAndCatalog->m_numCreations, 4); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset1.txt", AZ::DataStream::ST_XML, &assetWithPreload1, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset2.txt", AZ::DataStream::ST_XML, &assetWithPreload2, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset3.txt", AZ::DataStream::ST_XML, &assetWithPreload3, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "DelayLoadAsset.txt", AZ::DataStream::ST_XML, &delayedAsset, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "NoLoadAsset.txt", AZ::DataStream::ST_XML, &noLoadAsset, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset1.txt", &assetWithPreload1, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset2.txt", &assetWithPreload2, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset3.txt", &assetWithPreload3, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("DelayLoadAsset.txt", &delayedAsset, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("NoLoadAsset.txt", &noLoadAsset, m_serializeContext)); AssetWithQueueAndPreLoadReferences preLoadRoot; AssetWithQueueAndPreLoadReferences preLoadA; @@ -297,16 +297,16 @@ namespace UnitTest preLoadBrokenA.m_preLoad = m_testAssetManager->CreateAsset(PreloadBrokenDepBId, AssetLoadBehavior::PreLoad); preLoadBrokenB.m_preLoad = m_testAssetManager->CreateAsset(PreloadAssetNoDataId, AssetLoadBehavior::PreLoad); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "PreLoadRoot.txt", AZ::DataStream::ST_XML, &preLoadRoot, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "PreLoadA.txt", AZ::DataStream::ST_XML, &preLoadA, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "PreLoadB.txt", AZ::DataStream::ST_XML, &noRefs, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "PreLoadC.txt", AZ::DataStream::ST_XML, &noRefs, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "QueueLoadA.txt", AZ::DataStream::ST_XML, &queueLoadA, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "QueueLoadB.txt", AZ::DataStream::ST_XML, &noRefs, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "QueueLoadC.txt", AZ::DataStream::ST_XML, &noRefs, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "PreLoadBrokenA.txt", AZ::DataStream::ST_XML, &preLoadBrokenA, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "PreLoadBrokenB.txt", AZ::DataStream::ST_XML, &preLoadBrokenB, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "PreLoadNoData.txt", AZ::DataStream::ST_XML, &noRefs, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("PreLoadRoot.txt", &preLoadRoot, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("PreLoadA.txt", &preLoadA, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("PreLoadB.txt", &noRefs, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("PreLoadC.txt", &noRefs, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("QueueLoadA.txt", &queueLoadA, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("QueueLoadB.txt", &noRefs, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("QueueLoadC.txt", &noRefs, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("PreLoadBrokenA.txt", &preLoadBrokenA, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("PreLoadBrokenB.txt", &preLoadBrokenB, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("PreLoadNoData.txt", &noRefs, m_serializeContext)); AssetWithQueueAndPreLoadReferences circularA; AssetWithQueueAndPreLoadReferences circularB; @@ -318,43 +318,15 @@ namespace UnitTest circularC.m_preLoad = m_testAssetManager->CreateAsset(CircularBId, AssetLoadBehavior::PreLoad); circularD.m_preLoad = circularC.m_preLoad; - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "CircularA.txt", AZ::DataStream::ST_XML, &circularA, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "CircularB.txt", AZ::DataStream::ST_XML, &circularB, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "CircularC.txt", AZ::DataStream::ST_XML, &circularC, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "CircularD.txt", AZ::DataStream::ST_XML, &circularD, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("CircularA.txt", &circularA, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("CircularB.txt", &circularB, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("CircularC.txt", &circularC, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("CircularD.txt", &circularD, m_serializeContext)); + m_assetHandlerAndCatalog->m_numCreations = 0; } } - void TearDownTest() - { - DeleteAssetFromDisk(GetTestFolderPath() + "TestAsset4.txt"); - DeleteAssetFromDisk(GetTestFolderPath() + "TestAsset5.txt"); - DeleteAssetFromDisk(GetTestFolderPath() + "TestAsset6.txt"); - - DeleteAssetFromDisk(GetTestFolderPath() + "TestAsset1.txt"); - DeleteAssetFromDisk(GetTestFolderPath() + "TestAsset2.txt"); - DeleteAssetFromDisk(GetTestFolderPath() + "TestAsset3.txt"); - DeleteAssetFromDisk(GetTestFolderPath() + "DelayLoadAsset.txt"); - DeleteAssetFromDisk(GetTestFolderPath() + "NoLoadAsset.txt"); - - DeleteAssetFromDisk(GetTestFolderPath() + "PreLoadRoot.txt"); - DeleteAssetFromDisk(GetTestFolderPath() + "PreLoadA.txt"); - DeleteAssetFromDisk(GetTestFolderPath() + "PreLoadB.txt"); - DeleteAssetFromDisk(GetTestFolderPath() + "PreLoadC.txt"); - DeleteAssetFromDisk(GetTestFolderPath() + "QueueLoadA.txt"); - DeleteAssetFromDisk(GetTestFolderPath() + "QueueLoadB.txt"); - DeleteAssetFromDisk(GetTestFolderPath() + "QueueLoadC.txt"); - DeleteAssetFromDisk(GetTestFolderPath() + "PreLoadBrokenA.txt"); - DeleteAssetFromDisk(GetTestFolderPath() + "PreLoadBrokenB.txt"); - DeleteAssetFromDisk(GetTestFolderPath() + "PreLoadNoData.txt"); - - DeleteAssetFromDisk(GetTestFolderPath() + "CircularA.txt"); - DeleteAssetFromDisk(GetTestFolderPath() + "CircularB.txt"); - DeleteAssetFromDisk(GetTestFolderPath() + "CircularC.txt"); - DeleteAssetFromDisk(GetTestFolderPath() + "CircularD.txt"); - } - void CheckFinishedCreationsAndDestructions() { // Make sure asset jobs have finished before validating the number of destroyed assets, because it's possible that the asset job @@ -367,7 +339,7 @@ namespace UnitTest }; static constexpr AZStd::chrono::seconds MaxDispatchTimeoutSeconds = BaseAssetManagerTest::DefaultTimeoutSeconds * 12; - + template bool DispatchEventsUntilCondition(AZ::Data::AssetManager& assetManager, Pred&& conditionPredicate, AZStd::chrono::seconds logIntervalSeconds = BaseAssetManagerTest::DefaultTimeoutSeconds, @@ -609,23 +581,8 @@ namespace UnitTest EXPECT_EQ(baseStatus, expected_base_status); } - struct DebugListener : AZ::Interface::Registrar - { - void AssetStatusUpdate(AZ::Data::AssetId id, AZ::Data::AssetData::AssetStatus status) override - { - AZ::Debug::Trace::Output( - "", AZStd::string::format("Status %s - %d\n", id.ToString().c_str(), static_cast(status)).c_str()); - } - void ReleaseAsset(AZ::Data::AssetId id) override - { - AZ::Debug::Trace::Output( - "", AZStd::string::format("Release %s\n", id.ToString().c_str()).c_str()); - } - }; - TEST_F(AssetJobsFloodTest, RapidAcquireAndRelease) { - DebugListener listener; auto assetUuids = { MyAsset1Id, MyAsset2Id, @@ -656,7 +613,7 @@ namespace UnitTest { Asset asset1 = m_testAssetManager->GetAsset(assetUuid, azrtti_typeid(), AZ::Data::AssetLoadBehavior::PreLoad); - + if (checkLoaded) { asset1.BlockUntilLoadComplete(); @@ -729,8 +686,8 @@ namespace UnitTest AssetWithSerializedData ap; - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "a.txt", AZ::DataStream::ST_XML, &ap, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "b.txt", AZ::DataStream::ST_XML, &ap, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("a.txt", &ap, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("b.txt", &ap, m_serializeContext)); } auto& assetManager = AssetManager::Instance(); @@ -793,7 +750,7 @@ namespace UnitTest * Verify that loads without using the Asset Container still work correctly */ class AssetContainerDisableTest - : public BaseAssetManagerTest + : public DisklessAssetManagerBase { public: static inline const AZ::Uuid MyAsset1Id{ "{5B29FE2B-6B41-48C9-826A-C723951B0560}" }; @@ -812,7 +769,7 @@ namespace UnitTest void SetUp() override { - BaseAssetManagerTest::SetUp(); + DisklessAssetManagerBase::SetUp(); SetupTest(); } @@ -822,7 +779,7 @@ namespace UnitTest AssetManager::Instance().UnregisterHandler(m_assetHandlerAndCatalog); delete m_assetHandlerAndCatalog; AssetManager::Destroy(); - BaseAssetManagerTest::TearDown(); + DisklessAssetManagerBase::TearDown(); } void SetupAssets() @@ -864,9 +821,9 @@ namespace UnitTest AssetWithSerializedData ap2; AssetWithSerializedData ap3; - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset4.txt", AZ::DataStream::ST_XML, &ap1, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset5.txt", AZ::DataStream::ST_XML, &ap2, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset6.txt", AZ::DataStream::ST_XML, &ap3, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset4.txt", &ap1, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset5.txt", &ap2, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset6.txt", &ap3, m_serializeContext)); AssetWithAssetReference assetWithPreload1; AssetWithAssetReference assetWithPreload2; @@ -877,9 +834,9 @@ namespace UnitTest assetWithPreload3.m_asset = m_testAssetManager->CreateAsset(MyAsset6Id, AssetLoadBehavior::PreLoad); EXPECT_EQ(m_assetHandlerAndCatalog->m_numCreations, 3); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset1.txt", AZ::DataStream::ST_XML, &assetWithPreload1, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset2.txt", AZ::DataStream::ST_XML, &assetWithPreload2, m_serializeContext)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset3.txt", AZ::DataStream::ST_XML, &assetWithPreload3, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset1.txt", &assetWithPreload1, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset2.txt", &assetWithPreload2, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset3.txt", &assetWithPreload3, m_serializeContext)); m_assetHandlerAndCatalog->m_numCreations = 0; } @@ -957,11 +914,11 @@ namespace UnitTest m_testAssetManager->SetParallelDependentLoadingEnabled(true); } -#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS +#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS TEST_F(AssetJobsFloodTest, DISABLED_LoadTest_SameAsset_DifferentFilters) #else TEST_F(AssetJobsFloodTest, LoadTest_SameAsset_DifferentFilters) -#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS +#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS { m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusConnect(); @@ -1306,11 +1263,11 @@ namespace UnitTest m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusDisconnect(); } -#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS +#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS TEST_F(AssetJobsFloodTest, DISABLED_AssetWithNoLoadReference_LoadDependencies_NoLoadNotLoaded) #else TEST_F(AssetJobsFloodTest, AssetWithNoLoadReference_LoadDependencies_NoLoadNotLoaded) -#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS +#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS { m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusConnect(); // Setup has already created/destroyed assets @@ -1347,11 +1304,11 @@ namespace UnitTest m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusDisconnect(); } -#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS +#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS TEST_F(AssetJobsFloodTest, DISABLED_AssetWithNoLoadReference_LoadContainerDependencies_LoadAllLoadsNoLoad) #else TEST_F(AssetJobsFloodTest, AssetWithNoLoadReference_LoadContainerDependencies_LoadAllLoadsNoLoad) -#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS +#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS { m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusConnect(); // Setup has already created/destroyed assets @@ -1386,11 +1343,11 @@ namespace UnitTest m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusDisconnect(); } -#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS +#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS TEST_F(AssetJobsFloodTest, DISABLED_AssetWithNoLoadReference_LoadDependencies_BehaviorObeyed) #else TEST_F(AssetJobsFloodTest, AssetWithNoLoadReference_LoadDependencies_BehaviorObeyed) -#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS || AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS +#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS { m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusConnect(); // Setup has already created/destroyed assets @@ -2029,11 +1986,12 @@ namespace UnitTest CheckFinishedCreationsAndDestructions(); m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusDisconnect(); } + /** * Run multiple threads that get and release assets simultaneously to test AssetManager's thread safety */ class AssetJobsMultithreadedTest - : public BaseAssetManagerTest + : public DisklessAssetManagerBase { public: static inline const AZ::Uuid MyAsset1Id{ "{5B29FE2B-6B41-48C9-826A-C723951B0560}" }; @@ -2043,6 +2001,7 @@ namespace UnitTest static inline const AZ::Uuid MyAsset5Id{ "{D9CDAB04-D206-431E-BDC0-1DD615D56197}" }; static inline const AZ::Uuid MyAsset6Id{ "{B2F139C3-5032-4B52-ADCA-D52A8F88E043}" }; + // Initialize the Job Manager with 2 threads for the Asset Manager to use. size_t GetNumJobManagerThreads() const override { return 2; } @@ -2093,9 +2052,9 @@ namespace UnitTest AssetWithSerializedData ap2; AssetWithSerializedData ap3; - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset4.txt", AZ::DataStream::ST_XML, &ap1, &context)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset5.txt", AZ::DataStream::ST_XML, &ap2, &context)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset6.txt", AZ::DataStream::ST_XML, &ap3, &context)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset4.txt", &ap1, &context)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset5.txt", &ap2, &context)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset6.txt", &ap3, &context)); AssetWithAssetReference assetWithPreload1; AssetWithAssetReference assetWithPreload2; @@ -2104,9 +2063,9 @@ namespace UnitTest assetWithPreload2.m_asset = AssetManager::Instance().CreateAsset(MyAsset5Id, AssetLoadBehavior::PreLoad); assetWithPreload3.m_asset = AssetManager::Instance().CreateAsset(MyAsset6Id, AssetLoadBehavior::PreLoad); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset1.txt", AZ::DataStream::ST_XML, &assetWithPreload1, &context)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset2.txt", AZ::DataStream::ST_XML, &assetWithPreload2, &context)); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset3.txt", AZ::DataStream::ST_XML, &assetWithPreload3, &context)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset1.txt", &assetWithPreload1, &context)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset2.txt", &assetWithPreload2, &context)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset3.txt", &assetWithPreload3, &context)); EXPECT_TRUE(assetHandlerAndCatalog->m_numCreations == 3); assetHandlerAndCatalog->m_numCreations = 0; @@ -2206,22 +2165,22 @@ namespace UnitTest // A will be saved to disk with MyAsset1Id AssetWithAssetReference a; a.m_asset = AssetManager::Instance().CreateAsset(MyAsset2Id); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset1.txt", AZ::DataStream::ST_XML, &a, &context)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset1.txt", &a, &context)); AssetWithAssetReference b; b.m_asset = AssetManager::Instance().CreateAsset(MyAsset3Id); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset2.txt", AZ::DataStream::ST_XML, &b, &context)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset2.txt", &b, &context)); AssetWithAssetReference c; c.m_asset = AssetManager::Instance().CreateAsset(MyAsset4Id); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset3.txt", AZ::DataStream::ST_XML, &c, &context)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset3.txt", &c, &context)); AssetWithAssetReference d; d.m_asset = AssetManager::Instance().CreateAsset(MyAsset5Id); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset4.txt", AZ::DataStream::ST_XML, &d, &context)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset4.txt", &d, &context)); AssetWithAssetReference e; e.m_asset = AssetManager::Instance().CreateAsset(MyAsset6Id); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset5.txt", AZ::DataStream::ST_XML, &e, &context)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset5.txt", &e, &context)); AssetWithAssetReference f; f.m_asset = AssetManager::Instance().CreateAsset(MyAsset1Id); // refer back to asset1 - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset6.txt", AZ::DataStream::ST_XML, &f, &context)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset6.txt", &f, &context)); EXPECT_TRUE(assetHandlerAndCatalog->m_numCreations == 6); assetHandlerAndCatalog->m_numCreations = 0; @@ -2362,26 +2321,26 @@ namespace UnitTest // AssetD is MYASSETD AssetWithSerializedData d; d.m_data = 42; - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset4.txt", AZ::DataStream::ST_XML, &d, &context)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset4.txt", &d, &context)); // AssetC is MYASSETC AssetWithAssetReference c; c.m_asset = db.CreateAsset(AssetId(MyAssetDId)); // point at D - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset3.txt", AZ::DataStream::ST_XML, &c, &context)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset3.txt", &c, &context)); // AssetB is MYASSETB AssetWithAssetReference b; b.m_asset = db.CreateAsset(AssetId(MyAssetCId)); // point at C - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset2.txt", AZ::DataStream::ST_XML, &b, &context)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset2.txt", &b, &context)); // AssetA will be written to disk as MYASSETA AssetWithAssetReference a; a.m_asset = db.CreateAsset(AssetId(MyAssetBId)); // point at B - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset1.txt", AZ::DataStream::ST_XML, &a, &context)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset1.txt", &a, &context)); } - const size_t numThreads = 4; - AZStd::atomic_int threadCount(numThreads); + constexpr size_t NumThreads = 4; + AZStd::atomic_int threadCount(NumThreads); AZStd::condition_variable cv; AZStd::vector threads; AZStd::atomic_bool keepDispatching(true); @@ -2396,7 +2355,7 @@ namespace UnitTest AZStd::thread dispatchThread(dispatch); - for (size_t threadIdx = 0; threadIdx < numThreads; ++threadIdx) + for (size_t threadIdx = 0; threadIdx < NumThreads; ++threadIdx) { threads.emplace_back([&threadCount, &db, &cv]() { @@ -2584,7 +2543,6 @@ namespace UnitTest #if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS TEST_F(AssetJobsMultithreadedTest, DISABLED_ParallelDeepAssetReferences) #else - // temporarily disabled until sporadic failures can be root caused TEST_F(AssetJobsMultithreadedTest, ParallelDeepAssetReferences) #endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS { @@ -2592,7 +2550,7 @@ namespace UnitTest } class AssetManagerTests - : public BaseAssetManagerTest + : public DisklessAssetManagerBase { protected: static inline const AZ::Uuid MyAsset1Id{ "{5B29FE2B-6B41-48C9-826A-C723951B0560}" }; @@ -2607,7 +2565,7 @@ namespace UnitTest void SetUp() override { - BaseAssetManagerTest::SetUp(); + DisklessAssetManagerBase::SetUp(); m_console = AZStd::make_unique(); AZ::Interface::Register(m_console.get()); @@ -2646,7 +2604,7 @@ namespace UnitTest AssetManager::Destroy(); AZ::Interface::Unregister(m_console.get()); m_console = nullptr; - BaseAssetManagerTest::TearDown(); + DisklessAssetManagerBase::TearDown(); } }; @@ -2997,7 +2955,7 @@ namespace UnitTest * the middle of loading. The tests help ensure that assets can't get stuck in perpetual loading states. **/ class AssetManagerClearAssetReferenceTests - : public BaseAssetManagerTest + : public DisklessAssetManagerBase { protected: static inline const AZ::Uuid RootAssetId{ "{AB13F568-C676-41FE-A7E9-341F71A78104}" }; @@ -3016,7 +2974,7 @@ namespace UnitTest void SetUp() override { - BaseAssetManagerTest::SetUp(); + DisklessAssetManagerBase::SetUp(); // create the database AssetManager::Descriptor desc; @@ -3054,21 +3012,18 @@ namespace UnitTest // Create and save the dependent asset first, so that we can get a reference to it. AssetWithSerializedData dependentBlockingAsset; - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "DependentPreloadBlockingAsset.txt", - AZ::DataStream::ST_XML, &dependentBlockingAsset, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("DependentPreloadBlockingAsset.txt", &dependentBlockingAsset, m_serializeContext)); AssetWithAssetReference dependentAsset; dependentAsset.m_asset = AssetManager::Instance().CreateAsset( NestedDependentPreloadBlockingAssetId, AssetLoadBehavior::PreLoad); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "DependentPreloadAsset.txt", - AZ::DataStream::ST_XML, &dependentAsset, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("DependentPreloadAsset.txt", &dependentAsset, m_serializeContext)); // Create and save the top-level asset. AssetWithAssetReference rootAsset; rootAsset.m_asset = AssetManager::Instance().CreateAsset( DependentPreloadAssetId, AssetLoadBehavior::PreLoad); - EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "RootAsset.txt", - AZ::DataStream::ST_XML, &rootAsset, m_serializeContext)); + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("RootAsset.txt", &rootAsset, m_serializeContext)); } void TearDown() override @@ -3080,7 +3035,7 @@ namespace UnitTest delete m_assetHandlerAndCatalog; AssetManager::Destroy(); - BaseAssetManagerTest::TearDown(); + DisklessAssetManagerBase::TearDown(); } }; diff --git a/Code/Framework/AzCore/Tests/Asset/BaseAssetManagerTest.cpp b/Code/Framework/AzCore/Tests/Asset/BaseAssetManagerTest.cpp index 4dbd3c0e1e..532cb0a1d8 100644 --- a/Code/Framework/AzCore/Tests/Asset/BaseAssetManagerTest.cpp +++ b/Code/Framework/AzCore/Tests/Asset/BaseAssetManagerTest.cpp @@ -67,7 +67,10 @@ namespace UnitTest { SerializeContextFixture::SetUp(); + SuppressTraceOutput(false); + AZ::JobManagerDesc jobDesc; + AZ::JobManagerThreadDesc threadDesc; for (size_t threadCount = 0; threadCount < GetNumJobManagerThreads(); threadCount++) { @@ -111,9 +114,21 @@ namespace UnitTest delete m_jobContext; delete m_jobManager; + // Reset back to default suppression settings to avoid affecting other tests + SuppressTraceOutput(true); + SerializeContextFixture::TearDown(); } + void BaseAssetManagerTest::SuppressTraceOutput(bool suppress) + { + UnitTest::TestRunner::Instance().m_suppressAsserts = suppress; + UnitTest::TestRunner::Instance().m_suppressErrors = suppress; + UnitTest::TestRunner::Instance().m_suppressWarnings = suppress; + UnitTest::TestRunner::Instance().m_suppressPrintf = suppress; + UnitTest::TestRunner::Instance().m_suppressOutput = suppress; + } + void BaseAssetManagerTest::WriteAssetToDisk(const AZStd::string& assetName, [[maybe_unused]] const AZStd::string& assetIdGuid) { AZStd::string assetFileName = GetTestFolderPath() + assetName; @@ -150,4 +165,254 @@ namespace UnitTest EXPECT_FALSE(AssetManager::Instance().HasActiveJobsOrStreamerRequests()); } + + MemoryStreamerWrapper::MemoryStreamerWrapper() + { + using ::testing::_; + using ::testing::NiceMock; + using ::testing::Return; + + ON_CALL(m_mockStreamer, SuspendProcessing()).WillByDefault([this]() + { + m_suspended = true; + }); + + ON_CALL(m_mockStreamer, ResumeProcessing()).WillByDefault([this]() + { + AZStd::unique_lock lock(m_mutex); + + m_suspended = false; + + while (!m_processingQueue.empty()) + { + FileRequestHandle requestHandle = m_processingQueue.front(); + m_processingQueue.pop(); + + const auto& onCompleteCallback = GetReadRequest(requestHandle)->m_callback; + + if (onCompleteCallback) + { + onCompleteCallback(requestHandle); + } + } + }); + + ON_CALL(m_mockStreamer, Read(_, ::testing::An(), _, _, _, _)) + .WillByDefault( + [this]( + [[maybe_unused]] AZStd::string_view relativePath, IStreamerTypes::RequestMemoryAllocator& allocator, size_t size, + AZStd::chrono::microseconds deadline, IStreamerTypes::Priority priority, [[maybe_unused]] size_t offset) + { + AZStd::unique_lock lock(m_mutex); + + ReadRequest request; + + // Save off the requested deadline and priority + request.m_deadline = deadline; + request.m_priority = priority; + request.m_data = allocator.Allocate(size, size, 8); + + const auto* virtualFile = FindFile(relativePath); + + AZ_Assert( + virtualFile->size() == size, "Streamer read request size did not match size of saved file: %d vs %d (%.*s)", + virtualFile->size(), size, + relativePath.size(), relativePath.data()); + AZ_Assert(size > 0, "Size is zero %.*s", relativePath.size(), relativePath.data()); + + memcpy(request.m_data.m_address, virtualFile->data(), size); + + // Create a real file request result and return it + request.m_request = m_context.GetNewExternalRequest(); + + m_readRequests.push_back(request); + + return request.m_request; + }); + + ON_CALL(m_mockStreamer, SetRequestCompleteCallback(_, _)) + .WillByDefault([this](FileRequestPtr& request, AZ::IO::IStreamer::OnCompleteCallback callback) -> FileRequestPtr& + { + // Save off the callback just so that we can call it when the request is "done" + AZStd::unique_lock lock(m_mutex); + ReadRequest* readRequest = GetReadRequest(request); + readRequest->m_callback = callback; + + return request; + }); + + ON_CALL(m_mockStreamer, QueueRequest(_)) + .WillByDefault([this](const auto& fileRequest) + { + if (!m_suspended) + { + decltype(ReadRequest::m_callback) onCompleteCallback; + + AZStd::unique_lock lock(m_mutex); + ReadRequest* readRequest = GetReadRequest(fileRequest); + onCompleteCallback = readRequest->m_callback; + + if (onCompleteCallback) + { + onCompleteCallback(fileRequest); + + m_readRequests.erase(readRequest); + } + } + else + { + AZStd::unique_lock lock(m_mutex); + + m_processingQueue.push(fileRequest); + } + }); + + ON_CALL(m_mockStreamer, GetRequestStatus(_)) + .WillByDefault([]([[maybe_unused]] FileRequestHandle request) + { + // Return whatever request status has been set in this class + return IO::IStreamerTypes::RequestStatus::Completed; + }); + + ON_CALL(m_mockStreamer, GetReadRequestResult(_, _, _, _)) + .WillByDefault([this]( + [[maybe_unused]] FileRequestHandle request, void*& buffer, AZ::u64& numBytesRead, + IStreamerTypes::ClaimMemory claimMemory) + { + // Make sure the requestor plans to free the data buffer we allocated. + EXPECT_EQ(claimMemory, IStreamerTypes::ClaimMemory::Yes); + + AZStd::unique_lock lock(m_mutex); + + ReadRequest* readRequest = GetReadRequest(request); + + // Provide valid data buffer results. + numBytesRead = readRequest->m_data.m_size; + buffer = readRequest->m_data.m_address; + + return true; + }); + + ON_CALL(m_mockStreamer, RescheduleRequest(_, _, _)) + .WillByDefault([this](IO::FileRequestPtr target, AZStd::chrono::microseconds newDeadline, IO::IStreamerTypes::Priority newPriority) + { + AZStd::unique_lock lock(m_mutex); + ReadRequest* readRequest = GetReadRequest(target); + + readRequest->m_deadline = newDeadline; + readRequest->m_priority = newPriority; + + return target; + }); + } + + ReadRequest* MemoryStreamerWrapper::GetReadRequest(FileRequestHandle request) + { + auto itr = AZStd::find_if( + m_readRequests.begin(), m_readRequests.end(), + [request](const ReadRequest& searchItem) -> bool + { + return (searchItem.m_request == request); + }); + + return itr; + } + + AZStd::vector* MemoryStreamerWrapper::FindFile(AZStd::string_view path) + { + auto itr = m_virtualFiles.find(path); + + if (itr == m_virtualFiles.end()) + { + // Path didn't work as-is, does it have the test folder prefixed? If so try removing it + if (AZ::StringFunc::StartsWith(path, GetTestFolderPath())) + { + AZStd::string_view pathWithoutFolder = path; + + pathWithoutFolder = AZ::StringFunc::LStrip(pathWithoutFolder, GetTestFolderPath().c_str()); + itr = m_virtualFiles.find(pathWithoutFolder); + } + else // Path isn't prefixed, so try adding it + { + itr = m_virtualFiles.find(GetTestFolderPath().append(path)); + } + } + + if (itr != m_virtualFiles.end()) + { + return &itr->second; + } + + // Currently no test expects a file not to exist so we assert to make it easy to quickly find where something went wrong + // If we ever need to test for a non-existent file this assert should just be conditionally disabled for that specific test + AZ_Assert(false, "Failed to find virtual file %*.s", path.size(), path.data()) + + return nullptr; + } + + void DisklessAssetManagerBase::SetUp() + { + using ::testing::_; + using ::testing::NiceMock; + using ::testing::Return; + + BaseAssetManagerTest::SetUp(); + + ON_CALL(m_fileIO, Size(::testing::Matcher(::testing::_), _)) + .WillByDefault( + [this](const char* path, u64& size) + { + AZStd::scoped_lock lock(m_streamerWrapper->m_mutex); + + const auto* file = m_streamerWrapper->FindFile(path); + + if (file) + { + size = file->size(); + return ResultCode::Success; + } + + AZ_Error("DisklessAssetManagerBase", false, "Failed to find virtual file %.*s", path); + + return ResultCode::Error; + }); + + m_prevFileIO = IO::FileIOBase::GetInstance(); + IO::FileIOBase::SetInstance(nullptr); + IO::FileIOBase::SetInstance(&m_fileIO); + } + + void DisklessAssetManagerBase::TearDown() + { + IO::FileIOBase::SetInstance(nullptr); + IO::FileIOBase::SetInstance(m_prevFileIO); + + BaseAssetManagerTest::TearDown(); + } + + IO::IStreamer* DisklessAssetManagerBase::CreateStreamer() + { + m_streamerWrapper = AZStd::make_unique(); + + return &(m_streamerWrapper->m_mockStreamer); + } + + void DisklessAssetManagerBase::DestroyStreamer(IO::IStreamer*) + { + m_streamerWrapper = nullptr; + } + + void DisklessAssetManagerBase::WriteAssetToDisk(const AZStd::string& assetName, const AZStd::string&) + { + AZStd::string assetFileName = GetTestFolderPath() + assetName; + + AssetWithCustomData asset; + + EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile(assetFileName, &asset, m_serializeContext)); + } + + void DisklessAssetManagerBase::DeleteAssetFromDisk(const AZStd::string&) + { + + } } diff --git a/Code/Framework/AzCore/Tests/Asset/BaseAssetManagerTest.h b/Code/Framework/AzCore/Tests/Asset/BaseAssetManagerTest.h index 757edb3713..af48c74a60 100644 --- a/Code/Framework/AzCore/Tests/Asset/BaseAssetManagerTest.h +++ b/Code/Framework/AzCore/Tests/Asset/BaseAssetManagerTest.h @@ -20,7 +20,8 @@ #include #include #include - +#include +#include namespace UnitTest { @@ -58,14 +59,20 @@ namespace UnitTest // Subclasses can optionally override the streamer creation and destruction virtual IO::IStreamer* CreateStreamer() { return aznew IO::Streamer(AZStd::thread_desc{}, StreamerComponent::CreateStreamerStack()); } - virtual void DestroyStreamer(IO::IStreamer* streamer) { delete streamer; } + virtual void DestroyStreamer(IO::IStreamer* streamer) + { + delete streamer; + streamer = nullptr; + } void SetUp() override; void TearDown() override; + static void SuppressTraceOutput(bool suppress); + // Helper methods to create and destroy actual assets on the disk for true end-to-end asset loading. - void WriteAssetToDisk(const AZStd::string& assetName, const AZStd::string& assetIdGuid); - void DeleteAssetFromDisk(const AZStd::string& assetName); + virtual void WriteAssetToDisk(const AZStd::string& assetName, const AZStd::string& assetIdGuid); + virtual void DeleteAssetFromDisk(const AZStd::string& assetName); void BlockUntilAssetJobsAreComplete(); @@ -80,4 +87,57 @@ namespace UnitTest AZStd::vector m_assetsWritten; }; + + struct ReadRequest + { + AZStd::chrono::milliseconds m_deadline{}; + AZ::IO::IStreamerTypes::Priority m_priority{}; + IO::IStreamerTypes::RequestMemoryAllocatorResult m_data{ nullptr, 0, IO::IStreamerTypes::MemoryType::ReadWrite }; + AZ::IO::IStreamer::OnCompleteCallback m_callback; + IO::FileRequestPtr m_request; + }; + + struct MemoryStreamerWrapper + { + MemoryStreamerWrapper(); + ~MemoryStreamerWrapper() = default; + + ReadRequest* GetReadRequest(IO::FileRequestHandle request); + + template + bool WriteMemoryFile(const AZStd::string& filePath, TObject* object, AZ::SerializeContext* context) + { + auto& buffer = m_virtualFiles[filePath]; + ByteContainerStream stream(&buffer); + + return AZ::Utils::SaveObjectToStream(stream, DataStream::StreamType::ST_XML, object, context); + } + + AZStd::vector* FindFile(AZStd::string_view path); + + ::testing::NiceMock m_mockStreamer; + IO::StreamerContext m_context; + AZStd::atomic_bool m_suspended{ false }; + + AZStd::recursive_mutex m_mutex; + AZStd::queue m_processingQueue; // Keeps tracks of requests that have been queued while processing is suspended + AZStd::vector m_readRequests; + AZStd::unordered_map> m_virtualFiles; + }; + + struct DisklessAssetManagerBase : BaseAssetManagerTest + { + void SetUp() override; + void TearDown() override; + IO::IStreamer* CreateStreamer() override; + void DestroyStreamer(IO::IStreamer*) override; + + void WriteAssetToDisk(const AZStd::string& assetName, const AZStd::string& assetIdGuid) override; + void DeleteAssetFromDisk(const AZStd::string& assetName) override; + + AZStd::unique_ptr m_streamerWrapper; + ::testing::NiceMock m_fileIO; + IO::FileIOBase* m_prevFileIO{}; + }; + } diff --git a/Code/Framework/AzCore/Tests/Components.cpp b/Code/Framework/AzCore/Tests/Components.cpp index 013d998c52..159d55e948 100644 --- a/Code/Framework/AzCore/Tests/Components.cpp +++ b/Code/Framework/AzCore/Tests/Components.cpp @@ -175,7 +175,6 @@ namespace UnitTest ComponentApplication componentApp; ComponentApplication::Descriptor desc; desc.m_useExistingAllocator = true; - desc.m_enableDrilling = false; // we already created a memory driller for the test (AllocatorsFixture) ComponentApplication::StartupParameters startupParams; startupParams.m_allocator = &AZ::AllocatorInstance::Get(); Entity* systemEntity = componentApp.Create(desc, startupParams); @@ -631,7 +630,6 @@ namespace UnitTest ComponentApplication::Descriptor desc; desc.m_useExistingAllocator = true; - desc.m_enableDrilling = false; // we already created a memory driller for the test (AllocatorsFixture in Components) ComponentApplication::StartupParameters startupParams; startupParams.m_allocator = &AZ::AllocatorInstance::Get(); diff --git a/Code/Framework/AzCore/Tests/DOM/DomJsonBenchmarks.cpp b/Code/Framework/AzCore/Tests/DOM/DomJsonBenchmarks.cpp new file mode 100644 index 0000000000..0baa4aeb53 --- /dev/null +++ b/Code/Framework/AzCore/Tests/DOM/DomJsonBenchmarks.cpp @@ -0,0 +1,185 @@ +/* + * 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 + * + */ + +#if defined(HAVE_BENCHMARK) + +#include +#include +#include +#include +#include +#include +#include + +namespace Benchmark +{ + class DomJsonBenchmark : public UnitTest::AllocatorsBenchmarkFixture + { + public: + void SetUp(const ::benchmark::State& st) override + { + UnitTest::AllocatorsBenchmarkFixture::SetUp(st); + AZ::NameDictionary::Create(); + } + + void SetUp(::benchmark::State& st) override + { + UnitTest::AllocatorsBenchmarkFixture::SetUp(st); + AZ::NameDictionary::Create(); + } + + void TearDown(::benchmark::State& st) override + { + AZ::NameDictionary::Destroy(); + UnitTest::AllocatorsBenchmarkFixture::TearDown(st); + } + + void TearDown(const ::benchmark::State& st) override + { + AZ::NameDictionary::Destroy(); + UnitTest::AllocatorsBenchmarkFixture::TearDown(st); + } + + AZStd::string GenerateDomJsonBenchmarkPayload(int64_t entryCount, int64_t stringTemplateLength) + { + rapidjson::Document document; + document.SetObject(); + + AZStd::string entryTemplate; + while (entryTemplate.size() < static_cast(stringTemplateLength)) + { + entryTemplate += "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor "; + } + entryTemplate.resize(stringTemplateLength); + AZStd::string buffer; + + auto createString = [&](int n) -> rapidjson::Value + { + buffer = AZStd::string::format("#%i %s", n, entryTemplate.c_str()); + return rapidjson::Value(buffer.data(), static_cast(buffer.size()), document.GetAllocator()); + }; + + auto createEntry = [&](int n) -> rapidjson::Value + { + rapidjson::Value entry(rapidjson::kObjectType); + entry.AddMember("string", createString(n), document.GetAllocator()); + entry.AddMember("int", rapidjson::Value(n), document.GetAllocator()); + entry.AddMember("double", rapidjson::Value(static_cast(n) * 0.5), document.GetAllocator()); + entry.AddMember("bool", rapidjson::Value(n % 2 == 0), document.GetAllocator()); + entry.AddMember("null", rapidjson::Value(rapidjson::kNullType), document.GetAllocator()); + return entry; + }; + + auto createArray = [&]() -> rapidjson::Value + { + rapidjson::Value array; + array.SetArray(); + for (int i = 0; i < entryCount; ++i) + { + array.PushBack(createEntry(i), document.GetAllocator()); + } + return array; + }; + + auto createObject = [&]() -> rapidjson::Value + { + rapidjson::Value object; + object.SetObject(); + for (int i = 0; i < entryCount; ++i) + { + buffer = AZStd::string::format("Key%i", i); + rapidjson::Value key; + key.SetString(buffer.data(), static_cast(buffer.length()), document.GetAllocator()); + object.AddMember(key.Move(), createArray(), document.GetAllocator()); + } + return object; + }; + + document.SetObject(); + document.AddMember("entries", createObject(), document.GetAllocator()); + + AZStd::string serializedJson; + auto result = AZ::JsonSerializationUtils::WriteJsonString(document, serializedJson); + AZ_Assert(result.IsSuccess(), "Failed to serialize generated JSON"); + return serializedJson; + } + }; + +// Helper macro for registering JSON benchmarks +#define BENCHMARK_REGISTER_JSON(BaseClass, Method) \ + BENCHMARK_REGISTER_F(BaseClass, Method) \ + ->Args({ 10, 5 }) \ + ->Args({ 10, 500 }) \ + ->Args({ 100, 5 }) \ + ->Args({ 100, 500 }) \ + ->Unit(benchmark::kMillisecond); + + BENCHMARK_DEFINE_F(DomJsonBenchmark, DomDeserializeToDocumentInPlace)(benchmark::State& state) + { + AZ::Dom::JsonBackend backend; + AZStd::string serializedPayload = GenerateDomJsonBenchmarkPayload(state.range(0), state.range(1)); + + for (auto _ : state) + { + state.PauseTiming(); + AZStd::string payloadCopy = serializedPayload; + state.ResumeTiming(); + + auto result = AZ::Dom::Json::WriteToRapidJsonDocument( + [&](AZ::Dom::Visitor& visitor) + { + return AZ::Dom::Utils::ReadFromStringInPlace(backend, payloadCopy, visitor); + }); + + benchmark::DoNotOptimize(result.GetValue()); + } + + state.SetBytesProcessed(serializedPayload.size() * state.iterations()); + } + BENCHMARK_REGISTER_JSON(DomJsonBenchmark, DomDeserializeToDocumentInPlace) + + BENCHMARK_DEFINE_F(DomJsonBenchmark, DomDeserializeToDocument)(benchmark::State& state) + { + AZ::Dom::JsonBackend backend; + AZStd::string serializedPayload = GenerateDomJsonBenchmarkPayload(state.range(0), state.range(1)); + + for (auto _ : state) + { + auto result = AZ::Dom::Json::WriteToRapidJsonDocument( + [&](AZ::Dom::Visitor& visitor) + { + return AZ::Dom::Utils::ReadFromString(backend, serializedPayload, AZ::Dom::Lifetime::Temporary, visitor); + }); + + benchmark::DoNotOptimize(result.GetValue()); + } + + state.SetBytesProcessed(serializedPayload.size() * state.iterations()); + } + BENCHMARK_REGISTER_JSON(DomJsonBenchmark, DomDeserializeToDocument) + + BENCHMARK_DEFINE_F(DomJsonBenchmark, JsonUtilsDeserializeToDocument)(benchmark::State& state) + { + AZ::Dom::JsonBackend backend; + AZStd::string serializedPayload = GenerateDomJsonBenchmarkPayload(state.range(0), state.range(1)); + + for (auto _ : state) + { + auto result = AZ::JsonSerializationUtils::ReadJsonString(serializedPayload); + + benchmark::DoNotOptimize(result.GetValue()); + } + + state.SetBytesProcessed(serializedPayload.size() * state.iterations()); + } + BENCHMARK_REGISTER_JSON(DomJsonBenchmark, JsonUtilsDeserializeToDocument) + +#undef BENCHMARK_REGISTER_JSON +} // namespace Benchmark + +#endif // defined(HAVE_BENCHMARK) diff --git a/Code/Framework/AzCore/Tests/DOM/DomJsonTests.cpp b/Code/Framework/AzCore/Tests/DOM/DomJsonTests.cpp new file mode 100644 index 0000000000..c7af6438cf --- /dev/null +++ b/Code/Framework/AzCore/Tests/DOM/DomJsonTests.cpp @@ -0,0 +1,219 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +namespace AZ::Dom::Tests +{ + class DomJsonTests : public UnitTest::AllocatorsFixture + { + public: + void SetUp() override + { + UnitTest::AllocatorsFixture::SetUp(); + NameDictionary::Create(); + m_document = AZStd::make_unique(); + } + + void TearDown() override + { + m_document.reset(); + NameDictionary::Destroy(); + UnitTest::AllocatorsFixture::TearDown(); + } + + rapidjson::Value CreateString(const AZStd::string& text) + { + rapidjson::Value key; + key.SetString(text.c_str(), static_cast(text.length()), m_document->GetAllocator()); + return key; + } + + template + void AddValue(const AZStd::string& key, T value) + { + m_document->AddMember(CreateString(key), rapidjson::Value(value), m_document->GetAllocator()); + } + + // Validate round-trip serialization to and from rapidjson::Document and a UTF-8 encoded string + void PerformSerializationChecks() + { + // Generate a canonical serializaed representation of this document using rapidjson + // This will be pretty-printed using the same rapidjson pretty printer we use, so should be binary identical + // to any output generated by the visitor API + AZStd::string canonicalSerializedDocument; + AZ::JsonSerializationUtils::WriteJsonString(*m_document, canonicalSerializedDocument); + + auto visitDocumentFn = [this](AZ::Dom::Visitor& visitor) + { + return Json::VisitRapidJsonValue(*m_document, visitor, Lifetime::Temporary); + }; + + // Document -> Document + { + auto result = Json::WriteToRapidJsonDocument(visitDocumentFn); + EXPECT_TRUE(result.IsSuccess()); + EXPECT_EQ(AZ::JsonSerialization::Compare(*m_document, result.GetValue()), AZ::JsonSerializerCompareResult::Equal); + } + + // Document -> string + { + AZStd::string serializedDocument; + JsonBackend backend; + auto result = backend.WriteToBuffer(serializedDocument, visitDocumentFn); + EXPECT_TRUE(result.IsSuccess()); + EXPECT_EQ(canonicalSerializedDocument, serializedDocument); + } + + // string -> Document + { + auto result = Json::WriteToRapidJsonDocument( + [&canonicalSerializedDocument](AZ::Dom::Visitor& visitor) + { + JsonBackend backend; + return Dom::Utils::ReadFromString(backend, canonicalSerializedDocument, Lifetime::Temporary, visitor); + }); + EXPECT_TRUE(result.IsSuccess()); + EXPECT_EQ(AZ::JsonSerialization::Compare(*m_document, result.GetValue()), JsonSerializerCompareResult::Equal); + } + + // string -> string + { + AZStd::string serializedDocument; + JsonBackend backend; + auto result = backend.WriteToBuffer( + serializedDocument, + [&backend, &canonicalSerializedDocument](AZ::Dom::Visitor& visitor) + { + return Dom::Utils::ReadFromString(backend, canonicalSerializedDocument, Lifetime::Temporary, visitor); + }); + EXPECT_TRUE(result.IsSuccess()); + EXPECT_EQ(canonicalSerializedDocument, serializedDocument); + } + } + + AZStd::unique_ptr m_document; + }; + + TEST_F(DomJsonTests, EmptyArray) + { + m_document->SetArray(); + PerformSerializationChecks(); + } + + TEST_F(DomJsonTests, SimpleArray) + { + m_document->SetArray(); + for (int i = 0; i < 5; ++i) + { + m_document->PushBack(i, m_document->GetAllocator()); + } + PerformSerializationChecks(); + } + + TEST_F(DomJsonTests, NestedArrays) + { + m_document->SetArray(); + for (int j = 0; j < 7; ++j) + { + rapidjson::Value nestedArray(rapidjson::kArrayType); + for (int i = 0; i < 5; ++i) + { + nestedArray.PushBack(i, m_document->GetAllocator()); + } + m_document->PushBack(nestedArray.Move(), m_document->GetAllocator()); + } + PerformSerializationChecks(); + } + + TEST_F(DomJsonTests, EmptyObject) + { + m_document->SetObject(); + PerformSerializationChecks(); + } + + TEST_F(DomJsonTests, SimpleObject) + { + m_document->SetObject(); + for (int i = 0; i < 5; ++i) + { + m_document->AddMember(CreateString(AZStd::string::format("Key%i", i)), rapidjson::Value(i), m_document->GetAllocator()); + } + PerformSerializationChecks(); + } + + TEST_F(DomJsonTests, NestedObjects) + { + m_document->SetObject(); + for (int j = 0; j < 7; ++j) + { + rapidjson::Value nestedObject(rapidjson::kObjectType); + for (int i = 0; i < 5; ++i) + { + nestedObject.AddMember(CreateString(AZStd::string::format("Key%i", i)), rapidjson::Value(i), m_document->GetAllocator()); + } + m_document->AddMember(CreateString(AZStd::string::format("Obj%i", j)), nestedObject.Move(), m_document->GetAllocator()); + } + PerformSerializationChecks(); + } + + TEST_F(DomJsonTests, Int64) + { + m_document->SetObject(); + AddValue("int64_min", AZStd::numeric_limits::min()); + AddValue("int64_max", AZStd::numeric_limits::max()); + PerformSerializationChecks(); + } + + TEST_F(DomJsonTests, Uint64) + { + m_document->SetObject(); + AddValue("uint64_min", AZStd::numeric_limits::min()); + AddValue("uint64_max", AZStd::numeric_limits::max()); + PerformSerializationChecks(); + } + + TEST_F(DomJsonTests, Double) + { + m_document->SetObject(); + AddValue("double_min", AZStd::numeric_limits::min()); + AddValue("double_max", AZStd::numeric_limits::max()); + PerformSerializationChecks(); + } + + TEST_F(DomJsonTests, Null) + { + m_document->SetObject(); + m_document->AddMember(CreateString("null_value"), rapidjson::Value(rapidjson::kNullType), m_document->GetAllocator()); + PerformSerializationChecks(); + } + + TEST_F(DomJsonTests, Bool) + { + m_document->SetObject(); + AddValue("true_value", true); + AddValue("false_value", false); + PerformSerializationChecks(); + } + + TEST_F(DomJsonTests, String) + { + m_document->SetObject(); + m_document->AddMember(CreateString("empty_string"), CreateString(""), m_document->GetAllocator()); + m_document->AddMember(CreateString("short_string"), CreateString("test"), m_document->GetAllocator()); + m_document->AddMember( + CreateString("long_string"), CreateString("abcdefghijklmnopqrstuvwxyz0123456789"), m_document->GetAllocator()); + PerformSerializationChecks(); + } +} // namespace AZ::Dom::Tests diff --git a/Code/Framework/AzCore/Tests/Debug.cpp b/Code/Framework/AzCore/Tests/Debug.cpp index 0d6e1a51e0..e27cdcd27e 100644 --- a/Code/Framework/AzCore/Tests/Debug.cpp +++ b/Code/Framework/AzCore/Tests/Debug.cpp @@ -6,9 +6,8 @@ * */ -#include #include -#include +#include #include #include #include @@ -105,7 +104,7 @@ namespace UnitTest } class TraceTest - : public AZ::Debug::TraceMessageDrillerBus::Handler + : public AZ::Debug::TraceMessageBus::Handler , public ::testing::Test { int m_numTracePrintfs; @@ -114,12 +113,13 @@ namespace UnitTest : m_numTracePrintfs(0) {} ////////////////////////////////////////////////////////////////////////// - // TraceMessagesDrillerBus - void OnPrintf(const char* windowName, const char* message) override + // TraceMessageBus + bool OnPrintf(const char* windowName, const char* message) override { (void)windowName; (void)message; ++m_numTracePrintfs; + return false; } ////////////////////////////////////////////////////////////////////////// diff --git a/Code/Framework/AzCore/Tests/Debug/AssetTracking.cpp b/Code/Framework/AzCore/Tests/Debug/AssetTracking.cpp deleted file mode 100644 index 89968406c7..0000000000 --- a/Code/Framework/AzCore/Tests/Debug/AssetTracking.cpp +++ /dev/null @@ -1,169 +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 -#include -#include -#include - -using namespace AZ; - -namespace UnitTest -{ - namespace - { - struct TestData - { - }; - - using AssetTree = AZ::Debug::AssetTree; - using AllocationTable = AZ::Debug::AllocationTable; - - struct AssetTrackingTestEnvironment - { - AssetTrackingTestEnvironment() : m_table(m_mutex) - { - } - - AZStd::mutex m_mutex; - AssetTree m_tree; - AllocationTable m_table; - AZStd::unique_ptr m_assetTracking; - }; - } - - class AssetTrackingTests - : public ::testing::Test - { - public: - void SetUp() override - { - AZ::AllocatorInstance::Create(); - m_env.reset(new AssetTrackingTestEnvironment); - m_env->m_assetTracking.reset(aznew AZ::Debug::AssetTracking(&m_env->m_tree, &m_env->m_table)); - } - - void TearDown() override - { - m_env.reset(); - AZ::AllocatorInstance::Destroy(); - } - - void RunTests() - { - TestAssetScopes(); - TestScopedAllocation(); - TestScopeAttach(); - } - - private: - void TestAssetScopes() - { - const char* debugScopeText; - { - AZ_ASSET_NAMED_SCOPE("TestAssetScopes.1"); - { - AZ_ASSET_NAMED_SCOPE("TestAssetScopes.2"); - { - AZ_ASSET_NAMED_SCOPE("TestAssetScopes.3"); - debugScopeText = AZ::Debug::AssetTracking::GetDebugScope(); - EXPECT_STREQ(debugScopeText, "TestAssetScopes.3\nTestAssetScopes.2\nTestAssetScopes.1\n"); - } - - debugScopeText = AZ::Debug::AssetTracking::GetDebugScope(); - EXPECT_STREQ(debugScopeText, "TestAssetScopes.2\nTestAssetScopes.1\n"); - } - - - debugScopeText = AZ::Debug::AssetTracking::GetDebugScope(); - EXPECT_STREQ(debugScopeText, "TestAssetScopes.1\n"); - } - } - - void TestScopedAllocation() - { - void* TEST_POINTER = (void*)0xDEADBEEFull; - static const size_t TEST_SIZE = 32; - - { - AZ_ASSET_NAMED_SCOPE("TestScopedAllocation.1"); - AZ::Debug::AssetTreeNodeBase* activeAsset = m_env->m_assetTracking->GetCurrentThreadAsset(); - m_env->m_table.Get().emplace(TEST_POINTER, AllocationTable::RecordType{ activeAsset, (uint32_t)TEST_SIZE, TestData() }); - } - - auto& rootAsset = m_env->m_tree.m_rootAssets; - auto itr = rootAsset.m_children.find("TestScopedAllocation.1"); - - EXPECT_EQ(&rootAsset, &m_env->m_tree.GetRoot()); - ASSERT_NE(itr, rootAsset.m_children.end()); - EXPECT_EQ(itr->second.m_primaryinfo->m_id->m_id, "TestScopedAllocation.1"); - - EXPECT_EQ(&itr->second, m_env->m_table.FindAllocation(TEST_POINTER)); - - auto allocationRecord = m_env->m_table.Get().find(TEST_POINTER); - ASSERT_NE(allocationRecord, m_env->m_table.Get().end()); - EXPECT_EQ(allocationRecord->second.m_asset, &itr->second); - EXPECT_EQ(allocationRecord->second.m_size, TEST_SIZE); - - // Test realocation - void* TEST_REALLOC_POINTER = (void*)0xDEADC0DEull; - static const size_t TEST_REALLOC_SIZE = 128; - - m_env->m_table.ReallocateAllocation(TEST_POINTER, TEST_REALLOC_POINTER, TEST_REALLOC_SIZE); - allocationRecord = m_env->m_table.Get().find(TEST_POINTER); - EXPECT_EQ(allocationRecord, m_env->m_table.Get().end()); - allocationRecord = m_env->m_table.Get().find(TEST_REALLOC_POINTER); - ASSERT_NE(allocationRecord, m_env->m_table.Get().end()); - EXPECT_EQ(allocationRecord->second.m_asset, &itr->second); - EXPECT_EQ(allocationRecord->second.m_size, TEST_REALLOC_SIZE); - - // Test resize - static const size_t TEST_RESIZE_SIZE = 1024; - m_env->m_table.ResizeAllocation(TEST_REALLOC_POINTER, TEST_RESIZE_SIZE); - EXPECT_EQ(allocationRecord->second.m_size, TEST_RESIZE_SIZE); - } - - void TestScopeAttach() - { - void* TEST_POINTER = (void*)0xDEADBEEFull; - static const size_t TEST_SIZE = 32; - AZ::Debug::AssetTreeNodeBase* originatingAsset; - - { - AZ_ASSET_ATTACH_TO_SCOPE(TEST_POINTER); - EXPECT_EQ(m_env->m_assetTracking->GetCurrentThreadAsset(), nullptr); - } - - { - AZ_ASSET_NAMED_SCOPE("TestScopeAttach.1"); - { - AZ_ASSET_NAMED_SCOPE("TestScopeAttach.2"); - { - originatingAsset = m_env->m_assetTracking->GetCurrentThreadAsset(); - EXPECT_NE(originatingAsset, nullptr); - m_env->m_table.Get().emplace(TEST_POINTER, AllocationTable::RecordType{ originatingAsset, (uint32_t)TEST_SIZE, TestData() }); - } - } - } - - EXPECT_EQ(m_env->m_assetTracking->GetCurrentThreadAsset(), nullptr); - - { - AZ_ASSET_ATTACH_TO_SCOPE(TEST_POINTER); - EXPECT_EQ(m_env->m_assetTracking->GetCurrentThreadAsset(), originatingAsset); - } - } - - AZStd::unique_ptr m_env; - }; - - TEST_F(AssetTrackingTests, Test) - { - RunTests(); - } -} diff --git a/Code/Framework/AzCore/Tests/EBus.cpp b/Code/Framework/AzCore/Tests/EBus.cpp index 9acf0f8a05..470a95435b 100644 --- a/Code/Framework/AzCore/Tests/EBus.cpp +++ b/Code/Framework/AzCore/Tests/EBus.cpp @@ -2500,7 +2500,7 @@ namespace UnitTest namespace RoutingTest { - class DrillerInterceptor : public EBusVersion1::Router + class EBusInterceptor : public EBusVersion1::Router { public: void OnEvent(int a) override @@ -2555,20 +2555,20 @@ namespace UnitTest TEST_F(EBus, Routing) { using namespace RoutingTest; - DrillerInterceptor driller; + EBusInterceptor interceptor; EBusVersion1Handler v1Handler; v1Handler.BusConnect(); - driller.BusRouterConnect(); + interceptor.BusRouterConnect(); EBusVersion1::Broadcast(&EBusVersion1::Events::OnEvent, 1020); - EXPECT_EQ(1, driller.m_numOnEvent); + EXPECT_EQ(1, interceptor.m_numOnEvent); EXPECT_EQ(1, v1Handler.m_numOnEvent); - driller.BusRouterDisconnect(); + interceptor.BusRouterDisconnect(); EBusVersion1::Broadcast(&EBusVersion1::Events::OnEvent, 1020); - EXPECT_EQ(1, driller.m_numOnEvent); + EXPECT_EQ(1, interceptor.m_numOnEvent); EXPECT_EQ(2, v1Handler.m_numOnEvent); // routing events diff --git a/Code/Framework/AzCore/Tests/EBus/ScheduledEventTests.cpp b/Code/Framework/AzCore/Tests/EBus/ScheduledEventTests.cpp index ec9a09fb09..b9c690edcc 100644 --- a/Code/Framework/AzCore/Tests/EBus/ScheduledEventTests.cpp +++ b/Code/Framework/AzCore/Tests/EBus/ScheduledEventTests.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include @@ -26,22 +26,22 @@ namespace UnitTest SetupAllocator(); AZ::NameDictionary::Create(); - m_loggerComponent = new AZ::LoggerSystemComponent; - m_timeComponent = new AZ::TimeSystemComponent; - m_eventSchedulerComponent = new AZ::EventSchedulerSystemComponent; + m_loggerComponent = AZStd::make_unique(); + m_timeSystem = AZStd::make_unique(); + m_eventSchedulerComponent = AZStd::make_unique(); - m_testEvent = new AZ::ScheduledEvent([this] { TestBasicEvent(); }, AZ::Name("UnitTestEvent fire once event")); - m_testRequeue = new AZ::ScheduledEvent([this] { TestAutoRequeuedEvent(); }, AZ::Name("UnitTestEvent auto Requeue")); + m_testEvent = AZStd::make_unique([this] { TestBasicEvent(); }, AZ::Name("UnitTestEvent fire once event")); + m_testRequeue = AZStd::make_unique([this] { TestAutoRequeuedEvent(); }, AZ::Name("UnitTestEvent auto Requeue")); } void TearDown() override { - delete m_testEvent; - delete m_testRequeue; + m_testEvent.reset(); + m_testRequeue.reset(); - delete m_eventSchedulerComponent; - delete m_timeComponent; - delete m_loggerComponent; + m_eventSchedulerComponent.reset(); + m_timeSystem.reset(); + m_loggerComponent.reset(); AZ::NameDictionary::Destroy(); TeardownAllocator(); @@ -60,12 +60,12 @@ namespace UnitTest uint32_t m_basicEventTriggerCount = 0; uint32_t m_requeuedEventTriggerCount = 0; - AZ::ScheduledEvent* m_testEvent = nullptr; - AZ::ScheduledEvent* m_testRequeue = nullptr; + AZStd::unique_ptr m_testEvent; + AZStd::unique_ptr m_testRequeue; - AZ::LoggerSystemComponent* m_loggerComponent = nullptr; - AZ::TimeSystemComponent* m_timeComponent = nullptr; - AZ::EventSchedulerSystemComponent* m_eventSchedulerComponent = nullptr; + AZStd::unique_ptr m_loggerComponent; + AZStd::unique_ptr m_timeSystem; + AZStd::unique_ptr m_eventSchedulerComponent; }; TEST_F(ScheduledEventTests, TestFireOnce) diff --git a/Code/Framework/AzCore/Tests/Math/FrustumTests.cpp b/Code/Framework/AzCore/Tests/Math/FrustumTests.cpp index 294d622a92..31fa6b626f 100644 --- a/Code/Framework/AzCore/Tests/Math/FrustumTests.cpp +++ b/Code/Framework/AzCore/Tests/Math/FrustumTests.cpp @@ -552,7 +552,6 @@ namespace UnitTest box.testCaseName = "BoxShaped"; frustums.push_back(box); - // Default values in a CCamera from Cry_Camera.h FrustumTestCase defaultCameraFrustum; defaultCameraFrustum.nearTopLeft = AZ::Vector3(-0.204621f, 0.200000f, 0.153465f); defaultCameraFrustum.nearTopRight = AZ::Vector3(0.204621f, 0.200000f, 0.153465f); diff --git a/Code/Framework/AzCore/Tests/Math/MathTestData.h b/Code/Framework/AzCore/Tests/Math/MathTestData.h index f87ab8f448..72a98e835d 100644 --- a/Code/Framework/AzCore/Tests/Math/MathTestData.h +++ b/Code/Framework/AzCore/Tests/Math/MathTestData.h @@ -33,6 +33,15 @@ namespace MathTestData AZ::Matrix3x3::CreateScale(AZ::Vector3(0.7f, 1.3f, 0.9f)) }; + static const AZ::Matrix4x4 Matrix4x4s[] = { + AZ::Matrix4x4::CreateIdentity(), + AZ::Matrix4x4::CreateFromQuaternionAndTranslation(AZ::Quaternion(-0.46f, 0.26f, -0.22f, 0.82f), AZ::Vector3(1.0f, 5.0f, 10.0f)), + AZ::Matrix4x4::CreateFromTransform(AZ::Transform::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateScale(AZ::Vector3(1.0f, 2.0f, 3.0f)), AZ::Vector3(2.0f, 4.0f, 6.0f))), + AZ::Matrix4x4::CreateScale(AZ::Vector3(5.0f, 10.0f, 15.0f)), + AZ::Matrix4x4::CreateRotationZ(AZ::DegToRad(45.0f)) + }; + using AxisPair = AZStd::pair; static const AxisPair Axes[] = { { AZ::Constants::Axis::XPositive, AZ::Vector3::CreateAxisX(1.0f) }, diff --git a/Code/Framework/AzCore/Tests/Math/Matrix3x4Tests.cpp b/Code/Framework/AzCore/Tests/Math/Matrix3x4Tests.cpp index 8f8f8ca1e9..20d1107757 100644 --- a/Code/Framework/AzCore/Tests/Math/Matrix3x4Tests.cpp +++ b/Code/Framework/AzCore/Tests/Math/Matrix3x4Tests.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "MathTestData.h" @@ -392,6 +393,32 @@ namespace UnitTest INSTANTIATE_TEST_CASE_P(MATH_Matrix3x4, Matrix3x4CreateFromMatrix3x3Fixture, ::testing::ValuesIn(MathTestData::Matrix3x3s)); + using Matrix3x4CreateFromMatrix4x4Fixture = ::testing::TestWithParam; + + TEST_P(Matrix3x4CreateFromMatrix4x4Fixture, UnsafeCreateFromMatrix4x4) + { + const AZ::Matrix4x4 matrix4x4 = GetParam(); + const AZ::Matrix3x4 matrix3x4 = AZ::Matrix3x4::UnsafeCreateFromMatrix4x4(matrix4x4); + EXPECT_THAT(matrix3x4.GetTranslation(), IsClose(matrix4x4.GetTranslation())); + const AZ::Vector3 vector(2.3f, -0.6, 1.8f); + EXPECT_THAT(matrix3x4.TransformVector(vector), IsClose((matrix4x4 * AZ::Vector3ToVector4(vector, 0.0f)).GetAsVector3())); + const AZ::Vector3 point(12.3f, -5.6, 7.3f); + EXPECT_THAT(matrix3x4.TransformPoint(point), IsClose((matrix4x4 * AZ::Vector3ToVector4(point, 1.0f)).GetAsVector3())); + } + + INSTANTIATE_TEST_CASE_P(MATH_Matrix3x4, Matrix3x4CreateFromMatrix4x4Fixture, ::testing::ValuesIn(MathTestData::Matrix4x4s)); + + TEST(MATH_Matrix3x4, TransformPoint) + { + const AZ::Matrix3x4 matrix3x4 = AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation( + AZ::Matrix3x3::CreateRotationY(AZ::DegToRad(90.0f)), AZ::Vector3(5.0f, 0.0f, 0.0f)); + + const AZ::Vector3 result = matrix3x4.TransformPoint(AZ::Vector3(1.0f, 0.0f, 0.0f)); + const AZ::Vector3 expected = AZ::Vector3(5.0f, 0.0f, -1.0f); + + EXPECT_THAT(result, expected); + } + TEST(MATH_Matrix3x4, CreateScale) { const AZ::Vector3 scale(1.7f, 0.3f, 2.4f); diff --git a/Code/Framework/AzCore/Tests/Math/TransformTests.cpp b/Code/Framework/AzCore/Tests/Math/TransformTests.cpp index 0138d32376..752c622ae8 100644 --- a/Code/Framework/AzCore/Tests/Math/TransformTests.cpp +++ b/Code/Framework/AzCore/Tests/Math/TransformTests.cpp @@ -402,17 +402,18 @@ namespace UnitTest AllocatorsFixture::SetUp(); AZ::ComponentApplication::Descriptor desc; desc.m_useExistingAllocator = true; - desc.m_enableDrilling = false; // we already created a memory driller for the test (AllocatorsFixture) - m_app.Create(desc); + m_app.reset(aznew AZ::ComponentApplication); + m_app->Create(desc); } void TearDown() override { - m_app.Destroy(); + m_app->Destroy(); + m_app.reset(); AllocatorsFixture::TearDown(); } - AZ::ComponentApplication m_app; + AZStd::unique_ptr m_app; }; TEST_F(MATH_TransformApplicationFixture, DeserializingOldFormat) diff --git a/Code/Framework/AzCore/Tests/Memory.cpp b/Code/Framework/AzCore/Tests/Memory.cpp index 5a483c1ed1..5287167c8b 100644 --- a/Code/Framework/AzCore/Tests/Memory.cpp +++ b/Code/Framework/AzCore/Tests/Memory.cpp @@ -12,8 +12,6 @@ #include #include -#include -#include #include #include #include @@ -44,16 +42,13 @@ namespace UnitTest void SetUp() override { AZ::AllocatorManager::Instance().SetDefaultTrackingMode(AZ::Debug::AllocationRecords::RECORD_FULL); - m_drillerManager = Debug::DrillerManager::Create(); - m_drillerManager->Register(aznew MemoryDriller); + AZ::AllocatorManager::Instance().EnterProfilingMode(); } void TearDown() override { - Debug::DrillerManager::Destroy(m_drillerManager); + AZ::AllocatorManager::Instance().ExitProfilingMode(); AZ::AllocatorManager::Instance().SetDefaultTrackingMode(AZ::Debug::AllocationRecords::RECORD_NO_RECORDS); } - protected: - Debug::DrillerManager* m_drillerManager = nullptr; }; class SystemAllocatorTest diff --git a/Code/Framework/AzCore/Tests/Memory/LeakDetection.cpp b/Code/Framework/AzCore/Tests/Memory/LeakDetection.cpp index 09255ce16f..dcfc931141 100644 --- a/Code/Framework/AzCore/Tests/Memory/LeakDetection.cpp +++ b/Code/Framework/AzCore/Tests/Memory/LeakDetection.cpp @@ -270,8 +270,7 @@ namespace UnitTest void SetUp() override { - m_drillerManager = AZ::Debug::DrillerManager::Create(); - m_drillerManager->Register(aznew AZ::Debug::MemoryDriller); + AZ::AllocatorManager::Instance().EnterProfilingMode(); AZ::AllocatorManager::Instance().SetDefaultTrackingMode(AZ::Debug::AllocationRecords::RECORD_FULL); if (azrtti_typeid() != azrtti_typeid()) // simplifies instead of template specialization @@ -292,7 +291,7 @@ namespace UnitTest // Other allocators need the SystemAllocator in order to work AZ::AllocatorInstance::Destroy(); } - AZ::Debug::DrillerManager::Destroy(m_drillerManager); + AZ::AllocatorManager::Instance().ExitProfilingMode(); m_busRedirector.BusDisconnect(); EXPECT_EQ(m_leakExpected, m_leakDetected); @@ -352,7 +351,6 @@ namespace UnitTest }; BusRedirector m_busRedirector; - AZ::Debug::DrillerManager* m_drillerManager = nullptr; bool m_leakDetected = false; bool m_leakExpected = false; }; diff --git a/Code/Framework/AzCore/Tests/Rtti.cpp b/Code/Framework/AzCore/Tests/Rtti.cpp index 935df848c2..85089b0602 100644 --- a/Code/Framework/AzCore/Tests/Rtti.cpp +++ b/Code/Framework/AzCore/Tests/Rtti.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include diff --git a/Code/Framework/AzCore/Tests/Serialization.cpp b/Code/Framework/AzCore/Tests/Serialization.cpp index 219744a480..1666913317 100644 --- a/Code/Framework/AzCore/Tests/Serialization.cpp +++ b/Code/Framework/AzCore/Tests/Serialization.cpp @@ -59,6 +59,7 @@ #include #include #include +#include #include #include @@ -8151,5 +8152,98 @@ namespace UnitTest m_serializeContext->Class(); m_serializeContext->DisableRemoveReflection(); } + + template + class PathSerializationParamFixture + : public ScopedAllocatorSetupFixture + , public ::testing::WithParamInterface + { + public: + PathSerializationParamFixture() + : ScopedAllocatorSetupFixture( + []() { AZ::SystemAllocator::Descriptor desc; desc.m_stackRecordLevels = 30; return desc; }() + ) + {} + + // We must expose the class for serialization first. + void SetUp() override + { + m_serializeContext = AZStd::make_unique(); + AZ::IO::PathReflect(m_serializeContext.get()); + + } + + void TearDown() override + { + m_serializeContext->EnableRemoveReflection(); + AZ::IO::PathReflect(m_serializeContext.get()); + m_serializeContext->DisableRemoveReflection(); + + m_serializeContext.reset(); + } + + protected: + AZStd::unique_ptr m_serializeContext; + }; + + struct PathSerializationParams + { + const char m_preferredSeparator{}; + const char* m_testPath{}; + }; + using PathSerializationFixture = PathSerializationParamFixture; + + TEST_P(PathSerializationFixture, PathSerializer_SerializesStringBackedPath_Succeeds) + { + const auto& testParams = GetParam(); + { + // Path serialization + AZ::IO::Path testPath{ testParams.m_testPath, testParams.m_preferredSeparator }; + + AZStd::vector byteBuffer; + AZ::IO::ByteContainerStream byteStream(&byteBuffer); + auto objStream = AZ::ObjectStream::Create(&byteStream, *m_serializeContext, AZ::ObjectStream::ST_XML); + objStream->WriteClass(&testPath); + objStream->Finalize(); + + byteStream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN); + + AZ::IO::Path loadPath{ testParams.m_preferredSeparator }; + EXPECT_TRUE(AZ::Utils::LoadObjectFromStreamInPlace(byteStream, loadPath, m_serializeContext.get())); + EXPECT_EQ(testPath.LexicallyNormal(), loadPath); + } + + { + // FixedMaxPath serialization + AZ::IO::FixedMaxPath testFixedMaxPath{ testParams.m_testPath, testParams.m_preferredSeparator }; + + AZStd::vector byteBuffer; + AZ::IO::ByteContainerStream byteStream(&byteBuffer); + auto objStream = AZ::ObjectStream::Create(&byteStream, *m_serializeContext, AZ::ObjectStream::ST_XML); + objStream->WriteClass(&testFixedMaxPath); + objStream->Finalize(); + + byteStream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN); + + AZ::IO::FixedMaxPath loadPath{ testParams.m_preferredSeparator }; + EXPECT_TRUE(AZ::Utils::LoadObjectFromStreamInPlace(byteStream, loadPath, m_serializeContext.get())); + EXPECT_EQ(testFixedMaxPath.LexicallyNormal(), loadPath); + } + } + + INSTANTIATE_TEST_CASE_P( + PathSerialization, + PathSerializationFixture, + ::testing::Values( + PathSerializationParams{ AZ::IO::PosixPathSeparator, "" }, + PathSerializationParams{ AZ::IO::PosixPathSeparator, "test" }, + PathSerializationParams{ AZ::IO::PosixPathSeparator, "/test" }, + PathSerializationParams{ AZ::IO::WindowsPathSeparator, "test" }, + PathSerializationParams{ AZ::IO::WindowsPathSeparator, "/test" }, + PathSerializationParams{ AZ::IO::WindowsPathSeparator, "D:test" }, + PathSerializationParams{ AZ::IO::WindowsPathSeparator, "D:/test" }, + PathSerializationParams{ AZ::IO::WindowsPathSeparator, "test/foo/../bar" } + ) + ); } diff --git a/Code/Framework/AzCore/Tests/Serialization/Json/BasicContainerSerializerTests.cpp b/Code/Framework/AzCore/Tests/Serialization/Json/BasicContainerSerializerTests.cpp index 4a6a8e9e8a..17a4cfbc7f 100644 --- a/Code/Framework/AzCore/Tests/Serialization/Json/BasicContainerSerializerTests.cpp +++ b/Code/Framework/AzCore/Tests/Serialization/Json/BasicContainerSerializerTests.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include diff --git a/Code/Framework/AzCore/Tests/Settings/SettingsRegistryMergeUtilsTests.cpp b/Code/Framework/AzCore/Tests/Settings/SettingsRegistryMergeUtilsTests.cpp index 00a18f7682..485593a699 100644 --- a/Code/Framework/AzCore/Tests/Settings/SettingsRegistryMergeUtilsTests.cpp +++ b/Code/Framework/AzCore/Tests/Settings/SettingsRegistryMergeUtilsTests.cpp @@ -539,6 +539,22 @@ tags=tools,renderer,metal)" EXPECT_STREQ("Bat", commandLine.GetMiscValue(2).c_str()); } + TEST_F(SettingsRegistryMergeUtilsCommandLineFixture, RegsetFileArgument_DoesNotMergeNUL) + { + AZStd::string regsetFile = AZ::IO::SystemFile::GetNullFilename(); + AZ::CommandLine commandLine; + commandLine.Parse({ "--regset-file", regsetFile }); + + AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_registry, commandLine, false); + + // Add a settings path to anchor loaded settings underneath + regsetFile = AZStd::string::format("%s::/AnchorPath/Of/Settings", AZ::IO::SystemFile::GetNullFilename()); + commandLine.Parse({ "--regset-file", regsetFile }); + + AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_registry, commandLine, false); + EXPECT_EQ(AZ::SettingsRegistryInterface::Type::NoType, m_registry->GetType("/AnchorPath/Of/Settings")); + } + using SettingsRegistryAncestorDescendantOrEqualPathFixture = SettingsRegistryMergeUtilsCommandLineFixture; TEST_F(SettingsRegistryAncestorDescendantOrEqualPathFixture, ValidateThatAncestorOrDescendantOrPathWithTheSameValue_Succeeds) diff --git a/Code/Framework/AzCore/Tests/StatisticalProfiler.cpp b/Code/Framework/AzCore/Tests/StatisticalProfiler.cpp index fc5fe66c4b..291f2a8110 100644 --- a/Code/Framework/AzCore/Tests/StatisticalProfiler.cpp +++ b/Code/Framework/AzCore/Tests/StatisticalProfiler.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include @@ -468,28 +468,29 @@ namespace UnitTest /** Trace message handler to track messages during tests */ struct MyTraceMessageSink final - : public AZ::Debug::TraceMessageDrillerBus::Handler + : public AZ::Debug::TraceMessageBus::Handler { MyTraceMessageSink() { - AZ::Debug::TraceMessageDrillerBus::Handler::BusConnect(); + AZ::Debug::TraceMessageBus::Handler::BusConnect(); } ~MyTraceMessageSink() { - AZ::Debug::TraceMessageDrillerBus::Handler::BusDisconnect(); + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); } ////////////////////////////////////////////////////////////////////////// - // TraceMessageDrillerBus - void OnPrintf(const char* window, const char* message) override + // TraceMessageBus + bool OnPrintf(const char* window, const char* message) override { - OnOutput(window, message); + return OnOutput(window, message); } - void OnOutput(const char* window, const char* message) override + bool OnOutput(const char* window, const char* message) override { printf("%s: %s\n", window, message); + return false; } }; //struct MyTraceMessageSink diff --git a/Code/Framework/AzCore/Tests/StreamerTests.cpp b/Code/Framework/AzCore/Tests/StreamerTests.cpp index fec81a9031..9007e94ce4 100644 --- a/Code/Framework/AzCore/Tests/StreamerTests.cpp +++ b/Code/Framework/AzCore/Tests/StreamerTests.cpp @@ -258,7 +258,6 @@ namespace AZ m_application = aznew GemTestApplication(); AZ::ComponentApplication::Descriptor appDesc; appDesc.m_useExistingAllocator = true; - appDesc.m_enableDrilling = false; auto m_systemEntity = m_application->Create(appDesc); m_systemEntity->AddComponent(aznew AZ::StreamerComponent()); m_systemEntity->Init(); diff --git a/Code/Framework/AzCore/Tests/TaskTests.cpp b/Code/Framework/AzCore/Tests/TaskTests.cpp index 9e839f60ee..4f53772d51 100644 --- a/Code/Framework/AzCore/Tests/TaskTests.cpp +++ b/Code/Framework/AzCore/Tests/TaskTests.cpp @@ -610,15 +610,16 @@ namespace UnitTest g.Follows(e, f); g.Precedes(d); - TaskGraphEvent ev; - graph.SubmitOnExecutor(*m_executor, &ev); - ev.Wait(); + TaskGraphEvent ev1; + graph.SubmitOnExecutor(*m_executor, &ev1); + ev1.Wait(); EXPECT_EQ(3 | 0b100000, x); x = 0; - graph.SubmitOnExecutor(*m_executor, &ev); - ev.Wait(); + TaskGraphEvent ev2; + graph.SubmitOnExecutor(*m_executor, &ev2); + ev2.Wait(); EXPECT_EQ(3 | 0b100000, x); } diff --git a/Code/Framework/AzCore/Tests/TestCatalog.cpp b/Code/Framework/AzCore/Tests/TestCatalog.cpp index c633c6391b..cb8fa11c72 100644 --- a/Code/Framework/AzCore/Tests/TestCatalog.cpp +++ b/Code/Framework/AzCore/Tests/TestCatalog.cpp @@ -167,7 +167,8 @@ namespace UnitTest if (!info.m_streamName.empty()) { AZStd::string fullName = GetTestFolderPath() + info.m_streamName; - info.m_dataLen = static_cast(IO::SystemFile::Length(fullName.c_str())); + IO::FileIOBase* io = IO::FileIOBase::GetInstance(); + io->Size(fullName.c_str(), info.m_dataLen); } else { @@ -187,8 +188,11 @@ namespace UnitTest if (!info.m_streamName.empty()) { + IO::FileIOBase* io = AZ::IO::FileIOBase::GetInstance(); + AZStd::string fullName = GetTestFolderPath() + info.m_streamName; - info.m_dataLen = static_cast(IO::SystemFile::Length(fullName.c_str())); + + io->Size(fullName.c_str(), info.m_dataLen); } else { diff --git a/Code/Framework/AzCore/Tests/Time/TimeTests.cpp b/Code/Framework/AzCore/Tests/Time/TimeTests.cpp index 6727ef1501..7ba01ec0e0 100644 --- a/Code/Framework/AzCore/Tests/Time/TimeTests.cpp +++ b/Code/Framework/AzCore/Tests/Time/TimeTests.cpp @@ -6,7 +6,7 @@ * */ -#include +#include #include namespace UnitTest @@ -18,16 +18,16 @@ namespace UnitTest void SetUp() override { SetupAllocator(); - m_timeComponent = new AZ::TimeSystemComponent; + m_timeSystem = AZStd::make_unique(); } void TearDown() override { - delete m_timeComponent; + m_timeSystem.reset(); TeardownAllocator(); } - AZ::TimeSystemComponent* m_timeComponent = nullptr; + AZStd::unique_ptr m_timeSystem; }; TEST_F(TimeTests, TestConversionUsToMs) @@ -44,6 +44,30 @@ namespace UnitTest EXPECT_EQ(timeUs, AZ::TimeUs{ 1000000 }); } + TEST_F(TimeTests, TestConversionTimeMsToSeconds) + { + AZ::TimeMs timeMs = AZ::TimeMs{ 1000 }; + float timeSecondsFloat = AZ::TimeMsToSeconds(timeMs); + EXPECT_TRUE(AZ::IsClose(timeSecondsFloat, 1.0f)); + + double timeSecondsDouble = AZ::TimeMsToSecondsDouble(timeMs); + EXPECT_TRUE(AZ::IsClose(timeSecondsDouble, 1.0)); + } + + TEST_F(TimeTests, TestConversionSecondsToTimeUs) + { + double seconds = 1.0; + AZ::TimeUs timeUs = AZ::SecondsToTimeUs(seconds); + EXPECT_EQ(timeUs, AZ::TimeUs{ 1000000 }); + } + + TEST_F(TimeTests, TestConversionSecondsToTimeMs) + { + double seconds = 1.0; + AZ::TimeMs timeMs = AZ::SecondsToTimeMs(seconds); + EXPECT_EQ(timeMs, AZ::TimeMs{ 1000 }); + } + TEST_F(TimeTests, TestClocks) { AZ::TimeUs timeUs = AZ::GetElapsedTimeUs(); diff --git a/Code/Framework/AzCore/Tests/azcoretests_files.cmake b/Code/Framework/AzCore/Tests/azcoretests_files.cmake index d39595c45e..b7fe113bc6 100644 --- a/Code/Framework/AzCore/Tests/azcoretests_files.cmake +++ b/Code/Framework/AzCore/Tests/azcoretests_files.cmake @@ -69,7 +69,6 @@ set(FILES TickBusTest.cpp UUIDTests.cpp XML.cpp - Debug/AssetTracking.cpp Debug/LocalFileEventLoggerTests.cpp Debug/Trace.cpp Debug/UnhandledExceptions.cpp @@ -213,6 +212,8 @@ set(FILES AZStd/Variant.cpp AZStd/VariantSerialization.cpp AZStd/VectorAndArray.cpp + DOM/DomJsonTests.cpp + DOM/DomJsonBenchmarks.cpp ) # Prevent the following files from being grouped in UNITY builds diff --git a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp index 768ef1f0b6..b3521a877f 100644 --- a/Code/Framework/AzFramework/AzFramework/Application/Application.cpp +++ b/Code/Framework/AzFramework/AzFramework/Application/Application.cpp @@ -553,11 +553,9 @@ namespace AzFramework //////////////////////////////////////////////////////////////////////////// - AZ_CVAR(float, t_frameTimeOverride, 0.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "If > 0, overrides the application delta frame-time with the provided value"); - - void Application::Tick(float deltaOverride /*= -1.f*/) + void Application::Tick() { - ComponentApplication::Tick((t_frameTimeOverride > 0.0f) ? t_frameTimeOverride : deltaOverride); + ComponentApplication::Tick(); } //////////////////////////////////////////////////////////////////////////// diff --git a/Code/Framework/AzFramework/AzFramework/Application/Application.h b/Code/Framework/AzFramework/AzFramework/Application/Application.h index a318ede4a2..27144e4376 100644 --- a/Code/Framework/AzFramework/AzFramework/Application/Application.h +++ b/Code/Framework/AzFramework/AzFramework/Application/Application.h @@ -87,7 +87,7 @@ namespace AzFramework */ virtual void Stop(); - void Tick(float deltaOverride = -1.f) override; + void Tick() override; AZ::ComponentTypeList GetRequiredSystemComponents() const override; diff --git a/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp b/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp index 0fede85aa7..04802a8d0e 100644 --- a/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp +++ b/Code/Framework/AzFramework/AzFramework/Archive/Archive.cpp @@ -34,7 +34,6 @@ #include #include #include -#include #include #include #include @@ -43,13 +42,10 @@ namespace AZ::IO { - AZ_CVAR(int, sys_PakPriority, aznumeric_cast(ArchiveVars{}.nPriority), nullptr, AZ::ConsoleFunctorFlags::Null, - "If set to 1, tells Archive to try to open the file in pak first, then go to file system"); - AZ_CVAR(int, sys_PakMessageInvalidFileAccess, ArchiveVars{}.nMessageInvalidFileAccess, nullptr, AZ::ConsoleFunctorFlags::Null, - "Message Box synchronous file access when in game"); - - AZ_CVAR(int, sys_PakWarnOnPakAccessFailures, ArchiveVars{}.nWarnOnPakAccessFails, nullptr, AZ::ConsoleFunctorFlags::Null, - "If 1, access failure for Paks is treated as a warning, if zero it is only a log message."); + AZ_CVAR(int, sys_PakPriority, aznumeric_cast(ArchiveVars{}.m_fileSearchPriority), nullptr, AZ::ConsoleFunctorFlags::Null, + "If set to 0, tells Archive to try to open the file on the file system first othewise check mounted paks.\n" + "If set to 1, tells Archive to try to open the file in pak first, then go to file system.\n" + "If set to 2, tells the Archive to only open files from the pak"); AZ_CVAR(int, sys_report_files_not_found_in_paks, 0, nullptr, AZ::ConsoleFunctorFlags::Null, "Reports when files are searched for in paks and not found. 1 = log, 2 = warning, 3 = error"); AZ_CVAR(int32_t, az_archive_verbosity, 0, nullptr, AZ::ConsoleFunctorFlags::Null, @@ -437,9 +433,9 @@ namespace AZ::IO ////////////////////////////////////////////////////////////////////////// - bool Archive::IsFileExist(AZStd::string_view sFilename, EFileSearchLocation fileLocation) + bool Archive::IsFileExist(AZStd::string_view sFilename, FileSearchLocation fileLocation) { - const AZ::IO::ArchiveLocationPriority nVarPakPriority = GetPakPriority(); + const AZ::IO::FileSearchPriority nVarPakPriority = GetPakPriority(); auto szFullPath = AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(sFilename); if (!szFullPath) @@ -450,25 +446,25 @@ namespace AZ::IO switch(fileLocation) { - case IArchive::eFileLocation_Any: + case FileSearchLocation::Any: // Search for file based on pak priority switch (nVarPakPriority) { - case ArchiveLocationPriority::ePakPriorityFileFirst: + case FileSearchPriority::FileFirst: return FileIOBase::GetDirectInstance()->Exists(szFullPath->c_str()) || FindPakFileEntry(szFullPath->Native()); - case ArchiveLocationPriority::ePakPriorityPakFirst: + case FileSearchPriority::PakFirst: return FindPakFileEntry(szFullPath->Native()) || IO::FileIOBase::GetDirectInstance()->Exists(szFullPath->c_str()); - case ArchiveLocationPriority::ePakPriorityPakOnly: + case FileSearchPriority::PakOnly: return FindPakFileEntry(szFullPath->Native()); default: - AZ_Assert(false, "PakPriority %d doesn't match a value in the ArchiveLocationPriority enum", + AZ_Assert(false, "PakPriority %d doesn't match a value in the FileSearchPriority enum", aznumeric_cast(nVarPakPriority)); } break; - case IArchive::eFileLocation_InPak: + case FileSearchLocation::InPak: return FindPakFileEntry(szFullPath->Native()); - case IArchive::eFileLocation_OnDisk: - if (nVarPakPriority != ArchiveLocationPriority::ePakPriorityPakOnly) + case FileSearchLocation::OnDisk: + if (nVarPakPriority != FileSearchPriority::PakOnly) { return FileIOBase::GetDirectInstance()->Exists(szFullPath->c_str()); } @@ -485,7 +481,7 @@ namespace AZ::IO ////////////////////////////////////////////////////////////////////////// bool Archive::IsFolder(AZStd::string_view sPath) { - AZStd::fixed_string filePath{ sPath }; + AZ::IO::FixedMaxPath filePath{ sPath }; return AZ::IO::FileIOBase::GetDirectInstance()->IsDirectory(filePath.c_str()); } @@ -515,7 +511,7 @@ namespace AZ::IO // get the priority into local variable to avoid it changing in the course of // this function execution (?) - const ArchiveLocationPriority nVarPakPriority = GetPakPriority(); + const FileSearchPriority nVarPakPriority = GetPakPriority(); AZ::IO::OpenMode nOSFlags = AZ::IO::GetOpenModeFromStringMode(szMode); @@ -628,17 +624,17 @@ namespace AZ::IO switch (nVarPakPriority) { - case ArchiveLocationPriority::ePakPriorityFileFirst: + case FileSearchPriority::FileFirst: { AZ::IO::HandleType fileHandle = OpenFromFileSystem(); return fileHandle != AZ::IO::InvalidHandle ? fileHandle : OpenFromArchive(); } - case ArchiveLocationPriority::ePakPriorityPakFirst: + case FileSearchPriority::PakFirst: { AZ::IO::HandleType fileHandle = OpenFromArchive(); return fileHandle != AZ::IO::InvalidHandle ? fileHandle : OpenFromFileSystem(); } - case ArchiveLocationPriority::ePakPriorityPakOnly: + case FileSearchPriority::PakOnly: { return OpenFromArchive(); } @@ -810,7 +806,7 @@ namespace AZ::IO return 0; } - if (GetPakPriority() == ArchiveLocationPriority::ePakPriorityFileFirst) // if the file system files have priority now.. + if (GetPakPriority() == FileSearchPriority::FileFirst) // if the file system files have priority now.. { IArchive::SignedFileSize nFileSize = GetFileSizeOnDisk(fullPath->Native()); if (nFileSize != IArchive::FILE_NOT_PRESENT) @@ -825,7 +821,7 @@ namespace AZ::IO return pFileEntry->desc.lSizeUncompressed; } - if (bAllowUseFileSystem || GetPakPriority() == ArchiveLocationPriority::ePakPriorityPakFirst) // if the archive files had more priority, we didn't attempt fopen before- try it now + if (bAllowUseFileSystem || GetPakPriority() == FileSearchPriority::PakFirst) // if the archive files had more priority, we didn't attempt fopen before- try it now { IArchive::SignedFileSize nFileSize = GetFileSizeOnDisk(fullPath->Native()); if (nFileSize != IArchive::FILE_NOT_PRESENT) @@ -1023,7 +1019,7 @@ namespace AZ::IO ////////////////////////////////////////////////////////////////////////// - AZ::IO::ArchiveFileIterator Archive::FindFirst(AZStd::string_view pDir, EFileSearchType searchType) + AZ::IO::ArchiveFileIterator Archive::FindFirst(AZStd::string_view pDir, FileSearchLocation searchType) { auto szFullPath = AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(pDir); if (!szFullPath) @@ -1036,18 +1032,21 @@ namespace AZ::IO bool bAllowUseFileSystem{}; switch (searchType) { - case IArchive::eFileSearchType_AllowInZipsOnly: - bAllowUseFileSystem = false; - bScanZips = true; - break; - case IArchive::eFileSearchType_AllowOnDiskAndInZips: - bAllowUseFileSystem = true; - bScanZips = true; - break; - case IArchive::eFileSearchType_AllowOnDiskOnly: - bAllowUseFileSystem = true; - bScanZips = false; - break; + case FileSearchLocation::InPak: + bAllowUseFileSystem = false; + bScanZips = true; + break; + case FileSearchLocation::Any: + bAllowUseFileSystem = true; + bScanZips = true; + break; + case FileSearchLocation::OnDisk: + bAllowUseFileSystem = true; + bScanZips = false; + break; + default: + AZ_Assert(false, "Invalid search location value supplied"); + break; } AZStd::intrusive_ptr pFindData = aznew AZ::IO::FindData(); @@ -1218,7 +1217,7 @@ namespace AZ::IO else { // [LYN-2376] Remove once legacy slice support is removed - AZStd::vector levelDirs; + AZStd::vector levelDirs; if (addLevels) { @@ -1380,7 +1379,7 @@ namespace AZ::IO return true; } - if (AZ::IO::ArchiveFileIterator fileIterator = FindFirst(pWildcardIn, IArchive::eFileSearchType_AllowOnDiskOnly); fileIterator) + if (AZ::IO::ArchiveFileIterator fileIterator = FindFirst(pWildcardIn, FileSearchLocation::OnDisk); fileIterator) { AZStd::vector files; do @@ -1955,15 +1954,15 @@ namespace AZ::IO } // gets the current archive priority - ArchiveLocationPriority Archive::GetPakPriority() const + FileSearchPriority Archive::GetPakPriority() const { - int pakPriority = aznumeric_cast(ArchiveVars{}.nPriority); + FileSearchPriority pakPriority = ArchiveVars{}.m_fileSearchPriority; if (auto console = AZ::Interface::Get(); console != nullptr) { - [[maybe_unused]] AZ::GetValueResult getCvarResult = console->GetCvarValue("sys_PakPriority", pakPriority); + [[maybe_unused]] AZ::GetValueResult getCvarResult = console->GetCvarValue("sys_PakPriority", reinterpret_cast(pakPriority)); AZ_Error("Archive", getCvarResult == AZ::GetValueResult::Success, "Lookup of 'sys_PakPriority console variable failed with error %s", AZ::GetEnumString(getCvarResult)); } - return static_cast(pakPriority); + return pakPriority; } ////////////////////////////////////////////////////////////////////////// @@ -2030,13 +2029,13 @@ namespace AZ::IO switch (GetPakPriority()) { - case ArchiveLocationPriority::ePakPriorityFileFirst: + case FileSearchPriority::FileFirst: info.m_conflictResolution = AZ::IO::ConflictResolution::PreferFile; break; - case ArchiveLocationPriority::ePakPriorityPakFirst: + case FileSearchPriority::PakFirst: info.m_conflictResolution = AZ::IO::ConflictResolution::PreferArchive; break; - case ArchiveLocationPriority::ePakPriorityPakOnly: + case FileSearchPriority::PakOnly: info.m_conflictResolution = AZ::IO::ConflictResolution::UseArchiveOnly; break; } @@ -2147,13 +2146,13 @@ namespace AZ::IO return manifestInfo; } - AZStd::vector Archive::ScanForLevels(ZipDir::CachePtr pZip) + AZStd::vector Archive::ScanForLevels(ZipDir::CachePtr pZip) { - AZStd::queue scanDirs; - AZStd::vector levelDirs; - AZStd::string currentDir = "levels"; - AZStd::string currentDirPattern; - AZStd::string currentFilePattern; + AZStd::queue scanDirs; + AZStd::vector levelDirs; + AZ::IO::Path currentDir = "levels"; + AZ::IO::Path currentDirPattern; + AZ::IO::Path currentFilePattern; ZipDir::FindDir findDir(pZip); findDir.FindFirst(currentDir.c_str()); @@ -2171,11 +2170,10 @@ namespace AZ::IO scanDirs.pop(); } - currentDirPattern = currentDir + AZ_FILESYSTEM_SEPARATOR_WILDCARD; - currentFilePattern = currentDir + AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING + "level.pak"; + currentDirPattern = currentDir / "*"; + currentFilePattern = currentDir / "level.pak"; - ZipDir::FileEntry* fileEntry = findFile.FindExact(currentFilePattern.c_str()); - if (fileEntry) + if (ZipDir::FileEntry* fileEntry = findFile.FindExact(currentFilePattern); fileEntry) { levelDirs.emplace_back(currentDir); continue; @@ -2183,9 +2181,7 @@ namespace AZ::IO for (findDir.FindFirst(currentDirPattern.c_str()); findDir.GetDirEntry(); findDir.FindNext()) { - AZStd::string_view dirName = findDir.GetDirName(); - AZStd::string dirToAdd = AZStd::string::format("%s/%.*s", currentDir.data(), aznumeric_cast(dirName.size()), dirName.data()); - scanDirs.push(dirToAdd); + scanDirs.push(currentDir / findDir.GetDirName()); } } while (!scanDirs.empty()); diff --git a/Code/Framework/AzFramework/AzFramework/Archive/Archive.h b/Code/Framework/AzFramework/AzFramework/Archive/Archive.h index 279702b433..d429aa8f17 100644 --- a/Code/Framework/AzFramework/AzFramework/Archive/Archive.h +++ b/Code/Framework/AzFramework/AzFramework/Archive/Archive.h @@ -207,7 +207,7 @@ namespace AZ::IO uint64_t FTell(AZ::IO::HandleType handle) override; int FFlush(AZ::IO::HandleType handle) override; int FClose(AZ::IO::HandleType handle) override; - AZ::IO::ArchiveFileIterator FindFirst(AZStd::string_view pDir, EFileSearchType searchType = eFileSearchType_AllowInZipsOnly) override; + AZ::IO::ArchiveFileIterator FindFirst(AZStd::string_view pDir, FileSearchLocation searchType = FileSearchLocation::InPak) override; AZ::IO::ArchiveFileIterator FindNext(AZ::IO::ArchiveFileIterator fileIterator) override; bool FindClose(AZ::IO::ArchiveFileIterator fileIterator) override; int FEof(AZ::IO::HandleType handle) override; @@ -219,7 +219,7 @@ namespace AZ::IO bool RemoveDir(AZStd::string_view pName) override; // remove directory from FS (if supported) bool IsAbsPath(AZStd::string_view pPath) override; - bool IsFileExist(AZStd::string_view sFilename, EFileSearchLocation fileLocation = eFileLocation_Any) override; + bool IsFileExist(AZStd::string_view sFilename, FileSearchLocation fileLocation = FileSearchLocation::Any) override; bool IsFolder(AZStd::string_view sPath) override; IArchive::SignedFileSize GetFileSizeOnDisk(AZStd::string_view filename) override; @@ -255,7 +255,7 @@ namespace AZ::IO bool DisableRuntimeFileAccess(bool status, AZStd::thread_id threadId) override; // gets the current archive priority - ArchiveLocationPriority GetPakPriority() const override; + FileSearchPriority GetPakPriority() const override; uint64_t GetFileOffsetOnMedia(AZStd::string_view szName) const override; @@ -305,7 +305,7 @@ namespace AZ::IO AZStd::shared_ptr GetBundleCatalog(ZipDir::CachePtr pZip, const AZStd::string& catalogName); // [LYN-2376] Remove once legacy slice support is removed - AZStd::vector ScanForLevels(ZipDir::CachePtr pZip); + AZStd::vector ScanForLevels(ZipDir::CachePtr pZip); mutable AZStd::shared_mutex m_csOpenFiles; ZipPseudoFileArray m_arrOpenFiles; diff --git a/Code/Framework/AzFramework/AzFramework/Archive/ArchiveFileIO.cpp b/Code/Framework/AzFramework/AzFramework/Archive/ArchiveFileIO.cpp index 9e6e1034ea..8cc2b9dfb4 100644 --- a/Code/Framework/AzFramework/AzFramework/Archive/ArchiveFileIO.cpp +++ b/Code/Framework/AzFramework/AzFramework/Archive/ArchiveFileIO.cpp @@ -169,7 +169,7 @@ namespace AZ::IO size = m_archive->FGetSize(filePath, true); if (!size) { - return m_archive->IsFileExist(filePath, IArchive::eFileLocation_Any) ? IO::ResultCode::Success : IO::ResultCode::Error; + return m_archive->IsFileExist(filePath, FileSearchLocation::Any) ? IO::ResultCode::Success : IO::ResultCode::Error; } return IO::ResultCode::Success; diff --git a/Code/Framework/AzFramework/AzFramework/Archive/ArchiveFindData.cpp b/Code/Framework/AzFramework/AzFramework/Archive/ArchiveFindData.cpp index 7b483dc5de..9e4290131e 100644 --- a/Code/Framework/AzFramework/AzFramework/Archive/ArchiveFindData.cpp +++ b/Code/Framework/AzFramework/AzFramework/Archive/ArchiveFindData.cpp @@ -78,9 +78,9 @@ namespace AZ::IO { // get the priority into local variable to avoid it changing in the course of // this function execution - ArchiveLocationPriority nVarPakPriority = archive->GetPakPriority(); + FileSearchPriority nVarPakPriority = archive->GetPakPriority(); - if (nVarPakPriority == ArchiveLocationPriority::ePakPriorityFileFirst) + if (nVarPakPriority == FileSearchPriority::FileFirst) { // first, find the file system files ScanFS(archive, szDir); @@ -96,7 +96,7 @@ namespace AZ::IO { ScanZips(archive, szDir); } - if (bAllowUseFS || nVarPakPriority != ArchiveLocationPriority::ePakPriorityPakOnly) + if (bAllowUseFS || nVarPakPriority != FileSearchPriority::PakOnly) { ScanFS(archive, szDir); } diff --git a/Code/Framework/AzFramework/AzFramework/Archive/ArchiveVars.cpp b/Code/Framework/AzFramework/AzFramework/Archive/ArchiveVars.cpp new file mode 100644 index 0000000000..0098d97b8d --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Archive/ArchiveVars.cpp @@ -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 + +namespace AZ::IO +{ + FileSearchPriority GetDefaultFileSearchPriority() + { +#if defined(LY_ARCHIVE_FILE_SEARCH_MODE) + return FileSearchPriority{ LY_ARCHIVE_FILE_SEARCH_MODE }; +#else + return FileSearchPriority{ !ArchiveVars::IsReleaseConfig + ? FileSearchPriority::FileFirst + : FileSearchPriority::PakOnly }; +#endif + } +} diff --git a/Code/Framework/AzFramework/AzFramework/Archive/ArchiveVars.h b/Code/Framework/AzFramework/AzFramework/Archive/ArchiveVars.h index 931b07fa71..2c2ec33544 100644 --- a/Code/Framework/AzFramework/AzFramework/Archive/ArchiveVars.h +++ b/Code/Framework/AzFramework/AzFramework/Archive/ArchiveVars.h @@ -9,17 +9,26 @@ #pragma once -#include - namespace AZ::IO { - enum class ArchiveLocationPriority + enum class FileSearchPriority { - ePakPriorityFileFirst = 0, - ePakPriorityPakFirst = 1, - ePakPriorityPakOnly = 2 + FileFirst, + PakFirst, + PakOnly }; + + //file location enum used in isFileExist to control where the archive system looks for the file. + enum class FileSearchLocation + { + Any, + OnDisk, + InPak + }; + + FileSearchPriority GetDefaultFileSearchPriority(); + // variables that control behavior of the Archive subsystem struct ArchiveVars { @@ -28,29 +37,10 @@ namespace AZ::IO #else inline static constexpr bool IsReleaseConfig{}; #endif - - public: - int nReadSlice{}; - int nSaveTotalResourceList{}; - int nSaveFastloadResourceList{}; - int nSaveMenuCommonResourceList{}; int nSaveLevelResourceList{}; - int nValidateFileHashes{ IsReleaseConfig ? 0 : 1 }; - int nUncachedStreamReads{ 1 }; - int nInMemoryPerPakSizeLimit{ 6 }; // Limits in MB - int nTotalInMemoryPakSizeLimit{ 30 }; - int nLoadCache{}; - int nLoadModePaks{}; - int nStreamCache{ STREAM_CACHE_DEFAULT }; - ArchiveLocationPriority nPriority{ IsReleaseConfig - ? ArchiveLocationPriority::ePakPriorityPakOnly - : ArchiveLocationPriority::ePakPriorityFileFirst }; // Which file location to favor (loose vs. pak files) + FileSearchPriority m_fileSearchPriority{ GetDefaultFileSearchPriority()}; int nMessageInvalidFileAccess{}; int nLogInvalidFileAccess{ IsReleaseConfig ? 0 : 1 }; - int nDisableNonLevelRelatedPaks{ 1 }; - int nWarnOnPakAccessFails{ 1 }; // Whether to treat failed pak access as a warning or log message - int nSetLogLevel{ 3 }; - int nLogAllFileAccess{}; }; } diff --git a/Code/Framework/AzFramework/AzFramework/Archive/IArchive.h b/Code/Framework/AzFramework/AzFramework/Archive/IArchive.h index bd9615110a..d7eaee6e24 100644 --- a/Code/Framework/AzFramework/AzFramework/Archive/IArchive.h +++ b/Code/Framework/AzFramework/AzFramework/Archive/IArchive.h @@ -18,13 +18,13 @@ #include #include - +#include enum EStreamSourceMediaType : int32_t; namespace AZ::IO { - enum class ArchiveLocationPriority; + enum class FileSearchPriority; struct IResourceList; struct INestedArchive; struct IArchive; @@ -114,14 +114,6 @@ namespace AZ::IO RFOM_NextLevel // used for level2level loading }; - //file location enum used in isFileExist to control where the archive system looks for the file. - enum EFileSearchLocation - { - eFileLocation_Any = 0, - eFileLocation_OnDisk, - eFileLocation_InPak, - }; - enum EInMemoryArchiveLocation { eInMemoryPakLocale_Unload = 0, @@ -130,12 +122,6 @@ namespace AZ::IO eInMemoryPakLocale_PAK, }; - enum EFileSearchType - { - eFileSearchType_AllowInZipsOnly = 0, - eFileSearchType_AllowOnDiskAndInZips, - eFileSearchType_AllowOnDiskOnly - }; using SignedFileSize = int64_t; @@ -213,7 +199,7 @@ namespace AZ::IO virtual AZStd::intrusive_ptr PoolAllocMemoryBlock(size_t nSize, const char* sUsage, size_t nAlign = 1) = 0; // Arguments: - virtual ArchiveFileIterator FindFirst(AZStd::string_view pDir, EFileSearchType searchType = eFileSearchType_AllowInZipsOnly) = 0; + virtual ArchiveFileIterator FindFirst(AZStd::string_view pDir, FileSearchLocation searchType = FileSearchLocation::InPak) = 0; virtual ArchiveFileIterator FindNext(AZ::IO::ArchiveFileIterator handle) = 0; virtual bool FindClose(AZ::IO::ArchiveFileIterator handle) = 0; //returns file modification time @@ -221,7 +207,7 @@ namespace AZ::IO // Description: // Checks if specified file exist in filesystem. - virtual bool IsFileExist(AZStd::string_view sFilename, EFileSearchLocation = eFileLocation_Any) = 0; + virtual bool IsFileExist(AZStd::string_view sFilename, FileSearchLocation = FileSearchLocation::Any) = 0; // Checks if path is a folder virtual bool IsFolder(AZStd::string_view sPath) = 0; @@ -283,7 +269,7 @@ namespace AZ::IO virtual bool DisableRuntimeFileAccess(bool status, AZStd::thread_id threadId) = 0; // gets the current pak priority - virtual ArchiveLocationPriority GetPakPriority() const = 0; + virtual FileSearchPriority GetPakPriority() const = 0; // Summary: // Return offset in archive file (ideally has to return offset on DVD) for streaming requests sorting @@ -295,7 +281,7 @@ namespace AZ::IO // Event sent when a archive file is opened that contains a level.pak // @param const AZStd::vector& - Array of directories containing level.pak files - using LevelPackOpenEvent = AZ::Event&>; + using LevelPackOpenEvent = AZ::Event&>; virtual auto GetLevelPackOpenEvent()->LevelPackOpenEvent* = 0; // Event sent when a archive contains a level.pak is closed // @param const AZStd::string_view - Name of the pak file that was closed diff --git a/Code/Framework/AzFramework/AzFramework/Asset/AssetBundleManifest.cpp b/Code/Framework/AzFramework/AzFramework/Asset/AssetBundleManifest.cpp index 43a5e8fdb3..b4da5745be 100644 --- a/Code/Framework/AzFramework/AzFramework/Asset/AssetBundleManifest.cpp +++ b/Code/Framework/AzFramework/AzFramework/Asset/AssetBundleManifest.cpp @@ -7,23 +7,153 @@ */ #include +#include +#include #include namespace AzFramework { - - const int AssetBundleManifest::CurrentBundleVersion = 2; + // Redirects writing of the AssetBundleManifest to an older version if the bundle version + // is not set to the current version + static void OldBundleManifestWriter(AZ::SerializeContext::EnumerateInstanceCallContext& callContext, const void* bundleManifestPointer, + const AZ::SerializeContext::ClassData&, const AZ::SerializeContext::ClassElement* assetBundleManifestClassElement); + + static bool BundleManifestVersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& rootElement); + + const int AssetBundleManifest::CurrentBundleVersion = 3; const char AssetBundleManifest::s_manifestFileName[] = "manifest.xml"; + + AssetBundleManifest::AssetBundleManifest() = default; + AssetBundleManifest::~AssetBundleManifest() = default; + void AssetBundleManifest::ReflectSerialize(AZ::SerializeContext* serializeContext) { if (serializeContext) { serializeContext->Class() - ->Version(2) + ->Version(CurrentBundleVersion, &BundleManifestVersionConverter) + ->Attribute(AZ::SerializeContextAttributes::ObjectStreamWriteElementOverride, &OldBundleManifestWriter) ->Field("BundleVersion", &AssetBundleManifest::m_bundleVersion) ->Field("CatalogName", &AssetBundleManifest::m_catalogName) - ->Field("DependentBundleNames", &AssetBundleManifest::m_depedendentBundleNames) + ->Field("DependentBundleNames", &AssetBundleManifest::m_dependentBundleNames) ->Field("LevelNames", &AssetBundleManifest::m_levelDirs); + + // Make sure the AZStd::vector type is reflected so that it can be read + // using DataElement::GetChildData + serializeContext->RegisterGenericType>(); } } + + bool BundleManifestVersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& rootElement) + { + if (rootElement.GetVersion() < 3) + { + static constexpr AZ::u32 levelNamesCrc = AZ_CRC_CE("LevelNames"); + AZStd::vector newLevelDirs; + if (AZStd::vector oldLevelNames; rootElement.GetChildData(levelNamesCrc, oldLevelNames)) + { + newLevelDirs.insert(newLevelDirs.end(), + AZStd::make_move_iterator(oldLevelNames.begin()), AZStd::make_move_iterator(oldLevelNames.end())); + } + else + { + AZ_Error("AssetBundleManifest", false, R"(Unable to read "levelNames" from AssetBundleManifest version %u )", + rootElement.GetVersion()); + } + + rootElement.RemoveElementByName(levelNamesCrc); + rootElement.AddElementWithData(context, "LevelNames", newLevelDirs); + } + return true; + } + + void OldBundleManifestWriter(AZ::SerializeContext::EnumerateInstanceCallContext& callContext, const void* bundleManifestPointer, + const AZ::SerializeContext::ClassData&, const AZ::SerializeContext::ClassElement* assetBundleManifestClassElement) + { + // Copy the AssetBundleManifest current version instance to the AssetBundleManifest V2 instance + auto assetBundleManifestCurrent = reinterpret_cast(bundleManifestPointer); + if (assetBundleManifestCurrent->GetBundleVersion() <= 2) + { + auto serializeContext = const_cast(callContext.m_context); + + struct AssetBundleManifestV2 + { + // Use the same ClassName and typeid as the AssetBundleManifest + AZ_TYPE_INFO(AssetBundleManifest, azrtti_typeid()); + AZStd::string m_catalogName; + AZStd::vector m_dependentBundleNames; + AZStd::vector m_levelDirs; + int m_bundleVersion{}; + }; + auto ReflectAssetBundleManifestV2 = [](AZ::SerializeContext* serializeContext) + { + serializeContext->Class() + ->Version(2) + ->Field("BundleVersion", &AssetBundleManifestV2::m_bundleVersion) + ->Field("CatalogName", &AssetBundleManifestV2::m_catalogName) + ->Field("DependentBundleNames", &AssetBundleManifestV2::m_dependentBundleNames) + ->Field("LevelNames", &AssetBundleManifestV2::m_levelDirs); + }; + + // Unreflect the AssetBundleManifest class at the version since it shares the same typeid + // as the older version and Reflect the V2 AssetBundlerManifest + serializeContext->EnableRemoveReflection(); + AssetBundleManifest::ReflectSerialize(serializeContext); + serializeContext->DisableRemoveReflection(); + ReflectAssetBundleManifestV2(serializeContext); + + // Use the Current AssetBundleManifest instance to make a Version 2 AssetBundleManifest + AssetBundleManifestV2 assetBundleManifestV2; + assetBundleManifestV2.m_catalogName = assetBundleManifestCurrent->GetCatalogName(); + assetBundleManifestV2.m_dependentBundleNames = assetBundleManifestCurrent->GetDependentBundleNames(); + assetBundleManifestV2.m_bundleVersion = assetBundleManifestCurrent->GetBundleVersion(); + for (const AZ::IO::Path& levelDir : assetBundleManifestCurrent->GetLevelDirectories()) + { + assetBundleManifestV2.m_levelDirs.emplace_back(levelDir.Native()); + } + + const AZ::TypeId& assetBundlerManifestTypeId = azrtti_typeid(); + const auto assetBundleManifestV2ClassData = serializeContext->FindClassData(assetBundlerManifestTypeId); + + // Create an AssetBundleManifest Version 2 Class Eleemnt + // It will copy over the name and nameCrc values of the current AssetBundleManifestelemnt + auto CreateAssetBundleManifestV2ClassElement = [&assetBundlerManifestTypeId]( + const AZ::SerializeContext::ClassElement* currentVersionElement) -> AZ::SerializeContext::ClassElement + { + AZ::SerializeContext::ClassElement v2ClassElement; + // Copy over the name of he current + if (currentVersionElement) + { + v2ClassElement.m_name = currentVersionElement->m_name; + v2ClassElement.m_nameCrc = currentVersionElement->m_nameCrc; + } + v2ClassElement.m_dataSize = sizeof(AssetBundleManifest); + v2ClassElement.m_azRtti = AZ::GetRttiHelper(); + v2ClassElement.m_genericClassInfo = nullptr; + v2ClassElement.m_typeId = assetBundlerManifestTypeId; + v2ClassElement.m_editData = nullptr; + v2ClassElement.m_attributeOwnership = AZ::SerializeContext::ClassElement::AttributeOwnership::Self; + return v2ClassElement; + }; + const auto assetBundleManifestV2ClassElement = CreateAssetBundleManifestV2ClassElement(assetBundleManifestClassElement); + + serializeContext->EnumerateInstanceConst(&callContext, &assetBundleManifestV2, assetBundlerManifestTypeId, + assetBundleManifestV2ClassData, assetBundleManifestClassElement ? &assetBundleManifestV2ClassElement : nullptr); + + // Unreflect the V2 AssetBundleManifest and Re-reflect the AssetBundleManifest class at the current version + serializeContext->EnableRemoveReflection(); + ReflectAssetBundleManifestV2(serializeContext); + serializeContext->DisableRemoveReflection(); + AssetBundleManifest::ReflectSerialize(serializeContext); + } + } + + const AZStd::vector& AssetBundleManifest::GetLevelDirectories() const + { + return m_levelDirs; + } + void AssetBundleManifest::SetLevelsDirectory(const AZStd::vector& levelDirs) + { + m_levelDirs = levelDirs; + } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Asset/AssetBundleManifest.h b/Code/Framework/AzFramework/AzFramework/Asset/AssetBundleManifest.h index 9760482231..eb0390a8c0 100644 --- a/Code/Framework/AzFramework/AzFramework/Asset/AssetBundleManifest.h +++ b/Code/Framework/AzFramework/AzFramework/Asset/AssetBundleManifest.h @@ -8,6 +8,7 @@ #pragma once +#include #include #include #include @@ -27,7 +28,8 @@ namespace AzFramework AZ_TYPE_INFO(AssetBundleManifest, "{8628A669-7B19-4C48-A7CB-F670CC9586FD}"); AZ_CLASS_ALLOCATOR(AssetBundleManifest, AZ::SystemAllocator, 0); - AssetBundleManifest() = default; + AssetBundleManifest(); + ~AssetBundleManifest(); static void ReflectSerialize(AZ::SerializeContext* serializeContext); @@ -35,21 +37,21 @@ namespace AzFramework // of files within the AssetBundle in order to update the Asset Registry at runtime when // loading the bundle const AZStd::string& GetCatalogName() const { return m_catalogName; } - AZStd::vector GetDependentBundleNames() const { return m_depedendentBundleNames; } - AZStd::vector GetLevelDirectories() const { return m_levelDirs; } + AZStd::vector GetDependentBundleNames() const { return m_dependentBundleNames; } + const AZStd::vector& GetLevelDirectories() const; int GetBundleVersion() const { return m_bundleVersion; } void SetCatalogName(const AZStd::string& catalogName) { m_catalogName = catalogName; } void SetBundleVersion(int bundleVersion) { m_bundleVersion = bundleVersion; } - void SetDependentBundleNames(const AZStd::vector& dependentBundleNames) { m_depedendentBundleNames = dependentBundleNames; } - void SetLevelsDirectory(const AZStd::vector& levelDirs) { m_levelDirs = levelDirs; } + void SetDependentBundleNames(const AZStd::vector& dependentBundleNames) { m_dependentBundleNames = dependentBundleNames; } + void SetLevelsDirectory(const AZStd::vector& levelDirs); static const char s_manifestFileName[]; static const int CurrentBundleVersion; - private: + private: AZStd::string m_catalogName; - AZStd::vector m_depedendentBundleNames; - AZStd::vector m_levelDirs; + AZStd::vector m_dependentBundleNames; + AZStd::vector m_levelDirs; int m_bundleVersion = CurrentBundleVersion; }; diff --git a/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.cpp b/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.cpp index 3d76bcefc4..0c6b195434 100644 --- a/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.cpp +++ b/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.cpp @@ -1186,7 +1186,7 @@ namespace AzFramework } - bool AssetCatalog::CreateBundleManifest(const AZStd::string& deltaCatalogPath, const AZStd::vector& dependentBundleNames, const AZStd::string& fileDirectory, int bundleVersion, const AZStd::vector& levelDirs) + bool AssetCatalog::CreateBundleManifest(const AZStd::string& deltaCatalogPath, const AZStd::vector& dependentBundleNames, const AZStd::string& fileDirectory, int bundleVersion, const AZStd::vector& levelDirs) { if (bundleVersion > AzFramework::AssetBundleManifest::CurrentBundleVersion || bundleVersion < 0) { diff --git a/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.h b/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.h index 64f3c2e3d2..20f1355e7f 100644 --- a/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.h +++ b/Code/Framework/AzFramework/AzFramework/Asset/AssetCatalog.h @@ -67,7 +67,7 @@ namespace AzFramework bool InsertDeltaCatalogBefore(AZStd::shared_ptr deltaCatalog, AZStd::shared_ptr afterDeltaCatalog) override; bool RemoveDeltaCatalog(AZStd::shared_ptr deltaCatalog) override; static bool SaveAssetBundleManifest(const char* assetBundleManifestFile, AzFramework::AssetBundleManifest* bundleManifest); - bool CreateBundleManifest(const AZStd::string& deltaCatalogPath, const AZStd::vector& dependentBundleNames, const AZStd::string& fileDirectory, int bundleVersion, const AZStd::vector& levelDirs) override; + bool CreateBundleManifest(const AZStd::string& deltaCatalogPath, const AZStd::vector& dependentBundleNames, const AZStd::string& fileDirectory, int bundleVersion, const AZStd::vector& levelDirs) override; bool CreateDeltaCatalog(const AZStd::vector& files, const AZStd::string& filePath) override; void AddExtension(const char* extension) override; diff --git a/Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponent.cpp b/Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponent.cpp index 3db19c53ee..bb361e56de 100644 --- a/Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Asset/AssetSystemComponent.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -17,7 +18,6 @@ #include #include #include -#include #include #include @@ -28,6 +28,8 @@ #include #include +AZ_DECLARE_BUDGET(AzFramework); + namespace AzFramework { namespace AssetBenchmark @@ -302,7 +304,7 @@ namespace AzFramework // SystemTickBus overrides void AssetSystemComponent::OnSystemTick() { - AZ_TRACE_METHOD(); + AZ_PROFILE_FUNCTION(AzFramework); LegacyAssetEventBus::ExecuteQueuedEvents(); } diff --git a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp index 68c4b00243..809f664a10 100644 --- a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.cpp @@ -323,6 +323,13 @@ namespace AzFramework return localZ; } + void TransformComponent::SetWorldRotation(const AZ::Vector3& eulerAnglesRadian) + { + AZ::Transform newWorldTransform = m_worldTM; + newWorldTransform.SetRotation(AZ::Quaternion::CreateFromEulerAnglesRadians(eulerAnglesRadian)); + SetWorldTM(newWorldTransform); + } + void TransformComponent::SetWorldRotationQuaternion(const AZ::Quaternion& quaternion) { AZ::Transform newWorldTransform = m_worldTM; @@ -527,7 +534,7 @@ namespace AzFramework void TransformComponent::SetParentImpl(AZ::EntityId parentId, bool isKeepWorldTM) { - if (parentId == GetEntityId()) + if (GetEntity() && parentId == GetEntityId()) { AZ_Warning("TransformComponent", false, "An entity can not be set as its own parent."); return; diff --git a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h index ac282b4d49..2375f28ed1 100644 --- a/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h +++ b/Code/Framework/AzFramework/AzFramework/Components/TransformComponent.h @@ -108,6 +108,7 @@ namespace AzFramework float GetLocalZ() override; // Rotation modifiers + void SetWorldRotation(const AZ::Vector3& eulerAnglesRadian) override; void SetWorldRotationQuaternion(const AZ::Quaternion& quaternion) override; AZ::Vector3 GetWorldRotation() override; diff --git a/Code/Framework/AzFramework/AzFramework/Entity/EntityContext.cpp b/Code/Framework/AzFramework/AzFramework/Entity/EntityContext.cpp index c1283c9379..2faed93ab7 100644 --- a/Code/Framework/AzFramework/AzFramework/Entity/EntityContext.cpp +++ b/Code/Framework/AzFramework/AzFramework/Entity/EntityContext.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include #include @@ -243,8 +242,6 @@ namespace AzFramework //========================================================================= void EntityContext::ActivateEntity(AZ::EntityId entityId) { - AZ_ASSET_ATTACH_TO_SCOPE(this); - // Verify that this context has the right to perform operations on the entity bool validEntity = IsOwnedByThisContext(entityId); AZ_Warning("GameEntityContext", validEntity, "Entity with id %llu does not belong to the game context.", entityId); diff --git a/Code/Framework/AzFramework/AzFramework/Entity/SliceEntityOwnershipService.cpp b/Code/Framework/AzFramework/AzFramework/Entity/SliceEntityOwnershipService.cpp index 9a077cd611..ab14dfeb7f 100644 --- a/Code/Framework/AzFramework/AzFramework/Entity/SliceEntityOwnershipService.cpp +++ b/Code/Framework/AzFramework/AzFramework/Entity/SliceEntityOwnershipService.cpp @@ -386,7 +386,6 @@ namespace AzFramework void SliceEntityOwnershipService::OnAssetReady(AZ::Data::Asset readyAsset) { AZ_PROFILE_FUNCTION(AzFramework); - AZ_ASSET_ATTACH_TO_SCOPE(readyAsset.Get()); AZ_Assert(readyAsset.GetAs(), "Asset is not a slice!"); @@ -400,7 +399,6 @@ namespace AzFramework // we intentionally capture readyAsset by value here, so that its refcount doesn't hit 0 by the time this call happens. AZStd::function instantiateCallback = [this, readyAsset]() { - AZ_ASSET_ATTACH_TO_SCOPE(readyAsset.Get()); const AZ::Data::AssetId readyAssetId = readyAsset.GetId(); for (auto iter = m_queuedSliceInstantiations.begin(); iter != m_queuedSliceInstantiations.end(); ) { diff --git a/Code/Framework/AzFramework/AzFramework/Input/Buses/Requests/InputDeviceRequestBus.h b/Code/Framework/AzFramework/AzFramework/Input/Buses/Requests/InputDeviceRequestBus.h index 147ff7f0b8..74e2459ab6 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Buses/Requests/InputDeviceRequestBus.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Buses/Requests/InputDeviceRequestBus.h @@ -229,17 +229,13 @@ namespace AzFramework //! Alias for the EBus implementation of this interface using Bus = AZ::EBus>; - //////////////////////////////////////////////////////////////////////////////////////////// - //! Alias for the function type used to create the custom implementations - using CreateFunctionType = typename InputDeviceType::Implementation*(*)(InputDeviceType&); - //////////////////////////////////////////////////////////////////////////////////////////// //! Set a custom implementation for this input device type, either for a specific instance //! by addressing the call to an InputDeviceId, or for all existing instances by broadcast. //! Passing InputDeviceType::Implementation::Create as the argument will create the default //! device implementation, while passing nullptr will delete any existing implementation. - //! \param[in] createFunction Pointer to the function that will create the implementation. - virtual void SetCustomImplementation(CreateFunctionType createFunction) = 0; + //! \param[in] implementationFactory Pointer to the function that creates the implementation. + virtual void SetCustomImplementation(typename InputDeviceType::ImplementationFactory implementationFactory) = 0; }; //////////////////////////////////////////////////////////////////////////////////////////////// @@ -267,18 +263,14 @@ namespace AzFramework AZ_DISABLE_COPY_MOVE(InputDeviceImplementationRequestHandler); protected: - //////////////////////////////////////////////////////////////////////////////////////////// - //! Alias for the function type used to create the custom implementations - using CreateFunctionType = typename InputDeviceType::Implementation*(*)(InputDeviceType&); - //////////////////////////////////////////////////////////////////////////////////////////// //! \ref InputDeviceImplementationRequest::SetCustomImplementation - AZ_INLINE void SetCustomImplementation(CreateFunctionType createFunction) override + AZ_INLINE void SetCustomImplementation(typename InputDeviceType::ImplementationFactory implementationFactory) override { AZStd::unique_ptr newImplementation; - if (createFunction) + if (implementationFactory) { - newImplementation.reset(createFunction(m_inputDevice)); + newImplementation.reset(implementationFactory(m_inputDevice)); } m_inputDevice.SetImplementation(AZStd::move(newImplementation)); } diff --git a/Code/Framework/AzFramework/AzFramework/Input/Channels/InputChannelId.cpp b/Code/Framework/AzFramework/AzFramework/Input/Channels/InputChannelId.cpp index 496d89d767..ef39e50cb8 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Channels/InputChannelId.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Channels/InputChannelId.cpp @@ -27,28 +27,4 @@ namespace AzFramework ; } } - - //////////////////////////////////////////////////////////////////////////////////////////////// - const char* InputChannelId::GetName() const - { - return m_name.c_str(); - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - const AZ::Crc32& InputChannelId::GetNameCrc32() const - { - return m_crc32; - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - bool InputChannelId::operator==(const InputChannelId& other) const - { - return (m_crc32 == other.m_crc32); - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - bool InputChannelId::operator!=(const InputChannelId& other) const - { - return !(*this == other); - } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Input/Channels/InputChannelId.h b/Code/Framework/AzFramework/AzFramework/Input/Channels/InputChannelId.h index a4d8ac83eb..62d79285f5 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Channels/InputChannelId.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Channels/InputChannelId.h @@ -39,53 +39,58 @@ namespace AzFramework //////////////////////////////////////////////////////////////////////////////////////////// //! Constructor - //! \param[in] name Name of the input channel (will be ignored if exceeds MAX_NAME_LENGTH) + //! \param[in] name Name of the input channel (will be truncated if exceeds MAX_NAME_LENGTH) explicit constexpr InputChannelId(AZStd::string_view name = "") - : m_name(name) - , m_crc32(name) + : m_name(name.substr(0, MAX_NAME_LENGTH)) + , m_crc32(name.substr(0, MAX_NAME_LENGTH)) { } - constexpr InputChannelId(const InputChannelId& other) = default; - constexpr InputChannelId(InputChannelId&& other) = default; - constexpr InputChannelId& operator=(const InputChannelId& other) - { - m_name = other.m_name; - m_crc32 = other.m_crc32; - return *this; - } - constexpr InputChannelId& operator=(InputChannelId&& other) - { - m_name = AZStd::move(other.m_name); - m_crc32 = AZStd::move(other.m_crc32); - other.m_crc32 = 0; - return *this; - } + //////////////////////////////////////////////////////////////////////////////////////////// + // Default copying and moving + AZ_DEFAULT_COPY_MOVE(InputChannelId); + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Default destructor ~InputChannelId() = default; //////////////////////////////////////////////////////////////////////////////////////////// //! Access to the input channel's name //! \return Name of the input channel - const char* GetName() const; + constexpr const char* GetName() const + { + return m_name.c_str(); + } //////////////////////////////////////////////////////////////////////////////////////////// //! Access to the crc32 of the input channel's name //! \return crc32 of the input channel name - const AZ::Crc32& GetNameCrc32() const; + constexpr const AZ::Crc32& GetNameCrc32() const + { + return m_crc32; + } //////////////////////////////////////////////////////////////////////////////////////////// - ///@{ //! Equality comparison operator //! \param[in] other Another instance of the class to compare for equality - bool operator==(const InputChannelId& other) const; - bool operator!=(const InputChannelId& other) const; - ///@} + constexpr bool operator==(const InputChannelId& other) const + { + return m_crc32 == other.m_crc32; + } + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Inequality comparison operator + //! \param[in] other Another instance of the class to compare for inequality + constexpr bool operator!=(const InputChannelId& other) const + { + return !(*this == other); + } private: //////////////////////////////////////////////////////////////////////////////////////////// // Variables AZStd::fixed_string m_name; //!< Name of the input channel - AZ::Crc32 m_crc32; //!< Crc32 of the input channel + AZ::Crc32 m_crc32; //!< Crc32 of the input channel name }; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.cpp index 51aa008519..c7629c7afb 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.cpp @@ -14,14 +14,6 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// namespace AzFramework { - //////////////////////////////////////////////////////////////////////////////////////////////// - const char* InputDeviceGamepad::Name("gamepad"); - const InputDeviceId InputDeviceGamepad::IdForIndex0(Name, 0); - const InputDeviceId InputDeviceGamepad::IdForIndex1(Name, 1); - const InputDeviceId InputDeviceGamepad::IdForIndex2(Name, 2); - const InputDeviceId InputDeviceGamepad::IdForIndex3(Name, 3); - const InputDeviceId InputDeviceGamepad::IdForIndexN(AZ::u32 n) { return InputDeviceId(Name, n); } - //////////////////////////////////////////////////////////////////////////////////////////////// bool InputDeviceGamepad::IsGamepadDevice(const InputDeviceId& inputDeviceId) { @@ -94,7 +86,14 @@ namespace AzFramework //////////////////////////////////////////////////////////////////////////////////////////////// InputDeviceGamepad::InputDeviceGamepad(AZ::u32 index) - : InputDevice(InputDeviceId(Name, index)) + : InputDeviceGamepad(InputDeviceId(Name, index)) // Delegated constructor + { + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + InputDeviceGamepad::InputDeviceGamepad(const InputDeviceId& inputDeviceId, + ImplementationFactory implementationFactory) + : InputDevice(inputDeviceId) , m_allChannelsById() , m_buttonChannelsById() , m_triggerChannelsById() @@ -144,8 +143,8 @@ namespace AzFramework m_thumbStickDirectionChannelsById[channelId] = channel; } - // Create the platform specific implementation - m_pimpl.reset(Implementation::Create(*this)); + // Create the platform specific or custom implementation + m_pimpl.reset(implementationFactory ? implementationFactory(*this) : nullptr); // Connect to the haptic feedback request bus InputHapticFeedbackRequestBus::Handler::BusConnect(GetInputDeviceId()); diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h index 286b4f0df3..b143709621 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad.h @@ -32,16 +32,16 @@ namespace AzFramework public: //////////////////////////////////////////////////////////////////////////////////////////// //! The name used to identify any game-pad input device - static const char* Name; + static constexpr inline const char* Name{"gamepad"}; //////////////////////////////////////////////////////////////////////////////////////////// //! The id used to identify a game-pad input device with a specific index ///@{ - static const InputDeviceId IdForIndex0; - static const InputDeviceId IdForIndex1; - static const InputDeviceId IdForIndex2; - static const InputDeviceId IdForIndex3; - static const InputDeviceId IdForIndexN(AZ::u32 n); + static constexpr inline InputDeviceId IdForIndex0{Name, 0}; + static constexpr inline InputDeviceId IdForIndex1{Name, 1}; + static constexpr inline InputDeviceId IdForIndex2{Name, 2}; + static constexpr inline InputDeviceId IdForIndex3{Name, 3}; + static constexpr inline InputDeviceId IdForIndexN(AZ::u32 n) { return InputDeviceId(Name, n); } ///@} //////////////////////////////////////////////////////////////////////////////////////////// @@ -182,6 +182,14 @@ namespace AzFramework // Reflection static void Reflect(AZ::ReflectContext* context); + //////////////////////////////////////////////////////////////////////////////////////////// + // Foward declare the internal Implementation class so it can be passed into the constructor + class Implementation; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Alias for the function type used to create a custom implementation for this input device + using ImplementationFactory = Implementation*(InputDeviceGamepad&); + //////////////////////////////////////////////////////////////////////////////////////////// //! Constructor explicit InputDeviceGamepad(); @@ -191,6 +199,13 @@ namespace AzFramework //! \param[in] index Index of the game-pad device explicit InputDeviceGamepad(AZ::u32 index); + //////////////////////////////////////////////////////////////////////////////////////////// + //! Constructor + //! \param[in] inputDeviceId Id of the input device + //! \param[in] implementationFactory Optional override of the default Implementation::Create + explicit InputDeviceGamepad(const InputDeviceId& inputDeviceId, + ImplementationFactory implementationFactory = &Implementation::Create); + //////////////////////////////////////////////////////////////////////////////////////////// // Disable copying AZ_DISABLE_COPY_MOVE(InputDeviceGamepad); diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/InputDeviceId.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/InputDeviceId.cpp index fa0955bd16..7f9a0039ac 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/InputDeviceId.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/InputDeviceId.cpp @@ -29,71 +29,4 @@ namespace AzFramework ; } } - - //////////////////////////////////////////////////////////////////////////////////////////////// - InputDeviceId::InputDeviceId(const char* name, AZ::u32 index) - : m_crc32(name) - , m_index(index) - { - memset(m_name, 0, AZ_ARRAY_SIZE(m_name)); - azstrncpy(m_name, NAME_BUFFER_SIZE, name, MAX_NAME_LENGTH); - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - InputDeviceId::InputDeviceId(const InputDeviceId& other) - : m_crc32(other.m_crc32) - , m_index(other.m_index) - { - memset(m_name, 0, AZ_ARRAY_SIZE(m_name)); - azstrcpy(m_name, NAME_BUFFER_SIZE, other.m_name); - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - InputDeviceId& InputDeviceId::operator=(const InputDeviceId& other) - { - azstrcpy(m_name, NAME_BUFFER_SIZE, other.m_name); - m_crc32 = other.m_crc32; - m_index = other.m_index; - return *this; - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - const char* InputDeviceId::GetName() const - { - return m_name; - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - const AZ::Crc32& InputDeviceId::GetNameCrc32() const - { - return m_crc32; - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - AZ::u32 InputDeviceId::GetIndex() const - { - return m_index; - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - bool InputDeviceId::operator==(const InputDeviceId& other) const - { - return (m_crc32 == other.m_crc32) && (m_index == other.m_index); - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - bool InputDeviceId::operator!=(const InputDeviceId& other) const - { - return !(*this == other); - } - - //////////////////////////////////////////////////////////////////////////////////////////////// - bool InputDeviceId::operator<(const InputDeviceId& other) const - { - if (m_index == other.m_index) - { - return m_crc32 < other.m_crc32; - } - return m_index < other.m_index; - } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/InputDeviceId.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/InputDeviceId.h index 6d2aa8b9fd..ffc1745c3b 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/InputDeviceId.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/InputDeviceId.h @@ -11,6 +11,7 @@ #include #include #include +#include //////////////////////////////////////////////////////////////////////////////////////////////////// namespace AzFramework @@ -22,8 +23,7 @@ namespace AzFramework public: //////////////////////////////////////////////////////////////////////////////////////////// // Constants - static const int NAME_BUFFER_SIZE = 64; - static const int MAX_NAME_LENGTH = NAME_BUFFER_SIZE - 1; + static constexpr int MAX_NAME_LENGTH = 64; //////////////////////////////////////////////////////////////////////////////////////////// // Allocator @@ -41,17 +41,16 @@ namespace AzFramework //! Constructor //! \param[in] name Name of the input device (will be truncated if exceeds MAX_NAME_LENGTH) //! \param[in] index Index of the input device (optional) - explicit InputDeviceId(const char* name, AZ::u32 index = 0); + explicit constexpr InputDeviceId(AZStd::string_view name, AZ::u32 index = 0) + : m_name(name.substr(0, MAX_NAME_LENGTH)) + , m_crc32(name.substr(0, MAX_NAME_LENGTH)) + , m_index(index) + { + } //////////////////////////////////////////////////////////////////////////////////////////// - //! Copy constructor - //! \param[in] other Another instance of the class to copy from - InputDeviceId(const InputDeviceId& other); - - //////////////////////////////////////////////////////////////////////////////////////////// - //! Copy assignment operator - //! \param[in] other Another instance of the class to copy from - InputDeviceId& operator=(const InputDeviceId& other); + // Default copying and moving + AZ_DEFAULT_COPY_MOVE(InputDeviceId); //////////////////////////////////////////////////////////////////////////////////////////// //! Default destructor @@ -60,12 +59,18 @@ namespace AzFramework //////////////////////////////////////////////////////////////////////////////////////////// //! Access to the input device's name //! \return Name of the input device - const char* GetName() const; + constexpr const char* GetName() const + { + return m_name.c_str(); + } //////////////////////////////////////////////////////////////////////////////////////////// //! Access to the crc32 of the input device's name //! \return crc32 of the input device name - const AZ::Crc32& GetNameCrc32() const; + constexpr const AZ::Crc32& GetNameCrc32() const + { + return m_crc32; + } //////////////////////////////////////////////////////////////////////////////////////////// //! Access to the input device's index. Used for differentiating between multiple instances @@ -75,27 +80,45 @@ namespace AzFramework //! at startup using indicies 0->3. As gamepads connect/disconnect at runtime we assign the //! appropriate (system dependent) local user id (see InputDevice::GetAssignedLocalUserId). //! \return Index of the input device - AZ::u32 GetIndex() const; + constexpr AZ::u32 GetIndex() const + { + return m_index; + } //////////////////////////////////////////////////////////////////////////////////////////// - ///@{ //! Equality comparison operator //! \param[in] other Another instance of the class to compare for equality - bool operator==(const InputDeviceId& other) const; - bool operator!=(const InputDeviceId& other) const; - ///@} + constexpr bool operator==(const InputDeviceId& other) const + { + return (m_crc32 == other.m_crc32) && (m_index == other.m_index); + } + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Inequality comparison operator + //! \param[in] other Another instance of the class to compare for inequality + constexpr bool operator!=(const InputDeviceId& other) const + { + return !(*this == other); + } //////////////////////////////////////////////////////////////////////////////////////////// //! Less than comparison operator //! \param[in] other Another instance of the class to compare - bool operator<(const InputDeviceId& other) const; + constexpr bool operator<(const InputDeviceId& other) const + { + if (m_index == other.m_index) + { + return m_crc32 < other.m_crc32; + } + return m_index < other.m_index; + } private: //////////////////////////////////////////////////////////////////////////////////////////// // Variables - char m_name[NAME_BUFFER_SIZE]; //!< Name of the input device - AZ::Crc32 m_crc32; //!< Crc32 of the input device - AZ::u32 m_index; //!< Index of the input device + AZStd::fixed_string m_name; //!< Name of the input device + AZ::Crc32 m_crc32; //!< Crc32 of the input device name + AZ::u32 m_index; //!< Index of the input device }; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.cpp index d1117ea895..08674ea92f 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.cpp @@ -15,9 +15,6 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// namespace AzFramework { - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputDeviceId InputDeviceKeyboard::Id("keyboard"); - //////////////////////////////////////////////////////////////////////////////////////////////// bool InputDeviceKeyboard::IsKeyboardDevice(const InputDeviceId& inputDeviceId) { @@ -182,8 +179,9 @@ namespace AzFramework } //////////////////////////////////////////////////////////////////////////////////////////////// - InputDeviceKeyboard::InputDeviceKeyboard(AzFramework::InputDeviceId id) - : InputDevice(id) + InputDeviceKeyboard::InputDeviceKeyboard(const InputDeviceId& inputDeviceId, + ImplementationFactory implementationFactory) + : InputDevice(inputDeviceId) , m_modifierKeyStates(AZStd::make_shared()) , m_allChannelsById() , m_keyChannelsById() @@ -203,8 +201,8 @@ namespace AzFramework m_keyChannelsById[channelId] = channel; } - // Create the platform specific implementation - m_pimpl.reset(Implementation::Create(*this)); + // Create the platform specific or custom implementation + m_pimpl.reset(implementationFactory ? implementationFactory(*this) : nullptr); // Connect to the text entry request bus InputTextEntryRequestBus::Handler::BusConnect(GetInputDeviceId()); diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h index e3c21ec326..f8300eefdf 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h @@ -33,7 +33,7 @@ namespace AzFramework public: //////////////////////////////////////////////////////////////////////////////////////////// //! The id used to identify the primary physical keyboard input device - static const InputDeviceId Id; + static constexpr inline InputDeviceId Id{"keyboard"}; //////////////////////////////////////////////////////////////////////////////////////////// //! Check whether an input device id identifies a physical keyboard (regardless of index) @@ -370,9 +370,20 @@ namespace AzFramework // Reflection static void Reflect(AZ::ReflectContext* context); + //////////////////////////////////////////////////////////////////////////////////////////// + // Foward declare the internal Implementation class so it can be passed into the constructor + class Implementation; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Alias for the function type used to create a custom implementation for this input device + using ImplementationFactory = Implementation*(InputDeviceKeyboard&); + //////////////////////////////////////////////////////////////////////////////////////////// //! Constructor - InputDeviceKeyboard(AzFramework::InputDeviceId id = Id); + //! \param[in] inputDeviceId Optional override of the default input device id + //! \param[in] implementationFactory Optional override of the default Implementation::Create + explicit InputDeviceKeyboard(const InputDeviceId& inputDeviceId = Id, + ImplementationFactory implementationFactory = &Implementation::Create); //////////////////////////////////////////////////////////////////////////////////////////// // Disable copying diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.cpp index c144f63d2c..fec51349c9 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.cpp @@ -14,9 +14,6 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// namespace AzFramework { - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputDeviceId InputDeviceMotion::Id("motion"); - //////////////////////////////////////////////////////////////////////////////////////////////// bool InputDeviceMotion::IsMotionDevice(const InputDeviceId& inputDeviceId) { @@ -60,8 +57,9 @@ namespace AzFramework } //////////////////////////////////////////////////////////////////////////////////////////////// - InputDeviceMotion::InputDeviceMotion() - : InputDevice(Id) + InputDeviceMotion::InputDeviceMotion(const InputDeviceId& inputDeviceId, + ImplementationFactory implementationFactory) + : InputDevice(inputDeviceId) , m_allChannelsById() , m_accelerationChannelsById() , m_rotationRateChannelsById() @@ -107,8 +105,8 @@ namespace AzFramework m_orientationChannelsById[channelId] = channel; } - // Create the platform specific implementation - m_pimpl.reset(Implementation::Create(*this)); + // Create the platform specific or custom implementation + m_pimpl.reset(implementationFactory ? implementationFactory(*this) : nullptr); // Connect to the motion sensor request bus InputMotionSensorRequestBus::Handler::BusConnect(GetInputDeviceId()); diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.h index 872bd1cfa0..14783e01ca 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Motion/InputDeviceMotion.h @@ -28,7 +28,7 @@ namespace AzFramework public: //////////////////////////////////////////////////////////////////////////////////////////// //! The id used to identify the primary motion input device - static const InputDeviceId Id; + static constexpr inline InputDeviceId Id{"motion"}; //////////////////////////////////////////////////////////////////////////////////////////// //! Check whether an input device id identifies a motion device (regardless of index) @@ -126,9 +126,20 @@ namespace AzFramework // Reflection static void Reflect(AZ::ReflectContext* context); + //////////////////////////////////////////////////////////////////////////////////////////// + // Foward declare the internal Implementation class so it can be passed into the constructor + class Implementation; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Alias for the function type used to create a custom implementation for this input device + using ImplementationFactory = Implementation*(InputDeviceMotion&); + //////////////////////////////////////////////////////////////////////////////////////////// //! Constructor - InputDeviceMotion(); + //! \param[in] inputDeviceId Optional override of the default input device id + //! \param[in] implementationFactory Optional override of the default Implementation::Create + explicit InputDeviceMotion(const InputDeviceId& inputDeviceId = Id, + ImplementationFactory implementationFactory = &Implementation::Create); //////////////////////////////////////////////////////////////////////////////////////////// // Disable copying diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.cpp index e9af4cc4ce..d869fa1f83 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.cpp @@ -15,18 +15,6 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// namespace AzFramework { - //////////////////////////////////////////////////////////////////////////////////////////////// - const AZ::u32 InputDeviceMouse::MovementSampleRateDefault = 60; - - //////////////////////////////////////////////////////////////////////////////////////////////// - const AZ::u32 InputDeviceMouse::MovementSampleRateQueueAll = std::numeric_limits::max(); - - //////////////////////////////////////////////////////////////////////////////////////////////// - const AZ::u32 InputDeviceMouse::MovementSampleRateAccumulateAll = 0; - - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputDeviceId InputDeviceMouse::Id("mouse"); - //////////////////////////////////////////////////////////////////////////////////////////////// bool InputDeviceMouse::IsMouseDevice(const InputDeviceId& inputDeviceId) { @@ -67,8 +55,9 @@ namespace AzFramework } //////////////////////////////////////////////////////////////////////////////////////////////// - InputDeviceMouse::InputDeviceMouse(AzFramework::InputDeviceId id) - : InputDevice(id) + InputDeviceMouse::InputDeviceMouse(const InputDeviceId& inputDeviceId, + ImplementationFactory implementationFactory) + : InputDevice(inputDeviceId) , m_allChannelsById() , m_buttonChannelsById() , m_movementChannelsById() @@ -97,8 +86,8 @@ namespace AzFramework m_cursorPositionChannel = aznew InputChannelDeltaWithSharedPosition2D(SystemCursorPosition, *this, m_cursorPositionData2D); m_allChannelsById[SystemCursorPosition] = m_cursorPositionChannel; - // Create the platform specific implementation - m_pimpl.reset(Implementation::Create(*this)); + // Create the platform specific or custom implementation + m_pimpl.reset(implementationFactory ? implementationFactory(*this) : nullptr); // Connect to the system cursor request bus InputSystemCursorRequestBus::Handler::BusConnect(GetInputDeviceId()); diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.h index 3b35e03f1b..8ad2088d2b 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.h @@ -31,23 +31,23 @@ namespace AzFramework //////////////////////////////////////////////////////////////////////////////////////////// //! Default sample rate for raw mouse movement events that aims to strike a balance between //! responsiveness and performance. - static const AZ::u32 MovementSampleRateDefault; + static constexpr inline AZ::u32 MovementSampleRateDefault{60}; //////////////////////////////////////////////////////////////////////////////////////////// //! Sample rate for raw mouse movement that will cause all events received in the same frame //! to be queued and dispatched as individual events. This results in maximum responsiveness //! but may potentially impact performance depending how many events happen over each frame. - static const AZ::u32 MovementSampleRateQueueAll; + static constexpr inline AZ::u32 MovementSampleRateQueueAll{std::numeric_limits::max()}; //////////////////////////////////////////////////////////////////////////////////////////// //! Sample rate for raw mouse movement that will cause all events received in the same frame //! to be accumulated and dispatched as a single event. Optimal for performance, but results //! in sluggish/unresponsive mouse movement, especially when running at low frame rates. - static const AZ::u32 MovementSampleRateAccumulateAll; + static constexpr inline AZ::u32 MovementSampleRateAccumulateAll{0}; //////////////////////////////////////////////////////////////////////////////////////////// //! The id used to identify the primary mouse input device - static const InputDeviceId Id; + static constexpr inline InputDeviceId Id{"mouse"}; //////////////////////////////////////////////////////////////////////////////////////////// //! Check whether an input device id identifies a mouse (regardless of index) @@ -122,9 +122,20 @@ namespace AzFramework // Reflection static void Reflect(AZ::ReflectContext* context); + //////////////////////////////////////////////////////////////////////////////////////////// + // Foward declare the internal Implementation class so it can be passed into the constructor + class Implementation; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Alias for the function type used to create a custom implementation for this input device + using ImplementationFactory = Implementation*(InputDeviceMouse&); + //////////////////////////////////////////////////////////////////////////////////////////// //! Constructor - explicit InputDeviceMouse(AzFramework::InputDeviceId id = Id); + //! \param[in] inputDeviceId Optional override of the default input device id + //! \param[in] implementationFactory Optional override of the default Implementation::Create + explicit InputDeviceMouse(const InputDeviceId& inputDeviceId = Id, + ImplementationFactory implementationFactory = &Implementation::Create); //////////////////////////////////////////////////////////////////////////////////////////// // Disable copying diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.cpp index be850e9289..395ebcecb5 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.cpp @@ -15,9 +15,6 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// namespace AzFramework { - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputDeviceId InputDeviceTouch::Id("touch"); - //////////////////////////////////////////////////////////////////////////////////////////////// bool InputDeviceTouch::IsTouchDevice(const InputDeviceId& inputDeviceId) { @@ -59,8 +56,9 @@ namespace AzFramework } //////////////////////////////////////////////////////////////////////////////////////////////// - InputDeviceTouch::InputDeviceTouch() - : InputDevice(Id) + InputDeviceTouch::InputDeviceTouch(const InputDeviceId& inputDeviceId, + ImplementationFactory implementationFactory) + : InputDevice(inputDeviceId) , m_allChannelsById() , m_touchChannelsById() , m_pimpl(nullptr) @@ -75,8 +73,8 @@ namespace AzFramework m_touchChannelsById[channelId] = channel; } - // Create the platform specific implementation - m_pimpl.reset(Implementation::Create(*this)); + // Create the platform specific or custom implementation + m_pimpl.reset(implementationFactory ? implementationFactory(*this) : nullptr); } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.h index d21834c22a..6e5e0c7ca9 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/Touch/InputDeviceTouch.h @@ -25,7 +25,7 @@ namespace AzFramework public: //////////////////////////////////////////////////////////////////////////////////////////// //! The id used to identify the primary touch input device - static const InputDeviceId Id; + static constexpr inline InputDeviceId Id{"touch"}; //////////////////////////////////////////////////////////////////////////////////////////// //! Check whether an input device id identifies a touch device (regardless of index) @@ -77,9 +77,20 @@ namespace AzFramework // Reflection static void Reflect(AZ::ReflectContext* context); + //////////////////////////////////////////////////////////////////////////////////////////// + // Foward declare the internal Implementation class so it can be passed into the constructor + class Implementation; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Alias for the function type used to create a custom implementation for this input device + using ImplementationFactory = Implementation*(InputDeviceTouch&); + //////////////////////////////////////////////////////////////////////////////////////////// //! Constructor - InputDeviceTouch(); + //! \param[in] inputDeviceId Optional override of the default input device id + //! \param[in] implementationFactory Optional override of the default Implementation::Create + explicit InputDeviceTouch(const InputDeviceId& inputDeviceId = Id, + ImplementationFactory implementationFactory = &Implementation::Create); //////////////////////////////////////////////////////////////////////////////////////////// // Disable copying diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.cpp b/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.cpp index f3024f11ab..5cfc4ea480 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.cpp +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.cpp @@ -14,9 +14,6 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// namespace AzFramework { - //////////////////////////////////////////////////////////////////////////////////////////////// - const InputDeviceId InputDeviceVirtualKeyboard::Id("virtual_keyboard"); - //////////////////////////////////////////////////////////////////////////////////////////////// bool InputDeviceVirtualKeyboard::IsVirtualKeyboardDevice(const InputDeviceId& inputDeviceId) { @@ -51,8 +48,9 @@ namespace AzFramework } //////////////////////////////////////////////////////////////////////////////////////////////// - InputDeviceVirtualKeyboard::InputDeviceVirtualKeyboard() - : InputDevice(Id) + InputDeviceVirtualKeyboard::InputDeviceVirtualKeyboard(const InputDeviceId& inputDeviceId, + ImplementationFactory implementationFactory) + : InputDevice(inputDeviceId) , m_allChannelsById() , m_pimpl() , m_implementationRequestHandler(*this) @@ -65,8 +63,8 @@ namespace AzFramework m_commandChannelsById[channelId] = channel; } - // Create the platform specific implementation - m_pimpl.reset(Implementation::Create(*this)); + // Create the platform specific or custom implementation + m_pimpl.reset(implementationFactory ? implementationFactory(*this) : nullptr); // Connect to the text entry request bus InputTextEntryRequestBus::Handler::BusConnect(GetInputDeviceId()); diff --git a/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.h b/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.h index 6f62c3ec61..c9f6286128 100644 --- a/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.h +++ b/Code/Framework/AzFramework/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard.h @@ -25,7 +25,7 @@ namespace AzFramework public: //////////////////////////////////////////////////////////////////////////////////////////// //! The id used to identify the primary virtual keyboard input device - static const InputDeviceId Id; + static constexpr inline InputDeviceId Id{"virtual_keyboard"}; //////////////////////////////////////////////////////////////////////////////////////////// //! Check whether an input device id identifies a virtual keyboard (regardless of index) @@ -69,9 +69,20 @@ namespace AzFramework // Reflection static void Reflect(AZ::ReflectContext* context); + //////////////////////////////////////////////////////////////////////////////////////////// + // Foward declare the internal Implementation class so it can be passed into the constructor + class Implementation; + + //////////////////////////////////////////////////////////////////////////////////////////// + //! Alias for the function type used to create a custom implementation for this input device + using ImplementationFactory = Implementation*(InputDeviceVirtualKeyboard&); + //////////////////////////////////////////////////////////////////////////////////////////// //! Constructor - InputDeviceVirtualKeyboard(); + //! \param[in] inputDeviceId Optional override of the default input device id + //! \param[in] implementationFactory Optional override of the default Implementation::Create + explicit InputDeviceVirtualKeyboard(const InputDeviceId& inputDeviceId = Id, + ImplementationFactory implementationFactory = &Implementation::Create); //////////////////////////////////////////////////////////////////////////////////////////// // Disable copying diff --git a/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h b/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h index c657e0edc7..ccf9acdf8b 100644 --- a/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Matchmaking/IMatchmakingRequests.h @@ -24,17 +24,17 @@ namespace AzFramework IMatchmakingRequests() = default; virtual ~IMatchmakingRequests() = default; - // Registers a player's acceptance or rejection of a proposed matchmaking. - // @param acceptMatchRequest The request of AcceptMatch operation + //! Registers a player's acceptance or rejection of a proposed matchmaking. + //! @param acceptMatchRequest The request of AcceptMatch operation virtual void AcceptMatch(const AcceptMatchRequest& acceptMatchRequest) = 0; - // Create a game match for a group of players. - // @param startMatchmakingRequest The request of StartMatchmaking operation - // @return A unique identifier for a matchmaking ticket + //! Create a game match for a group of players. + //! @param startMatchmakingRequest The request of StartMatchmaking operation + //! @return A unique identifier for a matchmaking ticket virtual AZStd::string StartMatchmaking(const StartMatchmakingRequest& startMatchmakingRequest) = 0; - // Cancels a matchmaking ticket that is currently being processed. - // @param stopMatchmakingRequest The request of StopMatchmaking operation + //! Cancels a matchmaking ticket that is currently being processed. + //! @param stopMatchmakingRequest The request of StopMatchmaking operation virtual void StopMatchmaking(const StopMatchmakingRequest& stopMatchmakingRequest) = 0; }; @@ -48,16 +48,16 @@ namespace AzFramework IMatchmakingAsyncRequests() = default; virtual ~IMatchmakingAsyncRequests() = default; - // AcceptMatch Async - // @param acceptMatchRequest The request of AcceptMatch operation + //! AcceptMatch Async + //! @param acceptMatchRequest The request of AcceptMatch operation virtual void AcceptMatchAsync(const AcceptMatchRequest& acceptMatchRequest) = 0; - // StartMatchmaking Async - // @param startMatchmakingRequest The request of StartMatchmaking operation + //! StartMatchmaking Async + //! @param startMatchmakingRequest The request of StartMatchmaking operation virtual void StartMatchmakingAsync(const StartMatchmakingRequest& startMatchmakingRequest) = 0; - // StopMatchmaking Async - // @param stopMatchmakingRequest The request of StopMatchmaking operation + //! StopMatchmaking Async + //! @param stopMatchmakingRequest The request of StopMatchmaking operation virtual void StopMatchmakingAsync(const StopMatchmakingRequest& stopMatchmakingRequest) = 0; }; @@ -76,14 +76,14 @@ namespace AzFramework static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; ////////////////////////////////////////////////////////////////////////// - // OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes + //! OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes virtual void OnAcceptMatchAsyncComplete() = 0; - // OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes - // @param matchmakingTicketId The unique identifier for the matchmaking ticket + //! OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes + //! @param matchmakingTicketId The unique identifier for the matchmaking ticket virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0; - // OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes + //! OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes virtual void OnStopMatchmakingAsyncComplete() = 0; }; using MatchmakingAsyncRequestNotificationBus = AZ::EBus; diff --git a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h index aa19b94b4a..0ec52c7215 100644 --- a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h +++ b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingNotifications.h @@ -29,17 +29,17 @@ namespace AzFramework static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; ////////////////////////////////////////////////////////////////////////// - // OnMatchAcceptance is fired when match is found and pending on acceptance - // Use this notification to accept found match + //! OnMatchAcceptance is fired when match is found and pending on acceptance + //! Use this notification to accept found match virtual void OnMatchAcceptance() = 0; - // OnMatchComplete is fired when match is complete + //! OnMatchComplete is fired when match is complete virtual void OnMatchComplete() = 0; - // OnMatchError is fired when match is processed with error + //! OnMatchError is fired when match is processed with error virtual void OnMatchError() = 0; - // OnMatchFailure is fired when match is failed to complete + //! OnMatchFailure is fired when match is failed to complete virtual void OnMatchFailure() = 0; }; using MatchmakingNotificationBus = AZ::EBus; diff --git a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingRequests.h b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingRequests.h index 9169a83588..5f5dcc0643 100644 --- a/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Matchmaking/MatchmakingRequests.h @@ -29,11 +29,11 @@ namespace AzFramework AcceptMatchRequest() = default; virtual ~AcceptMatchRequest() = default; - // Player response to accept or reject match + //! Player response to accept or reject match bool m_acceptMatch; - // A list of unique identifiers for players delivering the response + //! A list of unique identifiers for players delivering the response AZStd::vector m_playerIds; - // A unique identifier for a matchmaking ticket + //! A unique identifier for a matchmaking ticket AZStd::string m_ticketId; }; @@ -47,7 +47,7 @@ namespace AzFramework StartMatchmakingRequest() = default; virtual ~StartMatchmakingRequest() = default; - // A unique identifier for a matchmaking ticket + //! A unique identifier for a matchmaking ticket AZStd::string m_ticketId; }; @@ -61,7 +61,7 @@ namespace AzFramework StopMatchmakingRequest() = default; virtual ~StopMatchmakingRequest() = default; - // A unique identifier for a matchmaking ticket + //! A unique identifier for a matchmaking ticket AZStd::string m_ticketId; }; } // namespace AzFramework diff --git a/Code/Tools/AssetProcessor/native/utilities/CommunicatorTracePrinter.cpp b/Code/Framework/AzFramework/AzFramework/Process/ProcessCommunicatorTracePrinter.cpp similarity index 82% rename from Code/Tools/AssetProcessor/native/utilities/CommunicatorTracePrinter.cpp rename to Code/Framework/AzFramework/AzFramework/Process/ProcessCommunicatorTracePrinter.cpp index c6008deb34..51674bb32c 100644 --- a/Code/Tools/AssetProcessor/native/utilities/CommunicatorTracePrinter.cpp +++ b/Code/Framework/AzFramework/AzFramework/Process/ProcessCommunicatorTracePrinter.cpp @@ -6,16 +6,17 @@ * */ -#include "CommunicatorTracePrinter.h" +#include "ProcessCommunicatorTracePrinter.h" -CommunicatorTracePrinter::CommunicatorTracePrinter(AzFramework::ProcessCommunicator* communicator, const char* window) : + +ProcessCommunicatorTracePrinter::ProcessCommunicatorTracePrinter(AzFramework::ProcessCommunicator* communicator, const char* window) : m_communicator(communicator), m_window(window) { m_stringBeingConcatenated.reserve(1024); } -CommunicatorTracePrinter::~CommunicatorTracePrinter() +ProcessCommunicatorTracePrinter::~ProcessCommunicatorTracePrinter() { // flush stdout WriteCurrentString(false); @@ -24,7 +25,7 @@ CommunicatorTracePrinter::~CommunicatorTracePrinter() WriteCurrentString(true); } -void CommunicatorTracePrinter::Pump() +void ProcessCommunicatorTracePrinter::Pump() { if (m_communicator->IsValid()) { @@ -42,7 +43,7 @@ void CommunicatorTracePrinter::Pump() } } -void CommunicatorTracePrinter::ParseDataBuffer(AZ::u32 readSize, bool isFromStdErr) +void ProcessCommunicatorTracePrinter::ParseDataBuffer(AZ::u32 readSize, bool isFromStdErr) { if (readSize > AZ_ARRAY_SIZE(m_streamBuffer)) { @@ -67,7 +68,7 @@ void CommunicatorTracePrinter::ParseDataBuffer(AZ::u32 readSize, bool isFromStdE } } -void CommunicatorTracePrinter::WriteCurrentString(bool isFromStdErr) +void ProcessCommunicatorTracePrinter::WriteCurrentString(bool isFromStdErr) { AZStd::string& bufferToUse = isFromStdErr ? m_errorStringBeingConcatenated : m_stringBeingConcatenated; diff --git a/Code/Tools/AssetProcessor/native/utilities/CommunicatorTracePrinter.h b/Code/Framework/AzFramework/AzFramework/Process/ProcessCommunicatorTracePrinter.h similarity index 54% rename from Code/Tools/AssetProcessor/native/utilities/CommunicatorTracePrinter.h rename to Code/Framework/AzFramework/AzFramework/Process/ProcessCommunicatorTracePrinter.h index c2e4da32af..5e14fa290d 100644 --- a/Code/Tools/AssetProcessor/native/utilities/CommunicatorTracePrinter.h +++ b/Code/Framework/AzFramework/AzFramework/Process/ProcessCommunicatorTracePrinter.h @@ -10,20 +10,21 @@ #include -//! CommunicatorTracePrinter listens to stderr and stdout of a running process and writes its output to the AZ_Trace system +//! ProcessCommunicatorTracePrinter listens to stderr and stdout of a running process and writes its output to the AZ_Trace system //! Importantly, it does not do any blocking operations. -class CommunicatorTracePrinter +class ProcessCommunicatorTracePrinter { public: - CommunicatorTracePrinter(AzFramework::ProcessCommunicator* communicator, const char* window); - ~CommunicatorTracePrinter(); + ProcessCommunicatorTracePrinter(AzFramework::ProcessCommunicator* communicator, const char* window); + ~ProcessCommunicatorTracePrinter(); - // call this periodically to drain the buffers and write them. + //! Call this periodically to drain the buffers and write them. void Pump(); - // drains the buffer into the string thats being built, then traces the string when it hits a newline. + //! Drains the buffer into the string that's being built, then traces the string when it hits a newline. void ParseDataBuffer(AZ::u32 readSize, bool isFromStdErr); + //! Prints the current buffer to AZ_Error or AZ_TracePrintf so that it can be picked up by AZ::Debug::Trace void WriteCurrentString(bool isFromStdError); private: diff --git a/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.cpp b/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.cpp index 798b1fe99f..d64c83a6db 100644 --- a/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.cpp +++ b/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include @@ -22,7 +21,7 @@ namespace AzFramework AZStd::scoped_ptr pWatcher(LaunchProcess(processLaunchInfo, communicationType)); if (!pWatcher) { - AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str()); + AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.GetCommandLineParametersAsString().c_str()); return false; } else @@ -31,7 +30,7 @@ namespace AzFramework ProcessCommunicator* pCommunicator = pWatcher->GetCommunicator(); if (!pCommunicator || !pCommunicator->IsValid()) { - AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str()); + AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.GetCommandLineParametersAsString().c_str()); return false; } else diff --git a/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.h b/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.h index 042f450db0..323136c1e0 100644 --- a/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.h +++ b/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace AzFramework { @@ -37,7 +38,7 @@ namespace AzFramework * On windows, the command line will be passed as-is to the shell (with quotes) * on UNIX/OSX, the command line will be converted as appropriate (quotes removed, but used to chop up parameters) */ - AZStd::string m_commandlineParameters; + AZStd::variant> m_commandlineParameters; /** * (optional) If you specify a working directory, the command will be executed with that directory as the current directory. @@ -50,6 +51,8 @@ namespace AzFramework //Not Supported On Mac bool m_showWindow = true; + + AZStd::string GetCommandLineParametersAsString() const; }; static const AZ::u32 INFINITE_TIMEOUT = (AZ::u32) -1; diff --git a/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.cpp b/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.cpp index 6bbb07ea74..ed3ff44d1c 100644 --- a/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.cpp @@ -83,7 +83,7 @@ namespace AzFramework::ProjectManager return ProjectPathCheckResult::ProjectManagerLaunchFailed; } - bool LaunchProjectManager([[maybe_unused]]const AZStd::string& commandLineArgs) + bool LaunchProjectManager([[maybe_unused]] const AZStd::vector& commandLineArgs) { bool launchSuccess = false; #if (AZ_TRAIT_AZFRAMEWORK_USE_PROJECT_MANAGER) @@ -105,7 +105,12 @@ namespace AzFramework::ProjectManager } AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = executablePath.String() + commandLineArgs; + + AZStd::vector launchCmd = { executablePath.String() }; + launchCmd.insert(launchCmd.end(), commandLineArgs.begin(), commandLineArgs.end()); + + processLaunchInfo.m_commandlineParameters = AZStd::move(launchCmd); + launchSuccess = AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo); } if (ownsSystemAllocator) diff --git a/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.h b/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.h index 323045886d..ca4e88ebe1 100644 --- a/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.h +++ b/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include namespace AzFramework::ProjectManager @@ -29,5 +30,5 @@ namespace AzFramework::ProjectManager //! current executable. Requires the o3de cli and python. //! @param commandLineArgs additional command line arguments to provide to the project manager //! @return true on success, false if failed to find or launch the executable - bool LaunchProjectManager(const AZStd::string& commandLineArgs = ""); + bool LaunchProjectManager(const AZStd::vector& commandLineArgs = {}); } // AzFramework::ProjectManager diff --git a/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h b/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h index 065d6bb9d5..188ea7b994 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Session/ISessionHandlingRequests.h @@ -18,16 +18,16 @@ namespace AzFramework //! The properties for handling join session request. struct SessionConnectionConfig { - // A unique identifier for registered player in session. + //! A unique identifier for registered player in session. AZStd::string m_playerSessionId; - // The DNS identifier assigned to the instance that is running the session. + //! The DNS identifier assigned to the instance that is running the session. AZStd::string m_dnsName; - // The IP address of the session. + //! The IP address of the session. AZStd::string m_ipAddress; - // The port number for the session. + //! The port number for the session. uint16_t m_port = 0; }; @@ -35,10 +35,10 @@ namespace AzFramework //! The properties for handling player connect/disconnect struct PlayerConnectionConfig { - // A unique identifier for player connection. + //! A unique identifier for player connection. uint32_t m_playerConnectionId = 0; - // A unique identifier for registered player in session. + //! A unique identifier for registered player in session. AZStd::string m_playerSessionId; }; @@ -51,12 +51,12 @@ namespace AzFramework ISessionHandlingClientRequests() = default; virtual ~ISessionHandlingClientRequests() = default; - // Request the player join session - // @param sessionConnectionConfig The required properties to handle the player join session process - // @return The result of player join session process + //! Request the player join session + //! @param sessionConnectionConfig The required properties to handle the player join session process + //! @return The result of player join session process virtual bool RequestPlayerJoinSession(const SessionConnectionConfig& sessionConnectionConfig) = 0; - // Request the connected player leave session + //! Request the connected player leave session virtual void RequestPlayerLeaveSession() = 0; }; @@ -69,26 +69,26 @@ namespace AzFramework ISessionHandlingProviderRequests() = default; virtual ~ISessionHandlingProviderRequests() = default; - // Handle the destroy session process + //! Handle the destroy session process virtual void HandleDestroySession() = 0; - // Validate the player join session process - // @param playerConnectionConfig The required properties to validate the player join session process - // @return The result of player join session validation + //! Validate the player join session process + //! @param playerConnectionConfig The required properties to validate the player join session process + //! @return The result of player join session validation virtual bool ValidatePlayerJoinSession(const PlayerConnectionConfig& playerConnectionConfig) = 0; - // Handle the player leave session process - // @param playerConnectionConfig The required properties to handle the player leave session process + //! Handle the player leave session process + //! @param playerConnectionConfig The required properties to handle the player leave session process virtual void HandlePlayerLeaveSession(const PlayerConnectionConfig& playerConnectionConfig) = 0; - // Retrieves the file location of a pem-encoded TLS certificate for Client to Server communication - // @return If successful, returns the file location of TLS certificate file; if not successful, returns - // empty string. + //! Retrieves the file location of a pem-encoded TLS certificate for Client to Server communication + //! @return If successful, returns the file location of TLS certificate file; if not successful, returns + //! empty string. virtual AZ::IO::Path GetExternalSessionCertificate() = 0; - // Retrieves the file location of a pem-encoded TLS certificate for Server to Server communication - // @return If successful, returns the file location of TLS certificate file; if not successful, returns - // empty string. + //! Retrieves the file location of a pem-encoded TLS certificate for Server to Server communication + //! @return If successful, returns the file location of TLS certificate file; if not successful, returns + //! empty string. virtual AZ::IO::Path GetInternalSessionCertificate() = 0; }; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h b/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h index bdc3fe1444..8b985a3187 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Session/ISessionRequests.h @@ -25,22 +25,22 @@ namespace AzFramework ISessionRequests() = default; virtual ~ISessionRequests() = default; - // Create a session for players to find and join. - // @param createSessionRequest The request of CreateSession operation - // @return The request id if session creation request succeeds; empty if it fails + //! Create a session for players to find and join. + //! @param createSessionRequest The request of CreateSession operation + //! @return The request id if session creation request succeeds; empty if it fails virtual AZStd::string CreateSession(const CreateSessionRequest& createSessionRequest) = 0; - // Retrieve all active sessions that match the given search criteria and sorted in specific order. - // @param searchSessionsRequest The request of SearchSessions operation - // @return The response of SearchSessions operation + //! Retrieve all active sessions that match the given search criteria and sorted in specific order. + //! @param searchSessionsRequest The request of SearchSessions operation + //! @return The response of SearchSessions operation virtual SearchSessionsResponse SearchSessions(const SearchSessionsRequest& searchSessionsRequest) const = 0; - // Reserve an open player slot in a session, and perform connection from client to server. - // @param joinSessionRequest The request of JoinSession operation - // @return True if joining session succeeds; False otherwise + //! Reserve an open player slot in a session, and perform connection from client to server. + //! @param joinSessionRequest The request of JoinSession operation + //! @return True if joining session succeeds; False otherwise virtual bool JoinSession(const JoinSessionRequest& joinSessionRequest) = 0; - // Disconnect player from session. + //! Disconnect player from session. virtual void LeaveSession() = 0; }; @@ -54,19 +54,19 @@ namespace AzFramework ISessionAsyncRequests() = default; virtual ~ISessionAsyncRequests() = default; - // CreateSession Async - // @param createSessionRequest The request of CreateSession operation + //! CreateSession Async + //! @param createSessionRequest The request of CreateSession operation virtual void CreateSessionAsync(const CreateSessionRequest& createSessionRequest) = 0; - // SearchSessions Async - // @param searchSessionsRequest The request of SearchSessions operation + //! SearchSessions Async + //! @param searchSessionsRequest The request of SearchSessions operation virtual void SearchSessionsAsync(const SearchSessionsRequest& searchSessionsRequest) const = 0; - // JoinSession Async - // @param joinSessionRequest The request of JoinSession operation + //! JoinSession Async + //! @param joinSessionRequest The request of JoinSession operation virtual void JoinSessionAsync(const JoinSessionRequest& joinSessionRequest) = 0; - // LeaveSession Async + //! LeaveSession Async virtual void LeaveSessionAsync() = 0; }; @@ -85,19 +85,19 @@ namespace AzFramework static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; ////////////////////////////////////////////////////////////////////////// - // OnCreateSessionAsyncComplete is fired once CreateSessionAsync completes - // @param createSessionResponse The request id if session creation request succeeds; empty if it fails + //! OnCreateSessionAsyncComplete is fired once CreateSessionAsync completes + //! @param createSessionResponse The request id if session creation request succeeds; empty if it fails virtual void OnCreateSessionAsyncComplete(const AZStd::string& createSessionReponse) = 0; - // OnSearchSessionsAsyncComplete is fired once SearchSessionsAsync completes - // @param searchSessionsResponse The response of SearchSessions call + //! OnSearchSessionsAsyncComplete is fired once SearchSessionsAsync completes + //! @param searchSessionsResponse The response of SearchSessions call virtual void OnSearchSessionsAsyncComplete(const SearchSessionsResponse& searchSessionsResponse) = 0; - // OnJoinSessionAsyncComplete is fired once JoinSessionAsync completes - // @param joinSessionsResponse True if joining session succeeds; False otherwise + //! OnJoinSessionAsyncComplete is fired once JoinSessionAsync completes + //! @param joinSessionsResponse True if joining session succeeds; False otherwise virtual void OnJoinSessionAsyncComplete(bool joinSessionsResponse) = 0; - // OnLeaveSessionAsyncComplete is fired once LeaveSessionAsync completes + //! OnLeaveSessionAsyncComplete is fired once LeaveSessionAsync completes virtual void OnLeaveSessionAsyncComplete() = 0; }; using SessionAsyncRequestNotificationBus = AZ::EBus; diff --git a/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h b/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h index 45e40c2f29..f951cf58fa 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h +++ b/Code/Framework/AzFramework/AzFramework/Session/SessionConfig.h @@ -24,46 +24,46 @@ namespace AzFramework SessionConfig() = default; virtual ~SessionConfig() = default; - // A time stamp indicating when this session was created. Format is a number expressed in Unix time as milliseconds. + //! A time stamp indicating when this session was created. Format is a number expressed in Unix time as milliseconds. uint64_t m_creationTime = 0; - // A time stamp indicating when this data object was terminated. Same format as creation time. + //! A time stamp indicating when this data object was terminated. Same format as creation time. uint64_t m_terminationTime = 0; - // A unique identifier for a player or entity creating the session. + //! A unique identifier for a player or entity creating the session. AZStd::string m_creatorId; - // A collection of custom properties for a session. + //! A collection of custom properties for a session. AZStd::unordered_map m_sessionProperties; - // The matchmaking process information that was used to create the session. + //! The matchmaking process information that was used to create the session. AZStd::string m_matchmakingData; - // A unique identifier for the session. + //! A unique identifier for the session. AZStd::string m_sessionId; - // A descriptive label that is associated with a session. + //! A descriptive label that is associated with a session. AZStd::string m_sessionName; - // The DNS identifier assigned to the instance that is running the session. + //! The DNS identifier assigned to the instance that is running the session. AZStd::string m_dnsName; - // The IP address of the session. + //! The IP address of the session. AZStd::string m_ipAddress; - // The port number for the session. + //! The port number for the session. uint16_t m_port = 0; - // The maximum number of players that can be connected simultaneously to the session. + //! The maximum number of players that can be connected simultaneously to the session. uint64_t m_maxPlayer = 0; - // Number of players currently in the session. + //! Number of players currently in the session. uint64_t m_currentPlayer = 0; - // Current status of the session. + //! Current status of the session. AZStd::string m_status; - // Provides additional information about session status. + //! Provides additional information about session status. AZStd::string m_statusReason; }; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h b/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h index 2213343aa2..cf1382aeba 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h +++ b/Code/Framework/AzFramework/AzFramework/Session/SessionNotifications.h @@ -29,42 +29,42 @@ namespace AzFramework static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; ////////////////////////////////////////////////////////////////////////// - // OnSessionHealthCheck is fired in health check process - // Use this notification to perform any custom health check - // @return True if OnSessionHealthCheck succeeds, false otherwise + //! OnSessionHealthCheck is fired in health check process + //! Use this notification to perform any custom health check + //! @return True if OnSessionHealthCheck succeeds, false otherwise virtual bool OnSessionHealthCheck() = 0; - // OnCreateSessionBegin is fired at the beginning of session creation process - // Use this notification to perform any necessary configuration or initialization before - // creating session - // @param sessionConfig The properties to describe a session - // @return True if OnCreateSessionBegin succeeds, false otherwise + //! OnCreateSessionBegin is fired at the beginning of session creation process + //! Use this notification to perform any necessary configuration or initialization before + //! creating session + //! @param sessionConfig The properties to describe a session + //! @return True if OnCreateSessionBegin succeeds, false otherwise virtual bool OnCreateSessionBegin(const SessionConfig& sessionConfig) = 0; - // OnCreateSessionEnd is fired at the end of session creation process - // Use this notification to perform any follow-up operation after session is created and active + //! OnCreateSessionEnd is fired at the end of session creation process + //! Use this notification to perform any follow-up operation after session is created and active virtual void OnCreateSessionEnd() = 0; - // OnDestroySessionBegin is fired at the beginning of session termination process - // Use this notification to perform any cleanup operation before destroying session, - // like gracefully disconnect players, cleanup data, etc. - // @return True if OnDestroySessionBegin succeeds, false otherwise + //! OnDestroySessionBegin is fired at the beginning of session termination process + //! Use this notification to perform any cleanup operation before destroying session, + //! like gracefully disconnect players, cleanup data, etc. + //! @return True if OnDestroySessionBegin succeeds, false otherwise virtual bool OnDestroySessionBegin() = 0; - // OnDestroySessionEnd is fired at the end of session termination process - // Use this notification to perform any follow-up operation after session is destroyed, - // like shutdown application process, etc. + //! OnDestroySessionEnd is fired at the end of session termination process + //! Use this notification to perform any follow-up operation after session is destroyed, + //! like shutdown application process, etc. virtual void OnDestroySessionEnd() = 0; - // OnUpdateSessionBegin is fired at the beginning of session update process - // Use this notification to perform any configuration or initialization to handle - // the session settings changing - // @param sessionConfig The properties to describe a session - // @param updateReason The reason for session update + //! OnUpdateSessionBegin is fired at the beginning of session update process + //! Use this notification to perform any configuration or initialization to handle + //! the session settings changing + //! @param sessionConfig The properties to describe a session + //! @param updateReason The reason for session update virtual void OnUpdateSessionBegin(const SessionConfig& sessionConfig, const AZStd::string& updateReason) = 0; - // OnUpdateSessionBegin is fired at the end of session update process - // Use this notification to perform any follow-up operations after session is updated + //! OnUpdateSessionBegin is fired at the end of session update process + //! Use this notification to perform any follow-up operations after session is updated virtual void OnUpdateSessionEnd() = 0; }; using SessionNotificationBus = AZ::EBus; diff --git a/Code/Framework/AzFramework/AzFramework/Session/SessionRequests.h b/Code/Framework/AzFramework/AzFramework/Session/SessionRequests.h index 1ae018e1ef..d806e6fefa 100644 --- a/Code/Framework/AzFramework/AzFramework/Session/SessionRequests.h +++ b/Code/Framework/AzFramework/AzFramework/Session/SessionRequests.h @@ -31,16 +31,16 @@ namespace AzFramework CreateSessionRequest() = default; virtual ~CreateSessionRequest() = default; - // A unique identifier for a player or entity creating the session. + //! A unique identifier for a player or entity creating the session. AZStd::string m_creatorId; - // A collection of custom properties for a session. + //! A collection of custom properties for a session. AZStd::unordered_map m_sessionProperties; - // A descriptive label that is associated with a session. + //! A descriptive label that is associated with a session. AZStd::string m_sessionName; - // The maximum number of players that can be connected simultaneously to the session. + //! The maximum number of players that can be connected simultaneously to the session. uint64_t m_maxPlayer = 0; }; @@ -54,17 +54,17 @@ namespace AzFramework SearchSessionsRequest() = default; virtual ~SearchSessionsRequest() = default; - // String containing the search criteria for the session search. If no filter expression is included, the request returns results - // for all active sessions. + //! String containing the search criteria for the session search. If no filter expression is included, the request returns results + //! for all active sessions. AZStd::string m_filterExpression; - // Instructions on how to sort the search results. If no sort expression is included, the request returns results in random order. + //! Instructions on how to sort the search results. If no sort expression is included, the request returns results in random order. AZStd::string m_sortExpression; - // The maximum number of results to return. + //! The maximum number of results to return. uint8_t m_maxResult = 0; - // A token that indicates the start of the next sequential page of results. + //! A token that indicates the start of the next sequential page of results. AZStd::string m_nextToken; }; @@ -78,10 +78,10 @@ namespace AzFramework SearchSessionsResponse() = default; virtual ~SearchSessionsResponse() = default; - // A collection of sessions that match the search criteria and sorted in specific order. + //! A collection of sessions that match the search criteria and sorted in specific order. AZStd::vector m_sessionConfigs; - // A token that indicates the start of the next sequential page of results. + //! A token that indicates the start of the next sequential page of results. AZStd::string m_nextToken; }; @@ -95,13 +95,13 @@ namespace AzFramework JoinSessionRequest() = default; virtual ~JoinSessionRequest() = default; - // A unique identifier for the session. + //! A unique identifier for the session. AZStd::string m_sessionId; - // A unique identifier for a player. Player IDs are developer-defined. + //! A unique identifier for a player. Player IDs are developer-defined. AZStd::string m_playerId; - // Developer-defined information related to a player. + //! Developer-defined information related to a player. AZStd::string m_playerData; }; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/RootSpawnableInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/RootSpawnableInterface.h index 72a3031e3e..d3fe62eae3 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/RootSpawnableInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/RootSpawnableInterface.h @@ -30,12 +30,22 @@ namespace AzFramework //! Called when the root spawnable has been assigned a new value. This may be called several times without a call to release //! in between. + //! @note: The callback is not queued but immediately called from a random thread. This is done because this callback is typically + //! used before entities are spawned and if it's queued then the entities spawn before this callback is called. //! @param rootSpawnable The new root spawnable that was assigned. //! @param generation The generation of the root spawnable. This will increment every time a new spawnable is assigned. virtual void OnRootSpawnableAssigned([[maybe_unused]] AZ::Data::Asset rootSpawnable, [[maybe_unused]] uint32_t generation) {} + //! Called when the root spawnable has completed spawning of entities. This may be called several times without a call to release + //! in between. + //! @note: This callback is queued and will be called with a delay and from the main thread. + //! @param rootSpawnable The new root spawnable that was used to spawn entities from. + //! @param generation The generation of the root spawnable. This will increment every time a new spawnable is assigned. + virtual void OnRootSpawnableReady( + [[maybe_unused]] AZ::Data::Asset rootSpawnable, [[maybe_unused]] uint32_t generation) {} //! Called when the root spawnable has Released. This will only be called if there's no root spawnable assigned to take the //! place of the original root spawnable. + //! Note: This callback is queued and will be called with a delay and from the main thread. //! @param generation The generation of the root spawnable that was released. virtual void OnRootSpawnableReleased([[maybe_unused]] uint32_t generation) {} }; diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.cpp index 4855dc15b3..6259a57ab5 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.cpp @@ -6,12 +6,473 @@ * */ +#include #include #include +#include +#include +#include #include namespace AzFramework { + // + // EntityAlias + // + + bool Spawnable::EntityAlias::HasLowerIndex(const EntityAlias& other) const + { + return m_sourceIndex == other.m_sourceIndex ? + m_aliasType < other.m_aliasType : + m_sourceIndex < other.m_sourceIndex; + } + + + // + // EntityAliasVisitorBase + // + + bool Spawnable::EntityAliasVisitorBase::IsValid(const EntityAliasList* aliases) const + { + return aliases != nullptr; + } + + bool Spawnable::EntityAliasVisitorBase::HasAliases(const EntityAliasList* aliases) const + { + AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked."); + return !aliases->empty(); + } + + bool Spawnable::EntityAliasVisitorBase::AreAllSpawnablesReady(const EntityAliasList* aliases) const + { + AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked."); + for (const EntityAlias& alias : *aliases) + { + if (!alias.m_queueLoad || + alias.m_aliasType == Spawnable::EntityAliasType::Original || + alias.m_aliasType == Spawnable::EntityAliasType::Disable) + { + continue; + } + if (!alias.m_spawnable.IsReady() && !alias.m_spawnable.IsError()) + { + return false; + } + } + return true; + } + + auto Spawnable::EntityAliasVisitorBase::begin(const EntityAliasList* aliases) const -> EntityAliasList::const_iterator + { + AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked."); + return aliases->cbegin(); + } + + auto Spawnable::EntityAliasVisitorBase::end(const EntityAliasList* aliases) const -> EntityAliasList::const_iterator + { + AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked."); + return aliases->cend(); + } + + auto Spawnable::EntityAliasVisitorBase::cbegin(const EntityAliasList* aliases) const -> EntityAliasList::const_iterator + { + AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked."); + return aliases->cbegin(); + } + + auto Spawnable::EntityAliasVisitorBase::cend(const EntityAliasList* aliases) const -> EntityAliasList::const_iterator + { + AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked."); + return aliases->cend(); + } + + void Spawnable::EntityAliasVisitorBase::ListTargetSpawnables( + const EntityAliasList* aliases, const ListTargetSpawanblesCallback& callback) const + { + AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked."); + AZStd::unordered_set spawnableIds; + for (const Spawnable::EntityAlias& alias : *aliases) + { + // If the spawnable id is not valid it means that the alias is referencing the spawnable it's stored on. + if (alias.m_spawnable.GetId().IsValid()) + { + auto it = spawnableIds.find(alias.m_spawnable.GetId()); + if (it == spawnableIds.end()) + { + callback(alias.m_spawnable); + spawnableIds.emplace(alias.m_spawnable.GetId()); + } + } + } + } + + void Spawnable::EntityAliasVisitorBase::ListTargetSpawnables( + const EntityAliasList* aliases, AZ::Crc32 tag, const ListTargetSpawanblesCallback& callback) const + { + AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked."); + AZStd::unordered_set spawnableIds; + for (const Spawnable::EntityAlias& alias : *aliases) + { + // If the spawnable id is not valid it means that the alias is referencing the spawnable it's stored on. + if (alias.m_tag == tag && alias.m_spawnable.GetId().IsValid()) + { + auto it = spawnableIds.find(alias.m_spawnable.GetId()); + if (it == spawnableIds.end()) + { + callback(alias.m_spawnable); + spawnableIds.emplace(alias.m_spawnable.GetId()); + } + } + } + } + + + // + // EntityAliasVisitor + // + + Spawnable::EntityAliasVisitor::EntityAliasVisitor(Spawnable& owner, EntityAliasList* entityAliasList) + : m_owner(owner) + , m_entityAliasList(entityAliasList) + { + } + + Spawnable::EntityAliasVisitor::~EntityAliasVisitor() + { + if (IsValid()) + { + Optimize(); + + AZ_Assert( + m_owner.m_shareState == ShareState::ReadWrite, "Attempting to unlock a spawnable that's not in the locked state (%i).", + m_owner.m_shareState.load()); + m_owner.m_shareState = ShareState::NotShared; + } + } + + Spawnable::EntityAliasVisitor::EntityAliasVisitor(EntityAliasVisitor&& rhs) + : m_owner(rhs.m_owner) + , m_entityAliasList(rhs.m_entityAliasList) + { + m_dirty = rhs.m_dirty; + + rhs.m_entityAliasList = nullptr; + rhs.m_dirty = false; + } + + auto Spawnable::EntityAliasVisitor::operator=(EntityAliasVisitor&& rhs) -> EntityAliasVisitor& + { + if (this != &rhs) + { + this->~EntityAliasVisitor(); + new(this) EntityAliasVisitor(AZStd::move(rhs)); + } + return *this; + } + + bool Spawnable::EntityAliasVisitor::IsValid() const + { + return EntityAliasVisitorBase::IsValid(m_entityAliasList); + } + + bool Spawnable::EntityAliasVisitor::HasAliases() const + { + return EntityAliasVisitorBase::HasAliases(m_entityAliasList); + } + + bool Spawnable::EntityAliasVisitor::AreAllSpawnablesReady() const + { + return EntityAliasVisitorBase::AreAllSpawnablesReady(m_entityAliasList); + } + + auto Spawnable::EntityAliasVisitor::begin() const -> EntityAliasList::const_iterator + { + return EntityAliasVisitorBase::begin(m_entityAliasList); + } + + auto Spawnable::EntityAliasVisitor::end() const -> EntityAliasList::const_iterator + { + return EntityAliasVisitorBase::end(m_entityAliasList); + } + + auto Spawnable::EntityAliasVisitor::cbegin() const -> EntityAliasList::const_iterator + { + return EntityAliasVisitorBase::cbegin(m_entityAliasList); + } + + auto Spawnable::EntityAliasVisitor::cend() const -> EntityAliasList::const_iterator + { + return EntityAliasVisitorBase::cend(m_entityAliasList); + } + + void Spawnable::EntityAliasVisitor::ListTargetSpawnables(const ListTargetSpawanblesCallback& callback) const + { + EntityAliasVisitorBase::ListTargetSpawnables(m_entityAliasList, callback); + } + + void Spawnable::EntityAliasVisitor::ListTargetSpawnables(AZ::Crc32 tag, const ListTargetSpawanblesCallback& callback) const + { + EntityAliasVisitorBase::ListTargetSpawnables(m_entityAliasList, tag, callback); + } + + void Spawnable::EntityAliasVisitor::AddAlias( + AZ::Data::Asset targetSpawnable, + AZ::Crc32 tag, + uint32_t sourceIndex, + uint32_t targetIndex, + Spawnable::EntityAliasType aliasType, + bool queueLoad) + { + AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked."); + AZ_Assert(sourceIndex < m_owner.GetEntities().size(), "Invalid source index (%i) for entity alias", sourceIndex); + if (targetSpawnable.IsReady()) + { + AZ_Assert( + targetIndex < targetSpawnable->GetEntities().size(), "Invalid target index (%i) for entity alias '%s'", targetIndex, + targetSpawnable.GetHint().c_str()); + } + + m_entityAliasList->push_back(Spawnable::EntityAlias{ targetSpawnable, tag, sourceIndex, targetIndex, aliasType, queueLoad }); + m_dirty = true; + } + + void Spawnable::EntityAliasVisitor::ListSpawnablesRequiringLoad(const ListSpawnablesRequiringLoadCallback& callback) + { + AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked."); + for (Spawnable::EntityAlias& alias : *m_entityAliasList) + { + if (alias.m_queueLoad && + alias.m_aliasType != Spawnable::EntityAliasType::Original && + alias.m_aliasType != Spawnable::EntityAliasType::Disable && + !alias.m_spawnable.IsLoading() && + !alias.m_spawnable.IsReady() && + !alias.m_spawnable.IsError()) + { + callback(alias.m_spawnable); + } + } + } + + void Spawnable::EntityAliasVisitor::UpdateAliases(const UpdateCallback& callback) + { + AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked."); + for (Spawnable::EntityAlias& alias : *m_entityAliasList) + { + AZ::Data::Asset targetSpawnable(alias.m_spawnable); + callback(alias.m_aliasType, alias.m_queueLoad, targetSpawnable, alias.m_tag, alias.m_sourceIndex, alias.m_targetIndex); + } + m_dirty = true; + } + + void Spawnable::EntityAliasVisitor::UpdateAliases(AZ::Crc32 tag, const UpdateCallback& callback) + { + AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked."); + for (Spawnable::EntityAlias& alias : *m_entityAliasList) + { + if (alias.m_tag == tag) + { + AZ::Data::Asset targetSpawnable(alias.m_spawnable); + callback(alias.m_aliasType, alias.m_queueLoad, targetSpawnable, alias.m_tag, alias.m_sourceIndex, alias.m_targetIndex); + m_dirty = true; + } + } + } + + void Spawnable::EntityAliasVisitor::UpdateAliasType(uint32_t index, Spawnable::EntityAliasType newType) + { + AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked."); + AZ_Assert( + index < m_entityAliasList->size(), "Unable to update entity alias at index %i as there are only %zu aliases in spawnable.", + index, m_entityAliasList->size()); + (*m_entityAliasList)[index].m_aliasType = newType; + m_dirty = true; + } + + void Spawnable::EntityAliasVisitor::Optimize() + { + AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked."); + if (m_dirty) + { + AZStd::stable_sort( + m_entityAliasList->begin(), m_entityAliasList->end(), + [](const Spawnable::EntityAlias& lhs, const Spawnable::EntityAlias& rhs) + { + // Sort by source index from smallest to largest so during spawning the entities can be iterated linearly over. + // If the source index is the same then sort by alias type so the next steps can optimize away superfluous steps. + return lhs.HasLowerIndex(rhs); + }); + + // Remove aliases that are not going to have any practical effect and insert aliases where needed to simplify the spawning. + // This is done at runtime rather than at build time because the above ebus allows other systems to make adjustments to the + // aliases, for instance Networking can decide to disable certain aliases when running on a client. This in turn also requires + // the aliases to be in their recorded order during building as the ebus handlers may depend on that order to determine what + // entities need to be updated. + uint32_t previousIndex = AZStd::numeric_limits::max(); + Spawnable::EntityAliasType previousType = + static_cast(AZStd::numeric_limits>::max()); + Spawnable::EntityAlias* it = m_entityAliasList->begin(); + Spawnable::EntityAlias* end = m_entityAliasList->end(); + while (it < end) + { + // If there's a switch to a new source index and the previous index only had an original it can + // be removed. + if (previousType == Spawnable::EntityAliasType::Original && previousIndex != it->m_sourceIndex) + { + it = m_entityAliasList->erase(it - 1); + end = m_entityAliasList->end(); + if (it == end) + { + break; + } + } + + switch (it->m_aliasType) + { + case Spawnable::EntityAliasType::Original: + [[fallthrough]]; + case Spawnable::EntityAliasType::Disable: + [[fallthrough]]; + case Spawnable::EntityAliasType::Replace: + // If the previous entry was a disabled, original or replace alias then remove it as it will be overwritten by the + // current entry. + if (previousIndex == it->m_sourceIndex && + (previousType == Spawnable::EntityAliasType::Original || + previousType == Spawnable::EntityAliasType::Disable || + previousType == Spawnable::EntityAliasType::Replace)) + { + previousIndex = it->m_sourceIndex; + previousType = it->m_aliasType; + // Erase instead of a swap-and-pop in order to preserver the order. + it = m_entityAliasList->erase(it - 1) + 1; + end = m_entityAliasList->end(); + } + else + { + previousIndex = it->m_sourceIndex; + previousType = it->m_aliasType; + ++it; + } + break; + case Spawnable::EntityAliasType::Additional: + [[fallthrough]]; + case Spawnable::EntityAliasType::Merge: + // If this is the first entry for this index then insert an original in front of it so the spawnable entity manager + // doesn't have to check for the case there's a merge and/or addition without an entity to extend. + if (previousIndex != it->m_sourceIndex) + { + Spawnable::EntityAlias insert; + // No load, as the asset is already loaded. + insert.m_spawnable = AZ::Data::Asset({}, azrtti_typeid()); + insert.m_sourceIndex = it->m_sourceIndex; + insert.m_targetIndex = it->m_sourceIndex; // Source index as the original entry for this slot is added. + insert.m_aliasType = Spawnable::EntityAliasType::Original; + + previousIndex = it->m_sourceIndex; + previousType = it->m_aliasType; + + // Insert to maintain the order. + it = m_entityAliasList->insert(it, AZStd::move(insert)); + it += 2; + end = m_entityAliasList->end(); + } + else + { + previousType = it->m_aliasType; + ++it; + } + break; + default: + AZ_Assert(false, "Invalid Spawnable entity alias type found during asset loading: %i", it->m_aliasType); + break; + } + } + + // Check if the last entry is an "Original" in which case it can be removed. + if (!m_entityAliasList->empty() && m_entityAliasList->back().m_aliasType == Spawnable::EntityAliasType::Original) + { + m_entityAliasList->pop_back(); + } + + // Reclaim memory because after this point the aliases will not change anymore. + m_entityAliasList->shrink_to_fit(); + m_dirty = false; + } + } + + + + // + // EntityAliasConstVisitor + // + + Spawnable::EntityAliasConstVisitor::EntityAliasConstVisitor(const Spawnable& owner, const EntityAliasList* entityAliasList) + : m_owner(owner) + , m_entityAliasList(entityAliasList) + { + } + + Spawnable::EntityAliasConstVisitor::~EntityAliasConstVisitor() + { + if (IsValid()) + { + AZ_Assert( + m_owner.m_shareState <= ShareState::Read, "Attempting to unlock a read shared spawnable that was not in a read shared mode (%i).", + m_owner.m_shareState.load()); + m_owner.m_shareState++; + } + } + + bool Spawnable::EntityAliasConstVisitor::IsValid() const + { + return EntityAliasVisitorBase::IsValid(m_entityAliasList); + } + + bool Spawnable::EntityAliasConstVisitor::HasAliases() const + { + return EntityAliasVisitorBase::HasAliases(m_entityAliasList); + } + + bool Spawnable::EntityAliasConstVisitor::AreAllSpawnablesReady() const + { + return EntityAliasVisitorBase::AreAllSpawnablesReady(m_entityAliasList); + } + + auto Spawnable::EntityAliasConstVisitor::begin() const -> EntityAliasList::const_iterator + { + return EntityAliasVisitorBase::begin(m_entityAliasList); + } + + auto Spawnable::EntityAliasConstVisitor::end() const -> EntityAliasList::const_iterator + { + return EntityAliasVisitorBase::end(m_entityAliasList); + } + + auto Spawnable::EntityAliasConstVisitor::cbegin() const -> EntityAliasList::const_iterator + { + return EntityAliasVisitorBase::cbegin(m_entityAliasList); + } + + auto Spawnable::EntityAliasConstVisitor::cend() const -> EntityAliasList::const_iterator + { + return EntityAliasVisitorBase::cend(m_entityAliasList); + } + + void Spawnable::EntityAliasConstVisitor::ListTargetSpawnables(const ListTargetSpawanblesCallback& callback) const + { + EntityAliasVisitorBase::ListTargetSpawnables(m_entityAliasList, callback); + } + + void Spawnable::EntityAliasConstVisitor::ListTargetSpawnables(AZ::Crc32 tag, const ListTargetSpawanblesCallback& callback) const + { + EntityAliasVisitorBase::ListTargetSpawnables(m_entityAliasList, tag, callback); + } + + + + // + // Spawnable + // + Spawnable::Spawnable(const AZ::Data::AssetId& id, AssetStatus status) : AZ::Data::AssetData(id, status) { @@ -27,6 +488,33 @@ namespace AzFramework return m_entities; } + auto Spawnable::TryGetAliasesConst() const -> EntityAliasConstVisitor + { + int32_t expected = ShareState::NotShared; + do + { + // Try to set the lock to a negative number to indicate a shared read. + if (m_shareState.compare_exchange_strong(expected, expected - 1)) + { + return EntityAliasConstVisitor(*this, &m_entityAliases); + } + // as long as the value is negative or not shared then keep trying to get a shared read lock. + } while (expected <= 0); + return EntityAliasConstVisitor(*this, nullptr); + } + + auto Spawnable::TryGetAliases() const -> EntityAliasConstVisitor + { + return TryGetAliasesConst(); + } + + auto Spawnable::TryGetAliases() -> EntityAliasVisitor + { + int32_t expected = ShareState::NotShared; + return m_shareState.compare_exchange_strong(expected, ShareState::ReadWrite) ? EntityAliasVisitor(*this, &m_entityAliases) + : EntityAliasVisitor(*this, nullptr); + } + bool Spawnable::IsEmpty() const { return m_entities.empty(); @@ -44,11 +532,29 @@ namespace AzFramework void Spawnable::Reflect(AZ::ReflectContext* context) { + EntityAlias::Reflect(context); + if (auto* serializeContext = azrtti_cast(context); serializeContext != nullptr) { - serializeContext->Class()->Version(1) + serializeContext->Class()->Version(2) ->Field("Meta data", &Spawnable::m_metaData) + ->Field("Entity aliases", &Spawnable::m_entityAliases) ->Field("Entities", &Spawnable::m_entities); } } + + void Spawnable::EntityAlias::Reflect(AZ::ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context); serializeContext != nullptr) + { + serializeContext->Class() + ->Version(1) + ->Field("Spawnable", &EntityAlias::m_spawnable) + ->Field("Tag", &EntityAlias::m_tag) + ->Field("Source Index", &EntityAlias::m_sourceIndex) + ->Field("Target Index", &EntityAlias::m_targetIndex) + ->Field("Alias Type", &EntityAlias::m_aliasType) + ->Field("Queue Load", &EntityAlias::m_queueLoad); + } + } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.h b/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.h index 37c22d503d..f029246847 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/Spawnable.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -29,7 +30,148 @@ namespace AzFramework AZ_CLASS_ALLOCATOR(Spawnable, AZ::SystemAllocator, 0); AZ_RTTI(AzFramework::Spawnable, "{855E3021-D305-4845-B284-20C3F7FDF16B}", AZ::Data::AssetData); + // The order is important for sorting in the SpawnableAssetHandler. + enum class EntityAliasType : uint8_t + { + Original, //!< The original entity is spawned. + Disable, //!< No entity will be spawned. + Replace, //!< The entity alias is spawned instead of the original. + Additional, //!< The original entity is spawned as well as the alias. The alias will get a new entity id. + Merge //!< The original entity is spawned and the components of the alias are added. The caller is responsible for + //!< maintaining a valid component list. + }; + + enum ShareState : int32_t + { + Read = -1, + NotShared = 0, + ReadWrite = 1 + }; + + //! An entity alias redirects the spawning of an entity to another entity, possibly in another spawnable. + struct EntityAlias + { + AZ_CLASS_ALLOCATOR(EntityAlias, AZ::SystemAllocator, 0); + AZ_TYPE_INFO(AzFramework::Spawnable::EntityAlias, "{C8D0C5BC-1F0B-4572-98C1-73B2CA8C9356}"); + + bool HasLowerIndex(const EntityAlias& other) const; + + AZ::Data::Asset m_spawnable; //!< The spawnable containing the target entity to spawn. + uint32_t m_tag{ 0 }; //!< A unique tag to identify this alias with. + uint32_t m_sourceIndex{ 0 }; //!< The index of the entity in the original spawnable that will be replaced. + uint32_t m_targetIndex{ 0 }; //!< The index of the entity in the target spawnable that will be used to replace the original. + EntityAliasType m_aliasType{ EntityAliasType::Original }; //!< The kind of replacement. + bool m_queueLoad{ false }; //!< Whether or not to automatically queue the spawnable for loading. + + static void Reflect(AZ::ReflectContext* context); + }; + using EntityList = AZStd::vector>; + using EntityAliasList = AZStd::vector; + + private: + class EntityAliasVisitorBase + { + protected: + bool IsValid(const EntityAliasList* aliases) const; + + bool HasAliases(const EntityAliasList* aliases) const; + bool AreAllSpawnablesReady(const EntityAliasList* aliases) const; + + EntityAliasList::const_iterator begin(const EntityAliasList* aliases) const; + EntityAliasList::const_iterator end(const EntityAliasList* aliases) const; + EntityAliasList::const_iterator cbegin(const EntityAliasList* aliases) const; + EntityAliasList::const_iterator cend(const EntityAliasList* aliases) const; + + using ListTargetSpawanblesCallback = AZStd::function& targetSpawnable)>; + void ListTargetSpawnables(const EntityAliasList* aliases, const ListTargetSpawanblesCallback& callback) const; + void ListTargetSpawnables(const EntityAliasList* aliases, AZ::Crc32 tag, const ListTargetSpawanblesCallback& callback) const; + }; + + public: + class EntityAliasVisitor final : public EntityAliasVisitorBase + { + public: + EntityAliasVisitor(Spawnable& owner, EntityAliasList* m_entityAliasList); + ~EntityAliasVisitor(); + + EntityAliasVisitor(EntityAliasVisitor&& rhs); + EntityAliasVisitor& operator=(EntityAliasVisitor&& rhs); + + EntityAliasVisitor(const EntityAliasVisitor& rhs) = delete; + EntityAliasVisitor& operator=(const EntityAliasVisitor& rhs) = delete; + + //! Checks if the visitor was able to retrieve data. This needs to be checked before calling any other functions. + bool IsValid() const; + + bool HasAliases() const; + bool AreAllSpawnablesReady() const; + + // Modification of aliases is limited to specific changes that can only be done through the available modification functions. + // For this reason access through iterators is limited to unmodifiable constant iterators. + + EntityAliasList::const_iterator begin() const; + EntityAliasList::const_iterator end() const; + EntityAliasList::const_iterator cbegin() const; + EntityAliasList::const_iterator cend() const; + + void ListTargetSpawnables(const ListTargetSpawanblesCallback& callback) const; + void ListTargetSpawnables(AZ::Crc32 tag, const ListTargetSpawanblesCallback& callback) const; + + void AddAlias( + AZ::Data::Asset targetSpawnable, + AZ::Crc32 tag, + uint32_t sourceIndex, + uint32_t targetIndex, + Spawnable::EntityAliasType aliasType, + bool queueLoad); + + using ListSpawnablesRequiringLoadCallback = AZStd::function& spawnablePendingLoad)>; + void ListSpawnablesRequiringLoad(const ListSpawnablesRequiringLoadCallback& callback); + + using UpdateCallback = AZStd::function& aliasedSpawnable, + const AZ::Crc32 tag, + const uint32_t sourceIndex, + const uint32_t targetIndex)>; + void UpdateAliases(const UpdateCallback& callback); + void UpdateAliases(AZ::Crc32 tag, const UpdateCallback& callback); + void UpdateAliasType(uint32_t index, Spawnable::EntityAliasType newType); + + void Optimize(); + + private: + Spawnable& m_owner; + EntityAliasList* m_entityAliasList{ nullptr }; + bool m_dirty{ false }; + }; + + class EntityAliasConstVisitor final : public EntityAliasVisitorBase + { + public: + EntityAliasConstVisitor(const Spawnable& owner, const EntityAliasList* entityAliasList); + ~EntityAliasConstVisitor(); + + //! Checks if the visitor was able to retrieve data. This needs to be checked before calling any other functions. + bool IsValid() const; + + bool HasAliases() const; + bool AreAllSpawnablesReady() const; + + EntityAliasList::const_iterator begin() const; + EntityAliasList::const_iterator end() const; + EntityAliasList::const_iterator cbegin() const; + EntityAliasList::const_iterator cend() const; + + void ListTargetSpawnables(const ListTargetSpawanblesCallback& callback) const; + void ListTargetSpawnables(AZ::Crc32 tag, const ListTargetSpawanblesCallback& callback) const; + + private: + const Spawnable& m_owner; + const EntityAliasList* m_entityAliasList; + }; inline static constexpr const char* FileExtension = "spawnable"; inline static constexpr const char* DotFileExtension = ".spawnable"; @@ -45,6 +187,9 @@ namespace AzFramework const EntityList& GetEntities() const; EntityList& GetEntities(); + EntityAliasConstVisitor TryGetAliasesConst() const; + EntityAliasConstVisitor TryGetAliases() const; + EntityAliasVisitor TryGetAliases(); bool IsEmpty() const; SpawnableMetaData& GetMetaData(); @@ -55,11 +200,12 @@ namespace AzFramework private: SpawnableMetaData m_metaData; + // Aliases that optionally replace the ones stored in this spawnable. + EntityAliasList m_entityAliases; // Container for keeping all entities of the prefab the Spawnable was created from. // Includes both direct and nested entities of the prefab. EntityList m_entities; + + mutable AZStd::atomic m_shareState{ ShareState::NotShared }; }; - - using SpawnableList = AZStd::vector; - } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetBus.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetBus.h new file mode 100644 index 0000000000..d3d7bfabb7 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetBus.h @@ -0,0 +1,38 @@ +/* + * 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 +#include +#include + +namespace AzFramework +{ + class SpawnableAssetEvents : public AZ::EBusTraits + { + public: + using MutexType = AZStd::recursive_mutex; + + //! Callback to allow the entity aliases in a spawnable to adjusted based on runtime requirements. + //! This will be called by the Asset Manager as part of the creation of the spawnable asset from loaded file data. Any work done + //! in this callback will be counted towards the maximum amount of time allocated to asset handlers to construct their assets, + //! it's recommended to keep work done in this callback to a minimum and prefer delaying any complex processing. + //! + //! ALERT: Do not start blocking asset requests in this callback. + //! Since this is part of the Asset Manager's asset streaming, doing a blocking load in this callback will cause the job + //! processing the spawnable asset to locked out of doing any asset streaming work. If there are more spawnables doing + //! this than there are job threads available the engine will enter a deadlock situation as no more assets can complete + //! loading and no job threads become free as they're all waiting for assets to complete. It is however safe to queue + //! an asset for loading. + virtual void OnResolveAliases( + Spawnable::EntityAliasVisitor& aliases, const SpawnableMetaData& metadata, const Spawnable::EntityList& entities) = 0; + }; + + using SpawnableAssetEventsBus = AZ::EBus; +} // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetHandler.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetHandler.cpp index c24b538de7..411ee56687 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetHandler.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetHandler.cpp @@ -9,8 +9,10 @@ #include #include #include +#include #include #include +#include namespace AzFramework { @@ -52,6 +54,7 @@ namespace AzFramework AZ::ObjectStream::FilterDescriptor filter(assetLoadFilterCB); if (AZ::Utils::LoadObjectFromStreamInPlace(*stream, *spawnable, nullptr /*SerializeContext*/, filter)) { + ResolveEntityAliases(spawnable, asset, stream->GetStreamingDeadline(), stream->GetStreamingPriority(), assetLoadFilterCB); return AZ::Data::AssetHandler::LoadResult::LoadComplete; } else @@ -91,4 +94,41 @@ namespace AzFramework AZ::Uuid subIdHash = AZ::Uuid::CreateData(id.data(), id.size()); return azlossy_caster(subIdHash.GetHash()); } + + void SpawnableAssetHandler::ResolveEntityAliases( + Spawnable* spawnable, + [[maybe_unused]] const AZ::Data::Asset& asset, + AZStd::chrono::milliseconds streamingDeadline, + AZ::IO::IStreamerTypes::Priority streamingPriority, + const AZ::Data::AssetFilterCB& assetLoadFilterCB) + { + Spawnable::EntityAliasVisitor aliases = spawnable->TryGetAliases(); + AZ_Assert(aliases.IsValid(), "Newly created Spawnable '%s' was already locked.", asset.GetHint().c_str()); + if (aliases.HasAliases()) + { + AZ_Assert( + AZStd::is_sorted( + aliases.begin(), aliases.end(), + [](const Spawnable::EntityAlias& lhs, const Spawnable::EntityAlias& rhs) + { + return lhs.HasLowerIndex(rhs); + }), + "Spawnable '%s' has an unsorted entity alias list.", asset.GetHint().c_str()); + + SpawnableAssetEventsBus::Broadcast( + &SpawnableAssetEvents::OnResolveAliases, aliases, spawnable->GetMetaData(), spawnable->GetEntities()); + + // The aliases will only be optimized if OnResolveAliases has made any changes. + aliases.Optimize(); + aliases.ListSpawnablesRequiringLoad( + [&assetLoadFilterCB, streamingDeadline, streamingPriority](AZ::Data::Asset& assetPendingLoad) + { + AZ::Data::AssetLoadParameters loadInfo; + loadInfo.m_assetLoadFilterCB = assetLoadFilterCB; + loadInfo.m_deadline = streamingDeadline; + loadInfo.m_priority = streamingPriority; + assetPendingLoad.QueueLoad(loadInfo); + }); + } + } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetHandler.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetHandler.h index 94ec9b13fd..e043019e29 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetHandler.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableAssetHandler.h @@ -50,5 +50,13 @@ namespace AzFramework const AZ::Data::Asset& asset, AZStd::shared_ptr stream, const AZ::Data::AssetFilterCB& assetLoadFilterCB) override; + + private: + void ResolveEntityAliases( + class Spawnable* spawnable, + const AZ::Data::Asset& asset, + AZStd::chrono::milliseconds streamingDeadline, + AZ::IO::IStreamerTypes::Priority streamingPriority, + const AZ::Data::AssetFilterCB& assetLoadFilterCB); }; } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.cpp index b98ea275e4..912bf05058 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.cpp @@ -26,7 +26,7 @@ namespace AzFramework return m_threadData != nullptr; } - uint64_t SpawnableEntitiesContainer::GetCurrentGeneration() const + uint32_t SpawnableEntitiesContainer::GetCurrentGeneration() const { return m_currentGeneration; } @@ -37,7 +37,7 @@ namespace AzFramework SpawnableEntitiesInterface::Get()->SpawnAllEntities(m_threadData->m_spawnedEntitiesTicket); } - void SpawnableEntitiesContainer::SpawnEntities(AZStd::vector entityIndices) + void SpawnableEntitiesContainer::SpawnEntities(AZStd::vector entityIndices) { AZ_Assert(m_threadData, "Calling SpawnEntities on a Spawnable container that's not set."); SpawnableEntitiesInterface::Get()->SpawnEntities( @@ -78,15 +78,21 @@ namespace AzFramework } } - void SpawnableEntitiesContainer::Alert(AlertCallback callback) + void SpawnableEntitiesContainer::Alert(AlertCallback callback, CheckIfSpawnableIsLoaded spawnableCheck) { AZ_Assert(m_threadData, "Calling DespawnEntities on a Spawnable container that's not set."); - SpawnableEntitiesInterface::Get()->Barrier( - m_threadData->m_spawnedEntitiesTicket, - [generation = m_threadData->m_generation, callback = AZStd::move(callback)](EntitySpawnTicket::Id) - { - callback(generation); - }); + auto callbackWrapper = [generation = m_threadData->m_generation, callback = AZStd::move(callback)](EntitySpawnTicket::Id) + { + callback(generation); + }; + if (spawnableCheck == CheckIfSpawnableIsLoaded::No) + { + SpawnableEntitiesInterface::Get()->Barrier(m_threadData->m_spawnedEntitiesTicket, AZStd::move(callbackWrapper)); + } + else + { + SpawnableEntitiesInterface::Get()->LoadBarrier(m_threadData->m_spawnedEntitiesTicket, AZStd::move(callbackWrapper)); + } } void SpawnableEntitiesContainer::Connect(AZ::Data::Asset spawnable) diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.h index 6fa295e18c..cabef38ff5 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesContainer.h @@ -36,6 +36,12 @@ namespace AzFramework public: using AlertCallback = AZStd::function; + enum class CheckIfSpawnableIsLoaded : bool + { + Yes, + No + }; + //! Constructs a new spawnables entity container that has not been connected. SpawnableEntitiesContainer() = default; //! Constructs a new spawnables entity container that connects to the provided spawnable. @@ -48,13 +54,13 @@ namespace AzFramework //! Returns a number that identifies the current generation of the container with. The completion callback can still receive //! calls from older generations as processing completes on those. The returned value can be used to help calls tell //! older versions apart from newer ones. - [[nodiscard]] uint64_t GetCurrentGeneration() const; + [[nodiscard]] uint32_t GetCurrentGeneration() const; //! Puts in a request to spawn entities using all entities in the provided spawnable as a template. void SpawnAllEntities(); //! Puts in a request to spawn entities using the entities found in the spawnable at the provided indices as a template. //! @param entityIndices A list of indices to the entities in the spawnable. - void SpawnEntities(AZStd::vector entityIndices); + void SpawnEntities(AZStd::vector entityIndices); //! Puts in a request to despawn all previous spawned entities. void DespawnAllEntities(); @@ -73,7 +79,11 @@ namespace AzFramework //! other than the calling thread including the main thread. Note that because the alert is queued it can still be called //! after the container has been deleted or can be called for a previously assigned spawnable. In the latter case check //! if the current generation matches the generation provided with the callback. - void Alert(AlertCallback callback); + //! @param callback The function called when the alert triggers. This can be called from a different thread than the one that + //! the one that made the call to Alert. + //! @param checkSpawnableIsLoaded If true the alert will also block until the spawnable has been loaded. If false then it will + //! be called after all previous calls have completed, but the spawnable may not be loaded at that point. + void Alert(AlertCallback callback, CheckIfSpawnableIsLoaded spawnableCheck = CheckIfSpawnableIsLoaded::No); private: void Connect(AZ::Data::Asset spawnable); diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp index 171d626b27..37091d8f0f 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.cpp @@ -152,7 +152,7 @@ namespace AzFramework // SpawnableIndexEntityPair // - SpawnableIndexEntityPair::SpawnableIndexEntityPair(AZ::Entity** entityIterator, size_t* indexIterator) + SpawnableIndexEntityPair::SpawnableIndexEntityPair(AZ::Entity** entityIterator, uint32_t* indexIterator) : m_entity(entityIterator) , m_index(indexIterator) { @@ -168,7 +168,7 @@ namespace AzFramework return *m_entity; } - size_t SpawnableIndexEntityPair::GetIndex() const + uint32_t SpawnableIndexEntityPair::GetIndex() const { return *m_index; } @@ -177,7 +177,7 @@ namespace AzFramework // SpawnableIndexEntityIterator // - SpawnableIndexEntityIterator::SpawnableIndexEntityIterator(AZ::Entity** entityIterator, size_t* indexIterator) + SpawnableIndexEntityIterator::SpawnableIndexEntityIterator(AZ::Entity** entityIterator, uint32_t* indexIterator) : m_value(entityIterator, indexIterator) { } @@ -248,7 +248,7 @@ namespace AzFramework // SpawnableConstIndexEntityContainerView::SpawnableConstIndexEntityContainerView( - AZ::Entity** beginEntity, size_t* beginIndices, size_t length) + AZ::Entity** beginEntity, uint32_t* beginIndices, size_t length) : m_begin(beginEntity, beginIndices) , m_end(beginEntity + length, beginIndices + length) { diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h index 74a17020df..dc9c7b4538 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesInterface.h @@ -85,19 +85,19 @@ namespace AzFramework AZ::Entity* GetEntity(); const AZ::Entity* GetEntity() const; - size_t GetIndex() const; + uint32_t GetIndex() const; private: SpawnableIndexEntityPair() = default; SpawnableIndexEntityPair(const SpawnableIndexEntityPair&) = default; SpawnableIndexEntityPair(SpawnableIndexEntityPair&&) = default; - SpawnableIndexEntityPair(AZ::Entity** entityIterator, size_t* indexIterator); + SpawnableIndexEntityPair(AZ::Entity** entityIterator, uint32_t* indexIterator); SpawnableIndexEntityPair& operator=(const SpawnableIndexEntityPair&) = default; SpawnableIndexEntityPair& operator=(SpawnableIndexEntityPair&&) = default; AZ::Entity** m_entity { nullptr }; - size_t* m_index { nullptr }; + uint32_t* m_index { nullptr }; }; class SpawnableIndexEntityIterator @@ -110,7 +110,7 @@ namespace AzFramework using pointer = SpawnableIndexEntityPair*; using reference = SpawnableIndexEntityPair&; - SpawnableIndexEntityIterator(AZ::Entity** entityIterator, size_t* indexIterator); + SpawnableIndexEntityIterator(AZ::Entity** entityIterator, uint32_t* indexIterator); SpawnableIndexEntityIterator& operator++(); SpawnableIndexEntityIterator operator++(int); @@ -132,7 +132,7 @@ namespace AzFramework class SpawnableConstIndexEntityContainerView { public: - SpawnableConstIndexEntityContainerView(AZ::Entity** beginEntity, size_t* beginIndices, size_t length); + SpawnableConstIndexEntityContainerView(AZ::Entity** beginEntity, uint32_t* beginIndices, size_t length); const SpawnableIndexEntityIterator& begin(); const SpawnableIndexEntityIterator& end(); @@ -144,6 +144,16 @@ namespace AzFramework SpawnableIndexEntityIterator m_end; }; + //! Information used when updating the type of an entity alias. + struct EntityAliasTypeChange + { + //! The index of the alias in the spawnable. Note that due to optimizations done on the entity aliases the index of an alias + //! can change over time. + uint32_t m_aliasIndex; + //! The type to replace type stored in the spawnable at the index provided by m_aliasIndex. + Spawnable::EntityAliasType m_newAliasType; + }; + //! Requests to the SpawnableEntitiesInterface require a ticket with a valid spawnable that is used as a template. A ticket can //! be reused for multiple calls on the same spawnable and is safe to be used by multiple threads at the same time. Entities created //! from the spawnable may be tracked by the ticket and so using the same ticket is needed to despawn the exact entities created @@ -178,6 +188,7 @@ namespace AzFramework using EntityDespawnCallback = AZStd::function; using RetrieveEntitySpawnTicketCallback = AZStd::function; using ReloadSpawnableCallback = AZStd::function; + using UpdateEntityAliasTypesCallback = AZStd::function; using ListEntitiesCallback = AZStd::function; using ListIndicesEntitiesCallback = AZStd::function; using ClaimEntitiesCallback = AZStd::function; @@ -247,6 +258,15 @@ namespace AzFramework SpawnablePriority m_priority { SpawnablePriority_Default }; }; + struct UpdateEntityAliasTypesOptionalArgs final + { + //! Callback that's called when entity aliases are updated. This can be triggered from a different thread than the one that + //! made the function call to update. + UpdateEntityAliasTypesCallback m_completionCallback; + //! The priority at which this call will be executed. + SpawnablePriority m_priority{ SpawnablePriority_Default }; + }; + struct ListEntitiesOptionalArgs final { //! The priority at which this call will be executed. @@ -265,6 +285,14 @@ namespace AzFramework SpawnablePriority m_priority{ SpawnablePriority_Default }; }; + struct LoadBarrierOptionalArgs final + { + //! The priority at which this call will be executed. + SpawnablePriority m_priority{ SpawnablePriority_Default }; + //! Also checks if the spawnables referenced in the entity aliases that are marked to be loaded are loaded. + bool m_checkAliasSpawnables{ true }; + }; + //! Interface definition to (de)spawn entities from a spawnable into the game world. //! //! While the callbacks of the individual calls are being processed they will block processing any other request. Callbacks can be @@ -298,7 +326,7 @@ namespace AzFramework //! @param entityIndices The indices into the template entities stored in the spawnable that will be used to spawn entities from. //! @param optionalArgs Optional additional arguments, see SpawnEntitiesOptionalArgs. virtual void SpawnEntities( - EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) = 0; + EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) = 0; //! Removes all entities in the provided list from the environment. //! @param ticket The ticket previously used to spawn entities with. //! @param optionalArgs Optional additional arguments, see DespawnAllEntitiesOptionalArgs. @@ -320,6 +348,16 @@ namespace AzFramework virtual void ReloadSpawnable( EntitySpawnTicket& ticket, AZ::Data::Asset spawnable, ReloadSpawnableOptionalArgs optionalArgs = {}) = 0; + //! Allows updating the entity alias on a spawnable. This allows the spawning behavior for all entities spawned from the used + //! spawnable to be changed and is not restricted to this ticket alone. + //! @param ticket Holds the information for the spawnable. + //! @param updateAliases An array of index and alias type values used to update the entity alias list. + //! @param optionalArgs Optional additional arguments, see UpdateEntityAliasTypesOptionalArgs. + virtual void UpdateEntityAliasTypes( + EntitySpawnTicket& ticket, + AZStd::vector updatedAliases, + UpdateEntityAliasTypesOptionalArgs optionalArgs = {}) = 0; + //! List all entities that are spawned using this ticket. //! @param ticket Only the entities associated with this ticket will be listed. //! @param listCallback Required callback that will be called to list the entities on. @@ -351,31 +389,37 @@ namespace AzFramework //! @param completionCallback Required callback that will be called as soon as the barrier has been reached. //! @param optionalArgs Optional additional arguments, see BarrierOptionalArgs. virtual void Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) = 0; + //! Blocks until the spawnable is loaded and all operations made on the provided ticket before the barrier call have completed. + //! @param ticket The ticket to monitor. + //! @param completionCallback Required callback that will be called as soon as the barrier has been reached. + //! @param optionalArgs Optional additional arguments, see BarrierOptionalArgs. + virtual void LoadBarrier( + EntitySpawnTicket& ticket, BarrierCallback completionCallback, LoadBarrierOptionalArgs optionalArgs = {}) = 0; protected: [[nodiscard]] virtual AZStd::pair CreateTicket(AZ::Data::Asset&& spawnable) = 0; virtual void DestroyTicket(void* ticket) = 0; template - static T& GetTicketPayload(EntitySpawnTicket& ticket) + [[nodiscard]] static T& GetTicketPayload(EntitySpawnTicket& ticket) { return *reinterpret_cast(ticket.m_payload); } template - static const T& GetTicketPayload(const EntitySpawnTicket& ticket) + [[nodiscard]] static const T& GetTicketPayload(const EntitySpawnTicket& ticket) { return *reinterpret_cast(ticket.m_payload); } template - static T* GetTicketPayload(EntitySpawnTicket* ticket) + [[nodiscard]] static T* GetTicketPayload(EntitySpawnTicket* ticket) { return reinterpret_cast(ticket->m_payload); } template - static const T* GetTicketPayload(const EntitySpawnTicket* ticket) + [[nodiscard]] static const T* GetTicketPayload(const EntitySpawnTicket* ticket) { return reinterpret_cast(ticket->m_payload); } diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp index ef7351aabb..37570caec7 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.cpp @@ -60,7 +60,7 @@ namespace AzFramework } void SpawnableEntitiesManager::SpawnEntities( - EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs) + EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs) { AZ_Assert(ticket.IsValid(), "Ticket provided to SpawnEntities hasn't been initialized."); @@ -128,6 +128,20 @@ namespace AzFramework QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } + void SpawnableEntitiesManager::UpdateEntityAliasTypes( + EntitySpawnTicket& ticket, + AZStd::vector updatedAliases, + UpdateEntityAliasTypesOptionalArgs optionalArgs) + { + AZ_Assert(ticket.IsValid(), "Ticket provided to ReloadSpawnable hasn't been initialized."); + + UpdateEntityAliasTypesCommand queueEntry; + queueEntry.m_entityAliases = AZStd::move(updatedAliases); + queueEntry.m_ticketId = ticket.GetId(); + queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback); + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + } + void SpawnableEntitiesManager::ListEntities( EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs) { @@ -175,6 +189,19 @@ namespace AzFramework QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); } + void SpawnableEntitiesManager::LoadBarrier( + EntitySpawnTicket& ticket, BarrierCallback completionCallback, LoadBarrierOptionalArgs optionalArgs) + { + AZ_Assert(completionCallback, "Load barrier on spawnable entities called without a valid callback to use."); + AZ_Assert(ticket.IsValid(), "Ticket provided to LoadBarrier hasn't been initialized."); + + LoadBarrierCommand queueEntry; + queueEntry.m_ticketId = ticket.GetId(); + queueEntry.m_completionCallback = AZStd::move(completionCallback); + queueEntry.m_checkAliasSpawnables = optionalArgs.m_checkAliasSpawnables; + QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry)); + } + auto SpawnableEntitiesManager::ProcessQueue(CommandQueuePriority priority) -> CommandQueueStatus { CommandQueueStatus result = CommandQueueStatus::NoCommandsLeft; @@ -203,13 +230,13 @@ namespace AzFramework for (size_t i = 0; i < delayedSize; ++i) { Requests& request = queue.m_delayed.front(); - bool result = AZStd::visit( - [this](auto&& args) -> bool + CommandResult result = AZStd::visit( + [this](auto&& args) -> CommandResult { return ProcessRequest(args); }, request); - if (!result) + if (result == CommandResult::Requeue) { queue.m_delayed.emplace_back(AZStd::move(request)); } @@ -230,13 +257,13 @@ namespace AzFramework while (!pendingRequestQueue.empty()) { Requests& request = pendingRequestQueue.front(); - bool result = AZStd::visit( - [this](auto&& args) -> bool + CommandResult result = AZStd::visit( + [this](auto&& args) -> CommandResult { return ProcessRequest(args); }, request); - if (!result) + if (result == CommandResult::Requeue) { queue.m_delayed.emplace_back(AZStd::move(request)); } @@ -273,14 +300,73 @@ namespace AzFramework } } - AZ::Entity* SpawnableEntitiesManager::CloneSingleEntity(const AZ::Entity& entityTemplate, - EntityIdMap& templateToCloneMap, AZ::SerializeContext& serializeContext) + AZ::Entity* SpawnableEntitiesManager::CloneSingleEntity(const AZ::Entity& entityPrototype, + EntityIdMap& prototypeToCloneMap, AZ::SerializeContext& serializeContext) { // If the same ID gets remapped more than once, preserve the original remapping instead of overwriting it. constexpr bool allowDuplicateIds = false; return AZ::IdUtils::Remapper::CloneObjectAndGenerateNewIdsAndFixRefs( - &entityTemplate, templateToCloneMap, &serializeContext); + &entityPrototype, prototypeToCloneMap, &serializeContext); + } + + AZ::Entity* SpawnableEntitiesManager::CloneSingleAliasedEntity( + const AZ::Entity& entityPrototype, + const Spawnable::EntityAlias& alias, + EntityIdMap& prototypeToCloneMap, + AZ::Entity* previouslySpawnedEntity, + AZ::SerializeContext& serializeContext) + { + AZ::Entity* clone = nullptr; + switch (alias.m_aliasType) + { + case Spawnable::EntityAliasType::Original: + // Behave as the original version. + clone = CloneSingleEntity(entityPrototype, prototypeToCloneMap, serializeContext); + AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + return clone; + case Spawnable::EntityAliasType::Disable: + // Do nothing. + return nullptr; + case Spawnable::EntityAliasType::Replace: + clone = CloneSingleEntity(*(alias.m_spawnable->GetEntities()[alias.m_targetIndex]), prototypeToCloneMap, serializeContext); + AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + return clone; + case Spawnable::EntityAliasType::Additional: + // The asset handler will have sorted and inserted a Spawnable::EntityAliasType::Original, so the just + // spawn the additional entity. + clone = CloneSingleEntity(*(alias.m_spawnable->GetEntities()[alias.m_targetIndex]), prototypeToCloneMap, serializeContext); + AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + return clone; + case Spawnable::EntityAliasType::Merge: + AZ_Assert(previouslySpawnedEntity != nullptr, "Merging components but there's no entity to add to yet."); + AppendComponents( + *previouslySpawnedEntity, alias.m_spawnable->GetEntities()[alias.m_targetIndex]->GetComponents(), prototypeToCloneMap, + serializeContext); + return nullptr; + default: + AZ_Assert(false, "Unsupported spawnable entity alias type: %i", alias.m_aliasType); + return nullptr; + } + } + + void SpawnableEntitiesManager::AppendComponents( + AZ::Entity& target, + const AZ::Entity::ComponentArrayType& componentPrototypes, + EntityIdMap& prototypeToCloneMap, + AZ::SerializeContext& serializeContext) + { + // Only components are added and entities are looked up so no duplicate entity ids should be encountered. + constexpr bool allowDuplicateIds = false; + + for (const AZ::Component* component : componentPrototypes) + { + AZ::Component* clone = AZ::IdUtils::Remapper::CloneObjectAndGenerateNewIdsAndFixRefs( + component, prototypeToCloneMap, &serializeContext); + AZ_Assert(clone, "Unable to clone component for entity '%s' (%zu).", target.GetName().c_str(), target.GetId()); + [[maybe_unused]] bool result = target.AddComponent(clone); + AZ_Assert(result, "Unable to add cloned component to entity '%s' (%zu).", target.GetName().c_str(), target.GetId()); + } } void SpawnableEntitiesManager::InitializeEntityIdMappings( @@ -316,161 +402,264 @@ namespace AzFramework } } - - bool SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request) + auto SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request) -> CommandResult { Ticket& ticket = *request.m_ticket; if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId) { - AZStd::vector& spawnedEntities = ticket.m_spawnedEntities; - AZStd::vector& spawnedEntityIndices = ticket.m_spawnedEntityIndices; - - // Keep track how many entities there were in the array initially - size_t spawnedEntitiesInitialCount = spawnedEntities.size(); - - // These are 'template' entities we'll be cloning from - const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities(); - size_t entitiesToSpawnSize = entitiesToSpawn.size(); - - // Reserve buffers - spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize); - spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize); - - // Pre-generate the full set of entity id to new entity id mappings, so that during the clone operation below, - // any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly. - // We clear out and regenerate the set of IDs on every SpawnAllEntities call, because presumably every entity reference - // in every entity we're about to instantiate is intended to point to an entity in our newly-instantiated batch, regardless - // of spawn order. If we didn't clear out the map, it would be possible for some entities here to have references to - // previously-spawned entities from a previous SpawnEntities or SpawnAllEntities call. - InitializeEntityIdMappings(entitiesToSpawn, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); - - for (size_t i = 0; i < entitiesToSpawnSize; ++i) + if (Spawnable::EntityAliasConstVisitor aliases = ticket.m_spawnable->TryGetAliasesConst(); + aliases.IsValid() && aliases.AreAllSpawnablesReady()) { - // If this entity has previously been spawned, give it a new id in the reference map - RefreshEntityIdMapping(entitiesToSpawn[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); + AZStd::vector& spawnedEntities = ticket.m_spawnedEntities; + AZStd::vector& spawnedEntityIndices = ticket.m_spawnedEntityIndices; - AZ::Entity* clone = CloneSingleEntity(*entitiesToSpawn[i], ticket.m_entityIdReferenceMap, *request.m_serializeContext); - AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); + // Keep track how many entities there were in the array initially + size_t spawnedEntitiesInitialCount = spawnedEntities.size(); - spawnedEntities.emplace_back(clone); - spawnedEntityIndices.push_back(i); - } + // These are 'prototype' entities we'll be cloning from + const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities(); + uint32_t entitiesToSpawnSize = aznumeric_caster(entitiesToSpawn.size()); - // loadAll is true if every entity has been spawned only once - ticket.m_loadAll = (spawnedEntities.size() == entitiesToSpawnSize); - - // Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context. - if (request.m_preInsertionCallback) - { - request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView( - ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); - } + // Reserve buffers + spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize); + spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize); - // Add to the game context, now the entities are active - for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) - { - (*it)->SetSpawnTicketId(request.m_ticketId); - GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); - } - - // Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context. - if (request.m_completionCallback) - { - request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView( - ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); - } - - ticket.m_currentRequestId++; - return true; - } - else - { - return false; - } - } - - bool SpawnableEntitiesManager::ProcessRequest(SpawnEntitiesCommand& request) - { - Ticket& ticket = *request.m_ticket; - if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId) - { - AZStd::vector& spawnedEntities = ticket.m_spawnedEntities; - AZStd::vector& spawnedEntityIndices = ticket.m_spawnedEntityIndices; - AZ_Assert( - spawnedEntities.size() == spawnedEntityIndices.size(), - "The indices for the spawned entities has gone out of sync with the entities."); - - // Keep track of how many entities there were in the array initially - size_t spawnedEntitiesInitialCount = spawnedEntities.size(); - - // These are 'template' entities we'll be cloning from - const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities(); - size_t entitiesToSpawnSize = request.m_entityIndices.size(); - - if (ticket.m_entityIdReferenceMap.empty() || !request.m_referencePreviouslySpawnedEntities) - { - // This map keeps track of ids from template (spawnable) to clone (instance) allowing patch ups of fields referring - // to entityIds outside of a given entity. - // We pre-generate the full set of entity id to new entity id mappings, so that during the clone operation below, + // Pre-generate the full set of entity-id-to-new-entity-id mappings, so that during the clone operation below, // any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly. - // By default, we only initialize this map once because it needs to persist across multiple SpawnEntities calls, so - // that reference fixups work even when the entity being referenced is spawned in a different SpawnEntities - // (or SpawnAllEntities) call. - // However, the caller can also choose to reset the map by passing in "m_referencePreviouslySpawnedEntities = false". + // We clear out and regenerate the set of IDs on every SpawnAllEntities call, because presumably every entity reference + // in every entity we're about to instantiate is intended to point to an entity in our newly-instantiated batch, regardless + // of spawn order. If we didn't clear out the map, it would be possible for some entities here to have references to + // previously-spawned entities from a previous SpawnEntities or SpawnAllEntities call. InitializeEntityIdMappings(entitiesToSpawn, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); - } - spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize); - spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize); - - for (size_t index : request.m_entityIndices) - { - if (index < entitiesToSpawn.size()) + auto aliasIt = aliases.begin(); + auto aliasEnd = aliases.end(); + if (aliasIt == aliasEnd) { - // If this entity has previously been spawned, give it a new id in the reference map - RefreshEntityIdMapping( - entitiesToSpawn[index].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); + for (uint32_t i = 0; i < entitiesToSpawnSize; ++i) + { + // If this entity has previously been spawned, give it a new id in the reference map + RefreshEntityIdMapping( + entitiesToSpawn[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); - AZ::Entity* clone = - CloneSingleEntity(*entitiesToSpawn[index], ticket.m_entityIdReferenceMap, *request.m_serializeContext); - AZ_Assert(clone != nullptr, "Failed to clone spawnable entity."); - - spawnedEntities.push_back(clone); - spawnedEntityIndices.push_back(index); + spawnedEntities.emplace_back( + CloneSingleEntity(*entitiesToSpawn[i], ticket.m_entityIdReferenceMap, *request.m_serializeContext)); + spawnedEntityIndices.push_back(i); + } } - } - ticket.m_loadAll = false; + else + { + for (uint32_t i = 0; i < entitiesToSpawnSize; ++i) + { + // If this entity has previously been spawned, give it a new id in the reference map + RefreshEntityIdMapping( + entitiesToSpawn[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); - // Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context. - if (request.m_preInsertionCallback) - { - request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView( - ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); - } + if (aliasIt == aliasEnd || aliasIt->m_sourceIndex != i) + { + spawnedEntities.emplace_back( + CloneSingleEntity(*entitiesToSpawn[i], ticket.m_entityIdReferenceMap, *request.m_serializeContext)); + spawnedEntityIndices.push_back(i); + } + else + { + // The list of entities has already been sorted and optimized (See SpawnableEntitiesAliasList:Optimize) so can + // be safely executed in order without risking an invalid state. + AZ::Entity* previousEntity = nullptr; + do + { + AZ::Entity* clone = CloneSingleAliasedEntity( + *entitiesToSpawn[i], *aliasIt, ticket.m_entityIdReferenceMap, previousEntity, + *request.m_serializeContext); + previousEntity = clone; + if (clone) + { + spawnedEntities.emplace_back(clone); + spawnedEntityIndices.push_back(i); + } + ++aliasIt; + } while (aliasIt != aliasEnd && aliasIt->m_sourceIndex == i); + } + } + } - // Add to the game context, now the entities are active - for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) - { - (*it)->SetSpawnTicketId(request.m_ticketId); - GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); - } + // There were no initial entities then the ticket now holds exactly all entities. If there were already entities then + // a new set are not added so it no longer holds exactly the number of entities. + ticket.m_loadAll = spawnedEntitiesInitialCount == 0; - if (request.m_completionCallback) - { - request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView( - ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); - } + auto newEntitiesBegin = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; + auto newEntitiesEnd = ticket.m_spawnedEntities.end(); + // Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context. + if (request.m_preInsertionCallback) + { + request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView(newEntitiesBegin, newEntitiesEnd)); + } - ticket.m_currentRequestId++; - return true; - } - else - { - return false; + // Add to the game context, now the entities are active + for (auto it = newEntitiesBegin; it != newEntitiesEnd; ++it) + { + AZ::Entity* clone = (*it); + // The entity component framework doesn't handle entities without TransformComponent safely. + if (!clone->GetComponents().empty()) + { + clone->SetSpawnTicketId(request.m_ticketId); + GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); + } + } + + // Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context. + if (request.m_completionCallback) + { + request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(newEntitiesBegin, newEntitiesEnd)); + } + + ticket.m_currentRequestId++; + return CommandResult::Executed; + } } + return CommandResult::Requeue; } - bool SpawnableEntitiesManager::ProcessRequest(DespawnAllEntitiesCommand& request) + auto SpawnableEntitiesManager::ProcessRequest(SpawnEntitiesCommand& request) -> CommandResult + { + Ticket& ticket = *request.m_ticket; + if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId) + { + if (Spawnable::EntityAliasConstVisitor aliases = ticket.m_spawnable->TryGetAliasesConst(); + aliases.IsValid() && aliases.AreAllSpawnablesReady()) + { + AZStd::vector& spawnedEntities = ticket.m_spawnedEntities; + AZStd::vector& spawnedEntityIndices = ticket.m_spawnedEntityIndices; + AZ_Assert( + spawnedEntities.size() == spawnedEntityIndices.size(), + "The indices for the spawned entities has gone out of sync with the entities."); + + // Keep track of how many entities there were in the array initially + size_t spawnedEntitiesInitialCount = spawnedEntities.size(); + + // These are 'prototype' entities we'll be cloning from + const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities(); + size_t entitiesToSpawnSize = request.m_entityIndices.size(); + + if (ticket.m_entityIdReferenceMap.empty() || !request.m_referencePreviouslySpawnedEntities) + { + // This map keeps track of ids from prototype (spawnable) to clone (instance) allowing patch ups of fields referring + // to entityIds outside of a given entity. + // We pre-generate the full set of entity id to new entity id mappings, so that during the clone operation below, + // any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly. + // By default, we only initialize this map once because it needs to persist across multiple SpawnEntities calls, so + // that reference fixups work even when the entity being referenced is spawned in a different SpawnEntities + // (or SpawnAllEntities) call. + // However, the caller can also choose to reset the map by passing in "m_referencePreviouslySpawnedEntities = false". + InitializeEntityIdMappings(entitiesToSpawn, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); + } + + spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize); + spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize); + + auto aliasBegin = aliases.begin(); + auto aliasEnd = aliases.end(); + if (aliasBegin == aliasEnd) + { + for (uint32_t index : request.m_entityIndices) + { + if (index < entitiesToSpawn.size()) + { + // If this entity has previously been spawned, give it a new id in the reference map + RefreshEntityIdMapping( + entitiesToSpawn[index].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); + + spawnedEntities.push_back( + CloneSingleEntity(*entitiesToSpawn[index], ticket.m_entityIdReferenceMap, *request.m_serializeContext)); + spawnedEntityIndices.push_back(index); + } + } + } + else + { + for (uint32_t index : request.m_entityIndices) + { + if (index < entitiesToSpawn.size()) + { + // If this entity has previously been spawned, give it a new id in the reference map + RefreshEntityIdMapping( + entitiesToSpawn[index].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); + + auto aliasIt = AZStd::lower_bound( + aliasBegin, aliasEnd, index, + [](const Spawnable::EntityAlias& lhs, uint32_t rhs) + { + return lhs.m_sourceIndex < rhs; + }); + + if (aliasIt == aliasEnd || aliasIt->m_sourceIndex != index) + { + spawnedEntities.emplace_back( + CloneSingleEntity(*entitiesToSpawn[index], ticket.m_entityIdReferenceMap, *request.m_serializeContext)); + spawnedEntityIndices.push_back(index); + } + else + { + // The list of entities has already been sorted and optimized (See SpawnableEntitiesAliasList:Optimize) so + // can be safely executed in order without risking an invalid state. + AZ::Entity* previousEntity = nullptr; + do + { + AZ::Entity* clone = CloneSingleAliasedEntity( + *entitiesToSpawn[index], *aliasIt, ticket.m_entityIdReferenceMap, previousEntity, + *request.m_serializeContext); + previousEntity = clone; + if (clone) + { + spawnedEntities.emplace_back(clone); + spawnedEntityIndices.push_back(index); + } + + ++aliasIt; + } while (aliasIt != aliasEnd && aliasIt->m_sourceIndex == index); + } + } + } + } + ticket.m_loadAll = false; + + // Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context. + if (request.m_preInsertionCallback) + { + request.m_preInsertionCallback( + request.m_ticketId, + SpawnableEntityContainerView( + ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); + } + + // Add to the game context, now the entities are active + for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it) + { + AZ::Entity* clone = (*it); + // The entity component framework doesn't handle entities without TransformComponent safely. + if (!clone->GetComponents().empty()) + { + clone->SetSpawnTicketId(request.m_ticketId); + GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it); + } + } + + if (request.m_completionCallback) + { + request.m_completionCallback( + request.m_ticketId, + SpawnableConstEntityContainerView( + ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end())); + } + + ticket.m_currentRequestId++; + return CommandResult::Executed; + } + } + return CommandResult::Requeue; + } + + auto SpawnableEntitiesManager::ProcessRequest(DespawnAllEntitiesCommand& request) -> CommandResult { Ticket& ticket = *request.m_ticket; if (request.m_requestId == ticket.m_currentRequestId) @@ -495,15 +684,15 @@ namespace AzFramework } ticket.m_currentRequestId++; - return true; + return CommandResult::Executed; } else { - return false; + return CommandResult::Requeue; } } - bool SpawnableEntitiesManager::ProcessRequest(DespawnEntityCommand& request) + auto SpawnableEntitiesManager::ProcessRequest(DespawnEntityCommand& request) -> CommandResult { Ticket& ticket = *request.m_ticket; if (request.m_requestId == ticket.m_currentRequestId) @@ -529,15 +718,15 @@ namespace AzFramework } ticket.m_currentRequestId++; - return true; + return CommandResult::Executed; } else { - return false; + return CommandResult::Requeue; } } - bool SpawnableEntitiesManager::ProcessRequest(ReloadSpawnableCommand& request) + auto SpawnableEntitiesManager::ProcessRequest(ReloadSpawnableCommand& request) -> CommandResult { Ticket& ticket = *request.m_ticket; AZ_Assert(ticket.m_spawnable.GetId() == request.m_spawnable.GetId(), @@ -564,7 +753,7 @@ namespace AzFramework // Pre-generate the full set of entity id to new entity id mappings, so that during the clone operation below, // any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly. // This map is intentionally cleared out and regenerated here to ensure that we're starting fresh with mappings that - // match the new set of template entities getting spawned. + // match the new set of prototype entities getting spawned. InitializeEntityIdMappings(entities, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); if (ticket.m_loadAll) @@ -574,7 +763,7 @@ namespace AzFramework ticket.m_spawnedEntityIndices.clear(); size_t entitiesToSpawnSize = entities.size(); - for (size_t i = 0; i < entitiesToSpawnSize; ++i) + for (uint32_t i = 0; i < entitiesToSpawnSize; ++i) { // If this entity has previously been spawned, give it a new id in the reference map RefreshEntityIdMapping(entities[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned); @@ -590,7 +779,7 @@ namespace AzFramework { size_t entitiesSize = entities.size(); - for (size_t index : ticket.m_spawnedEntityIndices) + for (uint32_t index : ticket.m_spawnedEntityIndices) { // It's possible for the new spawnable to have a different number of entities, so guard against this. // It's also possible that the entities have moved within the spawnable to a new index. This can't be @@ -616,15 +805,40 @@ namespace AzFramework ticket.m_currentRequestId++; - return true; + return CommandResult::Executed; } else { - return false; + return CommandResult::Requeue; } } - bool SpawnableEntitiesManager::ProcessRequest(ListEntitiesCommand& request) + auto SpawnableEntitiesManager::ProcessRequest(UpdateEntityAliasTypesCommand& request) -> CommandResult + { + Ticket& ticket = *request.m_ticket; + if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId) + { + if (Spawnable::EntityAliasVisitor aliases = ticket.m_spawnable->TryGetAliases(); aliases.IsValid()) + { + for (EntityAliasTypeChange& replacement : request.m_entityAliases) + { + aliases.UpdateAliasType(replacement.m_aliasIndex, replacement.m_newAliasType); + } + aliases.Optimize(); + + if (request.m_completionCallback) + { + request.m_completionCallback(request.m_ticketId); + } + + ticket.m_currentRequestId++; + return CommandResult::Executed; + } + } + return CommandResult::Requeue; + } + + auto SpawnableEntitiesManager::ProcessRequest(ListEntitiesCommand& request) -> CommandResult { Ticket& ticket = *request.m_ticket; if (request.m_requestId == ticket.m_currentRequestId) @@ -632,15 +846,15 @@ namespace AzFramework request.m_listCallback(request.m_ticketId, SpawnableConstEntityContainerView( ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end())); ticket.m_currentRequestId++; - return true; + return CommandResult::Executed; } else { - return false; + return CommandResult::Requeue; } } - bool SpawnableEntitiesManager::ProcessRequest(ListIndicesEntitiesCommand& request) + auto SpawnableEntitiesManager::ProcessRequest(ListIndicesEntitiesCommand& request) -> CommandResult { Ticket& ticket = *request.m_ticket; if (request.m_requestId == ticket.m_currentRequestId) @@ -651,15 +865,15 @@ namespace AzFramework request.m_listCallback(request.m_ticketId, SpawnableConstIndexEntityContainerView( ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntityIndices.begin(), ticket.m_spawnedEntities.size())); ticket.m_currentRequestId++; - return true; + return CommandResult::Executed; } else { - return false; + return CommandResult::Requeue; } } - bool SpawnableEntitiesManager::ProcessRequest(ClaimEntitiesCommand& request) + auto SpawnableEntitiesManager::ProcessRequest(ClaimEntitiesCommand& request) -> CommandResult { Ticket& ticket = *request.m_ticket; if (request.m_requestId == ticket.m_currentRequestId) @@ -671,15 +885,15 @@ namespace AzFramework ticket.m_spawnedEntityIndices.clear(); ticket.m_currentRequestId++; - return true; + return CommandResult::Executed; } else { - return false; + return CommandResult::Requeue; } } - bool SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request) + auto SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request) -> CommandResult { Ticket& ticket = *request.m_ticket; if (request.m_requestId == ticket.m_currentRequestId) @@ -690,15 +904,39 @@ namespace AzFramework } ticket.m_currentRequestId++; - return true; + return CommandResult::Executed; } else { - return false; + return CommandResult::Requeue; } } - bool SpawnableEntitiesManager::ProcessRequest(DestroyTicketCommand& request) + auto SpawnableEntitiesManager::ProcessRequest(LoadBarrierCommand& request) -> CommandResult + { + Ticket& ticket = *request.m_ticket; + if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId) + { + if (request.m_checkAliasSpawnables) + { + if (Spawnable::EntityAliasConstVisitor visitor = ticket.m_spawnable->TryGetAliasesConst(); + !visitor.IsValid() || !visitor.AreAllSpawnablesReady()) + { + return CommandResult::Requeue; + } + } + + request.m_completionCallback(request.m_ticketId); + ticket.m_currentRequestId++; + return CommandResult::Executed; + } + else + { + return CommandResult::Requeue; + } + } + + auto SpawnableEntitiesManager::ProcessRequest(DestroyTicketCommand& request) -> CommandResult { if (request.m_requestId == request.m_ticket->m_currentRequestId) { @@ -706,19 +944,24 @@ namespace AzFramework { if (entity != nullptr) { - // Setting it to 0 is needed to avoid the infite loop between GameEntityContext and SpawnableEntitiesManager. + // Setting it to 0 is needed to avoid the infinite loop between GameEntityContext and SpawnableEntitiesManager. entity->SetSpawnTicketId(0); GameEntityContextRequestBus::Broadcast( &GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId()); } + else + { + // Entities without components wouldn't have been send to the GameEntityContext. + delete entity; + } } delete request.m_ticket; - return true; + return CommandResult::Executed; } else { - return false; + return CommandResult::Requeue; } } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h index c3de5be003..d2b5c3c9af 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableEntitiesManager.h @@ -55,13 +55,18 @@ namespace AzFramework void SpawnAllEntities(EntitySpawnTicket& ticket, SpawnAllEntitiesOptionalArgs optionalArgs = {}) override; void SpawnEntities( - EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) override; + EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) override; void DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs = {}) override; void DespawnEntity(AZ::EntityId entityId, EntitySpawnTicket& ticket, DespawnEntityOptionalArgs optionalArgs = {}) override; void RetrieveEntitySpawnTicket(EntitySpawnTicket::Id entitySpawnTicketId, RetrieveEntitySpawnTicketCallback callback) override; void ReloadSpawnable( EntitySpawnTicket& ticket, AZ::Data::Asset spawnable, ReloadSpawnableOptionalArgs optionalArgs = {}) override; + void UpdateEntityAliasTypes( + EntitySpawnTicket& ticket, + AZStd::vector updatedAliases, + UpdateEntityAliasTypesOptionalArgs optionalArgs = {}) override; + void ListEntities( EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs = {}) override; void ListIndicesAndEntities( @@ -70,6 +75,8 @@ namespace AzFramework EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs = {}) override; void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) override; + void LoadBarrier( + EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback, LoadBarrierOptionalArgs optionalArgs = {}) override; // // The following function is thread safe but intended to be run from the main thread. @@ -78,14 +85,20 @@ namespace AzFramework CommandQueueStatus ProcessQueue(CommandQueuePriority priority); protected: - struct Ticket + enum class CommandResult : bool + { + Executed, + Requeue + }; + + struct Ticket final { AZ_CLASS_ALLOCATOR(Ticket, AZ::ThreadPoolAllocator, 0); static constexpr uint32_t Processing = AZStd::numeric_limits::max(); - //! Map of template entity ids to their associated instance ids. - //! Tickets can be used to spawn the same template entities multiple times, in any order, across multiple calls. - //! Since template entities can reference other entities, this map is used to fix up those references across calls + //! Map of prototype entity ids to their associated instance ids. + //! Tickets can be used to spawn the same prototype entities multiple times, in any order, across multiple calls. + //! Since prototype entities can reference other entities, this map is used to fix up those references across calls //! using the following policy: //! - Entities referencing an entity that hasn't been spawned yet will get a reference to the id that *will* be used //! the first time that entity will be spawned. The reference will be invalid until that entity is spawned, but @@ -100,14 +113,14 @@ namespace AzFramework AZStd::unordered_set m_previouslySpawned; AZStd::vector m_spawnedEntities; - AZStd::vector m_spawnedEntityIndices; + AZStd::vector m_spawnedEntityIndices; AZ::Data::Asset m_spawnable; uint32_t m_nextRequestId{ 0 }; //!< Next id for this ticket. uint32_t m_currentRequestId { 0 }; //!< The id for the command that should be executed. bool m_loadAll{ true }; }; - struct SpawnAllEntitiesCommand + struct SpawnAllEntitiesCommand final { EntitySpawnCallback m_completionCallback; EntityPreInsertionCallback m_preInsertionCallback; @@ -116,9 +129,9 @@ namespace AzFramework EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; - struct SpawnEntitiesCommand + struct SpawnEntitiesCommand final { - AZStd::vector m_entityIndices; + AZStd::vector m_entityIndices; EntitySpawnCallback m_completionCallback; EntityPreInsertionCallback m_preInsertionCallback; AZ::SerializeContext* m_serializeContext; @@ -127,7 +140,7 @@ namespace AzFramework uint32_t m_requestId; bool m_referencePreviouslySpawnedEntities; }; - struct DespawnAllEntitiesCommand + struct DespawnAllEntitiesCommand final { EntityDespawnCallback m_completionCallback; Ticket* m_ticket; @@ -142,7 +155,7 @@ namespace AzFramework EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; - struct ReloadSpawnableCommand + struct ReloadSpawnableCommand final { AZ::Data::Asset m_spawnable; ReloadSpawnableCallback m_completionCallback; @@ -151,35 +164,51 @@ namespace AzFramework EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; - struct ListEntitiesCommand + struct UpdateEntityAliasTypesCommand final + { + AZStd::vector m_entityAliases; + UpdateEntityAliasTypesCallback m_completionCallback; + Ticket* m_ticket; + EntitySpawnTicket::Id m_ticketId; + uint32_t m_requestId; + }; + struct ListEntitiesCommand final { ListEntitiesCallback m_listCallback; Ticket* m_ticket; EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; - struct ListIndicesEntitiesCommand + struct ListIndicesEntitiesCommand final { ListIndicesEntitiesCallback m_listCallback; Ticket* m_ticket; EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; - struct ClaimEntitiesCommand + struct ClaimEntitiesCommand final { ClaimEntitiesCallback m_listCallback; Ticket* m_ticket; EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; - struct BarrierCommand + struct BarrierCommand final { BarrierCallback m_completionCallback; Ticket* m_ticket; EntitySpawnTicket::Id m_ticketId; uint32_t m_requestId; }; - struct DestroyTicketCommand + struct LoadBarrierCommand final + { + BarrierCallback m_completionCallback; + Ticket* m_ticket; + EntitySpawnTicket::Id m_ticketId; + uint32_t m_requestId; + bool m_checkAliasSpawnables; + }; + struct DestroyTicketCommand final { Ticket* m_ticket; uint32_t m_requestId; @@ -191,10 +220,12 @@ namespace AzFramework DespawnAllEntitiesCommand, DespawnEntityCommand, ReloadSpawnableCommand, + UpdateEntityAliasTypesCommand, ListEntitiesCommand, ListIndicesEntitiesCommand, ClaimEntitiesCommand, BarrierCommand, + LoadBarrierCommand, DestroyTicketCommand>; struct Queue @@ -212,18 +243,31 @@ namespace AzFramework CommandQueueStatus ProcessQueue(Queue& queue); AZ::Entity* CloneSingleEntity( - const AZ::Entity& entityTemplate, EntityIdMap& templateToCloneMap, AZ::SerializeContext& serializeContext); + const AZ::Entity& entityPrototype, EntityIdMap& prototypeToCloneMap, AZ::SerializeContext& serializeContext); + AZ::Entity* CloneSingleAliasedEntity( + const AZ::Entity& entityPrototype, + const Spawnable::EntityAlias& alias, + EntityIdMap& prototypeToCloneMap, + AZ::Entity* previouslySpawnedEntity, + AZ::SerializeContext& serializeContext); + void AppendComponents( + AZ::Entity& target, + const AZ::Entity::ComponentArrayType& componentPrototypes, + EntityIdMap& prototypeToCloneMap, + AZ::SerializeContext& serializeContext); - bool ProcessRequest(SpawnAllEntitiesCommand& request); - bool ProcessRequest(SpawnEntitiesCommand& request); - bool ProcessRequest(DespawnAllEntitiesCommand& request); - bool ProcessRequest(DespawnEntityCommand& request); - bool ProcessRequest(ReloadSpawnableCommand& request); - bool ProcessRequest(ListEntitiesCommand& request); - bool ProcessRequest(ListIndicesEntitiesCommand& request); - bool ProcessRequest(ClaimEntitiesCommand& request); - bool ProcessRequest(BarrierCommand& request); - bool ProcessRequest(DestroyTicketCommand& request); + CommandResult ProcessRequest(SpawnAllEntitiesCommand& request); + CommandResult ProcessRequest(SpawnEntitiesCommand& request); + CommandResult ProcessRequest(DespawnAllEntitiesCommand& request); + CommandResult ProcessRequest(DespawnEntityCommand& request); + CommandResult ProcessRequest(ReloadSpawnableCommand& request); + CommandResult ProcessRequest(UpdateEntityAliasTypesCommand& request); + CommandResult ProcessRequest(ListEntitiesCommand& request); + CommandResult ProcessRequest(ListIndicesEntitiesCommand& request); + CommandResult ProcessRequest(ClaimEntitiesCommand& request); + CommandResult ProcessRequest(BarrierCommand& request); + CommandResult ProcessRequest(LoadBarrierCommand& request); + CommandResult ProcessRequest(DestroyTicketCommand& request); //! Generate a base set of original-to-new entity ID mappings to use during spawning. //! Since Entity references get fixed up on an entity-by-entity basis while spawning, it's important to have the complete diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.cpp b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.cpp index 957786c6df..6ca2f3a53a 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.cpp +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.cpp @@ -72,7 +72,7 @@ namespace AzFramework uint64_t SpawnableSystemComponent::AssignRootSpawnable(AZ::Data::Asset rootSpawnable) { - uint64_t generation = 0; + uint32_t generation = 0; if (m_rootSpawnableId == rootSpawnable.GetId()) { @@ -87,16 +87,25 @@ namespace AzFramework // Suspend and resume processing in the container that completion calls aren't received until // everything has been setup to accept callbacks from the call. m_rootSpawnableContainer.Reset(rootSpawnable); - m_rootSpawnableContainer.SpawnAllEntities(); generation = m_rootSpawnableContainer.GetCurrentGeneration(); - AZ_TracePrintf("Spawnables", "Root spawnable set to '%s' at generation %zu.\n", rootSpawnable.GetHint().c_str(), - generation); + + // Don't send out the alert that the root spawnable has been assigned until the spawnable itself is ready. The common + // use case is for handlers to do something with the information in the spawnable before the entities get spawned. + m_rootSpawnableContainer.Alert( + [rootSpawnable](uint32_t generation) + { + RootSpawnableNotificationBus::Broadcast( + &RootSpawnableNotificationBus::Events::OnRootSpawnableAssigned, AZStd::move(rootSpawnable), generation); + }, SpawnableEntitiesContainer::CheckIfSpawnableIsLoaded::Yes); + m_rootSpawnableContainer.SpawnAllEntities(); m_rootSpawnableContainer.Alert( [newSpawnable = AZStd::move(rootSpawnable)](uint32_t generation) { RootSpawnableNotificationBus::QueueBroadcast( - &RootSpawnableNotificationBus::Events::OnRootSpawnableAssigned, newSpawnable, generation); + &RootSpawnableNotificationBus::Events::OnRootSpawnableReady, AZStd::move(newSpawnable), generation); }); + + AZ_TracePrintf("Spawnables", "Root spawnable set to '%s' at generation %zu.\n", rootSpawnable.GetHint().c_str(), generation); } else { @@ -132,6 +141,12 @@ namespace AzFramework AZ_TracePrintf("Spawnables", "New root spawnable '%s' assigned (generation: %i).\n", rootSpawnable.GetHint().c_str(), generation); } + void SpawnableSystemComponent::OnRootSpawnableReady( + [[maybe_unused]] AZ::Data::Asset rootSpawnable, [[maybe_unused]] uint32_t generation) + { + AZ_TracePrintf("Spawnables", "Entities from new root spawnable '%s' are ready (generation: %i).\n", rootSpawnable.GetHint().c_str(), generation); + } + void SpawnableSystemComponent::OnRootSpawnableReleased([[maybe_unused]] uint32_t generation) { AZ_TracePrintf("Spawnables", "Generation %i of the root spawnable has been released.\n", generation); diff --git a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h index 74e255d624..ecd2a9b728 100644 --- a/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h +++ b/Code/Framework/AzFramework/AzFramework/Spawnable/SpawnableSystemComponent.h @@ -82,6 +82,7 @@ namespace AzFramework // void OnRootSpawnableAssigned(AZ::Data::Asset rootSpawnable, uint32_t generation) override; + void OnRootSpawnableReady(AZ::Data::Asset rootSpawnable, uint32_t generation) override; void OnRootSpawnableReleased(uint32_t generation) override; protected: diff --git a/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.cpp b/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.cpp index 561408db20..d0926008c5 100644 --- a/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.cpp +++ b/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.cpp @@ -12,29 +12,95 @@ namespace AzFramework::Terrain { + // Create a handler that can be accessed from Python scripts to receive terrain change notifications. + class TerrainDataNotificationHandler final + : public AzFramework::Terrain::TerrainDataNotificationBus::Handler + , public AZ::BehaviorEBusHandler + { + public: + AZ_EBUS_BEHAVIOR_BINDER( + TerrainDataNotificationHandler, + "{A83EF103-295A-4653-8279-F30FBF3F9037}", + AZ::SystemAllocator, + OnTerrainDataCreateBegin, + OnTerrainDataCreateEnd, + OnTerrainDataDestroyBegin, + OnTerrainDataDestroyEnd, + OnTerrainDataChanged); + + void OnTerrainDataCreateBegin() override + { + Call(FN_OnTerrainDataCreateBegin); + } + + void OnTerrainDataCreateEnd() override + { + Call(FN_OnTerrainDataCreateEnd); + } + + void OnTerrainDataDestroyBegin() override + { + Call(FN_OnTerrainDataDestroyBegin); + } + + void OnTerrainDataDestroyEnd() override + { + Call(FN_OnTerrainDataDestroyEnd); + } + + void OnTerrainDataChanged( + const AZ::Aabb& dirtyRegion, AzFramework::Terrain::TerrainDataNotifications::TerrainDataChangedMask dataChangedMask) override + { + Call(FN_OnTerrainDataChanged, dirtyRegion, dataChangedMask); + } + }; + void TerrainDataRequests::Reflect(AZ::ReflectContext* context) { if (AZ::BehaviorContext* behaviorContext = azrtti_cast(context)) { behaviorContext->EBus("TerrainDataRequestBus") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) ->Attribute(AZ::Script::Attributes::Category, "Terrain") - ->Event("GetHeight", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetHeight) - ->Event("GetNormal", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetNormal) - ->Event("GetMaxSurfaceWeight", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetMaxSurfaceWeight) - ->Event("GetMaxSurfaceWeightFromVector2", - &AzFramework::Terrain::TerrainDataRequestBus::Events::GetMaxSurfaceWeightFromVector2) - ->Event("GetSurfaceWeights", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetSurfaceWeights) - ->Event("GetSurfaceWeightsFromVector2", - &AzFramework::Terrain::TerrainDataRequestBus::Events::GetSurfaceWeightsFromVector2) + ->Attribute(AZ::Script::Attributes::Module, "terrain") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Event("GetHeight", &AzFramework::Terrain::TerrainDataRequestBus::Events::BehaviorContextGetHeight) + ->Event("GetHeightFromFloats", &AzFramework::Terrain::TerrainDataRequestBus::Events::BehaviorContextGetHeightFromFloats) + ->Event("GetHeightFromVector2", &AzFramework::Terrain::TerrainDataRequestBus::Events::BehaviorContextGetHeightFromVector2) + ->Event("GetNormal", &AzFramework::Terrain::TerrainDataRequestBus::Events::BehaviorContextGetNormal) + ->Event("GetMaxSurfaceWeight", &AzFramework::Terrain::TerrainDataRequestBus::Events::BehaviorContextGetMaxSurfaceWeight) + ->Event( + "GetMaxSurfaceWeightFromVector2", + &AzFramework::Terrain::TerrainDataRequestBus::Events::BehaviorContextGetMaxSurfaceWeightFromVector2) + ->Event("GetSurfaceWeights", &AzFramework::Terrain::TerrainDataRequestBus::Events::BehaviorContextGetSurfaceWeights) + ->Event( + "GetSurfaceWeightsFromVector2", + &AzFramework::Terrain::TerrainDataRequestBus::Events::BehaviorContextGetSurfaceWeightsFromVector2) + ->Event("GetIsHole", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetIsHole) ->Event("GetIsHoleFromFloats", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetIsHoleFromFloats) - ->Event("GetSurfacePoint", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetSurfacePoint) - ->Event("GetSurfacePointFromVector2", - &AzFramework::Terrain::TerrainDataRequestBus::Events::GetSurfacePointFromVector2) + ->Event("GetSurfacePoint", &AzFramework::Terrain::TerrainDataRequestBus::Events::BehaviorContextGetSurfacePoint) + ->Event( + "GetSurfacePointFromVector2", + &AzFramework::Terrain::TerrainDataRequestBus::Events::BehaviorContextGetSurfacePointFromVector2) ->Event("GetTerrainAabb", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetTerrainAabb) - ->Event("GetTerrainHeightQueryResolution", + ->Event( + "GetTerrainHeightQueryResolution", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetTerrainHeightQueryResolution) - ; + ; + + behaviorContext->EBus("TerrainDataNotificationBus") + ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) + ->Attribute(AZ::Script::Attributes::Category, "Terrain") + ->Attribute(AZ::Script::Attributes::Module, "terrain") + ->Event("OnTerrainDataCreateBegin", &AzFramework::Terrain::TerrainDataNotifications::OnTerrainDataCreateBegin) + ->Event("OnTerrainDataCreateEnd", &AzFramework::Terrain::TerrainDataNotifications::OnTerrainDataCreateEnd) + ->Event("OnTerrainDataDestroyBegin", &AzFramework::Terrain::TerrainDataNotifications::OnTerrainDataDestroyBegin) + ->Event("OnTerrainDataDestroyEnd", &AzFramework::Terrain::TerrainDataNotifications::OnTerrainDataDestroyEnd) + ->Event("OnTerrainDataChanged", &AzFramework::Terrain::TerrainDataNotifications::OnTerrainDataChanged) + ->Handler() + ; } + //TerrainDataNotificationHandler::Reflect(context); } } // namespace AzFramework::Terrain diff --git a/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.h b/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.h index 30a2f8e044..2572a07494 100644 --- a/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.h +++ b/Code/Framework/AzFramework/AzFramework/Terrain/TerrainDataRequestBus.h @@ -52,8 +52,8 @@ namespace AzFramework virtual void SetTerrainAabb(const AZ::Aabb& worldBounds) = 0; //! Returns terrains height in meters at location x,y. - //! @terrainExistsPtr: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside a terrain HOLE then *terrainExistsPtr will become false, - //! otherwise *terrainExistsPtr will become true. + //! @terrainExistsPtr: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside + //! a terrain HOLE then *terrainExistsPtr will become false, otherwise *terrainExistsPtr will become true. virtual float GetHeight(const AZ::Vector3& position, Sampler sampler = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0; virtual float GetHeightFromVector2( const AZ::Vector2& position, Sampler sampler = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0; @@ -68,8 +68,7 @@ namespace AzFramework // Given an XY coordinate, return the surface normal. //! @terrainExists: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside a - //! terrain HOLE then *terrainExistsPtr will be set to false, - //! otherwise *terrainExistsPtr will be set to true. + //! terrain HOLE then *terrainExistsPtr will be set to false, otherwise *terrainExistsPtr will be set to true. virtual AZ::Vector3 GetNormal( const AZ::Vector3& position, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0; virtual AZ::Vector3 GetNormalFromVector2( @@ -78,8 +77,8 @@ namespace AzFramework float x, float y, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0; //! Given an XY coordinate, return the max surface type and weight. - //! @terrainExists: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside a terrain HOLE then *terrainExistsPtr will be set to false, - //! otherwise *terrainExistsPtr will be set to true. + //! @terrainExists: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside + //! a terrain HOLE then *terrainExistsPtr will be set to false, otherwise *terrainExistsPtr will be set to true. virtual SurfaceData::SurfaceTagWeight GetMaxSurfaceWeight( const AZ::Vector3& position, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0; virtual SurfaceData::SurfaceTagWeight GetMaxSurfaceWeightFromVector2( @@ -87,8 +86,8 @@ namespace AzFramework virtual SurfaceData::SurfaceTagWeight GetMaxSurfaceWeightFromFloats( float x, float y, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0; - //! Given an XY coordinate, return the set of surface types and weights. The Vector3 input position version is defined to ignore - //! the input Z value. + //! Given an XY coordinate, return the set of surface types and weights. The Vector3 input position version is defined to + //! ignore the input Z value. virtual void GetSurfaceWeights( const AZ::Vector3& inPosition, SurfaceData::SurfaceTagWeightList& outSurfaceWeights, @@ -106,13 +105,14 @@ namespace AzFramework Sampler sampleFilter = Sampler::DEFAULT, bool* terrainExistsPtr = nullptr) const = 0; - //! Convenience function for low level systems that can't do a reverse lookup from Crc to string. Everyone else should use GetMaxSurfaceWeight or GetMaxSurfaceWeightFromFloats. + //! Convenience function for low level systems that can't do a reverse lookup from Crc to string. Everyone else should use + //! GetMaxSurfaceWeight or GetMaxSurfaceWeightFromFloats. //! Not available in the behavior context. //! Returns nullptr if the position is inside a hole or outside of the terrain boundaries. virtual const char* GetMaxSurfaceName( const AZ::Vector3& position, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0; - //! Given an XY coordinate, return all terrain information at that location. The Vector3 input position version is defined + //! Given an XY coordinate, return all terrain information at that location. The Vector3 input position version is defined //! to ignore the input Z value. virtual void GetSurfacePoint( const AZ::Vector3& inPosition, @@ -130,6 +130,70 @@ namespace AzFramework SurfaceData::SurfacePoint& outSurfacePoint, Sampler sampleFilter = Sampler::DEFAULT, bool* terrainExistsPtr = nullptr) const = 0; + + private: + // Private variations of the GetSurfacePoint API exposed to BehaviorContext that returns a value instead of + // using an "out" parameter. The "out" parameter is useful for reusing memory allocated in SurfacePoint when + // using the public API, but can't easily be used from Script Canvas. + SurfaceData::SurfacePoint BehaviorContextGetSurfacePoint( + const AZ::Vector3& inPosition, + Sampler sampleFilter = Sampler::DEFAULT) const + { + SurfaceData::SurfacePoint result; + GetSurfacePoint(inPosition, result, sampleFilter); + return result; + } + SurfaceData::SurfacePoint BehaviorContextGetSurfacePointFromVector2( + const AZ::Vector2& inPosition, Sampler sampleFilter = Sampler::DEFAULT) const + { + SurfaceData::SurfacePoint result; + GetSurfacePointFromVector2(inPosition, result, sampleFilter); + return result; + } + // Private variations of the GetHeight.., GetNormal..., GetMaxSurfaceWeight..., GetSurfaceWeights... APIs + // exposed to BehaviorContext that does not use the terrainExists "out" parameter. + float BehaviorContextGetHeight(const AZ::Vector3& position, Sampler sampler = Sampler::BILINEAR) + { + return GetHeight(position, sampler, nullptr); + } + float BehaviorContextGetHeightFromVector2(const AZ::Vector2& position, Sampler sampler = Sampler::BILINEAR) + { + return GetHeightFromVector2(position, sampler, nullptr); + } + float BehaviorContextGetHeightFromFloats(float x, float y, Sampler sampler = Sampler::BILINEAR) + { + return GetHeightFromFloats(x, y, sampler, nullptr); + } + AZ::Vector3 BehaviorContextGetNormal(const AZ::Vector3& position, Sampler sampleFilter = Sampler::BILINEAR) + { + return GetNormal(position, sampleFilter, nullptr); + } + SurfaceData::SurfaceTagWeight BehaviorContextGetMaxSurfaceWeight( + const AZ::Vector3& position, Sampler sampleFilter = Sampler::BILINEAR) + { + return GetMaxSurfaceWeight(position, sampleFilter, nullptr); + } + SurfaceData::SurfaceTagWeight BehaviorContextGetMaxSurfaceWeightFromVector2( + const AZ::Vector2& inPosition, Sampler sampleFilter = Sampler::DEFAULT) + { + return GetMaxSurfaceWeightFromVector2(inPosition, sampleFilter, nullptr); + } + SurfaceData::SurfaceTagWeightList BehaviorContextGetSurfaceWeights( + const AZ::Vector3& inPosition, + Sampler sampleFilter = Sampler::DEFAULT) + { + SurfaceData::SurfaceTagWeightList list; + GetSurfaceWeights(inPosition, list, sampleFilter, nullptr); + return list; + } + SurfaceData::SurfaceTagWeightList BehaviorContextGetSurfaceWeightsFromVector2( + const AZ::Vector2& inPosition, + Sampler sampleFilter = Sampler::DEFAULT) + { + SurfaceData::SurfaceTagWeightList list; + GetSurfaceWeightsFromVector2(inPosition, list, sampleFilter, nullptr); + return list; + } }; using TerrainDataRequestBus = AZ::EBus; diff --git a/Code/Framework/AzFramework/AzFramework/UnitTest/TestDebugDisplayRequests.h b/Code/Framework/AzFramework/AzFramework/UnitTest/TestDebugDisplayRequests.h index 72b15c1a69..b71d10c751 100644 --- a/Code/Framework/AzFramework/AzFramework/UnitTest/TestDebugDisplayRequests.h +++ b/Code/Framework/AzFramework/AzFramework/UnitTest/TestDebugDisplayRequests.h @@ -13,6 +13,13 @@ namespace UnitTest { + //! Null implementation of DebugDisplayRequests for dummy draw calls. + class NullDebugDisplayRequests : public AzFramework::DebugDisplayRequests + { + public: + virtual ~NullDebugDisplayRequests() = default; + }; + //! Minimal implementation of DebugDisplayRequests to support testing shapes. //! Stores a list of points based on received draw calls to delineate the exterior of the object requested to be drawn. class TestDebugDisplayRequests : public AzFramework::DebugDisplayRequests diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp index 771884ac6a..856d40e57d 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp @@ -335,10 +335,13 @@ namespace AzFramework Camera nextCamera = targetCamera; const float rotateSpeed = m_rotateSpeedFn(); - nextCamera.m_pitch -= float(cursorDelta.m_y) * rotateSpeed * Invert(m_invertPitchFn()); - nextCamera.m_yaw -= float(cursorDelta.m_x) * rotateSpeed * Invert(m_invertYawFn()); + const float deltaPitch = aznumeric_cast(cursorDelta.m_y) * rotateSpeed * Invert(m_invertPitchFn()); + const float deltaYaw = aznumeric_cast(cursorDelta.m_x) * rotateSpeed * Invert(m_invertYawFn()); + nextCamera.m_pitch -= deltaPitch; + nextCamera.m_yaw -= deltaYaw; nextCamera.m_yaw = WrapYawRotation(nextCamera.m_yaw); + if (m_constrainPitch()) { nextCamera.m_pitch = ClampPitchRotation(nextCamera.m_pitch); diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h index 4eea94cbe7..96a9b54dc8 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h @@ -25,6 +25,9 @@ namespace AzFramework struct WindowSize; + //! Tolerance to use when limiting pitch to avoid reaching +/-Pi/2 exactly. + constexpr float CameraPitchTolerance = 1.0e-4f; + //! Returns Euler angles (pitch, roll, yaw) for the incoming orientation. //! @note Order of rotation is Z, Y, X. AZ::Vector3 EulerAngles(const AZ::Matrix3x3& orientation); @@ -318,11 +321,26 @@ namespace AzFramework return m_handlingEvents; } - //! Clamps pitch to be +/-90 degrees (-Pi/2, Pi/2). + //! Returns min/max values for camera pitch (in radians). + inline AZStd::tuple CameraPitchMinMaxRadians() + { + return { -AZ::Constants::HalfPi, AZ::Constants::HalfPi }; + } + + //! Returns min/max values for camera pitch (in radians) including a small tolerance at each + //! extreme (looking directly up or down) to avoid floating point accuracy issues. + inline AZStd::tuple CameraPitchMinMaxRadiansWithTolerance() + { + const auto [pitchMinRadians, pitchMaxRadians] = CameraPitchMinMaxRadians(); + return { pitchMinRadians + CameraPitchTolerance, pitchMaxRadians - CameraPitchTolerance }; + } + + //! Clamps pitch to be +/-90 degrees (-Pi/2, Pi/2) with a minor tolerance at each extreme. //! @param pitch Pitch angle in radians. inline float ClampPitchRotation(const float pitch) { - return AZ::GetClamp(pitch, -AZ::Constants::HalfPi, AZ::Constants::HalfPi); + const auto [pitchMin, pitchMax] = CameraPitchMinMaxRadiansWithTolerance(); + return AZ::GetClamp(pitch, pitchMin, pitchMax); } //! Ensures yaw wraps between 0 and 360 degrees (0, 2Pi). diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportBus.h b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportBus.h index 131ecc0c57..f53b84a63f 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportBus.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportBus.h @@ -15,6 +15,7 @@ namespace AZ { class Matrix4x4; + class Matrix3x4; class Transform; class ReflectContext; } // namespace AZ @@ -32,6 +33,8 @@ namespace AzFramework //! Gets the current camera's world to view matrix. virtual const AZ::Matrix4x4& GetCameraViewMatrix() const = 0; + //! Gets the current camera's world to view matrix as a Matrix3x4. + virtual AZ::Matrix3x4 GetCameraViewMatrixAsMatrix3x4() const = 0; //! Sets the current camera's world to view matrix. virtual void SetCameraViewMatrix(const AZ::Matrix4x4& matrix) = 0; //! Gets the current camera's projection (view to clip) matrix. diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.cpp index 87eee53fbd..a23b306546 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.cpp @@ -31,34 +31,31 @@ namespace AzFramework // multiplication which must be used (see CameraTransformFromCameraView and CameraViewFromCameraTransform) // note: coordinate system convention is right handed // see Matrix4x4::CreateProjection for more details - static AZ::Matrix4x4 ZYCoordinateSystemConversion() + static AZ::Matrix3x4 ZYCoordinateSystemConversion() { // note: the below matrix is the result of these combined transformations // pitch = AZ::Matrix4x4::CreateRotationX(AZ::DegToRad(-90.0f)); // yaw = AZ::Matrix4x4::CreateRotationZ(AZ::DegToRad(180.0f)); // conversion = pitch * yaw - return AZ::Matrix4x4::CreateFromColumns( - AZ::Vector4(-1.0f, 0.0f, 0.0f, 0.0f), AZ::Vector4(0.0f, 0.0f, 1.0f, 0.0f), AZ::Vector4(0.0f, 1.0f, 0.0f, 0.0f), - AZ::Vector4(0.0f, 0.0f, 0.0f, 1.0f)); + return AZ::Matrix3x4::CreateFromColumns( + AZ::Vector3(-1.0f, 0.0f, 0.0f), AZ::Vector3(0.0f, 0.0f, 1.0f), AZ::Vector3(0.0f, 1.0f, 0.0f), AZ::Vector3(0.0f, 0.0f, 0.0f)); } - AZ::Matrix4x4 CameraTransform(const CameraState& cameraState) + AZ::Matrix3x4 CameraTransform(const CameraState& cameraState) { - return AZ::Matrix4x4::CreateFromColumns( - AZ::Vector3ToVector4(cameraState.m_side), AZ::Vector3ToVector4(cameraState.m_forward), AZ::Vector3ToVector4(cameraState.m_up), - AZ::Vector3ToVector4(cameraState.m_position, 1.0f)); + return AZ::Matrix3x4::CreateFromColumns(cameraState.m_side, cameraState.m_forward, cameraState.m_up, cameraState.m_position); } - AZ::Matrix4x4 CameraView(const CameraState& cameraState) + AZ::Matrix3x4 CameraView(const CameraState& cameraState) { // ensure the camera is looking down positive z with the x axis pointing left - return ZYCoordinateSystemConversion() * CameraTransform(cameraState).GetInverseTransform(); + return ZYCoordinateSystemConversion() * CameraTransform(cameraState).GetInverseFast(); } - AZ::Matrix4x4 InverseCameraView(const CameraState& cameraState) + AZ::Matrix3x4 InverseCameraView(const CameraState& cameraState) { // ensure the camera is looking down positive z with the x axis pointing left - return CameraView(cameraState).GetInverseTransform(); + return CameraView(cameraState).GetInverseFast(); } AZ::Matrix4x4 CameraProjection(const CameraState& cameraState) @@ -72,14 +69,14 @@ namespace AzFramework return CameraProjection(cameraState).GetInverseFull(); } - AZ::Matrix4x4 CameraTransformFromCameraView(const AZ::Matrix4x4& cameraView) + AZ::Matrix3x4 CameraTransformFromCameraView(const AZ::Matrix3x4& cameraView) { - return (ZYCoordinateSystemConversion() * cameraView).GetInverseTransform(); + return (ZYCoordinateSystemConversion() * cameraView).GetInverseFast(); } - AZ::Matrix4x4 CameraViewFromCameraTransform(const AZ::Matrix4x4& cameraTransform) + AZ::Matrix3x4 CameraViewFromCameraTransform(const AZ::Matrix3x4& cameraTransform) { - return ZYCoordinateSystemConversion() * cameraTransform.GetInverseTransform(); + return ZYCoordinateSystemConversion() * cameraTransform.GetInverseFast(); } AZ::Frustum FrustumFromCameraState(const CameraState& cameraState) @@ -91,16 +88,17 @@ namespace AzFramework { const auto worldFromView = AzFramework::CameraTransform(cameraState); const auto cameraWorldTransform = AZ::Transform::CreateFromMatrix3x3AndTranslation( - AZ::Matrix3x3::CreateFromMatrix4x4(worldFromView), worldFromView.GetTranslation()); + AZ::Matrix3x3::CreateFromMatrix3x4(worldFromView), worldFromView.GetTranslation()); return AZ::ViewFrustumAttributes( cameraWorldTransform, AspectRatio(cameraState.m_viewportSize), cameraState.m_fovOrZoom, cameraState.m_nearClip, cameraState.m_farClip); } - AZ::Vector3 WorldToScreenNdc(const AZ::Vector3& worldPosition, const AZ::Matrix4x4& cameraView, const AZ::Matrix4x4& cameraProjection) + AZ::Vector3 WorldToScreenNdc(const AZ::Vector3& worldPosition, const AZ::Matrix3x4& cameraView, const AZ::Matrix4x4& cameraProjection) { // transform the world space position to clip space - const auto clipSpacePosition = cameraProjection * cameraView * AZ::Vector3ToVector4(worldPosition, 1.0f); + const auto clipSpacePosition = + cameraProjection * AZ::Vector3ToVector4(cameraView.TransformPoint(worldPosition), 1.0f); // transform the clip space position to ndc space (perspective divide) const auto ndcPosition = clipSpacePosition / clipSpacePosition.GetW(); // transform ndc space from <-1,1> to <0, 1> range @@ -109,7 +107,7 @@ namespace AzFramework ScreenPoint WorldToScreen( const AZ::Vector3& worldPosition, - const AZ::Matrix4x4& cameraView, + const AZ::Matrix3x4& cameraView, const AZ::Matrix4x4& cameraProjection, const AZ::Vector2& viewportSize) { @@ -123,7 +121,7 @@ namespace AzFramework } AZ::Vector3 ScreenNdcToWorld( - const AZ::Vector2& normalizedScreenPosition, const AZ::Matrix4x4& inverseCameraView, const AZ::Matrix4x4& inverseCameraProjection) + const AZ::Vector2& normalizedScreenPosition, const AZ::Matrix3x4& inverseCameraView, const AZ::Matrix4x4& inverseCameraProjection) { // convert screen space coordinates from <0, 1> to <-1,1> range const auto ndcPosition = normalizedScreenPosition * 2.0f - AZ::Vector2::CreateOne(); @@ -140,7 +138,7 @@ namespace AzFramework AZ::Vector3 ScreenToWorld( const ScreenPoint& screenPosition, - const AZ::Matrix4x4& inverseCameraView, + const AZ::Matrix3x4& inverseCameraView, const AZ::Matrix4x4& inverseCameraProjection, const AZ::Vector2& viewportSize) { diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.h b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.h index 8a6c0249b2..9cd88e1c0c 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.h @@ -16,6 +16,7 @@ namespace AZ { class Frustum; + class Matrix3x4; class Matrix4x4; struct ViewFrustumAttributes; } // namespace AZ @@ -43,7 +44,7 @@ namespace AzFramework } //! Projects a position in world space to screen space normalized device coordinates for the given camera. - AZ::Vector3 WorldToScreenNdc(const AZ::Vector3& worldPosition, const AZ::Matrix4x4& cameraView, const AZ::Matrix4x4& cameraProjection); + AZ::Vector3 WorldToScreenNdc(const AZ::Vector3& worldPosition, const AZ::Matrix3x4& cameraView, const AZ::Matrix4x4& cameraProjection); //! Projects a position in world space to screen space for the given camera. ScreenPoint WorldToScreen(const AZ::Vector3& worldPosition, const CameraState& cameraState); @@ -52,7 +53,7 @@ namespace AzFramework //! is called many times in a loop. ScreenPoint WorldToScreen( const AZ::Vector3& worldPosition, - const AZ::Matrix4x4& cameraView, + const AZ::Matrix3x4& cameraView, const AZ::Matrix4x4& cameraProjection, const AZ::Vector2& viewportSize); @@ -64,14 +65,14 @@ namespace AzFramework //! is called many times in a loop. AZ::Vector3 ScreenToWorld( const ScreenPoint& screenPosition, - const AZ::Matrix4x4& inverseCameraView, + const AZ::Matrix3x4& inverseCameraView, const AZ::Matrix4x4& inverseCameraProjection, const AZ::Vector2& viewportSize); //! Unprojects a position in screen space normalized device coordinates to world space. //! Note: The position returned will be on the near clip plane of the camera in world space. AZ::Vector3 ScreenNdcToWorld( - const AZ::Vector2& ndcPosition, const AZ::Matrix4x4& inverseCameraView, const AZ::Matrix4x4& inverseCameraProjection); + const AZ::Vector2& ndcPosition, const AZ::Matrix3x4& inverseCameraView, const AZ::Matrix4x4& inverseCameraProjection); //! Returns the camera projection for the current camera state. AZ::Matrix4x4 CameraProjection(const CameraState& cameraState); @@ -81,27 +82,27 @@ namespace AzFramework //! Returns the camera view for the current camera state. //! @note This is the 'v' in the MVP transform going from world space to view space (viewFromWorld). - AZ::Matrix4x4 CameraView(const CameraState& cameraState); + AZ::Matrix3x4 CameraView(const CameraState& cameraState); //! Returns the inverse of the camera view for the current camera state. //! @note This is the same as the CameraTransform but corrected for Z up. - AZ::Matrix4x4 InverseCameraView(const CameraState& cameraState); + AZ::Matrix3x4 InverseCameraView(const CameraState& cameraState); //! Returns the camera transform for the current camera state. //! @note This is the inverse of 'v' in the MVP transform going from view space to world space (worldFromView). - AZ::Matrix4x4 CameraTransform(const CameraState& cameraState); + AZ::Matrix3x4 CameraTransform(const CameraState& cameraState); //! Takes a camera view (the world to camera space transform) and returns the //! corresponding camera transform (the world position and orientation of the camera). //! @note The parameter is the viewFromWorld transform (the 'v' in MVP) going from world space //! to view space. The return value is worldFromView transform going from view space to world space. - AZ::Matrix4x4 CameraTransformFromCameraView(const AZ::Matrix4x4& cameraView); + AZ::Matrix3x4 CameraTransformFromCameraView(const AZ::Matrix3x4& cameraView); //! Takes a camera transform (the world position and orientation of the camera) and //! returns the corresponding camera view (to be used to transform from world to camera space). //! @note The parameter is the worldFromView transform going from view space to world space. The //! return value is viewFromWorld transform (the 'v' in MVP) going from view space to world space. - AZ::Matrix4x4 CameraViewFromCameraTransform(const AZ::Matrix4x4& cameraTransform); + AZ::Matrix3x4 CameraViewFromCameraTransform(const AZ::Matrix3x4& cameraTransform); //! Returns a frustum representing the camera transform and view volume in world space. AZ::Frustum FrustumFromCameraState(const CameraState& cameraState); diff --git a/Code/Framework/AzFramework/AzFramework/Windowing/NativeWindow.cpp b/Code/Framework/AzFramework/AzFramework/Windowing/NativeWindow.cpp index c89d12a8ae..bb3c5e4388 100644 --- a/Code/Framework/AzFramework/AzFramework/Windowing/NativeWindow.cpp +++ b/Code/Framework/AzFramework/AzFramework/Windowing/NativeWindow.cpp @@ -143,6 +143,13 @@ namespace AzFramework return vsync_interval; } + bool NativeWindow::SetSyncInterval(uint32_t newSyncInterval) + { + vsync_interval = newSyncInterval; + return true; + } + + /*static*/ bool NativeWindow::GetFullScreenStateOfDefaultWindow() { NativeWindowHandle defaultWindowHandle = nullptr; diff --git a/Code/Framework/AzFramework/AzFramework/Windowing/NativeWindow.h b/Code/Framework/AzFramework/AzFramework/Windowing/NativeWindow.h index 0eb699475f..9c844034cb 100644 --- a/Code/Framework/AzFramework/AzFramework/Windowing/NativeWindow.h +++ b/Code/Framework/AzFramework/AzFramework/Windowing/NativeWindow.h @@ -132,6 +132,7 @@ namespace AzFramework void ToggleFullScreenState() override; float GetDpiScaleFactor() const override; uint32_t GetSyncInterval() const override; + bool SetSyncInterval(uint32_t newSyncInterval) override; uint32_t GetDisplayRefreshRate() const override; //! Get the full screen state of the default window. diff --git a/Code/Framework/AzFramework/AzFramework/Windowing/WindowBus.h b/Code/Framework/AzFramework/AzFramework/Windowing/WindowBus.h index d3bd0ce82c..faae925580 100644 --- a/Code/Framework/AzFramework/AzFramework/Windowing/WindowBus.h +++ b/Code/Framework/AzFramework/AzFramework/Windowing/WindowBus.h @@ -78,6 +78,10 @@ namespace AzFramework //! Returns the sync interval which tells the drivers the number of v-blanks to synchronize with virtual uint32_t GetSyncInterval() const = 0; + //! Sets the sync interval which tells the drivers the number of v-blanks to synchronize with + //! Returns if the sync interval was succesfully set + virtual bool SetSyncInterval(uint32_t newSyncInterval) = 0; + //! Returns the refresh rate of the main display virtual uint32_t GetDisplayRefreshRate() const = 0; }; diff --git a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake index 13d9003031..aac7bd8f14 100644 --- a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake +++ b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake @@ -21,6 +21,7 @@ set(FILES Archive/ArchiveFindData.cpp Archive/ArchiveFindData.h Archive/ArchiveVars.h + Archive/ArchiveVars.cpp Archive/Codec.h Archive/IArchive.h Archive/INestedArchive.h @@ -78,6 +79,7 @@ set(FILES CommandLine/CommandLine.h CommandLine/CommandRegistrationBus.h Debug/DebugCameraBus.h + feature_options.cmake Viewport/ViewportBus.h Viewport/ViewportBus.cpp Viewport/ViewportColors.h @@ -275,6 +277,8 @@ set(FILES Process/ProcessWatcher.cpp Process/ProcessWatcher.h Process/ProcessCommon_fwd.h + Process/ProcessCommunicatorTracePrinter.cpp + Process/ProcessCommunicatorTracePrinter.h ProjectManager/ProjectManager.h ProjectManager/ProjectManager.cpp Render/GameIntersectorComponent.h @@ -287,6 +291,7 @@ set(FILES Spawnable/RootSpawnableInterface.h Spawnable/Spawnable.cpp Spawnable/Spawnable.h + Spawnable/SpawnableAssetBus.h Spawnable/SpawnableAssetHandler.h Spawnable/SpawnableAssetHandler.cpp Spawnable/SpawnableEntitiesContainer.h diff --git a/Code/Framework/AzFramework/AzFramework/feature_options.cmake b/Code/Framework/AzFramework/AzFramework/feature_options.cmake new file mode 100644 index 0000000000..e10ddf6ba6 --- /dev/null +++ b/Code/Framework/AzFramework/AzFramework/feature_options.cmake @@ -0,0 +1,13 @@ +# +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# +# + +set(LY_ARCHIVE_FILE_SEARCH_MODE "" CACHE STRING "Set the default file search mode to locate non-Pak files within the Archive System\n\ + Valid values are:\n\ + 0 = Search FileSystem first, before searching within mounted Paks (default in debug/profile)\n\ + 1 = Search mounted Paks first, before searching FileSystem\n\ + 2 = Search only mounted Paks (default in release)\n") diff --git a/Code/Framework/AzFramework/CMakeLists.txt b/Code/Framework/AzFramework/CMakeLists.txt index c8eeac5c2d..9f30d23a19 100644 --- a/Code/Framework/AzFramework/CMakeLists.txt +++ b/Code/Framework/AzFramework/CMakeLists.txt @@ -6,6 +6,7 @@ # # +include(AzFramework/feature_options.cmake) ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME}) ly_get_list_relative_pal_filename(common_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/Common) @@ -33,6 +34,14 @@ ly_add_target( 3rdParty::lz4 ) +set(LY_SEARCH_MODE_DEFINE $<$:LY_ARCHIVE_FILE_SEARCH_MODE=${LY_ARCHIVE_FILE_SEARCH_MODE}>) + +ly_add_source_properties( + SOURCES + AzFramework/Archive/ArchiveVars.cpp + PROPERTY COMPILE_DEFINITIONS + VALUES ${LY_SEARCH_MODE_DEFINE}) + if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Tests/Platform/${PAL_PLATFORM_NAME}) @@ -43,6 +52,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) FILES_CMAKE Tests/framework_shared_tests_files.cmake AzFramework/Physics/physics_mock_files.cmake + Tests/terrain_mock_files.cmake INCLUDE_DIRECTORIES PUBLIC Tests diff --git a/Code/Framework/AzFramework/Platform/Android/AzFramework/Archive/ArchiveVars_Android.h b/Code/Framework/AzFramework/Platform/Android/AzFramework/Archive/ArchiveVars_Android.h deleted file mode 100644 index 5a80afe271..0000000000 --- a/Code/Framework/AzFramework/Platform/Android/AzFramework/Archive/ArchiveVars_Android.h +++ /dev/null @@ -1,11 +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 - -#define STREAM_CACHE_DEFAULT 0 diff --git a/Code/Framework/AzFramework/Platform/Android/AzFramework/Archive/ArchiveVars_Platform.h b/Code/Framework/AzFramework/Platform/Android/AzFramework/Archive/ArchiveVars_Platform.h deleted file mode 100644 index 42a2a43006..0000000000 --- a/Code/Framework/AzFramework/Platform/Android/AzFramework/Archive/ArchiveVars_Platform.h +++ /dev/null @@ -1,10 +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 diff --git a/Code/Framework/AzFramework/Platform/Android/AzFramework/Process/ProcessWatcher_Android.cpp b/Code/Framework/AzFramework/Platform/Android/AzFramework/Process/ProcessWatcher_Android.cpp index f87c51bdc5..d637da29fe 100644 --- a/Code/Framework/AzFramework/Platform/Android/AzFramework/Process/ProcessWatcher_Android.cpp +++ b/Code/Framework/AzFramework/Platform/Android/AzFramework/Process/ProcessWatcher_Android.cpp @@ -6,10 +6,10 @@ * */ +#include #include #include - namespace AzFramework { @@ -83,4 +83,23 @@ namespace AzFramework { } + + AZStd::string ProcessLauncher::ProcessLaunchInfo::GetCommandLineParametersAsString() const + { + struct CommandLineParametersVisitor + { + AZStd::string operator()(const AZStd::string& commandLine) const + { + return commandLine; + } + + AZStd::string operator()(const AZStd::vector& commandLineArray) const + { + AZStd::string commandLineResult; + AZ::StringFunc::Join(commandLineResult, commandLineArray.begin(), commandLineArray.end(), " "); + return commandLineResult; + } + }; + return AZStd::visit(CommandLineParametersVisitor{}, m_commandlineParameters); + } } //namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Android/platform_android_files.cmake b/Code/Framework/AzFramework/Platform/Android/platform_android_files.cmake index c220bc1154..9eddc4964a 100644 --- a/Code/Framework/AzFramework/Platform/Android/platform_android_files.cmake +++ b/Code/Framework/AzFramework/Platform/Android/platform_android_files.cmake @@ -27,8 +27,6 @@ set(FILES AzFramework/Input/User/LocalUserId_Platform.h ../Common/Default/AzFramework/Input/User/LocalUserId_Default.h AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard_Android.cpp - AzFramework/Archive/ArchiveVars_Platform.h - AzFramework/Archive/ArchiveVars_Android.h AzFramework/Process/ProcessCommon.h AzFramework/Process/ProcessWatcher_Android.cpp AzFramework/Process/ProcessCommunicator_Android.cpp diff --git a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.h b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.h index 00383abf6c..a6d3029a57 100644 --- a/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.h +++ b/Code/Framework/AzFramework/Platform/Common/Xcb/AzFramework/XcbInputDeviceKeyboard.h @@ -5,6 +5,9 @@ * SPDX-License-Identifier: Apache-2.0 OR MIT * */ +#pragma once + +#pragma once #include #include diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Archive/ArchiveVars_Platform.h b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Archive/ArchiveVars_Platform.h deleted file mode 100644 index 1a0b774b97..0000000000 --- a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Archive/ArchiveVars_Platform.h +++ /dev/null @@ -1,10 +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 diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp index 51a8545443..7c86e00fec 100644 --- a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp +++ b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp @@ -10,12 +10,11 @@ #include #include -#include - #include #include #include #include +#include #include #include @@ -220,36 +219,52 @@ namespace AzFramework // this is so that the callers (which could be numerous) do not have to worry about this and sprinkle ifdefs // all over their code. // We'll convert this to UNIX style command line parameters by counting and eliminating quotes: - - AZStd::vector commandTokens; - - AZStd::string outputString; - bool inQuotes = false; - for (const char currentChar : processLaunchInfo.m_commandlineParameters) - { - if (currentChar == '"') - { - inQuotes = !inQuotes; - } - else if ((currentChar == ' ') && (!inQuotes)) - { - // its a space outside of quotes, so it ends the current parameter - commandTokens.push_back(outputString); - outputString.clear(); - } - else - { - // Its a normal character, or its a space inside quotes - outputString.push_back(currentChar); - } - } - if (!outputString.empty()) + // Struct uses overloaded operator() to quote command line arguments based + // on whether a string or a vector was supplied + struct EscapeCommandArguments { - commandTokens.push_back(outputString); - outputString.clear(); - } - + void operator()(const AZStd::string& commandParameterString) + { + AZStd::string outputString; + bool inQuotes = false; + for (size_t pos = 0; pos < commandParameterString.size(); ++pos) + { + char currentChar = commandParameterString[pos]; + if (currentChar == '"') + { + inQuotes = !inQuotes; + } + else if ((currentChar == ' ') && (!inQuotes)) + { + // its a space outside of quotes, so it ends the current parameter + commandArray.push_back(outputString); + outputString.clear(); + } + else + { + // Its a normal character, or its a space inside quotes + outputString.push_back(currentChar); + } + } + + if (!outputString.empty()) + { + commandArray.push_back(outputString); + outputString.clear(); + } + } + + void operator()(const AZStd::vector& commandParameterArray) + { + commandArray = commandParameterArray; + } + AZStd::vector& commandArray; + }; + + AZStd::vector commandTokens; + AZStd::visit(EscapeCommandArguments{ commandTokens }, processLaunchInfo.m_commandlineParameters); + if (!processLaunchInfo.m_processExecutableString.empty()) { commandTokens.insert(commandTokens.begin(), processLaunchInfo.m_processExecutableString); @@ -452,4 +467,23 @@ namespace AzFramework kill(m_pWatcherData->m_childProcessId, SIGKILL); } + + AZStd::string ProcessLauncher::ProcessLaunchInfo::GetCommandLineParametersAsString() const + { + struct CommandLineParametersVisitor + { + AZStd::string operator()(const AZStd::string& commandLine) const + { + return commandLine; + } + + AZStd::string operator()(const AZStd::vector& commandLineArray) const + { + AZStd::string commandLineResult; + AZ::StringFunc::Join(commandLineResult, commandLineArray.begin(), commandLineArray.end(), " "); + return commandLineResult; + } + }; + return AZStd::visit(CommandLineParametersVisitor{}, m_commandlineParameters); + } } //namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Linux/platform_linux_files.cmake b/Code/Framework/AzFramework/Platform/Linux/platform_linux_files.cmake index 206563fdda..da1ce54a15 100644 --- a/Code/Framework/AzFramework/Platform/Linux/platform_linux_files.cmake +++ b/Code/Framework/AzFramework/Platform/Linux/platform_linux_files.cmake @@ -28,6 +28,4 @@ set(FILES AzFramework/Input/User/LocalUserId_Platform.h ../Common/Default/AzFramework/Input/User/LocalUserId_Default.h ../Common/Unimplemented/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard_Unimplemented.cpp - AzFramework/Archive/ArchiveVars_Platform.h - AzFramework/Archive/ArchiveVars_Linux.h ) diff --git a/Code/Framework/AzFramework/Platform/Mac/AzFramework/Archive/ArchiveVars_Mac.h b/Code/Framework/AzFramework/Platform/Mac/AzFramework/Archive/ArchiveVars_Mac.h deleted file mode 100644 index 5a80afe271..0000000000 --- a/Code/Framework/AzFramework/Platform/Mac/AzFramework/Archive/ArchiveVars_Mac.h +++ /dev/null @@ -1,11 +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 - -#define STREAM_CACHE_DEFAULT 0 diff --git a/Code/Framework/AzFramework/Platform/Mac/AzFramework/Archive/ArchiveVars_Platform.h b/Code/Framework/AzFramework/Platform/Mac/AzFramework/Archive/ArchiveVars_Platform.h deleted file mode 100644 index 5311c78de6..0000000000 --- a/Code/Framework/AzFramework/Platform/Mac/AzFramework/Archive/ArchiveVars_Platform.h +++ /dev/null @@ -1,10 +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 diff --git a/Code/Framework/AzFramework/Platform/Mac/AzFramework/Process/ProcessWatcher_Mac.cpp b/Code/Framework/AzFramework/Platform/Mac/AzFramework/Process/ProcessWatcher_Mac.cpp index 1ba9ff3067..e131ab4a7b 100644 --- a/Code/Framework/AzFramework/Platform/Mac/AzFramework/Process/ProcessWatcher_Mac.cpp +++ b/Code/Framework/AzFramework/Platform/Mac/AzFramework/Process/ProcessWatcher_Mac.cpp @@ -11,13 +11,12 @@ #include #include -#include - #include #include #include #include #include +#include #include #include @@ -210,46 +209,51 @@ namespace AzFramework // this is so that the callers (which could be numerous) do not have to worry about this and sprinkle ifdefs // all over their code. // We'll convert this to UNIX style command line parameters by counting and eliminating quotes: - + + // Struct uses overloaded operator() to quote command line arguments based + // on whether a string or a vector was supplied + struct EscapeCommandArguments + { + void operator()(const AZStd::string& commandParameterString) + { + AZStd::string outputString; + bool inQuotes = false; + for (size_t pos = 0; pos < commandParameterString.size(); ++pos) + { + char currentChar = commandParameterString[pos]; + if (currentChar == '"') + { + inQuotes = !inQuotes; + } + else if ((currentChar == ' ') && (!inQuotes)) + { + // its a space outside of quotes, so it ends the current parameter + commandArray.push_back(outputString); + outputString.clear(); + } + else + { + // Its a normal character, or its a space inside quotes + outputString.push_back(currentChar); + } + } + + if (!outputString.empty()) + { + commandArray.push_back(outputString); + outputString.clear(); + } + } + + void operator()(const AZStd::vector& commandParameterArray) + { + commandArray = commandParameterArray; + } + AZStd::vector& commandArray; + }; + AZStd::vector commandTokens; - - AZStd::string outputString; - bool inQuotes = false; - for (size_t pos = 0; pos < processLaunchInfo.m_commandlineParameters.size(); ++pos) - { - char currentChar = processLaunchInfo.m_commandlineParameters[pos]; - if (currentChar == '"') - { - // Allow quote literals to go through as quotes which do NOT alter our "in quotes" bool below - // This is to conform with our PC parameter strings which will sometimes include path parameters which - // Can have spaces and commas and need to be output as paramname="\"Some pa,ram\"" in order to capture both correctly - if (outputString.length() && outputString.back() == '\\') - { - outputString.back() = currentChar; - } - else - { - inQuotes = !inQuotes; - } - } - else if ((currentChar == ' ') && (!inQuotes)) - { - // its a space outside of quotes, so it ends the current parameter - commandTokens.push_back(outputString); - outputString.clear(); - } - else - { - // Its a normal character, or its a space inside quotes - outputString.push_back(currentChar); - } - } - - if (!outputString.empty()) - { - commandTokens.push_back(outputString); - outputString.clear(); - } + AZStd::visit(EscapeCommandArguments{ commandTokens }, processLaunchInfo.m_commandlineParameters); if (!processLaunchInfo.m_processExecutableString.empty()) { @@ -417,5 +421,24 @@ namespace AzFramework kill(m_pWatcherData->m_childProcessId, SIGKILL); waitpid(m_pWatcherData->m_childProcessId, NULL, 0); } + + AZStd::string ProcessLauncher::ProcessLaunchInfo::GetCommandLineParametersAsString() const + { + struct CommandLineParametersVisitor + { + AZStd::string operator()(const AZStd::string& commandLine) const + { + return commandLine; + } + + AZStd::string operator()(const AZStd::vector& commandLineArray) const + { + AZStd::string commandLineResult; + AZ::StringFunc::Join(commandLineResult, commandLineArray.begin(), commandLineArray.end(), " "); + return commandLineResult; + } + }; + return AZStd::visit(CommandLineParametersVisitor{}, m_commandlineParameters); + } } //namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Mac/platform_mac_files.cmake b/Code/Framework/AzFramework/Platform/Mac/platform_mac_files.cmake index 78f11a65da..319f8f7a46 100644 --- a/Code/Framework/AzFramework/Platform/Mac/platform_mac_files.cmake +++ b/Code/Framework/AzFramework/Platform/Mac/platform_mac_files.cmake @@ -30,8 +30,6 @@ set(FILES AzFramework/Input/User/LocalUserId_Platform.h ../Common/Default/AzFramework/Input/User/LocalUserId_Default.h ../Common/Unimplemented/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard_Unimplemented.cpp - AzFramework/Archive/ArchiveVars_Platform.h - AzFramework/Archive/ArchiveVars_Mac.h ../Common/Apple/AzFramework/Utils/SystemUtilsApple.h ../Common/Apple/AzFramework/Utils/SystemUtilsApple.mm ) diff --git a/Code/Framework/AzFramework/Platform/Windows/AzFramework/Archive/ArchiveVars_Platform.h b/Code/Framework/AzFramework/Platform/Windows/AzFramework/Archive/ArchiveVars_Platform.h deleted file mode 100644 index 771c25c998..0000000000 --- a/Code/Framework/AzFramework/Platform/Windows/AzFramework/Archive/ArchiveVars_Platform.h +++ /dev/null @@ -1,10 +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 diff --git a/Code/Framework/AzFramework/Platform/Windows/AzFramework/Process/ProcessWatcher_Win.cpp b/Code/Framework/AzFramework/Platform/Windows/AzFramework/Process/ProcessWatcher_Win.cpp index 6e21ecc205..b0ef2f09a4 100644 --- a/Code/Framework/AzFramework/Platform/Windows/AzFramework/Process/ProcessWatcher_Win.cpp +++ b/Code/Framework/AzFramework/Platform/Windows/AzFramework/Process/ProcessWatcher_Win.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -99,7 +100,7 @@ namespace AzFramework AZStd::wstring editableCommandLine; AZStd::wstring processExecutableString; AZStd::wstring workingDirectory; - AZStd::to_wstring(editableCommandLine, processLaunchInfo.m_commandlineParameters); + AZStd::to_wstring(editableCommandLine, processLaunchInfo.GetCommandLineParametersAsString()); AZStd::to_wstring(processExecutableString, processLaunchInfo.m_processExecutableString); AZStd::to_wstring(workingDirectory, processLaunchInfo.m_workingDirectory); @@ -355,4 +356,41 @@ namespace AzFramework ::TerminateProcess(m_pWatcherData->processInformation.hProcess, exitCode); } } + + AZStd::string ProcessLauncher::ProcessLaunchInfo::GetCommandLineParametersAsString() const + { + struct CommandLineParametersVisitor + { + AZStd::string operator()(const AZStd::string& commandLine) const + { + return commandLine; + } + + AZStd::string operator()(const AZStd::vector& commandLineArray) const + { + AZStd::string commandLineResult; + + // When re-constructing a command line from an argument list (on windows), if an argument + // is double-quoted, then the double-quotes must be escaped properly otherwise + // it will be absorbed by the native argument parser and possibly evaluated as + // multiple values for arguments + AZStd::string_view escapedDoubleQuote = R"("\")"; + + AZStd::vector preprocessedCommandArray; + + for (const auto& commandArg : commandLineArray) + { + AZStd::string replacedArg = commandArg; + AZ::StringFunc::Replace(replacedArg, R"(")", R"("\")", false, true, true); + preprocessedCommandArray.emplace_back(replacedArg); + } + AZ::StringFunc::Join(commandLineResult, preprocessedCommandArray.begin(), preprocessedCommandArray.end(), " "); + + return commandLineResult; + } + }; + + return AZStd::visit(CommandLineParametersVisitor{}, m_commandlineParameters); + } + } // namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Windows/AzFramework/Windowing/NativeWindow_Windows.cpp b/Code/Framework/AzFramework/Platform/Windows/AzFramework/Windowing/NativeWindow_Windows.cpp index 57b37037ae..80428e2557 100644 --- a/Code/Framework/AzFramework/Platform/Windows/AzFramework/Windowing/NativeWindow_Windows.cpp +++ b/Code/Framework/AzFramework/Platform/Windows/AzFramework/Windowing/NativeWindow_Windows.cpp @@ -11,6 +11,7 @@ #include #include +#include #include namespace AzFramework @@ -54,6 +55,7 @@ namespace AzFramework RECT m_windowRectToRestoreOnFullScreenExit; //!< The position and size of the window to restore when exiting full screen. UINT m_windowStyleToRestoreOnFullScreenExit; //!< The style(s) of the window to restore when exiting full screen. bool m_isInBorderlessWindowFullScreenState = false; //!< Was a borderless window used to enter full screen state? + bool m_shouldEnterFullScreenStateOnActivate = false; //!< Should we enter full screen state when the window is activated? using GetDpiForWindowType = UINT(HWND hwnd); GetDpiForWindowType* m_getDpiFunction = nullptr; @@ -233,14 +235,14 @@ namespace AzFramework const UINT rawInputHeaderSize = sizeof(RAWINPUTHEADER); GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &rawInputSize, rawInputHeaderSize); - LPBYTE rawInputBytes = new BYTE[rawInputSize]; + AZStd::array rawInputBytesArray; + LPBYTE rawInputBytes = rawInputBytesArray.data(); GetRawInputData((HRAWINPUT)lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize); RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes; AzFramework::RawInputNotificationBusWindows::Broadcast( &AzFramework::RawInputNotificationBusWindows::Events::OnRawInputEvent, *rawInput); - delete [] rawInputBytes; break; } case WM_CHAR: @@ -249,6 +251,28 @@ namespace AzFramework AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputCodeUnitUTF16Event, codeUnitUTF16); break; } + case WM_ACTIVATE: + { + // Alt-tabbing out of the app while it is in a full screen state does not + // work unless we explicitly exit the full screen state upon deactivation, + // in which case we want to enter full screen state again upon activation. + const bool windowIsNowInactive = (LOWORD(wParam) == WA_INACTIVE); + const bool windowFullScreenState = nativeWindowImpl->GetFullScreenState(); + if (windowIsNowInactive && + windowFullScreenState) + { + nativeWindowImpl->m_shouldEnterFullScreenStateOnActivate = true; + nativeWindowImpl->SetFullScreenState(false); + } + else if (!windowIsNowInactive && + !windowFullScreenState && + nativeWindowImpl->m_shouldEnterFullScreenStateOnActivate) + { + nativeWindowImpl->m_shouldEnterFullScreenStateOnActivate = false; + nativeWindowImpl->SetFullScreenState(true); + } + break; + } case WM_SYSKEYDOWN: { // Handle ALT+ENTER to toggle full screen unless exclsuive full screen diff --git a/Code/Framework/AzFramework/Platform/Windows/platform_windows_files.cmake b/Code/Framework/AzFramework/Platform/Windows/platform_windows_files.cmake index b8f5b5f841..021ecd3298 100644 --- a/Code/Framework/AzFramework/Platform/Windows/platform_windows_files.cmake +++ b/Code/Framework/AzFramework/Platform/Windows/platform_windows_files.cmake @@ -32,6 +32,4 @@ set(FILES AzFramework/Input/User/LocalUserId_Platform.h ../Common/Default/AzFramework/Input/User/LocalUserId_Default.h ../Common/Unimplemented/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard_Unimplemented.cpp - AzFramework/Archive/ArchiveVars_Platform.h - AzFramework/Archive/ArchiveVars_Windows.h ) diff --git a/Code/Framework/AzFramework/Platform/iOS/AzFramework/Archive/ArchiveVars_Platform.h b/Code/Framework/AzFramework/Platform/iOS/AzFramework/Archive/ArchiveVars_Platform.h deleted file mode 100644 index 6ebbe5aade..0000000000 --- a/Code/Framework/AzFramework/Platform/iOS/AzFramework/Archive/ArchiveVars_Platform.h +++ /dev/null @@ -1,10 +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 diff --git a/Code/Framework/AzFramework/Platform/iOS/AzFramework/Archive/ArchiveVars_iOS.h b/Code/Framework/AzFramework/Platform/iOS/AzFramework/Archive/ArchiveVars_iOS.h deleted file mode 100644 index 5a80afe271..0000000000 --- a/Code/Framework/AzFramework/Platform/iOS/AzFramework/Archive/ArchiveVars_iOS.h +++ /dev/null @@ -1,11 +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 - -#define STREAM_CACHE_DEFAULT 0 diff --git a/Code/Framework/AzFramework/Platform/iOS/AzFramework/Process/ProcessWatcher_iOS.cpp b/Code/Framework/AzFramework/Platform/iOS/AzFramework/Process/ProcessWatcher_iOS.cpp index f87c51bdc5..c7b7513064 100644 --- a/Code/Framework/AzFramework/Platform/iOS/AzFramework/Process/ProcessWatcher_iOS.cpp +++ b/Code/Framework/AzFramework/Platform/iOS/AzFramework/Process/ProcessWatcher_iOS.cpp @@ -6,10 +6,10 @@ * */ +#include #include #include - namespace AzFramework { @@ -83,4 +83,23 @@ namespace AzFramework { } + + AZStd::string ProcessLauncher::ProcessLaunchInfo::GetCommandLineParametersAsString() const + { + struct CommandLineParametersVisitor + { + AZStd::string operator()(const AZStd::string& commandLine) const + { + return commandLine; + } + + AZStd::string operator()(const AZStd::vector& commandLineArray) const + { + AZStd::string commandLineResult; + Az::StringFunc::Join(commandLineResult, commandLineArray.begin(), commandLineArray.end(), " "); + return commandLineResult; + } + }; + return AZStd::visit(CommandLineParametersVisitor{}, m_commandlineParameters); + } } //namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/iOS/platform_ios_files.cmake b/Code/Framework/AzFramework/Platform/iOS/platform_ios_files.cmake index 7745f43ec9..e784e4f6df 100644 --- a/Code/Framework/AzFramework/Platform/iOS/platform_ios_files.cmake +++ b/Code/Framework/AzFramework/Platform/iOS/platform_ios_files.cmake @@ -27,8 +27,6 @@ set(FILES AzFramework/Input/User/LocalUserId_Platform.h ../Common/Default/AzFramework/Input/User/LocalUserId_Default.h ../Common/Apple/AzFramework/Input/Devices/VirtualKeyboard/InputDeviceVirtualKeyboard_Apple.mm - AzFramework/Archive/ArchiveVars_Platform.h - AzFramework/Archive/ArchiveVars_iOS.h AzFramework/Process/ProcessCommon.h AzFramework/Process/ProcessWatcher_iOS.cpp AzFramework/Process/ProcessCommunicator_iOS.cpp diff --git a/Code/Framework/AzFramework/Tests/ArchiveTests.cpp b/Code/Framework/AzFramework/Tests/ArchiveTests.cpp index ff0e3ab724..33924d7e0c 100644 --- a/Code/Framework/AzFramework/Tests/ArchiveTests.cpp +++ b/Code/Framework/AzFramework/Tests/ArchiveTests.cpp @@ -273,7 +273,7 @@ namespace UnitTest // Also enable extra verbosity in the AZ::IO::Archive code CVarIntValueScope previousLocationPriority{ *console, "sys_pakPriority" }; CVarIntValueScope oldArchiveVerbosity{ *console, "az_archive_verbosity" }; - console->PerformCommand("sys_PakPriority", { AZ::CVarFixedString::format("%d", aznumeric_cast(AZ::IO::ArchiveLocationPriority::ePakPriorityPakOnly)) }); + console->PerformCommand("sys_PakPriority", { AZ::CVarFixedString::format("%d", aznumeric_cast(AZ::IO::FileSearchPriority::PakOnly)) }); console->PerformCommand("az_archive_verbosity", { "1" }); // ---- Archive FGetCachedFileDataTests (these leverage Archive CachedFile mechanism for caching data --- @@ -459,7 +459,7 @@ namespace UnitTest // Once the archive has been deleted it should no longer be searched CVarIntValueScope previousLocationPriority{ *console, "sys_pakPriority" }; - console->PerformCommand("sys_PakPriority", { AZ::CVarFixedString::format("%d", aznumeric_cast(AZ::IO::ArchiveLocationPriority::ePakPriorityPakOnly)) }); + console->PerformCommand("sys_PakPriority", { AZ::CVarFixedString::format("%d", aznumeric_cast(AZ::IO::FileSearchPriority::PakOnly)) }); handle = archive->FindFirst("levels\\*"); EXPECT_FALSE(static_cast(handle)); @@ -785,7 +785,7 @@ namespace UnitTest EXPECT_TRUE(archive->OpenPack("@usercache@", realNameBuf)); EXPECT_TRUE(archive->IsFileExist("@usercache@/foundit.dat")); - EXPECT_FALSE(archive->IsFileExist("@usercache@/foundit.dat", AZ::IO::IArchive::eFileLocation_OnDisk)); + EXPECT_FALSE(archive->IsFileExist("@usercache@/foundit.dat", AZ::IO::FileSearchLocation::OnDisk)); EXPECT_FALSE(archive->IsFileExist("@usercache@/notfoundit.dat")); EXPECT_TRUE(archive->ClosePack(realNameBuf)); @@ -793,7 +793,7 @@ namespace UnitTest EXPECT_TRUE(archive->OpenPack("@products@", realNameBuf)); EXPECT_TRUE(archive->IsFileExist("@products@/foundit.dat")); EXPECT_FALSE(archive->IsFileExist("@usercache@/foundit.dat")); // do not find it in the previous location! - EXPECT_FALSE(archive->IsFileExist("@products@/foundit.dat", AZ::IO::IArchive::eFileLocation_OnDisk)); + EXPECT_FALSE(archive->IsFileExist("@products@/foundit.dat", AZ::IO::FileSearchLocation::OnDisk)); EXPECT_FALSE(archive->IsFileExist("@products@/notfoundit.dat")); EXPECT_TRUE(archive->ClosePack(realNameBuf)); @@ -802,8 +802,8 @@ namespace UnitTest EXPECT_TRUE(archive->IsFileExist("@products@/mystuff/foundit.dat")); EXPECT_FALSE(archive->IsFileExist("@products@/foundit.dat")); // do not find it in the previous locations! EXPECT_FALSE(archive->IsFileExist("@usercache@/foundit.dat")); // do not find it in the previous locations! - EXPECT_FALSE(archive->IsFileExist("@products@/foundit.dat", AZ::IO::IArchive::eFileLocation_OnDisk)); - EXPECT_FALSE(archive->IsFileExist("@products@/mystuff/foundit.dat", AZ::IO::IArchive::eFileLocation_OnDisk)); + EXPECT_FALSE(archive->IsFileExist("@products@/foundit.dat", AZ::IO::FileSearchLocation::OnDisk)); + EXPECT_FALSE(archive->IsFileExist("@products@/mystuff/foundit.dat", AZ::IO::FileSearchLocation::OnDisk)); EXPECT_FALSE(archive->IsFileExist("@products@/notfoundit.dat")); // non-existent file EXPECT_FALSE(archive->IsFileExist("@products@/mystuff/notfoundit.dat")); // non-existent file EXPECT_TRUE(archive->ClosePack(realNameBuf)); diff --git a/Code/Framework/AzFramework/Tests/BinToTextEncode.cpp b/Code/Framework/AzFramework/Tests/BinToTextEncode.cpp index a1ebdfd945..a4055db33c 100644 --- a/Code/Framework/AzFramework/Tests/BinToTextEncode.cpp +++ b/Code/Framework/AzFramework/Tests/BinToTextEncode.cpp @@ -34,24 +34,20 @@ namespace UnitTest AllocatorInstance::Create(); ComponentApplication::Descriptor desc; desc.m_useExistingAllocator = true; - desc.m_enableDrilling = false; // we already created a memory driller for the test (AllocatorsFixture) - m_app.Create(desc); + m_app.reset(aznew ComponentApplication); + m_app->Create(desc); } void TearDown() override { - m_app.Destroy(); + m_app->Destroy(); + m_app.reset(); AllocatorInstance::Destroy(); AllocatorInstance::Destroy(); AllocatorsFixture::TearDown(); } - ~Base64Test() override - { - - } - - ComponentApplication m_app; + AZStd::unique_ptr m_app; }; TEST_F(Base64Test, EmptyStringEncodeTest) diff --git a/Code/Framework/AzFramework/Tests/CameraInputTests.cpp b/Code/Framework/AzFramework/Tests/CameraInputTests.cpp index 005623677c..5f327fb2fd 100644 --- a/Code/Framework/AzFramework/Tests/CameraInputTests.cpp +++ b/Code/Framework/AzFramework/Tests/CameraInputTests.cpp @@ -322,6 +322,17 @@ namespace UnitTest EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateZero())); } + TEST(CameraInput, CameraPitchIsClampedWithExpectedTolerance) + { + const auto [expectedMinPitch, expectedMaxPitch] = AzFramework::CameraPitchMinMaxRadiansWithTolerance(); + const float minPitch = AzFramework::ClampPitchRotation(-AZ::Constants::HalfPi); + const float maxPitch = AzFramework::ClampPitchRotation(AZ::Constants::HalfPi); + + using ::testing::FloatNear; + EXPECT_THAT(minPitch, FloatNear(expectedMinPitch, AzFramework::CameraPitchTolerance)); + EXPECT_THAT(maxPitch, FloatNear(expectedMaxPitch, AzFramework::CameraPitchTolerance)); + } + TEST_F(CameraInputFixture, OrbitRotateCameraInputRotatesPitchOffsetByNinetyDegreesWithRequiredPixelDelta) { const auto cameraStartingPosition = AZ::Vector3::CreateAxisY(-20.0f); diff --git a/Code/Framework/AzFramework/Tests/EntityContext.cpp b/Code/Framework/AzFramework/Tests/EntityContext.cpp index 74a4948e04..520067a9a6 100644 --- a/Code/Framework/AzFramework/Tests/EntityContext.cpp +++ b/Code/Framework/AzFramework/Tests/EntityContext.cpp @@ -58,7 +58,6 @@ namespace UnitTest ComponentApplication app; ComponentApplication::Descriptor desc; desc.m_useExistingAllocator = true; - desc.m_enableDrilling = false; // we already created a memory driller for the test (AllocatorsFixture) app.Create(desc); Data::AssetManager::Instance().RegisterHandler(aznew SliceAssetHandler(app.GetSerializeContext()), AZ::AzTypeInfo::Uuid()); diff --git a/Code/Framework/AzFramework/Tests/FileTagTests.cpp b/Code/Framework/AzFramework/Tests/FileTagTests.cpp index 2b09a5638c..5005ab6ef9 100644 --- a/Code/Framework/AzFramework/Tests/FileTagTests.cpp +++ b/Code/Framework/AzFramework/Tests/FileTagTests.cpp @@ -83,7 +83,6 @@ namespace UnitTest m_data = AZStd::make_unique(); using namespace AzFramework::FileTag; AZ::ComponentApplication::Descriptor desc; - desc.m_enableDrilling = false; m_data->m_application.Start(desc); const char* testAssetRoot = m_tempDirectory.GetDirectory(); diff --git a/Code/Framework/AzFramework/Tests/InputTests.cpp b/Code/Framework/AzFramework/Tests/InputTests.cpp index 9942912622..31558250b5 100644 --- a/Code/Framework/AzFramework/Tests/InputTests.cpp +++ b/Code/Framework/AzFramework/Tests/InputTests.cpp @@ -29,6 +29,13 @@ namespace InputUnitTests //////////////////////////////////////////////////////////////////////////////////////////////// class InputTest : public ScopedAllocatorSetupFixture { + public: + InputTest() : ScopedAllocatorSetupFixture() + { + // Many input tests are only valid if the GamePad device is supported on this platform. + m_gamepadSupported = InputDeviceGamepad::GetMaxSupportedGamepads() > 0; + } + protected: //////////////////////////////////////////////////////////////////////////////////////////// void SetUp() override @@ -46,8 +53,27 @@ namespace InputUnitTests //////////////////////////////////////////////////////////////////////////////////////////// AZStd::unique_ptr m_inputSystemComponent; + bool m_gamepadSupported; }; + //////////////////////////////////////////////////////////////////////////////////////////////// + TEST_F(InputTest, InputChannelId_ConstExpression_CopyConstructorSuccessfull) + { + constexpr InputChannelId testInputChannelId1("TestInputChannelId"); + constexpr InputChannelId testInputChannelId2(testInputChannelId1); + static_assert(testInputChannelId1 == testInputChannelId2); + EXPECT_EQ(testInputChannelId1, testInputChannelId2); + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + TEST_F(InputTest, InputDeviceId_ConstExpression_CopyConstructorSuccessfull) + { + constexpr InputDeviceId testInputDeviceId1("TestInputDeviceId"); + constexpr InputDeviceId testInputDeviceId2(testInputDeviceId1); + static_assert(testInputDeviceId1 == testInputDeviceId2); + EXPECT_EQ(testInputDeviceId1, testInputDeviceId2); + } + //////////////////////////////////////////////////////////////////////////////////////////////// TEST_F(InputTest, InputContext_InitWithDataStruct_InitializationSuccessfull) { @@ -60,12 +86,17 @@ namespace InputUnitTests } //////////////////////////////////////////////////////////////////////////////////////////////// -#if AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS - TEST_F(InputTest, DISABLED_InputContext_ActivateDeactivate_Successfull) -#else TEST_F(InputTest, InputContext_ActivateDeactivate_Successfull) -#endif // AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS { + if (!m_gamepadSupported) + { + #if defined(GTEST_SKIP) + GTEST_SKIP() << "Skipping test InputContext_ActivateDeactivate_Successfull"; + #else + SUCCEED() << "Skipping test InputContext_ActivateDeactivate_Successfull"; + #endif + return; + } // Create an input context (they are inactive by default). InputContext inputContext("TestInputContext"); @@ -130,12 +161,18 @@ namespace InputUnitTests } //////////////////////////////////////////////////////////////////////////////////////////////// -#if AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS - TEST_F(InputTest, DISABLED_InputContext_AddRemoveInputMapping_Successfull) -#else TEST_F(InputTest, InputContext_AddRemoveInputMapping_Successfull) -#endif // AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS { + if (!m_gamepadSupported) + { + #if defined(GTEST_SKIP) + GTEST_SKIP() << "Skipping test InputContext_AddRemoveInputMapping_Successfull"; + #else + SUCCEED() << "Skipping test InputContext_AddRemoveInputMapping_Successfull"; + #endif + return; + } + // Create an input context and activate it. InputContext inputContext("TestInputContext"); inputContext.Activate(); @@ -238,12 +275,18 @@ namespace InputUnitTests } //////////////////////////////////////////////////////////////////////////////////////////////// -#if AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS - TEST_F(InputTest, DISABLED_InputContext_ConsumeProcessedInput_Consumed) -#else TEST_F(InputTest, InputContext_ConsumeProcessedInput_Consumed) -#endif // AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS { + if (!m_gamepadSupported) + { + #if defined(GTEST_SKIP) + GTEST_SKIP() << "Skipping test InputContext_ConsumeProcessedInput_Consumed"; + #else + SUCCEED() << "Skipping test InputContext_ConsumeProcessedInput_Consumed"; + #endif + return; + } + InputContext::InitData initData; // Create a high priority input context that consumes input processed by any of its mappings. @@ -322,12 +365,18 @@ namespace InputUnitTests } //////////////////////////////////////////////////////////////////////////////////////////////// -#if AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS - TEST_F(InputTest, DISABLED_InputContext_FilteredInput_Mapped) -#else TEST_F(InputTest, InputContext_FilteredInput_Mapped) -#endif // AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS { + if (!m_gamepadSupported) + { + #if defined(GTEST_SKIP) + GTEST_SKIP() << "Skipping test InputContext_FilteredInput_Mapped"; + #else + SUCCEED() << "Skipping test InputContext_FilteredInput_Mapped"; + #endif + return; + } + // Create an input context that initially only listens for keyboard input. InputContext::InitData initData; initData.autoActivate = true; @@ -395,12 +444,18 @@ namespace InputUnitTests } //////////////////////////////////////////////////////////////////////////////////////////////// -#if AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS - TEST_F(InputTest, DISABLED_InputMappingOr_AddRemoveSourceInput_Successful) -#else TEST_F(InputTest, InputMappingOr_AddRemoveSourceInput_Successful) -#endif // AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS { + if (!m_gamepadSupported) + { + #if defined(GTEST_SKIP) + GTEST_SKIP() << "Skipping test InputMappingOr_AddRemoveSourceInput_Successful"; + #else + SUCCEED() << "Skipping test InputMappingOr_AddRemoveSourceInput_Successful"; + #endif + return; + } + // Create an input context and activate it. InputContext inputContext("TestInputContext"); inputContext.Activate(); @@ -473,12 +528,18 @@ namespace InputUnitTests } //////////////////////////////////////////////////////////////////////////////////////////////// -#if AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS - TEST_F(InputTest, DISABLED_InputMappingOr_SingleSourceInput_Mapped) -#else TEST_F(InputTest, InputMappingOr_SingleSourceInput_Mapped) -#endif // AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS { + if (!m_gamepadSupported) + { + #if defined(GTEST_SKIP) + GTEST_SKIP() << "Skipping test InputMappingOr_SingleSourceInput_Mapped"; + #else + SUCCEED() << "Skipping test InputMappingOr_SingleSourceInput_Mapped"; + #endif + return; + } + // Create an input context and activate it. InputContext inputContext("TestInputContext"); inputContext.Activate(); @@ -540,12 +601,18 @@ namespace InputUnitTests } //////////////////////////////////////////////////////////////////////////////////////////////// -#if AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS - TEST_F(InputTest, DISABLED_InputMappingOr_MultipleSourceInputs_Mapped) -#else TEST_F(InputTest, InputMappingOr_MultipleSourceInputs_Mapped) -#endif // AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS { + if (!m_gamepadSupported) + { + #if defined(GTEST_SKIP) + GTEST_SKIP() << "Skipping test InputMappingOr_MultipleSourceInputs_Mapped"; + #else + SUCCEED() << "Skipping test InputMappingOr_MultipleSourceInputs_Mapped"; + #endif + return; + } + // Create an input context and activate it. InputContext inputContext("TestInputContext"); inputContext.Activate(); @@ -632,12 +699,18 @@ namespace InputUnitTests } //////////////////////////////////////////////////////////////////////////////////////////////// -#if AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS - TEST_F(InputTest, DISABLED_InputMappingAnd_AddRemoveSourceInput_Successful) -#else TEST_F(InputTest, InputMappingAnd_AddRemoveSourceInput_Successful) -#endif // AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS { + if (!m_gamepadSupported) + { + #if defined(GTEST_SKIP) + GTEST_SKIP() << "Skipping test InputMappingAnd_AddRemoveSourceInput_Successful"; + #else + SUCCEED() << "Skipping test InputMappingAnd_AddRemoveSourceInput_Successful"; + #endif + return; + } + // Create an input context and activate it. InputContext inputContext("TestInputContext"); inputContext.Activate(); @@ -710,12 +783,18 @@ namespace InputUnitTests } //////////////////////////////////////////////////////////////////////////////////////////////// -#if AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS - TEST_F(InputTest, DISABLED_InputMappingAnd_SingleSourceInput_Mapped) -#else TEST_F(InputTest, InputMappingAnd_SingleSourceInput_Mapped) -#endif // AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS { + if (!m_gamepadSupported) + { + #if defined(GTEST_SKIP) + GTEST_SKIP() << "Skipping test InputMappingAnd_SingleSourceInput_Mapped"; + #else + SUCCEED() << "Skipping test InputMappingAnd_SingleSourceInput_Mapped"; + #endif + return; + } + // Create an input context and activate it. InputContext inputContext("TestInputContext"); inputContext.Activate(); @@ -777,12 +856,18 @@ namespace InputUnitTests } //////////////////////////////////////////////////////////////////////////////////////////////// -#if AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS - TEST_F(InputTest, DISABLED_InputMappingAnd_MultipleSourceInputs_Mapped) -#else TEST_F(InputTest, InputMappingAnd_MultipleSourceInputs_Mapped) -#endif // AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS { + if (!m_gamepadSupported) + { + #if defined(GTEST_SKIP) + GTEST_SKIP() << "Skipping test InputMappingAnd_MultipleSourceInputs_Mapped"; + #else + SUCCEED() << "Skipping test InputMappingAnd_MultipleSourceInputs_Mapped"; + #endif + return; + } + // Create an input context and activate it. InputContext inputContext("TestInputContext"); inputContext.Activate(); @@ -891,12 +976,18 @@ namespace InputUnitTests } //////////////////////////////////////////////////////////////////////////////////////////////// -#if AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS - TEST_F(InputTest, DISABLED_InputMappingAnd_MultipleSourceInputsWithDifferentValues_ValuesAveraged) -#else TEST_F(InputTest, InputMappingAnd_MultipleSourceInputsWithDifferentValues_ValuesAveraged) -#endif // AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS { + if (!m_gamepadSupported) + { + #if defined(GTEST_SKIP) + GTEST_SKIP() << "Skipping test InputMappingAnd_MultipleSourceInputsWithDifferentValues_ValuesAveraged"; + #else + SUCCEED() << "Skipping test InputMappingAnd_MultipleSourceInputsWithDifferentValues_ValuesAveraged"; + #endif + return; + } + // Create an input context and activate it. InputContext inputContext("TestInputContext"); inputContext.Activate(); @@ -951,12 +1042,18 @@ namespace InputUnitTests } //////////////////////////////////////////////////////////////////////////////////////////////// -#if AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS - TEST_F(InputTest, DISABLED_InputMappingAnd_MultipleSourceInputsFromTheSameInputDeviceTypeWithDifferentIndicies_NotMapped) -#else TEST_F(InputTest, InputMappingAnd_MultipleSourceInputsFromTheSameInputDeviceTypeWithDifferentIndicies_NotMapped) -#endif // AZ_TRAIT_DISABLE_FAILED_INPUT_TESTS { + if (!m_gamepadSupported) + { + #if defined(GTEST_SKIP) + GTEST_SKIP() << "Skipping test InputMappingAnd_MultipleSourceInputsFromTheSameInputDeviceTypeWithDifferentIndicies_NotMapped"; + #else + SUCCEED() << "Skipping test InputMappingAnd_MultipleSourceInputsFromTheSameInputDeviceTypeWithDifferentIndicies_NotMapped"; + #endif + return; + } + // Create an input context and activate it. InputContext inputContext("TestInputContext"); inputContext.Activate(); diff --git a/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h b/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h index a437545adf..4d04fa5bc9 100644 --- a/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h +++ b/Code/Framework/AzFramework/Tests/Mocks/MockSpawnableEntitiesInterface.h @@ -36,7 +36,7 @@ namespace AzFramework MOCK_METHOD3( SpawnEntities, - void(EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs)); + void(EntitySpawnTicket& ticket, AZStd::vector entityIndices, SpawnEntitiesOptionalArgs optionalArgs)); MOCK_METHOD2(DespawnAllEntities, void(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs)); @@ -49,6 +49,13 @@ namespace AzFramework ReloadSpawnable, void(EntitySpawnTicket& ticket, AZ::Data::Asset spawnable, ReloadSpawnableOptionalArgs optionalArgs)); + MOCK_METHOD3( + UpdateEntityAliasTypes, + void( + EntitySpawnTicket& ticket, + AZStd::vector updatedAliases, + UpdateEntityAliasTypesOptionalArgs optionalArgs)); + MOCK_METHOD3( ListEntities, void(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs)); @@ -61,6 +68,7 @@ namespace AzFramework void(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs)); MOCK_METHOD3(Barrier, void(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs)); + MOCK_METHOD3(LoadBarrier, void(EntitySpawnTicket& ticket, BarrierCallback completionCallback, LoadBarrierOptionalArgs optionalArgs)); MOCK_METHOD1(CreateTicket, AZStd::pair(AZ::Data::Asset&& spawnable)); MOCK_METHOD1(DestroyTicket, void(void* ticket)); diff --git a/Code/Framework/AzFramework/Tests/Mocks/MockWindowRequests.h b/Code/Framework/AzFramework/Tests/Mocks/MockWindowRequests.h index 63f73d0b28..a166df19b4 100644 --- a/Code/Framework/AzFramework/Tests/Mocks/MockWindowRequests.h +++ b/Code/Framework/AzFramework/Tests/Mocks/MockWindowRequests.h @@ -36,6 +36,7 @@ namespace UnitTest MOCK_METHOD0(ToggleFullScreenState, void()); MOCK_CONST_METHOD0(GetDpiScaleFactor, float()); MOCK_CONST_METHOD0(GetSyncInterval, uint32_t()); + MOCK_METHOD1(SetSyncInterval, bool(uint32_t)); MOCK_CONST_METHOD0(GetDisplayRefreshRate, uint32_t()); }; } // namespace UnitTest diff --git a/Code/Framework/AzFramework/Tests/Mocks/Terrain/MockTerrainDataRequestBus.h b/Code/Framework/AzFramework/Tests/Mocks/Terrain/MockTerrainDataRequestBus.h new file mode 100644 index 0000000000..dbce11d639 --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Mocks/Terrain/MockTerrainDataRequestBus.h @@ -0,0 +1,80 @@ +/* + * 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 + +#include +#include +#include +#include + +namespace UnitTest +{ + class MockTerrainDataNotificationListener : public AzFramework::Terrain::TerrainDataNotificationBus::Handler + { + public: + MockTerrainDataNotificationListener() + { + AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusConnect(); + } + + ~MockTerrainDataNotificationListener() + { + AzFramework::Terrain::TerrainDataNotificationBus::Handler::BusDisconnect(); + } + + MOCK_METHOD0(OnTerrainDataCreateBegin, void()); + MOCK_METHOD0(OnTerrainDataCreateEnd, void()); + MOCK_METHOD0(OnTerrainDataDestroyBegin, void()); + MOCK_METHOD0(OnTerrainDataDestroyEnd, void()); + MOCK_METHOD2(OnTerrainDataChanged, void(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask)); + }; + + class MockTerrainDataRequests : public AzFramework::Terrain::TerrainDataRequestBus::Handler + { + public: + MockTerrainDataRequests() + { + AzFramework::Terrain::TerrainDataRequestBus::Handler::BusConnect(); + } + + ~MockTerrainDataRequests() + { + AzFramework::Terrain::TerrainDataRequestBus::Handler::BusDisconnect(); + } + + MOCK_CONST_METHOD0(GetTerrainHeightQueryResolution, AZ::Vector2()); + MOCK_METHOD1(SetTerrainHeightQueryResolution, void(AZ::Vector2)); + MOCK_CONST_METHOD0(GetTerrainAabb, AZ::Aabb()); + MOCK_METHOD1(SetTerrainAabb, void(const AZ::Aabb&)); + MOCK_CONST_METHOD3(GetHeight, float(const AZ::Vector3&, Sampler, bool*)); + MOCK_CONST_METHOD3(GetHeightFromVector2, float(const AZ::Vector2&, Sampler, bool*)); + MOCK_CONST_METHOD4(GetHeightFromFloats, float(float, float, Sampler, bool*)); + MOCK_CONST_METHOD2(GetIsHole, bool(const AZ::Vector3&, Sampler)); + MOCK_CONST_METHOD2(GetIsHoleFromVector2, bool(const AZ::Vector2&, Sampler)); + MOCK_CONST_METHOD3(GetIsHoleFromFloats, bool(float, float, Sampler)); + MOCK_CONST_METHOD3(GetNormal, AZ::Vector3(const AZ::Vector3&, Sampler, bool*)); + MOCK_CONST_METHOD3(GetNormalFromVector2, AZ::Vector3(const AZ::Vector2&, Sampler, bool*)); + MOCK_CONST_METHOD4(GetNormalFromFloats, AZ::Vector3(float, float, Sampler, bool*)); + MOCK_CONST_METHOD3(GetMaxSurfaceWeight, AzFramework::SurfaceData::SurfaceTagWeight(const AZ::Vector3&, Sampler, bool*)); + MOCK_CONST_METHOD3(GetMaxSurfaceWeightFromVector2, AzFramework::SurfaceData::SurfaceTagWeight(const AZ::Vector2&, Sampler, bool*)); + MOCK_CONST_METHOD4(GetMaxSurfaceWeightFromFloats, AzFramework::SurfaceData::SurfaceTagWeight(float, float, Sampler, bool*)); + MOCK_CONST_METHOD4(GetSurfaceWeights, void(const AZ::Vector3&, AzFramework::SurfaceData::SurfaceTagWeightList&, Sampler, bool*)); + MOCK_CONST_METHOD4( + GetSurfaceWeightsFromVector2, void(const AZ::Vector2&, AzFramework::SurfaceData::SurfaceTagWeightList&, Sampler, bool*)); + MOCK_CONST_METHOD5( + GetSurfaceWeightsFromFloats, void(float, float, AzFramework::SurfaceData::SurfaceTagWeightList&, Sampler, bool*)); + MOCK_CONST_METHOD3(GetMaxSurfaceName, const char*(const AZ::Vector3&, Sampler, bool*)); + MOCK_CONST_METHOD4(GetSurfacePoint, void(const AZ::Vector3&, AzFramework::SurfaceData::SurfacePoint&, Sampler, bool*)); + MOCK_CONST_METHOD4( + GetSurfacePointFromVector2, void(const AZ::Vector2&, AzFramework::SurfaceData::SurfacePoint&, Sampler, bool*)); + MOCK_CONST_METHOD5( + GetSurfacePointFromFloats, void(float, float, AzFramework::SurfaceData::SurfacePoint&, Sampler, bool*)); + }; +} // namespace UnitTest diff --git a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h index 2a63a5f158..524df3e436 100644 --- a/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h +++ b/Code/Framework/AzFramework/Tests/Platform/Common/Xcb/XcbBaseTestFixture.h @@ -10,6 +10,7 @@ #include #include +#include #include "MockXcbInterface.h" @@ -17,7 +18,7 @@ namespace AzFramework { // Sets up mock behavior for the xcb library, providing an xcb_connection_t that is returned from a call to xcb_connect class XcbBaseTestFixture - : public testing::Test + : public ::UnitTest::ScopedAllocatorSetupFixture { public: void SetUp() override; diff --git a/Code/Framework/AzFramework/Tests/ProcessLaunchParseTests.cpp b/Code/Framework/AzFramework/Tests/ProcessLaunchParseTests.cpp index 17dd04c587..0a7eb24df4 100644 --- a/Code/Framework/AzFramework/Tests/ProcessLaunchParseTests.cpp +++ b/Code/Framework/AzFramework/Tests/ProcessLaunchParseTests.cpp @@ -75,7 +75,8 @@ namespace UnitTest AzFramework::ProcessOutput processOutput; AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest"; + processLaunchInfo.m_commandlineParameters.emplace(AZStd::string(AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest")); + processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath(); processLaunchInfo.m_showWindow = false; bool launchReturn = AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput); @@ -90,7 +91,9 @@ namespace UnitTest AzFramework::ProcessOutput processOutput; AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest -param1 param1val -param2=param2val"; + processLaunchInfo.m_commandlineParameters.emplace>( + AZStd::vector{AZStd::string(AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest"), "-param1", "param1val","-param2", "param2val"}); + processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath(); processLaunchInfo.m_showWindow = false; bool launchReturn = AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput); @@ -117,14 +120,16 @@ namespace UnitTest #if AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS TEST_F(ProcessLaunchParseTests, DISABLED_ProcessLauncher_StringsWithCommas_Success) #else - TEST_F(ProcessLaunchParseTests, ProcessLauncher_StringsWithCommas_Success) + TEST_F(ProcessLaunchParseTests, ProcessLauncher_WithCommas_Success) #endif // AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS { ProcessLaunchParseTests::ParsedArgMap argMap; AzFramework::ProcessOutput processOutput; AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER R"(ProcessLaunchTest -param1 "\"param,1val\"" -param2="\"param2v,al\"")"; + processLaunchInfo.m_commandlineParameters.emplace>( + AZStd::vector{AZStd::string(AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest"), "-param1", "param,1val","-param2", "param2v,al"}); + processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath(); processLaunchInfo.m_showWindow = false; bool launchReturn = AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput); @@ -137,28 +142,32 @@ namespace UnitTest EXPECT_NE(param1itr, argMap.end()); AZStd::vector param1{ param1itr->second }; - EXPECT_EQ(param1.size(), 1); - EXPECT_EQ(param1[0], "param,1val"); + EXPECT_EQ(param1.size(), 2); + EXPECT_EQ(param1[0], "param"); + EXPECT_EQ(param1[1], "1val"); auto param2itr = argMap.find("param2"); EXPECT_NE(param2itr, argMap.end()); AZStd::vector param2{ param2itr->second }; - EXPECT_EQ(param2.size(), 1); - EXPECT_EQ(param2[0], "param2v,al"); + EXPECT_EQ(param2.size(), 2); + EXPECT_EQ(param2[0], "param2v"); + EXPECT_EQ(param2[1], "al"); } #if AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS TEST_F(ProcessLaunchParseTests, DISABLED_ProcessLauncher_StringsWithSpaces_Success) #else - TEST_F(ProcessLaunchParseTests, ProcessLauncher_StringsWithSpaces_Success) + TEST_F(ProcessLaunchParseTests, ProcessLauncher_WithSpaces_Success) #endif // AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS { ProcessLaunchParseTests::ParsedArgMap argMap; AzFramework::ProcessOutput processOutput; AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER R"(ProcessLaunchTest -param1 "\"param 1val\"" -param2="\"param2v al\"")"; + processLaunchInfo.m_commandlineParameters.emplace>(AZStd::vector{ + AZStd::string(AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest"), "-param1", R"("param 1val")", R"(-param2="param2v al")" }); + processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath(); processLaunchInfo.m_showWindow = false; bool launchReturn = AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput); @@ -185,14 +194,16 @@ namespace UnitTest #if AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS TEST_F(ProcessLaunchParseTests, DISABLED_ProcessLauncher_StringsWithSpacesAndComma_Success) #else - TEST_F(ProcessLaunchParseTests, ProcessLauncher_StringsWithSpacesAndComma_Success) + TEST_F(ProcessLaunchParseTests, ProcessLauncher_WithSpacesAndComma_Success) #endif // AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS { ProcessLaunchParseTests::ParsedArgMap argMap; AzFramework::ProcessOutput processOutput; AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER R"(ProcessLaunchTest -param1 "\"par,am 1val\"" -param2="\"param,2v al\"")"; + processLaunchInfo.m_commandlineParameters.emplace>(AZStd::vector{ + AZStd::string(AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest"), "-param1", R"("param, 1val")", R"(-param2="param,2v al")" }); + processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath(); processLaunchInfo.m_showWindow = false; bool launchReturn = AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput); @@ -206,7 +217,7 @@ namespace UnitTest AZStd::vector param1{ param1itr->second }; EXPECT_EQ(param1.size(), 1); - EXPECT_EQ(param1[0], "par,am 1val"); + EXPECT_EQ(param1[0], "param, 1val"); auto param2itr = argMap.find("param2"); EXPECT_NE(param2itr, argMap.end()); @@ -216,35 +227,4 @@ namespace UnitTest EXPECT_EQ(param2[0], "param,2v al"); } - TEST_F(ProcessLaunchParseTests, ProcessLauncher_CommaStringNoQuotes_Success) - { - ProcessLaunchParseTests::ParsedArgMap argMap; - AzFramework::ProcessOutput processOutput; - AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - - processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest -param1 param,1val -param2=param2v,al"; - processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath(); - processLaunchInfo.m_showWindow = false; - bool launchReturn = AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput); - - EXPECT_EQ(launchReturn, true); - - argMap = ProcessLaunchParseTests::ParseParameters(processOutput.outputResult); - - auto param1itr = argMap.find("param1"); - EXPECT_NE(param1itr, argMap.end()); - AZStd::vector param1{ param1itr->second }; - - EXPECT_EQ(param1.size(), 2); - EXPECT_EQ(param1[0], "param"); - EXPECT_EQ(param1[1], "1val"); - - auto param2itr = argMap.find("param2"); - EXPECT_NE(param2itr, argMap.end()); - AZStd::vector param2{ param2itr->second }; - - EXPECT_EQ(param2.size(), 2); - EXPECT_EQ(param2[0], "param2v"); - EXPECT_EQ(param2[1], "al"); - } } // namespace UnitTest diff --git a/Code/Framework/AzFramework/Tests/Scene.cpp b/Code/Framework/AzFramework/Tests/Scene.cpp index 31df6e6774..a7325cc810 100644 --- a/Code/Framework/AzFramework/Tests/Scene.cpp +++ b/Code/Framework/AzFramework/Tests/Scene.cpp @@ -113,7 +113,6 @@ namespace SceneUnitTest m_app.RegisterComponentDescriptor(AZ::StreamerComponent::CreateDescriptor()); AZ::ComponentApplication::Descriptor desc; - desc.m_enableDrilling = false; // the unit test framework already adds a driller m_systemEntity = m_app.Create(desc); m_systemEntity->Init(); diff --git a/Code/Framework/AzFramework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp b/Code/Framework/AzFramework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp index 2dc32d14d5..0dc00f81dd 100644 --- a/Code/Framework/AzFramework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp +++ b/Code/Framework/AzFramework/Tests/Spawnable/SpawnableEntitiesManagerTests.cpp @@ -55,6 +55,40 @@ namespace UnitTest AZ::EntityId m_entityReference; }; + class SourceSpawnableComponent : public AZ::Component + { + public: + AZ_COMPONENT(SourceSpawnableComponent, "{47FF79CE-A95B-420E-8BEB-F1CC58087B87}"); + + void Activate() override {} + void Deactivate() override {} + + static void Reflect(AZ::ReflectContext* reflection) + { + if (auto* serializeContext = azrtti_cast(reflection)) + { + serializeContext->Class(); + } + } + }; + + class TargetSpawnableComponent : public AZ::Component + { + public: + AZ_COMPONENT(TargetSpawnableComponent, "{B4041561-63A7-4E1E-80F1-78C08D497960}"); + + void Activate() override {} + void Deactivate() override {} + + static void Reflect(AZ::ReflectContext* reflection) + { + if (auto* serializeContext = azrtti_cast(reflection)) + { + serializeContext->Class(); + } + } + }; + class SpawnableEntitiesManagerTest : public AllocatorsFixture { public: @@ -66,6 +100,8 @@ namespace UnitTest AZ::ComponentApplication::Descriptor descriptor; m_application->Start(descriptor); m_application->RegisterComponentDescriptor(ComponentWithEntityReference::CreateDescriptor()); + m_application->RegisterComponentDescriptor(SourceSpawnableComponent::CreateDescriptor()); + m_application->RegisterComponentDescriptor(TargetSpawnableComponent::CreateDescriptor()); // Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is // shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash @@ -109,10 +145,126 @@ namespace UnitTest entities.reserve(numElements); for (size_t i=0; i()); + auto entry = AZStd::make_unique(); + entry->AddComponent(aznew SourceSpawnableComponent()); + entities.push_back(AZStd::move(entry)); } } + AZ::Data::Asset CreateTargetSpawnable(size_t numElements) + { + auto target = aznew AzFramework::Spawnable( + AZ::Data::AssetId(AZ::Uuid("{716CD8C3-0BA8-4F32-B579-0EC7C967796F}")), AZ::Data::AssetData::AssetStatus::Ready); + + AzFramework::Spawnable::EntityList& entities = target->GetEntities(); + entities.reserve(numElements); + for (size_t i = 0; i < numElements; ++i) + { + auto entry = AZStd::make_unique(); + entry->AddComponent(aznew TargetSpawnableComponent()); + entities.push_back(AZStd::move(entry)); + } + + return AZ::Data::Asset(target, AZ::Data::AssetLoadBehavior::NoLoad); + } + + template + void InsertEntityAliases( + const AZStd::array& sourceIds, + const AZStd::array& targetIds, + const AZStd::array& aliasTypes, + AZ::Data::Asset* target = nullptr) + { + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + + for (uint32_t i = 0; i < AliasCount; ++i) + { + if (target) + { + visitor.AddAlias(*target, AZ::Crc32(i), sourceIds[i], targetIds[i], aliasTypes[i], false); + } + else + { + AZ::Data::Asset spawnable( + AZ::Data::AssetId(AZ::Uuid("{4CBEC17A-52D6-42D5-9037-F4C05B9CE1D9}"), i), azrtti_typeid()); + visitor.AddAlias(AZStd::move(spawnable), AZ::Crc32(i), sourceIds[i], targetIds[i], aliasTypes[i], false); + } + } + } + + static bool AreAllEntitiesReplaced(AzFramework::SpawnableConstEntityContainerView entities) + { + for (const AZ::Entity* entity : entities) + { + if (entity) + { + if (entity->FindComponent() != nullptr || + entity->FindComponent() == nullptr) + { + return false; + } + } + else + { + return false; + } + } + return true; + } + + static bool IsEveryOtherEntityAReplacement(AzFramework::SpawnableConstEntityContainerView entities) + { + bool onAlternative = true; + for (const AZ::Entity* entity : entities) + { + if (entity) + { + if (onAlternative) + { + if (entity->FindComponent() == nullptr || + entity->FindComponent() != nullptr) + { + return false; + } + } + else + { + if (entity->FindComponent() != nullptr || + entity->FindComponent() == nullptr) + { + return false; + } + } + onAlternative = !onAlternative; + } + else + { + return false; + } + } + return true; + } + + static bool AreAllMerged(AzFramework::SpawnableConstEntityContainerView entities) + { + for (const AZ::Entity* entity : entities) + { + if (entity) + { + if (entity->FindComponent() == nullptr || + entity->FindComponent() == nullptr) + { + return false; + } + } + else + { + return false; + } + } + return true; + } + void CreateRecursiveHierarchy() { AzFramework::Spawnable::EntityList& entities = m_spawnable->GetEntities(); @@ -245,6 +397,30 @@ namespace UnitTest TestApplication* m_application { nullptr }; }; + + // + // Constructors + // + + TEST_F(SpawnableEntitiesManagerTest, EntitySpawnTicket_Move_Works) + { + AzFramework::EntitySpawnTicket ticket1(*m_spawnableAsset); + AzFramework::EntitySpawnTicket ticket2(*m_spawnableAsset); + + const AzFramework::EntitySpawnTicket::Id ticket1Id = ticket1.GetId(); + const AzFramework::EntitySpawnTicket::Id ticket2Id = ticket2.GetId(); + + AzFramework::EntitySpawnTicket ticketMoveConstructor(AZStd::move(ticket1)); + EXPECT_TRUE(ticketMoveConstructor.IsValid()); + EXPECT_EQ(ticketMoveConstructor.GetId(), ticket1Id); + + AzFramework::EntitySpawnTicket ticketMoveOperator; + ticketMoveOperator = AZStd::move(ticket2); + EXPECT_TRUE(ticketMoveOperator.IsValid()); + EXPECT_EQ(ticketMoveOperator.GetId(), ticket2Id); + } + + // // SpawnAllEntitities // @@ -366,24 +542,6 @@ namespace UnitTest } } - TEST_F(SpawnableEntitiesManagerTest, EntitySpawnTicket_Move_Works) - { - AzFramework::EntitySpawnTicket ticket1(*m_spawnableAsset); - AzFramework::EntitySpawnTicket ticket2(*m_spawnableAsset); - - const AzFramework::EntitySpawnTicket::Id ticket1Id = ticket1.GetId(); - const AzFramework::EntitySpawnTicket::Id ticket2Id = ticket2.GetId(); - - AzFramework::EntitySpawnTicket ticketMoveConstructor(AZStd::move(ticket1)); - EXPECT_TRUE(ticketMoveConstructor.IsValid()); - EXPECT_EQ(ticketMoveConstructor.GetId(), ticket1Id); - - AzFramework::EntitySpawnTicket ticketMoveOperator; - ticketMoveOperator = AZStd::move(ticket2); - EXPECT_TRUE(ticketMoveOperator.IsValid()); - EXPECT_EQ(ticketMoveOperator.GetId(), ticket2Id); - } - TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_DeleteTicketBeforeCall_NoCrash) { { @@ -393,6 +551,135 @@ namespace UnitTest m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); } + TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithDisabled_NoEntitiesSpawned) + { + using namespace AzFramework; + static constexpr size_t NumEntities = 4; + FillSpawnable(NumEntities); + InsertEntityAliases( + { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, + { Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable, + Spawnable::EntityAliasType::Disable }); + + size_t spawnedEntitiesCount = 0; + auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) + { + spawnedEntitiesCount += entities.size(); + }; + AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(callback); + m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + + EXPECT_EQ(0, spawnedEntitiesCount); + } + + TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_SomeAliasesWithDisabled_RegularEntitiesAreSpawned) + { + using namespace AzFramework; + static constexpr size_t NumEntities = 8; + FillSpawnable(NumEntities); + InsertEntityAliases<2>({ 1, 3 }, { 1, 3 }, { Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable }); + + size_t spawnedEntitiesCount = 0; + auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) + { + spawnedEntitiesCount += entities.size(); + }; + AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(callback); + m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + + EXPECT_EQ(6, spawnedEntitiesCount); + } + + TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithReplace_EntitiesSpawnedFromTarget) + { + using namespace AzFramework; + static constexpr size_t NumEntities = 4; + FillSpawnable(NumEntities); + AZ::Data::Asset target = CreateTargetSpawnable(4); + InsertEntityAliases<4>( + { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, + { Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, + Spawnable::EntityAliasType::Replace }, + &target); + + size_t spawnedEntitiesCount = 0; + bool allReplaced = false; + auto callback = [&spawnedEntitiesCount, &allReplaced]( + AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) + { + spawnedEntitiesCount += entities.size(); + allReplaced = AreAllEntitiesReplaced(entities); + }; + AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(callback); + m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + + EXPECT_EQ(4, spawnedEntitiesCount); + EXPECT_TRUE(allReplaced); + } + + TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithAdditional_SourceAndTargetComponentsMerged) + { + using namespace AzFramework; + static constexpr size_t NumEntities = 4; + FillSpawnable(NumEntities); + AZ::Data::Asset target = CreateTargetSpawnable(4); + InsertEntityAliases<4>( + { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, + { Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, + Spawnable::EntityAliasType::Additional }, + &target); + + size_t spawnedEntitiesCount = 0; + bool allAdded = false; + auto callback = [&spawnedEntitiesCount, &allAdded]( + AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) + { + spawnedEntitiesCount += entities.size(); + allAdded = IsEveryOtherEntityAReplacement(entities); + }; + AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(callback); + m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + + EXPECT_EQ(8, spawnedEntitiesCount); + EXPECT_TRUE(allAdded); + } + + TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithMerge_SourceAndTargetComponentsMerged) + { + using namespace AzFramework; + static constexpr size_t NumEntities = 4; + FillSpawnable(NumEntities); + AZ::Data::Asset target = CreateTargetSpawnable(4); + InsertEntityAliases<4>( + { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, + { Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge, + Spawnable::EntityAliasType::Merge }, + &target); + + size_t spawnedEntitiesCount = 0; + bool allMerged = false; + auto callback = [&spawnedEntitiesCount, &allMerged]( + AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) + { + spawnedEntitiesCount += entities.size(); + allMerged = AreAllMerged(entities); + }; + AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(callback); + m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + + EXPECT_EQ(4, spawnedEntitiesCount); + EXPECT_TRUE(allMerged); + } // // SpawnEntities @@ -403,7 +690,7 @@ namespace UnitTest static constexpr size_t NumEntities = 4; FillSpawnable(NumEntities); - AZStd::vector indices = { 0, 2, 3, 1 }; + AZStd::vector indices = { 0, 2, 3, 1 }; size_t spawnedEntitiesCount = 0; auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) @@ -423,7 +710,7 @@ namespace UnitTest static constexpr size_t NumEntities = 1; FillSpawnable(NumEntities); - AZStd::vector indices = { 0, 0 }; + AZStd::vector indices = { 0, 0 }; size_t spawnedEntitiesCount = 0; auto callback = @@ -444,7 +731,7 @@ namespace UnitTest static constexpr size_t NumEntities = 4; FillSpawnable(NumEntities); - AZStd::vector indices = { 0, 2, 3, 1 }; + AZStd::vector indices = { 0, 2, 3, 1 }; size_t spawnedEntitiesCount = 0; auto callback = @@ -467,7 +754,7 @@ namespace UnitTest FillSpawnable(NumEntities); CreateSingleParent(); - AZStd::vector indices = { 0, 1, 2, 3 }; + AZStd::vector indices = { 0, 1, 2, 3 }; AZStd::vector parents; auto callback = [&parents](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) @@ -499,7 +786,7 @@ namespace UnitTest FillSpawnable(NumEntities); CreateSingleParent(); - AZStd::vector indices = { 0, 1, 2, 3 }; + AZStd::vector indices = { 0, 1, 2, 3 }; AZStd::vector parents; auto callback = @@ -754,6 +1041,148 @@ namespace UnitTest m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); } + TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithDisabled_NoEntitiesSpawned) + { + using namespace AzFramework; + static constexpr size_t NumEntities = 4; + FillSpawnable(NumEntities); + + InsertEntityAliases( + { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, + { Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable, + Spawnable::EntityAliasType::Disable }); + + AZStd::vector indices = { 0, 2, 3, 1 }; + + size_t spawnedEntitiesCount = 0; + auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) + { + spawnedEntitiesCount += entities.size(); + }; + AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(callback); + m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + + EXPECT_EQ(0, spawnedEntitiesCount); + } + + TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_SomeAliasesWithDisabled_RegularEntitiesAreSpawned) + { + using namespace AzFramework; + FillSpawnable(8); + InsertEntityAliases<3>( + { 1, 3, 6 }, { 1, 3, 6 }, + { Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable }); + + AZStd::vector indices = { 0, 2, 3, 1, 2, 3, 0, 1, 6, 4, 5, 7, 4, 1, 0, 6 }; + + size_t spawnedEntitiesCount = 0; + auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) + { + spawnedEntitiesCount += entities.size(); + }; + AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(callback); + m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + + EXPECT_EQ(9, spawnedEntitiesCount); + } + + TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithReplace_EntitiesSpawnedFromTarget) + { + using namespace AzFramework; + static constexpr size_t NumEntities = 4; + FillSpawnable(NumEntities); + AZ::Data::Asset target = CreateTargetSpawnable(4); + InsertEntityAliases<4>( + { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, + { Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, + Spawnable::EntityAliasType::Replace }, + &target); + + AZStd::vector indices = { 0, 2, 3, 1 }; + + size_t spawnedEntitiesCount = 0; + bool allReplaced = false; + auto callback = [&spawnedEntitiesCount, &allReplaced]( + AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) + { + spawnedEntitiesCount += entities.size(); + allReplaced = AreAllEntitiesReplaced(entities); + }; + AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(callback); + m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + + EXPECT_EQ(4, spawnedEntitiesCount); + EXPECT_TRUE(allReplaced); + } + + TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithAdditional_SourceAndTargetComponentsMerged) + { + using namespace AzFramework; + static constexpr size_t NumEntities = 4; + FillSpawnable(NumEntities); + AZ::Data::Asset target = CreateTargetSpawnable(4); + InsertEntityAliases<4>( + { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, + { Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, + Spawnable::EntityAliasType::Additional }, + &target); + + AZStd::vector indices = { 0, 2, 3, 1 }; + + size_t spawnedEntitiesCount = 0; + bool allAdded = false; + auto callback = + [&spawnedEntitiesCount, &allAdded]( + AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) + { + spawnedEntitiesCount += entities.size(); + allAdded = IsEveryOtherEntityAReplacement(entities); + }; + AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(callback); + m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + + EXPECT_EQ(8, spawnedEntitiesCount); + EXPECT_TRUE(allAdded); + } + + TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithMerge_SourceAndTargetComponentsMerged) + { + using namespace AzFramework; + static constexpr size_t NumEntities = 4; + FillSpawnable(NumEntities); + AZ::Data::Asset target = CreateTargetSpawnable(4); + InsertEntityAliases<4>( + { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, + { Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge, + Spawnable::EntityAliasType::Merge }, + &target); + + AZStd::vector indices = { 0, 2, 3, 1 }; + + size_t spawnedEntitiesCount = 0; + bool allMerged = false; + auto callback = [&spawnedEntitiesCount, &allMerged]( + AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities) + { + spawnedEntitiesCount += entities.size(); + allMerged = AreAllMerged(entities); + }; + AzFramework::SpawnEntitiesOptionalArgs optionalArgs; + optionalArgs.m_completionCallback = AZStd::move(callback); + m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs)); + m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular); + + EXPECT_EQ(4, spawnedEntitiesCount); + EXPECT_TRUE(allMerged); + } // // DespawnAllEntities diff --git a/Code/Framework/AzFramework/Tests/Spawnable/SpawnableTests.cpp b/Code/Framework/AzFramework/Tests/Spawnable/SpawnableTests.cpp new file mode 100644 index 0000000000..94641037f0 --- /dev/null +++ b/Code/Framework/AzFramework/Tests/Spawnable/SpawnableTests.cpp @@ -0,0 +1,513 @@ +/* + * 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 +#include +#include +#include + +namespace UnitTest +{ + class SpawnableTest : public AllocatorsFixture + { + public: + static constexpr size_t DefaultEntityAliasTestCount = 8; + + void SetUp() override + { + AllocatorsFixture::SetUp(); + + m_spawnable = aznew AzFramework::Spawnable(); + } + + void TearDown() override + { + delete m_spawnable; + m_spawnable = nullptr; + + AllocatorsFixture::TearDown(); + } + + void InsertEntities(size_t count) + { + AzFramework::Spawnable::EntityList& entities = m_spawnable->GetEntities(); + entities.reserve(entities.size() + count); + for (size_t i = 0; i < count; ++i) + { + entities.emplace_back(AZStd::make_unique()); + } + } + + template + void InsertEntityAliases( + const AZStd::array& sourceIds, + const AZStd::array& targetIds, + const AZStd::array& aliasTypes, + bool queueLoad = false) + { + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + + for (uint32_t i = 0; i < Count; ++i) + { + AZ::Data::Asset spawnable( + AZ::Data::AssetId(AZ::Uuid("{4CBEC17A-52D6-42D5-9037-F4C05B9CE1D9}"), i), azrtti_typeid()); + visitor.AddAlias(spawnable, AZ::Crc32(i), sourceIds[i], targetIds[i], aliasTypes[i], queueLoad); + } + } + + template + void InsertEntityAliases(bool queueLoad) + { + using namespace AzFramework; + + AZStd::array ids; + for (uint32_t i=0; i(Count); ++i) + { + ids[i] = i; + } + + AZStd::array aliasTypes; + for (uint32_t i = 0; i < aznumeric_cast(Count); ++i) + { + aliasTypes[i] = Spawnable::EntityAliasType::Replace; + } + + InsertEntityAliases(ids, ids, aliasTypes, queueLoad); + } + + template + void InsertEntityAliases() + { + InsertEntityAliases(false); + } + + protected: + AzFramework::Spawnable* m_spawnable; + }; + + + // + // TryGetAliasesConst + // + + TEST_F(SpawnableTest, TryGetAliasesConst_GetVisitor_VisitorDataIsAvailable) + { + AzFramework::Spawnable::EntityAliasConstVisitor visitor = m_spawnable->TryGetAliasesConst(); + EXPECT_TRUE(visitor.IsValid()); + } + + TEST_F(SpawnableTest, TryGetAliasesConst_VisitorThatIsNotReadShared_VisitorDataIsNotAvailable) + { + AzFramework::Spawnable::EntityAliasVisitor readWriteVisitor = m_spawnable->TryGetAliases(); + ASSERT_TRUE(readWriteVisitor.IsValid()); + + AzFramework::Spawnable::EntityAliasConstVisitor visitor = m_spawnable->TryGetAliasesConst(); + EXPECT_FALSE(visitor.IsValid()); + } + + TEST_F(SpawnableTest, TryGetAliasesConst_VisitorThatIsAlreadyReadShared_VisitorDataIsAvailable) + { + AzFramework::Spawnable::EntityAliasConstVisitor readVisitor = m_spawnable->TryGetAliasesConst(); + ASSERT_TRUE(readVisitor.IsValid()); + + AzFramework::Spawnable::EntityAliasConstVisitor visitor = m_spawnable->TryGetAliasesConst(); + EXPECT_TRUE(visitor.IsValid()); + } + + + // + // TryGetAliases + // + + TEST_F(SpawnableTest, TryGetAliases_GetVisitor_VisitorDataIsAvailable) + { + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + EXPECT_TRUE(visitor.IsValid()); + } + + TEST_F(SpawnableTest, TryGetAliasesConst_VisitorThatIsAlreadyShared_VisitorDataNotIsAvailable) + { + AzFramework::Spawnable::EntityAliasConstVisitor readVisitor = m_spawnable->TryGetAliasesConst(); + ASSERT_TRUE(readVisitor.IsValid()); + + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + EXPECT_FALSE(visitor.IsValid()); + } + + + // + // EntityAliasVisitor + // + + + // + // HasAliases + // + + TEST_F(SpawnableTest, EntityAliasVisitor_HasAliases_EmptyAliasList_ReturnsFalse) + { + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + ASSERT_TRUE(visitor.IsValid()); + + EXPECT_FALSE(visitor.HasAliases()); + } + + TEST_F(SpawnableTest, EntityAliasVisitor_HasAliases_FilledInAliasList_ReturnsTrue) + { + InsertEntities(8); + InsertEntityAliases<8>(); + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + ASSERT_TRUE(visitor.IsValid()); + + EXPECT_TRUE(visitor.HasAliases()); + } + + + // + // Optimize + // + + TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_SortEntityAliases_AliasesAreSortedBySourceAndTargetId) + { + InsertEntities(DefaultEntityAliasTestCount); + InsertEntityAliases(); + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + ASSERT_TRUE(visitor.IsValid()); + + // Optimize doesn't need to be explicitly called because the setup of the aliases will cause the alias list to be sorted and optimized. + + uint32_t sourceIndex = 0; + uint32_t targetIndex = 0; + for (const AzFramework::Spawnable::EntityAlias& alias : visitor) + { + if (alias.m_sourceIndex != sourceIndex) + { + ASSERT_LE(sourceIndex, alias.m_sourceIndex); + } + else + { + ASSERT_LE(targetIndex, alias.m_targetIndex); + } + sourceIndex = alias.m_sourceIndex; + targetIndex = alias.m_targetIndex; + } + } + + TEST_F( + SpawnableTest, EntityAliasVisitor_Optimize_RemoveUnused_OnlySecondToLastAliasRemains) + { + using namespace AzFramework; + InsertEntities(8); + InsertEntityAliases<8>( + { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 1, 2, 3, 4, 5, 6, 7 }, + { Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Disable, + Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Disable, + Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Original }); + + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + ASSERT_TRUE(visitor.IsValid()); + + EXPECT_EQ(1, AZStd::distance(visitor.begin(), visitor.end())); + EXPECT_EQ(Spawnable::EntityAliasType::Replace, visitor.begin()->m_aliasType); + EXPECT_EQ(6, visitor.begin()->m_targetIndex); + } + + TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_AddAdditional_ThreeAdditionalAliasesAreAdded) + { + using namespace AzFramework; + InsertEntities(8); + InsertEntityAliases<8>( + { 0, 0, 0, 0, 1, 2, 2, 2 }, { 0, 1, 2, 3, 4, 5, 6, 7 }, + { Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, + Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, + Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional }); + + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + ASSERT_TRUE(visitor.IsValid()); + + EXPECT_EQ(11, AZStd::distance(visitor.begin(), visitor.end())); + EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()->m_aliasType); + EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()[5].m_aliasType); + EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()[7].m_aliasType); + } + + TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_OriginalsOnly_AliasListIsEmpty) + { + using namespace AzFramework; + InsertEntities(8); + InsertEntityAliases<8>( + { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 1, 2, 3, 4, 5, 6, 7 }, + { Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original, + Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original, + Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original }); + + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + ASSERT_TRUE(visitor.IsValid()); + + EXPECT_EQ(0, AZStd::distance(visitor.begin(), visitor.end())); + } + + TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_MixedOriginals_AllOriginalsRemoved) + { + using namespace AzFramework; + InsertEntities(8); + InsertEntityAliases<8>( + { 0, 0, 0, 1, 1, 2, 2, 2 }, { 0, 1, 2, 3, 4, 5, 6, 7 }, + { Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original, + Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Original, + Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Original }); + + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + ASSERT_TRUE(visitor.IsValid()); + + EXPECT_EQ(2, AZStd::distance(visitor.begin(), visitor.end())); + EXPECT_EQ(Spawnable::EntityAliasType::Disable, visitor.begin()->m_aliasType); + EXPECT_EQ(Spawnable::EntityAliasType::Replace, visitor.begin()[1].m_aliasType); + } + + TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_MergeAfterOriginal_NoAdditionalOriginalIsInserted) + { + using namespace AzFramework; + InsertEntities(8); + InsertEntityAliases<8>( + { 0, 0, 1, 1, 2, 2, 2, 2 }, { 0, 1, 2, 3, 4, 5, 6, 7 }, + { Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Replace, + Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original, + Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original }); + + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + ASSERT_TRUE(visitor.IsValid()); + + EXPECT_EQ(4, AZStd::distance(visitor.begin(), visitor.end())); + EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()->m_aliasType); + EXPECT_EQ(Spawnable::EntityAliasType::Merge, visitor.begin()[1].m_aliasType); + EXPECT_EQ(Spawnable::EntityAliasType::Replace, visitor.begin()[2].m_aliasType); + EXPECT_EQ(Spawnable::EntityAliasType::Merge, visitor.begin()[3].m_aliasType); + } + + + // + // UpdateAliasType + // + + TEST_F(SpawnableTest, EntityAliasVisitor_UpdateAliasType_AllToOriginal_NoAliasesAfterOptimization) + { + using namespace AzFramework; + InsertEntities(8); + InsertEntityAliases<8>( + { 0, 1, 2, 3, 4, 5, 6, 7 }, { 0, 1, 2, 3, 4, 5, 6, 7 }, + { Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, + Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, + Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace }); + + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + ASSERT_TRUE(visitor.IsValid()); + + for (uint32_t i = 0; i < 8; ++i) + { + visitor.UpdateAliasType(i, Spawnable::EntityAliasType::Original); + } + + for (const Spawnable::EntityAlias& alias : visitor) + { + EXPECT_EQ(Spawnable::EntityAliasType::Original, alias.m_aliasType); + } + + visitor.Optimize(); + + EXPECT_EQ(0, AZStd::distance(visitor.begin(), visitor.end())); + } + + + // + // UpdateAliases + // + + TEST_F(SpawnableTest, EntityAliasVisitor_UpdateAliases_AllToOriginal_NoAliasesAfterOptimization) + { + using namespace AzFramework; + InsertEntities(8); + InsertEntityAliases<8>( + { 0, 1, 2, 3, 4, 5, 6, 7 }, { 0, 1, 2, 3, 4, 5, 6, 7 }, + { Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, + Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, + Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace }); + + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + ASSERT_TRUE(visitor.IsValid()); + + auto callback = + [](Spawnable::EntityAliasType& aliasType, bool& /*queueLoad*/, const AZ::Data::Asset& /*aliasedSpawnable*/, + const AZ::Crc32 /*tag*/, const uint32_t /*sourceIndex*/, const uint32_t /*targetIndex*/) + { + aliasType = Spawnable::EntityAliasType::Original; + }; + visitor.UpdateAliases(AZStd::move(callback)); + + for (const Spawnable::EntityAlias& alias : visitor) + { + EXPECT_EQ(Spawnable::EntityAliasType::Original, alias.m_aliasType); + } + + visitor.Optimize(); + + EXPECT_EQ(0, AZStd::distance(visitor.begin(), visitor.end())); + } + + TEST_F(SpawnableTest, EntityAliasVisitor_UpdateAliases_FilterByTag_OnlyOneAliasUpdated) + { + using namespace AzFramework; + InsertEntities(8); + InsertEntityAliases<8>( + { 0, 1, 2, 3, 4, 5, 6, 7 }, { 0, 1, 2, 3, 4, 5, 6, 7 }, + { Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, + Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, + Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace }); + + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + ASSERT_TRUE(visitor.IsValid()); + + bool correctTag = false; + size_t numberOfUpdates = 0; + auto callback = [&correctTag, &numberOfUpdates](Spawnable::EntityAliasType& aliasType, bool& /*queueLoad*/, + const AZ::Data::Asset& /*aliasedSpawnable*/, const AZ::Crc32 tag, const uint32_t /*sourceIndex*/, + const uint32_t /*targetIndex*/) + { + correctTag = (tag == AZ::Crc32(3)); + numberOfUpdates++; + aliasType = Spawnable::EntityAliasType::Original; + }; + visitor.UpdateAliases(AZ::Crc32(3), AZStd::move(callback)); + + EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()[3].m_aliasType); + } + + + // + // AreAllSpawnablesReady + // + + TEST_F(SpawnableTest, EntityAliasVisitor_AreAllSpawnablesReady_CheckFakeLoadedAssets_ReturnsTrue) + { + using namespace AzFramework; + InsertEntities(DefaultEntityAliasTestCount); + InsertEntityAliases(); + + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + ASSERT_TRUE(visitor.IsValid()); + + EXPECT_TRUE(visitor.AreAllSpawnablesReady()); + } + + TEST_F(SpawnableTest, EntityAliasVisitor_AreAllSpawnablesReady_CheckFakeNotLoadedAssets_ReturnsFalse) + { + using namespace AzFramework; + InsertEntities(8); + InsertEntityAliases<8>(true); + + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + ASSERT_TRUE(visitor.IsValid()); + + EXPECT_FALSE(visitor.AreAllSpawnablesReady()); + } + + + // + // ListTargetSpawnables + // + + TEST_F(SpawnableTest, EntityAliasVisitor_ListTargetSpawnables_ListAllTargetAssets_AllTargetsListed) + { + using namespace AzFramework; + InsertEntities(DefaultEntityAliasTestCount); + InsertEntityAliases(); + + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + ASSERT_TRUE(visitor.IsValid()); + + size_t count = 0; + bool correctAssets = true; + auto callback = [&count, &correctAssets](const AZ::Data::Asset& targetSpawnable) + { + correctAssets = correctAssets && (targetSpawnable.GetId().m_subId == count); + count++; + }; + visitor.ListTargetSpawnables(callback); + + EXPECT_EQ(8, count); + EXPECT_TRUE(correctAssets); + } + + TEST_F(SpawnableTest, EntityAliasVisitor_ListTargetSpawnables_ListTaggedTargetAssets_OneAssetListed) + { + using namespace AzFramework; + InsertEntities(DefaultEntityAliasTestCount); + InsertEntityAliases(); + + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + ASSERT_TRUE(visitor.IsValid()); + + size_t count = 0; + bool correctAsset = false; + auto callback = [&count, &correctAsset](const AZ::Data::Asset& targetSpawnable) + { + correctAsset = (targetSpawnable.GetId().m_subId == 3); + count++; + }; + visitor.ListTargetSpawnables(AZ::Crc32(3), callback); + + EXPECT_EQ(1, count); + EXPECT_TRUE(correctAsset); + } + + + // + // ListSpawnablesRequiringLoad + // + + TEST_F(SpawnableTest, EntityAliasVisitor_ListSpawnablesRequiringLoad_AllSetToLoaded_AllTargetsListed) + { + using namespace AzFramework; + InsertEntities(DefaultEntityAliasTestCount); + InsertEntityAliases(true); + + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + ASSERT_TRUE(visitor.IsValid()); + + size_t count = 0; + bool correctAssets = true; + auto callback = [&count, &correctAssets](const AZ::Data::Asset& targetSpawnable) + { + correctAssets = correctAssets && (targetSpawnable.GetId().m_subId == count); + count++; + }; + visitor.ListSpawnablesRequiringLoad(callback); + + EXPECT_EQ(8, count); + EXPECT_TRUE(correctAssets); + } + + TEST_F(SpawnableTest, EntityAliasVisitor_ListSpawnablesRequiringLoad_AllSetToNotLoaded_NoTargetsListed) + { + using namespace AzFramework; + InsertEntities(DefaultEntityAliasTestCount); + InsertEntityAliases(false); + + AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases(); + ASSERT_TRUE(visitor.IsValid()); + + size_t count = 0; + auto callback = [&count](const AZ::Data::Asset& /*targetSpawnable*/) + { + count++; + }; + visitor.ListSpawnablesRequiringLoad(callback); + + EXPECT_EQ(0, count); + } +} // namespace UnitTest diff --git a/Code/Framework/AzFramework/Tests/frameworktests_files.cmake b/Code/Framework/AzFramework/Tests/frameworktests_files.cmake index 6c4f611352..e4877e34a9 100644 --- a/Code/Framework/AzFramework/Tests/frameworktests_files.cmake +++ b/Code/Framework/AzFramework/Tests/frameworktests_files.cmake @@ -10,6 +10,7 @@ set(FILES Main.cpp Spawnable/SpawnableEntitiesInterfaceTests.cpp Spawnable/SpawnableEntitiesManagerTests.cpp + Spawnable/SpawnableTests.cpp ArchiveCompressionTests.cpp ArchiveTests.cpp BehaviorEntityTests.cpp diff --git a/Code/Editor/Lib/Tests/Camera/editor_lib_camera_test_files.cmake b/Code/Framework/AzFramework/Tests/terrain_mock_files.cmake similarity index 82% rename from Code/Editor/Lib/Tests/Camera/editor_lib_camera_test_files.cmake rename to Code/Framework/AzFramework/Tests/terrain_mock_files.cmake index 69d3e37f2d..590c64d88b 100644 --- a/Code/Editor/Lib/Tests/Camera/editor_lib_camera_test_files.cmake +++ b/Code/Framework/AzFramework/Tests/terrain_mock_files.cmake @@ -7,5 +7,5 @@ # set(FILES - test_EditorCamera.cpp -) + Mocks/Terrain/MockTerrainDataRequestBus.h +) \ No newline at end of file diff --git a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ActionDispatcher.h b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ActionDispatcher.h index 06db67a05f..99f11c37da 100644 --- a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ActionDispatcher.h +++ b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ActionDispatcher.h @@ -39,6 +39,10 @@ namespace AzManipulatorTestFramework DerivedDispatcherT* MouseLButtonDown(); //! Set the left mouse button up. DerivedDispatcherT* MouseLButtonUp(); + //! Set the middle mouse button down. + DerivedDispatcherT* MouseMButtonDown(); + //! Set the middle mouse button up. + DerivedDispatcherT* MouseMButtonUp(); //! Send a double click event. DerivedDispatcherT* MouseLButtonDoubleClick(); //! Set the keyboard modifier button down. @@ -73,6 +77,8 @@ namespace AzManipulatorTestFramework virtual void CameraStateImpl(const AzFramework::CameraState& cameraState) = 0; virtual void MouseLButtonDownImpl() = 0; virtual void MouseLButtonUpImpl() = 0; + virtual void MouseMButtonDownImpl() = 0; + virtual void MouseMButtonUpImpl() = 0; virtual void MouseLButtonDoubleClickImpl() = 0; virtual void MousePositionImpl(const AzFramework::ScreenPoint& position) = 0; virtual void KeyboardModifierDownImpl(const AzToolsFramework::ViewportInteraction::KeyboardModifier& keyModifier) = 0; @@ -183,6 +189,22 @@ namespace AzManipulatorTestFramework return static_cast(this); } + template + DerivedDispatcherT* ActionDispatcher::MouseMButtonDown() + { + Log("Mouse middle button down"); + MouseMButtonDownImpl(); + return static_cast(this); + } + + template + DerivedDispatcherT* ActionDispatcher::MouseMButtonUp() + { + Log("Mouse middle button up"); + MouseMButtonUpImpl(); + return static_cast(this); + } + template DerivedDispatcherT* ActionDispatcher::MouseLButtonDoubleClick() { diff --git a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/AzManipulatorTestFramework.h b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/AzManipulatorTestFramework.h index 5f0179d6ad..66227c692b 100644 --- a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/AzManipulatorTestFramework.h +++ b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/AzManipulatorTestFramework.h @@ -8,6 +8,7 @@ #pragma once +#include #include #include #include @@ -40,7 +41,7 @@ namespace AzManipulatorTestFramework //! Set the angular step. virtual void SetAngularStep(float step) = 0; //! Get the viewport id. - virtual int GetViewportId() const = 0; + virtual AzFramework::ViewportId GetViewportId() const = 0; //! Updates the visibility state. //! Updates which entities are currently visible given the current camera state. virtual void UpdateVisibility() = 0; diff --git a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/AzManipulatorTestFrameworkTestHelpers.h b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/AzManipulatorTestFrameworkTestHelpers.h index f87f83c1b2..63392eb82f 100644 --- a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/AzManipulatorTestFrameworkTestHelpers.h +++ b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/AzManipulatorTestFrameworkTestHelpers.h @@ -29,7 +29,8 @@ namespace UnitTest void SetUpEditorFixtureImpl() override { ToolsApplicationFixtureT::SetUpEditorFixtureImpl(); - m_viewportManipulatorInteraction = AZStd::make_unique(); + m_viewportManipulatorInteraction = + AZStd::make_unique(ToolsApplicationFixtureT::CreateDebugDisplayRequests()); m_actionDispatcher = AZStd::make_unique(*m_viewportManipulatorInteraction); m_cameraState = AzFramework::CreateIdentityDefaultCamera(AZ::Vector3::CreateZero(), AzManipulatorTestFramework::DefaultViewportSize); diff --git a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/DirectManipulatorViewportInteraction.h b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/DirectManipulatorViewportInteraction.h index 3421116b96..33afee695b 100644 --- a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/DirectManipulatorViewportInteraction.h +++ b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/DirectManipulatorViewportInteraction.h @@ -17,11 +17,10 @@ namespace AzManipulatorTestFramework class ViewportInteraction; //! Implementation of manipulator viewport interaction that manipulates the manager directly. - class DirectCallManipulatorViewportInteraction - : public ManipulatorViewportInteraction + class DirectCallManipulatorViewportInteraction : public ManipulatorViewportInteraction { public: - DirectCallManipulatorViewportInteraction(); + explicit DirectCallManipulatorViewportInteraction(AZStd::shared_ptr debugDisplayRequests); ~DirectCallManipulatorViewportInteraction(); // ManipulatorViewportInteractionInterface ... @@ -30,7 +29,7 @@ namespace AzManipulatorTestFramework private: AZStd::shared_ptr m_customManager; - std::unique_ptr m_viewportInteraction; - std::unique_ptr m_manipulatorManager; + AZStd::unique_ptr m_viewportInteraction; + AZStd::unique_ptr m_manipulatorManager; }; } // namespace AzManipulatorTestFramework diff --git a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ImmediateModeActionDispatcher.h b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ImmediateModeActionDispatcher.h index 5d1f606e8c..8a05a15fd9 100644 --- a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ImmediateModeActionDispatcher.h +++ b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ImmediateModeActionDispatcher.h @@ -25,7 +25,7 @@ namespace AzManipulatorTestFramework using MouseInteractionEvent = AzToolsFramework::ViewportInteraction::MouseInteractionEvent; public: - explicit ImmediateModeActionDispatcher(ManipulatorViewportInteraction& viewportManipulatorInteraction); + explicit ImmediateModeActionDispatcher(ManipulatorViewportInteraction& manipulatorViewportInteraction); ~ImmediateModeActionDispatcher(); //! Clear the current event state. @@ -55,13 +55,15 @@ namespace AzManipulatorTestFramework AZStd::chrono::milliseconds EditorViewportInputTimeNow() override; protected: - // ActionDispatcher ... + // ActionDispatcher overrides ... void SetSnapToGridImpl(bool enabled) override; void SetStickySelectImpl(bool enabled) override; void GridSizeImpl(float size) override; void CameraStateImpl(const AzFramework::CameraState& cameraState) override; void MouseLButtonDownImpl() override; void MouseLButtonUpImpl() override; + void MouseMButtonDownImpl() override; + void MouseMButtonUpImpl() override; void MouseLButtonDoubleClickImpl() override; void MousePositionImpl(const AzFramework::ScreenPoint& position) override; void KeyboardModifierDownImpl(const KeyboardModifier& keyModifier) override; @@ -82,7 +84,7 @@ namespace AzManipulatorTestFramework const MouseInteractionEvent* GetMouseInteractionEvent() const; mutable AZStd::unique_ptr m_event; - ManipulatorViewportInteraction& m_viewportManipulatorInteraction; + ManipulatorViewportInteraction& m_manipulatorViewportInteraction; //! Current time that ticks up after each call to EditorViewportInputTimeNow. AZStd::chrono::milliseconds m_timeNow = AZStd::chrono::milliseconds(0); diff --git a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/IndirectManipulatorViewportInteraction.h b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/IndirectManipulatorViewportInteraction.h index a7b1be2c7d..da2804dfd5 100644 --- a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/IndirectManipulatorViewportInteraction.h +++ b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/IndirectManipulatorViewportInteraction.h @@ -21,7 +21,7 @@ namespace AzManipulatorTestFramework class IndirectCallManipulatorViewportInteraction : public ManipulatorViewportInteraction { public: - IndirectCallManipulatorViewportInteraction(); + explicit IndirectCallManipulatorViewportInteraction(AZStd::shared_ptr debugDisplayRequests); ~IndirectCallManipulatorViewportInteraction(); // ManipulatorViewportInteractionInterface ... diff --git a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ViewportInteraction.h b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ViewportInteraction.h index 3c41b28851..1568c1a931 100644 --- a/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ViewportInteraction.h +++ b/Code/Framework/AzManipulatorTestFramework/Include/AzManipulatorTestFramework/ViewportInteraction.h @@ -11,10 +11,13 @@ #include #include +namespace AzFramework +{ + class DebugDisplayRequests; +} + namespace AzManipulatorTestFramework { - class NullDebugDisplayRequests; - //! Implementation of the viewport interaction model to handle viewport interaction requests. class ViewportInteraction : public ViewportInteractionInterface @@ -23,7 +26,7 @@ namespace AzManipulatorTestFramework , private AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler { public: - ViewportInteraction(); + explicit ViewportInteraction(AZStd::shared_ptr debugDisplayRequests); ~ViewportInteraction(); // ViewportInteractionInterface overrides ... @@ -33,7 +36,7 @@ namespace AzManipulatorTestFramework void SetAngularSnapping(bool enabled) override; void SetGridSize(float size) override; void SetAngularStep(float step) override; - int GetViewportId() const override; + AzFramework::ViewportId GetViewportId() const override; void UpdateVisibility() override; void SetStickySelect(bool enabled) override; AZ::Vector3 DefaultEditorCameraPosition() const override; @@ -60,9 +63,10 @@ namespace AzManipulatorTestFramework void FindVisibleEntities(AZStd::vector& visibleEntities) override; private: + static constexpr AzFramework::ViewportId m_viewportId = 1234; //!< Arbitrary viewport id for manipulator tests. + AzFramework::EntityVisibilityQuery m_entityVisibilityQuery; - AZStd::unique_ptr m_nullDebugDisplayRequests; - const int m_viewportId = 1234; // Arbitrary viewport id for manipulator tests + AZStd::shared_ptr m_debugDisplayRequests; AzFramework::CameraState m_cameraState; bool m_gridSnapping = false; bool m_angularSnapping = false; diff --git a/Code/Framework/AzManipulatorTestFramework/Source/DirectManipulatorViewportInteraction.cpp b/Code/Framework/AzManipulatorTestFramework/Source/DirectManipulatorViewportInteraction.cpp index c9234fe488..c96840e4c9 100644 --- a/Code/Framework/AzManipulatorTestFramework/Source/DirectManipulatorViewportInteraction.cpp +++ b/Code/Framework/AzManipulatorTestFramework/Source/DirectManipulatorViewportInteraction.cpp @@ -118,10 +118,11 @@ namespace AzManipulatorTestFramework return m_manipulatorManager->Interacting(); } - DirectCallManipulatorViewportInteraction::DirectCallManipulatorViewportInteraction() + DirectCallManipulatorViewportInteraction::DirectCallManipulatorViewportInteraction( + AZStd::shared_ptr debugDisplayRequests) : m_customManager( AZStd::make_unique(AzToolsFramework::ManipulatorManagerId(AZ::Crc32("TestManipulatorManagerId")))) - , m_viewportInteraction(AZStd::make_unique()) + , m_viewportInteraction(AZStd::make_unique(AZStd::move(debugDisplayRequests))) , m_manipulatorManager(AZStd::make_unique(m_viewportInteraction.get(), m_customManager)) { } diff --git a/Code/Framework/AzManipulatorTestFramework/Source/ImmediateModeActionDispatcher.cpp b/Code/Framework/AzManipulatorTestFramework/Source/ImmediateModeActionDispatcher.cpp index 5bfe63bd99..aea93ca342 100644 --- a/Code/Framework/AzManipulatorTestFramework/Source/ImmediateModeActionDispatcher.cpp +++ b/Code/Framework/AzManipulatorTestFramework/Source/ImmediateModeActionDispatcher.cpp @@ -29,8 +29,8 @@ namespace AzManipulatorTestFramework using KeyboardModifier = AzToolsFramework::ViewportInteraction::KeyboardModifier; using MouseInteractionEvent = AzToolsFramework::ViewportInteraction::MouseInteractionEvent; - ImmediateModeActionDispatcher::ImmediateModeActionDispatcher(ManipulatorViewportInteraction& viewportManipulatorInteraction) - : m_viewportManipulatorInteraction(viewportManipulatorInteraction) + ImmediateModeActionDispatcher::ImmediateModeActionDispatcher(ManipulatorViewportInteraction& manipulatorViewportInteraction) + : m_manipulatorViewportInteraction(manipulatorViewportInteraction) { AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusConnect(); AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler::BusConnect(); @@ -48,34 +48,34 @@ namespace AzManipulatorTestFramework // mouse down and mouse up event, to match the editor behavior we insert this event // to ensure the tests are simulating the same environment as the editor GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Move; - m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); + m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); } void ImmediateModeActionDispatcher::SetSnapToGridImpl(const bool enabled) { - m_viewportManipulatorInteraction.GetViewportInteraction().SetGridSnapping(enabled); + m_manipulatorViewportInteraction.GetViewportInteraction().SetGridSnapping(enabled); } void ImmediateModeActionDispatcher::SetStickySelectImpl(const bool enabled) { - m_viewportManipulatorInteraction.GetViewportInteraction().SetStickySelect(enabled); + m_manipulatorViewportInteraction.GetViewportInteraction().SetStickySelect(enabled); } void ImmediateModeActionDispatcher::GridSizeImpl(const float size) { - m_viewportManipulatorInteraction.GetViewportInteraction().SetGridSize(size); + m_manipulatorViewportInteraction.GetViewportInteraction().SetGridSize(size); } void ImmediateModeActionDispatcher::CameraStateImpl(const AzFramework::CameraState& cameraState) { - m_viewportManipulatorInteraction.GetViewportInteraction().SetCameraState(cameraState); + m_manipulatorViewportInteraction.GetViewportInteraction().SetCameraState(cameraState); } void ImmediateModeActionDispatcher::MouseLButtonDownImpl() { ToggleOn(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Left); GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Down; - m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); + m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); // the mouse position will be the same as the previous event, thus the delta will be 0 MouseMoveAfterButton(); } @@ -83,17 +83,35 @@ namespace AzManipulatorTestFramework void ImmediateModeActionDispatcher::MouseLButtonUpImpl() { GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Up; - m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); + m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); ToggleOff(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Left); // the mouse position will be the same as the previous event, thus the delta will be 0 MouseMoveAfterButton(); } + void ImmediateModeActionDispatcher::MouseMButtonDownImpl() + { + ToggleOn(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Middle); + GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Down; + m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); + // the mouse position will be the same as the previous event, thus the delta will be 0 + MouseMoveAfterButton(); + } + + void ImmediateModeActionDispatcher::MouseMButtonUpImpl() + { + GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Up; + m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); + ToggleOff(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Middle); + // the mouse position will be the same as the previous event, thus the delta will be 0 + MouseMoveAfterButton(); + } + void ImmediateModeActionDispatcher::MouseLButtonDoubleClickImpl() { GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::DoubleClick; ToggleOn(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Left); - m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); + m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); ToggleOff(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Left); // the mouse position will be the same as the previous event, thus the delta will be 0 MouseMoveAfterButton(); @@ -101,10 +119,10 @@ namespace AzManipulatorTestFramework void ImmediateModeActionDispatcher::MousePositionImpl(const AzFramework::ScreenPoint& position) { - const auto cameraState = m_viewportManipulatorInteraction.GetViewportInteraction().GetCameraState(); + const auto cameraState = m_manipulatorViewportInteraction.GetViewportInteraction().GetCameraState(); GetMouseInteractionEvent()->m_mouseInteraction.m_mousePick = BuildMousePick(position, cameraState); GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Move; - m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); + m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event); } void ImmediateModeActionDispatcher::KeyboardModifierDownImpl(const KeyboardModifier& keyModifier) @@ -144,7 +162,7 @@ namespace AzManipulatorTestFramework { m_event = AZStd::unique_ptr(AZStd::make_unique()); m_event->m_mouseInteraction.m_interactionId.m_viewportId = - m_viewportManipulatorInteraction.GetViewportInteraction().GetViewportId(); + m_manipulatorViewportInteraction.GetViewportInteraction().GetViewportId(); } return m_event.get(); @@ -178,12 +196,12 @@ namespace AzManipulatorTestFramework void ImmediateModeActionDispatcher::ExpectManipulatorBeingInteractedImpl() { - EXPECT_TRUE(m_viewportManipulatorInteraction.GetManipulatorManager().ManipulatorBeingInteracted()); + EXPECT_TRUE(m_manipulatorViewportInteraction.GetManipulatorManager().ManipulatorBeingInteracted()); } void ImmediateModeActionDispatcher::ExpectManipulatorNotBeingInteractedImpl() { - EXPECT_FALSE(m_viewportManipulatorInteraction.GetManipulatorManager().ManipulatorBeingInteracted()); + EXPECT_FALSE(m_manipulatorViewportInteraction.GetManipulatorManager().ManipulatorBeingInteracted()); } ImmediateModeActionDispatcher* ImmediateModeActionDispatcher::ResetEvent() diff --git a/Code/Framework/AzManipulatorTestFramework/Source/IndirectManipulatorViewportInteraction.cpp b/Code/Framework/AzManipulatorTestFramework/Source/IndirectManipulatorViewportInteraction.cpp index 9bcb8986d5..9c5f84196f 100644 --- a/Code/Framework/AzManipulatorTestFramework/Source/IndirectManipulatorViewportInteraction.cpp +++ b/Code/Framework/AzManipulatorTestFramework/Source/IndirectManipulatorViewportInteraction.cpp @@ -76,8 +76,9 @@ namespace AzManipulatorTestFramework return manipulatorInteracting; } - IndirectCallManipulatorViewportInteraction::IndirectCallManipulatorViewportInteraction() - : m_viewportInteraction(AZStd::make_unique()) + IndirectCallManipulatorViewportInteraction::IndirectCallManipulatorViewportInteraction( + AZStd::shared_ptr debugDisplayRequests) + : m_viewportInteraction(AZStd::make_unique(AZStd::move(debugDisplayRequests))) , m_manipulatorManager(AZStd::make_unique(*m_viewportInteraction)) { } diff --git a/Code/Framework/AzManipulatorTestFramework/Source/ViewportInteraction.cpp b/Code/Framework/AzManipulatorTestFramework/Source/ViewportInteraction.cpp index 08cae0c738..1a28c8cad1 100644 --- a/Code/Framework/AzManipulatorTestFramework/Source/ViewportInteraction.cpp +++ b/Code/Framework/AzManipulatorTestFramework/Source/ViewportInteraction.cpp @@ -13,15 +13,8 @@ namespace AzManipulatorTestFramework { - // Null debug display for dummy draw calls - class NullDebugDisplayRequests : public AzFramework::DebugDisplayRequests - { - public: - virtual ~NullDebugDisplayRequests() = default; - }; - - ViewportInteraction::ViewportInteraction() - : m_nullDebugDisplayRequests(AZStd::make_unique()) + ViewportInteraction::ViewportInteraction(AZStd::shared_ptr debugDisplayRequests) + : m_debugDisplayRequests(AZStd::move(debugDisplayRequests)) { AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler::BusConnect(m_viewportId); AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Handler::BusConnect(m_viewportId); @@ -102,7 +95,7 @@ namespace AzManipulatorTestFramework AzFramework::DebugDisplayRequests& ViewportInteraction::GetDebugDisplay() { - return *m_nullDebugDisplayRequests; + return *m_debugDisplayRequests; } void ViewportInteraction::SetGridSnapping(const bool enabled) @@ -135,20 +128,20 @@ namespace AzManipulatorTestFramework m_angularStep = step; } - int ViewportInteraction::GetViewportId() const + AzFramework::ViewportId ViewportInteraction::GetViewportId() const { return m_viewportId; } AZ::Vector3 ViewportInteraction::ViewportScreenToWorld([[maybe_unused]] const AzFramework::ScreenPoint& screenPosition) { - return AZ::Vector3::CreateZero(); + return AzFramework::ScreenToWorld(screenPosition, m_cameraState); } AzToolsFramework::ViewportInteraction::ProjectedViewportRay ViewportInteraction::ViewportScreenToWorldRay( [[maybe_unused]] const AzFramework::ScreenPoint& screenPosition) { - return {}; + return AzToolsFramework::ViewportInteraction::ViewportScreenToWorldRay(m_cameraState, screenPosition); } float ViewportInteraction::DeviceScalingFactor() diff --git a/Code/Framework/AzManipulatorTestFramework/Tests/GridSnappingTest.cpp b/Code/Framework/AzManipulatorTestFramework/Tests/GridSnappingTest.cpp index 4af66edc0a..8707d08bb3 100644 --- a/Code/Framework/AzManipulatorTestFramework/Tests/GridSnappingTest.cpp +++ b/Code/Framework/AzManipulatorTestFramework/Tests/GridSnappingTest.cpp @@ -26,7 +26,8 @@ namespace UnitTest { public: GridSnappingFixture() - : m_viewportManipulatorInteraction(AZStd::make_unique()) + : m_viewportManipulatorInteraction(AZStd::make_unique( + AZStd::make_shared())) , m_actionDispatcher( AZStd::make_unique(*m_viewportManipulatorInteraction)) { diff --git a/Code/Framework/AzManipulatorTestFramework/Tests/ViewportInteractionTest.cpp b/Code/Framework/AzManipulatorTestFramework/Tests/ViewportInteractionTest.cpp index 38c3e33977..481647f960 100644 --- a/Code/Framework/AzManipulatorTestFramework/Tests/ViewportInteractionTest.cpp +++ b/Code/Framework/AzManipulatorTestFramework/Tests/ViewportInteractionTest.cpp @@ -15,7 +15,8 @@ namespace UnitTest { public: AValidViewportInteraction() - : m_viewportInteraction(AZStd::make_unique()) + : m_viewportInteraction( + AZStd::make_unique(AZStd::make_shared())) { } diff --git a/Code/Framework/AzManipulatorTestFramework/Tests/WorldSpaceBuilderTest.cpp b/Code/Framework/AzManipulatorTestFramework/Tests/WorldSpaceBuilderTest.cpp index 376c18072e..95afe0e47d 100644 --- a/Code/Framework/AzManipulatorTestFramework/Tests/WorldSpaceBuilderTest.cpp +++ b/Code/Framework/AzManipulatorTestFramework/Tests/WorldSpaceBuilderTest.cpp @@ -75,9 +75,11 @@ namespace UnitTest void SetUpEditorFixtureImpl() override { m_directState = - AZStd::make_unique(AZStd::make_unique()); + AZStd::make_unique(AZStd::make_unique( + AZStd::make_shared())); m_busState = - AZStd::make_unique(AZStd::make_unique()); + AZStd::make_unique(AZStd::make_unique( + AZStd::make_shared())); m_cameraState = AzFramework::CreateIdentityDefaultCamera(AZ::Vector3::CreateZero(), AzManipulatorTestFramework::DefaultViewportSize); } diff --git a/Code/Framework/AzNetworking/AzNetworking/AutoGen/CorePackets.AutoPackets.xml b/Code/Framework/AzNetworking/AzNetworking/AutoGen/CorePackets.AutoPackets.xml index 8ce3e5ad86..ae025b67e3 100644 --- a/Code/Framework/AzNetworking/AzNetworking/AutoGen/CorePackets.AutoPackets.xml +++ b/Code/Framework/AzNetworking/AzNetworking/AutoGen/CorePackets.AutoPackets.xml @@ -13,7 +13,9 @@ - + + + diff --git a/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.cpp b/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.cpp index 3a3ab06d02..6705ec8b36 100644 --- a/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.cpp @@ -42,7 +42,7 @@ namespace AzNetworking { const uint32_t sampleAtom = 1 - m_activeAtom; - if (m_atoms[sampleAtom].m_timeAccumulatorMs == AZ::TimeMs{0}) + if (m_atoms[sampleAtom].m_timeAccumulatorMs == AZ::Time::ZeroTimeMs) { return 0.0f; } diff --git a/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.h b/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.h index b576c64e86..19db52b979 100644 --- a/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.h +++ b/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/ConnectionMetrics.h @@ -19,7 +19,7 @@ namespace AzNetworking { DatarateAtom() = default; - AZ::TimeMs m_timeAccumulatorMs = AZ::TimeMs{ 0 }; + AZ::TimeMs m_timeAccumulatorMs = AZ::Time::ZeroTimeMs; uint32_t m_bytesTransmitted = 0; uint32_t m_packetsSent = 0; uint32_t m_packetsLost = 0; @@ -78,7 +78,7 @@ namespace AzNetworking ConnectionPacketEntry(PacketId packetId, AZ::TimeMs sendTimeMs); PacketId m_packetId = InvalidPacketId; - AZ::TimeMs m_sendTimeMs = AZ::TimeMs{0}; + AZ::TimeMs m_sendTimeMs = AZ::Time::ZeroTimeMs; }; //! @class ConnectionComputeRtt diff --git a/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/IConnection.h b/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/IConnection.h index 363fd1d37b..7afbbaee7c 100644 --- a/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/IConnection.h +++ b/Code/Framework/AzNetworking/AzNetworking/ConnectionLayer/IConnection.h @@ -28,8 +28,8 @@ namespace AzNetworking ConnectionQuality(int32_t lossPercentage, AZ::TimeMs latencyMs, AZ::TimeMs varianceMs); int32_t m_lossPercentage = 0; - AZ::TimeMs m_latencyMs = AZ::TimeMs{ 0 }; - AZ::TimeMs m_varianceMs = AZ::TimeMs{ 0 }; + AZ::TimeMs m_latencyMs = AZ::Time::ZeroTimeMs; + AZ::TimeMs m_varianceMs = AZ::Time::ZeroTimeMs; }; enum class TrustZone diff --git a/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.cpp b/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.cpp index b0f316cf50..eb07efe80f 100644 --- a/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.cpp @@ -122,10 +122,4 @@ namespace AzNetworking m_timeoutItemMap.erase(itemTimeoutId); } } - - void TimeoutQueue::UpdateTimeouts(ITimeoutHandler& timeoutHandler, int32_t maxTimeouts) - { - TimeoutHandler handler([&timeoutHandler](TimeoutQueue::TimeoutItem& item) { return timeoutHandler.HandleTimeout(item); }); - UpdateTimeouts(handler, maxTimeouts); - } } diff --git a/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.h b/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.h index 63417ea36f..1a4423144f 100644 --- a/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.h +++ b/Code/Framework/AzNetworking/AzNetworking/DataStructures/TimeoutQueue.h @@ -23,8 +23,6 @@ namespace AzNetworking Delete }; - class ITimeoutHandler; - //! @class TimeoutQueue //! @brief class for managing timeout items. class TimeoutQueue @@ -39,8 +37,8 @@ namespace AzNetworking void UpdateTimeoutTime(AZ::TimeMs currentTimeMs); uint64_t m_userData = 0; - AZ::TimeMs m_timeoutMs = AZ::TimeMs{0}; - AZ::TimeMs m_nextTimeoutTimeMs = AZ::TimeMs{0}; + AZ::TimeMs m_timeoutMs = AZ::Time::ZeroTimeMs; + AZ::TimeMs m_nextTimeoutTimeMs = AZ::Time::ZeroTimeMs; }; TimeoutQueue() = default; @@ -70,11 +68,6 @@ namespace AzNetworking using TimeoutHandler = AZStd::function; void UpdateTimeouts(const TimeoutHandler& timeoutHandler, int32_t maxTimeouts = -1); - //! Updates timeouts for all items, invokes timeout handlers if required. - //! @param timeoutHandler listener instance to call back on for timeouts - //! @param maxTimeouts the maximum number of timeouts to process before breaking iteration - void UpdateTimeouts(ITimeoutHandler& timeoutHandler, int32_t maxTimeouts = -1); - private: struct TimeoutQueueItem @@ -94,19 +87,6 @@ namespace AzNetworking TimeoutItemMap m_timeoutItemMap; TimeoutItemQueue m_timeoutItemQueue; }; - - //! @class ITimeoutHandler - //! @brief interface class for managing timeout items. - class ITimeoutHandler - { - public: - virtual ~ITimeoutHandler() = default; - - //! Handler callback for timed out items. - //! @param item containing registered timeout details - //! @return ETimeoutResult for whether to re-register or discard the timeout params - virtual TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) = 0; - }; } #include diff --git a/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkInterfaceMetrics.h b/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkInterfaceMetrics.h index 8cad6e5537..d4bfabf54a 100644 --- a/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkInterfaceMetrics.h +++ b/Code/Framework/AzNetworking/AzNetworking/Framework/NetworkInterfaceMetrics.h @@ -15,11 +15,11 @@ namespace AzNetworking struct NetworkInterfaceMetrics { //! Returns the total number of milliseconds spent updating this network interface. - AZ::TimeMs m_updateTimeMs = AZ::TimeMs{ 0 }; + AZ::TimeMs m_updateTimeMs = AZ::Time::ZeroTimeMs; //! Returns the total number of connections bound to this network interface. uint64_t m_connectionCount = 0; //! Returns the total number of milliseconds spent sending data on this network interface. - AZ::TimeMs m_sendTimeMs = AZ::TimeMs{ 0 }; + AZ::TimeMs m_sendTimeMs = AZ::Time::ZeroTimeMs; //! Returns the total number of packets sent on this socket. uint64_t m_sendPackets = 0; //! Returns the total number of encrypted packets sent on this socket. @@ -37,7 +37,7 @@ namespace AzNetworking //! Returns the total number of packets that had to be resent on this network interface due to packet loss. uint64_t m_resentPackets = 0; //! Returns the total number of milliseconds spent processing received data on this network interface. - AZ::TimeMs m_recvTimeMs = AZ::TimeMs{ 0 }; + AZ::TimeMs m_recvTimeMs = AZ::Time::ZeroTimeMs; //! Returns the total number of packets received on this socket. uint64_t m_recvPackets = 0; //! Returns the total number of bytes received on this socket after compression. diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.cpp index 6d7358a425..7537232a27 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.cpp @@ -27,13 +27,11 @@ namespace AzNetworking ConnectionId connectionId, const IpAddress& remoteAddress, TcpNetworkInterface& networkInterface, - TcpSocket& socket, - TimeoutId timeoutId + TcpSocket& socket ) : IConnection(connectionId, remoteAddress) , m_networkInterface(networkInterface) , m_socket(socket.CloneAndTakeOwnership()) - , m_timeoutId(timeoutId) , m_state(m_socket->IsOpen() ? ConnectionState::Connecting : ConnectionState::Disconnected) , m_connectionRole(ConnectionRole::Acceptor) , m_registeredSocketFd(InvalidSocketFd) @@ -163,13 +161,6 @@ namespace AzNetworking break; } - TimeoutQueue::TimeoutItem* timeoutItem = m_networkInterface.m_connectionTimeoutQueue.RetrieveItem(GetTimeoutId()); - if (timeoutItem == nullptr) - { - return true; - } - timeoutItem->UpdateTimeoutTime(startTimeMs); - NetworkOutputSerializer serializer(buffer.GetBuffer(), static_cast(buffer.GetSize())); if (m_state == ConnectionState::Connecting) { diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.h b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.h index b769aea086..3d74f3f336 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.h +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.h @@ -38,14 +38,12 @@ namespace AzNetworking //! @param remoteAddress IP address of the remote endpoint //! @param networkInterface TcpNetworkInterface that owns this connection instance //! @param socket TCP socket to take ownership of and use for sending and receiving data - //! @param timeoutId timeout identifier of this connection instance TcpConnection ( ConnectionId connectionId, const IpAddress& remoteAddress, TcpNetworkInterface& networkInterface, - TcpSocket& socket, - TimeoutId timeoutId + TcpSocket& socket ); //! Construct a new socket with optional encryption, used when initiating a new connection @@ -69,14 +67,6 @@ namespace AzNetworking //! @return the TcpSocket bound to this TcpConnection TcpSocket* GetTcpSocket() const; - //! Sets the timeout identifier for this TcpConnection. - //! @param timeoutId the timeout identifier to use for this TcpConnection - void SetTimeoutId(TimeoutId timeoutId); - - //! Returns the timeout identifier for this TcpConnection. - //! @return the timeout identifier for this TcpConnection - TimeoutId GetTimeoutId() const; - //! Returns true if this connection instance is in an open state, and is capable of actively sending and receiving packets. //! @return boolean true if this connection instance is in an open state bool IsOpen() const; @@ -142,7 +132,6 @@ namespace AzNetworking AZStd::unique_ptr m_socket; AZStd::unique_ptr m_compressor; - TimeoutId m_timeoutId; PacketId m_lastSentPacketId = InvalidPacketId; ConnectionState m_state = ConnectionState::Disconnected; ConnectionRole m_connectionRole = ConnectionRole::Connector; diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.inl b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.inl index ecd1e5e908..5b5f38774e 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.inl +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpConnection.inl @@ -15,16 +15,6 @@ namespace AzNetworking return m_socket.get(); } - inline void TcpConnection::SetTimeoutId(TimeoutId timeoutId) - { - m_timeoutId = timeoutId; - } - - inline TimeoutId TcpConnection::GetTimeoutId() const - { - return m_timeoutId; - } - inline bool TcpConnection::IsOpen() const { return m_socket->IsOpen(); diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpListenThread.h b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpListenThread.h index 0859e1d0f7..f2dc88cab4 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpListenThread.h +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpListenThread.h @@ -67,6 +67,6 @@ namespace AzNetworking uint32_t m_listenPortCount = 0; TcpSocketManager m_tcpSocketManager; AZ::ThreadSafeDeque m_listenPorts; - AZ::TimeMs m_updateTimeMs = AZ::TimeMs{ 0 }; + AZ::TimeMs m_updateTimeMs = AZ::Time::ZeroTimeMs; }; } diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp index 1ccff7be50..18ce25c4dd 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.cpp @@ -21,16 +21,11 @@ namespace AzNetworking static const bool net_TcpUseEncryption = false; #endif - AZ_CVAR(bool, net_TcpTimeoutConnections, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Boolean value on whether we should timeout Tcp connections"); - AZ_CVAR(AZ::TimeMs, net_TcpHeartbeatTimeMs, AZ::TimeMs{ 2 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Tcp connection heartbeat frequency"); - AZ_CVAR(AZ::TimeMs, net_TcpDefaultTimeoutMs, AZ::TimeMs{ 10 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time in milliseconds before we timeout an idle Tcp connection"); - TcpNetworkInterface::TcpNetworkInterface(AZ::Name name, IConnectionListener& connectionListener, TrustZone trustZone, TcpListenThread& listenThread) : m_name(name) , m_trustZone(trustZone) , m_connectionListener(connectionListener) , m_listenThread(listenThread) - , m_timeoutMs(net_TcpDefaultTimeoutMs) { ; } @@ -98,8 +93,6 @@ namespace AzNetworking } AZLOG_INFO("Adding new socket %d", static_cast(tcpSocket->GetSocketFd())); - const TimeoutId newTimeoutId = m_connectionTimeoutQueue.RegisterItem(static_cast(tcpSocket->GetSocketFd()), net_TcpHeartbeatTimeMs); - connection->SetTimeoutId(newTimeoutId); connection->SendReliablePacket(CorePackets::InitiateConnectionPacket()); m_connectionListener.OnConnect(connection.get()); m_connectionSet.AddConnection(AZStd::move(connection)); @@ -110,17 +103,11 @@ namespace AzNetworking { const AZ::TimeMs startTimeMs = AZ::GetElapsedTimeMs(); - // Time out any stale connections - { - ConnectionTimeoutFunctor functor(*this); - m_connectionTimeoutQueue.UpdateTimeouts(functor); - } - AcceptNewConnections(); auto readCallback = [this, startTimeMs](SocketFd socketFd) { HandleConnectionRecv(socketFd, startTimeMs); }; auto writeCallback = [this](SocketFd socketFd) { HandleConnectionSend(socketFd); }; - m_tcpSocketManager.ProcessEvents(AZ::TimeMs{ 0 }, readCallback, writeCallback); + m_tcpSocketManager.ProcessEvents(AZ::Time::ZeroTimeMs, readCallback, writeCallback); FlushQueuedRemoves(); @@ -258,8 +245,7 @@ namespace AzNetworking return; } AZLOG(NET_TcpTraffic, "Adding new socket %d", static_cast(tcpSocket.GetSocketFd())); - const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(static_cast(tcpSocket.GetSocketFd()), m_timeoutMs); - AZStd::unique_ptr connection = AZStd::make_unique(connectionId, remoteAddress, *this, tcpSocket, timeoutId); + AZStd::unique_ptr connection = AZStd::make_unique(connectionId, remoteAddress, *this, tcpSocket); AZ_Assert(connection->GetConnectionRole() == ConnectionRole::Acceptor, "Invalid role for connection"); GetConnectionListener().OnConnect(connection.get()); m_connectionSet.AddConnection(AZStd::move(connection)); @@ -286,7 +272,6 @@ namespace AzNetworking m_pendingRemoves.resize_no_construct(0); } - TcpNetworkInterface::PendingConnection::PendingConnection(SocketFd socketFd, uint32_t remoteIpAddress, uint16_t remotePort, uint16_t listenPort) : m_socketFd(socketFd) , m_remoteIpAddress(remoteIpAddress) @@ -295,34 +280,4 @@ namespace AzNetworking { ; } - - TcpNetworkInterface::ConnectionTimeoutFunctor::ConnectionTimeoutFunctor(TcpNetworkInterface& networkInterface) - : m_networkInterface(networkInterface) - { - ; - } - - TimeoutResult TcpNetworkInterface::ConnectionTimeoutFunctor::HandleTimeout(TimeoutQueue::TimeoutItem& item) - { - const SocketFd socketFd = static_cast(item.m_userData); - TcpConnection* tcpConnection = m_networkInterface.m_connectionSet.GetConnection(socketFd); - - if (tcpConnection == nullptr) - { - // We've already deleted this connection - return TimeoutResult::Delete; - } - - if (tcpConnection->GetConnectionRole() == ConnectionRole::Connector) - { - tcpConnection->SendReliablePacket(CorePackets::HeartbeatPacket()); - } - else if (net_TcpTimeoutConnections && (m_networkInterface.GetTimeoutMs() > AZ::TimeMs{ 0 })) - { - tcpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local); - return TimeoutResult::Delete; - } - - return TimeoutResult::Refresh; - } } diff --git a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h index d8f5d1b62b..d483a89cf3 100644 --- a/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/TcpTransport/TcpNetworkInterface.h @@ -137,16 +137,6 @@ namespace AzNetworking AZ_DISABLE_COPY_MOVE(TcpNetworkInterface); - struct ConnectionTimeoutFunctor final - : public ITimeoutHandler - { - ConnectionTimeoutFunctor(TcpNetworkInterface& networkInterface); - TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override; - private: - AZ_DISABLE_COPY_MOVE(ConnectionTimeoutFunctor); - TcpNetworkInterface& m_networkInterface; - }; - struct PendingRemove { SocketFd m_socketFd; @@ -156,13 +146,12 @@ namespace AzNetworking AZ::Name m_name; TrustZone m_trustZone; uint16_t m_port = 0; - AZ::TimeMs m_timeoutMs = AZ::TimeMs{ 0 }; + AZ::TimeMs m_timeoutMs = AZ::Time::ZeroTimeMs; IConnectionListener& m_connectionListener; TcpConnectionSet m_connectionSet; TcpSocketManager m_tcpSocketManager; AZ::ThreadSafeDeque m_pendingConnections; AZStd::vector m_pendingRemoves; - TimeoutQueue m_connectionTimeoutQueue; TcpListenThread& m_listenThread; friend class TcpConnection; // For access to private RequestDisconnect() method diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.cpp index 7b451865c4..452992f971 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.cpp @@ -79,7 +79,8 @@ namespace AzNetworking AZLOG(NET_Acks, "Unacked packet count exceeded, sending client heartbeat (curr %u : max %u)", m_unackedPacketCount, static_cast(net_UdpMaxUnackedPacketCount)); // This simply times out unreliable chunks that haven't completed within our timeout delay m_fragmentQueue.Update(); - SendUnreliablePacket(CorePackets::HeartbeatPacket()); + // This heartbeat is sent to minimize the time the remote endpoint spends waiting for ack vector replication, we don't require a response + SendUnreliablePacket(CorePackets::HeartbeatPacket(false)); } } @@ -289,7 +290,11 @@ namespace AzNetworking { return PacketDispatchResult::Failure; } - // Do nothing, we've already processed our ack packets + if (packet.GetRequestResponse()) + { + // We're replying to a heartbeat request, we don't want a response + SendUnreliablePacket(CorePackets::HeartbeatPacket(false)); + } return PacketDispatchResult::Success; } break; diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.h b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.h index 199a5a8347..ddd75c9946 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.h +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpConnection.h @@ -136,12 +136,12 @@ namespace AzNetworking AZ_DISABLE_COPY_MOVE(UdpConnection); UdpNetworkInterface& m_networkInterface; - UdpPacketTracker m_packetTracker; - UdpReliableQueue m_reliableQueue; - UdpFragmentQueue m_fragmentQueue; - ConnectionState m_state = ConnectionState::Disconnected; - ConnectionRole m_connectionRole = ConnectionRole::Connector; - DtlsEndpoint m_dtlsEndpoint; + UdpPacketTracker m_packetTracker; + UdpReliableQueue m_reliableQueue; + UdpFragmentQueue m_fragmentQueue; + ConnectionState m_state = ConnectionState::Disconnected; + ConnectionRole m_connectionRole = ConnectionRole::Connector; + DtlsEndpoint m_dtlsEndpoint; AZ::TimeMs m_lastSentPacketMs; uint32_t m_unackedPacketCount = 0; diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpFragmentQueue.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpFragmentQueue.cpp index fa4ee78a92..0c710f5a14 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpFragmentQueue.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpFragmentQueue.cpp @@ -20,7 +20,13 @@ namespace AzNetworking void UdpFragmentQueue::Update() { - m_timeoutQueue.UpdateTimeouts(*this); + m_timeoutQueue.UpdateTimeouts([this](TimeoutQueue::TimeoutItem& item) + { + const SequenceId fragmentSequence = static_cast(item.m_userData & 0xFF); + AZLOG(NET_FragmentQueue, "Timing out unreliable fragmented packet %u", static_cast(fragmentSequence)); + m_packetFragments.erase(fragmentSequence); + return TimeoutResult::Delete; + }); } void UdpFragmentQueue::Reset() @@ -163,12 +169,4 @@ namespace AzNetworking return handledPacket; } - - TimeoutResult UdpFragmentQueue::HandleTimeout(TimeoutQueue::TimeoutItem& item) - { - const SequenceId fragmentSequence = static_cast(item.m_userData & 0xFF); - AZLOG(NET_FragmentQueue, "Timing out unreliable fragmented packet %u", static_cast(fragmentSequence)); - m_packetFragments.erase(fragmentSequence); - return TimeoutResult::Delete; - } } diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpFragmentQueue.h b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpFragmentQueue.h index 9c929d63e8..5efa767283 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpFragmentQueue.h +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpFragmentQueue.h @@ -26,7 +26,6 @@ namespace AzNetworking //! @class UdpFragmentQueue //! @brief Class for reconstructing packet chunks into the original unsegmented packet. class UdpFragmentQueue - : public ITimeoutHandler { public: @@ -51,11 +50,6 @@ namespace AzNetworking private: - //! Handler callback for timed out items. - //! @param item containing registered timeout details - //! @return ETimeoutResult for whether to re-register or discard the timeout params - TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override; - TimeoutQueue m_timeoutQueue; SequenceGenerator m_sequenceGenerator; diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp index b0e64f93e3..1ca5922753 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.cpp @@ -31,7 +31,7 @@ namespace AzNetworking AZ_CVAR(bool, net_UdpTimeoutConnections, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Boolean value on whether we should timeout Udp connections"); AZ_CVAR(AZ::TimeMs, net_UdpPacketTimeSliceMs, AZ::TimeMs{ 8 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The number of milliseconds to allow for packet processing"); - AZ_CVAR(AZ::TimeMs, net_UdpHeartbeatTimeMs, AZ::TimeMs{ 2 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Udp connection heartbeat frequency"); + AZ_CVAR(uint32_t, net_UdpUnackedHeartbeats, 5, nullptr, AZ::ConsoleFunctorFlags::Null, "The number of heartbeats to attempt to send to keep a connection alive before giving up"); AZ_CVAR(AZ::TimeMs, net_UdpDefaultTimeoutMs, AZ::TimeMs{ 10 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time in milliseconds before we timeout an idle Udp connection"); AZ_CVAR(AZ::TimeMs, net_MinPacketTimeoutMs, AZ::TimeMs{ 200 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Minimum time to wait before timing out an unacked packet"); AZ_CVAR(int32_t, net_MaxTimeoutsPerFrame, 1000, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Maximum number of packet timeouts to allow to process in a single frame"); @@ -139,7 +139,8 @@ namespace AzNetworking } const ConnectionId connectionId = m_connectionSet.GetNextConnectionId(); - const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(aznumeric_cast(connectionId), m_timeoutMs); + const AZ::TimeMs timeoutTimeMs = m_timeoutMs / static_cast(static_cast(net_UdpUnackedHeartbeats)); + const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(aznumeric_cast(connectionId), timeoutTimeMs); AZStd::unique_ptr connection = AZStd::make_unique(connectionId, remoteAddress, *this, ConnectionRole::Connector); UdpPacketEncodingBuffer dtlsData; @@ -277,6 +278,7 @@ namespace AzNetworking } timeoutItem->UpdateTimeoutTime(startTimeMs); + connection->m_timeoutCounter = 0; PacketDispatchResult handledPacket = PacketDispatchResult::Failure; if (header.GetPacketType() < aznumeric_cast(CorePackets::PacketType::MAX)) @@ -319,16 +321,10 @@ namespace AzNetworking const AZ::TimeMs receiveTimeMs = AZ::GetElapsedTimeMs() - startTimeMs; // Time out any stale client connections - { - ConnectionTimeoutFunctor functor(*this); - m_connectionTimeoutQueue.UpdateTimeouts(functor); - } + m_connectionTimeoutQueue.UpdateTimeouts([this](TimeoutQueue::TimeoutItem& item) { return HandleConnectionTimeout(item); }); // Time out any packets that haven't been acked within our timeout window - { - PacketTimeoutFunctor functor(*this); - m_packetTimeoutQueue.UpdateTimeouts(functor, static_cast(net_MaxTimeoutsPerFrame)); - } + m_packetTimeoutQueue.UpdateTimeouts([this](TimeoutQueue::TimeoutItem& item) { return HandlePacketTimeout(item); }, static_cast(net_MaxTimeoutsPerFrame)); // Delete any connections we've disconnected for (RemovedConnection& removedConnection : m_removedConnections) @@ -709,21 +705,14 @@ namespace AzNetworking { // Packets involved in handshake are InitiateConnection, ConnectionHandshake and FragmentedPackets of ConnectionHandshake return packetType == aznumeric_cast(CorePackets::PacketType::InitiateConnectionPacket) || - packetType == aznumeric_cast(CorePackets::PacketType::ConnectionHandshakePacket) || - (packetType == aznumeric_cast(CorePackets::PacketType::FragmentedPacket) && endpoint.IsConnecting()); + packetType == aznumeric_cast(CorePackets::PacketType::ConnectionHandshakePacket) || + (packetType == aznumeric_cast(CorePackets::PacketType::FragmentedPacket) && endpoint.IsConnecting()); } - - UdpNetworkInterface::ConnectionTimeoutFunctor::ConnectionTimeoutFunctor(UdpNetworkInterface& networkInterface) - : m_networkInterface(networkInterface) - { - ; - } - - TimeoutResult UdpNetworkInterface::ConnectionTimeoutFunctor::HandleTimeout(TimeoutQueue::TimeoutItem& item) + TimeoutResult UdpNetworkInterface::HandleConnectionTimeout(TimeoutQueue::TimeoutItem& item) { const ConnectionId connectionId = ConnectionId(aznumeric_cast(item.m_userData)); - UdpConnection* udpConnection = static_cast(m_networkInterface.m_connectionSet.GetConnection(connectionId)); + UdpConnection* udpConnection = static_cast(m_connectionSet.GetConnection(connectionId)); if (udpConnection == nullptr) { @@ -731,22 +720,23 @@ namespace AzNetworking return TimeoutResult::Delete; } - if (udpConnection->GetConnectionState() == ConnectionState::Connecting) + if ((udpConnection->GetConnectionState() == ConnectionState::Connecting) + && udpConnection->GetDtlsEndpoint().IsConnecting()) { - if (udpConnection->GetDtlsEndpoint().IsConnecting()) - { - // DTLS prefers we resend data lost over the wire with fresh SSL IDs so account for that here - UdpPacketEncodingBuffer dtlsData; - udpConnection->ProcessHandshakeData(dtlsData); - return TimeoutResult::Refresh; - } + // DTLS prefers we resend data lost over the wire with fresh SSL IDs so account for that here + UdpPacketEncodingBuffer dtlsData; + udpConnection->ProcessHandshakeData(dtlsData); + return TimeoutResult::Refresh; } - if (udpConnection->GetConnectionRole() == ConnectionRole::Connector) + if ((udpConnection->GetConnectionRole() == ConnectionRole::Connector) + && (udpConnection->m_timeoutCounter < net_UdpUnackedHeartbeats)) { - udpConnection->SendUnreliablePacket(CorePackets::HeartbeatPacket()); + // Set the request response flag to true since we want a response to keep the connection alive + udpConnection->SendUnreliablePacket(CorePackets::HeartbeatPacket(true)); + ++udpConnection->m_timeoutCounter; } - else if (net_UdpTimeoutConnections && (m_networkInterface.GetTimeoutMs() > AZ::TimeMs{ 0 })) + else if (net_UdpTimeoutConnections && (GetTimeoutMs() > AZ::Time::ZeroTimeMs)) { udpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local); return TimeoutResult::Delete; @@ -755,19 +745,13 @@ namespace AzNetworking return TimeoutResult::Refresh; } - UdpNetworkInterface::PacketTimeoutFunctor::PacketTimeoutFunctor(UdpNetworkInterface& networkInterface) - : m_networkInterface(networkInterface) - { - ; - } - - TimeoutResult UdpNetworkInterface::PacketTimeoutFunctor::HandleTimeout(TimeoutQueue::TimeoutItem& item) + TimeoutResult UdpNetworkInterface::HandlePacketTimeout(TimeoutQueue::TimeoutItem& item) { ConnectionId connectionId; PacketId packetId; ReliabilityType reliability; DecodeTimeoutId(item.m_userData, connectionId, packetId, reliability); - UdpConnection* connection = static_cast(m_networkInterface.m_connectionSet.GetConnection(connectionId)); + UdpConnection* connection = static_cast(m_connectionSet.GetConnection(connectionId)); if (connection == nullptr) { @@ -782,16 +766,14 @@ namespace AzNetworking case PacketTimeoutResult::Acked: // Packet was already acked, just discard this timeout entry return TimeoutResult::Delete; - case PacketTimeoutResult::Pending: // Packet timed out before we received any info about it's sequence from the remote endpoint // The connection latency may have increased, and our Rtt metrics may still be adjusting.. // Just throw it back into the timeout queue return TimeoutResult::Refresh; - case PacketTimeoutResult::Lost: // Packet timed out and was not acked, so we consider it lost - m_networkInterface.m_connectionListener.OnPacketLost(connection, packetId); + m_connectionListener.OnPacketLost(connection, packetId); break; } diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h index 8f827c74c4..a640a6e3b8 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpNetworkInterface.h @@ -149,39 +149,29 @@ namespace AzNetworking //! @param endpoint whether the disconnection was initiated locally or remotely void RequestDisconnect(UdpConnection* connection, DisconnectReason reason, TerminationEndpoint endpoint); - //! Internal helper to check if a packet's type is for connection handshake + //! Internal helper to check if a packet's type is for connection handshake. //! @param endpoint DTLS endpoint participating in the handshake //! @param packetType type of the packet //! @return if the packet is for handshake bool IsHandshakePacket(const DtlsEndpoint& endpoint, AzNetworking::PacketType packetType) const; + //! Internal helper to manage connection timeout behaviour. + //! @param item the timeout item corresponding to the timed out connection + //! @return whether to delete or persist the timeout item + TimeoutResult HandleConnectionTimeout(TimeoutQueue::TimeoutItem& item); + + //! Internal helper to manage packet timeout behaviour. + //! @param item the timeout item corresponding to the timed out packet + //! @return whether to delete or persist the timeout item + TimeoutResult HandlePacketTimeout(TimeoutQueue::TimeoutItem& item); + AZ_DISABLE_COPY_MOVE(UdpNetworkInterface); - struct ConnectionTimeoutFunctor final - : public ITimeoutHandler - { - ConnectionTimeoutFunctor(UdpNetworkInterface& networkInterface); - TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override; - private: - AZ_DISABLE_COPY_MOVE(ConnectionTimeoutFunctor); - UdpNetworkInterface& m_networkInterface; - }; - - struct PacketTimeoutFunctor final - : public ITimeoutHandler - { - PacketTimeoutFunctor(UdpNetworkInterface& networkInterface); - TimeoutResult HandleTimeout(TimeoutQueue::TimeoutItem& item) override; - private: - AZ_DISABLE_COPY_MOVE(PacketTimeoutFunctor); - UdpNetworkInterface& m_networkInterface; - }; - AZ::Name m_name; TrustZone m_trustZone; uint16_t m_port = 0; bool m_allowIncomingConnections = false; - AZ::TimeMs m_timeoutMs = AZ::TimeMs{ 0 }; + AZ::TimeMs m_timeoutMs = AZ::Time::ZeroTimeMs; IConnectionListener& m_connectionListener; UdpConnectionSet m_connectionSet; TimeoutQueue m_connectionTimeoutQueue; diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpReaderThread.h b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpReaderThread.h index e0c97f157f..1d25a9e6e1 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpReaderThread.h +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpReaderThread.h @@ -94,6 +94,6 @@ namespace AzNetworking int32_t m_backIndex = 0; AZStd::array m_readerBuffers; AZStd::vector m_pendingAdds; - AZ::TimeMs m_updateTimeMs = AZ::TimeMs{ 0 }; + AZ::TimeMs m_updateTimeMs = AZ::Time::ZeroTimeMs; }; } diff --git a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp index 300b3527fa..85cc3a38f8 100644 --- a/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/UdpTransport/UdpSocket.cpp @@ -135,7 +135,7 @@ namespace AzNetworking int32_t sentBytes = size; #ifdef ENABLE_LATENCY_DEBUG - if (connectionQuality.m_latencyMs <= AZ::TimeMs{ 0 }) + if (connectionQuality.m_latencyMs <= AZ::Time::ZeroTimeMs) #endif { sentBytes = SendInternal(address, data, size, encrypt, dtlsEndpoint); @@ -153,9 +153,9 @@ namespace AzNetworking } } #ifdef ENABLE_LATENCY_DEBUG - else if ((connectionQuality.m_latencyMs > AZ::TimeMs{ 0 }) || (connectionQuality.m_varianceMs > AZ::TimeMs{ 0 })) + else if ((connectionQuality.m_latencyMs > AZ::Time::ZeroTimeMs) || (connectionQuality.m_varianceMs > AZ::Time::ZeroTimeMs)) { - const AZ::TimeMs jitterMs = aznumeric_cast(m_random.GetRandom()) % (connectionQuality.m_varianceMs > AZ::TimeMs{ 0 } + const AZ::TimeMs jitterMs = aznumeric_cast(m_random.GetRandom()) % (connectionQuality.m_varianceMs > AZ::Time::ZeroTimeMs ? connectionQuality.m_varianceMs : AZ::TimeMs{ 1 }); const AZ::TimeMs deferTimeMs = (connectionQuality.m_latencyMs) + jitterMs; diff --git a/Code/Framework/AzNetworking/AzNetworking/Utilities/EncryptionCommon.cpp b/Code/Framework/AzNetworking/AzNetworking/Utilities/EncryptionCommon.cpp index 9f496c3e0c..0903205eb8 100644 --- a/Code/Framework/AzNetworking/AzNetworking/Utilities/EncryptionCommon.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/Utilities/EncryptionCommon.cpp @@ -94,7 +94,7 @@ namespace AzNetworking static const uint32_t MaxCookieHistory = 8; static bool g_encryptionInitialized = false; static int32_t g_azNetworkingTrustDataIndex = 0; - static AZ::TimeMs g_lastCookieTimestamp = AZ::TimeMs{0}; + static AZ::TimeMs g_lastCookieTimestamp = AZ::Time::ZeroTimeMs; static uint64_t g_validCookieArray[MaxCookieHistory]; static uint32_t g_cookieReplaceIndex = 0; diff --git a/Code/Framework/AzNetworking/Tests/Serialization/DeltaSerializerTests.cpp b/Code/Framework/AzNetworking/Tests/Serialization/DeltaSerializerTests.cpp index 32562d9940..4ad9412849 100644 --- a/Code/Framework/AzNetworking/Tests/Serialization/DeltaSerializerTests.cpp +++ b/Code/Framework/AzNetworking/Tests/Serialization/DeltaSerializerTests.cpp @@ -15,7 +15,7 @@ namespace UnitTest { AzNetworking::PacketId m_packetId = AzNetworking::InvalidPacketId; uint32_t m_id = 0; - AZ::TimeMs m_timeMs = AZ::TimeMs{ 0 }; + AZ::TimeMs m_timeMs = AZ::Time::ZeroTimeMs; float m_blendFactor = 0.f; AZStd::vector m_growVector, m_shrinkVector; diff --git a/Code/Framework/AzNetworking/Tests/TcpTransport/TcpTransportTests.cpp b/Code/Framework/AzNetworking/Tests/TcpTransport/TcpTransportTests.cpp index 77632da572..d3a956bef3 100644 --- a/Code/Framework/AzNetworking/Tests/TcpTransport/TcpTransportTests.cpp +++ b/Code/Framework/AzNetworking/Tests/TcpTransport/TcpTransportTests.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include @@ -102,24 +102,24 @@ namespace UnitTest SetupAllocator(); AZ::NameDictionary::Create(); - m_loggerComponent = new AZ::LoggerSystemComponent; - m_timeComponent = new AZ::TimeSystemComponent; - m_networkingSystemComponent = new AzNetworking::NetworkingSystemComponent; + m_loggerComponent = AZStd::make_unique(); + m_timeSystem = AZStd::make_unique(); + m_networkingSystemComponent = AZStd::make_unique(); } void TearDown() override { - delete m_networkingSystemComponent; - delete m_timeComponent; - delete m_loggerComponent; + m_networkingSystemComponent.reset(); + m_timeSystem.reset(); + m_loggerComponent.reset(); AZ::NameDictionary::Destroy(); TeardownAllocator(); } - AZ::LoggerSystemComponent* m_loggerComponent; - AZ::TimeSystemComponent* m_timeComponent; - AzNetworking::NetworkingSystemComponent* m_networkingSystemComponent; + AZStd::unique_ptr m_loggerComponent; + AZStd::unique_ptr m_timeSystem; + AZStd::unique_ptr m_networkingSystemComponent; }; #if AZ_TRAIT_DISABLE_FAILED_NETWORKING_TESTS diff --git a/Code/Framework/AzNetworking/Tests/UdpTransport/UdpTransportTests.cpp b/Code/Framework/AzNetworking/Tests/UdpTransport/UdpTransportTests.cpp index 65c2cfa2b5..821c261fab 100644 --- a/Code/Framework/AzNetworking/Tests/UdpTransport/UdpTransportTests.cpp +++ b/Code/Framework/AzNetworking/Tests/UdpTransport/UdpTransportTests.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include @@ -105,24 +105,24 @@ namespace UnitTest SetupAllocator(); AZ::NameDictionary::Create(); - m_loggerComponent = new AZ::LoggerSystemComponent; - m_timeComponent = new AZ::TimeSystemComponent; - m_networkingSystemComponent = new AzNetworking::NetworkingSystemComponent; + m_loggerComponent = AZStd::make_unique(); + m_timeSystem = AZStd::make_unique(); + m_networkingSystemComponent = AZStd::make_unique(); } void TearDown() override { - delete m_networkingSystemComponent; - delete m_timeComponent; - delete m_loggerComponent; + m_networkingSystemComponent.reset(); + m_timeSystem.reset(); + m_loggerComponent.reset(); AZ::NameDictionary::Destroy(); TeardownAllocator(); } - AZ::LoggerSystemComponent* m_loggerComponent; - AZ::TimeSystemComponent* m_timeComponent; - AzNetworking::NetworkingSystemComponent* m_networkingSystemComponent; + AZStd::unique_ptr m_loggerComponent; + AZStd::unique_ptr m_timeSystem; + AZStd::unique_ptr m_networkingSystemComponent; }; TEST_F(UdpTransportTests, PacketIdWrap) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp index 7a76781cd7..7e78f64778 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDocking.cpp @@ -53,14 +53,13 @@ static void OptimizedSetParent(QWidget* widget, QWidget* parent) namespace AzQtComponents { - static FancyDockingDropZoneConstants g_FancyDockingConstants; // Constant for the threshold in pixels for snapping to edges while dragging for docking static const int g_snapThresholdInPixels = 15; - static QString g_minimizeButtonObjectName = "minimizeButton"; - static QString g_maximizeButtonObjectName = "maximizeButton"; - static QString g_closeButtonObjectName = "closeButton"; + static const QString MinimizeButtonObjectName = QStringLiteral("minimizeButton"); + static const QString MaximizeButtonObjectName = QStringLiteral("maximizeButton"); + static const QString CloseButtonObjectName = QStringLiteral("closeButton"); static Qt::Orientation orientation(Qt::DockWidgetArea area) { @@ -155,7 +154,7 @@ namespace AzQtComponents // Timer for updating our hovered drop zone opacity QObject::connect(m_dropZoneHoverFadeInTimer, &QTimer::timeout, this, &FancyDocking::onDropZoneHoverFadeInUpdate); - m_dropZoneHoverFadeInTimer->setInterval(g_FancyDockingConstants.dropZoneHoverFadeUpdateIntervalMS); + m_dropZoneHoverFadeInTimer->setInterval(FancyDockingDropZoneConstants::dropZoneHoverFadeUpdateIntervalMS); QIcon dragIcon = QIcon(QStringLiteral(":/Cursors/Grabbing.svg")); m_dragCursor = QCursor(dragIcon.pixmap(16), 5, 2); } @@ -333,13 +332,13 @@ namespace AzQtComponents */ void FancyDocking::onDropZoneHoverFadeInUpdate() { - const qreal dropZoneHoverOpacity = g_FancyDockingConstants.dropZoneHoverFadeIncrement + m_dropZoneState.dropZoneHoverOpacity(); + const qreal dropZoneHoverOpacity = FancyDockingDropZoneConstants::dropZoneHoverFadeIncrement + m_dropZoneState.dropZoneHoverOpacity(); // Once we've reached the full drop zone opacity, cut it off in case we // went over and stop the timer - if (dropZoneHoverOpacity >= g_FancyDockingConstants.dropZoneOpacity) + if (dropZoneHoverOpacity >= FancyDockingDropZoneConstants::dropZoneOpacity) { - m_dropZoneState.setDropZoneHoverOpacity(g_FancyDockingConstants.dropZoneOpacity); + m_dropZoneState.setDropZoneHoverOpacity(FancyDockingDropZoneConstants::dropZoneOpacity); m_dropZoneHoverFadeInTimer->stop(); } else @@ -460,7 +459,7 @@ namespace AzQtComponents // Minimize Icon QAction* minimizeAction = new QAction(tr("Minimize")); - minimizeAction->setObjectName(g_minimizeButtonObjectName); + minimizeAction->setObjectName(MinimizeButtonObjectName); connect(minimizeAction, &QAction::triggered, this, [titleBar]() { titleBar->handleMinimize(); @@ -470,7 +469,7 @@ namespace AzQtComponents // Maximize Icon QAction* maximizeAction = new QAction(tr("Maximize")); - maximizeAction->setObjectName(g_maximizeButtonObjectName); + maximizeAction->setObjectName(MaximizeButtonObjectName); connect(maximizeAction, &QAction::triggered, this, [titleBar]() { titleBar->handleMaximize(); @@ -480,7 +479,7 @@ namespace AzQtComponents // Close Icon QAction* closeAction = new QAction(tr("Close")); - closeAction->setObjectName(g_closeButtonObjectName); + closeAction->setObjectName(CloseButtonObjectName); connect(closeAction, &QAction::triggered, this, [titleBar]() { titleBar->handleClose(); @@ -792,12 +791,12 @@ namespace AzQtComponents QPoint mainWindowTopLeft = multiscreenMapFromGlobal(mainWindow->mapToGlobal(mainWindowRect.topLeft())); QPoint mainWindowTopRight = multiscreenMapFromGlobal(mainWindow->mapToGlobal(mainWindowRect.topRight())); QPoint mainWindowBottomLeft = multiscreenMapFromGlobal(mainWindow->mapToGlobal(mainWindowRect.bottomLeft())); - QSize absoluteLeftRightSize(g_FancyDockingConstants.absoluteDropZoneSizeInPixels, mainWindowRect.height()); + QSize absoluteLeftRightSize(FancyDockingDropZoneConstants::absoluteDropZoneSizeInPixels, mainWindowRect.height()); QRect absoluteLeftDropZone(mainWindowTopLeft, absoluteLeftRightSize); - QRect absoluteRightDropZone(mainWindowTopRight - QPoint(g_FancyDockingConstants.absoluteDropZoneSizeInPixels, 0), absoluteLeftRightSize); - QSize absoluteTopBottomSize(mainWindowRect.width(), g_FancyDockingConstants.absoluteDropZoneSizeInPixels); + QRect absoluteRightDropZone(mainWindowTopRight - QPoint(FancyDockingDropZoneConstants::absoluteDropZoneSizeInPixels, 0), absoluteLeftRightSize); + QSize absoluteTopBottomSize(mainWindowRect.width(), FancyDockingDropZoneConstants::absoluteDropZoneSizeInPixels); QRect absoluteTopDropZone(mainWindowTopLeft, absoluteTopBottomSize); - QRect absoluteBottomDropZone(mainWindowBottomLeft - QPoint(0, g_FancyDockingConstants.absoluteDropZoneSizeInPixels), absoluteTopBottomSize); + QRect absoluteBottomDropZone(mainWindowBottomLeft - QPoint(0, FancyDockingDropZoneConstants::absoluteDropZoneSizeInPixels), absoluteTopBottomSize); // If the drop target is a main window, then we will only show the absolute // drop zone if the cursor is in that zone already @@ -986,16 +985,16 @@ namespace AzQtComponents switch (m_dropZoneState.absoluteDropZoneArea()) { case Qt::LeftDockWidgetArea: - dockRect.setX(dockRect.x() + g_FancyDockingConstants.absoluteDropZoneSizeInPixels); + dockRect.setX(dockRect.x() + FancyDockingDropZoneConstants::absoluteDropZoneSizeInPixels); break; case Qt::RightDockWidgetArea: - dockRect.setWidth(dockRect.width() - g_FancyDockingConstants.absoluteDropZoneSizeInPixels); + dockRect.setWidth(dockRect.width() - FancyDockingDropZoneConstants::absoluteDropZoneSizeInPixels); break; case Qt::TopDockWidgetArea: - dockRect.setY(dockRect.y() + g_FancyDockingConstants.absoluteDropZoneSizeInPixels); + dockRect.setY(dockRect.y() + FancyDockingDropZoneConstants::absoluteDropZoneSizeInPixels); break; case Qt::BottomDockWidgetArea: - dockRect.setHeight(dockRect.height() - g_FancyDockingConstants.absoluteDropZoneSizeInPixels); + dockRect.setHeight(dockRect.height() - FancyDockingDropZoneConstants::absoluteDropZoneSizeInPixels); break; } @@ -1034,15 +1033,15 @@ namespace AzQtComponents // Set the drop zone width/height to the default, but if the dock widget // width and/or height is below the threshold, then switch to scaling them // down accordingly - int dropZoneWidth = g_FancyDockingConstants.dropZoneSizeInPixels; - if (dockWidth < g_FancyDockingConstants.minDockSizeBeforeDropZoneScalingInPixels) + int dropZoneWidth = FancyDockingDropZoneConstants::dropZoneSizeInPixels; + if (dockWidth < FancyDockingDropZoneConstants::minDockSizeBeforeDropZoneScalingInPixels) { - dropZoneWidth = aznumeric_cast(dockWidth * g_FancyDockingConstants.dropZoneScaleFactor); + dropZoneWidth = aznumeric_cast(dockWidth * FancyDockingDropZoneConstants::dropZoneScaleFactor); } - int dropZoneHeight = g_FancyDockingConstants.dropZoneSizeInPixels; - if (dockHeight < g_FancyDockingConstants.minDockSizeBeforeDropZoneScalingInPixels) + int dropZoneHeight = FancyDockingDropZoneConstants::dropZoneSizeInPixels; + if (dockHeight < FancyDockingDropZoneConstants::minDockSizeBeforeDropZoneScalingInPixels) { - dropZoneHeight = aznumeric_cast(dockHeight * g_FancyDockingConstants.dropZoneScaleFactor); + dropZoneHeight = aznumeric_cast(dockHeight * FancyDockingDropZoneConstants::dropZoneScaleFactor); } // Calculate the inner corners to be used when constructing the drop zone polygons @@ -1078,7 +1077,7 @@ namespace AzQtComponents int innerDropZoneWidth = m_dropZoneState.innerDropZoneRect().width(); int innerDropZoneHeight = m_dropZoneState.innerDropZoneRect().height(); int centerDropZoneDiameter = (innerDropZoneWidth < innerDropZoneHeight) ? innerDropZoneWidth : innerDropZoneHeight; - centerDropZoneDiameter = aznumeric_cast(centerDropZoneDiameter * g_FancyDockingConstants.centerTabDropZoneScale); + centerDropZoneDiameter = aznumeric_cast(centerDropZoneDiameter * FancyDockingDropZoneConstants::centerTabDropZoneScale); // Setup our center tab drop zone const QSize centerDropZoneSize(centerDropZoneDiameter, centerDropZoneDiameter); @@ -1986,7 +1985,7 @@ namespace AzQtComponents // hasn't faded in all the way yet, then ignore the drop zone area // which will make the widget floating bool modifiedKeyPressed = FancyDockingDropZoneWidget::CheckModifierKey(); - if (modifiedKeyPressed || m_dropZoneState.dropZoneHoverOpacity() != g_FancyDockingConstants.dropZoneOpacity) + if (modifiedKeyPressed || m_dropZoneState.dropZoneHoverOpacity() != FancyDockingDropZoneConstants::dropZoneOpacity) { area = Qt::NoDockWidgetArea; } @@ -2785,7 +2784,7 @@ namespace AzQtComponents RepaintFloatingIndicators(); } break; - case QEvent::WindowDeactivate: + case QEvent::WindowBlocked: // If our main window is deactivated while we are in the middle of // a docking drag operation (e.g. popup dialog for new level), we // should cancel our drag operation because the mouse release event @@ -3026,7 +3025,7 @@ namespace AzQtComponents { bool modifiedKeyPressed = FancyDockingDropZoneWidget::CheckModifierKey(); - m_ghostWidget->setWindowOpacity(modifiedKeyPressed ? 1.0f : g_FancyDockingConstants.draggingDockWidgetOpacity); + m_ghostWidget->setWindowOpacity(modifiedKeyPressed ? 1.0f : FancyDockingDropZoneConstants::draggingDockWidgetOpacity); m_ghostWidget->setPixmap(m_state.dockWidgetScreenGrab.screenGrab, m_state.placeholder(), m_state.placeholderScreen()); } } diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDockingDropZoneWidget.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDockingDropZoneWidget.cpp index d729929d7b..3873f0389b 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDockingDropZoneWidget.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDockingDropZoneWidget.cpp @@ -19,26 +19,6 @@ namespace AzQtComponents { - static FancyDockingDropZoneConstants g_Constants; - - FancyDockingDropZoneConstants::FancyDockingDropZoneConstants() - { - draggingDockWidgetOpacity = 0.6; - dropZoneOpacity = 0.4; - dropZoneSizeInPixels = 40; - minDockSizeBeforeDropZoneScalingInPixels = dropZoneSizeInPixels * 3; - dropZoneScaleFactor = 0.25; - centerTabDropZoneScale = 0.5; - centerTabIconScale = 0.5; - dropZoneColor = QColor(155, 155, 155); - dropZoneBorderColor = Qt::black; - dropZoneBorderInPixels = 1; - absoluteDropZoneSizeInPixels = 25; - dockingTargetDelayMS = 110; - dropZoneHoverFadeUpdateIntervalMS = 20; - dropZoneHoverFadeIncrement = dropZoneOpacity / (dockingTargetDelayMS / dropZoneHoverFadeUpdateIntervalMS); - centerDropZoneIconPath = QString(":/stylesheet/img/UI20/docking/tabs_icon.svg"); - } FancyDockingDropZoneWidget::FancyDockingDropZoneWidget(QMainWindow* mainWindow, QWidget* coordinatesRelativeTo, QScreen* screen, FancyDockingDropZoneState* dropZoneState) // NOTE: this will not work with multiple monitors if this widget has a parent. The floating drop zone @@ -154,7 +134,7 @@ namespace AzQtComponents // Draw all of the normal drop zones if they exist (if a dock widget is hovered over) painter.setPen(Qt::NoPen); - painter.setOpacity(g_Constants.dropZoneOpacity); + painter.setOpacity(FancyDockingDropZoneConstants::dropZoneOpacity); auto dropZones = m_dropZoneState->dropZones(); for (auto it = dropZones.cbegin(); it != dropZones.cend(); ++it) { @@ -189,7 +169,7 @@ namespace AzQtComponents // Otherwise, set the normal color else { - painter.setBrush(g_Constants.dropZoneColor); + painter.setBrush(FancyDockingDropZoneConstants::dropZoneColor); } // negate the window position to offset everything by that much @@ -214,8 +194,8 @@ namespace AzQtComponents // Scale the tabs icon based on the drop zone size and our specified offset // Doing this through QIcon to make sure that SVG is rendered already in desired resolution const QSize& dropZoneSize = dropZoneRect.size(); - const QSize requestedIconSize = dropZoneSize * g_Constants.centerTabIconScale; - const QIcon dropZoneIcon = QIcon(g_Constants.centerDropZoneIconPath); + const QSize requestedIconSize = dropZoneSize * FancyDockingDropZoneConstants::centerTabIconScale; + const QIcon dropZoneIcon = QIcon(FancyDockingDropZoneConstants::centerDropZoneIconPath); const QPixmap dropZonePixmap = dropZoneIcon.pixmap(requestedIconSize); const QSize receivedIconSize = dropZoneIcon.actualSize(requestedIconSize); @@ -264,7 +244,7 @@ namespace AzQtComponents } else { - painter.setBrush(g_Constants.dropZoneColor); + painter.setBrush(FancyDockingDropZoneConstants::dropZoneColor); } painter.drawRect(absoluteDropZoneRect); @@ -313,8 +293,8 @@ namespace AzQtComponents const QPoint innerBottomRight = innerDropZoneRect.bottomRight(); // Draw the lines using the appropriate pen - QPen dropZoneBorderPen(g_Constants.dropZoneBorderColor); - dropZoneBorderPen.setWidth(g_Constants.dropZoneBorderInPixels); + QPen dropZoneBorderPen(FancyDockingDropZoneConstants::dropZoneBorderColor); + dropZoneBorderPen.setWidth(FancyDockingDropZoneConstants::dropZoneBorderInPixels); painter.setPen(dropZoneBorderPen); painter.setOpacity(1); painter.drawLine(topLeft, innerTopLeft); diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDockingDropZoneWidget.h b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDockingDropZoneWidget.h index 94a6792833..865adcea59 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDockingDropZoneWidget.h +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/FancyDockingDropZoneWidget.h @@ -28,63 +28,58 @@ class QPainter; namespace AzQtComponents { - struct AZ_QT_COMPONENTS_API FancyDockingDropZoneConstants + namespace FancyDockingDropZoneConstants { // Constant for the opacity of the screen grab for the dock widget being dragged - qreal draggingDockWidgetOpacity; + static constexpr qreal draggingDockWidgetOpacity = 0.6; // Constant for the opacity of the normal drop zones - qreal dropZoneOpacity; + static constexpr qreal dropZoneOpacity = 0.4; // Constant for the default drop zone size (in pixels) - int dropZoneSizeInPixels; + static constexpr int dropZoneSizeInPixels = 40; // Constant for the dock width/height size (in pixels) before we need to start // scaling down the drop zone sizes, or else they will overlap with the center // tab icon or each other - int minDockSizeBeforeDropZoneScalingInPixels; + static constexpr int minDockSizeBeforeDropZoneScalingInPixels = dropZoneSizeInPixels * 3; // Constant for the factor by which we must scale down the drop zone sizes once // the dock width/height size is too small - qreal dropZoneScaleFactor; + static constexpr qreal dropZoneScaleFactor = 0.25; // Constant for the percentage to scale down the inner drop zone rectangle for the center tab drop zone - qreal centerTabDropZoneScale; + static constexpr qreal centerTabDropZoneScale = 0.5; // Constant for the percentage to scale down the center tab drop zone for the center tab icon - qreal centerTabIconScale; + static constexpr qreal centerTabIconScale = 0.5; // Constant for the drop zone hotspot default color - QColor dropZoneColor; + static const QColor dropZoneColor = QColor(155, 155, 155); // Constant for the drop zone border color - QColor dropZoneBorderColor; + static const QColor dropZoneBorderColor = Qt::black; // Constant for the border width in pixels separating the drop zones - int dropZoneBorderInPixels; + static constexpr int dropZoneBorderInPixels = 1; // Constant for the border width in pixels separating the drop zones - int absoluteDropZoneSizeInPixels; + static constexpr int absoluteDropZoneSizeInPixels = 25; // Constant for the delay (in milliseconds) before a drop zone becomes active // once it is hovered over - int dockingTargetDelayMS; + static constexpr int dockingTargetDelayMS = 110; // Constant for the rate at which we will update (fade in) the drop zone opacity // when hovered over (in milliseconds) - int dropZoneHoverFadeUpdateIntervalMS; + static constexpr int dropZoneHoverFadeUpdateIntervalMS = 20; // Constant for the incremental opacity increase for the hovered drop zone // that will fade in to the full drop zone opacity in the desired time - qreal dropZoneHoverFadeIncrement; + static constexpr qreal dropZoneHoverFadeIncrement = dropZoneOpacity / (dockingTargetDelayMS / dropZoneHoverFadeUpdateIntervalMS); // Constant for the path to the center drop zone tabs icon - QString centerDropZoneIconPath; - - FancyDockingDropZoneConstants(); - - FancyDockingDropZoneConstants(const FancyDockingDropZoneConstants&) = delete; - FancyDockingDropZoneConstants& operator=(const FancyDockingDropZoneConstants&) = delete; + static const QString centerDropZoneIconPath = QStringLiteral(":/stylesheet/img/UI20/docking/tabs_icon.svg"); }; class FancyDockingDropZoneState diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/BrowseEdit.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/BrowseEdit.cpp index 03d2c13f64..7e18843d65 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/BrowseEdit.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/BrowseEdit.cpp @@ -27,7 +27,7 @@ AZ_POP_DISABLE_WARNING namespace AzQtComponents { - static const char clearButtonActionNameC[] = "_q_qlineeditclearaction"; + static const QString ClearButtonActionNameC = QStringLiteral("_q_qlineeditclearaction"); struct BrowseEdit::InternalData { @@ -263,7 +263,7 @@ namespace AzQtComponents auto lineEdit = browseEdit->m_data->m_lineEdit; LineEdit::polish(style, lineEdit, lineEditConfig); - QAction* action = lineEdit->findChild(clearButtonActionNameC); + QAction* action = lineEdit->findChild(ClearButtonActionNameC); if (action) { QStyleOptionFrame option; @@ -284,7 +284,7 @@ namespace AzQtComponents auto lineEdit = browseEdit->m_data->m_lineEdit; LineEdit::unpolish(style, lineEdit, lineEditConfig); - QAction* action = lineEdit->findChild(clearButtonActionNameC); + QAction* action = lineEdit->findChild(ClearButtonActionNameC); if (action) { QStyleOptionFrame option; diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/ColorPicker/ColorController.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/ColorPicker/ColorController.cpp index 176443c0cd..4e8e86bc5a 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/ColorPicker/ColorController.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/ColorPicker/ColorController.cpp @@ -323,7 +323,7 @@ namespace AzQtComponents saturation *= 2.0 - lightness; } double value = (lightness + saturation) / 2.0; - saturation = (2.0 * saturation) / (lightness + saturation); + saturation = qFuzzyIsNull(lightness + saturation) ? 0 : (2.0 * saturation) / (lightness + saturation); m_hsv.saturation = AZ::GetClamp(saturation, 0.0, 1.0); m_hsv.value = AZ::GetClamp(value, 0.0, 12.5); @@ -341,11 +341,12 @@ namespace AzQtComponents double saturation = m_hsv.saturation * m_hsv.value; if (lightness <= 1.0) { - saturation /= lightness; + saturation = (qFuzzyIsNull(lightness)) ? 0.0 : saturation / lightness; } else { - saturation /= 2.0 - lightness; + double two_minus_lightness = 2.0 - lightness; + saturation = (qFuzzyIsNull(two_minus_lightness)) ? 0.0 : saturation / two_minus_lightness; } lightness /= 2.0; diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/Internal/OverlayWidgetLayer.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/Internal/OverlayWidgetLayer.cpp index f6417d3470..21f2500822 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/Internal/OverlayWidgetLayer.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/Internal/OverlayWidgetLayer.cpp @@ -32,7 +32,7 @@ namespace AzQtComponents namespace Internal { - const char* OverlayWidgetLayer::s_layerStyle = "background-color:rgba(0, 0, 0, 179)"; + static const QString LayerStyle = QStringLiteral("background-color:rgba(0, 0, 0, 179)"); OverlayWidgetLayer::OverlayWidgetLayer(OverlayWidget* parent, QWidget* centerWidget, QWidget* breakoutWidget, const char* title, const OverlayWidgetButtonList& buttons) @@ -66,7 +66,7 @@ namespace AzQtComponents if (breakoutWidget) { - setStyleSheet(s_layerStyle); + setStyleSheet(LayerStyle); setLayout(new QHBoxLayout()); // close the overlay if either dependent widget is destroyed @@ -100,7 +100,7 @@ namespace AzQtComponents } else { - setStyleSheet(s_layerStyle); + setStyleSheet(LayerStyle); } AddButtons(*m_ui.data(), buttons, parent == nullptr); } diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/Internal/OverlayWidgetLayer.h b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/Internal/OverlayWidgetLayer.h index 1ad3cb5071..cba61cb67d 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/Internal/OverlayWidgetLayer.h +++ b/Code/Framework/AzQtComponents/AzQtComponents/Components/Widgets/Internal/OverlayWidgetLayer.h @@ -59,8 +59,6 @@ namespace AzQtComponents bool eventFilter(QObject* object, QEvent* event) override; - static const char* s_layerStyle; - QVector