Merge branch 'development' into cmake/SPEC-7484
Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> # Conflicts: # Code/Editor/ToolBox.cpp
This commit is contained in:
@@ -1,378 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
# 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 io
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import errno
|
||||
import shutil
|
||||
import fnmatch
|
||||
import filecmp
|
||||
import fileinput
|
||||
import importlib
|
||||
import argparse
|
||||
import hashlib
|
||||
from xml.sax.saxutils import escape, unescape, quoteattr
|
||||
|
||||
# Maximum number of errors before bailing on AutoGen
|
||||
MAX_ERRORS = 100
|
||||
errorCount = 0
|
||||
|
||||
def PrintError(*objs):
|
||||
print(*objs, file=sys.stderr)
|
||||
global errorCount
|
||||
errorCount += 1
|
||||
if errorCount > MAX_ERRORS:
|
||||
print("Maximum errors exceeded (%d) please check the tty for errors" % MAX_ERRORS, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def PrintUnhandledExcptionInfo():
|
||||
print("An unexpected error occurred, please report the error you encountered and include your build output", file=sys.stderr)
|
||||
|
||||
def TransformEscape(string):
|
||||
return escape(quoteattr(unescape(string)))
|
||||
|
||||
def BooleanTrue(string):
|
||||
testString = string.lower().strip()
|
||||
return testString == "true" or testString == "1"
|
||||
|
||||
def CamelToHuman(string):
|
||||
return string[0].upper() + re.sub(r'((?<=[a-z])[A-Z]|(?<!\A)[A-Z](?=[a-z]))', r' \1', string[1:])
|
||||
|
||||
def StripFloat(string):
|
||||
return re.sub(r'(\d+(\.\d*)?|\.\d+)f', r'\g<1>0', string)
|
||||
|
||||
def CreateHashGuid(string):
|
||||
hash = hashlib.new('md5')
|
||||
hash.update(string.encode('utf-8'))
|
||||
hashStr = hash.hexdigest()
|
||||
return ("{" + hashStr[0:8] + "-" + hashStr[8:12] + "-" + hashStr[12:16] + "-" + hashStr[16:20] + "-" + hashStr[20:] + "}").upper()
|
||||
|
||||
def EtreeToString(xmlNode):
|
||||
return etree.tostring(xmlNode)
|
||||
|
||||
def SanitizePath(path):
|
||||
return (path or '').replace('\\', '/').replace('//', '/')
|
||||
|
||||
def SearchPaths(filename, paths=[]):
|
||||
if len(paths) > 0:
|
||||
for path in paths:
|
||||
testFile = os.path.join(path, filename)
|
||||
if os.path.exists(testFile):
|
||||
return os.path.abspath(testFile)
|
||||
if os.path.exists(filename):
|
||||
return os.path.abspath(filename)
|
||||
return None
|
||||
|
||||
def ComputeOutputPath(inputFiles, projectDir, outputDir):
|
||||
commonInputPath = os.path.commonprefix(inputFiles) # If we've globbed many source files, this finds the common prefix
|
||||
if os.path.isfile(commonInputPath): # If the commonInputPath resolves to an actual file, slice off the filename
|
||||
commonInputPath = os.path.dirname(commonInputPath)
|
||||
commonPath = os.path.commonprefix([commonInputPath, projectDir]) # Finds the common path between the data source files and our project directory (//depot/dev/Code/Framework/AzCore/)
|
||||
inputRelativePath = os.path.relpath(commonInputPath, commonPath) # Computes the relative path for the project source directory (Code/Framework/AzCore/AutoGen/)
|
||||
return os.path.join(outputDir, inputRelativePath) # Returns a suitable output directory (//depot/dev/Generated/Code/Framework/AzCore/AutoGen/)
|
||||
|
||||
def ProcessTemplateConversion(dataInputSet, dataInputFiles, templateFile, outputFile, templateCache, dryrun, verbose):
|
||||
if dryrun or not dataInputFiles:
|
||||
return
|
||||
try:
|
||||
outputFile = os.path.abspath(outputFile)
|
||||
outputPath = os.path.dirname(outputFile)
|
||||
treeRoots = []
|
||||
for dataInputFile in sorted(dataInputFiles):
|
||||
try:
|
||||
if dataInputFile in dataInputSet.keys():
|
||||
treeRoots.append(dataInputSet.get(dataInputFile))
|
||||
elif os.path.splitext(dataInputFile)[1] == ".xml":
|
||||
xml = etree.parse(dataInputFile)
|
||||
# xml.xinclude()
|
||||
xmlroot = xml.getroot()
|
||||
# look for an xml schema link for this document
|
||||
# xmlSchema = None
|
||||
# if 'xsi' in xmlroot.nsmap:
|
||||
# XMLSchemaNamespace = xmlroot.nsmap['xsi']
|
||||
# schemaLink = xmlroot.get('{' + XMLSchemaNamespace + '}schemaLocation')
|
||||
# if schemaLink is None:
|
||||
# schemaLink = xmlroot.attrib['{' + XMLSchemaNamespace + '}noNamespaceSchemaLocation']
|
||||
# if schemaLink:
|
||||
# # if we have a schemaLink, then we need to strip off the relative pathing and use our search paths
|
||||
# # relative pathing on the xml file itself is purely a nicety for Visual Studio to find the correct XSD for inline validation
|
||||
# xmlSchema = os.path.basename(schemaLink)
|
||||
# if xmlSchema:
|
||||
# # check the template directory, the template include dir, and the folder that houses the nvdef file, and the xml's location for the xsd
|
||||
# searchPaths = [os.path.dirname(templateFile)]
|
||||
# searchPaths += [os.path.dirname(dataInputFile)]
|
||||
# xmlShemaLoc = SearchPaths(xmlSchema, searchPaths)
|
||||
# try:
|
||||
# xmlSchemaDoc = etree.parse(xmlShemaLoc)
|
||||
# xmlSchemaObj = etree.XMLSchema(xmlSchemaDoc, attribute_defaults=True)
|
||||
# xmlSchemaObj.assertValid(xmlroot)
|
||||
# except etree.DocumentInvalid as e:
|
||||
# for error in e.error_log:
|
||||
# PrintError('%s(%d) : error InvalidXML %s' % (os.path.abspath(dataInputFile), error.line, error.message))
|
||||
# except IOError as e:
|
||||
# PrintError('%s(%s) : %s' % (os.path.abspath(dataInputFile), str(1), e.message))
|
||||
xmlroot = xml.getroot()
|
||||
dataInputSet[dataInputFile] = xml.getroot()
|
||||
treeRoots.append(xml.getroot())
|
||||
else:
|
||||
with open(dataInputFile) as jsonFile:
|
||||
jsonData = json.load(jsonFile)
|
||||
dataInputSet[dataInputFile] = jsonData
|
||||
treeRoots.append(jsonData)
|
||||
except IOError as e:
|
||||
PrintError('%s(%s) : %s' % (fileinput.filename(), str(fileinput.filelineno()), e.message))
|
||||
# except etree.XMLSyntaxError as e:
|
||||
# for error in e.error_log:
|
||||
# PrintError('%s(%s) : error XMLSyntaxError %s' % (os.path.abspath(dataInputFile), error.line, error.message))
|
||||
compareFD = io.StringIO()
|
||||
|
||||
searchPaths = [os.path.dirname(templateFile)]
|
||||
templateLoader = jinja2.FileSystemLoader(searchpath = searchPaths)
|
||||
templateEnv = jinja2.Environment(bytecode_cache = templateCache, loader = templateLoader, trim_blocks = True, extensions = ["jinja2.ext.do",])
|
||||
templateEnv.filters['relpath' ] = lambda x: os.path.relpath(x, outputPath)
|
||||
templateEnv.filters['dirname' ] = os.path.dirname
|
||||
templateEnv.filters['basename' ] = os.path.basename
|
||||
templateEnv.filters['splitext' ] = os.path.splitext
|
||||
templateEnv.filters['split' ] = os.path.split
|
||||
templateEnv.filters['startswith' ] = str.startswith
|
||||
templateEnv.filters['int' ] = int
|
||||
templateEnv.filters['str' ] = str
|
||||
templateEnv.filters['escape' ] = TransformEscape
|
||||
templateEnv.filters['len' ] = len
|
||||
templateEnv.filters['range' ] = range
|
||||
templateEnv.filters['stripFloat' ] = StripFloat
|
||||
templateEnv.filters['camelToHuman' ] = CamelToHuman
|
||||
templateEnv.filters['booleanTrue' ] = BooleanTrue
|
||||
templateEnv.filters['createHashGuid'] = CreateHashGuid
|
||||
templateEnv.filters['etreeToString' ] = EtreeToString
|
||||
templateJinja = templateEnv.get_template(os.path.basename(templateFile))
|
||||
templateVars = \
|
||||
{ \
|
||||
"dataFiles" : treeRoots, \
|
||||
"dataFileNames" : dataInputFiles, \
|
||||
"templateName" : templateFile, \
|
||||
"outputFile" : outputFile, \
|
||||
"filename" : os.path.splitext(os.path.basename(outputFile))[0], \
|
||||
}
|
||||
try:
|
||||
outputExtension = os.path.splitext(outputFile)[1]
|
||||
if outputExtension == ".xml" or outputExtension == ".xhtml" or outputExtension == ".xsd":
|
||||
compareFD.write('<?xml version="1.0"?>\n')
|
||||
compareFD.write('<!-- Copyright (c) Contributors to the Open 3D Engine Project. -->\n')
|
||||
compareFD.write('<!-- For complete copyright and license terms please see the LICENSE at the root of this distribution. -->\n')
|
||||
compareFD.write('\n')
|
||||
compareFD.write('<!-- SPDX-License-Identifier: Apache-2.0 OR MIT -->\n')
|
||||
compareFD.write('\n')
|
||||
compareFD.write('<!-- This file is generated automatically at compile time, DO NOT EDIT BY HAND -->\n')
|
||||
compareFD.write('<!-- Template Source {0}; XML Sources {1}-->\n'.format(templateFile, ', '.join(dataInputFiles)))
|
||||
compareFD.write('\n')
|
||||
elif outputExtension == ".lua":
|
||||
compareFD.write('-- Copyright (c) Contributors to the Open 3D Engine Project.\n')
|
||||
compareFD.write('-- For complete copyright and license terms please see the LICENSE at the root of this distribution.\n')
|
||||
compareFD.write('\n')
|
||||
compareFD.write('-- SPDX-License-Identifier: Apache-2.0 OR MIT\n')
|
||||
compareFD.write('\n')
|
||||
compareFD.write('-- This file is generated automatically at compile time, DO NOT EDIT BY HAND\n')
|
||||
compareFD.write('-- Template Source {0}; XML Sources {1}\n'.format(templateFile, ', '.join(dataInputFiles)))
|
||||
compareFD.write('\n')
|
||||
elif outputExtension == ".h" or outputExtension == ".hpp" or outputExtension == ".inl" or outputExtension == ".c" or outputExtension == ".cpp":
|
||||
compareFD.write('/*\n')
|
||||
compareFD.write(' * Copyright (c) Contributors to the Open 3D Engine Project.\n')
|
||||
compareFD.write(' * For complete copyright and license terms please see the LICENSE at the root of this distribution.\n')
|
||||
compareFD.write(' *\n')
|
||||
compareFD.write(' * SPDX-License-Identifier: Apache-2.0 OR MIT\n')
|
||||
compareFD.write(' *\n')
|
||||
compareFD.write(' * This file is generated automatically at compile time, DO NOT EDIT BY HAND\n')
|
||||
compareFD.write(' * Template Source {0}; Data Sources {1}\n'.format(templateFile, ', '.join(dataInputFiles)))
|
||||
compareFD.write(' */\n')
|
||||
compareFD.write('\n')
|
||||
compareFD.write(templateJinja.render(templateVars))
|
||||
compareFD.write('\n')
|
||||
except jinja2.exceptions.TemplateNotFound as e:
|
||||
PrintError('%s(1) : error TemplateNotFound %s' % (os.path.abspath(templateFile), e.message))
|
||||
except IOError as e:
|
||||
PrintError('%s(%s) : error I/O(%s) accessing %s : %s' % (fileinput.filename(), str(fileinput.filelineno()), e.errno, e.filename, e.strerror))
|
||||
except jinja2.exceptions.TemplateSyntaxError as e:
|
||||
PrintError('%s(%s) : error Template processing error: %s' % (os.path.abspath(e.filename), e.lineno, e.message))
|
||||
except jinja2.exceptions.UndefinedError as e:
|
||||
# Sadly, jinja doesn't provide the exact line of the template that had this error since the template is compiled directly to python code
|
||||
PrintError('%s(1) : error Template processing error: %s with %s' % (os.path.abspath(templateFile), e.message, ', '.join([os.path.basename(dataInputFile) for dataInputFile in dataInputFiles])))
|
||||
try:
|
||||
os.makedirs(os.path.dirname(outputFile))
|
||||
except OSError as e:
|
||||
if e.errno == errno.EEXIST:
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
try:
|
||||
if os.path.isfile(outputFile):
|
||||
with open(outputFile, 'r+') as currentFile:
|
||||
currentFileStringData = currentFile.read()
|
||||
if currentFileStringData == compareFD.getvalue():
|
||||
if verbose == True:
|
||||
print('Generated file %s is unchanged, skipping' % (outputFile))
|
||||
else:
|
||||
currentFile.truncate()
|
||||
with open(outputFile, 'w+') as currentFile:
|
||||
currentFile.write(compareFD.getvalue())
|
||||
print('Generating %s with template %s and inputs %s' % (outputFile, templateFile, ", ".join(dataInputFiles)))
|
||||
else:
|
||||
with open(outputFile, 'w+') as outputFD:
|
||||
outputFD.write(compareFD.getvalue())
|
||||
print('Generating %s using template %s and inputs %s' % (outputFile, templateFile, ", ".join(dataInputFiles)))
|
||||
except IOError as e:
|
||||
PrintError('%s(%s) : error I/O(%s) accessing %s : %s' % (fileinput.filename(), str(fileinput.filelineno()), e.errno, e.filename, e.strerror))
|
||||
except:
|
||||
PrintError('%s(%s) : error Processing: %s' % (fileinput.filename(), str(fileinput.filelineno()), line))
|
||||
PrintUnhandledExcptionInfo()
|
||||
raise
|
||||
compareFD.close()
|
||||
|
||||
def ProcessExpansionRule(sourceFiles, templateFiles, templateCache, outputDir, projectDir, expansionRule, dryrun, verbose, dataInputSet, outputFiles):
|
||||
try:
|
||||
# should be of the format inputFile(s),templateFile,outputFile, where inputFile and outputFile are subject to wildcarding and substitutions
|
||||
expansionRuleSet = expansionRule.split(",")
|
||||
inputFiles = expansionRuleSet[0]
|
||||
templateFile = None
|
||||
outputFile = expansionRuleSet[2]
|
||||
for fullPathTemplate in templateFiles:
|
||||
if expansionRuleSet[1] in fullPathTemplate:
|
||||
templateFile = fullPathTemplate
|
||||
break
|
||||
if templateFile is None:
|
||||
print("No matching template file found for %s, template may be missing from your _files.cmake" % expansionRuleSet[1])
|
||||
return
|
||||
# We have a few potential modes of input to output mapping that we'll have to handle depending on how the user formatted their azdef expansion rule
|
||||
# if the data input file was explicit
|
||||
# then output a single file for that explicit data
|
||||
# else the data is wildcarded
|
||||
# if the output contains $file or $fileprefix
|
||||
# then we can generate a *unique* name for each data input, we're in one-to-one mapping mode, create a unique output for each input
|
||||
# else if the output contains $path
|
||||
# then we can generate a unique name for each *directory* of data inputs, we're in many-to-one mapping mode, create a unique output for each directory
|
||||
# else the output is explicit, not wildcarded
|
||||
# generate a single output file containing all matching data file's
|
||||
# endif
|
||||
# endif
|
||||
testSingle = os.path.join(projectDir, inputFiles)
|
||||
if os.path.isfile(testSingle):
|
||||
# If we specified an *explicit* file to be processed (no wildcards for the data input file foo.json not *.foo.json), this is the branch that handles this case
|
||||
# This is explicitly one-to-one mapping
|
||||
dataInputFiles = [os.path.abspath(testSingle)]
|
||||
outputFileAbsolute = outputFile.replace("$path", ComputeOutputPath(dataInputFiles, projectDir, outputDir))
|
||||
outputFileAbsolute = outputFileAbsolute.replace("$fileprefix", os.path.splitext(os.path.basename(testSingle))[0].split(".")[0])
|
||||
outputFileAbsolute = outputFileAbsolute.replace("$file", os.path.splitext(os.path.basename(testSingle))[0])
|
||||
outputFileAbsolute = SanitizePath(outputFileAbsolute)
|
||||
ProcessTemplateConversion(dataInputSet, dataInputFiles, templateFile, outputFileAbsolute, templateCache, dryrun, verbose)
|
||||
outputFiles.append(outputFileAbsolute)
|
||||
else:
|
||||
# We've wildcarded the data input field, so we may have to handle one-to-one mapping of data files to output, or many-to-one mapping of data files to output
|
||||
if "$fileprefix" in outputFile or "$file" in outputFile:
|
||||
# Due to the wildcards in the output file, we've determined we'll do a one-to-one mapping of data files to output
|
||||
for filename in fnmatch.filter(sourceFiles, inputFiles):
|
||||
dataInputFiles = [os.path.abspath(filename)]
|
||||
outputFileAbsolute = outputFile.replace("$path", ComputeOutputPath(dataInputFiles, projectDir, outputDir))
|
||||
outputFileAbsolute = outputFileAbsolute.replace("$fileprefix", os.path.splitext(os.path.basename(filename))[0].split(".")[0])
|
||||
outputFileAbsolute = outputFileAbsolute.replace("$file", os.path.splitext(os.path.basename(filename))[0])
|
||||
outputFileAbsolute = SanitizePath(outputFileAbsolute)
|
||||
ProcessTemplateConversion(dataInputSet, dataInputFiles, templateFile, outputFileAbsolute, templateCache, dryrun, verbose)
|
||||
outputFiles.append(outputFileAbsolute)
|
||||
else:
|
||||
# Process all matches in one batch
|
||||
# Due to the lack of wildcards in the output file, we've determined we'll glob all matching input files into the template conversion
|
||||
for filename in fnmatch.filter(sourceFiles, inputFiles):
|
||||
dataInputFiles = [os.path.abspath(file) for file in fnmatch.filter(sourceFiles, inputFiles)]
|
||||
outputFileAbsolute = outputFile.replace("$path", ComputeOutputPath(dataInputFiles, projectDir, outputDir))
|
||||
outputFileAbsolute = SanitizePath(outputFileAbsolute)
|
||||
ProcessTemplateConversion(dataInputSet, dataInputFiles, templateFile, outputFileAbsolute, templateCache, dryrun, verbose)
|
||||
outputFiles.append(outputFileAbsolute)
|
||||
except IOError as e:
|
||||
PrintError('%s : error I/O(%s) accessing %s : %s' % (expansionRule, e.errno, e.filename, e.strerror))
|
||||
except:
|
||||
PrintError('%s : error Processing expansion rule' % expansionRule)
|
||||
PrintUnhandledExcptionInfo()
|
||||
raise
|
||||
|
||||
def ExecuteExpansionRules(cacheDir, outputDir, projectDir, inputFiles, expansionRules, dryrun, verbose, dataInputSet, outputFiles):
|
||||
# Get Globals
|
||||
global MAX_ERRORS
|
||||
global errorCount
|
||||
currentPath = os.getcwd()
|
||||
startTime = time.time()
|
||||
# Ensure jinja2 template cache dir actually exists...
|
||||
try:
|
||||
os.makedirs(cacheDir)
|
||||
except OSError as e:
|
||||
if e.errno == errno.EEXIST:
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
sourceFiles = []
|
||||
templateFiles = []
|
||||
for inputFile in inputFiles:
|
||||
if inputFile.endswith(".xml") or inputFile.endswith(".json"):
|
||||
sourceFiles.append(os.path.join(projectDir, inputFile))
|
||||
elif inputFile.endswith(".jinja"):
|
||||
templateFiles.append(os.path.join(projectDir, inputFile))
|
||||
templateCache = jinja2.FileSystemBytecodeCache(cacheDir)
|
||||
for expansionRule in expansionRules:
|
||||
ProcessExpansionRule(sourceFiles, templateFiles, templateCache, outputDir, projectDir, expansionRule, dryrun, verbose, dataInputSet, outputFiles)
|
||||
if not dryrun:
|
||||
elapsedTime = time.time() - startTime
|
||||
millis = int(round(elapsedTime * 10))
|
||||
m, s = divmod(elapsedTime, 60)
|
||||
h, m = divmod(m, 60)
|
||||
print('Total Time %d:%02d:%02d.%02d' % (h, m, s, millis))
|
||||
# Return true on success
|
||||
return errorCount == 0
|
||||
|
||||
# Main Function
|
||||
if __name__ == '__main__':
|
||||
# setup our command syntax
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("cacheDir", help="location to store jinja template cache files")
|
||||
parser.add_argument("outputDir", help="location to output generated files")
|
||||
parser.add_argument("projectDir", help="location to build directory against")
|
||||
parser.add_argument("inputFiles", help="set of files to run azcg expansion rules against")
|
||||
parser.add_argument("expansionRules", help="set of azcg expansion rules for matching data files to template files")
|
||||
parser.add_argument("-n", "--dryrun", action='store_true', help="does not execute autogen, only outputs the set of files that autogen would generate")
|
||||
parser.add_argument("-v", "--verbose", action='store_true', help="output only the set of files that would be generated by an expansion run")
|
||||
parser.add_argument("-p", "--pythonPaths", action='append', nargs='+', default=[""], help="set of additional python paths to use for module imports")
|
||||
|
||||
args = parser.parse_args()
|
||||
pythonPaths = args.pythonPaths
|
||||
cacheDir = args.cacheDir
|
||||
outputDir = args.outputDir
|
||||
projectDir = args.projectDir
|
||||
inputFiles = args.inputFiles.split(";")
|
||||
expansionRules = args.expansionRules.split(";")
|
||||
dryrun = args.dryrun
|
||||
verbose = args.verbose
|
||||
cacheDir = os.path.abspath(SanitizePath(cacheDir))
|
||||
outputDir = os.path.abspath(SanitizePath(outputDir))
|
||||
projectDir = os.path.abspath(SanitizePath(projectDir))
|
||||
|
||||
# Import 3rd party modules
|
||||
for pythonPath in pythonPaths:
|
||||
sys.path.append(pythonPath)
|
||||
import jinja2
|
||||
#from lxml import etree
|
||||
import xml.etree.cElementTree as etree
|
||||
import json
|
||||
|
||||
dataInputSet = {}
|
||||
outputFiles = []
|
||||
autoGenResult = ExecuteExpansionRules(cacheDir, outputDir, projectDir, inputFiles, expansionRules, dryrun, verbose, dataInputSet, outputFiles)
|
||||
if dryrun:
|
||||
print("%s" % ';'.join(outputFiles))
|
||||
if autoGenResult:
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(1)
|
||||
@@ -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
|
||||
#
|
||||
#
|
||||
|
||||
ly_add_target(
|
||||
NAME AzAutoGen HEADERONLY
|
||||
NAMESPACE AZ
|
||||
FILES_CMAKE
|
||||
azautogen_files.cmake
|
||||
)
|
||||
@@ -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
|
||||
#
|
||||
#
|
||||
|
||||
set(FILES
|
||||
AzAutoGen.py
|
||||
)
|
||||
@@ -84,7 +84,7 @@ namespace AZ
|
||||
else
|
||||
{
|
||||
AZ::Debug::Trace::Instance().Assert(__FILE__, __LINE__, AZ_FUNCTION_SIGNATURE,
|
||||
"Bus has multiple threads in its callstack records. Configure MutexType on the bus, or don't send to it from multiple threads");
|
||||
"Bus %s has multiple threads in its callstack records. Configure MutexType on the bus, or don't send to it from multiple threads", BusType::GetName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -268,7 +268,7 @@ namespace AZ
|
||||
m_messages.pop();
|
||||
if (numMessages == 1)
|
||||
{
|
||||
m_messages.get_container().clear(); // If it was the last message, free all memory.
|
||||
m_messages = {};
|
||||
}
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@@ -280,7 +280,7 @@ namespace AZ
|
||||
void Clear()
|
||||
{
|
||||
AZStd::lock_guard<MutexType> lock(m_messagesMutex);
|
||||
m_messages.get_container().clear();
|
||||
m_messages = {};
|
||||
}
|
||||
|
||||
void SetActive(bool isActive)
|
||||
@@ -289,7 +289,7 @@ namespace AZ
|
||||
m_isActive = isActive;
|
||||
if (!m_isActive)
|
||||
{
|
||||
m_messages.get_container().clear();
|
||||
m_messages = {};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#include <AzCore/base.h>
|
||||
#include <AzCore/Jobs/Job.h>
|
||||
#include <AzCore/Jobs/JobCancelGroup.h>
|
||||
#include <AzCore/Jobs/JobContext.h>
|
||||
#include <AzCore/Jobs/JobManager.h>
|
||||
#include <AzCore/std/parallel/atomic.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
Job::Job(bool isAutoDelete, AZ::JobContext* context, bool isCompletion, AZ::s8 priority)
|
||||
{
|
||||
if (context)
|
||||
{
|
||||
m_context = context;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_context = JobContext::GetParentContext();
|
||||
}
|
||||
|
||||
unsigned int countAndFlags = 1;
|
||||
if (isAutoDelete)
|
||||
{
|
||||
countAndFlags |= (unsigned int)FLAG_AUTO_DELETE;
|
||||
}
|
||||
if (isCompletion)
|
||||
{
|
||||
countAndFlags |= (unsigned int)FLAG_COMPLETION;
|
||||
}
|
||||
countAndFlags |= (unsigned int)((priority << FLAG_PRIORITY_START_BIT) & FLAG_PRIORITY_MASK);
|
||||
SetDependentCountAndFlags(countAndFlags);
|
||||
StoreDependent(NULL);
|
||||
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
SetState(STATE_SETUP);
|
||||
#endif // AZ_DEBUG_JOB_STATE
|
||||
}
|
||||
|
||||
void Job::Start()
|
||||
{
|
||||
//jobs are created with a count set to 1, we remove that count to allow the job to start
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert(m_state == STATE_SETUP, ("Jobs must be in the setup state before they can be started"));
|
||||
SetState(STATE_STARTED);
|
||||
#endif
|
||||
DecrementDependentCount();
|
||||
}
|
||||
|
||||
void Job::Reset(bool isClearDependent)
|
||||
{
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert((m_state == STATE_SETUP) || (m_state == STATE_PROCESSING), "Jobs must not be running when they are reset");
|
||||
SetState(STATE_SETUP);
|
||||
#endif
|
||||
unsigned int countAndFlags = GetDependentCountAndFlags();
|
||||
AZ_Assert((countAndFlags & (unsigned int)FLAG_AUTO_DELETE) == 0, "You can't call reset on AutoDelete jobs!");
|
||||
// Remove the FLAG_DEPENDENTCOUNT_MASK and FLAG_CHILD_JOBS flags
|
||||
countAndFlags = (countAndFlags & (~(FLAG_DEPENDENTCOUNT_MASK) & ~(FLAG_CHILD_JOBS))) | 1;
|
||||
SetDependentCountAndFlags(countAndFlags);
|
||||
if (isClearDependent)
|
||||
{
|
||||
StoreDependent(NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
Job* dependent = GetDependent();
|
||||
if (dependent)
|
||||
{
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert(dependent->m_state == STATE_SETUP, ("Dependent must be in setup state before it can be re-initialized"));
|
||||
#endif
|
||||
dependent->IncrementDependentCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Job::SetDependent(Job* dependent)
|
||||
{
|
||||
AZ_Assert(!GetDependent(), ("Job already has a dependent, should be cleared after the job is done"));
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert(m_state == STATE_SETUP, ("Dependent can only be set before the jobs are started"));
|
||||
AZ_Assert(dependent->m_state == STATE_SETUP, ("Dependent must be in the setup state"));
|
||||
#endif
|
||||
dependent->IncrementDependentCount();
|
||||
StoreDependent(dependent);
|
||||
}
|
||||
|
||||
void Job::SetDependentStarted(Job* dependent)
|
||||
{
|
||||
AZ_Assert(!GetDependent(), ("Job already has a dependent, should be cleared after the job is done"));
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert(m_state == STATE_SETUP, ("Dependent can only be set before the jobs are started"));
|
||||
//We don't require the dependent to be in STATE_SETUP, the user can call this from a context where they
|
||||
//know the dependent has not started yet, although it is in STATE_STARTED already, e.g. if SetDependent
|
||||
//is called from a job which the dependent is already dependent on.
|
||||
//Note that if the user gets this wrong, the dependent may start before this job is finished, and the asserts
|
||||
//may not even trigger due to race conditions. Hence why this function is 'experts only'.
|
||||
AZ_Assert((dependent->m_state == STATE_SETUP) || (dependent->m_state == STATE_STARTED)
|
||||
|| (dependent->m_state == STATE_SUSPENDED), "Dependent must be in the setup, started, or suspended state");
|
||||
#endif
|
||||
dependent->IncrementDependentCount();
|
||||
StoreDependent(dependent);
|
||||
}
|
||||
|
||||
void Job::SetDependentChild(Job* dependent)
|
||||
{
|
||||
AZ_Assert(!GetDependent(), ("Job already has a dependent, should be cleared after the job is done"));
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert(m_state == STATE_SETUP, ("Dependent can only be set before the jobs are started"));
|
||||
AZ_Assert(dependent->m_state == STATE_PROCESSING, "Dependent must be processing to add a child");
|
||||
#endif
|
||||
dependent->IncrementDependentCountAndSetChildFlag();
|
||||
StoreDependent(dependent);
|
||||
}
|
||||
|
||||
void Job::SetContinuation(Job* continuationJob)
|
||||
{
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert(m_state == STATE_PROCESSING, "Continuation jobs can only be set while we are processing, otherwise a regular dependent should be used");
|
||||
#endif
|
||||
Job* dependent = GetDependent();
|
||||
if (dependent) //nothing to do if there is no dependent... doesn't usually happen, except with synchronous processing and assists
|
||||
{
|
||||
continuationJob->SetDependentStarted(dependent);
|
||||
}
|
||||
}
|
||||
|
||||
void Job::StartAsChild(Job* childJob)
|
||||
{
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert(m_state == STATE_PROCESSING, "Child jobs can only be added while we are processing");
|
||||
#endif
|
||||
childJob->SetDependentChild(this);
|
||||
childJob->Start();
|
||||
}
|
||||
|
||||
void Job::WaitForChildren()
|
||||
{
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert(m_state == STATE_PROCESSING, "We must be currently processing in order to suspend");
|
||||
#endif
|
||||
if (GetDependentCount() != 0)
|
||||
{
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
SetState(STATE_SUSPENDED);
|
||||
#endif // AZ_DEBUG_JOB_STATE
|
||||
m_context->GetJobManager().SuspendJobUntilReady(this);
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
SetState(STATE_PROCESSING);
|
||||
#endif // AZ_DEBUG_JOB_STATE
|
||||
}
|
||||
AZ_Assert(GetDependentCount() == 0, "Suspended job has resumed, but still has non-zero dependent count, bug in JobManager?");
|
||||
}
|
||||
|
||||
bool Job::IsCancelled() const
|
||||
{
|
||||
JobCancelGroup* cancelGroup = m_context->GetCancelGroup();
|
||||
if (cancelGroup && cancelGroup->IsCancelled())
|
||||
{
|
||||
if (!IsCompletion()) // always run completion jobs, as they can be holding a synchronization primitive
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Job::IsAutoDelete() const
|
||||
{
|
||||
return (GetDependentCountAndFlags() & (unsigned int)FLAG_AUTO_DELETE) ? true : false;
|
||||
}
|
||||
|
||||
bool Job::IsCompletion() const
|
||||
{
|
||||
return (GetDependentCountAndFlags() & (unsigned int)FLAG_COMPLETION) ? true : false;
|
||||
}
|
||||
|
||||
void Job::StartAndAssistUntilComplete()
|
||||
{
|
||||
m_context->GetJobManager().StartJobAndAssistUntilComplete(this);
|
||||
}
|
||||
|
||||
void Job::StartAndWaitForCompletion()
|
||||
{
|
||||
//check if we are in a worker thread or a general user thread
|
||||
Job* currentJob = m_context->GetJobManager().GetCurrentJob();
|
||||
if (currentJob)
|
||||
{
|
||||
//worker thread, so just suspend this current job until the empty job completes
|
||||
currentJob->StartAsChild(this);
|
||||
currentJob->WaitForChildren();
|
||||
}
|
||||
else
|
||||
{
|
||||
StartAndAssistUntilComplete();
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Job::GetDependentCount() const
|
||||
{
|
||||
return (GetDependentCountAndFlags() & FLAG_DEPENDENTCOUNT_MASK);
|
||||
}
|
||||
|
||||
void Job::IncrementDependentCount()
|
||||
{
|
||||
AZ_Assert(GetDependentCount() < FLAG_DEPENDENTCOUNT_MASK, "Dependent count overflow");
|
||||
#ifdef AZCORE_JOBS_IMPL_SYNCHRONOUS
|
||||
++m_dependentCountAndFlags;
|
||||
#else
|
||||
m_dependentCountAndFlags.fetch_add(1, AZStd::memory_order_acq_rel);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Job::IncrementDependentCountAndSetChildFlag()
|
||||
{
|
||||
AZ_Assert(GetDependentCount() < FLAG_DEPENDENTCOUNT_MASK, "Dependent count overflow");
|
||||
#ifdef AZCORE_JOBS_IMPL_SYNCHRONOUS
|
||||
int oldCount = m_dependentCountAndFlags & FLAG_DEPENDENTCOUNT_MASK;
|
||||
m_dependentCountAndFlags = (m_dependentCountAndFlags & ~FLAG_DEPENDENTCOUNT_MASK) | (oldCount + 1) | FLAG_CHILD_JOBS;
|
||||
#else
|
||||
//use a single atomic operation to increment the count and set the child flag if possible
|
||||
unsigned int oldCountAndFlags, newCountAndFlags;
|
||||
do
|
||||
{
|
||||
oldCountAndFlags = m_dependentCountAndFlags.load(AZStd::memory_order_acquire);
|
||||
int oldCount = oldCountAndFlags & FLAG_DEPENDENTCOUNT_MASK;
|
||||
newCountAndFlags = (oldCountAndFlags & ~FLAG_DEPENDENTCOUNT_MASK) | (oldCount + 1) | FLAG_CHILD_JOBS;
|
||||
} while (!m_dependentCountAndFlags.compare_exchange_weak(oldCountAndFlags, newCountAndFlags, AZStd::memory_order_acq_rel, AZStd::memory_order_acquire));
|
||||
#endif
|
||||
}
|
||||
|
||||
void Job::DecrementDependentCount()
|
||||
{
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert((m_state == STATE_SETUP) || (m_state == STATE_STARTED)
|
||||
|| (m_state == STATE_PROCESSING) || (m_state == STATE_SUSPENDED), //child jobs
|
||||
"Job dependent count should not be decremented after job is already pending");
|
||||
#endif
|
||||
AZ_Assert(GetDependentCount() > 0, ("Job dependent count is already zero"));
|
||||
#ifdef AZCORE_JOBS_IMPL_SYNCHRONOUS
|
||||
unsigned int countAndFlags = m_dependentCountAndFlags--;
|
||||
#else
|
||||
unsigned int countAndFlags = m_dependentCountAndFlags.fetch_sub(1, AZStd::memory_order_acq_rel);
|
||||
#endif
|
||||
unsigned int count = countAndFlags & FLAG_DEPENDENTCOUNT_MASK;
|
||||
if (count == 1)
|
||||
{
|
||||
if (!(countAndFlags & FLAG_CHILD_JOBS))
|
||||
{
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert(m_state == STATE_STARTED, "Job has not been started but the dependent count is zero, must be a dependency error");
|
||||
SetState(STATE_PENDING);
|
||||
#endif
|
||||
m_context->GetJobManager().AddPendingJob(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AZ::s8 Job::GetPriority() const
|
||||
{
|
||||
return (GetDependentCountAndFlags() >> FLAG_PRIORITY_START_BIT) & 0xff;
|
||||
}
|
||||
|
||||
#ifdef AZCORE_JOBS_IMPL_SYNCHRONOUS
|
||||
void Job::StoreDependent(Job* job)
|
||||
{
|
||||
m_dependent = job;
|
||||
}
|
||||
|
||||
Job* Job::GetDependent() const
|
||||
{
|
||||
return m_dependent;
|
||||
}
|
||||
|
||||
void Job::SetDependentCountAndFlags(unsigned int countAndFlags)
|
||||
{
|
||||
m_dependentCountAndFlags = countAndFlags;
|
||||
}
|
||||
|
||||
unsigned int Job::GetDependentCountAndFlags() const
|
||||
{
|
||||
return m_dependentCountAndFlags;
|
||||
}
|
||||
#else
|
||||
void Job::StoreDependent(Job* job)
|
||||
{
|
||||
m_dependent.store(job, AZStd::memory_order_release);
|
||||
}
|
||||
|
||||
Job* Job::GetDependent() const
|
||||
{
|
||||
return m_dependent.load(AZStd::memory_order_acquire);
|
||||
}
|
||||
|
||||
void Job::SetDependentCountAndFlags(unsigned int countAndFlags)
|
||||
{
|
||||
m_dependentCountAndFlags.store(countAndFlags, AZStd::memory_order_release);
|
||||
}
|
||||
|
||||
unsigned int Job::GetDependentCountAndFlags() const
|
||||
{
|
||||
return m_dependentCountAndFlags.load(AZStd::memory_order_acquire);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -5,15 +5,14 @@
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef AZCORE_JOBS_JOB_H
|
||||
#define AZCORE_JOBS_JOB_H 1
|
||||
|
||||
#include <AzCore/base.h>
|
||||
#include <AzCore/Jobs/JobCancelGroup.h>
|
||||
#include <AzCore/Jobs/JobContext.h>
|
||||
#include <AzCore/Jobs/JobManager.h>
|
||||
#include <AzCore/std/parallel/atomic.h>
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/base.h>
|
||||
#include <AzCore/Jobs/JobCancelGroup.h>
|
||||
#include <AzCore/Jobs/JobContext.h>
|
||||
#include <AzCore/Jobs/JobManager.h>
|
||||
#include <AzCore/std/parallel/atomic.h>
|
||||
|
||||
#include <AzCore/Memory/PoolAllocator.h>
|
||||
|
||||
#if defined(_DEBUG)
|
||||
@@ -234,319 +233,22 @@ namespace AZ
|
||||
//would require atomic ops to set/read it, so not really worth it.
|
||||
int m_state;
|
||||
};
|
||||
|
||||
//============================================================================================================
|
||||
//============================================================================================================
|
||||
//============================================================================================================
|
||||
|
||||
inline Job::Job(bool isAutoDelete, JobContext* context, bool isCompletion, AZ::s8 priority)
|
||||
{
|
||||
if (context)
|
||||
{
|
||||
m_context = context;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_context = JobContext::GetParentContext();
|
||||
}
|
||||
|
||||
unsigned int countAndFlags = 1;
|
||||
if (isAutoDelete)
|
||||
{
|
||||
countAndFlags |= (unsigned int)FLAG_AUTO_DELETE;
|
||||
}
|
||||
if (isCompletion)
|
||||
{
|
||||
countAndFlags |= (unsigned int)FLAG_COMPLETION;
|
||||
}
|
||||
countAndFlags |= (unsigned int)((priority << FLAG_PRIORITY_START_BIT) & FLAG_PRIORITY_MASK);
|
||||
SetDependentCountAndFlags(countAndFlags);
|
||||
StoreDependent(NULL);
|
||||
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
SetState(STATE_SETUP);
|
||||
#endif // AZ_DEBUG_JOB_STATE
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE void Job::Start()
|
||||
{
|
||||
//jobs are created with a count set to 1, we remove that count to allow the job to start
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert(m_state == STATE_SETUP, ("Jobs must be in the setup state before they can be started"));
|
||||
SetState(STATE_STARTED);
|
||||
#endif
|
||||
DecrementDependentCount();
|
||||
}
|
||||
|
||||
inline void Job::Reset(bool isClearDependent)
|
||||
{
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert((m_state == STATE_SETUP) || (m_state == STATE_PROCESSING), "Jobs must not be running when they are reset");
|
||||
SetState(STATE_SETUP);
|
||||
#endif
|
||||
unsigned int countAndFlags = GetDependentCountAndFlags();
|
||||
AZ_Assert((countAndFlags & (unsigned int)FLAG_AUTO_DELETE) == 0, "You can't call reset on AutoDelete jobs!");
|
||||
// Remove the FLAG_DEPENDENTCOUNT_MASK and FLAG_CHILD_JOBS flags
|
||||
countAndFlags = (countAndFlags & (~(FLAG_DEPENDENTCOUNT_MASK) & ~(FLAG_CHILD_JOBS))) | 1;
|
||||
SetDependentCountAndFlags(countAndFlags);
|
||||
if (isClearDependent)
|
||||
{
|
||||
StoreDependent(NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
Job* dependent = GetDependent();
|
||||
if (dependent)
|
||||
{
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert(dependent->m_state == STATE_SETUP, ("Dependent must be in setup state before it can be re-initialized"));
|
||||
#endif
|
||||
dependent->IncrementDependentCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE void Job::SetDependent(Job* dependent)
|
||||
{
|
||||
AZ_Assert(!GetDependent(), ("Job already has a dependent, should be cleared after the job is done"));
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert(m_state == STATE_SETUP, ("Dependent can only be set before the jobs are started"));
|
||||
AZ_Assert(dependent->m_state == STATE_SETUP, ("Dependent must be in the setup state"));
|
||||
#endif
|
||||
dependent->IncrementDependentCount();
|
||||
StoreDependent(dependent);
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE void Job::SetDependentStarted(Job* dependent)
|
||||
{
|
||||
AZ_Assert(!GetDependent(), ("Job already has a dependent, should be cleared after the job is done"));
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert(m_state == STATE_SETUP, ("Dependent can only be set before the jobs are started"));
|
||||
//We don't require the dependent to be in STATE_SETUP, the user can call this from a context where they
|
||||
//know the dependent has not started yet, although it is in STATE_STARTED already, e.g. if SetDependent
|
||||
//is called from a job which the dependent is already dependent on.
|
||||
//Note that if the user gets this wrong, the dependent may start before this job is finished, and the asserts
|
||||
//may not even trigger due to race conditions. Hence why this function is 'experts only'.
|
||||
AZ_Assert((dependent->m_state == STATE_SETUP) || (dependent->m_state == STATE_STARTED)
|
||||
|| (dependent->m_state == STATE_SUSPENDED), "Dependent must be in the setup, started, or suspended state");
|
||||
#endif
|
||||
dependent->IncrementDependentCount();
|
||||
StoreDependent(dependent);
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE void Job::SetDependentChild(Job* dependent)
|
||||
{
|
||||
AZ_Assert(!GetDependent(), ("Job already has a dependent, should be cleared after the job is done"));
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert(m_state == STATE_SETUP, ("Dependent can only be set before the jobs are started"));
|
||||
AZ_Assert(dependent->m_state == STATE_PROCESSING, "Dependent must be processing to add a child");
|
||||
#endif
|
||||
dependent->IncrementDependentCountAndSetChildFlag();
|
||||
StoreDependent(dependent);
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE void Job::SetContinuation(Job* continuationJob)
|
||||
{
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert(m_state == STATE_PROCESSING, "Continuation jobs can only be set while we are processing, otherwise a regular dependent should be used");
|
||||
#endif
|
||||
Job* dependent = GetDependent();
|
||||
if (dependent) //nothing to do if there is no dependent... doesn't usually happen, except with synchronous processing and assists
|
||||
{
|
||||
continuationJob->SetDependentStarted(dependent);
|
||||
}
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE void Job::StartAsChild(Job* childJob)
|
||||
{
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert(m_state == STATE_PROCESSING, "Child jobs can only be added while we are processing");
|
||||
#endif
|
||||
childJob->SetDependentChild(this);
|
||||
childJob->Start();
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE void Job::WaitForChildren()
|
||||
{
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert(m_state == STATE_PROCESSING, "We must be currently processing in order to suspend");
|
||||
#endif
|
||||
if (GetDependentCount() != 0)
|
||||
{
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
SetState(STATE_SUSPENDED);
|
||||
#endif // AZ_DEBUG_JOB_STATE
|
||||
m_context->GetJobManager().SuspendJobUntilReady(this);
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
SetState(STATE_PROCESSING);
|
||||
#endif // AZ_DEBUG_JOB_STATE
|
||||
}
|
||||
AZ_Assert(GetDependentCount() == 0, "Suspended job has resumed, but still has non-zero dependent count, bug in JobManager?");
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE bool Job::IsCancelled() const
|
||||
{
|
||||
JobCancelGroup* cancelGroup = m_context->GetCancelGroup();
|
||||
if (cancelGroup && cancelGroup->IsCancelled())
|
||||
{
|
||||
if (!IsCompletion()) // always run completion jobs, as they can be holding a synchronization primitive
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE bool Job::IsAutoDelete() const
|
||||
{
|
||||
return (GetDependentCountAndFlags() & (unsigned int)FLAG_AUTO_DELETE) ? true : false;
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE bool Job::IsCompletion() const
|
||||
{
|
||||
return (GetDependentCountAndFlags() & (unsigned int)FLAG_COMPLETION) ? true : false;
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE void Job::StartAndAssistUntilComplete()
|
||||
{
|
||||
m_context->GetJobManager().StartJobAndAssistUntilComplete(this);
|
||||
}
|
||||
|
||||
inline void Job::StartAndWaitForCompletion()
|
||||
{
|
||||
//check if we are in a worker thread or a general user thread
|
||||
Job* currentJob = m_context->GetJobManager().GetCurrentJob();
|
||||
if (currentJob)
|
||||
{
|
||||
//worker thread, so just suspend this current job until the empty job completes
|
||||
currentJob->StartAsChild(this);
|
||||
currentJob->WaitForChildren();
|
||||
}
|
||||
else
|
||||
{
|
||||
StartAndAssistUntilComplete();
|
||||
}
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE JobContext* Job::GetContext() const
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Inline implementations
|
||||
inline JobContext* Job::GetContext() const
|
||||
{
|
||||
return m_context;
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE unsigned int Job::GetDependentCount() const
|
||||
{
|
||||
return (GetDependentCountAndFlags() & FLAG_DEPENDENTCOUNT_MASK);
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE void Job::IncrementDependentCount()
|
||||
{
|
||||
AZ_Assert(GetDependentCount() < FLAG_DEPENDENTCOUNT_MASK, "Dependent count overflow");
|
||||
#ifdef AZCORE_JOBS_IMPL_SYNCHRONOUS
|
||||
++m_dependentCountAndFlags;
|
||||
#else
|
||||
m_dependentCountAndFlags.fetch_add(1, AZStd::memory_order_acq_rel);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void Job::IncrementDependentCountAndSetChildFlag()
|
||||
{
|
||||
AZ_Assert(GetDependentCount() < FLAG_DEPENDENTCOUNT_MASK, "Dependent count overflow");
|
||||
#ifdef AZCORE_JOBS_IMPL_SYNCHRONOUS
|
||||
int oldCount = m_dependentCountAndFlags & FLAG_DEPENDENTCOUNT_MASK;
|
||||
m_dependentCountAndFlags = (m_dependentCountAndFlags & ~FLAG_DEPENDENTCOUNT_MASK) | (oldCount + 1) | FLAG_CHILD_JOBS;
|
||||
#else
|
||||
//use a single atomic operation to increment the count and set the child flag if possible
|
||||
unsigned int oldCountAndFlags, newCountAndFlags;
|
||||
do
|
||||
{
|
||||
oldCountAndFlags = m_dependentCountAndFlags.load(AZStd::memory_order_acquire);
|
||||
int oldCount = oldCountAndFlags & FLAG_DEPENDENTCOUNT_MASK;
|
||||
newCountAndFlags = (oldCountAndFlags & ~FLAG_DEPENDENTCOUNT_MASK) | (oldCount + 1) | FLAG_CHILD_JOBS;
|
||||
} while (!m_dependentCountAndFlags.compare_exchange_weak(oldCountAndFlags, newCountAndFlags, AZStd::memory_order_acq_rel, AZStd::memory_order_acquire));
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void Job::DecrementDependentCount()
|
||||
{
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert((m_state == STATE_SETUP) || (m_state == STATE_STARTED)
|
||||
|| (m_state == STATE_PROCESSING) || (m_state == STATE_SUSPENDED), //child jobs
|
||||
"Job dependent count should not be decremented after job is already pending");
|
||||
#endif
|
||||
AZ_Assert(GetDependentCount() > 0, ("Job dependent count is already zero"));
|
||||
#ifdef AZCORE_JOBS_IMPL_SYNCHRONOUS
|
||||
unsigned int countAndFlags = m_dependentCountAndFlags--;
|
||||
#else
|
||||
unsigned int countAndFlags = m_dependentCountAndFlags.fetch_sub(1, AZStd::memory_order_acq_rel);
|
||||
#endif
|
||||
unsigned int count = countAndFlags & FLAG_DEPENDENTCOUNT_MASK;
|
||||
if (count == 1)
|
||||
{
|
||||
if (!(countAndFlags & FLAG_CHILD_JOBS))
|
||||
{
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_Assert(m_state == STATE_STARTED, "Job has not been started but the dependent count is zero, must be a dependency error");
|
||||
SetState(STATE_PENDING);
|
||||
#endif
|
||||
m_context->GetJobManager().AddPendingJob(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline AZ::s8 Job::GetPriority() const
|
||||
{
|
||||
return (GetDependentCountAndFlags() >> FLAG_PRIORITY_START_BIT) & 0xff;
|
||||
}
|
||||
|
||||
#ifdef AZ_DEBUG_JOB_STATE
|
||||
AZ_FORCE_INLINE void Job::SetState(int state)
|
||||
inline void Job::SetState(int state)
|
||||
{
|
||||
m_state = state;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef AZCORE_JOBS_IMPL_SYNCHRONOUS
|
||||
AZ_FORCE_INLINE void Job::StoreDependent(Job* job)
|
||||
{
|
||||
m_dependent = job;
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE Job* Job::GetDependent() const
|
||||
{
|
||||
return m_dependent;
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE void Job::SetDependentCountAndFlags(unsigned int countAndFlags)
|
||||
{
|
||||
m_dependentCountAndFlags = countAndFlags;
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE unsigned int Job::GetDependentCountAndFlags() const
|
||||
{
|
||||
return m_dependentCountAndFlags;
|
||||
}
|
||||
#else
|
||||
AZ_FORCE_INLINE void Job::StoreDependent(Job* job)
|
||||
{
|
||||
m_dependent.store(job, AZStd::memory_order_release);
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE Job* Job::GetDependent() const
|
||||
{
|
||||
return m_dependent.load(AZStd::memory_order_acquire);
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE void Job::SetDependentCountAndFlags(unsigned int countAndFlags)
|
||||
{
|
||||
m_dependentCountAndFlags.store(countAndFlags, AZStd::memory_order_release);
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE unsigned int Job::GetDependentCountAndFlags() const
|
||||
{
|
||||
return m_dependentCountAndFlags.load(AZStd::memory_order_acquire);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
#pragma once
|
||||
|
||||
|
||||
@@ -42,8 +42,6 @@ namespace AZStd
|
||||
class unordered_multiset;
|
||||
template<AZStd::size_t NumBits>
|
||||
class bitset;
|
||||
template<class T, class Container/* = AZStd::deque<T>*/ >
|
||||
class stack;
|
||||
|
||||
template<class T>
|
||||
class intrusive_ptr;
|
||||
|
||||
@@ -236,6 +236,17 @@ namespace AZ
|
||||
*/
|
||||
ClassBuilder* ClassElement(Crc32 elementIdCrc, const char* description);
|
||||
|
||||
|
||||
/**
|
||||
* Declare element with attributes that belong to the class SerializeContext::Class, this is a logical structure, you can have one or more GroupElementToggles.
|
||||
* T must be a boolean variable that will enable and disable each DataElement attached to this structure.
|
||||
* \param description - Descriptive name of the field that will typically appear in a tooltip.
|
||||
* \param memberVariable - reference to the member variable so we can bind to serialization data.
|
||||
*/
|
||||
template<class T>
|
||||
ClassBuilder* GroupElementToggle(const char* description, T memberVariable);
|
||||
|
||||
|
||||
/**
|
||||
* Declare element with an associated UI handler that does not represent a specific class member variable.
|
||||
* \param uiId - name of a UI handler used to display the element
|
||||
@@ -515,6 +526,15 @@ namespace AZ
|
||||
return this;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// ClassElement
|
||||
//=========================================================================
|
||||
template<class T>
|
||||
inline EditContext::ClassBuilder* EditContext::ClassBuilder::GroupElementToggle(const char* name, T memberVariable)
|
||||
{
|
||||
return DataElement(AZ::Edit::ClassElements::Group, memberVariable, name, name, "");
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// UIElement
|
||||
//=========================================================================
|
||||
|
||||
@@ -57,6 +57,7 @@ namespace AZ::Internal
|
||||
// and avoid all this logic.
|
||||
|
||||
using namespace AZ::SettingsRegistryMergeUtils;
|
||||
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
||||
|
||||
AZ::IO::FixedMaxPath engineRoot;
|
||||
if (auto engineManifestPath = AZ::Utils::GetEngineManifestPath(); !engineManifestPath.empty())
|
||||
@@ -72,45 +73,16 @@ namespace AZ::Internal
|
||||
struct EngineInfo
|
||||
{
|
||||
AZ::IO::FixedMaxPath m_path;
|
||||
AZ::SettingsRegistryInterface::FixedValueString m_moniker;
|
||||
FixedValueString m_moniker;
|
||||
};
|
||||
|
||||
struct EnginePathsVisitor : public AZ::SettingsRegistryInterface::Visitor
|
||||
{
|
||||
void Visit(
|
||||
[[maybe_unused]] AZStd::string_view path, [[maybe_unused]] AZStd::string_view valueName,
|
||||
[[maybe_unused]] AZStd::string_view path, AZStd::string_view valueName,
|
||||
[[maybe_unused]] AZ::SettingsRegistryInterface::Type type, AZStd::string_view value) override
|
||||
{
|
||||
m_enginePaths.emplace_back(EngineInfo{AZ::IO::FixedMaxPath{value}.LexicallyNormal(), {}});
|
||||
}
|
||||
|
||||
AZ::SettingsRegistryInterface::VisitResponse Traverse(
|
||||
[[maybe_unused]] AZStd::string_view path, AZStd::string_view valueName,
|
||||
AZ::SettingsRegistryInterface::VisitAction action, AZ::SettingsRegistryInterface::Type type) override
|
||||
{
|
||||
auto response = AZ::SettingsRegistryInterface::VisitResponse::Continue;
|
||||
if (action == AZ::SettingsRegistryInterface::VisitAction::Begin)
|
||||
{
|
||||
if (type == AZ::SettingsRegistryInterface::Type::Array)
|
||||
{
|
||||
if (valueName.compare("engines") != 0)
|
||||
{
|
||||
response = AZ::SettingsRegistryInterface::VisitResponse::Skip;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (action == AZ::SettingsRegistryInterface::VisitAction::Value)
|
||||
{
|
||||
if (type == AZ::SettingsRegistryInterface::Type::String)
|
||||
{
|
||||
if (valueName.compare("path") != 0)
|
||||
{
|
||||
response = AZ::SettingsRegistryInterface::VisitResponse::Skip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
m_enginePaths.emplace_back(EngineInfo{ AZ::IO::FixedMaxPath{value}.LexicallyNormal(), FixedValueString{valueName} });
|
||||
}
|
||||
|
||||
AZStd::vector<EngineInfo> m_enginePaths{};
|
||||
@@ -119,11 +91,11 @@ namespace AZ::Internal
|
||||
EnginePathsVisitor pathVisitor;
|
||||
if (manifestLoaded)
|
||||
{
|
||||
auto enginePathsKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/engines", EngineManifestRootKey);
|
||||
auto enginePathsKey = FixedValueString::format("%s/engines_path", EngineManifestRootKey);
|
||||
settingsRegistry.Visit(pathVisitor, enginePathsKey);
|
||||
}
|
||||
|
||||
const auto engineMonikerKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/engine_name", EngineSettingsRootKey);
|
||||
const auto engineMonikerKey = FixedValueString::format("%s/engine_name", EngineSettingsRootKey);
|
||||
|
||||
AZStd::set<AZ::IO::FixedMaxPath> projectPathsNotFound;
|
||||
|
||||
@@ -135,7 +107,15 @@ namespace AZ::Internal
|
||||
if (settingsRegistry.MergeSettingsFile(
|
||||
engineSettingsPath.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, EngineSettingsRootKey))
|
||||
{
|
||||
settingsRegistry.Get(engineInfo.m_moniker, engineMonikerKey);
|
||||
FixedValueString engineName;
|
||||
settingsRegistry.Get(engineName, engineMonikerKey);
|
||||
AZ_Warning("SettingsRegistryMergeUtils",engineInfo.m_moniker == engineName,
|
||||
R"(The engine name key "%s" mapped to engine path "%s" within the global manifest of "%s")"
|
||||
R"( does not match the "engine_name" field "%s" in the engine.json)" "\n"
|
||||
"This engine should be re-registered.",
|
||||
engineInfo.m_moniker.c_str(), engineInfo.m_path.c_str(), engineManifestPath.c_str(),
|
||||
engineName.c_str())
|
||||
engineInfo.m_moniker = engineName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -221,6 +221,7 @@ set(FILES
|
||||
Jobs/Internal/JobManagerWorkStealing.cpp
|
||||
Jobs/Internal/JobManagerWorkStealing.h
|
||||
Jobs/Internal/JobNotify.h
|
||||
Jobs/Job.cpp
|
||||
Jobs/Job.h
|
||||
Jobs/JobCancelGroup.h
|
||||
Jobs/JobCompletion.h
|
||||
|
||||
@@ -5,206 +5,17 @@
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef AZSTD_QUEUE_H
|
||||
#define AZSTD_QUEUE_H 1
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/std/containers/deque.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/functional_basic.h>
|
||||
#include <queue>
|
||||
|
||||
namespace AZStd
|
||||
{
|
||||
/**
|
||||
* FIFO queue complaint with \ref CStd (23.2.3.1)
|
||||
* The only extension we have is that we allow access
|
||||
* to the underlying container via: get_container function.
|
||||
* Check the queue \ref AZStdExamples.
|
||||
*/
|
||||
template<class T, class Container = AZStd::deque<T> >
|
||||
class queue
|
||||
{
|
||||
enum
|
||||
{
|
||||
CONTAINER_VERSION = 1
|
||||
};
|
||||
public:
|
||||
typedef queue<T, Container> this_type;
|
||||
typedef Container container_type;
|
||||
typedef typename Container::value_type value_type;
|
||||
typedef typename Container::size_type size_type;
|
||||
typedef typename Container::reference reference;
|
||||
typedef typename Container::const_reference const_reference;
|
||||
|
||||
AZ_FORCE_INLINE queue() {}
|
||||
|
||||
AZ_FORCE_INLINE explicit queue(const container_type& container)
|
||||
: m_container(container) {}
|
||||
AZ_FORCE_INLINE bool empty() const { return m_container.empty(); }
|
||||
AZ_FORCE_INLINE size_type size() const { return m_container.size(); }
|
||||
AZ_FORCE_INLINE reference front() { return m_container.front(); }
|
||||
AZ_FORCE_INLINE const_reference front() const { return m_container.front(); }
|
||||
AZ_FORCE_INLINE reference back() { return m_container.back(); }
|
||||
AZ_FORCE_INLINE const_reference back() const { return m_container.back(); }
|
||||
AZ_FORCE_INLINE void push(const value_type& value) { m_container.push_back(value); }
|
||||
AZ_FORCE_INLINE void pop() { m_container.pop_front(); }
|
||||
|
||||
AZ_FORCE_INLINE void push() { m_container.push_back(); }
|
||||
|
||||
AZ_FORCE_INLINE queue(this_type&& rhs)
|
||||
: m_container(AZStd::move(rhs.m_container)) {}
|
||||
AZ_FORCE_INLINE explicit queue(Container&& container)
|
||||
: m_container(AZStd::move(container)) {}
|
||||
this_type& operator=(this_type&& rhs)
|
||||
{
|
||||
m_container = AZStd::move(rhs.m_container);
|
||||
return (*this);
|
||||
}
|
||||
void push(value_type&& value) { m_container.push_back(AZStd::move(value)); }
|
||||
template<class... Args>
|
||||
void emplace(Args&&... args) { m_container.emplace_back(AZStd::forward<Args>(args)...); }
|
||||
void swap(this_type& rhs) { AZStd::swap(m_container, rhs.m_container); }
|
||||
|
||||
AZ_FORCE_INLINE Container& get_container() { return m_container; }
|
||||
AZ_FORCE_INLINE const Container& get_container() const { return m_container; }
|
||||
|
||||
protected:
|
||||
Container m_container;
|
||||
};
|
||||
|
||||
// queue TEMPLATE FUNCTIONS
|
||||
template<class T, class Container>
|
||||
AZ_FORCE_INLINE bool operator==(const AZStd::queue<T, Container>& left, const AZStd::queue<T, Container>& right)
|
||||
{
|
||||
return left.get_container() == right.get_container();
|
||||
}
|
||||
|
||||
template<class T, class Container>
|
||||
AZ_FORCE_INLINE bool operator!=(const AZStd::queue<T, Container>& left, const AZStd::queue<T, Container>& right)
|
||||
{
|
||||
return left.get_container() != right.get_container();
|
||||
}
|
||||
|
||||
/* template<class T, class Container>
|
||||
AZ_FORCE_INLINE bool operator<(const queue<T,Container>& left, const queue<T,Container>& right)
|
||||
{
|
||||
return left.get_container() < right.get_container();
|
||||
}
|
||||
|
||||
template<class T, class Container>
|
||||
AZ_FORCE_INLINE bool operator>(const queue<T,Container>& left, const queue<T,Container>& right)
|
||||
{
|
||||
return left.get_container() > right.get_container();
|
||||
}
|
||||
|
||||
template<class T, class Container>
|
||||
AZ_FORCE_INLINE operator<=(const queue<T,Container>& left, const queue<T,Container>& right)
|
||||
{
|
||||
return left.get_container() <= right.get_container();
|
||||
}
|
||||
|
||||
template<class T, class Container>
|
||||
AZ_FORCE_INLINE bool operator>=(const queue<T,Container>& left, const queue<T,Container>& right)
|
||||
{
|
||||
return left.get_container() >= right.get_container();
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Priority queue is complaint with \ref CStd (23.2.3.2)
|
||||
* The only extension we have is that we allow access
|
||||
* to the underlying container via: get_container function.
|
||||
* Check the priority_queue \ref AZStdExamples.
|
||||
*/
|
||||
template<class T, class Container = AZStd::vector<T>, class Predicate = AZStd::less<typename Container::value_type> >
|
||||
class priority_queue
|
||||
{
|
||||
enum
|
||||
{
|
||||
CONTAINER_VERSION = 1
|
||||
};
|
||||
public:
|
||||
typedef priority_queue<T, Container, Predicate> this_type;
|
||||
typedef Container container_type;
|
||||
typedef typename Container::value_type value_type;
|
||||
typedef typename Container::size_type size_type;
|
||||
typedef typename Container::reference reference;
|
||||
typedef typename Container::const_reference const_reference;
|
||||
|
||||
AZ_FORCE_INLINE priority_queue() {}
|
||||
AZ_FORCE_INLINE explicit priority_queue(const Predicate& comp)
|
||||
: m_comp(comp) {}
|
||||
AZ_FORCE_INLINE priority_queue(const Predicate& comp, const container_type& container)
|
||||
: m_container(container)
|
||||
, m_comp(comp)
|
||||
{
|
||||
// construct by copying specified container, comparator
|
||||
AZStd::make_heap(m_container.begin(), m_container.end(), comp);
|
||||
}
|
||||
template<class InputIterator>
|
||||
AZ_FORCE_INLINE priority_queue(InputIterator first, InputIterator last)
|
||||
: m_container(first, last)
|
||||
, m_comp()
|
||||
{
|
||||
AZStd::make_heap(m_container.begin(), m_container.end(), m_comp);
|
||||
}
|
||||
|
||||
template<class InputIterator>
|
||||
AZ_FORCE_INLINE priority_queue(InputIterator first, InputIterator last, const Predicate& comp)
|
||||
: m_container(first, last)
|
||||
, m_comp(comp)
|
||||
{ // construct by copying [_First, _Last), specified comparator
|
||||
AZStd::make_heap(m_container.begin(), m_container.end(), m_comp);
|
||||
}
|
||||
|
||||
template<class InputIterator>
|
||||
AZ_FORCE_INLINE priority_queue(InputIterator first, InputIterator last, const Predicate& comp, const container_type& container)
|
||||
: m_container(container)
|
||||
, m_comp(comp)
|
||||
{ // construct by copying [_First, _Last), container, and comparator
|
||||
m_container.insert(m_container.end(), first, last);
|
||||
AZStd::make_heap(m_container.begin(), m_container.end(), m_comp);
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE bool empty() const { return m_container.empty(); }
|
||||
AZ_FORCE_INLINE size_type size() const { return m_container.size(); }
|
||||
AZ_FORCE_INLINE const_reference top() const { return m_container.front(); }
|
||||
AZ_FORCE_INLINE reference top() { return m_container.front(); }
|
||||
AZ_FORCE_INLINE void push(const value_type& value)
|
||||
{
|
||||
m_container.push_back(value);
|
||||
AZStd::push_heap(m_container.begin(), m_container.end(), m_comp);
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE void pop()
|
||||
{
|
||||
AZStd::pop_heap(m_container.begin(), m_container.end(), m_comp);
|
||||
m_container.pop_back();
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE priority_queue(this_type&& rhs)
|
||||
: m_container(AZStd::move(rhs.m_container))
|
||||
, m_comp(AZStd::move(rhs.m_comp)) {}
|
||||
AZ_FORCE_INLINE explicit priority_queue(const Predicate& pred, Container&& container)
|
||||
: m_container(AZStd::move(container))
|
||||
, m_comp(pred) {}
|
||||
this_type& operator=(this_type&& rhs)
|
||||
{
|
||||
m_container = AZStd::move(rhs.m_container);
|
||||
m_comp = AZStd::move(rhs.m_comp);
|
||||
return (*this);
|
||||
}
|
||||
void push(value_type&& value) { m_container.push_back(AZStd::move(value)); AZStd::push_heap(m_container.begin(), m_container.end(), m_comp); }
|
||||
template<class Args>
|
||||
void emplace(Args&& args) { m_container.emplace_back(AZStd::forward<Args>(args)); AZStd::push_heap(m_container.begin(), m_container.end(), m_comp); }
|
||||
void swap(this_type& rhs) { AZStd::swap(m_container, rhs.m_container); AZStd::swap(m_comp, rhs.m_comp); }
|
||||
|
||||
AZ_FORCE_INLINE Container& get_container() { return m_container; }
|
||||
AZ_FORCE_INLINE const Container& get_container() const { return m_container; }
|
||||
|
||||
protected:
|
||||
Container m_container;
|
||||
Predicate m_comp;
|
||||
};
|
||||
template<class T, class Container = AZStd::deque<T>>
|
||||
using queue = std::queue<T, Container>;
|
||||
template<class T, class Container = AZStd::vector<T>, class Compare = AZStd::less<typename Container::value_type>>
|
||||
using priority_queue = std::priority_queue<T, Container, Compare>;
|
||||
}
|
||||
|
||||
#endif // AZSTD_QUEUE_H
|
||||
#pragma once
|
||||
|
||||
@@ -5,103 +5,13 @@
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#ifndef AZSTD_STACK_H
|
||||
#define AZSTD_STACK_H 1
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/std/containers/deque.h>
|
||||
#include <stack>
|
||||
|
||||
namespace AZStd
|
||||
{
|
||||
/**
|
||||
* Stack container is complaint with \ref CStd (23.2.3.3)
|
||||
* The only extension we have is that we allow access
|
||||
* to the underlying container via: get_container function.
|
||||
* Check the stack \ref AZStdExamples.
|
||||
*/
|
||||
template<class T, class Container = AZStd::deque<T> >
|
||||
class stack
|
||||
{
|
||||
enum
|
||||
{
|
||||
CONTAINER_VERSION = 1
|
||||
};
|
||||
public:
|
||||
typedef stack<T, Container> this_type;
|
||||
typedef Container container_type;
|
||||
typedef typename Container::value_type value_type;
|
||||
typedef typename Container::size_type size_type;
|
||||
typedef typename Container::reference reference;
|
||||
typedef typename Container::const_reference const_reference;
|
||||
|
||||
AZ_FORCE_INLINE stack() {}
|
||||
AZ_FORCE_INLINE explicit stack(const container_type& container)
|
||||
: m_container(container) {}
|
||||
AZ_FORCE_INLINE bool empty() const { return m_container.empty(); }
|
||||
AZ_FORCE_INLINE size_type size() const { return m_container.size(); }
|
||||
AZ_FORCE_INLINE reference top() { return m_container.back(); }
|
||||
AZ_FORCE_INLINE const_reference top() const { return m_container.back(); }
|
||||
AZ_FORCE_INLINE reference back() { return m_container.back(); }
|
||||
AZ_FORCE_INLINE const_reference back() const { return m_container.back(); }
|
||||
AZ_FORCE_INLINE void push(const value_type& value) { m_container.push_back(value); }
|
||||
AZ_FORCE_INLINE void pop() { m_container.pop_back(); }
|
||||
AZ_FORCE_INLINE void push() { m_container.push_back(); }
|
||||
|
||||
AZ_FORCE_INLINE stack(this_type&& rhs)
|
||||
: m_container(AZStd::move(rhs.m_container)) {}
|
||||
AZ_FORCE_INLINE explicit stack(Container&& container)
|
||||
: m_container(AZStd::move(container)) {}
|
||||
this_type& operator=(this_type&& rhs) { m_container = AZStd::move(rhs.m_container); return *this; }
|
||||
void push(value_type&& value) { m_container.push_back(AZStd::move(value)); }
|
||||
template<class Args>
|
||||
void emplace(Args&& args) { m_container.emplace_back(AZStd::forward<Args>(args)); }
|
||||
void swap(this_type&& rhs) { m_container.swap(AZStd::move(rhs.m_container)); }
|
||||
|
||||
void swap(this_type& rhs) { AZStd::swap(m_container, rhs.m_container); }
|
||||
|
||||
AZ_FORCE_INLINE Container& get_container() { return m_container; }
|
||||
AZ_FORCE_INLINE const Container& get_container() const { return m_container; }
|
||||
|
||||
protected:
|
||||
Container m_container;
|
||||
};
|
||||
|
||||
// queue TEMPLATE FUNCTIONS
|
||||
template<class T, class Container>
|
||||
AZ_FORCE_INLINE bool operator==(const AZStd::stack<T, Container>& left, const AZStd::stack<T, Container>& right)
|
||||
{
|
||||
return left.get_container() == right.get_container();
|
||||
}
|
||||
|
||||
template<class T, class Container>
|
||||
AZ_FORCE_INLINE bool operator!=(const AZStd::stack<T, Container>& left, const AZStd::stack<T, Container>& right)
|
||||
{
|
||||
return left.get_container() != right.get_container();
|
||||
}
|
||||
|
||||
/* template<class T, class Container>
|
||||
AZ_FORCE_INLINE bool operator<(const queue<T,Container>& left, const queue<T,Container>& right)
|
||||
{
|
||||
return left.get_container() < right.get_container();
|
||||
}
|
||||
|
||||
template<class T, class Container>
|
||||
AZ_FORCE_INLINE bool operator>(const queue<T,Container>& left, const queue<T,Container>& right)
|
||||
{
|
||||
return left.get_container() > right.get_container();
|
||||
}
|
||||
|
||||
template<class T, class Container>
|
||||
AZ_FORCE_INLINE operator<=(const queue<T,Container>& left, const queue<T,Container>& right)
|
||||
{
|
||||
return left.get_container() <= right.get_container();
|
||||
}
|
||||
|
||||
template<class T, class Container>
|
||||
AZ_FORCE_INLINE bool operator>=(const queue<T,Container>& left, const queue<T,Container>& right)
|
||||
{
|
||||
return left.get_container() >= right.get_container();
|
||||
}*/
|
||||
template<class T, class Container = AZStd::deque<T>>
|
||||
using stack = std::stack<T, Container>;
|
||||
}
|
||||
|
||||
#endif // AZSTD_STACK_H
|
||||
#pragma once
|
||||
|
||||
@@ -298,7 +298,7 @@ namespace UnitTest
|
||||
AZ_TEST_ASSERT(int_queue.empty());
|
||||
AZ_TEST_ASSERT(int_queue.size() == 0);
|
||||
|
||||
// Queue uses deque as default container, so try to contruct to queue from a deque.
|
||||
// Queue uses deque as default container, so try to construct to queue from a deque.
|
||||
deque<int> container(40, 10);
|
||||
int_queue_type int_queue2(container);
|
||||
AZ_TEST_ASSERT(!int_queue2.empty());
|
||||
@@ -324,7 +324,7 @@ namespace UnitTest
|
||||
AZ_TEST_ASSERT(int_queue2.size() == 40);
|
||||
AZ_TEST_ASSERT(int_queue2.back() == 20);
|
||||
|
||||
int_queue.push();
|
||||
int_queue.emplace();
|
||||
AZ_TEST_ASSERT(!int_queue.empty());
|
||||
AZ_TEST_ASSERT(int_queue.size() == 1);
|
||||
|
||||
@@ -423,7 +423,7 @@ namespace UnitTest
|
||||
AZ_TEST_ASSERT(int_stack2.size() == 40);
|
||||
AZ_TEST_ASSERT(int_stack2.top() == 10);
|
||||
|
||||
int_stack.push();
|
||||
int_stack.emplace();
|
||||
AZ_TEST_ASSERT(!int_stack.empty());
|
||||
AZ_TEST_ASSERT(int_stack.size() == 1);
|
||||
// StackContainerTest-End
|
||||
@@ -669,4 +669,19 @@ namespace UnitTest
|
||||
++iteration;
|
||||
}
|
||||
}
|
||||
|
||||
using StackContainerTestFixture = ScopedAllocatorSetupFixture;
|
||||
|
||||
TEST_F(StackContainerTestFixture, StackEmplaceOperator_SupportsZeroOrMoreArguments)
|
||||
{
|
||||
using TestPairType = AZStd::pair<int, int>;
|
||||
AZStd::stack<TestPairType> testStack;
|
||||
testStack.emplace();
|
||||
testStack.emplace(1);
|
||||
testStack.emplace(2, 3);
|
||||
|
||||
using ContainerType = typename AZStd::stack<TestPairType>::container_type;
|
||||
AZStd::stack<TestPairType> expectedStack(ContainerType{ TestPairType{ 0, 0 }, TestPairType{ 1, 0 }, TestPairType{ 2, 3 } });
|
||||
EXPECT_EQ(expectedStack, testStack);
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -123,6 +123,12 @@ namespace AzPhysics
|
||||
->Field("Kinematic", &RigidBodyConfiguration::m_kinematic)
|
||||
->Field("CCD Enabled", &RigidBodyConfiguration::m_ccdEnabled)
|
||||
->Field("Compute Mass", &RigidBodyConfiguration::m_computeMass)
|
||||
->Field("Lock Linear X", &RigidBodyConfiguration::m_lockLinearX)
|
||||
->Field("Lock Linear Y", &RigidBodyConfiguration::m_lockLinearY)
|
||||
->Field("Lock Linear Z", &RigidBodyConfiguration::m_lockLinearZ)
|
||||
->Field("Lock Angular X", &RigidBodyConfiguration::m_lockAngularX)
|
||||
->Field("Lock Angular Y", &RigidBodyConfiguration::m_lockAngularY)
|
||||
->Field("Lock Angular Z", &RigidBodyConfiguration::m_lockAngularZ)
|
||||
->Field("Mass", &RigidBodyConfiguration::m_mass)
|
||||
->Field("Compute COM", &RigidBodyConfiguration::m_computeCenterOfMass)
|
||||
->Field("Centre of mass offset", &RigidBodyConfiguration::m_centerOfMassOffset)
|
||||
|
||||
@@ -62,6 +62,16 @@ namespace AzPhysics
|
||||
bool m_computeInertiaTensor = true;
|
||||
bool m_computeMass = true;
|
||||
|
||||
// Flags to restrict motion along specific world-space axes.
|
||||
bool m_lockLinearX = false;
|
||||
bool m_lockLinearY = false;
|
||||
bool m_lockLinearZ = false;
|
||||
|
||||
// Flags to restrict rotation around specific world-space axes.
|
||||
bool m_lockAngularX = false;
|
||||
bool m_lockAngularY = false;
|
||||
bool m_lockAngularZ = false;
|
||||
|
||||
//! If set, non-simulated shapes will also be included in the mass properties calculation.
|
||||
bool m_includeAllShapesInMassCalculation = false;
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace UnitTest
|
||||
|
||||
void TestDebugDisplayRequests::DrawWireBox(const AZ::Vector3& min, const AZ::Vector3& max)
|
||||
{
|
||||
const AZ::Transform& tm = m_transforms.back();
|
||||
const AZ::Transform& tm = m_transforms.top();
|
||||
m_points.push_back(tm.TransformPoint(AZ::Vector3(min.GetX(), min.GetY(), min.GetZ())));
|
||||
m_points.push_back(tm.TransformPoint(AZ::Vector3(min.GetX(), min.GetY(), max.GetZ())));
|
||||
m_points.push_back(tm.TransformPoint(AZ::Vector3(min.GetX(), max.GetY(), min.GetZ())));
|
||||
@@ -50,7 +50,7 @@ namespace UnitTest
|
||||
|
||||
void TestDebugDisplayRequests::DrawWireQuad(float width, float height)
|
||||
{
|
||||
const AZ::Transform& tm = m_transforms.back();
|
||||
const AZ::Transform& tm = m_transforms.top();
|
||||
m_points.push_back(tm.TransformPoint(AZ::Vector3(-0.5f * width, 0.0f, -0.5f * height)));
|
||||
m_points.push_back(tm.TransformPoint(AZ::Vector3(-0.5f * width, 0.0f, 0.5f * height)));
|
||||
m_points.push_back(tm.TransformPoint(AZ::Vector3(0.5f * width, 0.0f, -0.5f * height)));
|
||||
@@ -64,7 +64,7 @@ namespace UnitTest
|
||||
|
||||
void TestDebugDisplayRequests::DrawPoints(const AZStd::vector<AZ::Vector3>& points)
|
||||
{
|
||||
const AZ::Transform& tm = m_transforms.back();
|
||||
const AZ::Transform& tm = m_transforms.top();
|
||||
for (const auto& point : points)
|
||||
{
|
||||
m_points.push_back(tm.TransformPoint(point));
|
||||
@@ -100,7 +100,7 @@ namespace UnitTest
|
||||
|
||||
void TestDebugDisplayRequests::PushMatrix(const AZ::Transform& tm)
|
||||
{
|
||||
m_transforms.push(m_transforms.back() * tm);
|
||||
m_transforms.push(m_transforms.top() * tm);
|
||||
}
|
||||
|
||||
void TestDebugDisplayRequests::PopMatrix()
|
||||
|
||||
@@ -481,7 +481,7 @@ namespace AzFramework
|
||||
if (!m_freeOctreeNodes.empty())
|
||||
{
|
||||
// Take a free block of child nodes from our free list
|
||||
ExtractPageAndOffsetFromIndex(m_freeOctreeNodes.back(), nextChildPage, nextChildOffset);
|
||||
ExtractPageAndOffsetFromIndex(m_freeOctreeNodes.top(), nextChildPage, nextChildOffset);
|
||||
m_freeOctreeNodes.pop();
|
||||
}
|
||||
else
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace AzFramework
|
||||
class LinuxXcbConnectionManager
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(LinuxXcbConnectionManager, "{649951316-3626-4C9D-9DCA-2E7ABF84C0A9}");
|
||||
AZ_RTTI(LinuxXcbConnectionManager, "{1F756E14-8D74-42FD-843C-4863307710DB}");
|
||||
|
||||
virtual ~LinuxXcbConnectionManager() = default;
|
||||
|
||||
|
||||
+1
-1
@@ -177,7 +177,7 @@ namespace AzToolsFramework
|
||||
PrefabDomUtils::ApplyPatches(templateDomReference, templateDomReference.GetAllocator(), providedPatch);
|
||||
|
||||
//trigger propagation
|
||||
if (result.GetOutcome() != AZ::JsonSerializationResult::Outcomes::Success)
|
||||
if (result.GetProcessing() != AZ::JsonSerializationResult::Processing::Completed)
|
||||
{
|
||||
AZ_Error("Prefab", false, "Patch was not successfully applied.");
|
||||
return false;
|
||||
|
||||
@@ -90,10 +90,11 @@ namespace AzToolsFramework
|
||||
AZStd::unordered_map<Instance*, PrefabDom> nestedInstanceLinkPatchesMap;
|
||||
|
||||
// Retrieve all entities affected and identify Instances
|
||||
if (!RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonRootEntityOwningInstance->get(), entities, instances))
|
||||
PrefabOperationResult retrieveEntitiesAndInstancesOutcome = RetrieveAndSortPrefabEntitiesAndInstances(
|
||||
inputEntityList, commonRootEntityOwningInstance->get(), entities, instances);
|
||||
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(
|
||||
AZStd::string("Could not create a new prefab out of the entities provided - invalid selection."));
|
||||
return retrieveEntitiesAndInstancesOutcome;
|
||||
}
|
||||
|
||||
AZStd::unordered_map<AZ::EntityId, AZStd::string> oldEntityAliases;
|
||||
@@ -646,7 +647,12 @@ namespace AzToolsFramework
|
||||
{
|
||||
// Retrieve all nested instances that are part of the subtree under the current entity.
|
||||
EntityList entities;
|
||||
RetrieveAndSortPrefabEntitiesAndInstances({ entity }, beforeOwningInstance->get(), entities, instancesInvolved);
|
||||
PrefabOperationResult retrieveEntitiesAndInstancesOutcome = RetrieveAndSortPrefabEntitiesAndInstances(
|
||||
{ entity }, beforeOwningInstance->get(), entities, instancesInvolved);
|
||||
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
|
||||
{
|
||||
return retrieveEntitiesAndInstancesOutcome;
|
||||
}
|
||||
}
|
||||
|
||||
for (Instance* instance : instancesInvolved)
|
||||
@@ -748,7 +754,9 @@ namespace AzToolsFramework
|
||||
AZStd::vector<Instance*> instances;
|
||||
|
||||
// Retrieve all descendant entities and instances of this entity that belonged to the same owning instance.
|
||||
RetrieveAndSortPrefabEntitiesAndInstances({ entity }, beforeOwningInstance->get(), entities, instances);
|
||||
PrefabOperationResult retrieveEntitiesAndInstancesOutcome = RetrieveAndSortPrefabEntitiesAndInstances(
|
||||
{ entity }, beforeOwningInstance->get(), entities, instances);
|
||||
AZ_Error("Prefab", retrieveEntitiesAndInstancesOutcome.IsSuccess(), retrieveEntitiesAndInstancesOutcome.GetError().data());
|
||||
|
||||
AZStd::vector<AZStd::unique_ptr<Instance>> instanceUniquePtrs;
|
||||
AZStd::vector<AZStd::pair<Instance*, PrefabDom>> instancePatches;
|
||||
@@ -981,11 +989,12 @@ namespace AzToolsFramework
|
||||
AZStd::vector<Instance*> instances;
|
||||
|
||||
EntityList inputEntityList = EntityIdSetToEntityList(duplicationSet);
|
||||
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
|
||||
PrefabOperationResult retrieveEntitiesAndInstancesOutcome =
|
||||
RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
|
||||
|
||||
if (!success)
|
||||
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(AZStd::string("Failed to retrieve entities and instances from the given list of entity ids for duplication"));
|
||||
return AZStd::move(retrieveEntitiesAndInstancesOutcome);
|
||||
}
|
||||
|
||||
// Take a snapshot of the instance DOM before we manipulate it
|
||||
@@ -1128,11 +1137,12 @@ namespace AzToolsFramework
|
||||
AZStd::vector<AZ::Entity*> entities;
|
||||
AZStd::vector<Instance*> instances;
|
||||
|
||||
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
|
||||
PrefabOperationResult retrieveEntitiesAndInstancesOutcome =
|
||||
RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
|
||||
|
||||
if (!success)
|
||||
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
|
||||
{
|
||||
return AZ::Failure(AZStd::string("DeleteEntitiesAndAllDescendantsInInstance"));
|
||||
return AZStd::move(retrieveEntitiesAndInstancesOutcome);
|
||||
}
|
||||
|
||||
for (AZ::Entity* entity : entities)
|
||||
@@ -1405,13 +1415,16 @@ namespace AzToolsFramework
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool PrefabPublicHandler::RetrieveAndSortPrefabEntitiesAndInstances(
|
||||
const EntityList& inputEntities, Instance& commonRootEntityOwningInstance,
|
||||
EntityList& outEntities, AZStd::vector<Instance*>& outInstances) const
|
||||
PrefabOperationResult PrefabPublicHandler::RetrieveAndSortPrefabEntitiesAndInstances(
|
||||
const EntityList& inputEntities,
|
||||
Instance& commonRootEntityOwningInstance,
|
||||
EntityList& outEntities,
|
||||
AZStd::vector<Instance*>& outInstances) const
|
||||
{
|
||||
if (inputEntities.size() == 0)
|
||||
{
|
||||
return false;
|
||||
return AZ::Failure(
|
||||
AZStd::string("An empty list of input entities is provided to retrieve the prefab entities and instances."));
|
||||
}
|
||||
|
||||
AZStd::queue<AZ::Entity*> entityQueue;
|
||||
@@ -1438,8 +1451,8 @@ namespace AzToolsFramework
|
||||
AZ_Assert(
|
||||
owningInstance.has_value(),
|
||||
"An error occurred while retrieving entities and prefab instances : "
|
||||
"Owning instance of entity with id '%llu' couldn't be found",
|
||||
entity->GetId());
|
||||
"Owning instance of entity with name '%s' and id '%llu' couldn't be found",
|
||||
entity->GetName().c_str(), static_cast<AZ::u64>(entity->GetId()));
|
||||
|
||||
// Check if this entity is owned by the same instance owning the root.
|
||||
if (&owningInstance->get() == &commonRootEntityOwningInstance)
|
||||
@@ -1480,7 +1493,10 @@ namespace AzToolsFramework
|
||||
else
|
||||
{
|
||||
// This can only happen if one entity does not share the common root!
|
||||
return false;
|
||||
return AZ::Failure(AZStd::string::format(
|
||||
"Entity with name '%s' and id '%llu' has an owning instance that doesn't belong to the instance "
|
||||
"hierarchy of the selected entities.",
|
||||
entity->GetName().c_str(), static_cast<AZ::u64>(entity->GetId())));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1501,7 +1517,12 @@ namespace AzToolsFramework
|
||||
outInstances.push_back(instancePtr);
|
||||
}
|
||||
|
||||
return (outEntities.size() + outInstances.size()) > 0;
|
||||
if ((outEntities.size() + outInstances.size()) == 0)
|
||||
{
|
||||
return AZ::Failure(
|
||||
AZStd::string("An empty list of entities and prefab instances were retrieved from the selected entities"));
|
||||
}
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
EntityIdList PrefabPublicHandler::GenerateEntityIdListWithoutLevelInstance(
|
||||
|
||||
@@ -64,8 +64,11 @@ namespace AzToolsFramework
|
||||
|
||||
private:
|
||||
PrefabOperationResult DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants);
|
||||
bool RetrieveAndSortPrefabEntitiesAndInstances(const EntityList& inputEntities, Instance& commonRootEntityOwningInstance,
|
||||
EntityList& outEntities, AZStd::vector<Instance*>& outInstances) const;
|
||||
PrefabOperationResult RetrieveAndSortPrefabEntitiesAndInstances(
|
||||
const EntityList& inputEntities,
|
||||
Instance& commonRootEntityOwningInstance,
|
||||
EntityList& outEntities,
|
||||
AZStd::vector<Instance*>& outInstances) const;
|
||||
EntityIdList GenerateEntityIdListWithoutLevelInstance(const EntityIdList& entityIds) const;
|
||||
|
||||
InstanceOptionalReference GetOwnerInstanceByEntityId(AZ::EntityId entityId) const;
|
||||
|
||||
+4
-3
@@ -546,7 +546,7 @@ namespace AzToolsFramework
|
||||
|
||||
for (auto& element : nodeEditData->m_elements)
|
||||
{
|
||||
if (element.IsClassElement() && element.m_elementId == AZ::Edit::ClassElements::Group)
|
||||
if (element.m_elementId == AZ::Edit::ClassElements::Group)
|
||||
{
|
||||
groupData = (element.m_description && element.m_description[0]) ? &element : nullptr;
|
||||
continue;
|
||||
@@ -1112,13 +1112,14 @@ namespace AzToolsFramework
|
||||
const AZ::Edit::ElementData* groupData = nullptr;
|
||||
for (const AZ::Edit::ElementData& elementData : parentEditData->m_elements)
|
||||
{
|
||||
if (node->m_elementEditData == &elementData) // this element matches this node
|
||||
// this element matches this node
|
||||
if ((node->m_elementEditData == &elementData) && (elementData.m_elementId != AZ::Edit::ClassElements::Group))
|
||||
{
|
||||
// Record the last found group data
|
||||
node->m_groupElementData = groupData;
|
||||
break;
|
||||
}
|
||||
else if (elementData.IsClassElement() && elementData.m_elementId == AZ::Edit::ClassElements::Group)
|
||||
else if (elementData.m_elementId == AZ::Edit::ClassElements::Group)
|
||||
{
|
||||
if (!elementData.m_description || !elementData.m_description[0])
|
||||
{ // close the group
|
||||
|
||||
+38
@@ -12,6 +12,7 @@
|
||||
|
||||
#include <AzFramework/StringFunc/StringFunc.h>
|
||||
#include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
|
||||
#include <AzToolsFramework/UI/PropertyEditor/PropertyCheckBoxCtrl.hxx>
|
||||
|
||||
AZ_PUSH_DISABLE_WARNING(4244 4251 4800, "-Wunknown-warning-option") // 4244: conversion from 'int' to 'float', possible loss of data
|
||||
// 4251: class '...' needs to have dll-interface to be used by clients of class 'QInputEvent'
|
||||
@@ -141,6 +142,11 @@ namespace AzToolsFramework
|
||||
m_treeDepth = 0;
|
||||
|
||||
delete m_dropDownArrow;
|
||||
if (m_toggleSwitch != nullptr)
|
||||
{
|
||||
m_handler->DestroyGUI(m_toggleSwitch);
|
||||
m_toggleSwitch = nullptr;
|
||||
}
|
||||
|
||||
if (m_childWidget)
|
||||
{
|
||||
@@ -387,6 +393,13 @@ namespace AzToolsFramework
|
||||
setUpdatesEnabled(true);
|
||||
}
|
||||
|
||||
void PropertyRowWidget::InitializeToggleGroup(const char* groupName, PropertyRowWidget* pParent, int depth, InstanceDataNode* node, int labelWidth)
|
||||
{
|
||||
Initialize(groupName, pParent, depth, labelWidth);
|
||||
ChangeSourceNode(node);
|
||||
CreateGroupToggleSwitch();
|
||||
}
|
||||
|
||||
void PropertyRowWidget::Initialize(const char* groupName, PropertyRowWidget* pParent, int depth, int labelWidth)
|
||||
{
|
||||
Initialize(pParent, nullptr, depth, labelWidth);
|
||||
@@ -1102,6 +1115,19 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyRowWidget::CreateGroupToggleSwitch()
|
||||
{
|
||||
if (m_toggleSwitch == nullptr)
|
||||
{
|
||||
m_handlerName = AZ::Edit::UIHandlers::CheckBox;
|
||||
PropertyTypeRegistrationMessages::Bus::BroadcastResult(m_handler, &PropertyTypeRegistrationMessages::Bus::Events::ResolvePropertyHandler, m_handlerName, azrtti_typeid<bool>());
|
||||
m_toggleSwitch = m_handler->CreateGUI(this);
|
||||
m_middleLayout->insertWidget(0, m_toggleSwitch, 1);
|
||||
auto checkBoxCtrl = static_cast<AzToolsFramework::PropertyCheckBoxCtrl*>(m_toggleSwitch);
|
||||
QObject::connect(checkBoxCtrl, &AzToolsFramework::PropertyCheckBoxCtrl::valueChanged, this, &PropertyRowWidget::OnClickedToggleButton);
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyRowWidget::SetIndentSize(int w)
|
||||
{
|
||||
m_indent->changeSize(w, 1, QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
@@ -1110,6 +1136,18 @@ namespace AzToolsFramework
|
||||
m_leftHandSideLayout->activate();
|
||||
}
|
||||
|
||||
void PropertyRowWidget::OnClickedToggleButton(bool checked)
|
||||
{
|
||||
if (m_expanded != checked)
|
||||
{
|
||||
DoExpandOrContract(!IsExpanded(), 0 != (QGuiApplication::keyboardModifiers() & Qt::ControlModifier));
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyRowWidget::ChangeSourceNode(InstanceDataNode* node)
|
||||
{
|
||||
m_sourceNode = node;
|
||||
}
|
||||
|
||||
void PropertyRowWidget::SetExpanded(bool expanded)
|
||||
{
|
||||
|
||||
+9
@@ -50,6 +50,7 @@ namespace AzToolsFramework
|
||||
|
||||
virtual void Initialize(PropertyRowWidget* pParent, InstanceDataNode* dataNode, int depth, int labelWidth = 200);
|
||||
virtual void Initialize(const char* groupName, PropertyRowWidget* pParent, int depth, int labelWidth = 200);
|
||||
virtual void InitializeToggleGroup(const char* groupName, PropertyRowWidget* pParent, int depth, InstanceDataNode* node, int labelWidth = 200);
|
||||
virtual void Clear(); // for pooling
|
||||
|
||||
// --- NOT A UNIQUE IDENTIFIER ---
|
||||
@@ -143,11 +144,14 @@ namespace AzToolsFramework
|
||||
QVBoxLayout* GetLeftHandSideLayoutParent() { return m_leftHandSideLayoutParent; }
|
||||
QToolButton* GetIndicatorButton() { return m_indicatorButton; }
|
||||
QLabel* GetNameLabel() { return m_nameLabel; }
|
||||
QWidget* GetToggle() { return m_toggleSwitch; }
|
||||
const QWidget* GetToggle() const { return m_toggleSwitch; }
|
||||
void SetIndentSize(int w);
|
||||
void SetAsCustom(bool custom) { m_custom = custom; }
|
||||
|
||||
bool CanChildrenBeReordered() const;
|
||||
bool CanBeReordered() const;
|
||||
|
||||
protected:
|
||||
int CalculateLabelWidth() const;
|
||||
|
||||
@@ -177,6 +181,8 @@ namespace AzToolsFramework
|
||||
QLabel* m_defaultLabel; // if there is no handler, we use a m_defaultLabel label
|
||||
InstanceDataNode* m_sourceNode;
|
||||
|
||||
QWidget* m_toggleSwitch = nullptr;
|
||||
|
||||
QString m_currentFilterString;
|
||||
|
||||
struct ChangeNotification
|
||||
@@ -241,6 +247,8 @@ namespace AzToolsFramework
|
||||
void mouseDoubleClickEvent(QMouseEvent* event) override;
|
||||
|
||||
void UpdateDropDownArrow();
|
||||
void CreateGroupToggleSwitch();
|
||||
void ChangeSourceNode(InstanceDataNode* node);
|
||||
void UpdateDefaultLabel(InstanceDataNode* node);
|
||||
|
||||
void createContainerButtons();
|
||||
@@ -259,6 +267,7 @@ namespace AzToolsFramework
|
||||
private slots:
|
||||
void OnClickedExpansionButton();
|
||||
|
||||
void OnClickedToggleButton(bool checked);
|
||||
void OnClickedAddElementButton();
|
||||
void OnClickedRemoveElementButton();
|
||||
void OnClickedClearContainerButton();
|
||||
|
||||
+65
-30
@@ -169,6 +169,8 @@ namespace AzToolsFramework
|
||||
InstanceDataHierarchyList m_instances; ///< List of instance sets to display, other one can aggregate other instances.
|
||||
InstanceDataHierarchy::ValueComparisonFunction m_valueComparisonFunction;
|
||||
ReflectedPropertyEditor::WidgetList m_widgets;
|
||||
ReflectedPropertyEditor::WidgetList m_specialGroupWidgets;
|
||||
InstanceDataNode* groupSourceNode = nullptr;
|
||||
RowContainerType m_widgetsInDisplayOrder;
|
||||
UserWidgetToDataMap m_userWidgetsToData;
|
||||
VisibilityCallback m_visibilityCallback;
|
||||
@@ -501,6 +503,7 @@ namespace AzToolsFramework
|
||||
// if the node is in a group then create the widget for the group
|
||||
if (groupElementData)
|
||||
{
|
||||
bool isToggleGroup = false;
|
||||
const char* groupName = groupElementData->m_description;
|
||||
PropertyRowWidget*& widgetEntry = m_groupWidgets[{parent, groupName}];
|
||||
|
||||
@@ -509,14 +512,34 @@ namespace AzToolsFramework
|
||||
{
|
||||
widgetEntry = CreateOrPullFromPool();
|
||||
widgetEntry->SetFilterString(m_editor->GetFilterString());
|
||||
widgetEntry->Initialize(groupName, parent, depth, m_propertyLabelWidth);
|
||||
|
||||
// Initialized normally if the group does not have a member variable attached to it,
|
||||
// otherwise initialize it as a group that will have a toggle switch.
|
||||
if (groupElementData->IsClassElement())
|
||||
{
|
||||
widgetEntry->Initialize(groupName, parent, depth, m_propertyLabelWidth);
|
||||
}
|
||||
else
|
||||
{
|
||||
widgetEntry->InitializeToggleGroup(groupName, parent, depth, groupSourceNode, m_propertyLabelWidth);
|
||||
QWidget* toggleSwitch = widgetEntry->GetToggle();
|
||||
PropertyHandlerBase* pHandler = widgetEntry->GetHandler();
|
||||
m_userWidgetsToData[toggleSwitch] = groupSourceNode;
|
||||
m_specialGroupWidgets[groupSourceNode] = widgetEntry;
|
||||
pHandler->ConsumeAttributes_Internal(toggleSwitch, groupSourceNode);
|
||||
pHandler->ReadValuesIntoGUI_Internal(toggleSwitch, groupSourceNode);
|
||||
widgetEntry->OnValuesUpdated();
|
||||
isToggleGroup = true;
|
||||
}
|
||||
|
||||
widgetEntry->SetLeafIndentation(m_leafIndentation);
|
||||
widgetEntry->SetTreeIndentation(m_treeIndentation);
|
||||
widgetEntry->setObjectName(groupName);
|
||||
|
||||
for (const AZ::Edit::AttributePair& attribute : groupElementData->m_attributes)
|
||||
{
|
||||
PropertyAttributeReader reader(node->GetParent()->FirstInstance(), attribute.second);
|
||||
InstanceDataNode* readerNode = (isToggleGroup) ? groupSourceNode : node;
|
||||
PropertyAttributeReader reader(readerNode->GetParent()->FirstInstance(), attribute.second);
|
||||
QString descriptionOut;
|
||||
bool foundDescription = false;
|
||||
widgetEntry->ConsumeAttribute(attribute.first, reader, true, &descriptionOut, &foundDescription);
|
||||
@@ -608,7 +631,7 @@ namespace AzToolsFramework
|
||||
// creates and populates the GUI to edit the property if not already created
|
||||
void ReflectedPropertyEditor::Impl::CreateEditorWidget(PropertyRowWidget* pWidget)
|
||||
{
|
||||
if (!pWidget->HasChildWidgetAlready())
|
||||
if (!pWidget->HasChildWidgetAlready() && !pWidget->GetToggle())
|
||||
{
|
||||
PropertyHandlerBase* pHandler = pWidget->GetHandler();
|
||||
if (pHandler)
|
||||
@@ -735,36 +758,44 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pWidget = CreateOrPullFromPool();
|
||||
pWidget->show();
|
||||
|
||||
pWidget->SetFilterString(m_editor->GetFilterString());
|
||||
pWidget->Initialize(pParent, node, depth, m_propertyLabelWidth);
|
||||
|
||||
if (labelOverride != "")
|
||||
if (!node->GetElementEditMetadata() || (node->GetElementEditMetadata()->m_elementId != AZ::Edit::ClassElements::Group))
|
||||
{
|
||||
pWidget->SetNameLabel(labelOverride.data());
|
||||
pWidget = CreateOrPullFromPool();
|
||||
pWidget->show();
|
||||
|
||||
pWidget->SetFilterString(m_editor->GetFilterString());
|
||||
pWidget->Initialize(pParent, node, depth, m_propertyLabelWidth);
|
||||
|
||||
if (labelOverride != "")
|
||||
{
|
||||
pWidget->SetNameLabel(labelOverride.data());
|
||||
}
|
||||
|
||||
pWidget->setObjectName(pWidget->label());
|
||||
pWidget->SetSelectionEnabled(m_selectionEnabled);
|
||||
pWidget->SetLeafIndentation(m_leafIndentation);
|
||||
pWidget->SetTreeIndentation(m_treeIndentation);
|
||||
|
||||
m_widgets[node] = pWidget;
|
||||
m_widgetsInDisplayOrder.insert(widgetDisplayOrder, pWidget);
|
||||
|
||||
if (pParent)
|
||||
{
|
||||
pParent->AddedChild(pWidget);
|
||||
}
|
||||
|
||||
if (pParent || !m_hideRootProperties)
|
||||
{
|
||||
depth += 1;
|
||||
}
|
||||
pParent = pWidget;
|
||||
}
|
||||
|
||||
pWidget->setObjectName(pWidget->label());
|
||||
pWidget->SetSelectionEnabled(m_selectionEnabled);
|
||||
pWidget->SetLeafIndentation(m_leafIndentation);
|
||||
pWidget->SetTreeIndentation(m_treeIndentation);
|
||||
|
||||
m_widgets[node] = pWidget;
|
||||
m_widgetsInDisplayOrder.insert(widgetDisplayOrder, pWidget);
|
||||
|
||||
if (pParent)
|
||||
// Save the last InstanceDataNode that is a Group ClassElement so that we can use it as the source node for its widget.
|
||||
if (node->GetElementEditMetadata() && (node->GetElementEditMetadata()->m_elementId == AZ::Edit::ClassElements::Group))
|
||||
{
|
||||
pParent->AddedChild(pWidget);
|
||||
groupSourceNode = node;
|
||||
}
|
||||
|
||||
if (pParent || !m_hideRootProperties)
|
||||
{
|
||||
depth += 1;
|
||||
}
|
||||
pParent = pWidget;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1356,9 +1387,13 @@ namespace AzToolsFramework
|
||||
return;
|
||||
}
|
||||
|
||||
// get the property editor
|
||||
// Get the property editor from either the widget map or the special toggle group widgets
|
||||
auto rowWidget = m_widgets.find(it->second);
|
||||
if (rowWidget != m_widgets.end())
|
||||
if (rowWidget == m_widgets.end())
|
||||
{
|
||||
rowWidget = m_specialGroupWidgets.find(it->second);
|
||||
}
|
||||
if (rowWidget != m_widgets.end() || rowWidget != m_specialGroupWidgets.end())
|
||||
{
|
||||
InstanceDataNode* node = rowWidget->first;
|
||||
PropertyRowWidget* widget = rowWidget->second;
|
||||
|
||||
+3
@@ -51,6 +51,8 @@ namespace AzToolsFramework
|
||||
|
||||
typedef AZStd::unordered_map<InstanceDataNode*, PropertyRowWidget*> WidgetList;
|
||||
|
||||
ReflectedPropertyEditor::WidgetList m_specialGroupWidgets;
|
||||
|
||||
ReflectedPropertyEditor(QWidget* pParent);
|
||||
virtual ~ReflectedPropertyEditor();
|
||||
|
||||
@@ -62,6 +64,7 @@ namespace AzToolsFramework
|
||||
bool AddInstance(void* instance, const AZ::Uuid& classId, void* aggregateInstance = nullptr, void* compareInstance = nullptr);
|
||||
void SetCompareInstance(void* instance, const AZ::Uuid& classId);
|
||||
void ClearInstances();
|
||||
void ReadValuesIntoGui(QWidget* widget, InstanceDataNode* node);
|
||||
template<class T>
|
||||
bool AddInstance(T* instance, void* aggregateInstance = nullptr, void* compareInstance = nullptr)
|
||||
{
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <AzCore/Serialization/Utils.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <random>
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
using namespace AZ;
|
||||
@@ -727,6 +728,153 @@ namespace UnitTest
|
||||
|
||||
};
|
||||
|
||||
class GroupTestComponent : public AZ::Component
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(GroupTestComponent, "{C088C81D-D59D-43F1-85F8-B2E591BABA36}")
|
||||
|
||||
GroupTestComponent() = default;
|
||||
|
||||
struct SubData
|
||||
{
|
||||
AZ_TYPE_INFO(SubData, "{983316B5-17C0-476E-9CEB-CA749B3ABE5D}");
|
||||
AZ_CLASS_ALLOCATOR(SubData, AZ::SystemAllocator, 0);
|
||||
|
||||
SubData() {}
|
||||
explicit SubData(int v) : m_int(v) {}
|
||||
explicit SubData(bool b) : m_bool(b) {}
|
||||
explicit SubData(float f) : m_float(f) {}
|
||||
~SubData() = default;
|
||||
|
||||
float m_float = 0.f;
|
||||
int m_int = 0;
|
||||
bool m_bool = true;
|
||||
};
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<SubData>()
|
||||
->Version(1)
|
||||
->Field("SubInt", &SubData::m_int)
|
||||
->Field("SubToggle", &SubData::m_bool)
|
||||
->Field("SubFloat", &SubData::m_float)
|
||||
;
|
||||
|
||||
serializeContext->Class<GroupTestComponent, AZ::Component>()
|
||||
->Version(1)
|
||||
->Field("Float", &GroupTestComponent::m_float)
|
||||
->Field("GroupToggle", &GroupTestComponent::m_groupToggle)
|
||||
->Field("GroupFloat", &GroupTestComponent::m_groupFloat)
|
||||
->Field("ToggleGroupInt", &GroupTestComponent::m_toggleGroupInt)
|
||||
->Field("SubDataNormal", &GroupTestComponent::m_subGroupForNormal)
|
||||
->Field("SubDataToggle", &GroupTestComponent::m_subGroupForToggle)
|
||||
;
|
||||
|
||||
if (AZ::EditContext* edit = serializeContext->GetEditContext())
|
||||
{
|
||||
edit->Class<GroupTestComponent>("Group Test Component", "Testing normal groups and toggle groups")
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->DataElement(0, &GroupTestComponent::m_float, "Float Field", "A float field")
|
||||
->ClassElement(AZ::Edit::ClassElements::Group, "Normal Group")
|
||||
->DataElement(0, &GroupTestComponent::m_groupFloat, "Float Field", "A float field")
|
||||
->DataElement(0, &GroupTestComponent::m_subGroupForNormal, "Struct Field", "A sub data type")
|
||||
->GroupElementToggle("Group Toggle", &GroupTestComponent::m_groupToggle)
|
||||
->DataElement(0, &GroupTestComponent::m_toggleGroupInt, "Normal Integer", "An Integer")
|
||||
->DataElement(0, &GroupTestComponent::m_subGroupForToggle, "Struct Field", "A sub data type")
|
||||
;
|
||||
|
||||
edit->Class<SubData>("SubGroup Test Component", "Testing nested normal groups and toggle groups")
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->ClassElement(AZ::Edit::ClassElements::Group, "Normal SubGroup")
|
||||
->DataElement(0, &SubData::m_int, "SubGroup Int Field", "An int")
|
||||
->GroupElementToggle("SubGroup Toggle", &SubData::m_bool)
|
||||
->DataElement(0, &SubData::m_float, "SubGroup Float Field", "An int")
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Activate() override
|
||||
{
|
||||
}
|
||||
|
||||
void Deactivate() override
|
||||
{
|
||||
}
|
||||
|
||||
float m_float = 0.f;
|
||||
float m_groupFloat = 0.f;
|
||||
int m_toggleGroupInt = 0;
|
||||
AZStd::string m_string;
|
||||
bool m_groupToggle = false;
|
||||
|
||||
SubData m_subGroupForNormal;
|
||||
SubData m_subGroupForToggle;
|
||||
};
|
||||
|
||||
class InstanceDataHierarchyGroupTestFixture : public AllocatorsFixture
|
||||
{
|
||||
public:
|
||||
InstanceDataHierarchyGroupTestFixture() = default;
|
||||
|
||||
AZStd::unique_ptr<SerializeContext> m_serializeContext;
|
||||
AZStd::unique_ptr<AZ::Entity> testEntity1;
|
||||
AzToolsFramework::InstanceDataHierarchy* instanceDataHierarchy;
|
||||
AzToolsFramework::InstanceDataNode* componentNode1 = nullptr;
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
AllocatorsFixture::SetUp();
|
||||
|
||||
using AzToolsFramework::InstanceDataHierarchy;
|
||||
using AzToolsFramework::InstanceDataNode;
|
||||
|
||||
AZ::AllocatorInstance<AZ::PoolAllocator>::Create();
|
||||
|
||||
m_serializeContext.reset(aznew AZ::SerializeContext());
|
||||
m_serializeContext.get()->CreateEditContext();
|
||||
Entity::Reflect(m_serializeContext.get());
|
||||
GroupTestComponent::Reflect(m_serializeContext.get());
|
||||
|
||||
testEntity1.reset(new AZ::Entity());
|
||||
testEntity1->CreateComponent<GroupTestComponent>();
|
||||
|
||||
instanceDataHierarchy = aznew InstanceDataHierarchy();
|
||||
instanceDataHierarchy->AddRootInstance(testEntity1.get());
|
||||
instanceDataHierarchy->Build(m_serializeContext.get(), 0);
|
||||
|
||||
// Adding the nodes to a node stack
|
||||
auto rootNode = instanceDataHierarchy->GetRootNode();
|
||||
AZStd::stack<InstanceDataNode*> nodeStack;
|
||||
nodeStack.push(rootNode);
|
||||
while (!nodeStack.empty())
|
||||
{
|
||||
InstanceDataNode* node = nodeStack.top();
|
||||
nodeStack.pop();
|
||||
if (node->GetClassMetadata()->m_typeId == AZ::AzTypeInfo<GroupTestComponent>::Uuid())
|
||||
{
|
||||
componentNode1 = node;
|
||||
break;
|
||||
}
|
||||
for (InstanceDataNode& child : node->GetChildren())
|
||||
{
|
||||
nodeStack.push(&child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
m_serializeContext.reset();
|
||||
testEntity1.reset();
|
||||
delete instanceDataHierarchy;
|
||||
AZ::AllocatorInstance<AZ::PoolAllocator>::Destroy();
|
||||
AllocatorsFixture::TearDown();
|
||||
}
|
||||
};
|
||||
|
||||
class InstanceDataHierarchyKeyedContainerTest
|
||||
: public AllocatorsFixture
|
||||
{
|
||||
@@ -1315,4 +1463,108 @@ namespace UnitTest
|
||||
run();
|
||||
}
|
||||
|
||||
// Test to validate that the only ClassElement::Group nodes are ToggleGroups
|
||||
TEST_F(InstanceDataHierarchyGroupTestFixture, GroupToggleIsClassElementGroup)
|
||||
{
|
||||
using AzToolsFramework::InstanceDataHierarchy;
|
||||
using AzToolsFramework::InstanceDataNode;
|
||||
|
||||
for (auto child : componentNode1->GetChildren())
|
||||
{
|
||||
AZStd::string childName(child.GetElementMetadata()->m_name);
|
||||
if (childName.compare("GroupToggle") == 0)
|
||||
{
|
||||
EXPECT_EQ(child.GetElementEditMetadata()->m_elementId, AZ::Edit::ClassElements::Group);
|
||||
}
|
||||
if ((childName.compare("SubDataNormal") == 0) || (childName.compare("SubDataToggle") == 0))
|
||||
{
|
||||
for (auto subChild : child.GetChildren())
|
||||
{
|
||||
childName = subChild.GetElementMetadata()->m_name;
|
||||
if (childName.compare("SubToggle") == 0)
|
||||
{
|
||||
EXPECT_EQ(subChild.GetElementEditMetadata()->m_elementId, AZ::Edit::ClassElements::Group);
|
||||
}
|
||||
else
|
||||
{
|
||||
EXPECT_NE(subChild.GetElementEditMetadata()->m_elementId, AZ::Edit::ClassElements::Group);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test to ensure that each node has been assigned under the proper group and the group hierarchy is structured correctly
|
||||
TEST_F(InstanceDataHierarchyGroupTestFixture, ValidatingGroupAndSubGroupHierarchy)
|
||||
{
|
||||
using AzToolsFramework::InstanceDataHierarchy;
|
||||
using AzToolsFramework::InstanceDataNode;
|
||||
|
||||
for (auto child : componentNode1->GetChildren())
|
||||
{
|
||||
AZStd::string childName(child.GetElementMetadata()->m_name);
|
||||
if (childName.compare("GroupFloat") == 0)
|
||||
{
|
||||
EXPECT_EQ(child.GetGroupElementMetadata()->m_description, "Normal Group");
|
||||
}
|
||||
if (childName.compare("ToggleGroupInt") == 0)
|
||||
{
|
||||
EXPECT_EQ(child.GetGroupElementMetadata()->m_description, "Group Toggle");
|
||||
}
|
||||
if ((childName.compare("SubDataNormal") == 0) || (childName.compare("SubDataToggle") == 0))
|
||||
{
|
||||
for (auto subChild : child.GetChildren())
|
||||
{
|
||||
childName = subChild.GetElementMetadata()->m_name;
|
||||
if (childName.compare("SubInt") == 0)
|
||||
{
|
||||
EXPECT_EQ(subChild.GetGroupElementMetadata()->m_description, "Normal SubGroup");
|
||||
}
|
||||
if (childName.compare("SubFloat") == 0)
|
||||
{
|
||||
EXPECT_EQ(subChild.GetGroupElementMetadata()->m_description, "SubGroup Toggle");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class InstanceDataHierarchyGroupTestFixtureParameterized
|
||||
: public InstanceDataHierarchyGroupTestFixture
|
||||
, public ::testing::WithParamInterface<const char*>
|
||||
{
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
InstanceDataHierarchyGroupTestFixture,
|
||||
InstanceDataHierarchyGroupTestFixtureParameterized,
|
||||
::testing::Values("GroupFloat", "GroupToggle", "ToggleGroupInt", "SubInt", "SubToggle", "SubFloat"));
|
||||
|
||||
// Test to validate that each node in a group and Subgroup has the correct parent
|
||||
TEST_P(InstanceDataHierarchyGroupTestFixtureParameterized, ValidatingGroupAndSubGroupParents)
|
||||
{
|
||||
using AzToolsFramework::InstanceDataHierarchy;
|
||||
using AzToolsFramework::InstanceDataNode;
|
||||
|
||||
const char* paramName = GetParam();
|
||||
for (auto child : componentNode1->GetChildren())
|
||||
{
|
||||
AZStd::string childName(child.GetElementMetadata()->m_name);
|
||||
if (childName.compare(paramName) == 0)
|
||||
{
|
||||
EXPECT_EQ(child.GetParent()->GetClassMetadata()->m_name, "GroupTestComponent");
|
||||
}
|
||||
if ((childName.compare("SubDataNormal") == 0) || (childName.compare("SubDataToggle") == 0))
|
||||
{
|
||||
for (auto subChild : child.GetChildren())
|
||||
{
|
||||
childName = subChild.GetElementMetadata()->m_name;
|
||||
if (childName.compare(paramName) == 0)
|
||||
{
|
||||
EXPECT_EQ(subChild.GetParent()->GetClassMetadata()->m_name, "SubData");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace UnitTest
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#
|
||||
#
|
||||
|
||||
add_subdirectory(AzAutoGen)
|
||||
add_subdirectory(AtomCore)
|
||||
add_subdirectory(AzCore)
|
||||
add_subdirectory(AzQtComponents)
|
||||
|
||||
@@ -1688,12 +1688,12 @@ namespace GridMate
|
||||
return; //No connections to update
|
||||
}
|
||||
bool updateRate = false;
|
||||
AZ::u32 minRateBytesPerSecond = m_connByCongestionState.top().m_rate;
|
||||
AZ::u32 minRateBytesPerSecond = m_connByCongestionState.front().m_rate;
|
||||
//const AZ::u32 old = minRateBytesPerSecond; //For debugging
|
||||
|
||||
auto connIt = AZStd::find(m_connByCongestionState.get_container().begin(), m_connByCongestionState.get_container().end(), id);
|
||||
auto connIt = AZStd::find(m_connByCongestionState.begin(), m_connByCongestionState.end(), id);
|
||||
|
||||
if ( connIt == m_connByCongestionState.get_container().end())
|
||||
if ( connIt == m_connByCongestionState.end())
|
||||
{
|
||||
return; //Already disconnected
|
||||
}
|
||||
@@ -1708,11 +1708,11 @@ namespace GridMate
|
||||
|
||||
//If new min or old min increased, rebuild the heap and send an update
|
||||
if (bytesPerSecond < minRateBytesPerSecond
|
||||
|| (id == m_connByCongestionState.top().m_connection && bytesPerSecond > minRateBytesPerSecond))
|
||||
|| (id == m_connByCongestionState.front().m_connection && bytesPerSecond > minRateBytesPerSecond))
|
||||
{
|
||||
updateRate = true;
|
||||
minRateBytesPerSecond = bytesPerSecond;
|
||||
AZStd::make_heap(m_connByCongestionState.get_container().begin(), m_connByCongestionState.get_container().end());
|
||||
AZStd::make_heap(m_connByCongestionState.begin(), m_connByCongestionState.end());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -459,7 +459,7 @@ namespace GridMate
|
||||
}
|
||||
};
|
||||
static bool k_enableBackPressure;
|
||||
AZStd::priority_queue<RateConnectionPair> m_connByCongestionState; ///< Connections priority queue sorted by congestion window
|
||||
AZStd::vector<RateConnectionPair> m_connByCongestionState; ///< Connections priority queue sorted by congestion window
|
||||
/***
|
||||
* Updates connection's rate in priority and updates send limit
|
||||
*
|
||||
@@ -479,7 +479,9 @@ namespace GridMate
|
||||
}
|
||||
AZ_Assert(carrier, "NULL carrier!");
|
||||
|
||||
m_connByCongestionState.emplace(RateConnectionPair(AZ::u32(1500), id)); //default to 1500Bps (ex 1 Ethernet frame/second minimum)
|
||||
m_connByCongestionState.emplace_back(AZ::u32(1500), id); //default to 1500Bps (ex 1 Ethernet frame/second minimum)
|
||||
// Restore the heap property after pushing back another element
|
||||
AZStd::push_heap(m_connByCongestionState.begin(), m_connByCongestionState.end());
|
||||
}
|
||||
void OnDisconnect(Carrier* carrier, ConnectionID id, CarrierDisconnectReason reason) override
|
||||
{
|
||||
@@ -490,17 +492,17 @@ namespace GridMate
|
||||
}
|
||||
AZ_Assert(carrier, "NULL carrier!");
|
||||
|
||||
auto connIt = AZStd::find(m_connByCongestionState.get_container().begin(), m_connByCongestionState.get_container().end(), id);
|
||||
if (connIt != m_connByCongestionState.get_container().end())
|
||||
auto connIt = AZStd::find(m_connByCongestionState.begin(), m_connByCongestionState.end(), id);
|
||||
if (connIt != m_connByCongestionState.end())
|
||||
{
|
||||
//Since we are using a weakly sorted heap, we need to re-generate when the top is removed
|
||||
bool remake = (connIt == m_connByCongestionState.get_container().begin());
|
||||
bool remake = (connIt == m_connByCongestionState.begin());
|
||||
|
||||
m_connByCongestionState.get_container().erase(connIt);
|
||||
m_connByCongestionState.erase(connIt);
|
||||
|
||||
if (remake)
|
||||
{
|
||||
AZStd::make_heap(m_connByCongestionState.get_container().begin(), m_connByCongestionState.get_container().end());
|
||||
AZStd::make_heap(m_connByCongestionState.begin(), m_connByCongestionState.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user