Merge branch 'development' into LYN-3705-2
This commit is contained in:
@@ -163,6 +163,8 @@ ly_add_target(
|
||||
editor_files.cmake
|
||||
PLATFORM_INCLUDE_FILES
|
||||
Platform/${PAL_PLATFORM_NAME}/editor_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
|
||||
TARGET_PROPERTIES
|
||||
LY_INSTALL_GENERATE_RUN_TARGET TRUE
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
3rdParty::Qt::Core
|
||||
|
||||
@@ -415,33 +415,37 @@ namespace Editor
|
||||
}
|
||||
|
||||
// Ensure that the Windows WM_INPUT messages get passed through to the AzFramework input system.
|
||||
// These events are now consumed both in and out of game mode.
|
||||
if (msg->message == WM_INPUT)
|
||||
// These events are only broadcast in game mode. In Editor mode, RenderViewportWidget creates synthetic
|
||||
// keyboard and mouse events via Qt.
|
||||
if (GetIEditor()->IsInGameMode())
|
||||
{
|
||||
UINT rawInputSize;
|
||||
const UINT rawInputHeaderSize = sizeof(RAWINPUTHEADER);
|
||||
GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, NULL, &rawInputSize, rawInputHeaderSize);
|
||||
|
||||
AZStd::array<BYTE, sizeof(RAWINPUT)> rawInputBytesArray;
|
||||
LPBYTE rawInputBytes = rawInputBytesArray.data();
|
||||
|
||||
const UINT bytesCopied = GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize);
|
||||
CRY_ASSERT(bytesCopied == rawInputSize);
|
||||
|
||||
RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes;
|
||||
CRY_ASSERT(rawInput);
|
||||
|
||||
AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputEvent, *rawInput);
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (msg->message == WM_DEVICECHANGE)
|
||||
{
|
||||
if (msg->wParam == 0x0007) // DBT_DEVNODES_CHANGED
|
||||
if (msg->message == WM_INPUT)
|
||||
{
|
||||
AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputDeviceChangeEvent);
|
||||
UINT rawInputSize;
|
||||
const UINT rawInputHeaderSize = sizeof(RAWINPUTHEADER);
|
||||
GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, NULL, &rawInputSize, rawInputHeaderSize);
|
||||
|
||||
AZStd::array<BYTE, sizeof(RAWINPUT)> rawInputBytesArray;
|
||||
LPBYTE rawInputBytes = rawInputBytesArray.data();
|
||||
|
||||
const UINT bytesCopied = GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize);
|
||||
CRY_ASSERT(bytesCopied == rawInputSize);
|
||||
|
||||
RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes;
|
||||
CRY_ASSERT(rawInput);
|
||||
|
||||
AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputEvent, *rawInput);
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (msg->message == WM_DEVICECHANGE)
|
||||
{
|
||||
if (msg->wParam == 0x0007) // DBT_DEVNODES_CHANGED
|
||||
{
|
||||
AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputDeviceChangeEvent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
+82
-15
@@ -581,6 +581,8 @@ public:
|
||||
{{"project-path", "Supplies the path to the project that the Editor should use", "project-path"}, dummyString},
|
||||
{{"engine-path", "Supplies the path to the engine", "engine-path"}, dummyString},
|
||||
{{"project-cache-path", "Path to the project cache", "project-cache-path"}, dummyString},
|
||||
{{"project-user-path", "Path to the project user path", "project-user-path"}, dummyString},
|
||||
{{"project-log-path", "Path to the project log path", "project-log-path"}, dummyString}
|
||||
// add dummy entries here to prevent QCommandLineParser error-ing out on cmd line args that will be parsed later
|
||||
};
|
||||
|
||||
@@ -1483,7 +1485,6 @@ struct PythonTestOutputHandler final
|
||||
{
|
||||
PythonOutputHandler::OnExceptionMessage(message);
|
||||
printf("EXCEPTION: %.*s\n", static_cast<int>(message.size()), message.data());
|
||||
AZ::Debug::Trace::Terminate(1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1501,34 +1502,91 @@ void CCryEditApp::RunInitPythonScript(CEditCommandLineInfo& cmdInfo)
|
||||
using namespace AzToolsFramework;
|
||||
if (cmdInfo.m_bRunPythonScript || cmdInfo.m_bRunPythonTestScript)
|
||||
{
|
||||
// cmdInfo data is only available on startup, copy it
|
||||
QByteArray fileStr = cmdInfo.m_strFileName.toUtf8();
|
||||
|
||||
// We support specifying multiple files in the cmdline by separating them with ';'
|
||||
AZStd::vector<AZStd::string_view> fileList;
|
||||
AzFramework::StringFunc::TokenizeVisitor(
|
||||
fileStr.constData(),
|
||||
[&fileList](AZStd::string_view elem)
|
||||
{
|
||||
fileList.push_back(elem);
|
||||
}, ';', false /* keepEmptyStrings */
|
||||
);
|
||||
|
||||
if (cmdInfo.m_pythonArgs.length() > 0 || cmdInfo.m_bRunPythonTestScript)
|
||||
{
|
||||
AZStd::vector<AZStd::string> tokens;
|
||||
AzFramework::StringFunc::Tokenize(cmdInfo.m_pythonArgs.toUtf8().constData(), tokens, ' ');
|
||||
QByteArray pythonArgsStr = cmdInfo.m_pythonArgs.toUtf8();
|
||||
AZStd::vector<AZStd::string_view> pythonArgs;
|
||||
std::transform(tokens.begin(), tokens.end(), std::back_inserter(pythonArgs), [](auto& tokenData) { return tokenData.c_str(); });
|
||||
AzFramework::StringFunc::TokenizeVisitor(pythonArgsStr.constData(),
|
||||
[&pythonArgs](AZStd::string_view elem)
|
||||
{
|
||||
pythonArgs.push_back(elem);
|
||||
}, ' '
|
||||
);
|
||||
|
||||
if (cmdInfo.m_bRunPythonTestScript)
|
||||
{
|
||||
AZStd::string pythonTestCase;
|
||||
if (!cmdInfo.m_pythontTestCase.isEmpty())
|
||||
// Multiple testcases can be specified them with ';', these should match the files to run
|
||||
AZStd::vector<AZStd::string_view> testcaseList;
|
||||
testcaseList.resize(fileList.size());
|
||||
{
|
||||
pythonTestCase = cmdInfo.m_pythontTestCase.toUtf8().constData();
|
||||
int i = 0;
|
||||
AzFramework::StringFunc::TokenizeVisitor(
|
||||
fileStr.constData(),
|
||||
[&i, &testcaseList](AZStd::string_view elem)
|
||||
{
|
||||
testcaseList[i++] = (elem);
|
||||
}, ';', false /* keepEmptyStrings */
|
||||
);
|
||||
}
|
||||
|
||||
EditorPythonRunnerRequestBus::Broadcast(&EditorPythonRunnerRequestBus::Events::ExecuteByFilenameAsTest, cmdInfo.m_strFileName.toUtf8().constData(), pythonTestCase, pythonArgs);
|
||||
bool success = true;
|
||||
auto ExecuteByFilenamesTests = [&pythonArgs, &fileList, &testcaseList, &success](EditorPythonRunnerRequests* pythonRunnerRequests)
|
||||
{
|
||||
for (int i = 0; i < fileList.size(); ++i)
|
||||
{
|
||||
bool cur_success = pythonRunnerRequests->ExecuteByFilenameAsTest(fileList[i], testcaseList[i], pythonArgs);
|
||||
success = success && cur_success;
|
||||
}
|
||||
};
|
||||
EditorPythonRunnerRequestBus::Broadcast(ExecuteByFilenamesTests);
|
||||
|
||||
// Close the editor gracefully as the test has completed
|
||||
GetIEditor()->GetDocument()->SetModifiedFlag(false);
|
||||
QTimer::singleShot(0, qApp, &QApplication::closeAllWindows);
|
||||
if (success)
|
||||
{
|
||||
// Close the editor gracefully as the test has completed
|
||||
GetIEditor()->GetDocument()->SetModifiedFlag(false);
|
||||
QTimer::singleShot(0, qApp, &QApplication::closeAllWindows);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Close down the application with 0xF exit code indicating failure of the test
|
||||
AZ::Debug::Trace::Terminate(0xF);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorPythonRunnerRequestBus::Broadcast(&EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs, cmdInfo.m_strFileName.toUtf8().constData(), pythonArgs);
|
||||
auto ExecuteByFilenamesWithArgs = [&pythonArgs, &fileList](EditorPythonRunnerRequests* pythonRunnerRequests)
|
||||
{
|
||||
for (AZStd::string_view filename : fileList)
|
||||
{
|
||||
pythonRunnerRequests->ExecuteByFilenameWithArgs(filename, pythonArgs);
|
||||
}
|
||||
};
|
||||
EditorPythonRunnerRequestBus::Broadcast(ExecuteByFilenamesWithArgs);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorPythonRunnerRequestBus::Broadcast(&EditorPythonRunnerRequestBus::Events::ExecuteByFilename, cmdInfo.m_strFileName.toUtf8().constData());
|
||||
auto ExecuteByFilenames = [&fileList](EditorPythonRunnerRequests* pythonRunnerRequests)
|
||||
{
|
||||
for (AZStd::string_view filename : fileList)
|
||||
{
|
||||
pythonRunnerRequests->ExecuteByFilename(filename);
|
||||
}
|
||||
};
|
||||
EditorPythonRunnerRequestBus::Broadcast(ExecuteByFilenames);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2271,7 +2329,9 @@ int CCryEditApp::IdleProcessing(bool bBackgroundUpdate)
|
||||
bool bIsAppWindow = IsWindowInForeground();
|
||||
bool bActive = false;
|
||||
int res = 0;
|
||||
if (bIsAppWindow || m_bForceProcessIdle || m_bKeepEditorActive)
|
||||
if (bIsAppWindow || m_bForceProcessIdle || m_bKeepEditorActive
|
||||
// Automated tests must always keep the editor active, or they can get stuck
|
||||
|| m_bAutotestMode)
|
||||
{
|
||||
res = 1;
|
||||
bActive = true;
|
||||
@@ -2841,7 +2901,14 @@ void CCryEditApp::OpenProjectManager(const AZStd::string& screen)
|
||||
{
|
||||
// provide the current project path for in case we want to update the project
|
||||
AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath();
|
||||
const AZStd::string commandLineOptions = AZStd::string::format(" --screen %s --project-path %s", screen.c_str(), projectPath.c_str());
|
||||
#if !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
|
||||
const char* argumentQuoteString = R"(")";
|
||||
#else
|
||||
const char* argumentQuoteString = R"(\")";
|
||||
#endif
|
||||
const AZStd::string commandLineOptions = AZStd::string::format(R"( --screen %s --project-path %s%s%s)",
|
||||
screen.c_str(),
|
||||
argumentQuoteString, projectPath.c_str(), argumentQuoteString);
|
||||
bool launchSuccess = AzFramework::ProjectManager::LaunchProjectManager(commandLineOptions);
|
||||
if (!launchSuccess)
|
||||
{
|
||||
|
||||
@@ -463,7 +463,7 @@ void EditorViewportWidget::Update()
|
||||
|
||||
if (m_updateCameraPositionNextTick)
|
||||
{
|
||||
auto cameraState = m_renderViewport->GetCameraState();
|
||||
auto cameraState = GetCameraState();
|
||||
AZ::Matrix3x4 matrix;
|
||||
matrix.SetBasisAndTranslation(cameraState.m_side, cameraState.m_forward, cameraState.m_up, cameraState.m_position);
|
||||
auto m = AZMatrix3x4ToLYMatrix3x4(matrix);
|
||||
@@ -472,6 +472,12 @@ void EditorViewportWidget::Update()
|
||||
m_Camera.SetZRange(cameraState.m_nearClip, cameraState.m_farClip);
|
||||
}
|
||||
|
||||
// Ensure the FOV matches our internally stored setting if we're using the Editor camera
|
||||
if (!m_viewEntityId.IsValid() && !GetIEditor()->IsInGameMode())
|
||||
{
|
||||
SetFOV(GetFOV());
|
||||
}
|
||||
|
||||
// Reset the camera update flag now that we're finished updating our viewport context
|
||||
m_updateCameraPositionNextTick = false;
|
||||
|
||||
@@ -1138,6 +1144,17 @@ void EditorViewportWidget::OnMenuSelectCurrentCamera()
|
||||
|
||||
AzFramework::CameraState EditorViewportWidget::GetCameraState()
|
||||
{
|
||||
if (m_viewEntityId.IsValid())
|
||||
{
|
||||
bool cameraStateAcquired = false;
|
||||
AzFramework::CameraState cameraState;
|
||||
Camera::EditorCameraViewRequestBus::BroadcastResult(cameraStateAcquired,
|
||||
&Camera::EditorCameraViewRequestBus::Events::GetCameraState, cameraState);
|
||||
if (cameraStateAcquired)
|
||||
{
|
||||
return cameraState;
|
||||
}
|
||||
}
|
||||
return m_renderViewport->GetCameraState();
|
||||
}
|
||||
|
||||
@@ -1223,6 +1240,13 @@ AZStd::shared_ptr<AtomToolsFramework::ModularViewportCameraController> CreateMod
|
||||
AzFramework::ViewportId viewportId)
|
||||
{
|
||||
auto controller = AZStd::make_shared<AtomToolsFramework::ModularViewportCameraController>();
|
||||
|
||||
controller->SetCameraPriorityBuilderCallback(
|
||||
[](AtomToolsFramework::CameraControllerPriorityFn& cameraControllerPriorityFn)
|
||||
{
|
||||
cameraControllerPriorityFn = AtomToolsFramework::DefaultCameraControllerPriority;
|
||||
});
|
||||
|
||||
controller->SetCameraPropsBuilderCallback(
|
||||
[](AzFramework::CameraProps& cameraProps)
|
||||
{
|
||||
@@ -2613,8 +2637,6 @@ void EditorViewportWidget::DestroyRenderContext()
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void EditorViewportWidget::SetDefaultCamera()
|
||||
{
|
||||
// Ensure the FOV matches our internally stored setting
|
||||
SetFOV(GetFOV());
|
||||
if (IsDefaultCamera())
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -278,14 +278,9 @@ namespace
|
||||
PyExit();
|
||||
}
|
||||
|
||||
void PyReportTest(bool success, const AZStd::string& output)
|
||||
void PyTestOutput(const AZStd::string& output)
|
||||
{
|
||||
CCryEditApp::instance()->PrintAlways(output);
|
||||
if (!success)
|
||||
{
|
||||
gEnv->retCode = 0xF; // Special error code indicating a failure in tests
|
||||
}
|
||||
PyExitNoPrompt();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1956,7 +1951,7 @@ namespace AzToolsFramework
|
||||
addLegacyGeneral(behaviorContext->Method("get_pane_class_names", PyGetViewPaneNames, nullptr, "Get all available class names for use with open_pane & close_pane."));
|
||||
addLegacyGeneral(behaviorContext->Method("exit", PyExit, nullptr, "Exits the editor."));
|
||||
addLegacyGeneral(behaviorContext->Method("exit_no_prompt", PyExitNoPrompt, nullptr, "Exits the editor without prompting to save first."));
|
||||
addLegacyGeneral(behaviorContext->Method("report_test_result", PyReportTest, nullptr, "Report test information."));
|
||||
addLegacyGeneral(behaviorContext->Method("test_output", PyTestOutput, nullptr, "Report test information."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-30
@@ -321,42 +321,13 @@ bool CToolBoxManager::SetMacroTitle(int index, const QString& title, bool bToolb
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CToolBoxManager::Load(ActionManager* actionManager)
|
||||
void CToolBoxManager::Load([[maybe_unused]] ActionManager* actionManager)
|
||||
{
|
||||
Clear();
|
||||
|
||||
QString path;
|
||||
GetSaveFilePath(path);
|
||||
Load(path, nullptr, true, nullptr);
|
||||
|
||||
if (actionManager)
|
||||
{
|
||||
auto engineSourceAssetPath = AZ::IO::FixedMaxPath(AZ::Utils::GetEnginePath()) / "Assets";
|
||||
LoadShelves((engineSourceAssetPath / "Editor" / "Scripts").c_str(),
|
||||
(engineSourceAssetPath / "Editor" / "Scripts" / "Shelves").c_str(), actionManager);
|
||||
}
|
||||
}
|
||||
|
||||
void CToolBoxManager::LoadShelves(QString scriptPath, QString shelvesPath, ActionManager* actionManager)
|
||||
{
|
||||
IFileUtil::FileArray files;
|
||||
CFileUtil::ScanDirectory(shelvesPath, "*.xml", files);
|
||||
|
||||
const int shelfCount = files.size();
|
||||
for (int idx = 0; idx < shelfCount; ++idx)
|
||||
{
|
||||
if (Path::GetExt(files[idx].filename) != "xml")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QString shelfName(PathUtil::GetFileName(files[idx].filename.toUtf8().data()));
|
||||
|
||||
AmazonToolbar toolbar(shelfName, shelfName);
|
||||
Load(shelvesPath + QString("/") + files[idx].filename, &toolbar, false, actionManager);
|
||||
|
||||
m_toolbars.push_back(toolbar);
|
||||
}
|
||||
}
|
||||
|
||||
void CToolBoxManager::Load(QString xmlpath, AmazonToolbar* pToolbar, bool bToolbox, ActionManager* actionManager)
|
||||
|
||||
@@ -129,7 +129,6 @@ public:
|
||||
void Save() const;
|
||||
// Load macros configuration from registry.
|
||||
void Load(ActionManager* actionManager = nullptr);
|
||||
void LoadShelves(QString scriptPath, QString shelvesPath, ActionManager* actionManager);
|
||||
|
||||
//! Get the number of managed macros.
|
||||
int GetMacroCount(bool bToolbox) const;
|
||||
|
||||
@@ -20,11 +20,11 @@ struct AffineParts
|
||||
Vec3 scale; //!< Stretch factors.
|
||||
float fDet; //!< Sign of determinant.
|
||||
|
||||
/** Decompose matrix to its affnie parts.
|
||||
/** Decompose matrix to its affine parts.
|
||||
*/
|
||||
void Decompose(const Matrix34& mat);
|
||||
|
||||
/** Decompose matrix to its affnie parts.
|
||||
/** Decompose matrix to its affine parts.
|
||||
Assume there`s no stretch rotation.
|
||||
*/
|
||||
void SpectralDecompose(const Matrix34& mat);
|
||||
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
static void Record(IUndoObject* undo);
|
||||
|
||||
private:
|
||||
static const uint32 scDescSize = 256;
|
||||
static const AZ::u32 scDescSize = 256;
|
||||
char m_description[scDescSize];
|
||||
bool m_bCancelled;
|
||||
bool m_bStartedRecord;
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ImportGroup Label="PropertySheets" />
|
||||
<PropertyGroup>
|
||||
<DisableFastUpToDateCheck>True</DisableFastUpToDateCheck>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -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
|
||||
)
|
||||
@@ -10,28 +10,74 @@
|
||||
#include <AzCore/Debug/TraceMessageBus.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/Script/ScriptContext.h>
|
||||
#include <AzCore/Component/TickBus.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Debug
|
||||
{
|
||||
//! Trace Message Event Handler for Automation.
|
||||
//! Since TraceMessageBus will be called from multiple threads and
|
||||
//! python interpreter is single threaded, all the bus calls are
|
||||
//! queued into a list and called at the end of the frame in the main thread.
|
||||
//! @note this class is not using the usual AZ_EBUS_BEHAVIOR_BINDER
|
||||
//! macro as the signature needs to be changed to connect to Tick bus.
|
||||
class TraceMessageBusHandler
|
||||
: public AZ::Debug::TraceMessageBus::Handler
|
||||
, public AZ::BehaviorEBusHandler
|
||||
, public AZ::TickBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(TraceMessageBusHandler, AZ::SystemAllocator, 0);
|
||||
AZ_RTTI(TraceMessageBusHandler, "{5CDBAF09-5EB0-48AC-B327-2AF8601BB550}", AZ::BehaviorEBusHandler);
|
||||
|
||||
AZ_EBUS_BEHAVIOR_BINDER(TraceMessageBusHandler, "{5CDBAF09-5EB0-48AC-B327-2AF8601BB550}", AZ::SystemAllocator
|
||||
, OnPreAssert
|
||||
, OnPreError
|
||||
, OnPreWarning
|
||||
, OnAssert
|
||||
, OnError
|
||||
, OnWarning
|
||||
, OnException
|
||||
, OnPrintf
|
||||
, OnOutput
|
||||
);
|
||||
TraceMessageBusHandler();
|
||||
|
||||
using EventFunctionsParameterPack = AZStd::Internal::pack_traits_arg_sequence<
|
||||
decltype(&TraceMessageBusHandler::OnPreAssert),
|
||||
decltype(&TraceMessageBusHandler::OnPreError),
|
||||
decltype(&TraceMessageBusHandler::OnPreWarning),
|
||||
decltype(&TraceMessageBusHandler::OnAssert),
|
||||
decltype(&TraceMessageBusHandler::OnError),
|
||||
decltype(&TraceMessageBusHandler::OnWarning),
|
||||
decltype(&TraceMessageBusHandler::OnException),
|
||||
decltype(&TraceMessageBusHandler::OnPrintf),
|
||||
decltype(&TraceMessageBusHandler::OnOutput)
|
||||
>;
|
||||
|
||||
enum
|
||||
{
|
||||
FN_OnPreAssert = 0,
|
||||
FN_OnPreError,
|
||||
FN_OnPreWarning,
|
||||
FN_OnAssert,
|
||||
FN_OnError,
|
||||
FN_OnWarning,
|
||||
FN_OnException,
|
||||
FN_OnPrintf,
|
||||
FN_OnOutput,
|
||||
FN_MAX
|
||||
};
|
||||
|
||||
static inline constexpr const char* m_functionNames[FN_MAX] =
|
||||
{
|
||||
"OnPreAssert",
|
||||
"OnPreError",
|
||||
"OnPreWarning",
|
||||
"OnAssert",
|
||||
"OnError",
|
||||
"OnWarning",
|
||||
"OnException",
|
||||
"OnPrintf",
|
||||
"OnOutput"
|
||||
};
|
||||
|
||||
// AZ::BehaviorEBusHandler overrides...
|
||||
int GetFunctionIndex(const char* functionName) const override;
|
||||
void Disconnect() override;
|
||||
bool Connect(AZ::BehaviorValueParameter* id = nullptr) override;
|
||||
bool IsConnected() override;
|
||||
bool IsConnectedId(AZ::BehaviorValueParameter* id) override;
|
||||
|
||||
// TraceMessageBus
|
||||
/*
|
||||
@@ -48,63 +94,190 @@ namespace AZ
|
||||
bool OnPrintf(const char* window, const char* message) override;
|
||||
bool OnOutput(const char* window, const char* message) override;
|
||||
|
||||
// AZ::TickBus::Handler overrides ...
|
||||
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
|
||||
int GetTickOrder() override;
|
||||
|
||||
private:
|
||||
template<class R, class... Args>
|
||||
R CallResultReturn(const R& defaultReturnValue, int index, Args&&... args) const
|
||||
{
|
||||
R returnVal = defaultReturnValue;
|
||||
CallResult(returnVal, index, AZStd::forward<Args>(args)...);
|
||||
return returnVal;
|
||||
}
|
||||
void QueueMessageCall(AZStd::function<void()> messageCall);
|
||||
void FlushMessageCalls();
|
||||
|
||||
AZStd::list<AZStd::function<void()>> m_messageCalls;
|
||||
AZStd::mutex m_messageCallsLock;
|
||||
};
|
||||
|
||||
TraceMessageBusHandler::TraceMessageBusHandler()
|
||||
{
|
||||
m_events.resize(FN_MAX);
|
||||
|
||||
SetEvent(&TraceMessageBusHandler::OnPreAssert, m_functionNames[FN_OnPreAssert]);
|
||||
SetEvent(&TraceMessageBusHandler::OnPreError, m_functionNames[FN_OnPreError]);
|
||||
SetEvent(&TraceMessageBusHandler::OnPreWarning, m_functionNames[FN_OnPreWarning]);
|
||||
SetEvent(&TraceMessageBusHandler::OnAssert, m_functionNames[FN_OnAssert]);
|
||||
SetEvent(&TraceMessageBusHandler::OnError, m_functionNames[FN_OnError]);
|
||||
SetEvent(&TraceMessageBusHandler::OnWarning, m_functionNames[FN_OnWarning]);
|
||||
SetEvent(&TraceMessageBusHandler::OnException, m_functionNames[FN_OnException]);
|
||||
SetEvent(&TraceMessageBusHandler::OnPrintf, m_functionNames[FN_OnPrintf]);
|
||||
SetEvent(&TraceMessageBusHandler::OnOutput, m_functionNames[FN_OnOutput]);
|
||||
}
|
||||
|
||||
int TraceMessageBusHandler::GetFunctionIndex(const char* functionName) const
|
||||
{
|
||||
for (int i = 0; i < FN_MAX; ++i)
|
||||
{
|
||||
if (azstricmp(functionName, m_functionNames[i]) == 0)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void TraceMessageBusHandler::Disconnect()
|
||||
{
|
||||
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
|
||||
AZ::TickBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
bool TraceMessageBusHandler::Connect(AZ::BehaviorValueParameter* id)
|
||||
{
|
||||
AZ::TickBus::Handler::BusConnect();
|
||||
return AZ::Internal::EBusConnector<AZ::Debug::TraceMessageBus::Handler>::Connect(this, id);
|
||||
}
|
||||
|
||||
bool TraceMessageBusHandler::IsConnected()
|
||||
{
|
||||
return AZ::Internal::EBusConnector<AZ::Debug::TraceMessageBus::Handler>::IsConnected(this);
|
||||
}
|
||||
|
||||
bool TraceMessageBusHandler::IsConnectedId(AZ::BehaviorValueParameter* id)
|
||||
{
|
||||
return AZ::Internal::EBusConnector<AZ::Debug::TraceMessageBus::Handler>::IsConnectedId(this, id);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// TraceMessageBusHandler Implementation
|
||||
inline bool TraceMessageBusHandler::OnPreAssert(const char* fileName, int line, const char* func, const char* message)
|
||||
{
|
||||
return CallResultReturn(false, FN_OnPreAssert, fileName, line, func, message);
|
||||
QueueMessageCall(
|
||||
[this, fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]()
|
||||
{
|
||||
Call(FN_OnPreAssert, fileNameString.c_str(), line, funcString.c_str(), messageString.c_str());
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool TraceMessageBusHandler::OnPreError(const char* window, const char* fileName, int line, const char* func, const char* message)
|
||||
{
|
||||
return CallResultReturn(false, FN_OnPreError, window, fileName, line, func, message);
|
||||
QueueMessageCall(
|
||||
[this, windowString = AZStd::string(window), fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]()
|
||||
{
|
||||
Call(FN_OnPreError, windowString.c_str(), fileNameString.c_str(), line, funcString.c_str(), messageString.c_str());
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool TraceMessageBusHandler::OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message)
|
||||
{
|
||||
return CallResultReturn(false, FN_OnPreWarning, window, fileName, line, func, message);
|
||||
QueueMessageCall(
|
||||
[this, windowString = AZStd::string(window), fileNameString = AZStd::string(fileName), line, funcString = AZStd::string(func), messageString = AZStd::string(message)]()
|
||||
{
|
||||
return Call(FN_OnPreWarning, windowString.c_str(), fileNameString.c_str(), line, funcString.c_str(), messageString.c_str());
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool TraceMessageBusHandler::OnAssert(const char* message)
|
||||
{
|
||||
return CallResultReturn(false, FN_OnAssert, message);
|
||||
QueueMessageCall(
|
||||
[this, messageString = AZStd::string(message)]()
|
||||
{
|
||||
return Call(FN_OnAssert, messageString.c_str());
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool TraceMessageBusHandler::OnError(const char* window, const char* message)
|
||||
{
|
||||
return CallResultReturn(false, FN_OnError, window, message);
|
||||
QueueMessageCall(
|
||||
[this, windowString = AZStd::string(window), messageString = AZStd::string(message)]()
|
||||
{
|
||||
return Call(FN_OnError, windowString.c_str(), messageString.c_str());
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool TraceMessageBusHandler::OnWarning(const char* window, const char* message)
|
||||
{
|
||||
return CallResultReturn(false, FN_OnWarning, window, message);
|
||||
QueueMessageCall(
|
||||
[this, windowString = AZStd::string(window), messageString = AZStd::string(message)]()
|
||||
{
|
||||
return Call(FN_OnWarning, windowString.c_str(), messageString.c_str());
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool TraceMessageBusHandler::OnException(const char* message)
|
||||
{
|
||||
return CallResultReturn(false, FN_OnException, message);
|
||||
QueueMessageCall(
|
||||
[this, messageString = AZStd::string(message)]()
|
||||
{
|
||||
return Call(FN_OnException, messageString.c_str());
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool TraceMessageBusHandler::OnPrintf(const char* window, const char* message)
|
||||
{
|
||||
return CallResultReturn(false, FN_OnPrintf, window, message);
|
||||
QueueMessageCall(
|
||||
[this, windowString = AZStd::string(window), messageString = AZStd::string(message)]()
|
||||
{
|
||||
return Call(FN_OnPrintf, windowString.c_str(), messageString.c_str());
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool TraceMessageBusHandler::OnOutput(const char* window, const char* message)
|
||||
{
|
||||
return CallResultReturn(false, FN_OnOutput, window, message);
|
||||
QueueMessageCall(
|
||||
[this, windowString = AZStd::string(window), messageString = AZStd::string(message)]()
|
||||
{
|
||||
return Call(FN_OnOutput, windowString.c_str(), messageString.c_str());
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
void TraceMessageBusHandler::OnTick(
|
||||
[[maybe_unused]] float deltaTime,
|
||||
[[maybe_unused]] AZ::ScriptTimePoint time)
|
||||
{
|
||||
FlushMessageCalls();
|
||||
}
|
||||
|
||||
int TraceMessageBusHandler::GetTickOrder()
|
||||
{
|
||||
return AZ::TICK_LAST;
|
||||
}
|
||||
|
||||
void TraceMessageBusHandler::QueueMessageCall(AZStd::function<void()> messageCall)
|
||||
{
|
||||
AZStd::lock_guard<decltype(m_messageCallsLock)> lock(m_messageCallsLock);
|
||||
m_messageCalls.push_back(messageCall);
|
||||
}
|
||||
|
||||
void TraceMessageBusHandler::FlushMessageCalls()
|
||||
{
|
||||
AZStd::list<AZStd::function<void()>> messageCalls;
|
||||
{
|
||||
AZStd::lock_guard<decltype(m_messageCallsLock)> lock(m_messageCallsLock);
|
||||
m_messageCalls.swap(messageCalls); // Move calls to a new list to release the lock as soon as possible
|
||||
}
|
||||
|
||||
for (auto& messageCall : messageCalls)
|
||||
{
|
||||
messageCall();
|
||||
}
|
||||
}
|
||||
|
||||
void TraceReflect(ReflectContext* context)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace AZ
|
||||
return &out;
|
||||
}
|
||||
|
||||
Matrix4x4* MakeOrthographicMatrixRH(Matrix4x4& out, float left, float right, float bottom, float top, float nearDist, float farDist)
|
||||
Matrix4x4* MakeOrthographicMatrixRH(Matrix4x4& out, float left, float right, float bottom, float top, float nearDist, float farDist, bool reverseDepth)
|
||||
{
|
||||
AZ_Assert(right > left, "right should be greater than left");
|
||||
// valid to have matrix invert top/bottom and far/near
|
||||
@@ -83,6 +83,11 @@ namespace AZ
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (reverseDepth)
|
||||
{
|
||||
AZStd::swap(nearDist, farDist);
|
||||
}
|
||||
|
||||
out.SetRow(0, 2.f/(right - left), 0.f, 0.f, - (right + left) / (right - left) );
|
||||
out.SetRow(1, 0.f, 2.f / (top - bottom), 0.f, - (top + bottom) / (top - bottom) );
|
||||
out.SetRow(2, 0.f, 0.f, 1 / (nearDist - farDist), nearDist / (nearDist - farDist) );
|
||||
|
||||
@@ -57,8 +57,9 @@ namespace AZ
|
||||
//! @param top The y coordinate of top view-plane
|
||||
//! @param near Distance to the near view-plane. Must be no less than zero.
|
||||
//! @param far Distance to the far view-plane. Must be greater than zero.
|
||||
//! @param reverseDepth Set to true to reverse depth which means near distance maps to 1 and far distance maps to 0.
|
||||
//! @return Pointer of the output matrix
|
||||
Matrix4x4* MakeOrthographicMatrixRH(Matrix4x4& out, float left, float right, float bottom, float top, float nearDist, float farDist);
|
||||
Matrix4x4* MakeOrthographicMatrixRH(Matrix4x4& out, float left, float right, float bottom, float top, float nearDist, float farDist, bool reverseDepth = false);
|
||||
|
||||
//! Transforms a position by a matrix. This function can be used with any generic cases which include projection matrices.
|
||||
Vector3 MatrixTransformPosition(const Matrix4x4& matrix, const Vector3& inPosition);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -561,9 +541,24 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
AZ::IO::FixedMaxPath normalizedProjectPath = path.LexicallyNormal();
|
||||
registry.Set(FilePathKey_ProjectPath, normalizedProjectPath.Native());
|
||||
|
||||
// Add an alias to the project "user" directory
|
||||
AZ::IO::FixedMaxPath projectUserPath = (normalizedProjectPath / "user").LexicallyNormal();
|
||||
// Set the user directory with the provided path or using project/user as default
|
||||
auto projectUserPathKey = FixedValueString::format("%s/project_user_path", BootstrapSettingsRootKey);
|
||||
AZ::IO::FixedMaxPath projectUserPath;
|
||||
if (!registry.Get(projectUserPath.Native(), projectUserPathKey))
|
||||
{
|
||||
projectUserPath = (normalizedProjectPath / "user").LexicallyNormal();
|
||||
}
|
||||
registry.Set(FilePathKey_ProjectUserPath, projectUserPath.Native());
|
||||
|
||||
// Set the user directory with the provided path or using project/user as default
|
||||
auto projectLogPathKey = FixedValueString::format("%s/project_log_path", BootstrapSettingsRootKey);
|
||||
AZ::IO::FixedMaxPath projectLogPath;
|
||||
if (!registry.Get(projectLogPath.Native(), projectLogPathKey))
|
||||
{
|
||||
projectLogPath = (projectUserPath / "log").LexicallyNormal();
|
||||
}
|
||||
registry.Set(FilePathKey_ProjectLogPath, projectLogPath.Native());
|
||||
|
||||
// check for a default write storage path, fall back to the project's user/ directory if not
|
||||
AZStd::optional<AZ::IO::FixedMaxPathString> devWriteStorage = Utils::GetDevWriteStoragePath();
|
||||
registry.Set(FilePathKey_DevWriteStorage, devWriteStorage.has_value()
|
||||
@@ -948,7 +943,14 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
OptionKeyToRegsetKey{
|
||||
"project-cache-path",
|
||||
AZStd::string::format("%s/project_cache_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)},
|
||||
OptionKeyToRegsetKey{"project-build-path", ProjectBuildPath} };
|
||||
OptionKeyToRegsetKey{
|
||||
"project-user-path",
|
||||
AZStd::string::format("%s/project_user_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)},
|
||||
OptionKeyToRegsetKey{
|
||||
"project-log-path",
|
||||
AZStd::string::format("%s/project_log_path", AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)},
|
||||
OptionKeyToRegsetKey{"project-build-path", ProjectBuildPath},
|
||||
};
|
||||
|
||||
AZStd::fixed_vector<AZStd::string, commandOptions.size()> overrideArgs;
|
||||
|
||||
|
||||
@@ -44,6 +44,10 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
//! project settings can be stored
|
||||
inline static constexpr char FilePathKey_ProjectUserPath[] = "/Amazon/AzCore/Runtime/FilePaths/SourceProjectUserPath";
|
||||
|
||||
//! Store the absolute path to the Projects "log" directory, which is a transient directory where per user
|
||||
//! logs can be stored. By default this would be on "{FilePathKey_ProjectUserPath}/log"
|
||||
inline static constexpr char FilePathKey_ProjectLogPath[] = "/Amazon/AzCore/Runtime/FilePaths/SourceProjectLogPath";
|
||||
|
||||
//! User facing key which represents the root of a project cmake build tree. i.e the ${CMAKE_BINARY_DIR}
|
||||
//! A relative path is taking relative to the *project* root, NOT *engine* root.
|
||||
inline constexpr AZStd::string_view ProjectBuildPath = "/Amazon/Project/Settings/Build/project_build_path";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -120,6 +120,11 @@ namespace AZStd
|
||||
base_type::insert(*first);
|
||||
}
|
||||
}
|
||||
fixed_unordered_map(const AZStd::initializer_list<value_type>& list, const hasher& hash = hasher(),
|
||||
const key_eq& keyEqual = key_eq())
|
||||
: fixed_unordered_map(list.begin(), list.end(), hash, keyEqual)
|
||||
{
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE pair_iter_bool insert(const value_type& value)
|
||||
{
|
||||
@@ -241,6 +246,12 @@ namespace AZStd
|
||||
base_type::insert(*first);
|
||||
}
|
||||
}
|
||||
fixed_unordered_multimap(const AZStd::initializer_list<value_type>& list, const hasher& hash = hasher(),
|
||||
const key_eq& keyEqual = key_eq())
|
||||
: fixed_unordered_multimap(list.begin(), list.end(), hash, keyEqual)
|
||||
{
|
||||
}
|
||||
|
||||
AZ_FORCE_INLINE iterator insert(const value_type& value)
|
||||
{
|
||||
return base_type::insert_impl(value).first;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace AZStd
|
||||
1610612741ul, 3221225473ul, 4294967291ul
|
||||
};
|
||||
|
||||
// Bucket size suitable to hold n elements.
|
||||
AZStd::size_t hash_next_bucket_size(AZStd::size_t n)
|
||||
{
|
||||
const AZStd::size_t* first = prime_list;
|
||||
|
||||
@@ -134,6 +134,7 @@ namespace AZStd
|
||||
void rehash(HashTable* table, size_type numBucketsMin)
|
||||
{
|
||||
size_type num_buckets = 0;
|
||||
|
||||
numBucketsMin = (AZStd::max)(numBucketsMin, (size_type)ceilf((float)m_list.size() / m_max_load_factor));
|
||||
|
||||
if (numBucketsMin != 0)
|
||||
@@ -143,7 +144,7 @@ namespace AZStd
|
||||
|
||||
if (num_buckets == m_numBuckets)
|
||||
{
|
||||
return; // no point
|
||||
return; // no need yet to rehash
|
||||
}
|
||||
m_numBuckets = num_buckets;
|
||||
|
||||
@@ -165,32 +166,43 @@ namespace AZStd
|
||||
while (!m_list.empty())
|
||||
{
|
||||
cur = m_list.begin();
|
||||
typename list_type::iterator insertIter, curEnd(cur);
|
||||
const typename HashTable::key_type& valueKey = Traits::key_from_value(*cur);
|
||||
|
||||
typename list_type::iterator newIter, iter(cur);
|
||||
size_type numValues = 1;
|
||||
for (++iter; iter != last && table->m_keyEqual(Traits::key_from_value(*cur), Traits::key_from_value(*iter)); ++iter, ++numValues)
|
||||
// Get the number of same consecutive elements in the table with same key,
|
||||
// this allows range insertion of elements at once
|
||||
for (++curEnd; curEnd != last && table->m_keyEqual(valueKey, Traits::key_from_value(*curEnd)); ++curEnd, ++numValues)
|
||||
{
|
||||
}
|
||||
;
|
||||
|
||||
const typename HashTable::key_type& valueKey = Traits::key_from_value(*cur);
|
||||
size_type newBucketIndex = table->bucket_from_hash(table->m_hasher(valueKey));
|
||||
|
||||
// newBucket.first holds the total number of elements in the bucket
|
||||
// newBucket.second contains the pointer to the first element in the bucket
|
||||
vector_value_type& newBucket = newBuckets[newBucketIndex];
|
||||
size_type numElements = newBucket.first;
|
||||
newIter = newBucket.second;
|
||||
insertIter = newBucket.second;
|
||||
|
||||
// If we don't have elements in the bucket yet, transfer the elements directly
|
||||
if (numElements == 0)
|
||||
{
|
||||
newList.splice(newList.begin(), m_list, cur, iter);
|
||||
newList.splice(newList.begin(), m_list, cur, curEnd);
|
||||
newBucket.second = newList.begin();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!table->find_insert_position(valueKey, table->m_keyEqual, newIter, numElements, integral_constant<bool, Traits::has_multi_elements>()))
|
||||
// Since there are elements already in the bucket, update `insertIter` to where the elements will need to be inserted.
|
||||
if (!table->find_insert_position(valueKey, table->m_keyEqual, insertIter, numElements, integral_constant<bool, Traits::has_multi_elements>()))
|
||||
{
|
||||
continue;
|
||||
// An element was found but we don't allow for duplicate elements in this table.
|
||||
// This happens when there was an insertion of two elements that are equal but have different hashes,
|
||||
// which is undefined behavior for a hash table: ISO C++ N4713, section 23.14.15 - 5.3
|
||||
AZ_Assert(false, "Found a duplicate element when rehashing. "
|
||||
"Review the hashing function for this type and make sure two equal elements always have the same hash");
|
||||
}
|
||||
|
||||
newList.splice(newIter, m_list, cur, iter);
|
||||
newList.splice(insertIter, m_list, cur, curEnd);
|
||||
}
|
||||
|
||||
newBucket.first += numValues;
|
||||
@@ -251,15 +263,15 @@ namespace AZStd
|
||||
m_vector.set_allocator(typename vector_type::allocator_type(&m_allocator));
|
||||
}
|
||||
|
||||
allocator_type m_allocator; ///< The single instance of the allocator shared between list and vector containers.
|
||||
list_type m_list; ///< List with elements.
|
||||
vector_type m_vector; ///< Buckets with list iterators.
|
||||
allocator_type m_allocator; //!< The single instance of the allocator shared between list and vector containers.
|
||||
list_type m_list; //!< List with elements.
|
||||
vector_type m_vector; //!< Buckets with list iterators.
|
||||
|
||||
private:
|
||||
vector_value_type* m_buckets; ///< Current buckets array. (can point to the m_vector or m_startBucket).
|
||||
size_type m_numBuckets; ///< Current number of buckets.
|
||||
float m_max_load_factor;
|
||||
vector_value_type m_startBucket; ///< Start bucket used for before we start dynamically allocate memory from m_vector.
|
||||
vector_value_type* m_buckets; //!< Current buckets array. (can point to the m_vector or m_startBucket).
|
||||
size_type m_numBuckets; //!< Current number of buckets.
|
||||
float m_max_load_factor; //!< Maximum load (elements/buckets) before rehashing.
|
||||
vector_value_type m_startBucket; //!< Start bucket used for before we start dynamically allocate memory from m_vector.
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -321,8 +333,8 @@ namespace AZStd
|
||||
template<class HashTable>
|
||||
AZ_FORCE_INLINE void rehash(HashTable*, size_type) {}
|
||||
|
||||
vector_type m_vector; ///< Buckets with list iterators.
|
||||
list_type m_list; ///< List with elements.
|
||||
vector_type m_vector; //!< Buckets with list iterators.
|
||||
list_type m_list; //!< List with elements.
|
||||
};
|
||||
}
|
||||
|
||||
@@ -972,28 +984,32 @@ namespace AZStd
|
||||
rhs.clear();
|
||||
}
|
||||
|
||||
// find_insert_position sets insertIter to where the element should be inserted
|
||||
// and returns true if the element should be inserted, otherwise false
|
||||
template<class ComparableToKey, class KeyEq>
|
||||
bool find_insert_position(const ComparableToKey& keyCmp, const KeyEq& keyEq, iterator& iter, size_type numElements, const true_type& /* is multi elements */)
|
||||
bool find_insert_position(const ComparableToKey& keyCmp, const KeyEq& keyEq, iterator& insertIter, size_type numElements, const true_type& /* is multi elements */)
|
||||
{
|
||||
for (size_type i = 0; i < numElements; ++i, ++iter)
|
||||
for (size_type i = 0; i < numElements; ++i, ++insertIter)
|
||||
{
|
||||
if (keyEq(keyCmp, Traits::key_from_value(*iter)))
|
||||
if (keyEq(keyCmp, Traits::key_from_value(*insertIter)))
|
||||
{
|
||||
++iter;
|
||||
++insertIter;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// always return true since multi elements (like multiset) allow repeated elements
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class ComparableToKey, class KeyEq>
|
||||
bool find_insert_position(const ComparableToKey& keyCmp, const KeyEq& keyEq, iterator& iter, size_type numElements, const false_type& /* !is multi elements */)
|
||||
bool find_insert_position(const ComparableToKey& keyCmp, const KeyEq& keyEq, iterator& insertIter, size_type numElements, const false_type& /* !is multi elements */)
|
||||
{
|
||||
for (size_type i = 0; i < numElements; ++i, ++iter)
|
||||
for (size_type i = 0; i < numElements; ++i, ++insertIter)
|
||||
{
|
||||
if (keyEq(keyCmp, Traits::key_from_value(*iter)))
|
||||
if (keyEq(keyCmp, Traits::key_from_value(*insertIter)))
|
||||
{
|
||||
// Element already exists, it shouldn't be inserted as we don't allow more than one repeated element for this specialization
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME})
|
||||
ly_get_list_relative_pal_filename(common_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/Common)
|
||||
|
||||
if(LY_ENABLE_RAD_TELEMETRY)
|
||||
if(LY_RAD_TELEMETRY_ENABLED)
|
||||
set(AZ_CORE_RADTELEMETRY_FILES ${common_dir}/azcore_profile_telemetry_files.cmake)
|
||||
set(AZ_CORE_RADTELEMETRY_PLATFORM_INCLUDES ${pal_dir}/profile_telemetry_platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake)
|
||||
set(AZ_CORE_RADTELEMETRY_INCLUDE_DIRECTORIES ${common_dir})
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
# is being avoided to prevent overriding functions declared in other targets platfrom
|
||||
# specific cmake files
|
||||
|
||||
if(LY_ENABLE_RAD_TELEMETRY)
|
||||
if(LY_RAD_TELEMETRY_ENABLED)
|
||||
set(LY_COMPILE_DEFINITIONS PUBLIC AZ_PROFILE_TELEMETRY)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
|
||||
<!-- rapidjson::GenericValue - basic support -->
|
||||
<Type Name="rapidjson_ly::GenericValue<*,*>">
|
||||
<DisplayString Condition="(data_.f.flags & kTypeMask) == kNullType">null</DisplayString>
|
||||
<DisplayString Condition="data_.f.flags == kTrueFlag">true</DisplayString>
|
||||
<DisplayString Condition="data_.f.flags == kFalseFlag">false</DisplayString>
|
||||
<DisplayString Condition="data_.f.flags == kShortStringFlag">{data_.ss.str}</DisplayString>
|
||||
<DisplayString Condition="(data_.f.flags & kTypeMask) == kStringType">{(const char*)((size_t)data_.s.str & 0x0000FFFFFFFFFFFF)}</DisplayString>
|
||||
<DisplayString Condition="(data_.f.flags & kNumberIntFlag) == kNumberIntFlag">{data_.n.i.i}</DisplayString>
|
||||
<DisplayString Condition="(data_.f.flags & kNumberUintFlag) == kNumberUintFlag">{data_.n.u.u}</DisplayString>
|
||||
<DisplayString Condition="(data_.f.flags & kNumberInt64Flag) == kNumberInt64Flag">{data_.n.i64}</DisplayString>
|
||||
<DisplayString Condition="(data_.f.flags & kNumberUint64Flag) == kNumberUint64Flag">{data_.n.u64}</DisplayString>
|
||||
<DisplayString Condition="(data_.f.flags & kNumberDoubleFlag) == kNumberDoubleFlag">{data_.n.d}</DisplayString>
|
||||
<DisplayString Condition="data_.f.flags == kObjectType">Object members={data_.o.size}</DisplayString>
|
||||
<DisplayString Condition="data_.f.flags == kArrayType">Array members={data_.a.size}</DisplayString>
|
||||
<Expand>
|
||||
<Item Condition="data_.f.flags == kObjectType" Name="[size]">data_.o.size</Item>
|
||||
<Item Condition="data_.f.flags == kObjectType" Name="[capacity]">data_.o.capacity</Item>
|
||||
<ArrayItems Condition="data_.f.flags == kObjectType">
|
||||
<Size>data_.o.size</Size>
|
||||
<!-- NOTE: Rapidjson stores some extra data in the high bits of pointers, which is why the mask -->
|
||||
<ValuePointer>(rapidjson_ly::GenericMember<$T1,$T2>*)(((size_t)data_.o.members) & 0x0000FFFFFFFFFFFF)</ValuePointer>
|
||||
</ArrayItems>
|
||||
|
||||
<Item Condition="data_.f.flags == kArrayType" Name="[size]">data_.a.size</Item>
|
||||
<Item Condition="data_.f.flags == kArrayType" Name="[capacity]">data_.a.capacity</Item>
|
||||
<ArrayItems Condition="data_.f.flags == kArrayType">
|
||||
<Size>data_.a.size</Size>
|
||||
<!-- NOTE: Rapidjson stores some extra data in the high bits of pointers, which is why the mask -->
|
||||
<ValuePointer>(rapidjson_ly::GenericValue<$T1,$T2>*)(((size_t)data_.a.elements) & 0x0000FFFFFFFFFFFF)</ValuePointer>
|
||||
</ArrayItems>
|
||||
|
||||
</Expand>
|
||||
</Type>
|
||||
|
||||
</AutoVisualizer>
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
# is being avoided to prevent overriding functions declared in other targets platfrom
|
||||
# specific cmake files
|
||||
|
||||
if(LY_ENABLE_RAD_TELEMETRY)
|
||||
if(LY_RAD_TELEMETRY_ENABLED)
|
||||
set(LY_COMPILE_DEFINITIONS PUBLIC AZ_PROFILE_TELEMETRY)
|
||||
endif()
|
||||
|
||||
@@ -30,6 +30,7 @@ set(FILES
|
||||
../Common/VisualStudio/AzCore/Natvis/azcore.natvis
|
||||
../Common/VisualStudio/AzCore/Natvis/azcore.natstepfilter
|
||||
../Common/VisualStudio/AzCore/Natvis/azcore.natjmc
|
||||
../Common/VisualStudio/AzCore/Natvis/rapidjson.natvis
|
||||
AzCore/Debug/StackTracer_Windows.cpp
|
||||
../Common/WinAPI/AzCore/Debug/Trace_WinAPI.cpp
|
||||
../Common/WinAPI/AzCore/IO/Streamer/StreamerContext_WinAPI.cpp
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
# is being avoided to prevent overriding functions declared in other targets platfrom
|
||||
# specific cmake files
|
||||
|
||||
if(LY_ENABLE_RAD_TELEMETRY)
|
||||
if(LY_RAD_TELEMETRY_ENABLED)
|
||||
set(LY_COMPILE_DEFINITIONS PUBLIC AZ_PROFILE_TELEMETRY)
|
||||
endif()
|
||||
|
||||
@@ -6,6 +6,6 @@
|
||||
#
|
||||
#
|
||||
|
||||
if(LY_ENABLE_RAD_TELEMETRY)
|
||||
if(LY_RAD_TELEMETRY_ENABLED)
|
||||
set(LY_COMPILE_DEFINITIONS PUBLIC AZ_PROFILE_TELEMETRY)
|
||||
endif()
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,6 +287,55 @@ namespace UnitTest
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(HashedContainers, HashTable_InsertionDuplicateOnRehash)
|
||||
{
|
||||
struct TwoPtrs
|
||||
{
|
||||
void* m_ptr1;
|
||||
void* m_ptr2;
|
||||
|
||||
bool operator==(const TwoPtrs& other) const
|
||||
{
|
||||
if (m_ptr1 == other.m_ptr1)
|
||||
{
|
||||
return m_ptr2 == other.m_ptr2;
|
||||
}
|
||||
else if (m_ptr1 == other.m_ptr2)
|
||||
{
|
||||
return m_ptr2 == other.m_ptr1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// This hashing function produces different hashes for two equal values,
|
||||
// which violates the requirement for hashing functions.
|
||||
// The test makes sure that this does not reproduce an issue that caused the insert() function to loop infinitely.
|
||||
struct TwoPtrsHasher
|
||||
{
|
||||
size_t operator()(const TwoPtrs& p) const
|
||||
{
|
||||
size_t hash{ 0 };
|
||||
AZStd::hash_combine(hash, p.m_ptr1, p.m_ptr2);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
using PairSet = AZStd::unordered_set<TwoPtrs, TwoPtrsHasher>;
|
||||
PairSet set;
|
||||
set.insert({ (void*)1, (void*)2 });
|
||||
set.insert({ (void*)3, (void*)4 });
|
||||
set.insert({ (void*)5, (void*)6 });
|
||||
set.insert({ (void*)7, (void*)8 });
|
||||
// Elements with different hashes, but equal
|
||||
set.insert({ (void*)0x000001ceddd9ca20, (void*)0x000001ceddd9cba0 }); // hash(148335135725641)
|
||||
set.insert({ (void*)0x000001ceddd9cba0, (void*)0x000001ceddd9ca20 }); // hash(148335135764189)
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
// This will trigger the assertion of duplicated elements found
|
||||
// A bucket size of 23 since is where the collision between different hashes happens
|
||||
set.rehash(23);
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // 1 assertion
|
||||
}
|
||||
|
||||
TEST_F(HashedContainers, HashTable_Fixed)
|
||||
{
|
||||
array<int, 5> elements = {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
#include <AzCore/IO/GenericStreams.h>
|
||||
#include <AzCore/Math/Crc.h>
|
||||
#include <AzCore/Math/Uuid.h>
|
||||
#include <AzCore/Jobs/JobManager.h>
|
||||
#include <AzCore/Jobs/JobContext.h>
|
||||
#include <AzCore/Outcome/Outcome.h>
|
||||
@@ -698,7 +699,7 @@ namespace UnitTest
|
||||
auto& assetManager = AssetManager::Instance();
|
||||
|
||||
AssetBusCallbacks callbacks{};
|
||||
callbacks.SetOnAssetReadyCallback([&](const Asset<AssetData>&, AssetBusCallbacks&)
|
||||
callbacks.SetOnAssetReadyCallback([&, AssetNoRefB](const Asset<AssetData>&, AssetBusCallbacks&)
|
||||
{
|
||||
// This callback should run inside the "main thread" dispatch events loop
|
||||
auto loadAsset = assetManager.GetAsset<AssetWithSerializedData>(AZ::Uuid(AssetNoRefB), AssetLoadBehavior::Default);
|
||||
@@ -1149,7 +1150,7 @@ namespace UnitTest
|
||||
#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
|
||||
TEST_F(AssetJobsFloodTest, DISABLED_ContainerFilterTest_ContainersWithAndWithoutFiltering_Success)
|
||||
#else
|
||||
TEST_F(AssetJobsFloodTest, ContainerFilterTest_ContainersWithAndWithoutFiltering_Success)
|
||||
TEST_F(AssetJobsFloodTest, DISABLED_ContainerFilterTest_ContainersWithAndWithoutFiltering_Success)
|
||||
#endif // !AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
|
||||
{
|
||||
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusConnect();
|
||||
|
||||
@@ -707,25 +707,23 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
if (AZ::IO::FixedMaxPath projectUserPath;
|
||||
m_settingsRegistry->Get(projectUserPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath))
|
||||
AZ::IO::FixedMaxPath engineRoot = GetEngineRoot();
|
||||
AZ::IO::FixedMaxPath projectUserPath;
|
||||
if (!m_settingsRegistry->Get(projectUserPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath))
|
||||
{
|
||||
fileIoBase->SetAlias("@user@", projectUserPath.c_str());
|
||||
AZ::IO::FixedMaxPath projectLogPath = projectUserPath / "log";
|
||||
fileIoBase->SetAlias("@log@", projectLogPath.c_str());
|
||||
fileIoBase->CreatePath(projectLogPath.c_str()); // Create the log directory at this point
|
||||
projectUserPath = engineRoot / "user";
|
||||
}
|
||||
fileIoBase->SetAlias("@user@", projectUserPath.c_str());
|
||||
fileIoBase->CreatePath(projectUserPath.c_str());
|
||||
CreateUserCache(projectUserPath, *fileIoBase);
|
||||
|
||||
CreateUserCache(projectUserPath, *fileIoBase);
|
||||
}
|
||||
else
|
||||
AZ::IO::FixedMaxPath projectLogPath;
|
||||
if (!m_settingsRegistry->Get(projectLogPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectLogPath))
|
||||
{
|
||||
AZ::IO::FixedMaxPath fallbackLogPath = GetEngineRoot();
|
||||
fallbackLogPath /= "user";
|
||||
fileIoBase->SetAlias("@user@", fallbackLogPath.c_str());
|
||||
fallbackLogPath /= "log";
|
||||
fileIoBase->SetAlias("@log@", fallbackLogPath.c_str());
|
||||
fileIoBase->CreatePath(fallbackLogPath.c_str());
|
||||
projectLogPath = projectUserPath / "log";
|
||||
}
|
||||
fileIoBase->SetAlias("@log@", projectLogPath.c_str());
|
||||
fileIoBase->CreatePath(projectLogPath.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,13 @@ namespace Camera
|
||||
//! @return The camera frustum's height
|
||||
virtual float GetFrustumHeight() = 0;
|
||||
|
||||
//! Gets whether or not the camera is using an orthographic projection.
|
||||
//! @return True if the camera is using an orthographic projection, or false if the camera is using a perspective projection.
|
||||
virtual bool IsOrthographic() = 0;
|
||||
|
||||
//! @return The half width of the orthographic projection, @see SetOrthographicHalfWidth.
|
||||
virtual float GetOrthographicHalfWidth() = 0;
|
||||
|
||||
//! Sets the camera's field of view in degrees between 0 < fov < 180 degrees
|
||||
//! @param fov The camera frustum's new field of view in degrees
|
||||
virtual void SetFov(float fov)
|
||||
@@ -95,6 +102,15 @@ namespace Camera
|
||||
//! @param height The camera frustum's new height
|
||||
virtual void SetFrustumHeight(float height) = 0;
|
||||
|
||||
//! Sets whether or not the camera should use an orthographic projection in place of a perspective projection.
|
||||
//! @param orthographic If true, the camera will use an orthographic projection
|
||||
virtual void SetOrthographic(bool orthographic) = 0;
|
||||
|
||||
//! Sets the half-width of the orthographic projection.
|
||||
//! @params halfWidth Used to calculate the bounds of the projection while in orthographic mode.
|
||||
//! The height is calculated automatically based on the aspect ratio.
|
||||
virtual void SetOrthographicHalfWidth(float halfWidth) = 0;
|
||||
|
||||
//! Makes the camera the active view
|
||||
virtual void MakeActiveView() = 0;
|
||||
|
||||
|
||||
+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;
|
||||
|
||||
|
||||
@@ -227,8 +227,10 @@ namespace AzFramework
|
||||
|
||||
EntitySpawnTicket::EntitySpawnTicket(EntitySpawnTicket&& rhs)
|
||||
: m_payload(rhs.m_payload)
|
||||
, m_id(rhs.m_id)
|
||||
{
|
||||
rhs.m_payload = nullptr;
|
||||
rhs.m_id = 0;
|
||||
}
|
||||
|
||||
EntitySpawnTicket::EntitySpawnTicket(AZ::Data::Asset<Spawnable> spawnable)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -147,7 +147,9 @@ namespace AzFramework
|
||||
m_scrollDelta = scroll->m_delta;
|
||||
}
|
||||
|
||||
return m_cameras.HandleEvents(event, m_motionDelta, m_scrollDelta);
|
||||
m_handlingEvents = m_cameras.HandleEvents(event, m_motionDelta, m_scrollDelta);
|
||||
|
||||
return m_handlingEvents;
|
||||
}
|
||||
|
||||
Camera CameraSystem::StepCamera(const Camera& targetCamera, const float deltaTime)
|
||||
|
||||
@@ -262,12 +262,14 @@ namespace AzFramework
|
||||
public:
|
||||
bool HandleEvents(const InputEvent& event);
|
||||
Camera StepCamera(const Camera& targetCamera, float deltaTime);
|
||||
bool HandlingEvents() const { return m_handlingEvents; }
|
||||
|
||||
Cameras m_cameras; //!< Represents a collection of camera inputs that together provide a camera controller.
|
||||
|
||||
private:
|
||||
ScreenVector m_motionDelta; //!< The delta used for look/orbit/pan (rotation + translation) - two dimensional.
|
||||
float m_scrollDelta = 0.0f; //!< The delta used for dolly/movement (translation) - one dimensional.
|
||||
bool m_handlingEvents = false; //!< Is the camera system currently handling events (events are consumed and not propagated).
|
||||
};
|
||||
|
||||
//! A camera input to handle motion deltas that can rotate or orbit the camera.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME})
|
||||
ly_get_list_relative_pal_filename(common_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/Common)
|
||||
|
||||
set(LY_ENABLE_STATISTICAL_PROFILING OFF CACHE BOOL "Enables statistical profiling when using AZ_PROFILE_SCOPE. If True, it takes effect only if RAD Telemetry is disabled.")
|
||||
set(LY_STATISTICAL_PROFILING_ENABLED OFF CACHE BOOL "Enables statistical profiling when using AZ_PROFILE_SCOPE. If True, it takes effect only if RAD Telemetry is disabled.")
|
||||
set(LY_TOUCHBENDING_LAYER_BIT 63 CACHE STRING "Use TouchBending as the collision layer. The TouchBending layer can be a number from 1 to 63 (Default=63).")
|
||||
|
||||
ly_add_target(
|
||||
@@ -38,7 +38,7 @@ ly_add_target(
|
||||
3rdParty::lz4
|
||||
)
|
||||
|
||||
if(LY_ENABLE_STATISTICAL_PROFILING)
|
||||
if(LY_STATISTICAL_PROFILING_ENABLED)
|
||||
ly_add_source_properties(
|
||||
SOURCES AzFramework/Debug/StatisticalProfilerProxy.h
|
||||
PROPERTY COMPILE_DEFINITIONS
|
||||
|
||||
@@ -9,8 +9,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
|
||||
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
#include <xcb/xcb.h>
|
||||
#endif // LY_COMPILE_DEFINITIONS
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class LinuxLifecycleEvents
|
||||
@@ -25,4 +30,31 @@ namespace AzFramework
|
||||
|
||||
using Bus = AZ::EBus<LinuxLifecycleEvents>;
|
||||
};
|
||||
|
||||
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
class LinuxXcbConnectionManager
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(LinuxXcbConnectionManager, "{1F756E14-8D74-42FD-843C-4863307710DB}");
|
||||
|
||||
virtual ~LinuxXcbConnectionManager() = default;
|
||||
|
||||
virtual xcb_connection_t* GetXcbConnection() const = 0;
|
||||
};
|
||||
|
||||
class LinuxXcbConnectionManagerBusTraits
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// EBusTraits overrides
|
||||
static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
};
|
||||
|
||||
using LinuxXcbConnectionManagerBus = AZ::EBus<LinuxXcbConnectionManager, LinuxXcbConnectionManagerBusTraits>;
|
||||
using LinuxXcbConnectionManagerInterface = AZ::Interface<LinuxXcbConnectionManager>;
|
||||
|
||||
#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
} // namespace AzFramework
|
||||
|
||||
+47
@@ -12,6 +12,32 @@
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
{
|
||||
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
class LinuxXcbConnectionManagerImpl
|
||||
: public LinuxXcbConnectionManagerBus::Handler
|
||||
{
|
||||
public:
|
||||
LinuxXcbConnectionManagerImpl()
|
||||
{
|
||||
m_xcbConnection = xcb_connect(nullptr, nullptr);
|
||||
AZ_Error("ApplicationLinux", m_xcbConnection != nullptr, "Unable to connect to X11 Server.");
|
||||
LinuxXcbConnectionManagerBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
~LinuxXcbConnectionManagerImpl()
|
||||
{
|
||||
LinuxXcbConnectionManagerBus::Handler::BusDisconnect();
|
||||
xcb_disconnect(m_xcbConnection);
|
||||
}
|
||||
xcb_connection_t* GetXcbConnection() const override
|
||||
{
|
||||
return m_xcbConnection;
|
||||
}
|
||||
private:
|
||||
xcb_connection_t* m_xcbConnection = nullptr;
|
||||
};
|
||||
#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
class ApplicationLinux
|
||||
: public Application::Implementation
|
||||
@@ -27,6 +53,12 @@ namespace AzFramework
|
||||
// Application::Implementation
|
||||
void PumpSystemEventLoopOnce() override;
|
||||
void PumpSystemEventLoopUntilEmpty() override;
|
||||
private:
|
||||
|
||||
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
AZStd::unique_ptr<LinuxXcbConnectionManager> m_xcbConnectionManager;
|
||||
#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -39,11 +71,26 @@ namespace AzFramework
|
||||
ApplicationLinux::ApplicationLinux()
|
||||
{
|
||||
LinuxLifecycleEvents::Bus::Handler::BusConnect();
|
||||
|
||||
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
m_xcbConnectionManager = AZStd::make_unique<LinuxXcbConnectionManagerImpl>();
|
||||
if (LinuxXcbConnectionManagerInterface::Get() == nullptr)
|
||||
{
|
||||
LinuxXcbConnectionManagerInterface::Register(m_xcbConnectionManager.get());
|
||||
}
|
||||
#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
ApplicationLinux::~ApplicationLinux()
|
||||
{
|
||||
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
if (LinuxXcbConnectionManagerInterface::Get() == m_xcbConnectionManager.get())
|
||||
{
|
||||
LinuxXcbConnectionManagerInterface::Unregister(m_xcbConnectionManager.get());
|
||||
}
|
||||
m_xcbConnectionManager.reset();
|
||||
#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
LinuxLifecycleEvents::Bus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,3 +5,30 @@
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
# Based on the linux window manager trait, perform the appropriate additional build configurations
|
||||
# Only 'xcb', 'wayland', and 'xlib' are recognized
|
||||
if (${PAL_TRAIT_LINUX_WINDOW_MANAGER} STREQUAL "xcb")
|
||||
|
||||
find_library(XCB_LIBRARY xcb)
|
||||
|
||||
set(LY_BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
${XCB_LIBRARY}
|
||||
)
|
||||
|
||||
set(LY_COMPILE_DEFINITIONS PUBLIC PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB)
|
||||
|
||||
elseif(PAL_TRAIT_LINUX_WINDOW_MANAGER STREQUAL "wayland")
|
||||
|
||||
set(LY_COMPILE_DEFINITIONS PUBLIC PAL_TRAIT_LINUX_WINDOW_MANAGER_WAYLAND)
|
||||
|
||||
elseif(PAL_TRAIT_LINUX_WINDOW_MANAGER STREQUAL "xlib")
|
||||
|
||||
set(LY_COMPILE_DEFINITIONS PUBLIC PAL_TRAIT_LINUX_WINDOW_MANAGER_XLIB)
|
||||
|
||||
else()
|
||||
|
||||
message(FATAL_ERROR, "Linux Window Manager ${PAL_TRAIT_LINUX_WINDOW_MANAGER} is not recognized")
|
||||
|
||||
endif()
|
||||
|
||||
@@ -366,6 +366,24 @@ namespace UnitTest
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, EntitySpawnTicket_Move_Works)
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket1(*m_spawnableAsset);
|
||||
AzFramework::EntitySpawnTicket ticket2(*m_spawnableAsset);
|
||||
|
||||
const AzFramework::EntitySpawnTicket::Id ticket1Id = ticket1.GetId();
|
||||
const AzFramework::EntitySpawnTicket::Id ticket2Id = ticket2.GetId();
|
||||
|
||||
AzFramework::EntitySpawnTicket ticketMoveConstructor(AZStd::move(ticket1));
|
||||
EXPECT_TRUE(ticketMoveConstructor.IsValid());
|
||||
EXPECT_EQ(ticketMoveConstructor.GetId(), ticket1Id);
|
||||
|
||||
AzFramework::EntitySpawnTicket ticketMoveOperator;
|
||||
ticketMoveOperator = AZStd::move(ticket2);
|
||||
EXPECT_TRUE(ticketMoveOperator.IsValid());
|
||||
EXPECT_EQ(ticketMoveOperator.GetId(), ticket2Id);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
{
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace AzNetworking
|
||||
const AZ::TimeMs deltaTimeMs = currentTimeMs - m_lastLoggedTimeMs;
|
||||
|
||||
m_atoms[m_activeAtom].m_bytesTransmitted += byteCount;
|
||||
m_atoms[m_activeAtom].m_packetsSent++;
|
||||
m_atoms[m_activeAtom].m_timeAccumulatorMs += deltaTimeMs;
|
||||
|
||||
if (m_atoms[m_activeAtom].m_timeAccumulatorMs >= m_maxSampleTimeMs)
|
||||
@@ -32,6 +33,11 @@ namespace AzNetworking
|
||||
m_lastLoggedTimeMs = currentTimeMs;
|
||||
}
|
||||
|
||||
void DatarateMetrics::LogPacketLost()
|
||||
{
|
||||
m_atoms[m_activeAtom].m_packetsLost++;
|
||||
}
|
||||
|
||||
float DatarateMetrics::GetBytesPerSecond() const
|
||||
{
|
||||
const uint32_t sampleAtom = 1 - m_activeAtom;
|
||||
@@ -47,6 +53,18 @@ namespace AzNetworking
|
||||
return (bytesLogged * 1000.0f) / sampleTime; // (* 1000) to convert from bytes per millisecond to bytes per second
|
||||
}
|
||||
|
||||
float DatarateMetrics::GetLossRatePercent() const
|
||||
{
|
||||
const uint32_t sampleAtom = 1 - m_activeAtom;
|
||||
|
||||
if (m_atoms[sampleAtom].m_packetsSent == 0)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return float(m_atoms[sampleAtom].m_packetsLost) / float(m_atoms[sampleAtom].m_packetsSent);
|
||||
}
|
||||
|
||||
void ConnectionComputeRtt::LogPacketSent(PacketId packetId, AZ::TimeMs currentTimeMs)
|
||||
{
|
||||
for (uint32_t i = 0; i < MaxTrackableEntries; i++)
|
||||
|
||||
@@ -19,8 +19,10 @@ namespace AzNetworking
|
||||
{
|
||||
DatarateAtom() = default;
|
||||
|
||||
AZ::TimeMs m_timeAccumulatorMs = AZ::TimeMs{ 0 };
|
||||
uint32_t m_bytesTransmitted = 0;
|
||||
AZ::TimeMs m_timeAccumulatorMs = AZ::TimeMs{0};
|
||||
uint32_t m_packetsSent = 0;
|
||||
uint32_t m_packetsLost = 0;
|
||||
};
|
||||
|
||||
//! @class DatarateMetrics
|
||||
@@ -40,19 +42,26 @@ namespace AzNetworking
|
||||
//! @param currentTimeMs current process time in milliseconds
|
||||
void LogPacket(uint32_t byteCount, AZ::TimeMs currentTimeMs);
|
||||
|
||||
//! Invoked whenever a packet has determined to be lost.
|
||||
void LogPacketLost();
|
||||
|
||||
//! Retrieve a sample of the datarate being incurred by this connection in bytes per second.
|
||||
//! @return datarate for traffic sent to or from the connection in bytes per second
|
||||
float GetBytesPerSecond() const;
|
||||
|
||||
//! Returns the estimated packet loss rate as a percentage of packets.
|
||||
//! @return the estimated percentage loss rate
|
||||
float GetLossRatePercent() const;
|
||||
|
||||
private:
|
||||
|
||||
//! Used internally to swap buffers used for metric gathering.
|
||||
void SwapBuffers();
|
||||
|
||||
static constexpr AZ::TimeMs MaxSampleTimeMs = AZ::TimeMs{500};
|
||||
static constexpr AZ::TimeMs MaxSampleTimeMs = AZ::TimeMs{ 2000 };
|
||||
|
||||
AZ::TimeMs m_maxSampleTimeMs = MaxSampleTimeMs;
|
||||
AZ::TimeMs m_lastLoggedTimeMs = MaxSampleTimeMs;
|
||||
AZ::TimeMs m_maxSampleTimeMs = MaxSampleTimeMs;
|
||||
AZ::TimeMs m_lastLoggedTimeMs = MaxSampleTimeMs;
|
||||
uint32_t m_activeAtom = 0;
|
||||
DatarateAtom m_atoms[2];
|
||||
};
|
||||
@@ -69,7 +78,7 @@ namespace AzNetworking
|
||||
ConnectionPacketEntry(PacketId packetId, AZ::TimeMs sendTimeMs);
|
||||
|
||||
PacketId m_packetId = InvalidPacketId;
|
||||
AZ::TimeMs m_sendTimeMs = AZ::TimeMs{0};
|
||||
AZ::TimeMs m_sendTimeMs = AZ::TimeMs{0};
|
||||
};
|
||||
|
||||
//! @class ConnectionComputeRtt
|
||||
@@ -100,8 +109,8 @@ namespace AzNetworking
|
||||
|
||||
private:
|
||||
|
||||
static constexpr uint32_t MaxTrackableEntries = 4;
|
||||
static constexpr float InitialRoundTripTime = 0.1f; //< Start off with a 100 millisecond estimate for Rtt
|
||||
static constexpr uint32_t MaxTrackableEntries = 8;
|
||||
static constexpr float InitialRoundTripTime = 0.1f; //< Start off with a 100 millisecond estimate for Rtt
|
||||
|
||||
float m_roundTripTime = InitialRoundTripTime;
|
||||
ConnectionPacketEntry m_entries[MaxTrackableEntries];
|
||||
@@ -117,6 +126,11 @@ namespace AzNetworking
|
||||
//! Resets all internal metrics to defaults.
|
||||
void Reset();
|
||||
|
||||
void LogPacketSent(uint32_t byteCount, AZ::TimeMs currentTimeMs);
|
||||
void LogPacketRecv(uint32_t byteCount, AZ::TimeMs currentTimeMs);
|
||||
void LogPacketLost();
|
||||
void LogPacketAcked();
|
||||
|
||||
uint32_t m_packetsSent = 0;
|
||||
uint32_t m_packetsRecv = 0;
|
||||
uint32_t m_packetsLost = 0;
|
||||
|
||||
@@ -40,4 +40,33 @@ namespace AzNetworking
|
||||
{
|
||||
*this = ConnectionMetrics();
|
||||
}
|
||||
|
||||
inline void ConnectionMetrics::LogPacketSent(uint32_t byteCount, AZ::TimeMs currentTimeMs)
|
||||
{
|
||||
if (byteCount > 0)
|
||||
{
|
||||
m_packetsSent++;
|
||||
}
|
||||
m_sendDatarate.LogPacket(byteCount, currentTimeMs);
|
||||
}
|
||||
|
||||
inline void ConnectionMetrics::LogPacketRecv(uint32_t byteCount, AZ::TimeMs currentTimeMs)
|
||||
{
|
||||
if (byteCount > 0)
|
||||
{
|
||||
m_packetsRecv++;
|
||||
}
|
||||
m_recvDatarate.LogPacket(byteCount, currentTimeMs);
|
||||
}
|
||||
|
||||
inline void ConnectionMetrics::LogPacketLost()
|
||||
{
|
||||
m_packetsLost++;
|
||||
m_sendDatarate.LogPacketLost();
|
||||
}
|
||||
|
||||
inline void ConnectionMetrics::LogPacketAcked()
|
||||
{
|
||||
m_packetsAcked++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,11 +95,6 @@ namespace AzNetworking
|
||||
//! @return the max transmission unit for this connection
|
||||
virtual uint32_t GetConnectionMtu() const = 0;
|
||||
|
||||
//! Sets connection quality values for testing poor connection conditions.
|
||||
//! Currently unsupported on TcpConnections
|
||||
//! @param connectionQuality simulated connection quality values to use
|
||||
virtual void SetConnectionQuality(const ConnectionQuality& connectionQuality) = 0;
|
||||
|
||||
//! Returns the connection identifier for this connection instance.
|
||||
//! @return the connection identifier for this connection instance
|
||||
ConnectionId GetConnectionId() const;
|
||||
@@ -128,12 +123,23 @@ namespace AzNetworking
|
||||
//! @return reference to the connection metric info
|
||||
ConnectionMetrics& GetMetrics();
|
||||
|
||||
//! Retrieves debug connection quality settings.
|
||||
//! Currently unsupported on TcpConnections
|
||||
//! @return connection quality structure for this connection
|
||||
const ConnectionQuality& GetConnectionQuality() const;
|
||||
|
||||
//! Retrieves debug connection quality settings, non-const.
|
||||
//! Currently unsupported on TcpConnections
|
||||
//! @return connection quality structure for this connection
|
||||
ConnectionQuality& GetConnectionQuality();
|
||||
|
||||
private:
|
||||
|
||||
// The following data members are here in the interface for performance reasons
|
||||
ConnectionId m_connectionId = InvalidConnectionId;
|
||||
IpAddress m_remoteAddress;
|
||||
ConnectionMetrics m_connectionMetrics;
|
||||
ConnectionQuality m_connectionQuality;
|
||||
void* m_userData = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -59,4 +59,14 @@ namespace AzNetworking
|
||||
{
|
||||
return m_connectionMetrics;
|
||||
}
|
||||
|
||||
inline const ConnectionQuality& IConnection::GetConnectionQuality() const
|
||||
{
|
||||
return m_connectionQuality;
|
||||
}
|
||||
|
||||
inline ConnectionQuality& IConnection::GetConnectionQuality()
|
||||
{
|
||||
return m_connectionQuality;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,14 @@ namespace AzNetworking
|
||||
//! @return boolean true on success
|
||||
virtual bool Disconnect(ConnectionId connectionId, DisconnectReason reason) = 0;
|
||||
|
||||
//! Sets whether this connection interface can disconnect by virtue of a timeout
|
||||
//! @param timeoutEnabled If this connection interface will automatically disconnect due to a timeout
|
||||
virtual void SetTimeoutEnabled(bool timeoutEnabled) = 0;
|
||||
|
||||
//! Whether this connection interface will disconnect by virtue of a time out (does not account for cvars affecting all connections)
|
||||
//! @return boolean true if this connection will not disconnect on timeout (does not account for cvars affecting all connections)
|
||||
virtual bool IsTimeoutEnabled() const = 0;
|
||||
|
||||
//! Const access to the metrics tracked by this network interface.
|
||||
//! @return const reference to the metrics tracked by this network interface
|
||||
const NetworkInterfaceMetrics& GetMetrics() const;
|
||||
|
||||
@@ -122,7 +122,7 @@ namespace AzNetworking
|
||||
bool TcpConnection::UpdateRecv()
|
||||
{
|
||||
const AZ::TimeMs startTimeMs = AZ::GetElapsedTimeMs();
|
||||
GetMetrics().m_recvDatarate.LogPacket(0, startTimeMs);
|
||||
GetMetrics().LogPacketRecv(0, startTimeMs);
|
||||
|
||||
// Read new data off the input socket
|
||||
{
|
||||
@@ -261,11 +261,6 @@ namespace AzNetworking
|
||||
return 0; // do nothing, unsupported on TCP connections
|
||||
}
|
||||
|
||||
void TcpConnection::SetConnectionQuality([[maybe_unused]] const ConnectionQuality& connectionQuality)
|
||||
{
|
||||
; // do nothing, unsupported on TCP connections
|
||||
}
|
||||
|
||||
bool TcpConnection::SendPacketInternal(PacketType packetType, TcpPacketEncodingBuffer& payloadBuffer, AZ::TimeMs currentTimeMs)
|
||||
{
|
||||
AZ_Assert(payloadBuffer.GetCapacity() < AZStd::numeric_limits<uint16_t>::max(), "Buffer capacity should be representable using 2 bytes or less");
|
||||
@@ -333,8 +328,7 @@ namespace AzNetworking
|
||||
}
|
||||
|
||||
m_sendRingbuffer.AdvanceWriteBuffer(headerSize + payloadSize);
|
||||
GetMetrics().m_packetsSent++;
|
||||
GetMetrics().m_sendDatarate.LogPacket(headerSize + payloadSize, currentTimeMs);
|
||||
GetMetrics().LogPacketSent(headerSize + payloadSize, currentTimeMs);
|
||||
m_networkInterface.GetMetrics().m_sendPackets++;
|
||||
UpdateSend();
|
||||
return true;
|
||||
@@ -379,8 +373,7 @@ namespace AzNetworking
|
||||
memcpy(dstData, srcData, packetSize);
|
||||
|
||||
m_recvRingbuffer.AdvanceReadBuffer(serializer.GetReadSize() + packetSize);
|
||||
GetMetrics().m_packetsRecv++;
|
||||
GetMetrics().m_recvDatarate.LogPacket(packetSize, currentTimeMs);
|
||||
GetMetrics().LogPacketRecv(packetSize, currentTimeMs);
|
||||
m_networkInterface.GetMetrics().m_recvPackets++;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -102,7 +102,6 @@ namespace AzNetworking
|
||||
bool Disconnect(DisconnectReason reason, TerminationEndpoint endpoint) override;
|
||||
void SetConnectionMtu(uint32_t connectionMtu) override;
|
||||
uint32_t GetConnectionMtu() const override;
|
||||
void SetConnectionQuality(const ConnectionQuality& connectionQuality) override;
|
||||
// @}
|
||||
|
||||
//! Sets the registered socket file descriptor for this TcpConnection in the associated ConnectionSet instance.
|
||||
|
||||
@@ -174,6 +174,16 @@ namespace AzNetworking
|
||||
return connection->Disconnect(reason, TerminationEndpoint::Local);
|
||||
}
|
||||
|
||||
void TcpNetworkInterface::SetTimeoutEnabled(bool timeoutEnabled)
|
||||
{
|
||||
m_timeoutEnabled = timeoutEnabled;
|
||||
}
|
||||
|
||||
bool TcpNetworkInterface::IsTimeoutEnabled() const
|
||||
{
|
||||
return m_timeoutEnabled;
|
||||
}
|
||||
|
||||
void TcpNetworkInterface::QueueNewConnection(const PendingConnection& pendingConnection)
|
||||
{
|
||||
m_pendingConnections.PushBackItem(pendingConnection);
|
||||
@@ -306,7 +316,7 @@ namespace AzNetworking
|
||||
{
|
||||
tcpConnection->SendReliablePacket(CorePackets::HeartbeatPacket());
|
||||
}
|
||||
else if (net_TcpTimeoutConnections)
|
||||
else if (net_TcpTimeoutConnections && m_networkInterface.IsTimeoutEnabled())
|
||||
{
|
||||
tcpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local);
|
||||
return TimeoutResult::Delete;
|
||||
|
||||
@@ -99,6 +99,8 @@ namespace AzNetworking
|
||||
bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) override;
|
||||
bool StopListening() override;
|
||||
bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override;
|
||||
void SetTimeoutEnabled(bool timeoutEnabled) override;
|
||||
bool IsTimeoutEnabled() const override;
|
||||
//! @}
|
||||
|
||||
//! Queues a new incoming connection for this network interface.
|
||||
@@ -154,6 +156,7 @@ namespace AzNetworking
|
||||
AZ::Name m_name;
|
||||
TrustZone m_trustZone;
|
||||
uint16_t m_port = 0;
|
||||
bool m_timeoutEnabled = true;
|
||||
IConnectionListener& m_connectionListener;
|
||||
TcpConnectionSet m_connectionSet;
|
||||
TcpSocketManager m_tcpSocketManager;
|
||||
|
||||
@@ -152,7 +152,7 @@ namespace AzNetworking
|
||||
|
||||
void UdpConnection::ProcessAcked(PacketId packetId, AZ::TimeMs currentTimeMs)
|
||||
{
|
||||
GetMetrics().m_packetsAcked++;
|
||||
GetMetrics().LogPacketAcked();
|
||||
m_reliableQueue.OnPacketAcked(m_networkInterface, *this, packetId);
|
||||
|
||||
// Compute Rtt adjustments
|
||||
@@ -172,8 +172,7 @@ namespace AzNetworking
|
||||
GetMetrics().m_connectionRtt.LogPacketSent(packetId, currentTimeMs);
|
||||
}
|
||||
|
||||
GetMetrics().m_packetsSent++;
|
||||
GetMetrics().m_sendDatarate.LogPacket(packetSize, currentTimeMs);
|
||||
GetMetrics().LogPacketSent(packetSize, currentTimeMs);
|
||||
m_lastSentPacketMs = currentTimeMs;
|
||||
m_unackedPacketCount = 0;
|
||||
}
|
||||
@@ -193,7 +192,7 @@ namespace AzNetworking
|
||||
return PacketTimeoutResult::Acked;
|
||||
|
||||
case PacketAckState::Nacked:
|
||||
GetMetrics().m_packetsLost++;
|
||||
GetMetrics().LogPacketLost();
|
||||
if (reliability == ReliabilityType::Reliable)
|
||||
{
|
||||
m_reliableQueue.OnPacketLost(m_networkInterface, *this, packetId);
|
||||
@@ -224,8 +223,7 @@ namespace AzNetworking
|
||||
return false;
|
||||
}
|
||||
|
||||
GetMetrics().m_packetsRecv++;
|
||||
GetMetrics().m_recvDatarate.LogPacket(packetSize, currentTimeMs);
|
||||
GetMetrics().LogPacketRecv(packetSize, currentTimeMs);
|
||||
|
||||
if (header.GetIsReliable() && !m_reliableQueue.OnPacketReceived(header))
|
||||
{
|
||||
|
||||
@@ -66,13 +66,8 @@ namespace AzNetworking
|
||||
bool Disconnect(DisconnectReason reason, TerminationEndpoint endpoint) override;
|
||||
void SetConnectionMtu(uint32_t connectionMtu) override;
|
||||
uint32_t GetConnectionMtu() const override;
|
||||
void SetConnectionQuality(const ConnectionQuality& connectionQuality) override;
|
||||
// @}
|
||||
|
||||
//! Gets connection quality values for testing poor connection conditions.
|
||||
//! @return connection quality values for this IConnection instance
|
||||
const ConnectionQuality& GetConnectionQuality() const;
|
||||
|
||||
//! Returns a suitable encryption endpoint for this connection type.
|
||||
//! @return reference to the connections encryption endpoint
|
||||
DtlsEndpoint& GetDtlsEndpoint();
|
||||
@@ -146,8 +141,6 @@ namespace AzNetworking
|
||||
UdpFragmentQueue m_fragmentQueue;
|
||||
ConnectionState m_state = ConnectionState::Disconnected;
|
||||
ConnectionRole m_connectionRole = ConnectionRole::Connector;
|
||||
|
||||
ConnectionQuality m_connectionQuality;
|
||||
DtlsEndpoint m_dtlsEndpoint;
|
||||
|
||||
AZ::TimeMs m_lastSentPacketMs;
|
||||
@@ -160,4 +153,3 @@ namespace AzNetworking
|
||||
}
|
||||
|
||||
#include <AzNetworking/UdpTransport/UdpConnection.inl>
|
||||
|
||||
|
||||
@@ -10,16 +10,6 @@
|
||||
|
||||
namespace AzNetworking
|
||||
{
|
||||
inline void UdpConnection::SetConnectionQuality(const ConnectionQuality& connectionQuality)
|
||||
{
|
||||
m_connectionQuality = connectionQuality;
|
||||
}
|
||||
|
||||
inline const ConnectionQuality& UdpConnection::GetConnectionQuality() const
|
||||
{
|
||||
return m_connectionQuality;
|
||||
}
|
||||
|
||||
inline DtlsEndpoint& UdpConnection::GetDtlsEndpoint()
|
||||
{
|
||||
return m_dtlsEndpoint;
|
||||
|
||||
@@ -224,8 +224,7 @@ namespace AzNetworking
|
||||
continue;
|
||||
}
|
||||
|
||||
connection->GetMetrics().m_recvDatarate.LogPacket(packet.m_receivedBytes + UdpPacketHeaderSize, currentTimeMs);
|
||||
connection->GetMetrics().m_packetsRecv++;
|
||||
connection->GetMetrics().LogPacketRecv(packet.m_receivedBytes + UdpPacketHeaderSize, currentTimeMs);
|
||||
|
||||
// Decode the packet flag bitset first since it's always uncompressed
|
||||
UdpPacketHeader header;
|
||||
@@ -398,6 +397,16 @@ namespace AzNetworking
|
||||
return connection->Disconnect(reason, TerminationEndpoint::Local);
|
||||
}
|
||||
|
||||
void UdpNetworkInterface::SetTimeoutEnabled(bool timeoutEnabled)
|
||||
{
|
||||
m_timeoutEnabled = timeoutEnabled;
|
||||
}
|
||||
|
||||
bool UdpNetworkInterface::IsTimeoutEnabled() const
|
||||
{
|
||||
return m_timeoutEnabled;
|
||||
}
|
||||
|
||||
bool UdpNetworkInterface::IsEncrypted() const
|
||||
{
|
||||
return m_socket->IsEncrypted();
|
||||
@@ -730,7 +739,7 @@ namespace AzNetworking
|
||||
{
|
||||
udpConnection->SendUnreliablePacket(CorePackets::HeartbeatPacket());
|
||||
}
|
||||
else if (net_UdpTimeoutConnections)
|
||||
else if (net_UdpTimeoutConnections && m_networkInterface.IsTimeoutEnabled())
|
||||
{
|
||||
udpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local);
|
||||
return TimeoutResult::Delete;
|
||||
|
||||
@@ -104,6 +104,8 @@ namespace AzNetworking
|
||||
bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) override;
|
||||
bool StopListening() override;
|
||||
bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override;
|
||||
void SetTimeoutEnabled(bool timeoutEnabled) override;
|
||||
bool IsTimeoutEnabled() const override;
|
||||
//! @}
|
||||
|
||||
//! Returns true if this is an encrypted socket, false if not.
|
||||
@@ -179,6 +181,7 @@ namespace AzNetworking
|
||||
TrustZone m_trustZone;
|
||||
uint16_t m_port = 0;
|
||||
bool m_allowIncomingConnections = false;
|
||||
bool m_timeoutEnabled = true;
|
||||
IConnectionListener& m_connectionListener;
|
||||
UdpConnectionSet m_connectionSet;
|
||||
TimeoutQueue m_connectionTimeoutQueue;
|
||||
|
||||
@@ -126,7 +126,7 @@ namespace AzNetworking
|
||||
#ifdef ENABLE_LATENCY_DEBUG
|
||||
if (connectionQuality.m_lossPercentage > 0)
|
||||
{
|
||||
if (int32_t(m_random.GetRandom() % 100) < (connectionQuality.m_lossPercentage / 2))
|
||||
if (int32_t(m_random.GetRandom() % 100) < (connectionQuality.m_lossPercentage))
|
||||
{
|
||||
// Pretend we sent, but don't actually send
|
||||
return true;
|
||||
@@ -157,9 +157,11 @@ namespace AzNetworking
|
||||
#ifdef ENABLE_LATENCY_DEBUG
|
||||
else if ((connectionQuality.m_latencyMs > AZ::TimeMs{ 0 }) || (connectionQuality.m_varianceMs > AZ::TimeMs{ 0 }))
|
||||
{
|
||||
const AZ::TimeMs jitterMs = aznumeric_cast<AZ::TimeMs>(m_random.GetRandom()) % (connectionQuality.m_varianceMs / aznumeric_cast<AZ::TimeMs>(2));
|
||||
const AZ::TimeMs jitterMs = aznumeric_cast<AZ::TimeMs>(m_random.GetRandom()) % (connectionQuality.m_varianceMs > AZ::TimeMs{ 0 }
|
||||
? connectionQuality.m_varianceMs
|
||||
: AZ::TimeMs{ 1 });
|
||||
const AZ::TimeMs currTimeMs = AZ::GetElapsedTimeMs();
|
||||
const AZ::TimeMs deferTimeMs = (connectionQuality.m_latencyMs / aznumeric_cast<AZ::TimeMs>(2)) + jitterMs;
|
||||
const AZ::TimeMs deferTimeMs = (connectionQuality.m_latencyMs) + jitterMs;
|
||||
|
||||
DeferredData deferred = DeferredData(address, data, size, encrypt, dtlsEndpoint);
|
||||
AZ::Interface<AZ::IEventScheduler>::Get()->AddCallback([&, deferredData = deferred]
|
||||
|
||||
+42
-2
@@ -240,15 +240,51 @@ namespace AzQtComponents
|
||||
|
||||
// Center within the parent
|
||||
QRect geo = geometry();
|
||||
|
||||
// If the base size of the guest widget is larger than the screen,
|
||||
// then we need to resize it so that it will fit by either using
|
||||
// the minimum size (if one is set), or fallback to the screen size.
|
||||
if (m_guestWidget)
|
||||
{
|
||||
if (auto screen = m_guestWidget->screen())
|
||||
{
|
||||
const QRect screenGeometry = screen->availableGeometry();
|
||||
if (geo.width() > screenGeometry.width())
|
||||
{
|
||||
auto guestMinimumWidth = m_guestWidget->minimumWidth();
|
||||
if (guestMinimumWidth && guestMinimumWidth <= screenGeometry.width())
|
||||
{
|
||||
geo.setWidth(guestMinimumWidth);
|
||||
}
|
||||
else
|
||||
{
|
||||
geo.setWidth(screenGeometry.width());
|
||||
}
|
||||
}
|
||||
if (geo.height() > screenGeometry.height())
|
||||
{
|
||||
auto guestMinimumHeight = m_guestWidget->minimumHeight();
|
||||
if (guestMinimumHeight && guestMinimumHeight <= screenGeometry.height())
|
||||
{
|
||||
geo.setHeight(guestMinimumHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
geo.setHeight(screenGeometry.height());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
geo.moveCenter(parentWindowCenter);
|
||||
|
||||
QWindow *w = topLevelWidget->windowHandle();
|
||||
QWindow* w = topLevelWidget->windowHandle();
|
||||
if (!w)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QScreen *screen = w->screen();
|
||||
QScreen* screen = w->screen();
|
||||
if (!screen)
|
||||
{
|
||||
// defensive, shouldn't happen
|
||||
@@ -661,6 +697,10 @@ namespace AzQtComponents
|
||||
if (!restoreGeometryFromSettings())
|
||||
{
|
||||
show();
|
||||
|
||||
// If we failed to restore from settings (the first time this window is loaded),
|
||||
// then center it on the screen by default
|
||||
centerOnScreen(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,19 +43,20 @@ namespace AzQtComponents
|
||||
{
|
||||
const QChar decimalPoint = locale.decimalPoint();
|
||||
const QChar zeroDigit = locale.zeroDigit();
|
||||
const int numToStringDecimals = AZStd::max(numDecimals, 20);
|
||||
|
||||
// We want to truncate, not round. toString will round, so we add an extra decimal place to the formatting
|
||||
// so we can remove the last value
|
||||
QString retValue = locale.toString(value, 'f', (numDecimals > 0) ? numDecimals + 1 : 0);
|
||||
// We want to truncate, not round. toString will round, so we add extra decimal places to the formatting
|
||||
// so we can remove the last values
|
||||
QString retValue = locale.toString(value, 'f', (numDecimals > 0) ? numToStringDecimals : 0);
|
||||
|
||||
// Handle special cases when we have decimals in our value
|
||||
if (numDecimals > 0)
|
||||
{
|
||||
// Truncate the extra digit now, if it's still there
|
||||
// Truncate the extra digits now, if they're still there
|
||||
int decimalPointIndex = retValue.lastIndexOf(decimalPoint);
|
||||
if ((decimalPointIndex > 0) && (retValue.size() - (decimalPointIndex + 1)) == (numDecimals + 1))
|
||||
if ((decimalPointIndex > 0) && (retValue.size() - (decimalPointIndex + 1)) == numToStringDecimals)
|
||||
{
|
||||
retValue.resize(retValue.size() - 1);
|
||||
retValue.resize(retValue.size() - (numToStringDecimals - numDecimals));
|
||||
}
|
||||
|
||||
// Remove trailing zeros, since the locale conversion won't do
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace Camera
|
||||
using EditorCameraNotificationBus = AZ::EBus<EditorCameraNotifications>;
|
||||
|
||||
/**
|
||||
* This bus is for requesting any camera-view-related changes
|
||||
* This bus is for requesting any camera-view-related changes or information
|
||||
*/
|
||||
class EditorCameraViewRequests : public AZ::ComponentBus
|
||||
{
|
||||
@@ -115,6 +115,11 @@ namespace Camera
|
||||
* Sets this camera as the active view in the scene, otherwise restores the default editor camera if it was already active
|
||||
*/
|
||||
virtual void ToggleCameraAsActiveView() = 0;
|
||||
|
||||
/**
|
||||
* Gets the camera state associated with this view.
|
||||
*/
|
||||
virtual bool GetCameraState(AzFramework::CameraState& cameraState) = 0;
|
||||
};
|
||||
|
||||
using EditorCameraViewRequestBus = AZ::EBus<EditorCameraViewRequests>;
|
||||
|
||||
+2
-1
@@ -35,11 +35,12 @@ namespace AzToolsFramework
|
||||
[[maybe_unused]] AZStd::string_view filename, [[maybe_unused]] const AZStd::vector<AZStd::string_view>& args) {}
|
||||
|
||||
//! executes a Python script as a test
|
||||
virtual void ExecuteByFilenameAsTest(
|
||||
virtual bool ExecuteByFilenameAsTest(
|
||||
[[maybe_unused]] AZStd::string_view filename,
|
||||
[[maybe_unused]] AZStd::string_view testCase,
|
||||
[[maybe_unused]] const AZStd::vector<AZStd::string_view>& args)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
using EditorPythonRunnerRequestBus = AZ::EBus<EditorPythonRunnerRequests>;
|
||||
|
||||
+2
-9
@@ -72,15 +72,8 @@ namespace AzToolsFramework
|
||||
|
||||
if (m_rootInstance != nullptr)
|
||||
{
|
||||
// Need to save off the template id to remove the template after the instance is deleted.
|
||||
Prefab::TemplateId templateId = m_rootInstance->GetTemplateId();
|
||||
m_rootInstance.reset();
|
||||
if (templateId != Prefab::InvalidTemplateId)
|
||||
{
|
||||
// Remove the template here so that if we're in a Deactivate/Activate cycle, it can recreate the template/rootInstance
|
||||
// correctly
|
||||
m_prefabSystemComponent->RemoveTemplate(templateId);
|
||||
}
|
||||
m_prefabSystemComponent->RemoveAllTemplates();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +88,7 @@ namespace AzToolsFramework
|
||||
if (templateId != Prefab::InvalidTemplateId)
|
||||
{
|
||||
m_rootInstance->SetTemplateId(Prefab::InvalidTemplateId);
|
||||
m_prefabSystemComponent->RemoveTemplate(templateId);
|
||||
m_prefabSystemComponent->RemoveAllTemplates();
|
||||
}
|
||||
m_rootInstance->SetContainerEntityName("Level");
|
||||
}
|
||||
|
||||
+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;
|
||||
|
||||
+7
-5
@@ -1951,12 +1951,13 @@ namespace AzToolsFramework
|
||||
return;
|
||||
}
|
||||
|
||||
// If prefabs are enabled, there will be no root slice so bail out here since we don't need
|
||||
// to show any slice options in the menu
|
||||
AZ::SliceComponent* rootSlice = nullptr;
|
||||
AzFramework::SliceEntityOwnershipServiceRequestBus::EventResult(rootSlice, contextId,
|
||||
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::GetRootSlice);
|
||||
if (!rootSlice)
|
||||
{
|
||||
AZ_Error("PropertyEditor", false, "Entity context has no root slice");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2105,10 +2106,6 @@ namespace AzToolsFramework
|
||||
{
|
||||
QMenu* revertMenu = nullptr;
|
||||
|
||||
revertMenu = menu.addMenu(tr("Revert overrides"));
|
||||
revertMenu->setToolTipsVisible(true);
|
||||
revertMenu->setEnabled(false);
|
||||
|
||||
//check for changes on selected property
|
||||
if (componentClassData)
|
||||
{
|
||||
@@ -2128,6 +2125,11 @@ namespace AzToolsFramework
|
||||
return;
|
||||
}
|
||||
|
||||
// Only add the "Revert overrides" menu option if it belongs to a slice
|
||||
revertMenu = menu.addMenu(tr("Revert overrides"));
|
||||
revertMenu->setToolTipsVisible(true);
|
||||
revertMenu->setEnabled(false);
|
||||
|
||||
if (fieldNode)
|
||||
{
|
||||
bool hasChanges = fieldNode->HasChangesVersusComparison(false);
|
||||
|
||||
+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
|
||||
|
||||
+3
-1
@@ -245,7 +245,9 @@ namespace UnitTest
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBeforeUpdate, *firstInstance);
|
||||
|
||||
//remove instance from instance
|
||||
firstInstance->DetachNestedInstance(addedAlias);
|
||||
AZStd::unique_ptr<Instance> detachedInstance = firstInstance->DetachNestedInstance(addedAlias);
|
||||
ASSERT_TRUE(detachedInstance != nullptr);
|
||||
m_prefabSystemComponent->RemoveLink(detachedInstance->GetLinkId());
|
||||
|
||||
//create document with after change snapshot
|
||||
PrefabDom instanceDomAfterUpdate;
|
||||
|
||||
@@ -309,6 +309,7 @@ namespace UnitTest
|
||||
// and use the updated enclosing Instance to update the PrefabDom of Template.
|
||||
AZStd::unique_ptr<Instance> detachedInstance = newEnclosingInstance->DetachNestedInstance(nestedInstanceAliases.front());
|
||||
ASSERT_TRUE(detachedInstance);
|
||||
m_prefabSystemComponent->RemoveLink(detachedInstance->GetLinkId());
|
||||
|
||||
PrefabDom updatedTemplateDom;
|
||||
ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*newEnclosingInstance, updatedTemplateDom));
|
||||
|
||||
@@ -274,6 +274,7 @@ namespace UnitTest
|
||||
InstanceAlias aliasOfWheelInstanceToRetain = wheelInstanceAliasesUnderAxle.front();
|
||||
AZStd::unique_ptr<Instance> detachedInstance = axleInstance->DetachNestedInstance(wheelInstanceAliasesUnderAxle.back());
|
||||
ASSERT_TRUE(detachedInstance);
|
||||
m_prefabSystemComponent->RemoveLink(detachedInstance->GetLinkId());
|
||||
PrefabDom updatedAxleInstanceDom;
|
||||
ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*axleInstance, updatedAxleInstanceDom));
|
||||
m_prefabSystemComponent->UpdatePrefabTemplate(axleTemplateId, updatedAxleInstanceDom);
|
||||
|
||||
@@ -77,6 +77,19 @@ namespace UnitTest
|
||||
m_intSpinBox.reset();
|
||||
}
|
||||
|
||||
QString setupTruncationTest(QString textValue)
|
||||
{
|
||||
QString retval;
|
||||
m_doubleSpinBoxWithLineEdit->setDecimals(7);
|
||||
m_doubleSpinBoxWithLineEdit->setDisplayDecimals(3);
|
||||
m_doubleSpinBoxWithLineEdit->setFocus();
|
||||
m_doubleSpinBoxWithLineEdit->GetLineEdit()->setText(textValue);
|
||||
m_doubleSpinBoxWithLineEdit->clearFocus();
|
||||
|
||||
return m_doubleSpinBoxWithLineEdit->textFromValue(m_doubleSpinBoxWithLineEdit->value());
|
||||
}
|
||||
|
||||
|
||||
AZStd::unique_ptr<QWidget> m_dummyWidget;
|
||||
AZStd::unique_ptr<AzQtComponents::SpinBox> m_intSpinBox;
|
||||
AZStd::unique_ptr<AzQtComponents::DoubleSpinBox> m_doubleSpinBox;
|
||||
@@ -277,4 +290,34 @@ namespace UnitTest
|
||||
// test would result in a crash
|
||||
EXPECT_TRUE(m_intSpinBox.get() == nullptr);
|
||||
}
|
||||
|
||||
TEST_F(SpinBoxFixture, SpinBoxCheckHighValueTruncatesCorrectly)
|
||||
{
|
||||
QString value = setupTruncationTest("0.9999999");
|
||||
|
||||
EXPECT_TRUE(value == "0.999");
|
||||
}
|
||||
|
||||
TEST_F(SpinBoxFixture, SpinBoxCheckLowValueTruncatesCorrectly)
|
||||
{
|
||||
QString value = setupTruncationTest("0.0000001");
|
||||
|
||||
EXPECT_TRUE(value == "0.0");
|
||||
}
|
||||
|
||||
TEST_F(SpinBoxFixture, SpinBoxCheckBugValuesTruncatesCorrectly)
|
||||
{
|
||||
QString value = setupTruncationTest("0.12395");
|
||||
|
||||
EXPECT_TRUE(value == "0.123");
|
||||
|
||||
value = setupTruncationTest("0.94496");
|
||||
|
||||
EXPECT_TRUE(value == "0.944");
|
||||
|
||||
value = setupTruncationTest("0.0009999");
|
||||
|
||||
EXPECT_TRUE(value == "0.0");
|
||||
}
|
||||
|
||||
} // 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,9 +41,9 @@ foreach(project_name project_path IN ZIP_LISTS LY_PROJECTS_TARGET_NAME LY_PROJEC
|
||||
add_custom_target(${project_name}.Assets
|
||||
COMMENT "Processing ${project_name} assets..."
|
||||
COMMAND "${CMAKE_COMMAND}"
|
||||
-DLY_LOCK_FILE=$<TARGET_FILE_DIR:AZ::AssetProcessorBatch>/project_assets.lock
|
||||
-DLY_LOCK_FILE=$<GENEX_EVAL:$<TARGET_FILE_DIR:AZ::AssetProcessorBatch>>/project_assets.lock
|
||||
-P ${LY_ROOT_FOLDER}/cmake/CommandExecution.cmake
|
||||
EXEC_COMMAND $<TARGET_FILE:AZ::AssetProcessorBatch>
|
||||
EXEC_COMMAND $<GENEX_EVAL:$<TARGET_FILE:AZ::AssetProcessorBatch>>
|
||||
--zeroAnalysisMode
|
||||
--project-path=${project_real_path}
|
||||
--platforms=${LY_ASSET_DEPLOY_ASSET_TYPE}
|
||||
|
||||
@@ -3072,6 +3072,7 @@ namespace AssetProcessor
|
||||
|
||||
QElapsedTimer elapsedTimer;
|
||||
elapsedTimer.start();
|
||||
|
||||
for (auto jobIter = m_jobsToProcess.begin(); jobIter != m_jobsToProcess.end();)
|
||||
{
|
||||
JobDetails& job = *jobIter;
|
||||
@@ -3082,7 +3083,7 @@ namespace AssetProcessor
|
||||
jobIter = m_jobsToProcess.erase(jobIter);
|
||||
m_numOfJobsToAnalyze--;
|
||||
|
||||
// Update the remaining job status occasionally
|
||||
// Update the remaining job status occasionally
|
||||
if (elapsedTimer.elapsed() >= MILLISECONDS_BETWEEN_PROCESS_JOBS_STATUS_UPDATE)
|
||||
{
|
||||
Q_EMIT NumRemainingJobsChanged(m_activeFiles.size() + m_filesToExamine.size() + m_numOfJobsToAnalyze);
|
||||
@@ -3102,7 +3103,8 @@ namespace AssetProcessor
|
||||
// Process the first job if no jobs were analyzed.
|
||||
auto jobIter = m_jobsToProcess.begin();
|
||||
JobDetails& job = *jobIter;
|
||||
AZ_Warning(AssetProcessor::DebugChannel, false, " Cyclic job dependency detected. Processing job (%s, %s, %s, %s) to unblock.",
|
||||
AZ_Warning(
|
||||
AssetProcessor::DebugChannel, false, " Cyclic job dependency detected. Processing job (%s, %s, %s, %s) to unblock.",
|
||||
job.m_jobEntry.m_databaseSourceName.toUtf8().data(), job.m_jobEntry.m_jobKey.toUtf8().data(),
|
||||
job.m_jobEntry.m_platformInfo.m_identifier.c_str(), job.m_jobEntry.m_builderGuid.ToString<AZStd::string>().c_str());
|
||||
ProcessJob(job);
|
||||
|
||||
@@ -207,6 +207,11 @@ namespace AssetProcessor
|
||||
//! or a job dependency and we can only resolve these dependencies once all the create jobs are completed.
|
||||
struct JobToProcessEntry
|
||||
{
|
||||
bool operator<(const JobToProcessEntry& other)
|
||||
{
|
||||
return m_sourceFileInfo.m_pathRelativeToScanFolder < other.m_sourceFileInfo.m_pathRelativeToScanFolder;
|
||||
}
|
||||
|
||||
SourceFileInfo m_sourceFileInfo;
|
||||
AZStd::vector<JobDetails> m_jobsToAnalyze;
|
||||
// a vector of pairs of <builder which emitted it, the dependency>
|
||||
|
||||
@@ -244,6 +244,11 @@ namespace AssetProcessor
|
||||
m_jobEntry.m_builderGuid == rhs.m_jobEntry.m_builderGuid);
|
||||
}
|
||||
|
||||
static bool DatabaseSourceLexCompare(const JobDetails& left, const JobDetails& right)
|
||||
{
|
||||
return left.m_jobEntry.m_databaseSourceName <= right.m_jobEntry.m_databaseSourceName;
|
||||
}
|
||||
|
||||
JobDetails() = default;
|
||||
};
|
||||
|
||||
|
||||
@@ -197,10 +197,20 @@ namespace AssetProcessor
|
||||
{
|
||||
return priorityLeft > priorityRight;
|
||||
}
|
||||
|
||||
// Optionally stabilize queue order on the source name.
|
||||
// This is used in automated tests, to allow tests to have a stable
|
||||
// order that jobs with otherwise equal priority run, so tests process
|
||||
// assets in the same order each time they are run.
|
||||
if (m_sortQueueOnDBSourceName)
|
||||
{
|
||||
return leftJob->GetJobEntry().m_databaseSourceName < rightJob->GetJobEntry().m_databaseSourceName;
|
||||
}
|
||||
|
||||
// if we get all the way down here it means we're dealing with two assets which are not
|
||||
// in any compile groups, not a priority platform, not a priority type, priority platform, etc.
|
||||
// we can arrange these any way we want, but must pick at least a stable order.
|
||||
|
||||
return leftJob->GetJobEntry().m_jobRunKey < rightJob->GetJobEntry().m_jobRunKey;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,10 @@ namespace AssetProcessor
|
||||
void AddJobIdEntry(AssetProcessor::RCJob* rcJob);
|
||||
void RemoveJobIdEntry(AssetProcessor::RCJob* rcJob);
|
||||
|
||||
void SetQueueSortOnDBSourceName()
|
||||
{
|
||||
m_sortQueueOnDBSourceName = true;
|
||||
}
|
||||
|
||||
// implement QSortFilteRProxyModel:
|
||||
bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override;
|
||||
@@ -68,6 +72,11 @@ namespace AssetProcessor
|
||||
QSet<QString> m_currentlyConnectedPlatforms;
|
||||
bool m_dirtyNeedsResort = false; // instead of constantly resorting, we resort only when someone wants to pull an element from us
|
||||
|
||||
// By default, jobs with equal priority and escalation sort on the job run key. This flag changes
|
||||
// jobs to sort on the database source name. This is used for testing, to guarantee jobs run in the same
|
||||
// order for those tests each time they are run.
|
||||
bool m_sortQueueOnDBSourceName = false;
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// AssetProcessorPlatformBus::Handler
|
||||
void AssetProcessorPlatformConnected(const AZStd::string platform) override;
|
||||
|
||||
@@ -163,6 +163,11 @@ namespace AssetProcessor
|
||||
return ((!m_RCQueueSortModel.GetNextPendingJob()) && (m_RCJobListModel.jobsInFlight() == 0));
|
||||
}
|
||||
|
||||
void RCController::SetQueueSortOnDBSourceName()
|
||||
{
|
||||
m_RCQueueSortModel.SetQueueSortOnDBSourceName();
|
||||
}
|
||||
|
||||
void RCController::JobSubmitted(JobDetails details)
|
||||
{
|
||||
AssetProcessor::QueueElementID checkFile(details.m_jobEntry.m_databaseSourceName, details.m_jobEntry.m_platformInfo.m_identifier.c_str(), details.m_jobEntry.m_jobKey);
|
||||
|
||||
@@ -54,10 +54,11 @@ namespace AssetProcessor
|
||||
void StartJob(AssetProcessor::RCJob* rcJob);
|
||||
int NumberOfPendingCriticalJobsPerPlatform(QString platform);
|
||||
|
||||
void SetSystemRoot(const QDir& systemRoot);
|
||||
int NumberOfPendingJobsPerPlatform(QString platform);
|
||||
bool IsIdle();
|
||||
bool IsPriorityCopyJob(AssetProcessor::RCJob* rcJob);
|
||||
|
||||
void SetQueueSortOnDBSourceName();
|
||||
|
||||
Q_SIGNALS:
|
||||
void FileCompiled(JobEntry entry, AssetBuilderSDK::ProcessJobResponse response);
|
||||
void FileFailed(JobEntry entry);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user