Merge branch 'development' into atom_cpu_profiler_gem_promotion
Signed-off-by: AMZN-ScottR <24445312+AMZN-ScottR@users.noreply.github.com>
This commit is contained in:
@@ -54,6 +54,30 @@ def get_mesh_node_names(sceneGraph):
|
||||
|
||||
return meshDataList, paths
|
||||
|
||||
def add_material_component(entity_id):
|
||||
# Create an override AZ::Render::EditorMaterialComponent
|
||||
editor_material_component = azlmbr.entity.EntityUtilityBus(
|
||||
azlmbr.bus.Broadcast,
|
||||
"GetOrAddComponentByTypeName",
|
||||
entity_id,
|
||||
"EditorMaterialComponent")
|
||||
|
||||
# this fills out the material asset to a known product AZMaterial asset relative path
|
||||
json_update = json.dumps({
|
||||
"Controller": { "Configuration": { "materials": [
|
||||
{
|
||||
"Key": {},
|
||||
"Value": { "MaterialAsset":{
|
||||
"assetHint": "materials/basic_grey.azmaterial"
|
||||
}}
|
||||
}]
|
||||
}}
|
||||
});
|
||||
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, editor_material_component, json_update)
|
||||
|
||||
if not result:
|
||||
raise RuntimeError("UpdateComponentForEntity for editor_material_component failed")
|
||||
|
||||
def update_manifest(scene):
|
||||
import json
|
||||
import uuid, os
|
||||
@@ -75,6 +99,7 @@ def update_manifest(scene):
|
||||
|
||||
created_entities = []
|
||||
previous_entity_id = azlmbr.entity.InvalidEntityId
|
||||
first_mesh = True
|
||||
|
||||
# Loop every mesh node in the scene
|
||||
for activeMeshIndex in range(len(mesh_name_list)):
|
||||
@@ -112,6 +137,11 @@ def update_manifest(scene):
|
||||
if not result:
|
||||
raise RuntimeError("UpdateComponentForEntity failed for Mesh component")
|
||||
|
||||
# an example of adding a material component to override the default material
|
||||
if previous_entity_id is not None and first_mesh:
|
||||
first_mesh = False
|
||||
add_material_component(entity_id)
|
||||
|
||||
# Get the transform component
|
||||
transform_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", entity_id, "27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0")
|
||||
|
||||
|
||||
+2
-3
@@ -204,7 +204,7 @@ namespace PythonCoverage
|
||||
return coveringModuleOutputNames;
|
||||
}
|
||||
|
||||
void PythonCoverageEditorSystemComponent::OnStartExecuteByFilenameAsTest(AZStd::string_view filename, AZStd::string_view testCase, [[maybe_unused]] const AZStd::vector<AZStd::string_view>& args)
|
||||
void PythonCoverageEditorSystemComponent::OnStartExecuteByFilenameAsTest([[maybe_unused]]AZStd::string_view filename, AZStd::string_view testCase, [[maybe_unused]] const AZStd::vector<AZStd::string_view>& args)
|
||||
{
|
||||
if (m_coverageState == CoverageState::Disabled)
|
||||
{
|
||||
@@ -226,8 +226,7 @@ namespace PythonCoverage
|
||||
return;
|
||||
}
|
||||
|
||||
const AZStd::string scriptName = AZ::IO::Path(filename).Stem().Native();
|
||||
const auto coverageFile = m_coverageDir / AZStd::string::format("%s.pycoverage", scriptName.c_str());
|
||||
const auto coverageFile = m_coverageDir / AZStd::string::format("%.*s.pycoverage", AZ_STRING_ARG(testCase));
|
||||
|
||||
// If this is a different python script we clear the existing entity components and start afresh
|
||||
if (m_coverageFile != coverageFile)
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ def launch_and_validate_results(request, test_directory, editor, editor_script,
|
||||
logger.debug("Running automated test: {}".format(editor_script))
|
||||
editor.args.extend(["--skipWelcomeScreenDialog", "--regset=/Amazon/Settings/EnableSourceControl=false",
|
||||
"--regset=/Amazon/Preferences/EnablePrefabSystem=false", run_python, test_case,
|
||||
f"--pythontestcase={request.node.originalname}", "--runpythonargs", " ".join(cfg_args)])
|
||||
f"--pythontestcase={request.node.name}", "--runpythonargs", " ".join(cfg_args)])
|
||||
if auto_test_mode:
|
||||
editor.args.extend(["--autotest_mode"])
|
||||
if null_renderer:
|
||||
|
||||
+1
-1
@@ -126,7 +126,7 @@ def ForceRegion_LinearDampingForceOnRigidBodies():
|
||||
|
||||
# Constants
|
||||
CLOSE_ENOUGH = 0.001
|
||||
TIME_OUT = 3.0
|
||||
TIME_OUT = 10.0
|
||||
INITIAL_VELOCITY = azmath.Vector3(0.0, 0.0, -10.0)
|
||||
|
||||
# 1) Open level / Enter game mode
|
||||
|
||||
@@ -90,7 +90,7 @@ class TestAutomationBase:
|
||||
editor_starttime = time.time()
|
||||
self.logger.debug("Running automated test")
|
||||
testcase_module_filepath = self._get_testcase_module_filepath(testcase_module)
|
||||
pycmd = ["--runpythontest", testcase_module_filepath, f"-pythontestcase={request.node.originalname}"]
|
||||
pycmd = ["--runpythontest", testcase_module_filepath, f"-pythontestcase={request.node.name}"]
|
||||
if use_null_renderer:
|
||||
pycmd += ["-rhi=null"]
|
||||
if batch_mode:
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
|
||||
|
||||
UI Apps: AutomatedTesting.GameLauncher
|
||||
Launch AutomatedTesting.GameLauncher with Simple level
|
||||
Test should run in both gpu and non gpu
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import psutil
|
||||
|
||||
import ly_test_tools.environment.waiter as waiter
|
||||
import editor_python_test_tools.hydra_test_utils as editor_test_utils
|
||||
from ly_remote_console.remote_console_commands import RemoteConsole as RemoteConsole
|
||||
from ly_remote_console.remote_console_commands import (
|
||||
send_command_and_expect_response as send_command_and_expect_response,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("launcher_platform", ["windows"])
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
@pytest.mark.parametrize("level", ["Simple"])
|
||||
@pytest.mark.SUITE_smoke
|
||||
class TestRemoteConsoleLoadLevelWorks(object):
|
||||
@pytest.fixture
|
||||
def remote_console_instance(self, request):
|
||||
console = RemoteConsole()
|
||||
|
||||
def teardown():
|
||||
if console.connected:
|
||||
console.stop()
|
||||
|
||||
request.addfinalizer(teardown)
|
||||
|
||||
return console
|
||||
|
||||
def test_RemoteConsole_LoadLevel_Works(self, launcher, level, remote_console_instance, launcher_platform):
|
||||
expected_lines = ['Level system is loading "Simple"']
|
||||
|
||||
editor_test_utils.launch_and_validate_results_launcher(launcher, level, remote_console_instance, expected_lines, null_renderer=True)
|
||||
@@ -17,13 +17,6 @@
|
||||
"Gems/PhysicsEntities"
|
||||
]
|
||||
},
|
||||
"PhysXSamples":
|
||||
{
|
||||
"SourcePaths":
|
||||
[
|
||||
"Gems/PhysXSamples"
|
||||
]
|
||||
},
|
||||
"PrimitiveAssets":
|
||||
{
|
||||
"SourcePaths":
|
||||
|
||||
@@ -513,7 +513,7 @@ public:
|
||||
QString m_appRoot;
|
||||
QString m_logFile;
|
||||
QString m_pythonArgs;
|
||||
QString m_pythontTestCase;
|
||||
QString m_pythonTestCase;
|
||||
QString m_execFile;
|
||||
QString m_execLineCmd;
|
||||
|
||||
@@ -562,7 +562,7 @@ public:
|
||||
const std::vector<std::pair<CommandLineStringOption, QString&> > stringOptions = {
|
||||
{{"logfile", "File name of the log file to write out to.", "logfile"}, m_logFile},
|
||||
{{"runpythonargs", "Command-line argument string to pass to the python script if --runpython or --runpythontest was used.", "runpythonargs"}, m_pythonArgs},
|
||||
{{"pythontestcase", "Test case name of python test script if --runpythontest was used.", "pythontestcase"}, m_pythontTestCase},
|
||||
{{"pythontestcase", "Test case name of python test script if --runpythontest was used.", "pythontestcase"}, m_pythonTestCase},
|
||||
{{"exec", "cfg file to run on startup, used for systems like automation", "exec"}, m_execFile},
|
||||
{{"rhi", "Command-line argument to force which rhi to use", "dummyString"}, dummyString },
|
||||
{{"rhi-device-validation", "Command-line argument to configure rhi validation", "dummyString"}, dummyString },
|
||||
@@ -1535,11 +1535,12 @@ void CCryEditApp::RunInitPythonScript(CEditCommandLineInfo& cmdInfo)
|
||||
{
|
||||
// Multiple testcases can be specified them with ';', these should match the files to run
|
||||
AZStd::vector<AZStd::string_view> testcaseList;
|
||||
QByteArray pythonTestCase = cmdInfo.m_pythonTestCase.toUtf8();
|
||||
testcaseList.resize(fileList.size());
|
||||
{
|
||||
int i = 0;
|
||||
AzFramework::StringFunc::TokenizeVisitor(
|
||||
fileStr.constData(),
|
||||
pythonTestCase.constData(),
|
||||
[&i, &testcaseList](AZStd::string_view elem)
|
||||
{
|
||||
testcaseList[i++] = (elem);
|
||||
|
||||
@@ -75,7 +75,10 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial
|
||||
->Field("CaptureCursorLook", &CameraMovementSettings::m_captureCursorLook)
|
||||
->Field("OrbitYawRotationInverted", &CameraMovementSettings::m_orbitYawRotationInverted)
|
||||
->Field("PanInvertedX", &CameraMovementSettings::m_panInvertedX)
|
||||
->Field("PanInvertedY", &CameraMovementSettings::m_panInvertedY);
|
||||
->Field("PanInvertedY", &CameraMovementSettings::m_panInvertedY)
|
||||
->Field("DefaultPositionX", &CameraMovementSettings::m_defaultCameraPositionX)
|
||||
->Field("DefaultPositionY", &CameraMovementSettings::m_defaultCameraPositionY)
|
||||
->Field("DefaultPositionZ", &CameraMovementSettings::m_defaultCameraPositionZ);
|
||||
|
||||
serialize.Class<CameraInputSettings>()
|
||||
->Version(2)
|
||||
@@ -154,7 +157,16 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial
|
||||
"Invert direction of pan in local Y axis")
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_captureCursorLook, "Camera Capture Look Cursor",
|
||||
"Should the cursor be captured (hidden) while performing free look");
|
||||
"Should the cursor be captured (hidden) while performing free look")
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_defaultCameraPositionX, "Default X Camera Position",
|
||||
"Default X Camera Position when a level is opened")
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_defaultCameraPositionY, "Default Y Camera Position",
|
||||
"Default Y Camera Position when a level is opened")
|
||||
->DataElement(
|
||||
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_defaultCameraPositionZ, "Default Z Camera Position",
|
||||
"Default Z Camera Position when a level is opened");
|
||||
|
||||
editContext->Class<CameraInputSettings>("Camera Input Settings", "")
|
||||
->DataElement(
|
||||
@@ -271,6 +283,12 @@ void CEditorPreferencesPage_ViewportCamera::OnApply()
|
||||
SandboxEditor::SetCameraOrbitYawRotationInverted(m_cameraMovementSettings.m_orbitYawRotationInverted);
|
||||
SandboxEditor::SetCameraPanInvertedX(m_cameraMovementSettings.m_panInvertedX);
|
||||
SandboxEditor::SetCameraPanInvertedY(m_cameraMovementSettings.m_panInvertedY);
|
||||
SandboxEditor::SetDefaultCameraEditorPosition(
|
||||
AZ::Vector3(
|
||||
m_cameraMovementSettings.m_defaultCameraPositionX,
|
||||
m_cameraMovementSettings.m_defaultCameraPositionY,
|
||||
m_cameraMovementSettings.m_defaultCameraPositionZ
|
||||
));
|
||||
|
||||
SandboxEditor::SetCameraTranslateForwardChannelId(m_cameraInputSettings.m_translateForwardChannelId);
|
||||
SandboxEditor::SetCameraTranslateBackwardChannelId(m_cameraInputSettings.m_translateBackwardChannelId);
|
||||
@@ -308,6 +326,11 @@ void CEditorPreferencesPage_ViewportCamera::InitializeSettings()
|
||||
m_cameraMovementSettings.m_panInvertedX = SandboxEditor::CameraPanInvertedX();
|
||||
m_cameraMovementSettings.m_panInvertedY = SandboxEditor::CameraPanInvertedY();
|
||||
|
||||
AZ::Vector3 defaultCameraPosition = SandboxEditor::DefaultEditorCameraPosition();
|
||||
m_cameraMovementSettings.m_defaultCameraPositionX = defaultCameraPosition.GetX();
|
||||
m_cameraMovementSettings.m_defaultCameraPositionY = defaultCameraPosition.GetY();
|
||||
m_cameraMovementSettings.m_defaultCameraPositionZ = defaultCameraPosition.GetZ();
|
||||
|
||||
m_cameraInputSettings.m_translateForwardChannelId = SandboxEditor::CameraTranslateForwardChannelId().GetName();
|
||||
m_cameraInputSettings.m_translateBackwardChannelId = SandboxEditor::CameraTranslateBackwardChannelId().GetName();
|
||||
m_cameraInputSettings.m_translateLeftChannelId = SandboxEditor::CameraTranslateLeftChannelId().GetName();
|
||||
|
||||
@@ -57,6 +57,9 @@ private:
|
||||
bool m_orbitYawRotationInverted;
|
||||
bool m_panInvertedX;
|
||||
bool m_panInvertedY;
|
||||
float m_defaultCameraPositionX;
|
||||
float m_defaultCameraPositionY;
|
||||
float m_defaultCameraPositionZ;
|
||||
|
||||
AZ::Crc32 RotateSmoothingVisibility() const
|
||||
{
|
||||
|
||||
@@ -52,6 +52,9 @@ namespace SandboxEditor
|
||||
constexpr AZStd::string_view CameraOrbitDollyIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitDollyId";
|
||||
constexpr AZStd::string_view CameraOrbitPanIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitPanId";
|
||||
constexpr AZStd::string_view CameraFocusIdSetting = "/Amazon/Preferences/Editor/Camera/FocusId";
|
||||
constexpr AZStd::string_view CameraDefaultStartingPositionX = "/Amazon/Preferences/Editor/Camera/DefaultStartingPosition/x";
|
||||
constexpr AZStd::string_view CameraDefaultStartingPositionY = "/Amazon/Preferences/Editor/Camera/DefaultStartingPosition/y";
|
||||
constexpr AZStd::string_view CameraDefaultStartingPositionZ = "/Amazon/Preferences/Editor/Camera/DefaultStartingPosition/z";
|
||||
|
||||
template<typename T>
|
||||
void SetRegistry(const AZStd::string_view setting, T&& value)
|
||||
@@ -111,6 +114,21 @@ namespace SandboxEditor
|
||||
return AZStd::make_unique<EditorViewportSettingsCallbacksImpl>();
|
||||
}
|
||||
|
||||
AZ::Vector3 DefaultEditorCameraPosition()
|
||||
{
|
||||
float xPosition = aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionX, 0.0));
|
||||
float yPosition = aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionY, -10.0));
|
||||
float zPosition = aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionZ, 4.0));
|
||||
return AZ::Vector3(xPosition, yPosition, zPosition);
|
||||
}
|
||||
|
||||
void SetDefaultCameraEditorPosition(const AZ::Vector3 defaultCameraPosition)
|
||||
{
|
||||
SetRegistry(CameraDefaultStartingPositionX, defaultCameraPosition.GetX());
|
||||
SetRegistry(CameraDefaultStartingPositionY, defaultCameraPosition.GetY());
|
||||
SetRegistry(CameraDefaultStartingPositionZ, defaultCameraPosition.GetZ());
|
||||
}
|
||||
|
||||
AZ::u64 MaxItemsShownInAssetBrowserSearch()
|
||||
{
|
||||
return GetRegistry(AssetBrowserMaxItemsShownInSearchSetting, aznumeric_cast<AZ::u64>(50));
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include <AzCore/Settings/SettingsRegistry.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzCore/Math/Vector3.h>
|
||||
#include <AzFramework/Input/Channels/InputChannelId.h>
|
||||
|
||||
namespace SandboxEditor
|
||||
@@ -32,6 +33,9 @@ namespace SandboxEditor
|
||||
//! event will fire when a value in the settings registry (editorpreferences.setreg) is modified.
|
||||
SANDBOX_API AZStd::unique_ptr<EditorViewportSettingsCallbacks> CreateEditorViewportSettingsCallbacks();
|
||||
|
||||
SANDBOX_API AZ::Vector3 DefaultEditorCameraPosition();
|
||||
SANDBOX_API void SetDefaultCameraEditorPosition(AZ::Vector3 defaultCameraPosition);
|
||||
|
||||
SANDBOX_API AZ::u64 MaxItemsShownInAssetBrowserSearch();
|
||||
SANDBOX_API void SetMaxItemsShownInAssetBrowserSearch(AZ::u64 numberOfItemsShown);
|
||||
|
||||
|
||||
@@ -112,7 +112,6 @@ void StartFixedCursorMode(QObject *viewport);
|
||||
|
||||
#define RENDER_MESH_TEST_DISTANCE (0.2f)
|
||||
#define CURSOR_FONT_HEIGHT 8.0f
|
||||
|
||||
namespace AZ::ViewportHelpers
|
||||
{
|
||||
static const char TextCantCreateCameraNoLevel[] = "Cannot create camera when no level is loaded.";
|
||||
@@ -623,16 +622,10 @@ void EditorViewportWidget::OnEditorNotifyEvent(EEditorNotifyEvent event)
|
||||
PopDisableRendering();
|
||||
|
||||
{
|
||||
AZ::Aabb terrainAabb = AZ::Aabb::CreateFromPoint(AZ::Vector3::CreateZero());
|
||||
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(terrainAabb, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb);
|
||||
float sx = terrainAabb.GetXExtent();
|
||||
float sy = terrainAabb.GetYExtent();
|
||||
|
||||
Matrix34 viewTM;
|
||||
viewTM.SetIdentity();
|
||||
// Initial camera will be at middle of the map at the height of 2
|
||||
// meters above the terrain (default terrain height is 32)
|
||||
viewTM.SetTranslation(Vec3(sx * 0.5f, sy * 0.5f, 34.0f));
|
||||
|
||||
viewTM.SetTranslation(Vec3(m_editorViewportSettings.DefaultEditorCameraPosition()));
|
||||
SetViewTM(viewTM);
|
||||
|
||||
UpdateScene();
|
||||
@@ -647,16 +640,10 @@ void EditorViewportWidget::OnEditorNotifyEvent(EEditorNotifyEvent event)
|
||||
PopDisableRendering();
|
||||
|
||||
{
|
||||
AZ::Aabb terrainAabb = AZ::Aabb::CreateFromPoint(AZ::Vector3::CreateZero());
|
||||
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(terrainAabb, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb);
|
||||
float sx = terrainAabb.GetXExtent();
|
||||
float sy = terrainAabb.GetYExtent();
|
||||
|
||||
Matrix34 viewTM;
|
||||
viewTM.SetIdentity();
|
||||
// Initial camera will be at middle of the map at the height of 2
|
||||
// meters above the terrain (default terrain height is 32)
|
||||
viewTM.SetTranslation(Vec3(sx * 0.5f, sy * 0.5f, 34.0f));
|
||||
|
||||
viewTM.SetTranslation(Vec3(m_editorViewportSettings.DefaultEditorCameraPosition()));
|
||||
SetViewTM(viewTM);
|
||||
}
|
||||
break;
|
||||
@@ -1345,10 +1332,6 @@ void EditorViewportWidget::keyPressEvent(QKeyEvent* event)
|
||||
|
||||
void EditorViewportWidget::SetViewTM(const Matrix34& tm)
|
||||
{
|
||||
if (m_viewSourceType == ViewSourceType::None)
|
||||
{
|
||||
m_defaultViewTM = tm;
|
||||
}
|
||||
SetViewTM(tm, false);
|
||||
}
|
||||
|
||||
@@ -1445,6 +1428,10 @@ void EditorViewportWidget::SetViewTM(const Matrix34& camMatrix, bool bMoveOnly)
|
||||
"Please report this as a bug."
|
||||
);
|
||||
}
|
||||
else if (shouldUpdateObject == ShouldUpdateObject::No)
|
||||
{
|
||||
GetCurrentAtomView()->SetCameraTransform(LYTransformToAZMatrix3x4(camMatrix));
|
||||
}
|
||||
|
||||
if (m_pressedKeyState == KeyPressedState::PressedThisFrame)
|
||||
{
|
||||
@@ -2028,6 +2015,9 @@ void EditorViewportWidget::SetDefaultCamera()
|
||||
m_viewSourceType = ViewSourceType::None;
|
||||
GetViewManager()->SetCameraObjectId(GUID_NULL);
|
||||
SetName(m_defaultViewName);
|
||||
|
||||
// Set the default Editor Camera position.
|
||||
m_defaultViewTM.SetTranslation(Vec3(m_editorViewportSettings.DefaultEditorCameraPosition()));
|
||||
SetViewTM(m_defaultViewTM);
|
||||
|
||||
// Synchronize the configured editor viewport FOV to the default camera
|
||||
@@ -2530,6 +2520,11 @@ bool EditorViewportSettings::StickySelectEnabled() const
|
||||
return SandboxEditor::StickySelectEnabled();
|
||||
}
|
||||
|
||||
AZ::Vector3 EditorViewportSettings::DefaultEditorCameraPosition() const
|
||||
{
|
||||
return SandboxEditor::DefaultEditorCameraPosition();
|
||||
}
|
||||
|
||||
AZ_CVAR_EXTERNED(bool, ed_previewGameInFullscreen_once);
|
||||
|
||||
bool EditorViewportWidget::ShouldPreviewFullscreen() const
|
||||
@@ -2641,5 +2636,4 @@ void EditorViewportWidget::StopFullscreenPreview()
|
||||
// Show the main window
|
||||
MainWindow::instance()->show();
|
||||
}
|
||||
|
||||
#include <moc_EditorViewportWidget.cpp>
|
||||
|
||||
@@ -78,6 +78,7 @@ struct EditorViewportSettings : public AzToolsFramework::ViewportInteraction::Vi
|
||||
float ManipulatorLineBoundWidth() const override;
|
||||
float ManipulatorCircleBoundWidth() const override;
|
||||
bool StickySelectEnabled() const override;
|
||||
AZ::Vector3 DefaultEditorCameraPosition() const override;
|
||||
};
|
||||
|
||||
// EditorViewportWidget window
|
||||
|
||||
@@ -192,11 +192,86 @@ namespace AZ
|
||||
};
|
||||
|
||||
|
||||
//! SettingsRegistry notifier handler which is responsible for loading
|
||||
//! the project.json file at the new project path
|
||||
//! if an update to '<BootstrapSettingsRootKey>/project_path' key occurs.
|
||||
struct ProjectPathChangedEventHandler
|
||||
{
|
||||
ProjectPathChangedEventHandler(AZ::SettingsRegistryInterface& registry)
|
||||
: m_registry{ registry }
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
|
||||
{
|
||||
// Update the project settings when the project path is set
|
||||
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
||||
const auto projectPathKey = FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
|
||||
AZ::IO::FixedMaxPath newProjectPath;
|
||||
if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectPathKey, path)
|
||||
&& m_registry.Get(newProjectPath.Native(), projectPathKey) && newProjectPath != m_oldProjectPath)
|
||||
{
|
||||
// Update old Project path before attempting to merge in new Settings Registry values in order to prevent recursive calls
|
||||
m_oldProjectPath = newProjectPath;
|
||||
|
||||
// Merge the project.json file into settings registry under ProjectSettingsRootKey path.
|
||||
AZ::IO::FixedMaxPath projectMetadataFile{ AZ::SettingsRegistryMergeUtils::FindEngineRoot(m_registry) / newProjectPath };
|
||||
projectMetadataFile /= "project.json";
|
||||
m_registry.MergeSettingsFile(projectMetadataFile.Native(),
|
||||
AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey);
|
||||
|
||||
// Update all the runtime file paths based on the new "project_path" value.
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
AZ::IO::FixedMaxPath m_oldProjectPath;
|
||||
AZ::SettingsRegistryInterface& m_registry;
|
||||
};
|
||||
|
||||
//! SettingsRegistry notifier handler which adds the project name as a specialization tag
|
||||
//! to the registry
|
||||
//! if an update to '<ProjectSettingsRootKey>/project_name' key occurs.
|
||||
struct ProjectNameChangedEventHandler
|
||||
{
|
||||
ProjectNameChangedEventHandler(AZ::SettingsRegistryInterface& registry)
|
||||
: m_registry{ registry }
|
||||
{
|
||||
}
|
||||
|
||||
void operator()(AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
|
||||
{
|
||||
// Update the project specialization when the project name is set
|
||||
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
||||
const auto projectNameKey = FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey) + "/project_name";
|
||||
|
||||
FixedValueString newProjectName;
|
||||
if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectNameKey, path)
|
||||
&& m_registry.Get(newProjectName, projectNameKey) && newProjectName != m_oldProjectName)
|
||||
{
|
||||
// Add the project_name as a specialization for loading the build system dependency .setreg files
|
||||
auto newProjectNameSpecialization = FixedValueString::format("%s/%.*s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
|
||||
aznumeric_cast<int>(newProjectName.size()), newProjectName.data());
|
||||
auto oldProjectNameSpecialization = FixedValueString::format("%s/%s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
|
||||
m_oldProjectName.c_str());
|
||||
m_registry.Remove(oldProjectNameSpecialization);
|
||||
m_oldProjectName = newProjectName;
|
||||
m_registry.Set(newProjectNameSpecialization, true);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
AZ::SettingsRegistryInterface::FixedValueString m_oldProjectName;
|
||||
AZ::SettingsRegistryInterface& m_registry;
|
||||
};
|
||||
|
||||
//! SettingsRegistry notifier handler which updates relevant registry settings based
|
||||
//! on an update to '/Amazon/AzCore/Bootstrap/project_path' key.
|
||||
struct UpdateProjectSettingsEventHandler
|
||||
struct UpdateCommandLineEventHandler
|
||||
{
|
||||
UpdateProjectSettingsEventHandler(AZ::SettingsRegistryInterface& registry, AZ::CommandLine& commandLine)
|
||||
UpdateCommandLineEventHandler(AZ::SettingsRegistryInterface& registry, AZ::CommandLine& commandLine)
|
||||
: m_registry{ registry }
|
||||
, m_commandLine{ commandLine }
|
||||
{
|
||||
@@ -204,70 +279,14 @@ namespace AZ
|
||||
|
||||
void operator()(AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
|
||||
{
|
||||
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
||||
// #1 Update the project settings when the project path is set
|
||||
const auto projectPathKey = FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
AZ::IO::FixedMaxPath newProjectPath;
|
||||
if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectPathKey, path)
|
||||
&& m_registry.Get(newProjectPath.Native(), projectPathKey) && newProjectPath != m_oldProjectPath)
|
||||
{
|
||||
UpdateProjectSettingsFromProjectPath(AZ::IO::PathView(newProjectPath));
|
||||
}
|
||||
|
||||
// #2 Update the project specialization when the project name is set
|
||||
const auto projectNameKey = FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey) + "/project_name";
|
||||
FixedValueString newProjectName;
|
||||
if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectNameKey, path)
|
||||
&& m_registry.Get(newProjectName, projectNameKey) && newProjectName != m_oldProjectName)
|
||||
{
|
||||
UpdateProjectSpecializationFromProjectName(newProjectName);
|
||||
}
|
||||
|
||||
// #3 Update the ComponentApplication CommandLine instance when the command line settings are merged into the Settings Registry
|
||||
// Update the ComponentApplication CommandLine instance when the command line settings are merged into the Settings Registry
|
||||
if (path == AZ::SettingsRegistryMergeUtils::CommandLineValueChangedKey)
|
||||
{
|
||||
UpdateCommandLine();
|
||||
AZ::SettingsRegistryMergeUtils::GetCommandLineFromRegistry(m_registry, m_commandLine);
|
||||
}
|
||||
}
|
||||
|
||||
//! Add the project name as a specialization underneath the /Amazon/AzCore/Settings/Specializations path
|
||||
//! and remove the current project name specialization if one exists.
|
||||
void UpdateProjectSpecializationFromProjectName(AZStd::string_view newProjectName)
|
||||
{
|
||||
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
||||
// Add the project_name as a specialization for loading the build system dependency .setreg files
|
||||
auto newProjectNameSpecialization = FixedValueString::format("%s/%.*s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
|
||||
aznumeric_cast<int>(newProjectName.size()), newProjectName.data());
|
||||
auto oldProjectNameSpecialization = FixedValueString::format("%s/%s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
|
||||
m_oldProjectName.c_str());
|
||||
m_registry.Remove(oldProjectNameSpecialization);
|
||||
m_oldProjectName = newProjectName;
|
||||
m_registry.Set(newProjectNameSpecialization, true);
|
||||
}
|
||||
|
||||
void UpdateProjectSettingsFromProjectPath(AZ::IO::PathView newProjectPath)
|
||||
{
|
||||
// Update old Project path before attempting to merge in new Settings Registry values in order to prevent recursive calls
|
||||
m_oldProjectPath = newProjectPath;
|
||||
|
||||
// Merge the project.json file into settings registry under ProjectSettingsRootKey path.
|
||||
AZ::IO::FixedMaxPath projectMetadataFile{ AZ::SettingsRegistryMergeUtils::FindEngineRoot(m_registry) / newProjectPath };
|
||||
projectMetadataFile /= "project.json";
|
||||
m_registry.MergeSettingsFile(projectMetadataFile.Native(),
|
||||
AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey);
|
||||
|
||||
// Update all the runtime file paths based on the new "project_path" value.
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry);
|
||||
}
|
||||
|
||||
void UpdateCommandLine()
|
||||
{
|
||||
AZ::SettingsRegistryMergeUtils::GetCommandLineFromRegistry(m_registry, m_commandLine);
|
||||
}
|
||||
|
||||
private:
|
||||
AZ::IO::FixedMaxPath m_oldProjectPath;
|
||||
AZ::SettingsRegistryInterface::FixedValueString m_oldProjectName;
|
||||
AZ::SettingsRegistryInterface& m_registry;
|
||||
AZ::CommandLine& m_commandLine;
|
||||
};
|
||||
@@ -462,7 +481,12 @@ namespace AZ
|
||||
// 1. The 'project_path' key changes
|
||||
// 2. The project specialization when the 'project-name' key changes
|
||||
// 3. The ComponentApplication command line when the command line is stored to the registry
|
||||
m_projectChangedHandler = m_settingsRegistry->RegisterNotifier(UpdateProjectSettingsEventHandler{ *m_settingsRegistry, m_commandLine });
|
||||
m_projectPathChangedHandler = m_settingsRegistry->RegisterNotifier(ProjectPathChangedEventHandler{
|
||||
*m_settingsRegistry });
|
||||
m_projectNameChangedHandler = m_settingsRegistry->RegisterNotifier(ProjectNameChangedEventHandler{
|
||||
*m_settingsRegistry });
|
||||
m_commandLineUpdatedHandler = m_settingsRegistry->RegisterNotifier(UpdateCommandLineEventHandler{
|
||||
*m_settingsRegistry, m_commandLine });
|
||||
|
||||
// Merge Command Line arguments
|
||||
constexpr bool executeRegDumpCommands = false;
|
||||
@@ -515,11 +539,12 @@ namespace AZ
|
||||
Destroy();
|
||||
}
|
||||
|
||||
// The m_projectChangedHandler stores an AZStd::function internally
|
||||
// which allocates using the AZ SystemAllocator
|
||||
// m_projectChangedHandler is being default value initialized
|
||||
// to clear out the AZStd::function
|
||||
m_projectChangedHandler = {};
|
||||
// The SettingsRegistry Notify handlers stores an AZStd::function internally
|
||||
// which may allocates using the AZ SystemAllocator(if the functor > 16 bytes)
|
||||
// The handlers are being default value initialized to clear out the AZStd::function
|
||||
m_commandLineUpdatedHandler = {};
|
||||
m_projectNameChangedHandler = {};
|
||||
m_projectPathChangedHandler = {};
|
||||
|
||||
// Delete the AZ::IConsole if it was created by this application instance
|
||||
if (m_ownsConsole)
|
||||
|
||||
@@ -390,7 +390,9 @@ namespace AZ
|
||||
AZ::IO::FixedMaxPath m_engineRoot;
|
||||
AZ::IO::FixedMaxPath m_appRoot;
|
||||
|
||||
AZ::SettingsRegistryInterface::NotifyEventHandler m_projectChangedHandler;
|
||||
AZ::SettingsRegistryInterface::NotifyEventHandler m_projectPathChangedHandler;
|
||||
AZ::SettingsRegistryInterface::NotifyEventHandler m_projectNameChangedHandler;
|
||||
AZ::SettingsRegistryInterface::NotifyEventHandler m_commandLineUpdatedHandler;
|
||||
|
||||
// ConsoleFunctorHandle is responsible for unregistering the Settings Registry Console
|
||||
// from the m_console member when it goes out of scope
|
||||
|
||||
@@ -639,9 +639,10 @@ namespace AZ
|
||||
{
|
||||
// Make sure the there is a JSON object at the ConsoleRuntimeCommandKey or ConsoleAutoexecKey
|
||||
// So that JSON Patch is able to add values underneath that object (JSON Patch doesn't create intermediate objects)
|
||||
settingsRegistry.MergeSettings(R"({ "Amazon": { "AzCore": { "Runtime": { "ConsoleCommands": {} } } })"
|
||||
R"(,"O3DE": { "Autoexec": { "ConsoleCommands": {} } } })",
|
||||
SettingsRegistryInterface::Format::JsonMergePatch);
|
||||
settingsRegistry.MergeSettings(R"({})", SettingsRegistryInterface::Format::JsonMergePatch,
|
||||
IConsole::ConsoleRuntimeCommandKey);
|
||||
settingsRegistry.MergeSettings(R"({})", SettingsRegistryInterface::Format::JsonMergePatch,
|
||||
IConsole::ConsoleAutoexecCommandKey);
|
||||
m_consoleCommandKeyHandler = settingsRegistry.RegisterNotifier(ConsoleCommandKeyNotificationHandler{ settingsRegistry, *this });
|
||||
|
||||
JsonApplyPatchSettings applyPatchSettings;
|
||||
|
||||
@@ -192,26 +192,34 @@ namespace AZ
|
||||
//! @param path An offset at which traversal should start.
|
||||
//! @return Whether or not entries could be visited.
|
||||
virtual bool Visit(const VisitorCallback& callback, AZStd::string_view path) const = 0;
|
||||
|
||||
//! Register a callback that will be called whenever an entry gets a new/updated value.
|
||||
//!
|
||||
//! @callback The function to call when an entry gets a new/updated value.
|
||||
[[nodiscard]] virtual NotifyEventHandler RegisterNotifier(const NotifyCallback& callback) = 0;
|
||||
//! Register a callback that will be called whenever an entry gets a new/updated value.
|
||||
//! @callback The function to call when an entry gets a new/updated value.
|
||||
[[nodiscard]] virtual NotifyEventHandler RegisterNotifier(NotifyCallback&& callback) = 0;
|
||||
//! @return NotifyEventHandler instance which must persist to receive event signal
|
||||
[[nodiscard]] virtual NotifyEventHandler RegisterNotifier(NotifyCallback callback) = 0;
|
||||
//! Register a notify event handler with the NotifyEvent.
|
||||
//! The handler will be called whenever an entry gets a new/updated value.
|
||||
//! @param handler The handler to register with the NotifyEvent.
|
||||
virtual void RegisterNotifier(NotifyEventHandler& handler) = 0;
|
||||
|
||||
//! Register a function that will be called before a file is merged.
|
||||
//! @callback The function to call before a file is merged.
|
||||
[[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent(const PreMergeEventCallback& callback) = 0;
|
||||
//! Register a function that will be called before a file is merged.
|
||||
//! @callback The function to call before a file is merged.
|
||||
[[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback&& callback) = 0;
|
||||
//! @param callback The function to call before a file is merged.
|
||||
//! @return PreMergeEventHandler instance which must persist to receive event signal
|
||||
[[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback callback) = 0;
|
||||
//! Register a pre-merge handler with the PreMergeEvent.
|
||||
//! The handler will be called before a file is merged.
|
||||
//! @param handler The hanlder to register with the PreMergeEvent.
|
||||
virtual void RegisterPreMergeEvent(PreMergeEventHandler& handler) = 0;
|
||||
|
||||
//! Register a function that will be called after a file is merged.
|
||||
//! @callback The function to call after a file is merged.
|
||||
[[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent(const PostMergeEventCallback& callback) = 0;
|
||||
//! Register a function that will be called after a file is merged.
|
||||
//! @callback The function to call after a file is merged.
|
||||
[[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback&& callback) = 0;
|
||||
//! @param callback The function to call after a file is merged.
|
||||
//! @return PostMergeEventHandler instance which must persist to receive event signal
|
||||
[[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback callback) = 0;
|
||||
//! Register a post-merge hahndler with the PostMergeEvent.
|
||||
//! The handler will be called after a file is merged.
|
||||
//! @param handler The handler to register with the PostmergeEVent.
|
||||
virtual void RegisterPostMergeEvent(PostMergeEventHandler& hanlder) = 0;
|
||||
|
||||
//! Gets the boolean value at the provided path.
|
||||
//! @param result The target to write the result to.
|
||||
@@ -326,23 +334,25 @@ namespace AZ
|
||||
//! - all digits and dot -> floating point number
|
||||
//! - Everything else is considered a string.
|
||||
//! @param argument The command line argument.
|
||||
//! @param structure which contains functors which determine what characters are delimiters
|
||||
//! @param anchorKey The key where the merged command line argument will be anchored under
|
||||
//! @param commandLineSettings structure which contains functors which determine what characters are delimiters
|
||||
//! @return True if the command line argument could be parsed, otherwise false.
|
||||
virtual bool MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view rootKey = "",
|
||||
virtual bool MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view anchorKey = "",
|
||||
const CommandLineArgumentSettings& commandLineSettings = {}) = 0;
|
||||
//! Merges the json data provided into the settings registry.
|
||||
//! @param data The json data stored in a string.
|
||||
//! @param format The format of the provided data.
|
||||
//! @param anchorKey The key where the merged json content will be anchored under.
|
||||
//! @return True if the data was successfully merged, otherwise false.
|
||||
virtual bool MergeSettings(AZStd::string_view data, Format format) = 0;
|
||||
virtual bool MergeSettings(AZStd::string_view data, Format format, AZStd::string_view anchorKey = "") = 0;
|
||||
//! Loads a settings file and merges it into the registry.
|
||||
//! @param path The path to the registry file.
|
||||
//! @param format The format of the text data in the file at the provided path.
|
||||
//! @param rootKey The key where the root of the settings file will be stored under.
|
||||
//! @param anchorKey The key where the content of the settings file will be anchored.
|
||||
//! @param scratchBuffer An optional buffer that's used to load the file into. Use this when loading multiple patches to
|
||||
//! reduce the number of intermediate memory allocations.
|
||||
//! @return True if the registry file was successfully merged, otherwise false.
|
||||
virtual bool MergeSettingsFile(AZStd::string_view path, Format format, AZStd::string_view rootKey = "",
|
||||
virtual bool MergeSettingsFile(AZStd::string_view path, Format format, AZStd::string_view anchorKey = "",
|
||||
AZStd::vector<char>* scratchBuffer = nullptr) = 0;
|
||||
//! Loads all settings files in a folder and merges them into the registry.
|
||||
//! With the specializations "a" and "b" and platform "c" the files would be loaded in the order:
|
||||
@@ -357,11 +367,12 @@ namespace AZ
|
||||
//! @param platform An optional name of a platform. Platform overloads are located at <path>/Platform/<platform>/
|
||||
//! Files in a platform are applied in the same order as for the main folder but always after the same file
|
||||
//! in the main folder.
|
||||
//! @param anchorKey The registry path location where the settings will be anchored
|
||||
//! @param scratchBuffer An optional buffer that's used to load the file into. Use this when loading multiple patches to
|
||||
//! reduce the number of intermediate memory allocations.
|
||||
//! @return True if the registry folder was successfully merged, otherwise false.
|
||||
virtual bool MergeSettingsFolder(AZStd::string_view path, const Specializations& specializations,
|
||||
AZStd::string_view platform = {}, AZStd::string_view rootKey = "", AZStd::vector<char>* scratchBuffer = nullptr) = 0;
|
||||
AZStd::string_view platform = {}, AZStd::string_view anchorKey = "", AZStd::vector<char>* scratchBuffer = nullptr) = 0;
|
||||
|
||||
//! Stores the settings structure which is used when merging settings to the Settings Registry
|
||||
//! using JSON Merge Patch or JSON Merge Patch.
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include <AzCore/IO/FileReader.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/JSON/error/en.h>
|
||||
#include <AzCore/NativeUI//NativeUIRequests.h>
|
||||
#include <AzCore/NativeUI/NativeUIRequests.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
#include <AzCore/Serialization/Json/StackedString.h>
|
||||
#include <AzCore/Settings/SettingsRegistryImpl.h>
|
||||
@@ -21,6 +21,34 @@
|
||||
#include <AzCore/std/sort.h>
|
||||
#include <AzCore/std/parallel/scoped_lock.h>
|
||||
|
||||
namespace AZ::SettingsRegistryImplInternal
|
||||
{
|
||||
AZ::SettingsRegistryInterface::Type RapidjsonToSettingsRegistryType(const rapidjson::Value& value)
|
||||
{
|
||||
using Type = AZ::SettingsRegistryInterface::Type;
|
||||
switch (value.GetType())
|
||||
{
|
||||
case rapidjson::Type::kNullType:
|
||||
return Type::Null;
|
||||
case rapidjson::Type::kFalseType:
|
||||
return Type::Boolean;
|
||||
case rapidjson::Type::kTrueType:
|
||||
return Type::Boolean;
|
||||
case rapidjson::Type::kObjectType:
|
||||
return Type::Object;
|
||||
case rapidjson::Type::kArrayType:
|
||||
return Type::Array;
|
||||
case rapidjson::Type::kStringType:
|
||||
return Type::String;
|
||||
case rapidjson::Type::kNumberType:
|
||||
return value.IsDouble() ? Type::FloatingPoint :
|
||||
Type::Integer;
|
||||
}
|
||||
|
||||
return Type::NoType;
|
||||
}
|
||||
}
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
template<typename T>
|
||||
@@ -28,7 +56,7 @@ namespace AZ
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
// rapidjson::Pointer assets that the supplied string
|
||||
// rapidjson::Pointer asserts that the supplied string
|
||||
// is not nullptr even if the supplied size is 0
|
||||
// Setting to empty string to prevent assert
|
||||
path = "";
|
||||
@@ -70,7 +98,7 @@ namespace AZ
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
// rapidjson::Pointer assets that the supplied string
|
||||
// rapidjson::Pointer asserts that the supplied string
|
||||
// is not nullptr even if the supplied size is 0
|
||||
// Setting to empty string to prevent assert
|
||||
path = "";
|
||||
@@ -161,7 +189,7 @@ namespace AZ
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
// rapidjson::Pointer assets that the supplied string
|
||||
// rapidjson::Pointer asserts that the supplied string
|
||||
// is not nullptr even if the supplied size is 0
|
||||
// Setting to empty string to prevent assert
|
||||
path = "";
|
||||
@@ -212,17 +240,7 @@ namespace AZ
|
||||
return Visit(visitor, path);
|
||||
}
|
||||
|
||||
auto SettingsRegistryImpl::RegisterNotifier(const NotifyCallback& callback) -> NotifyEventHandler
|
||||
{
|
||||
NotifyEventHandler notifyHandler{ callback };
|
||||
{
|
||||
AZStd::scoped_lock lock(m_notifierMutex);
|
||||
notifyHandler.Connect(m_notifiers);
|
||||
}
|
||||
return notifyHandler;
|
||||
}
|
||||
|
||||
auto SettingsRegistryImpl::RegisterNotifier(NotifyCallback&& callback) -> NotifyEventHandler
|
||||
auto SettingsRegistryImpl::RegisterNotifier(NotifyCallback callback) -> NotifyEventHandler
|
||||
{
|
||||
NotifyEventHandler notifyHandler{ AZStd::move(callback) };
|
||||
{
|
||||
@@ -232,23 +250,19 @@ namespace AZ
|
||||
return notifyHandler;
|
||||
}
|
||||
|
||||
auto SettingsRegistryImpl::RegisterNotifier(NotifyEventHandler& notifyHandler) -> void
|
||||
{
|
||||
AZStd::scoped_lock lock(m_notifierMutex);
|
||||
notifyHandler.Connect(m_notifiers);
|
||||
}
|
||||
|
||||
void SettingsRegistryImpl::ClearNotifiers()
|
||||
{
|
||||
AZStd::scoped_lock lock(m_notifierMutex);
|
||||
m_notifiers.DisconnectAllHandlers();
|
||||
}
|
||||
|
||||
auto SettingsRegistryImpl::RegisterPreMergeEvent(const PreMergeEventCallback& callback) -> PreMergeEventHandler
|
||||
{
|
||||
PreMergeEventHandler preMergeHandler{ callback };
|
||||
{
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
preMergeHandler.Connect(m_preMergeEvent);
|
||||
}
|
||||
return preMergeHandler;
|
||||
}
|
||||
|
||||
auto SettingsRegistryImpl::RegisterPreMergeEvent(PreMergeEventCallback&& callback) -> PreMergeEventHandler
|
||||
auto SettingsRegistryImpl::RegisterPreMergeEvent(PreMergeEventCallback callback) -> PreMergeEventHandler
|
||||
{
|
||||
PreMergeEventHandler preMergeHandler{ AZStd::move(callback) };
|
||||
{
|
||||
@@ -258,17 +272,13 @@ namespace AZ
|
||||
return preMergeHandler;
|
||||
}
|
||||
|
||||
auto SettingsRegistryImpl::RegisterPostMergeEvent(const PostMergeEventCallback& callback) -> PostMergeEventHandler
|
||||
auto SettingsRegistryImpl::RegisterPreMergeEvent(PreMergeEventHandler& preMergeHandler) -> void
|
||||
{
|
||||
PostMergeEventHandler postMergeHandler{ callback };
|
||||
{
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
postMergeHandler.Connect(m_postMergeEvent);
|
||||
}
|
||||
return postMergeHandler;
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
preMergeHandler.Connect(m_preMergeEvent);
|
||||
}
|
||||
|
||||
auto SettingsRegistryImpl::RegisterPostMergeEvent(PostMergeEventCallback&& callback) -> PostMergeEventHandler
|
||||
auto SettingsRegistryImpl::RegisterPostMergeEvent(PostMergeEventCallback callback) -> PostMergeEventHandler
|
||||
{
|
||||
PostMergeEventHandler postMergeHandler{ AZStd::move(callback) };
|
||||
{
|
||||
@@ -278,6 +288,12 @@ namespace AZ
|
||||
return postMergeHandler;
|
||||
}
|
||||
|
||||
auto SettingsRegistryImpl::RegisterPostMergeEvent(PostMergeEventHandler& postMergeHandler) -> void
|
||||
{
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
postMergeHandler.Connect(m_postMergeEvent);
|
||||
}
|
||||
|
||||
void SettingsRegistryImpl::ClearMergeEvents()
|
||||
{
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
@@ -297,7 +313,36 @@ namespace AZ
|
||||
localNotifierEvent = AZStd::move(m_notifiers);
|
||||
}
|
||||
|
||||
localNotifierEvent.Signal(jsonPath, type);
|
||||
// Signal the NotifyEvent for each queued argument
|
||||
decltype(m_signalNotifierQueue) localNotifierQueue;
|
||||
{
|
||||
AZStd::scoped_lock signalLock(m_signalMutex);
|
||||
m_signalNotifierQueue.push_back({ FixedValueString{jsonPath}, type });
|
||||
// If the signal count was 0, then a dispatch is in progress
|
||||
if (m_signalCount++ == 0)
|
||||
{
|
||||
AZStd::swap(localNotifierQueue, m_signalNotifierQueue);
|
||||
}
|
||||
}
|
||||
|
||||
while (!localNotifierQueue.empty())
|
||||
{
|
||||
for (SignalNotifierArgs notifierArgs : localNotifierQueue)
|
||||
{
|
||||
localNotifierEvent.Signal(notifierArgs.m_jsonPath, notifierArgs.m_type);
|
||||
}
|
||||
// Clear the local notifier queue and check if more notifiers have been added
|
||||
localNotifierQueue = {};
|
||||
{
|
||||
AZStd::scoped_lock signalLock(m_signalMutex);
|
||||
AZStd::swap(localNotifierQueue, m_signalNotifierQueue);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
AZStd::scoped_lock signalLock(m_signalMutex);
|
||||
--m_signalCount;
|
||||
}
|
||||
|
||||
{
|
||||
// Swap the local handlers with the current m_notifiers which
|
||||
@@ -314,39 +359,19 @@ namespace AZ
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
//rapidjson::Pointer assets that the supplied string
|
||||
//rapidjson::Pointer asserts that the supplied string
|
||||
// is not nullptr even if the supplied size is 0
|
||||
// Setting to empty string to prevent assert
|
||||
path = "";
|
||||
}
|
||||
|
||||
|
||||
rapidjson::Pointer pointer(path.data(), path.length());
|
||||
if (pointer.IsValid())
|
||||
{
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
const rapidjson::Value* value = pointer.Get(m_settings);
|
||||
if (value)
|
||||
if (const rapidjson::Value* value = pointer.Get(m_settings); value != nullptr)
|
||||
{
|
||||
switch (value->GetType())
|
||||
{
|
||||
case rapidjson::Type::kNullType:
|
||||
return Type::Null;
|
||||
case rapidjson::Type::kFalseType:
|
||||
return Type::Boolean;
|
||||
case rapidjson::Type::kTrueType:
|
||||
return Type::Boolean;
|
||||
case rapidjson::Type::kObjectType:
|
||||
return Type::Object;
|
||||
case rapidjson::Type::kArrayType:
|
||||
return Type::Array;
|
||||
case rapidjson::Type::kStringType:
|
||||
return Type::String;
|
||||
case rapidjson::Type::kNumberType:
|
||||
return
|
||||
value->IsDouble() ? Type::FloatingPoint :
|
||||
Type::Integer;
|
||||
}
|
||||
return SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(*value);
|
||||
}
|
||||
}
|
||||
return Type::NoType;
|
||||
@@ -392,7 +417,7 @@ namespace AZ
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
// rapidjson::Pointer assets that the supplied string
|
||||
// rapidjson::Pointer asserts that the supplied string
|
||||
// is not nullptr even if the supplied size is 0
|
||||
// Setting to empty string to prevent assert
|
||||
path = "";
|
||||
@@ -471,13 +496,12 @@ namespace AZ
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
//rapidjson::Pointer assets that the supplied string
|
||||
// rapidjson::Pointer asserts that the supplied string
|
||||
// is not nullptr even if the supplied size is 0
|
||||
// Setting to empty string to prevent assert
|
||||
path = "";
|
||||
}
|
||||
|
||||
|
||||
rapidjson::Pointer pointer(path.data(), path.length());
|
||||
if (pointer.IsValid())
|
||||
{
|
||||
@@ -486,10 +510,14 @@ namespace AZ
|
||||
value, nullptr, valueTypeID, m_serializationSettings);
|
||||
if (jsonResult.GetProcessing() != JsonSerializationResult::Processing::Halted)
|
||||
{
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
rapidjson::Value& setting = pointer.Create(m_settings, m_settings.GetAllocator());
|
||||
setting = AZStd::move(store);
|
||||
SignalNotifier(path, Type::Object);
|
||||
auto anchorType = Type::NoType;
|
||||
{
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
rapidjson::Value& setting = pointer.Create(m_settings, m_settings.GetAllocator());
|
||||
setting = AZStd::move(store);
|
||||
anchorType = SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(setting);
|
||||
}
|
||||
SignalNotifier(path, anchorType);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -500,7 +528,7 @@ namespace AZ
|
||||
{
|
||||
if (path.empty())
|
||||
{
|
||||
// rapidjson::Pointer assets that the supplied string
|
||||
// rapidjson::Pointer asserts that the supplied string
|
||||
// is not nullptr even if the supplied size is 0
|
||||
// Setting to empty string to prevent assert
|
||||
path = "";
|
||||
@@ -605,7 +633,7 @@ namespace AZ
|
||||
return Set(key, value);
|
||||
}
|
||||
|
||||
bool SettingsRegistryImpl::MergeSettings(AZStd::string_view data, Format format)
|
||||
bool SettingsRegistryImpl::MergeSettings(AZStd::string_view data, Format format, AZStd::string_view anchorKey)
|
||||
{
|
||||
rapidjson::Document jsonPatch;
|
||||
constexpr int flags = rapidjson::kParseStopWhenDoneFlag | rapidjson::kParseCommentsFlag | rapidjson::kParseTrailingCommasFlag;
|
||||
@@ -631,17 +659,43 @@ namespace AZ
|
||||
return false;
|
||||
}
|
||||
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
|
||||
JsonSerializationResult::ResultCode mergeResult =
|
||||
JsonSerialization::ApplyPatch(m_settings, m_settings.GetAllocator(), jsonPatch, mergeApproach);
|
||||
if (mergeResult.GetProcessing() != JsonSerializationResult::Processing::Completed)
|
||||
rapidjson::Pointer anchorPath;
|
||||
if (!anchorKey.empty())
|
||||
{
|
||||
AZ_Error("Settings Registry", false, "Failed to fully merge data into registry.");
|
||||
return false;
|
||||
anchorPath = rapidjson::Pointer(anchorKey.data(), anchorKey.size());
|
||||
if (!anchorPath.IsValid())
|
||||
{
|
||||
rapidjson::Pointer pointer(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/-");
|
||||
AZ_Error("Settings Registry", false, R"(Anchor path "%.*s" is invalid.)", AZ_STRING_ARG(anchorKey));
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
|
||||
.AddMember(rapidjson::StringRef("Error"), rapidjson::StringRef("Invalid anchor key."), m_settings.GetAllocator())
|
||||
.AddMember(rapidjson::StringRef("Path"),
|
||||
rapidjson::Value(anchorKey.data(), aznumeric_caster(anchorKey.size()), m_settings.GetAllocator()),
|
||||
m_settings.GetAllocator());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
SignalNotifier("", Type::Object);
|
||||
auto anchorType = AZ::SettingsRegistryInterface::Type::NoType;
|
||||
{
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
rapidjson::Value& anchorRoot = anchorPath.IsValid() ? anchorPath.Create(m_settings, m_settings.GetAllocator())
|
||||
: m_settings;
|
||||
|
||||
JsonSerializationResult::ResultCode mergeResult =
|
||||
JsonSerialization::ApplyPatch(anchorRoot, m_settings.GetAllocator(), jsonPatch, mergeApproach);
|
||||
if (mergeResult.GetProcessing() != JsonSerializationResult::Processing::Completed)
|
||||
{
|
||||
AZ_Error("Settings Registry", false, "Failed to fully merge data into registry.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// The settings have been successfully merged, query the type at the anchor key
|
||||
anchorType = SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(anchorRoot);
|
||||
}
|
||||
|
||||
SignalNotifier(anchorKey, anchorType);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1225,10 +1279,12 @@ namespace AZ
|
||||
ScopedMergeEvent scopedMergeEvent(m_preMergeEvent, m_postMergeEvent, path, rootKey);
|
||||
|
||||
JsonSerializationResult::ResultCode mergeResult(JsonSerializationResult::Tasks::Merge);
|
||||
auto anchorType = Type::NoType;
|
||||
if (rootKey.empty())
|
||||
{
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
mergeResult = JsonSerialization::ApplyPatch(m_settings, m_settings.GetAllocator(), jsonPatch, mergeApproach, m_applyPatchSettings);
|
||||
anchorType = SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(m_settings);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1238,6 +1294,7 @@ namespace AZ
|
||||
AZStd::scoped_lock lock(m_settingMutex);
|
||||
Value& rootValue = root.Create(m_settings, m_settings.GetAllocator());
|
||||
mergeResult = JsonSerialization::ApplyPatch(rootValue, m_settings.GetAllocator(), jsonPatch, mergeApproach, m_applyPatchSettings);
|
||||
anchorType = SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(rootValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1265,7 +1322,7 @@ namespace AZ
|
||||
pointer.Create(m_settings, m_settings.GetAllocator()).SetString(path, m_settings.GetAllocator());
|
||||
}
|
||||
|
||||
SignalNotifier("", Type::Object);
|
||||
SignalNotifier(rootKey, anchorType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -48,14 +48,14 @@ namespace AZ
|
||||
Type GetType(AZStd::string_view path) const override;
|
||||
bool Visit(Visitor& visitor, AZStd::string_view path) const override;
|
||||
bool Visit(const VisitorCallback& callback, AZStd::string_view path) const override;
|
||||
[[nodiscard]] NotifyEventHandler RegisterNotifier(const NotifyCallback& callback) override;
|
||||
[[nodiscard]] NotifyEventHandler RegisterNotifier(NotifyCallback&& callback) override;
|
||||
[[nodiscard]] NotifyEventHandler RegisterNotifier(NotifyCallback callback) override;
|
||||
void RegisterNotifier(NotifyEventHandler& hanlder) override;
|
||||
void ClearNotifiers();
|
||||
|
||||
[[nodiscard]] PreMergeEventHandler RegisterPreMergeEvent(const PreMergeEventCallback& callback) override;
|
||||
[[nodiscard]] PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback&& callback) override;
|
||||
[[nodiscard]] PostMergeEventHandler RegisterPostMergeEvent(const PostMergeEventCallback& callback) override;
|
||||
[[nodiscard]] PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback&& callback) override;
|
||||
[[nodiscard]] PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback callback) override;
|
||||
void RegisterPreMergeEvent(PreMergeEventHandler& handler) override;
|
||||
[[nodiscard]] PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback callback) override;
|
||||
void RegisterPostMergeEvent(PostMergeEventHandler& handler) override;
|
||||
void ClearMergeEvents();
|
||||
|
||||
bool Get(bool& result, AZStd::string_view path) const override;
|
||||
@@ -76,13 +76,13 @@ namespace AZ
|
||||
|
||||
bool Remove(AZStd::string_view path) override;
|
||||
|
||||
bool MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view rootKey,
|
||||
bool MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view anchorKey,
|
||||
const CommandLineArgumentSettings& commandLineSettings) override;
|
||||
bool MergeSettings(AZStd::string_view data, Format format) override;
|
||||
bool MergeSettingsFile(AZStd::string_view path, Format format, AZStd::string_view rootKey,
|
||||
bool MergeSettings(AZStd::string_view data, Format format, AZStd::string_view anchorKey = "") override;
|
||||
bool MergeSettingsFile(AZStd::string_view path, Format format, AZStd::string_view anchorKey = "",
|
||||
AZStd::vector<char>* scratchBuffer = nullptr) override;
|
||||
bool MergeSettingsFolder(AZStd::string_view path, const Specializations& specializations,
|
||||
AZStd::string_view platform, AZStd::string_view rootKey = "", AZStd::vector<char>* scratchBuffer = nullptr) override;
|
||||
AZStd::string_view platform, AZStd::string_view anchorKey = "", AZStd::vector<char>* scratchBuffer = nullptr) override;
|
||||
|
||||
void SetApplyPatchSettings(const AZ::JsonApplyPatchSettings& applyPatchSettings) override;
|
||||
void GetApplyPatchSettings(AZ::JsonApplyPatchSettings& applyPatchSettings) override;
|
||||
@@ -121,6 +121,19 @@ namespace AZ
|
||||
PreMergeEvent m_preMergeEvent;
|
||||
PostMergeEvent m_postMergeEvent;
|
||||
|
||||
//! NOTE: During SignalNotifier, the registered notify event handlers are moved to a local NotifyEvent
|
||||
//! Therefore setting a value within the registry during signaling will queue future SignalNotifer calls
|
||||
//! These calls will then be invoked after the current signaling has completex
|
||||
//! This is done to avoid deadlock if another thread attempts to access register a notifier or signal one
|
||||
mutable AZStd::mutex m_signalMutex;
|
||||
struct SignalNotifierArgs
|
||||
{
|
||||
FixedValueString m_jsonPath;
|
||||
Type m_type;
|
||||
};
|
||||
AZStd::deque<SignalNotifierArgs> m_signalNotifierQueue;
|
||||
AZStd::atomic_int m_signalCount{};
|
||||
|
||||
rapidjson::Document m_settings;
|
||||
JsonSerializerSettings m_serializationSettings;
|
||||
JsonDeserializerSettings m_deserializationSettings;
|
||||
|
||||
@@ -23,12 +23,12 @@ namespace AZ
|
||||
MOCK_CONST_METHOD1(GetType, Type(AZStd::string_view));
|
||||
MOCK_CONST_METHOD2(Visit, bool(Visitor&, AZStd::string_view));
|
||||
MOCK_CONST_METHOD2(Visit, bool(const VisitorCallback&, AZStd::string_view));
|
||||
MOCK_METHOD1(RegisterNotifier, NotifyEventHandler(const NotifyCallback&));
|
||||
MOCK_METHOD1(RegisterNotifier, NotifyEventHandler(NotifyCallback&&));
|
||||
MOCK_METHOD1(RegisterPreMergeEvent, PreMergeEventHandler(const PreMergeEventCallback&));
|
||||
MOCK_METHOD1(RegisterPreMergeEvent, PreMergeEventHandler(PreMergeEventCallback&&));
|
||||
MOCK_METHOD1(RegisterPostMergeEvent, PostMergeEventHandler(const PostMergeEventCallback&));
|
||||
MOCK_METHOD1(RegisterPostMergeEvent, PostMergeEventHandler(PostMergeEventCallback&&));
|
||||
MOCK_METHOD1(RegisterNotifier, NotifyEventHandler(NotifyCallback));
|
||||
MOCK_METHOD1(RegisterNotifier, void(NotifyEventHandler&));
|
||||
MOCK_METHOD1(RegisterPreMergeEvent, PreMergeEventHandler(PreMergeEventCallback));
|
||||
MOCK_METHOD1(RegisterPreMergeEvent, void(PreMergeEventHandler&));
|
||||
MOCK_METHOD1(RegisterPostMergeEvent, PostMergeEventHandler(PostMergeEventCallback));
|
||||
MOCK_METHOD1(RegisterPostMergeEvent, void(PostMergeEventHandler&));
|
||||
|
||||
MOCK_CONST_METHOD2(Get, bool(bool&, AZStd::string_view));
|
||||
MOCK_CONST_METHOD2(Get, bool(s64&, AZStd::string_view));
|
||||
@@ -49,7 +49,7 @@ namespace AZ
|
||||
MOCK_METHOD1(Remove, bool(AZStd::string_view));
|
||||
|
||||
MOCK_METHOD3(MergeCommandLineArgument, bool(AZStd::string_view, AZStd::string_view, const CommandLineArgumentSettings&));
|
||||
MOCK_METHOD2(MergeSettings, bool(AZStd::string_view, Format));
|
||||
MOCK_METHOD3(MergeSettings, bool(AZStd::string_view, Format, AZStd::string_view));
|
||||
MOCK_METHOD4(MergeSettingsFile, bool(AZStd::string_view, Format, AZStd::string_view, AZStd::vector<char>*));
|
||||
MOCK_METHOD5(
|
||||
MergeSettingsFolder,
|
||||
|
||||
@@ -1299,6 +1299,35 @@ namespace SettingsRegistryTests
|
||||
EXPECT_FALSE(m_registry->MergeCommandLineArgument(" ", {}, {}));
|
||||
}
|
||||
|
||||
//
|
||||
// MergeSettings
|
||||
//
|
||||
TEST_F(SettingsRegistryTest, MergeSettings_MergeJsonWithAnchorKey_StoresSettingsUnderneathKey)
|
||||
{
|
||||
constexpr AZStd::string_view anchorKey = "/Anchor/Root/0";
|
||||
constexpr auto mergeFormat = AZ::SettingsRegistryInterface::Format::JsonMergePatch;
|
||||
EXPECT_TRUE(m_registry->MergeSettings(R"({ "Test": "1" })", mergeFormat, anchorKey));
|
||||
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::Array, m_registry->GetType("/Anchor/Root"));
|
||||
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::Object, m_registry->GetType("/Anchor/Root/0"));
|
||||
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType("/Anchor/Root/0/Test"));
|
||||
}
|
||||
|
||||
TEST_F(SettingsRegistryTest, MergeSettings_NotifierSignals_AtAnchorKeyAndStoresMergeType)
|
||||
{
|
||||
AZStd::string_view anchorKey = "/Anchor/Root";
|
||||
bool callbackInvoked{};
|
||||
auto callback = [anchorKey, &callbackInvoked](AZStd::string_view path, AZ::SettingsRegistryInterface::Type type)
|
||||
{
|
||||
EXPECT_EQ(anchorKey, path);
|
||||
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::Array, type);
|
||||
callbackInvoked = true;
|
||||
};
|
||||
auto testNotifier1 = m_registry->RegisterNotifier(callback);
|
||||
constexpr auto mergeFormat = AZ::SettingsRegistryInterface::Format::JsonMergePatch;
|
||||
EXPECT_TRUE(m_registry->MergeSettings(R"([ "Test" ])", mergeFormat, anchorKey));
|
||||
EXPECT_TRUE(callbackInvoked);
|
||||
}
|
||||
|
||||
//
|
||||
// MergeSettingsFile
|
||||
//
|
||||
@@ -1331,7 +1360,7 @@ namespace SettingsRegistryTests
|
||||
|
||||
auto callback = [this](AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
|
||||
{
|
||||
EXPECT_TRUE(path.empty());
|
||||
EXPECT_EQ("/Path", path);
|
||||
AZ::s64 value = -1;
|
||||
bool result = m_registry->Get(value, "/Path/Test");
|
||||
EXPECT_TRUE(result);
|
||||
|
||||
@@ -1924,13 +1924,11 @@ namespace AZ::IO
|
||||
ArchiveLocationPriority Archive::GetPakPriority() const
|
||||
{
|
||||
int pakPriority = aznumeric_cast<int>(ArchiveVars{}.nPriority);
|
||||
#if defined(AZ_ENABLE_TRACING)
|
||||
if (auto console = AZ::Interface<AZ::IConsole>::Get(); console != nullptr)
|
||||
{
|
||||
AZ::GetValueResult getCvarResult = console->GetCvarValue("sys_PakPriority", pakPriority);
|
||||
[[maybe_unused]] AZ::GetValueResult getCvarResult = console->GetCvarValue("sys_PakPriority", pakPriority);
|
||||
AZ_Error("Archive", getCvarResult == AZ::GetValueResult::Success, "Lookup of 'sys_PakPriority console variable failed with error %s", AZ::GetEnumString(getCvarResult));
|
||||
}
|
||||
#endif
|
||||
return static_cast<ArchiveLocationPriority>(pakPriority);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,22 +30,42 @@ namespace AzFramework
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// OnSessionHealthCheck is fired in health check process
|
||||
// @return The result of all OnSessionHealthCheck
|
||||
// Use this notification to perform any custom health check
|
||||
// @return True if OnSessionHealthCheck succeeds, false otherwise
|
||||
virtual bool OnSessionHealthCheck() = 0;
|
||||
|
||||
// OnCreateSessionBegin is fired at the beginning of session creation
|
||||
// OnCreateSessionBegin is fired at the beginning of session creation process
|
||||
// Use this notification to perform any necessary configuration or initialization before
|
||||
// creating session
|
||||
// @param sessionConfig The properties to describe a session
|
||||
// @return The result of all OnCreateSessionBegin notifications
|
||||
// @return True if OnCreateSessionBegin succeeds, false otherwise
|
||||
virtual bool OnCreateSessionBegin(const SessionConfig& sessionConfig) = 0;
|
||||
|
||||
// OnDestroySessionBegin is fired at the beginning of session termination
|
||||
// @return The result of all OnDestroySessionBegin notifications
|
||||
// OnCreateSessionEnd is fired at the end of session creation process
|
||||
// Use this notification to perform any follow-up operation after session is created and active
|
||||
virtual void OnCreateSessionEnd() = 0;
|
||||
|
||||
// OnDestroySessionBegin is fired at the beginning of session termination process
|
||||
// Use this notification to perform any cleanup operation before destroying session,
|
||||
// like gracefully disconnect players, cleanup data, etc.
|
||||
// @return True if OnDestroySessionBegin succeeds, false otherwise
|
||||
virtual bool OnDestroySessionBegin() = 0;
|
||||
|
||||
// OnUpdateSessionBegin is fired at the beginning of session update
|
||||
// OnDestroySessionEnd is fired at the end of session termination process
|
||||
// Use this notification to perform any follow-up operation after session is destroyed,
|
||||
// like shutdown application process, etc.
|
||||
virtual void OnDestroySessionEnd() = 0;
|
||||
|
||||
// OnUpdateSessionBegin is fired at the beginning of session update process
|
||||
// Use this notification to perform any configuration or initialization to handle
|
||||
// the session settings changing
|
||||
// @param sessionConfig The properties to describe a session
|
||||
// @param updateReason The reason for session update
|
||||
virtual void OnUpdateSessionBegin(const SessionConfig& sessionConfig, const AZStd::string& updateReason) = 0;
|
||||
|
||||
// OnUpdateSessionBegin is fired at the end of session update process
|
||||
// Use this notification to perform any follow-up operations after session is updated
|
||||
virtual void OnUpdateSessionEnd() = 0;
|
||||
};
|
||||
using SessionNotificationBus = AZ::EBus<SessionNotifications>;
|
||||
} // namespace AzFramework
|
||||
|
||||
+2
@@ -46,6 +46,8 @@ namespace AzManipulatorTestFramework
|
||||
virtual void UpdateVisibility() = 0;
|
||||
//! Set if sticky select is enabled or not.
|
||||
virtual void SetStickySelect(bool enabled) = 0;
|
||||
//! Get default Editor Camera Position.
|
||||
virtual AZ::Vector3 DefaultEditorCameraPosition() const = 0;
|
||||
};
|
||||
|
||||
//! This interface is used to simulate the manipulator manager while the manipulators are under test.
|
||||
|
||||
+1
@@ -36,6 +36,7 @@ namespace AzManipulatorTestFramework
|
||||
int GetViewportId() const override;
|
||||
void UpdateVisibility() override;
|
||||
void SetStickySelect(bool enabled) override;
|
||||
AZ::Vector3 DefaultEditorCameraPosition() const override;
|
||||
|
||||
// ViewportInteractionRequestBus overrides ...
|
||||
AzFramework::CameraState GetCameraState() override;
|
||||
|
||||
@@ -120,6 +120,11 @@ namespace AzManipulatorTestFramework
|
||||
m_stickySelect = enabled;
|
||||
}
|
||||
|
||||
AZ::Vector3 ViewportInteraction::DefaultEditorCameraPosition() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void ViewportInteraction::SetGridSize(float size)
|
||||
{
|
||||
m_gridSize = size;
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace AzNetworking
|
||||
: m_delta(delta)
|
||||
, m_dataSerializer(m_delta.GetBufferPtr(), m_delta.GetBufferCapacity())
|
||||
{
|
||||
m_namePrefix.reserve(128);
|
||||
;
|
||||
}
|
||||
|
||||
DeltaSerializerCreate::~DeltaSerializerCreate()
|
||||
@@ -73,7 +73,7 @@ namespace AzNetworking
|
||||
// Delete any left over records that might be hanging around
|
||||
for (auto iter : m_records)
|
||||
{
|
||||
delete iter.second;
|
||||
delete iter;
|
||||
}
|
||||
m_records.clear();
|
||||
}
|
||||
@@ -160,28 +160,13 @@ namespace AzNetworking
|
||||
return SerializeHelper(buffer, bufferCapacity, isString, outSize, name);
|
||||
}
|
||||
|
||||
AZStd::string DeltaSerializerCreate::GetNextObjectName(const char* name)
|
||||
bool DeltaSerializerCreate::BeginObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
|
||||
{
|
||||
AZStd::string objectName = name;
|
||||
objectName += ".";
|
||||
objectName += AZStd::to_string(m_objectCounter);
|
||||
++m_objectCounter;
|
||||
return objectName;
|
||||
}
|
||||
|
||||
bool DeltaSerializerCreate::BeginObject(const char* name, [[maybe_unused]] const char* typeName)
|
||||
{
|
||||
m_nameLengthStack.push_back(m_namePrefix.length());
|
||||
m_namePrefix += GetNextObjectName(name);
|
||||
m_namePrefix += ".";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeltaSerializerCreate::EndObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
|
||||
{
|
||||
const size_t prevLen = m_nameLengthStack.back();
|
||||
m_nameLengthStack.pop_back();
|
||||
m_namePrefix.resize(prevLen);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -205,25 +190,15 @@ namespace AzNetworking
|
||||
{
|
||||
typedef AbstractValue::ValueT<T> ValueType;
|
||||
|
||||
const size_t prevLen = m_namePrefix.length();
|
||||
m_namePrefix += GetNextObjectName(name);
|
||||
|
||||
const AZ::HashValue32 nameHash = AZ::TypeHash32(m_namePrefix.c_str());
|
||||
|
||||
m_namePrefix.resize(prevLen);
|
||||
|
||||
AbstractValue::BaseValue*& baseValue = m_records[nameHash];
|
||||
AbstractValue::BaseValue* baseValue = m_records.size() > m_objectCounter ? m_records[m_objectCounter] : nullptr;
|
||||
++m_objectCounter;
|
||||
|
||||
// If we are in the gather records phase, just save off the value records
|
||||
if (m_gatheringRecords)
|
||||
{
|
||||
if (baseValue != nullptr)
|
||||
{
|
||||
AZ_Assert(false, "Duplicate name encountered in delta serializer. This will cause data to be serialized incorrectly.");
|
||||
return false;
|
||||
}
|
||||
|
||||
AZ_Assert(baseValue == nullptr, "Expected to create a new record but found a pre-existing one at index %d", m_objectCounter - 1);
|
||||
baseValue = new ValueType(value);
|
||||
m_records.push_back(baseValue);
|
||||
}
|
||||
else // If we are not gathering records, then we are comparing them
|
||||
{
|
||||
|
||||
@@ -90,8 +90,6 @@ namespace AzNetworking
|
||||
DeltaSerializerCreate(const DeltaSerializerCreate&) = delete;
|
||||
DeltaSerializerCreate& operator=(const DeltaSerializerCreate&) = delete;
|
||||
|
||||
AZStd::string GetNextObjectName(const char* name);
|
||||
|
||||
template <typename T>
|
||||
bool SerializeHelper(T& value, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name);
|
||||
|
||||
@@ -105,9 +103,7 @@ namespace AzNetworking
|
||||
|
||||
bool m_gatheringRecords = false;
|
||||
uint32_t m_objectCounter = 0;
|
||||
AZStd::string m_namePrefix;
|
||||
AZStd::vector<size_t> m_nameLengthStack;
|
||||
AZStd::unordered_map<AZ::HashValue32, AbstractValue::BaseValue*> m_records;
|
||||
AZStd::vector<AbstractValue::BaseValue*> m_records;
|
||||
NetworkInputSerializer m_dataSerializer;
|
||||
};
|
||||
|
||||
|
||||
@@ -11,4 +11,213 @@
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
struct DeltaDataElement
|
||||
{
|
||||
AzNetworking::PacketId m_packetId = AzNetworking::InvalidPacketId;
|
||||
uint32_t m_id = 0;
|
||||
AZ::TimeMs m_timeMs = AZ::TimeMs{ 0 };
|
||||
float m_blendFactor = 0.f;
|
||||
AZStd::vector<int> m_growVector, m_shrinkVector;
|
||||
|
||||
bool Serialize(AzNetworking::ISerializer& serializer)
|
||||
{
|
||||
if (!serializer.Serialize(m_packetId, "PacketId")
|
||||
|| !serializer.Serialize(m_id, "Id")
|
||||
|| !serializer.Serialize(m_timeMs, "TimeMs")
|
||||
|| !serializer.Serialize(m_blendFactor, "BlendFactor")
|
||||
|| !serializer.Serialize(m_growVector, "GrowVector")
|
||||
|| !serializer.Serialize(m_shrinkVector, "ShrinkVector"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct DeltaDataContainer
|
||||
{
|
||||
AZStd::string m_containerName;
|
||||
AZStd::array<DeltaDataElement, 32> m_container;
|
||||
|
||||
// This logic is modeled after NetworkInputArray serialization in the Multiplayer Gem
|
||||
bool Serialize(AzNetworking::ISerializer& serializer)
|
||||
{
|
||||
// Always serialize the full first element
|
||||
if(!m_container[0].Serialize(serializer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32_t i = 1; i < m_container.size(); ++i)
|
||||
{
|
||||
if (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject)
|
||||
{
|
||||
AzNetworking::SerializerDelta deltaSerializer;
|
||||
// Read out the delta
|
||||
if (!deltaSerializer.Serialize(serializer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start with previous value
|
||||
m_container[i] = m_container[i - 1];
|
||||
// Then apply delta
|
||||
AzNetworking::DeltaSerializerApply applySerializer(deltaSerializer);
|
||||
if (!applySerializer.ApplyDelta(m_container[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AzNetworking::SerializerDelta deltaSerializer;
|
||||
// Create the delta
|
||||
AzNetworking::DeltaSerializerCreate createSerializer(deltaSerializer);
|
||||
if (!createSerializer.CreateDelta(m_container[i - 1], m_container[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Then write out the delta
|
||||
if (!deltaSerializer.Serialize(serializer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This logic is modeled after NetworkInputArray serialization in the Multiplayer Gem
|
||||
bool SerializeNoDelta(AzNetworking::ISerializer& serializer)
|
||||
{
|
||||
for (uint32_t i = 0; i < m_container.size(); ++i)
|
||||
{
|
||||
if(!m_container[i].Serialize(serializer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class DeltaSerializerTests
|
||||
: public UnitTest::AllocatorsTestFixture
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
UnitTest::AllocatorsTestFixture::SetUp();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
UnitTest::AllocatorsTestFixture::TearDown();
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr float BLEND_FACTOR_SCALE = 1.1f;
|
||||
static constexpr uint32_t TIME_SCALE = 10;
|
||||
|
||||
DeltaDataContainer TestDeltaContainer()
|
||||
{
|
||||
DeltaDataContainer testContainer;
|
||||
AZStd::vector<int> growVector, shrinkVector;
|
||||
shrinkVector.resize(testContainer.m_container.array_size);
|
||||
|
||||
testContainer.m_containerName = "TestContainer";
|
||||
for (int i = 0; i < testContainer.m_container.array_size; ++i)
|
||||
{
|
||||
testContainer.m_container[i].m_packetId = AzNetworking::PacketId(i);
|
||||
testContainer.m_container[i].m_id = i;
|
||||
testContainer.m_container[i].m_timeMs = AZ::TimeMs(i * TIME_SCALE);
|
||||
testContainer.m_container[i].m_blendFactor = BLEND_FACTOR_SCALE * i;
|
||||
growVector.push_back(i);
|
||||
testContainer.m_container[i].m_growVector = growVector;
|
||||
shrinkVector.resize(testContainer.m_container.array_size - i);
|
||||
testContainer.m_container[i].m_shrinkVector = shrinkVector;
|
||||
}
|
||||
|
||||
return testContainer;
|
||||
}
|
||||
|
||||
TEST_F(DeltaSerializerTests, DeltaArray)
|
||||
{
|
||||
DeltaDataContainer inContainer = TestDeltaContainer();
|
||||
AZStd::array<uint8_t, 2048> buffer;
|
||||
AzNetworking::NetworkInputSerializer inSerializer(buffer.data(), static_cast<uint32_t>(buffer.size()));
|
||||
|
||||
// Always serialize the full first element
|
||||
EXPECT_TRUE(inContainer.Serialize(inSerializer));
|
||||
|
||||
DeltaDataContainer outContainer;
|
||||
AzNetworking::NetworkOutputSerializer outSerializer(buffer.data(), static_cast<uint32_t>(buffer.size()));
|
||||
|
||||
EXPECT_TRUE(outContainer.Serialize(outSerializer));
|
||||
|
||||
for (uint32_t i = 0; i > outContainer.m_container.size(); ++i)
|
||||
{
|
||||
EXPECT_EQ(inContainer.m_container[i].m_blendFactor, outContainer.m_container[i].m_blendFactor);
|
||||
EXPECT_EQ(inContainer.m_container[i].m_id, outContainer.m_container[i].m_id);
|
||||
EXPECT_EQ(inContainer.m_container[i].m_packetId, outContainer.m_container[i].m_packetId);
|
||||
EXPECT_EQ(inContainer.m_container[i].m_timeMs, outContainer.m_container[i].m_timeMs);
|
||||
EXPECT_EQ(inContainer.m_container[i].m_growVector[i], outContainer.m_container[i].m_growVector[i]);
|
||||
EXPECT_EQ(inContainer.m_container[i].m_growVector.size(), outContainer.m_container[i].m_growVector.size());
|
||||
EXPECT_EQ(inContainer.m_container[i].m_shrinkVector.size(), outContainer.m_container[i].m_shrinkVector.size());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DeltaSerializerTests, DeltaSerializerCreateUnused)
|
||||
{
|
||||
// Every function here should return a constant value regardless of inputs
|
||||
AzNetworking::SerializerDelta deltaSerializer;
|
||||
AzNetworking::DeltaSerializerCreate createSerializer(deltaSerializer);
|
||||
|
||||
EXPECT_EQ(createSerializer.GetCapacity(), 0);
|
||||
EXPECT_EQ(createSerializer.GetSize(), 0);
|
||||
EXPECT_EQ(createSerializer.GetBuffer(), nullptr);
|
||||
EXPECT_EQ(createSerializer.GetSerializerMode(), AzNetworking::SerializerMode::ReadFromObject);
|
||||
|
||||
createSerializer.ClearTrackedChangesFlag(); //NO-OP
|
||||
EXPECT_FALSE(createSerializer.GetTrackedChangesFlag());
|
||||
EXPECT_TRUE(createSerializer.BeginObject("CreateSerializer", "Begin"));
|
||||
EXPECT_TRUE(createSerializer.EndObject("CreateSerializer", "End"));
|
||||
}
|
||||
|
||||
TEST_F(DeltaSerializerTests, DeltaArraySize)
|
||||
{
|
||||
DeltaDataContainer deltaContainer = TestDeltaContainer();
|
||||
DeltaDataContainer noDeltaContainer = TestDeltaContainer();
|
||||
|
||||
AZStd::array<uint8_t, 2048> deltaBuffer;
|
||||
AzNetworking::NetworkInputSerializer deltaSerializer(deltaBuffer.data(), static_cast<uint32_t>(deltaBuffer.size()));
|
||||
AZStd::array<uint8_t, 2048> noDeltaBuffer;
|
||||
AzNetworking::NetworkInputSerializer noDeltaSerializer(noDeltaBuffer.data(), static_cast<uint32_t>(noDeltaBuffer.size()));
|
||||
|
||||
EXPECT_TRUE(deltaContainer.Serialize(deltaSerializer));
|
||||
EXPECT_FALSE(noDeltaContainer.SerializeNoDelta(noDeltaSerializer)); // Should run out of space
|
||||
EXPECT_EQ(noDeltaSerializer.GetCapacity(), noDeltaSerializer.GetSize()); // Verify that the serializer filled up
|
||||
EXPECT_FALSE(noDeltaSerializer.IsValid()); // and that it is no longer valid due to lack of space
|
||||
}
|
||||
|
||||
TEST_F(DeltaSerializerTests, DeltaSerializerApplyUnused)
|
||||
{
|
||||
// Every function here should return a constant value regardless of inputs
|
||||
AzNetworking::SerializerDelta deltaSerializer;
|
||||
AzNetworking::DeltaSerializerApply applySerializer(deltaSerializer);
|
||||
|
||||
EXPECT_EQ(applySerializer.GetCapacity(), 0);
|
||||
EXPECT_EQ(applySerializer.GetSize(), 0);
|
||||
EXPECT_EQ(applySerializer.GetBuffer(), nullptr);
|
||||
EXPECT_EQ(applySerializer.GetSerializerMode(), AzNetworking::SerializerMode::WriteToObject);
|
||||
|
||||
applySerializer.ClearTrackedChangesFlag(); //NO-OP
|
||||
EXPECT_FALSE(applySerializer.GetTrackedChangesFlag());
|
||||
EXPECT_TRUE(applySerializer.BeginObject("CreateSerializer", "Begin"));
|
||||
EXPECT_TRUE(applySerializer.EndObject("CreateSerializer", "End"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,22 +381,13 @@ namespace AzToolsFramework
|
||||
return;
|
||||
}
|
||||
|
||||
bool isPrefabSystemEnabled = false;
|
||||
AzFramework::ApplicationRequests::Bus::BroadcastResult(
|
||||
isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
|
||||
|
||||
// For slices, orphan any children that remain attached to the entity
|
||||
// For prefabs, this is an unneeded operation because the prefab system handles the orphans
|
||||
// and the extra reparenting operation can be problematic for consumers subscribed to entity
|
||||
// events, such as the entity outliner.
|
||||
if (!isPrefabSystemEnabled)
|
||||
// Even though these child entities will immediately be destroyed, their entity info may be recycled
|
||||
// Ensure they don't have any lingering inaccurate parent data
|
||||
auto children = entityInfo.GetChildren();
|
||||
for (auto childId : children)
|
||||
{
|
||||
auto children = entityInfo.GetChildren();
|
||||
for (auto childId : children)
|
||||
{
|
||||
ReparentChild(childId, AZ::EntityId(), entityId);
|
||||
m_entityOrphanTable[entityId].insert(childId);
|
||||
}
|
||||
ReparentChild(childId, AZ::EntityId(), entityId);
|
||||
m_entityOrphanTable[entityId].insert(childId);
|
||||
}
|
||||
|
||||
m_savedOrderInfo[entityId] = AZStd::make_pair(entityInfo.GetParent(), entityInfo.GetIndexForSorting());
|
||||
@@ -1200,26 +1191,41 @@ namespace AzToolsFramework
|
||||
auto childItr = m_childIndexCache.find(childId);
|
||||
if (childItr == m_childIndexCache.end())
|
||||
{
|
||||
//cache indices for faster lookup
|
||||
m_childIndexCache[childId] = static_cast<AZ::u64>(m_children.size());
|
||||
m_children.push_back(childId);
|
||||
// m_children is guaranteed to be ordered by EntityId, do a sorted insertion
|
||||
auto insertedChildIndex = AZStd::upper_bound(m_children.begin(), m_children.end(), childId);
|
||||
insertedChildIndex = m_children.insert(insertedChildIndex, childId);
|
||||
|
||||
// Cache all affected child indices for fast lookup
|
||||
for (auto it = insertedChildIndex; it != m_children.end(); ++it)
|
||||
{
|
||||
const AZ::u64 newChildIndex = static_cast<AZ::u64>(it - m_children.begin());
|
||||
m_childIndexCache[*it] = newChildIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorEntityModel::EditorEntityModelEntry::RemoveChild(AZ::EntityId childId)
|
||||
{
|
||||
auto childItr = m_childIndexCache.find(childId);
|
||||
if (childItr != m_childIndexCache.end())
|
||||
// Retrieve our child index from the cache
|
||||
auto cachedIndexItr = m_childIndexCache.find(childId);
|
||||
if (cachedIndexItr == m_childIndexCache.end())
|
||||
{
|
||||
// Take the last entry and move it into the removed spot instead of deleting the entry and having to move all
|
||||
// following entries one step down.
|
||||
AZ::EntityId backEntity = m_children.back();
|
||||
m_children[childItr->second] = backEntity;
|
||||
// Update cached index for the moved id to the new index.
|
||||
m_childIndexCache[backEntity] = childItr->second;
|
||||
// Now remove the deleted id from the children and cache.
|
||||
m_childIndexCache.erase(childId);
|
||||
m_children.erase(m_children.end() - 1);
|
||||
AZ_Assert(false, "Attempted to remove an unknown child");
|
||||
return;
|
||||
}
|
||||
|
||||
// Build an iterator for m_children based on our cached index
|
||||
auto childItr = m_children.begin() + cachedIndexItr->second;
|
||||
|
||||
// Remove our child from the cache
|
||||
m_childIndexCache.erase(cachedIndexItr);
|
||||
|
||||
// Remove our child, fix up the cache entries for any subsequent children
|
||||
auto elementsToFixItr = m_children.erase(childItr);
|
||||
for (auto it = elementsToFixItr; it != m_children.end(); ++it)
|
||||
{
|
||||
const AZ::u64 newChildIndex = static_cast<AZ::u64>(it - m_children.begin());
|
||||
m_childIndexCache[*it] = newChildIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1256,8 +1262,17 @@ namespace AzToolsFramework
|
||||
|
||||
AZ::u64 EditorEntityModel::EditorEntityModelEntry::GetChildIndex(AZ::EntityId childId) const
|
||||
{
|
||||
// Return the cached index, if available.
|
||||
auto childItr = m_childIndexCache.find(childId);
|
||||
return childItr != m_childIndexCache.end() ? childItr->second : static_cast<AZ::u64>(m_children.size());
|
||||
if (childItr != m_childIndexCache.end())
|
||||
{
|
||||
return childItr->second;
|
||||
}
|
||||
|
||||
// On initialization, GetChildIndex may be queried for a childId that is not yet in the child list.
|
||||
// Return the position it would be inserted at in EditorEntityModelEntry::AddChild
|
||||
auto targetChildPositionItr = AZStd::upper_bound(m_children.begin(), m_children.end(), childId);
|
||||
return static_cast<AZ::u64>(targetChildPositionItr - m_children.begin());
|
||||
}
|
||||
|
||||
AZStd::string EditorEntityModel::EditorEntityModelEntry::GetName() const
|
||||
|
||||
+26
-29
@@ -119,6 +119,12 @@ namespace AzToolsFramework
|
||||
|
||||
int EntityOutlinerListModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
// For QTreeView models, non-0 columns shouldn't have children
|
||||
if (parent.isValid() && parent.column() != 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto parentId = GetEntityFromIndex(parent);
|
||||
|
||||
AZStd::size_t childCount = 0;
|
||||
@@ -133,17 +139,13 @@ namespace AzToolsFramework
|
||||
|
||||
QModelIndex EntityOutlinerListModel::index(int row, int column, const QModelIndex& parent) const
|
||||
{
|
||||
// sanity check
|
||||
if (!hasIndex(row, column, parent) || (parent.isValid() && parent.column() != 0) || (row < 0 || row >= rowCount(parent)))
|
||||
{
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
auto parentId = GetEntityFromIndex(parent);
|
||||
|
||||
// We have the row and column, so we just need the child ID to construct our index
|
||||
AZ::EntityId childId;
|
||||
EditorEntityInfoRequestBus::EventResult(childId, parentId, &EditorEntityInfoRequestBus::Events::GetChild, row);
|
||||
return GetIndexFromEntity(childId, column);
|
||||
AZ_Assert(childId.IsValid(), "No child found for parent");
|
||||
return createIndex(row, column, static_cast<AZ::u64>(childId));
|
||||
}
|
||||
|
||||
QVariant EntityOutlinerListModel::data(const QModelIndex& index, int role) const
|
||||
@@ -517,13 +519,18 @@ namespace AzToolsFramework
|
||||
{
|
||||
AZ::EntityId parentId;
|
||||
EditorEntityInfoRequestBus::EventResult(parentId, id, &EditorEntityInfoRequestBus::Events::GetParent);
|
||||
return GetIndexFromEntity(parentId, index.column());
|
||||
return GetIndexFromEntity(parentId, 0);
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
Qt::ItemFlags EntityOutlinerListModel::flags(const QModelIndex& index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
{
|
||||
return Qt::ItemIsDropEnabled;
|
||||
}
|
||||
|
||||
Qt::ItemFlags itemFlags = QAbstractItemModel::flags(index);
|
||||
switch (index.column())
|
||||
{
|
||||
@@ -1208,6 +1215,10 @@ namespace AzToolsFramework
|
||||
void EntityOutlinerListModel::ProcessEntityUpdates()
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(Editor);
|
||||
if (!m_entityChangeQueued)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_entityChangeQueued = false;
|
||||
if (m_layoutResetQueued)
|
||||
{
|
||||
@@ -1236,31 +1247,14 @@ namespace AzToolsFramework
|
||||
{
|
||||
AZ_PROFILE_SCOPE(Editor, "EntityOutlinerListModel::ProcessEntityUpdates:ChangeQueue");
|
||||
|
||||
// its faster to just do a bulk data change than to carefully pick out indices
|
||||
// so we'll just merge all ranges into a single range rather than try to make gaps
|
||||
QModelIndex firstChangeIndex;
|
||||
QModelIndex lastChangeIndex;
|
||||
|
||||
for (auto entityId : m_entityChangeQueue)
|
||||
{
|
||||
auto myIndex = GetIndexFromEntity(entityId, ColumnName);
|
||||
if ((!firstChangeIndex.isValid())||(firstChangeIndex.row() > myIndex.row()))
|
||||
if (entityId.IsValid())
|
||||
{
|
||||
firstChangeIndex = myIndex;
|
||||
const QModelIndex beginIndex = GetIndexFromEntity(entityId, ColumnName);
|
||||
const QModelIndex endIndex = createIndex(beginIndex.row(), VisibleColumnCount - 1, beginIndex.internalId());
|
||||
emit dataChanged(beginIndex, endIndex);
|
||||
}
|
||||
|
||||
if ((!lastChangeIndex.isValid())||(lastChangeIndex.row() < myIndex.row()))
|
||||
{
|
||||
// expand it to be the last column:
|
||||
lastChangeIndex = myIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstChangeIndex.isValid())
|
||||
{
|
||||
// expand to cover all visible columns:
|
||||
lastChangeIndex = createIndex(lastChangeIndex.row(), VisibleColumnCount - 1, lastChangeIndex.internalPointer());
|
||||
emit dataChanged(firstChangeIndex, lastChangeIndex);
|
||||
}
|
||||
|
||||
m_entityChangeQueue.clear();
|
||||
@@ -1382,6 +1376,9 @@ namespace AzToolsFramework
|
||||
m_isFilterDirty = true;
|
||||
QueueAncestorUpdate(parentId);
|
||||
emit EnableSelectionUpdates(true);
|
||||
|
||||
// Remove any pending updates for this removed entity.
|
||||
m_entityChangeQueue.erase(childId);
|
||||
}
|
||||
|
||||
void EntityOutlinerListModel::OnEntityInfoUpdatedOrderBegin(AZ::EntityId parentId, AZ::EntityId childId, AZ::u64 index)
|
||||
|
||||
+2
-1
@@ -145,6 +145,8 @@ namespace AzToolsFramework
|
||||
|
||||
void SetSortMode(EntityOutliner::DisplaySortMode sortMode) { m_sortMode = sortMode; }
|
||||
void SetDropOperationInProgress(bool inProgress);
|
||||
void ProcessEntityUpdates();
|
||||
|
||||
Q_SIGNALS:
|
||||
void ExpandEntity(const AZ::EntityId& entityId, bool expand);
|
||||
void SelectEntity(const AZ::EntityId& entityId, bool select);
|
||||
@@ -178,7 +180,6 @@ namespace AzToolsFramework
|
||||
void QueueEntityUpdate(AZ::EntityId entityId);
|
||||
void QueueAncestorUpdate(AZ::EntityId entityId);
|
||||
void QueueEntityToExpand(AZ::EntityId entityId, bool expand);
|
||||
void ProcessEntityUpdates();
|
||||
void ProcessEntityInfoResetEnd();
|
||||
AZStd::unordered_set<AZ::EntityId> m_entitySelectQueue;
|
||||
AZStd::unordered_set<AZ::EntityId> m_entityExpandQueue;
|
||||
|
||||
+1
-8
@@ -766,15 +766,8 @@ namespace AzToolsFramework
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
outPrefabAssetPath = product->GetRelativePath();
|
||||
|
||||
auto asset = AZ::Data::AssetManager::Instance().GetAsset(
|
||||
product->GetAssetId(),
|
||||
azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>(),
|
||||
AZ::Data::AssetLoadBehavior::Default);
|
||||
|
||||
return asset.BlockUntilLoadComplete() != AZ::Data::AssetData::AssetStatus::Error;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::WarnUserOfError(AZStd::string_view title, AZStd::string_view message)
|
||||
|
||||
@@ -198,6 +198,8 @@ namespace AzToolsFramework
|
||||
virtual float ManipulatorCircleBoundWidth() const = 0;
|
||||
//! Returns if sticky select is enabled or not.
|
||||
virtual bool StickySelectEnabled() const = 0;
|
||||
//! Returns the default viewport camera position.
|
||||
virtual AZ::Vector3 DefaultEditorCameraPosition() const = 0;
|
||||
|
||||
protected:
|
||||
~ViewportSettingsRequests() = default;
|
||||
|
||||
+32
-4
@@ -115,6 +115,33 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
CursorEntityIdQuery::CursorEntityIdQuery(AZ::EntityId entityId, AZ::EntityId rootEntityId)
|
||||
: m_entityId(entityId)
|
||||
, m_containerAncestorEntityId(rootEntityId)
|
||||
{
|
||||
}
|
||||
|
||||
AZ::EntityId CursorEntityIdQuery::EntityIdUnderCursor() const
|
||||
{
|
||||
return m_entityId;
|
||||
}
|
||||
|
||||
AZ::EntityId CursorEntityIdQuery::ContainerAncestorEntityId() const
|
||||
{
|
||||
return m_containerAncestorEntityId;
|
||||
}
|
||||
|
||||
bool CursorEntityIdQuery::HasContainerAncestorEntityId() const
|
||||
{
|
||||
if (m_entityId.IsValid())
|
||||
{
|
||||
return m_entityId != m_containerAncestorEntityId;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
EditorHelpers::EditorHelpers(const EditorVisibleEntityDataCache* entityDataCache)
|
||||
: m_entityDataCache(entityDataCache)
|
||||
{
|
||||
@@ -131,7 +158,7 @@ namespace AzToolsFramework
|
||||
m_invalidClicks = AZStd::make_unique<InvalidClicks>(AZStd::move(invalidClicks));
|
||||
}
|
||||
|
||||
AZ::EntityId EditorHelpers::HandleMouseInteraction(
|
||||
CursorEntityIdQuery EditorHelpers::FindEntityIdUnderCursor(
|
||||
const AzFramework::CameraState& cameraState, const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AzToolsFramework);
|
||||
@@ -202,17 +229,18 @@ namespace AzToolsFramework
|
||||
m_invalidClicks->AddInvalidClick(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
|
||||
}
|
||||
|
||||
return AZ::EntityId();
|
||||
return CursorEntityIdQuery(AZ::EntityId(), AZ::EntityId());
|
||||
}
|
||||
|
||||
// container entity support - if the entity that is being selected is part of a closed container,
|
||||
// change the selection to the container instead.
|
||||
if (ContainerEntityInterface* containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get())
|
||||
{
|
||||
return containerEntityInterface->FindHighestSelectableEntity(entityIdUnderCursor);
|
||||
const auto highestSelectableEntity = containerEntityInterface->FindHighestSelectableEntity(entityIdUnderCursor);
|
||||
return CursorEntityIdQuery(entityIdUnderCursor, highestSelectableEntity);
|
||||
}
|
||||
|
||||
return entityIdUnderCursor;
|
||||
return CursorEntityIdQuery(entityIdUnderCursor, AZ::EntityId());
|
||||
}
|
||||
|
||||
void EditorHelpers::Display2d(
|
||||
|
||||
@@ -32,6 +32,28 @@ namespace AzToolsFramework
|
||||
struct MouseInteractionEvent;
|
||||
}
|
||||
|
||||
//!< Represents the result of a query to find the id of the entity under the cursor (if any).
|
||||
class CursorEntityIdQuery
|
||||
{
|
||||
public:
|
||||
CursorEntityIdQuery(AZ::EntityId entityId, AZ::EntityId rootEntityId);
|
||||
|
||||
//! Returns the entity id under the cursor (if any).
|
||||
//! @note In the case of no entity id under the cursor, an invalid entity id is returned.
|
||||
AZ::EntityId EntityIdUnderCursor() const;
|
||||
|
||||
//! Returns the topmost container entity id in the hierarchy if the entity id under the cursor is inside a container entity, otherwise returns the entity id.
|
||||
//! @note In the case of no entity id under the cursor, an invalid entity id is returned.
|
||||
AZ::EntityId ContainerAncestorEntityId() const;
|
||||
|
||||
//! Returns true if the query has a container ancestor entity id, otherwise false.
|
||||
bool HasContainerAncestorEntityId() const;
|
||||
|
||||
private:
|
||||
AZ::EntityId m_entityId; //<! The entity id under the cursor.
|
||||
AZ::EntityId m_containerAncestorEntityId; //<! For entities in container entities, the topmost container entity id in the hierarchy, otherwise the entity id under the cursor.
|
||||
};
|
||||
|
||||
//! EditorHelpers are the visualizations that appear for entities
|
||||
//! when 'Display Helpers' is toggled on inside the editor.
|
||||
//! These include but are not limited to entity icons and shape visualizations.
|
||||
@@ -47,9 +69,9 @@ namespace AzToolsFramework
|
||||
EditorHelpers& operator=(const EditorHelpers&) = delete;
|
||||
~EditorHelpers() = default;
|
||||
|
||||
//! Handle any mouse interaction with the EditorHelpers.
|
||||
//! Finds the id of the entity under the cursor (if any). For entities in container entities, also finds the topmost container entity id in the hierarchy.
|
||||
//! Used to check if a particular entity was selected.
|
||||
AZ::EntityId HandleMouseInteraction(
|
||||
CursorEntityIdQuery FindEntityIdUnderCursor(
|
||||
const AzFramework::CameraState& cameraState, const ViewportInteraction::MouseInteractionEvent& mouseInteraction);
|
||||
|
||||
//! Do the drawing responsible for the EditorHelpers.
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ namespace AzToolsFramework
|
||||
|
||||
const AzFramework::CameraState cameraState = GetCameraState(viewportId);
|
||||
|
||||
m_cachedEntityIdUnderCursor = m_editorHelpers->HandleMouseInteraction(cameraState, mouseInteraction);
|
||||
m_cachedEntityIdUnderCursor = m_editorHelpers->FindEntityIdUnderCursor(cameraState, mouseInteraction).ContainerAncestorEntityId();
|
||||
|
||||
// when left clicking, if we successfully clicked an entity, assign that
|
||||
// to the entity field selected in the entity inspector (RPE)
|
||||
|
||||
+18
-3
@@ -27,6 +27,7 @@
|
||||
#include <AzToolsFramework/Manipulators/ScaleManipulators.h>
|
||||
#include <AzToolsFramework/Manipulators/TranslationManipulators.h>
|
||||
#include <AzToolsFramework/Maths/TransformUtils.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
|
||||
#include <AzToolsFramework/ToolsComponents/EditorLockComponentBus.h>
|
||||
#include <AzToolsFramework/ToolsComponents/EditorVisibilityBus.h>
|
||||
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
|
||||
@@ -1799,7 +1800,8 @@ namespace AzToolsFramework
|
||||
const AzFramework::ViewportId viewportId = mouseInteraction.m_mouseInteraction.m_interactionId.m_viewportId;
|
||||
const AzFramework::CameraState cameraState = GetCameraState(viewportId);
|
||||
|
||||
m_cachedEntityIdUnderCursor = m_editorHelpers->HandleMouseInteraction(cameraState, mouseInteraction);
|
||||
const auto cursorEntityIdQuery = m_editorHelpers->FindEntityIdUnderCursor(cameraState, mouseInteraction);
|
||||
m_cachedEntityIdUnderCursor = cursorEntityIdQuery.ContainerAncestorEntityId();
|
||||
|
||||
const auto selectClickEvent = ClickDetectorEventFromViewportInteraction(mouseInteraction);
|
||||
m_cursorState.SetCurrentPosition(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
|
||||
@@ -1825,8 +1827,6 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
const AZ::EntityId entityIdUnderCursor = m_cachedEntityIdUnderCursor;
|
||||
|
||||
EditorContextMenuUpdate(m_contextMenu, mouseInteraction);
|
||||
|
||||
m_boxSelect.HandleMouseInteraction(mouseInteraction);
|
||||
@@ -1842,6 +1842,21 @@ namespace AzToolsFramework
|
||||
return true;
|
||||
}
|
||||
|
||||
const AZ::EntityId entityIdUnderCursor = m_cachedEntityIdUnderCursor;
|
||||
|
||||
if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::DoubleClick &&
|
||||
mouseInteraction.m_mouseInteraction.m_mouseButtons.Left())
|
||||
{
|
||||
if (cursorEntityIdQuery.HasContainerAncestorEntityId())
|
||||
{
|
||||
if (auto prefabFocusInterface = AZ::Interface<Prefab::PrefabFocusInterface>::Get())
|
||||
{
|
||||
prefabFocusInterface->FocusOnPrefabInstanceOwningEntityId(cursorEntityIdQuery.ContainerAncestorEntityId());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool stickySelect = false;
|
||||
ViewportInteraction::ViewportSettingsRequestBus::EventResult(
|
||||
stickySelect, viewportId, &ViewportInteraction::ViewportSettingsRequestBus::Events::StickySelectEnabled);
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzTest/AzTest.h>
|
||||
|
||||
#include <AzFramework/Entity/EntityContextBus.h>
|
||||
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
|
||||
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
|
||||
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
|
||||
#include <AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx>
|
||||
#include <AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h>
|
||||
#include <AzToolsFramework/Undo/UndoSystem.h>
|
||||
#include <Prefab/PrefabTestFixture.h>
|
||||
|
||||
#include <QAbstractItemModelTester>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
// Test fixture for the entity outliner model that uses a QAbstractItemModelTester to validate the state of the model
|
||||
// when QAbstractItemModel signals fire. Tests will exit with a fatal error if an invalid state is detected.
|
||||
class EntityOutlinerTest : public PrefabTestFixture
|
||||
{
|
||||
protected:
|
||||
void SetUpEditorFixtureImpl() override
|
||||
{
|
||||
PrefabTestFixture::SetUpEditorFixtureImpl();
|
||||
GetApplication()->RegisterComponentDescriptor(AzToolsFramework::EditorEntityContextComponent::CreateDescriptor());
|
||||
|
||||
m_model = AZStd::make_unique<AzToolsFramework::EntityOutlinerListModel>();
|
||||
m_model->Initialize();
|
||||
m_modelTester =
|
||||
AZStd::make_unique<QAbstractItemModelTester>(m_model.get(), QAbstractItemModelTester::FailureReportingMode::Fatal);
|
||||
|
||||
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
|
||||
m_undoStack, &AzToolsFramework::ToolsApplicationRequestBus::Events::GetUndoStack);
|
||||
AZ_Assert(m_undoStack, "Failed to look up undo stack from tools application");
|
||||
|
||||
// Create a new root prefab - the synthetic "NewLevel.prefab" that comes in by default isn't suitable for outliner tests
|
||||
// because it's created before the EditorEntityModel that our EntityOutlinerListModel subscribes to, and we want to
|
||||
// recreate it as part of the fixture regardless.
|
||||
auto entityOwnershipService = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
|
||||
entityOwnershipService->CreateNewLevelPrefab("UnitTestRoot.prefab", "");
|
||||
}
|
||||
|
||||
void TearDownEditorFixtureImpl() override
|
||||
{
|
||||
m_undoStack = nullptr;
|
||||
m_modelTester.reset();
|
||||
m_model.reset();
|
||||
PrefabTestFixture::TearDownEditorFixtureImpl();
|
||||
}
|
||||
|
||||
// Creates an entity with a given name as one undoable operation
|
||||
// Parents to parentId, or the root prefab container entity if parentId is invalid
|
||||
AZ::EntityId CreateNamedEntity(AZStd::string name, AZ::EntityId parentId = AZ::EntityId())
|
||||
{
|
||||
auto createResult = m_prefabPublicInterface->CreateEntity(parentId, AZ::Vector3());
|
||||
AZ_Assert(createResult.IsSuccess(), "Failed to create entity: %s", createResult.GetError().c_str());
|
||||
AZ::EntityId entityId = createResult.GetValue();
|
||||
|
||||
AZ::Entity* entity = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId);
|
||||
|
||||
entity->Deactivate();
|
||||
|
||||
entity->SetName(name);
|
||||
|
||||
// Normally, in invalid parent ID should automatically parent us to the root prefab, but currently in the unit test
|
||||
// environment entities aren't created with a default transform component, so CreateEntity won't correctly parent.
|
||||
// We get the actual target parent ID here, then create our missing transform component.
|
||||
if (!parentId.IsValid())
|
||||
{
|
||||
auto prefabEditorEntityOwnershipInterface = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
|
||||
parentId = prefabEditorEntityOwnershipInterface->GetRootPrefabInstance()->get().GetContainerEntityId();
|
||||
}
|
||||
|
||||
auto transform = aznew AzToolsFramework::Components::TransformComponent;
|
||||
transform->SetParent(parentId);
|
||||
entity->AddComponent(transform);
|
||||
|
||||
entity->Activate();
|
||||
|
||||
// Update our undo cache entry to include the rename / reparent as one atomic operation.
|
||||
m_prefabPublicInterface->GenerateUndoNodesForEntityChangeAndUpdateCache(entityId, m_undoStack->GetTop());
|
||||
|
||||
ProcessDeferredUpdates();
|
||||
|
||||
return entityId;
|
||||
}
|
||||
|
||||
// Helper to visualize debug state
|
||||
void PrintModel()
|
||||
{
|
||||
AZStd::deque<AZStd::pair<QModelIndex, int>> indices;
|
||||
indices.push_back({ m_model->index(0, 0), 0 });
|
||||
while (!indices.empty())
|
||||
{
|
||||
auto [index, depth] = indices.front();
|
||||
indices.pop_front();
|
||||
|
||||
QString indentString;
|
||||
for (int i = 0; i < depth; ++i)
|
||||
{
|
||||
indentString += " ";
|
||||
}
|
||||
qDebug() << (indentString + index.data(Qt::DisplayRole).toString()) << index.internalId();
|
||||
for (int i = 0; i < m_model->rowCount(index); ++i)
|
||||
{
|
||||
indices.emplace_back(m_model->index(i, 0, index), depth + 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Gets the index of the root prefab, i.e. the "New Level" container entity
|
||||
QModelIndex GetRootIndex() const
|
||||
{
|
||||
return m_model->index(0, 0);
|
||||
}
|
||||
|
||||
// Kicks off any updates scheduled for the next tick
|
||||
void ProcessDeferredUpdates()
|
||||
{
|
||||
// Force a prefab propagation for updates that are deferred to the next tick.
|
||||
m_prefabSystemComponent->OnSystemTick();
|
||||
|
||||
// Ensure the model process its entity update queue
|
||||
m_model->ProcessEntityUpdates();
|
||||
}
|
||||
|
||||
// Performs an undo operation and ensures the tick-scheduled updates happen
|
||||
void Undo()
|
||||
{
|
||||
m_undoStack->Undo();
|
||||
ProcessDeferredUpdates();
|
||||
}
|
||||
|
||||
// Performs a redo operation and ensures the tick-scheduled updates happen
|
||||
void Redo()
|
||||
{
|
||||
m_undoStack->Redo();
|
||||
ProcessDeferredUpdates();
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AzToolsFramework::EntityOutlinerListModel> m_model;
|
||||
AZStd::unique_ptr<QAbstractItemModelTester> m_modelTester;
|
||||
AzToolsFramework::UndoSystem::UndoStack* m_undoStack = nullptr;
|
||||
};
|
||||
|
||||
TEST_F(EntityOutlinerTest, TestCreateFlatHierarchyUndoAndRedoWorks)
|
||||
{
|
||||
constexpr size_t entityCount = 10;
|
||||
|
||||
for (size_t i = 0; i < entityCount; ++i)
|
||||
{
|
||||
CreateNamedEntity(AZStd::string::format("Entity%zu", i));
|
||||
EXPECT_EQ(m_model->rowCount(GetRootIndex()), i + 1);
|
||||
}
|
||||
|
||||
for (int i = entityCount; i > 0; --i)
|
||||
{
|
||||
Undo();
|
||||
EXPECT_EQ(m_model->rowCount(GetRootIndex()), i - 1);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < entityCount; ++i)
|
||||
{
|
||||
Redo();
|
||||
EXPECT_EQ(m_model->rowCount(GetRootIndex()), i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EntityOutlinerTest, TestCreateNestedHierarchyUndoAndRedoWorks)
|
||||
{
|
||||
constexpr size_t depth = 5;
|
||||
|
||||
auto modelDepth = [this]() -> int
|
||||
{
|
||||
int depth = 0;
|
||||
QModelIndex index = GetRootIndex();
|
||||
while (m_model->rowCount(index) > 0)
|
||||
{
|
||||
++depth;
|
||||
index = m_model->index(0, 0, index);
|
||||
}
|
||||
return depth;
|
||||
};
|
||||
|
||||
AZ::EntityId parentId;
|
||||
for (int i = 0; i < depth; i++)
|
||||
{
|
||||
parentId = CreateNamedEntity(AZStd::string::format("EntityDepth%i", i), parentId);
|
||||
EXPECT_EQ(modelDepth(), i + 1);
|
||||
}
|
||||
|
||||
for (int i = depth - 1; i >= 0; --i)
|
||||
{
|
||||
Undo();
|
||||
EXPECT_EQ(modelDepth(), i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < depth; ++i)
|
||||
{
|
||||
Redo();
|
||||
EXPECT_EQ(modelDepth(), i + 1);
|
||||
}
|
||||
}
|
||||
} // namespace UnitTest
|
||||
@@ -120,6 +120,8 @@ set(FILES
|
||||
ToolsComponents/EditorLayerComponentTests.cpp
|
||||
ToolsComponents/EditorTransformComponentTests.cpp
|
||||
TransformComponent.cpp
|
||||
UI/EntityIdQLineEditTests.cpp
|
||||
UI/EntityOutlinerTests.cpp
|
||||
UI/EntityPropertyEditorTests.cpp
|
||||
UndoStack.cpp
|
||||
Viewport/ClusterTests.cpp
|
||||
|
||||
@@ -3573,19 +3573,61 @@ namespace AssetProcessor
|
||||
QString knownPathBeforeWildcard = encodedFileData.left(slashBeforeWildcardIndex + 1); // include the slash
|
||||
QString relativeSearch = encodedFileData.mid(slashBeforeWildcardIndex + 1); // skip the slash
|
||||
|
||||
for (int i = 0; i < m_platformConfig->GetScanFolderCount(); ++i)
|
||||
// Absolute path, just check the 1 scan folder
|
||||
if (AZ::IO::PathView(encodedFileData.toUtf8().constData()).IsAbsolute())
|
||||
{
|
||||
const ScanFolderInfo* scanFolderInfo = &m_platformConfig->GetScanFolderAt(i);
|
||||
|
||||
if (!scanFolderInfo->RecurseSubFolders() && encodedFileData.contains("/"))
|
||||
QString scanFolderName;
|
||||
if (!m_platformConfig->ConvertToRelativePath(encodedFileData, resultDatabaseSourceName, scanFolderName))
|
||||
{
|
||||
continue;
|
||||
AZ_Warning(
|
||||
AssetProcessor::ConsoleChannel, false,
|
||||
"'%s' does not appear to be in any input folder. Use relative paths instead.",
|
||||
sourceDependency.m_sourceFileDependencyPath.c_str());
|
||||
}
|
||||
|
||||
QDir rooted(scanFolderInfo->ScanPath());
|
||||
QString absolutePath = rooted.absoluteFilePath(knownPathBeforeWildcard);
|
||||
auto scanFolderInfo = m_platformConfig->GetScanFolderByPath(scanFolderName);
|
||||
|
||||
resolvedDependencyList.append(m_platformConfig->FindWildcardMatches(absolutePath, relativeSearch, false, scanFolderInfo->RecurseSubFolders()));
|
||||
// Make an absolute path that is ScanFolderPath + Part of search path before the wildcard
|
||||
QDir rooted(scanFolderName);
|
||||
QString scanFolderAndKnownSubPath = rooted.absoluteFilePath(knownPathBeforeWildcard);
|
||||
|
||||
resolvedDependencyList.append(m_platformConfig->FindWildcardMatches(
|
||||
scanFolderAndKnownSubPath, relativeSearch, false, scanFolderInfo->RecurseSubFolders()));
|
||||
}
|
||||
else // Relative path, check every scan folder
|
||||
{
|
||||
for (int i = 0; i < m_platformConfig->GetScanFolderCount(); ++i)
|
||||
{
|
||||
const ScanFolderInfo* scanFolderInfo = &m_platformConfig->GetScanFolderAt(i);
|
||||
|
||||
if (!scanFolderInfo->RecurseSubFolders() && encodedFileData.contains("/"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QDir rooted(scanFolderInfo->ScanPath());
|
||||
QString absolutePath = rooted.absoluteFilePath(knownPathBeforeWildcard);
|
||||
|
||||
resolvedDependencyList.append(m_platformConfig->FindWildcardMatches(
|
||||
absolutePath, relativeSearch, false, scanFolderInfo->RecurseSubFolders()));
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to relative paths
|
||||
for (auto dependencyItr = resolvedDependencyList.begin(); dependencyItr != resolvedDependencyList.end();)
|
||||
{
|
||||
QString relativePath, scanFolder;
|
||||
if (m_platformConfig->ConvertToRelativePath(*dependencyItr, relativePath, scanFolder))
|
||||
{
|
||||
*dependencyItr = relativePath;
|
||||
++dependencyItr;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Warning("AssetProcessor", false, "Failed to get relative path for wildcard dependency file %s. Is the file within a scan folder?",
|
||||
dependencyItr->toUtf8().constData());
|
||||
dependencyItr = resolvedDependencyList.erase(dependencyItr);
|
||||
}
|
||||
}
|
||||
|
||||
resultDatabaseSourceName = encodedFileData.replace('\\', '/');
|
||||
|
||||
@@ -1435,32 +1435,35 @@ namespace AssetProcessor
|
||||
return QString();
|
||||
}
|
||||
|
||||
QStringList PlatformConfiguration::FindWildcardMatches(const QString& sourceFolder, QString relativeName, bool includeFolders, bool recursiveSearch) const
|
||||
QStringList PlatformConfiguration::FindWildcardMatches(
|
||||
const QString& sourceFolder, QString relativeName, bool includeFolders, bool recursiveSearch) const
|
||||
{
|
||||
if (relativeName.isEmpty())
|
||||
{
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
const int pathLen = sourceFolder.length() + 1;
|
||||
QDir sourceFolderDir(sourceFolder);
|
||||
|
||||
relativeName.replace('\\', '/');
|
||||
QString posixRelativeName = QDir::fromNativeSeparators(relativeName);
|
||||
|
||||
QStringList returnList;
|
||||
QRegExp nameMatch{ relativeName, Qt::CaseInsensitive, QRegExp::Wildcard };
|
||||
QDirIterator diretoryIterator(sourceFolder, QDir::AllEntries | QDir::NoSymLinks | QDir::NoDotAndDotDot, recursiveSearch ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags);
|
||||
QRegExp nameMatch{ posixRelativeName, Qt::CaseInsensitive, QRegExp::Wildcard };
|
||||
QDirIterator dirIterator(
|
||||
sourceFolderDir.path(), QDir::AllEntries | QDir::NoSymLinks | QDir::NoDotAndDotDot,
|
||||
recursiveSearch ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags);
|
||||
QStringList files;
|
||||
while (diretoryIterator.hasNext())
|
||||
while (dirIterator.hasNext())
|
||||
{
|
||||
diretoryIterator.next();
|
||||
if (!includeFolders && !diretoryIterator.fileInfo().isFile())
|
||||
dirIterator.next();
|
||||
if (!includeFolders && !dirIterator.fileInfo().isFile())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
QString pathMatch{ diretoryIterator.filePath().mid(pathLen) };
|
||||
QString pathMatch{ sourceFolderDir.relativeFilePath(dirIterator.filePath()) };
|
||||
if (nameMatch.exactMatch(pathMatch))
|
||||
{
|
||||
returnList.append(AssetUtilities::NormalizeFilePath(diretoryIterator.filePath()));
|
||||
returnList.append(QDir::fromNativeSeparators(dirIterator.filePath()));
|
||||
}
|
||||
}
|
||||
return returnList;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
#include <GemRepo/GemRepoAddDialog.h>
|
||||
#include <FormLineEditWidget.h>
|
||||
#include <FormFolderBrowseEditWidget.h>
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
@@ -40,7 +40,7 @@ namespace O3DE::ProjectManager
|
||||
instructionContextLabel->setAlignment(Qt::AlignLeft);
|
||||
vLayout->addWidget(instructionContextLabel);
|
||||
|
||||
m_repoPath = new FormLineEditWidget(tr("Repository Path"), "", this);
|
||||
m_repoPath = new FormFolderBrowseEditWidget(tr("Repository Path"), "", this);
|
||||
m_repoPath->setFixedWidth(600);
|
||||
vLayout->addWidget(m_repoPath);
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace O3DE::ProjectManager
|
||||
QString m_summary = "No summary provided.";
|
||||
QString m_additionalInfo = "";
|
||||
QString m_directoryLink = "";
|
||||
QString m_repoLink = "";
|
||||
QString m_repoUri = "";
|
||||
QStringList m_includedGemPaths = {};
|
||||
QDateTime m_lastUpdated;
|
||||
};
|
||||
|
||||
@@ -60,8 +60,8 @@ namespace O3DE::ProjectManager
|
||||
|
||||
// Repo name and url link
|
||||
m_nameLabel->setText(m_model->GetName(modelIndex));
|
||||
m_repoLinkLabel->setText(m_model->GetRepoLink(modelIndex));
|
||||
m_repoLinkLabel->SetUrl(m_model->GetRepoLink(modelIndex));
|
||||
m_repoLinkLabel->setText(m_model->GetRepoUri(modelIndex));
|
||||
m_repoLinkLabel->SetUrl(m_model->GetRepoUri(modelIndex));
|
||||
|
||||
// Repo summary
|
||||
m_summaryLabel->setText(m_model->GetSummary(modelIndex));
|
||||
|
||||
@@ -145,6 +145,11 @@ namespace O3DE::ProjectManager
|
||||
GemRepoModel::SetEnabled(*model, modelIndex, !isAdded);
|
||||
return true;
|
||||
}
|
||||
else if (keyEvent->key() == Qt::Key_X)
|
||||
{
|
||||
emit RemoveRepo(modelIndex);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::MouseButtonPress)
|
||||
@@ -154,6 +159,7 @@ namespace O3DE::ProjectManager
|
||||
QRect fullRect, itemRect, contentRect;
|
||||
CalcRects(option, fullRect, itemRect, contentRect);
|
||||
const QRect buttonRect = CalcButtonRect(contentRect);
|
||||
const QRect deleteButtonRect = CalcDeleteButtonRect(contentRect);
|
||||
|
||||
if (buttonRect.contains(mouseEvent->pos()))
|
||||
{
|
||||
@@ -161,6 +167,11 @@ namespace O3DE::ProjectManager
|
||||
GemRepoModel::SetEnabled(*model, modelIndex, !isAdded);
|
||||
return true;
|
||||
}
|
||||
else if (deleteButtonRect.contains(mouseEvent->pos()))
|
||||
{
|
||||
emit RemoveRepo(modelIndex);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return QStyledItemDelegate::editorEvent(event, model, option, modelIndex);
|
||||
@@ -214,9 +225,14 @@ namespace O3DE::ProjectManager
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
QRect GemRepoItemDelegate::CalcDeleteButtonRect(const QRect& contentRect) const
|
||||
{
|
||||
const QPoint topLeft = QPoint(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2);
|
||||
return QRect(topLeft, QSize(s_iconSize, s_iconSize));
|
||||
}
|
||||
|
||||
void GemRepoItemDelegate::DrawEditButtons(QPainter* painter, const QRect& contentRect) const
|
||||
{
|
||||
painter->drawPixmap(contentRect.right() - s_iconSize * 2 - s_iconSpacing, contentRect.center().y() - s_iconSize / 2, m_editIcon);
|
||||
painter->drawPixmap(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2, m_deleteIcon);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,10 +66,14 @@ namespace O3DE::ProjectManager
|
||||
inline constexpr static int s_refreshIconSize = 14;
|
||||
inline constexpr static int s_refreshIconSpacing = 10;
|
||||
|
||||
signals:
|
||||
void RemoveRepo(const QModelIndex& modelIndex);
|
||||
|
||||
protected:
|
||||
void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const;
|
||||
QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const;
|
||||
QRect CalcButtonRect(const QRect& contentRect) const;
|
||||
QRect CalcDeleteButtonRect(const QRect& contentRect) const;
|
||||
void DrawButton(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const;
|
||||
void DrawEditButtons(QPainter* painter, const QRect& contentRect) const;
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <GemRepo/GemRepoListView.h>
|
||||
#include <GemRepo/GemRepoItemDelegate.h>
|
||||
|
||||
#include <QShortcut>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
GemRepoListView::GemRepoListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent)
|
||||
@@ -19,6 +21,9 @@ namespace O3DE::ProjectManager
|
||||
|
||||
setModel(model);
|
||||
setSelectionModel(selectionModel);
|
||||
setItemDelegate(new GemRepoItemDelegate(model, this));
|
||||
|
||||
GemRepoItemDelegate* itemDelegate = new GemRepoItemDelegate(model, this);
|
||||
connect(itemDelegate, &GemRepoItemDelegate::RemoveRepo, this, &GemRepoListView::RemoveRepo);
|
||||
setItemDelegate(itemDelegate);
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -25,5 +25,8 @@ namespace O3DE::ProjectManager
|
||||
public:
|
||||
explicit GemRepoListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent = nullptr);
|
||||
~GemRepoListView() = default;
|
||||
|
||||
signals:
|
||||
void RemoveRepo(const QModelIndex& modelIndex);
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace O3DE::ProjectManager
|
||||
item->setData(gemRepoInfo.m_summary, RoleSummary);
|
||||
item->setData(gemRepoInfo.m_isEnabled, RoleIsEnabled);
|
||||
item->setData(gemRepoInfo.m_directoryLink, RoleDirectoryLink);
|
||||
item->setData(gemRepoInfo.m_repoLink, RoleRepoLink);
|
||||
item->setData(gemRepoInfo.m_repoUri, RoleRepoUri);
|
||||
item->setData(gemRepoInfo.m_lastUpdated, RoleLastUpdated);
|
||||
item->setData(gemRepoInfo.m_path, RolePath);
|
||||
item->setData(gemRepoInfo.m_additionalInfo, RoleAdditionalInfo);
|
||||
@@ -83,9 +83,9 @@ namespace O3DE::ProjectManager
|
||||
return modelIndex.data(RoleDirectoryLink).toString();
|
||||
}
|
||||
|
||||
QString GemRepoModel::GetRepoLink(const QModelIndex& modelIndex)
|
||||
QString GemRepoModel::GetRepoUri(const QModelIndex& modelIndex)
|
||||
{
|
||||
return modelIndex.data(RoleRepoLink).toString();
|
||||
return modelIndex.data(RoleRepoUri).toString();
|
||||
}
|
||||
|
||||
QDateTime GemRepoModel::GetLastUpdated(const QModelIndex& modelIndex)
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace O3DE::ProjectManager
|
||||
static QString GetSummary(const QModelIndex& modelIndex);
|
||||
static QString GetAdditionalInfo(const QModelIndex& modelIndex);
|
||||
static QString GetDirectoryLink(const QModelIndex& modelIndex);
|
||||
static QString GetRepoLink(const QModelIndex& modelIndex);
|
||||
static QString GetRepoUri(const QModelIndex& modelIndex);
|
||||
static QDateTime GetLastUpdated(const QModelIndex& modelIndex);
|
||||
static QString GetPath(const QModelIndex& modelIndex);
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace O3DE::ProjectManager
|
||||
RoleSummary,
|
||||
RoleIsEnabled,
|
||||
RoleDirectoryLink,
|
||||
RoleRepoLink,
|
||||
RoleRepoUri,
|
||||
RoleLastUpdated,
|
||||
RolePath,
|
||||
RoleAdditionalInfo,
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <QTableWidget>
|
||||
#include <QFrame>
|
||||
#include <QStackedWidget>
|
||||
#include <QMessageBox>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -79,21 +80,48 @@ namespace O3DE::ProjectManager
|
||||
|
||||
if (repoAddDialog->exec() == QDialog::DialogCode::Accepted)
|
||||
{
|
||||
QString repoUrl = repoAddDialog->GetRepoPath();
|
||||
if (repoUrl.isEmpty())
|
||||
QString repoUri = repoAddDialog->GetRepoPath();
|
||||
if (repoUri.isEmpty())
|
||||
{
|
||||
QMessageBox::warning(this, tr("No Input"), tr("Please provide a repo Uri."));
|
||||
return;
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> addGemRepoResult = PythonBindingsInterface::Get()->AddGemRepo(repoUrl);
|
||||
if (addGemRepoResult.IsSuccess())
|
||||
bool addGemRepoResult = PythonBindingsInterface::Get()->AddGemRepo(repoUri);
|
||||
if (addGemRepoResult)
|
||||
{
|
||||
Reinit();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(this, tr("Operation failed"),
|
||||
QString("Failed to add gem repo: %1.<br>Error:<br>%2").arg(repoUrl, addGemRepoResult.GetError().c_str()));
|
||||
QString failureMessage = tr("Failed to add gem repo: %1.").arg(repoUri);
|
||||
QMessageBox::critical(this, tr("Operation failed"), failureMessage);
|
||||
AZ_Error("Project Manger", false, failureMessage.toUtf8());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GemRepoScreen::HandleRemoveRepoButton(const QModelIndex& modelIndex)
|
||||
{
|
||||
QString repoName = m_gemRepoModel->GetName(modelIndex);
|
||||
|
||||
QMessageBox::StandardButton warningResult = QMessageBox::warning(
|
||||
this, tr("Remove Repo"), tr("Are you sure you would like to remove gem repo: %1?").arg(repoName),
|
||||
QMessageBox::No | QMessageBox::Yes);
|
||||
|
||||
if (warningResult == QMessageBox::Yes)
|
||||
{
|
||||
QString repoUri = m_gemRepoModel->GetRepoUri(modelIndex);
|
||||
bool removeGemRepoResult = PythonBindingsInterface::Get()->RemoveGemRepo(repoUri);
|
||||
if (removeGemRepoResult)
|
||||
{
|
||||
Reinit();
|
||||
}
|
||||
else
|
||||
{
|
||||
QString failureMessage = tr("Failed to remove gem repo: %1.").arg(repoUri);
|
||||
QMessageBox::critical(this, tr("Operation failed"), failureMessage);
|
||||
AZ_Error("Project Manger", false, failureMessage.toUtf8());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -251,6 +279,8 @@ namespace O3DE::ProjectManager
|
||||
m_gemRepoListView = new GemRepoListView(m_gemRepoModel, m_gemRepoModel->GetSelectionModel(), this);
|
||||
middleVLayout->addWidget(m_gemRepoListView);
|
||||
|
||||
connect(m_gemRepoListView, &GemRepoListView::RemoveRepo, this, &GemRepoScreen::HandleRemoveRepoButton);
|
||||
|
||||
hLayout->addLayout(middleVLayout);
|
||||
|
||||
m_gemRepoInspector = new GemRepoInspector(m_gemRepoModel, this);
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
public slots:
|
||||
void HandleAddRepoButton();
|
||||
void HandleRemoveRepoButton(const QModelIndex& modelIndex);
|
||||
|
||||
private:
|
||||
void FillModel();
|
||||
|
||||
@@ -939,17 +939,60 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::AddGemRepo(const QString& repoUri)
|
||||
bool PythonBindings::AddGemRepo(const QString& repoUri)
|
||||
{
|
||||
// o3de scripts need method added
|
||||
(void)repoUri;
|
||||
return AZ::Failure<AZStd::string>("Adding Gem Repo not implemented yet in o3de scripts.");
|
||||
bool registrationResult = false;
|
||||
bool result = ExecuteWithLock(
|
||||
[&]
|
||||
{
|
||||
auto pyUri = QString_To_Py_String(repoUri);
|
||||
auto pythonRegistrationResult = m_register.attr("register")(
|
||||
pybind11::none(), pybind11::none(), pybind11::none(), pybind11::none(), pybind11::none(), pybind11::none(), pyUri);
|
||||
|
||||
// Returns an exit code so boolify it then invert result
|
||||
registrationResult = !pythonRegistrationResult.cast<bool>();
|
||||
});
|
||||
|
||||
return result && registrationResult;
|
||||
}
|
||||
|
||||
bool PythonBindings::RemoveGemRepo(const QString& repoUri)
|
||||
{
|
||||
bool registrationResult = false;
|
||||
bool result = ExecuteWithLock(
|
||||
[&]
|
||||
{
|
||||
auto pythonRegistrationResult = m_register.attr("register")(
|
||||
pybind11::none(), // engine_path
|
||||
pybind11::none(), // project_path
|
||||
pybind11::none(), // gem_path
|
||||
pybind11::none(), // external_subdir_path
|
||||
pybind11::none(), // template_path
|
||||
pybind11::none(), // restricted_path
|
||||
QString_To_Py_String(repoUri), // repo_uri
|
||||
pybind11::none(), // default_engines_folder
|
||||
pybind11::none(), // default_projects_folder
|
||||
pybind11::none(), // default_gems_folder
|
||||
pybind11::none(), // default_templates_folder
|
||||
pybind11::none(), // default_restricted_folder
|
||||
pybind11::none(), // default_third_party_folder
|
||||
pybind11::none(), // external_subdir_engine_path
|
||||
pybind11::none(), // external_subdir_project_path
|
||||
true, // remove
|
||||
false // force
|
||||
);
|
||||
|
||||
// Returns an exit code so boolify it then invert result
|
||||
registrationResult = !pythonRegistrationResult.cast<bool>();
|
||||
});
|
||||
|
||||
return result && registrationResult;
|
||||
}
|
||||
|
||||
GemRepoInfo PythonBindings::GetGemRepoInfo(pybind11::handle repoUri)
|
||||
{
|
||||
GemRepoInfo gemRepoInfo;
|
||||
gemRepoInfo.m_repoLink = Py_To_String(repoUri);
|
||||
gemRepoInfo.m_repoUri = Py_To_String(repoUri);
|
||||
|
||||
auto data = m_manifest.attr("get_repo_json_data")(repoUri);
|
||||
if (pybind11::isinstance<pybind11::dict>(data))
|
||||
@@ -957,7 +1000,7 @@ namespace O3DE::ProjectManager
|
||||
try
|
||||
{
|
||||
// required
|
||||
gemRepoInfo.m_repoLink = Py_To_String(data["repo_uri"]);
|
||||
gemRepoInfo.m_repoUri = Py_To_String(data["repo_uri"]);
|
||||
gemRepoInfo.m_name = Py_To_String(data["repo_name"]);
|
||||
gemRepoInfo.m_creator = Py_To_String(data["origin"]);
|
||||
|
||||
@@ -1019,13 +1062,13 @@ namespace O3DE::ProjectManager
|
||||
#else
|
||||
GemRepoInfo mockJohnRepo("JohnCreates", "John Smith", QDateTime(QDate(2021, 8, 31), QTime(11, 57)), true);
|
||||
mockJohnRepo.m_summary = "John's Summary. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sollicitudin dapibus urna";
|
||||
mockJohnRepo.m_repoLink = "https://github.com/o3de/o3de";
|
||||
mockJohnRepo.m_repoUri = "https://github.com/o3de/o3de";
|
||||
mockJohnRepo.m_additionalInfo = "John's additional info. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sollicitu.";
|
||||
gemRepos.push_back(mockJohnRepo);
|
||||
|
||||
GemRepoInfo mockJaneRepo("JanesGems", "Jane Doe", QDateTime(QDate(2021, 9, 10), QTime(18, 23)), false);
|
||||
mockJaneRepo.m_summary = "Jane's Summary.";
|
||||
mockJaneRepo.m_repoLink = "https://github.com/o3de/o3de.org";
|
||||
mockJaneRepo.m_repoUri = "https://github.com/o3de/o3de.org";
|
||||
gemRepos.push_back(mockJaneRepo);
|
||||
#endif // MOCK_GEM_REPO_INFO
|
||||
|
||||
|
||||
@@ -58,7 +58,8 @@ namespace O3DE::ProjectManager
|
||||
AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates(const QString& projectPath = {}) override;
|
||||
|
||||
// Gem Repos
|
||||
AZ::Outcome<void, AZStd::string> AddGemRepo(const QString& repoUri) override;
|
||||
bool AddGemRepo(const QString& repoUri) override;
|
||||
bool RemoveGemRepo(const QString& repoUri) override;
|
||||
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -169,11 +169,18 @@ namespace O3DE::ProjectManager
|
||||
// Gem Repos
|
||||
|
||||
/**
|
||||
* A gem repo to engine. Registers this gem repo with the current engine.
|
||||
* @param repoUri the absolute filesystem path or url to the gem repo manifest file.
|
||||
* @return An outcome with the success flag as well as an error message in case of a failure.
|
||||
* Registers this gem repo with the current engine.
|
||||
* @param repoUri the absolute filesystem path or url to the gem repo.
|
||||
* @return true on success, false on failure.
|
||||
*/
|
||||
virtual AZ::Outcome<void, AZStd::string> AddGemRepo(const QString& repoUri) = 0;
|
||||
virtual bool AddGemRepo(const QString& repoUri) = 0;
|
||||
|
||||
/**
|
||||
* Unregisters this gem repo with the current engine.
|
||||
* @param repoUri the absolute filesystem path or url to the gem repo.
|
||||
* @return true on success, false on failure.
|
||||
*/
|
||||
virtual bool RemoveGemRepo(const QString& repoUri) = 0;
|
||||
|
||||
/**
|
||||
* Get all available gem repo infos. Gathers all repos registered with the engine.
|
||||
|
||||
@@ -51,4 +51,21 @@ namespace AWSCore
|
||||
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-metrics/advanced-topics/";
|
||||
static constexpr const char AWSMetricsSettingsUrl[] =
|
||||
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-metrics/";
|
||||
|
||||
static constexpr const char AWSGameLiftGemOverviewUrl[] =
|
||||
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-gamelift/";
|
||||
static constexpr const char AWSGameLiftGemSetupUrl[] =
|
||||
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-gamelift/gem-setup/";
|
||||
static constexpr const char AWSGameLiftScriptingUrl[] =
|
||||
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-gamelift/scripting/";
|
||||
static constexpr const char AWSGameLiftAPIReferenceUrl[] =
|
||||
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-gamelift/cpp-api/";
|
||||
static constexpr const char AWSGameLiftAdvancedTopicsUrl[] =
|
||||
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-gamelift/advanced-topics/";
|
||||
static constexpr const char AWSGameLiftLocalTestingUrl[] =
|
||||
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-gamelift/local-testing/";
|
||||
static constexpr const char AWSGameLiftBuildPackagingUrl[] =
|
||||
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-gamelift/build-packaging-for-windows/";
|
||||
static constexpr const char AWSGameLiftResourceManagementUrl[] =
|
||||
"https://o3de.org/docs/user-guide/gems/reference/aws/aws-gamelift/resource-management/";
|
||||
} // namespace AWSCore
|
||||
|
||||
@@ -38,4 +38,14 @@ namespace AWSCore
|
||||
static constexpr const char AWSMetricsAPIReferenceActionText[] = "API reference";
|
||||
static constexpr const char AWSMetricsAdvancedTopicsActionText[] = "Advanced topics";
|
||||
static constexpr const char AWSMetricsSettingsActionText[] = "Metrics settings";
|
||||
|
||||
static constexpr const char AWSGameLiftActionText[] = "GameLift Gem";
|
||||
static constexpr const char AWSGameLiftGemOverviewActionText[] = "Gem overview";
|
||||
static constexpr const char AWSGameLiftGemSetupActionText[] = "Setup";
|
||||
static constexpr const char AWSMGameLiftScriptingActionText[] = "Scripting reference";
|
||||
static constexpr const char AWSGameLiftAPIReferenceActionText[] = "API reference";
|
||||
static constexpr const char AWSGameLiftAdvancedTopicsActionText[] = "Advanced topics";
|
||||
static constexpr const char AWSGameLiftLocalTestingActionText[] = "Local testing";
|
||||
static constexpr const char AWSGameLiftBuildPackagingActionText[] = "Build packaging (Windows)";
|
||||
static constexpr const char AWSGameLiftResourceManagementActionText[] = "Resource Management";
|
||||
} // namespace AWSCore
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace AWSCore
|
||||
// AWSCoreEditorRequestBus interface implementation
|
||||
void SetAWSClientAuthEnabled() override;
|
||||
void SetAWSMetricsEnabled() override;
|
||||
void SetAWSGameLiftEnabled() override;
|
||||
|
||||
QMenu* SetAWSFeatureSubMenu(const AZStd::string& menuText);
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ namespace AWSCore
|
||||
public:
|
||||
virtual void SetAWSClientAuthEnabled() = 0;
|
||||
virtual void SetAWSMetricsEnabled() = 0;
|
||||
virtual void SetAWSGameLiftEnabled() = 0;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// EBusTraits overrides
|
||||
|
||||
@@ -156,6 +156,11 @@ namespace AWSCore
|
||||
metrics->setIcon(QIcon(QString(":/Notifications/download.svg")));
|
||||
metrics->setDisabled(true);
|
||||
this->addAction(metrics);
|
||||
|
||||
QAction* gamelift = new QAction(QObject::tr(AWSGameLiftActionText));
|
||||
gamelift->setIcon(QIcon(QString(":/Notifications/download.svg")));
|
||||
gamelift->setDisabled(true);
|
||||
this->addAction(gamelift);
|
||||
}
|
||||
|
||||
void AWSCoreEditorMenu::SetAWSClientAuthEnabled()
|
||||
@@ -181,6 +186,23 @@ namespace AWSCore
|
||||
AddSpaceForIcon(subMenu);
|
||||
}
|
||||
|
||||
void AWSCoreEditorMenu::SetAWSGameLiftEnabled()
|
||||
{
|
||||
// TODO: instead of creating submenu in core editor, aws feature gem should return submenu component directly
|
||||
QMenu* subMenu = SetAWSFeatureSubMenu(AWSGameLiftActionText);
|
||||
|
||||
subMenu->addAction(AddExternalLinkAction(AWSGameLiftGemOverviewActionText, AWSGameLiftGemOverviewUrl, ":/Notifications/link.svg"));
|
||||
subMenu->addAction(AddExternalLinkAction(AWSGameLiftGemSetupActionText, AWSGameLiftGemSetupUrl, ":/Notifications/link.svg"));
|
||||
subMenu->addAction(AddExternalLinkAction(AWSMGameLiftScriptingActionText, AWSGameLiftScriptingUrl, ":/Notifications/link.svg"));
|
||||
subMenu->addAction(AddExternalLinkAction(AWSGameLiftAPIReferenceActionText, AWSGameLiftAPIReferenceUrl, ":/Notifications/link.svg"));
|
||||
subMenu->addAction(AddExternalLinkAction(AWSGameLiftAdvancedTopicsActionText, AWSGameLiftAdvancedTopicsUrl, ":/Notifications/link.svg"));
|
||||
subMenu->addAction(AddExternalLinkAction(AWSGameLiftLocalTestingActionText, AWSGameLiftLocalTestingUrl, ":/Notifications/link.svg"));
|
||||
subMenu->addAction(AddExternalLinkAction(AWSGameLiftBuildPackagingActionText, AWSGameLiftBuildPackagingUrl, ":/Notifications/link.svg"));
|
||||
subMenu->addAction(AddExternalLinkAction(AWSGameLiftResourceManagementActionText, AWSGameLiftResourceManagementUrl, ":/Notifications/link.svg"));
|
||||
|
||||
AddSpaceForIcon(subMenu);
|
||||
}
|
||||
|
||||
void AWSCoreEditorMenu::SetAWSMetricsEnabled()
|
||||
{
|
||||
// TODO: instead of creating submenu in core editor, aws feature gem should return submenu component directly
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
|
||||
using namespace AWSCore;
|
||||
|
||||
static constexpr const int ExpectedActionNumOnWindowsPlatform = 8;
|
||||
static constexpr const int ExpectedActionNumOnOtherPlatform = 6;
|
||||
static constexpr const int ExpectedActionNumOnWindowsPlatform = 9;
|
||||
static constexpr const int ExpectedActionNumOnOtherPlatform = 7;
|
||||
|
||||
class AWSCoreEditorMenuTest
|
||||
: public AWSCoreFixture
|
||||
@@ -60,6 +60,7 @@ TEST_F(AWSCoreEditorMenuTest, AWSCoreEditorMenu_BroadcastFeatureGemsAreEnabled_C
|
||||
|
||||
AWSCoreEditorRequestBus::Broadcast(&AWSCoreEditorRequests::SetAWSClientAuthEnabled);
|
||||
AWSCoreEditorRequestBus::Broadcast(&AWSCoreEditorRequests::SetAWSMetricsEnabled);
|
||||
AWSCoreEditorRequestBus::Broadcast(&AWSCoreEditorRequests::SetAWSGameLiftEnabled);
|
||||
|
||||
QList<QAction*> actualActions = testMenu.actions();
|
||||
for (QList<QAction*>::iterator itr = actualActions.begin(); itr != actualActions.end(); itr++)
|
||||
@@ -73,5 +74,10 @@ TEST_F(AWSCoreEditorMenuTest, AWSCoreEditorMenu_BroadcastFeatureGemsAreEnabled_C
|
||||
{
|
||||
EXPECT_TRUE((*itr)->isEnabled());
|
||||
}
|
||||
|
||||
if (QString::compare((*itr)->text(), AWSGameLiftActionText) == 0)
|
||||
{
|
||||
EXPECT_TRUE((*itr)->isEnabled());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,4 +94,41 @@ namespace AWSGameLift
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
};
|
||||
using AWSGameLiftMatchmakingRequestBus = AZ::EBus<AzFramework::IMatchmakingRequests, AWSGameLiftMatchmakingRequests>;
|
||||
|
||||
//! IAWSGameLiftMatchmakingEventRequests
|
||||
//! GameLift Gem matchmaking event interfaces which is used to track matchmaking ticket event
|
||||
//! Developer should define the way to poll matchmaking ticket event and behavior based on the ticket status
|
||||
//! Use AWSGameLiftClientLocalTicketTracker as an example, it uses continuous polling to query matchmaking ticket:
|
||||
//! StartPolling - local ticket tracker starts monitor process for matchmaking ticket, and joins player
|
||||
//! to the match once ticket is complete
|
||||
//! StopPolling - local ticket tracker cancels ongoing matchmaking ticket and stops monitoring process
|
||||
class IAWSGameLiftMatchmakingEventRequests
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(IAWSGameLiftMatchmakingEventRequests, "{C2DA440E-74E0-411E-813D-5880B50B0C9E}");
|
||||
|
||||
IAWSGameLiftMatchmakingEventRequests() = default;
|
||||
virtual ~IAWSGameLiftMatchmakingEventRequests() = default;
|
||||
|
||||
//! StartPolling
|
||||
//! Request to start process for polling matchmaking ticket based on given ticket id and player Id
|
||||
//! @param ticketId The requested matchmaking ticket id
|
||||
//! @param playerId The requested matchmaking player id
|
||||
virtual void StartPolling(const AZStd::string& ticketId, const AZStd::string& playerId) = 0;
|
||||
|
||||
//! StopPolling
|
||||
//! Request to stop process for polling matchmaking ticket
|
||||
virtual void StopPolling() = 0;
|
||||
};
|
||||
|
||||
// IAWSGameLiftMatchmakingEventRequests EBus wrapper for scripting
|
||||
class AWSGameLiftMatchmakingEventRequests
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
using MutexType = AZStd::recursive_mutex;
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
};
|
||||
using AWSGameLiftMatchmakingEventRequestBus = AZ::EBus<IAWSGameLiftMatchmakingEventRequests, AWSGameLiftMatchmakingEventRequests>;
|
||||
} // namespace AWSGameLift
|
||||
|
||||
+4
-2
@@ -30,12 +30,14 @@ namespace AWSGameLift
|
||||
|
||||
void AWSGameLiftClientLocalTicketTracker::ActivateTracker()
|
||||
{
|
||||
AZ::Interface<IAWSGameLiftMatchmakingInternalRequests>::Register(this);
|
||||
AZ::Interface<IAWSGameLiftMatchmakingEventRequests>::Register(this);
|
||||
AWSGameLiftMatchmakingEventRequestBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
void AWSGameLiftClientLocalTicketTracker::DeactivateTracker()
|
||||
{
|
||||
AZ::Interface<IAWSGameLiftMatchmakingInternalRequests>::Unregister(this);
|
||||
AWSGameLiftMatchmakingEventRequestBus::Handler::BusDisconnect();
|
||||
AZ::Interface<IAWSGameLiftMatchmakingEventRequests>::Unregister(this);
|
||||
StopPolling();
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -12,7 +12,7 @@
|
||||
#include <AzCore/std/parallel/mutex.h>
|
||||
#include <AzCore/std/parallel/thread.h>
|
||||
|
||||
#include <Request/IAWSGameLiftMatchmakingInternalRequests.h>
|
||||
#include <Request/IAWSGameLiftRequests.h>
|
||||
|
||||
#include <aws/gamelift/model/MatchmakingTicket.h>
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace AWSGameLift
|
||||
//! For use in production, please see GameLifts guidance about matchmaking at volume.
|
||||
//! The continuous polling approach here is only suitable for low volume matchmaking and is meant to aid with development only
|
||||
class AWSGameLiftClientLocalTicketTracker
|
||||
: public IAWSGameLiftMatchmakingInternalRequests
|
||||
: public AWSGameLiftMatchmakingEventRequestBus::Handler
|
||||
{
|
||||
public:
|
||||
static constexpr const char AWSGameLiftClientLocalTicketTrackerName[] = "AWSGameLiftClientLocalTicketTracker";
|
||||
@@ -44,7 +44,7 @@ namespace AWSGameLift
|
||||
virtual void ActivateTracker();
|
||||
virtual void DeactivateTracker();
|
||||
|
||||
// IAWSGameLiftMatchmakingInternalRequests interface implementation
|
||||
// AWSGameLiftMatchmakingEventRequestBus interface implementation
|
||||
void StartPolling(const AZStd::string& ticketId, const AZStd::string& playerId) override;
|
||||
void StopPolling() override;
|
||||
|
||||
|
||||
+14
-3
@@ -12,6 +12,8 @@
|
||||
#include <AzCore/Serialization/EditContextConstants.inl>
|
||||
#include <AzFramework/Session/SessionConfig.h>
|
||||
|
||||
#include <AWSGameLiftClientLocalTicketTracker.h>
|
||||
#include <AWSCoreBus.h>
|
||||
#include <AWSGameLiftClientManager.h>
|
||||
#include <AWSGameLiftClientSystemComponent.h>
|
||||
#include <Request/AWSGameLiftAcceptMatchRequest.h>
|
||||
@@ -59,10 +61,17 @@ namespace AWSGameLift
|
||||
behaviorContext->EBus<AWSGameLiftRequestBus>("AWSGameLiftRequestBus")
|
||||
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift")
|
||||
->Event("ConfigureGameLiftClient", &AWSGameLiftRequestBus::Events::ConfigureGameLiftClient,
|
||||
{{{"Region", ""}}})
|
||||
{ { { "Region", "" } } })
|
||||
->Event("CreatePlayerId", &AWSGameLiftRequestBus::Events::CreatePlayerId,
|
||||
{{{"IncludeBrackets", ""},
|
||||
{"IncludeDashes", ""}}});
|
||||
{ { { "IncludeBrackets", "" },
|
||||
{ "IncludeDashes", "" } } });
|
||||
|
||||
behaviorContext->EBus<AWSGameLiftMatchmakingEventRequestBus>("AWSGameLiftMatchmakingEventRequestBus")
|
||||
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift")
|
||||
->Event("StartPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StartPolling,
|
||||
{ { { "TicketId", "" },
|
||||
{ "PlayerId", "" } } })
|
||||
->Event("StopPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StopPolling);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +106,8 @@ namespace AWSGameLift
|
||||
m_gameliftClient.reset();
|
||||
m_gameliftManager->ActivateManager();
|
||||
m_gameliftTicketTracker->ActivateTracker();
|
||||
|
||||
AWSCore::AWSCoreEditorRequestBus::Broadcast(&AWSCore::AWSCoreEditorRequests::SetAWSGameLiftEnabled);
|
||||
}
|
||||
|
||||
void AWSGameLiftClientSystemComponent::Deactivate()
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
|
||||
#include <AWSGameLiftClientLocalTicketTracker.h>
|
||||
#include <Request/IAWSGameLiftInternalRequests.h>
|
||||
|
||||
namespace AWSGameLift
|
||||
{
|
||||
class AWSGameLiftClientManager;
|
||||
class AWSGameLiftClientLocalTicketTracker;
|
||||
|
||||
//! Gem client system component. Responsible for creating the gamelift client manager.
|
||||
class AWSGameLiftClientSystemComponent
|
||||
|
||||
+5
@@ -33,6 +33,11 @@ namespace AWSGameLift
|
||||
|
||||
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->Class<AzFramework::AcceptMatchRequest>("AcceptMatchRequest")
|
||||
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
|
||||
// Expose base type to BehaviorContext, but hide it to be used directly
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All);
|
||||
|
||||
behaviorContext->Class<AWSGameLiftAcceptMatchRequest>("AWSGameLiftAcceptMatchRequest")
|
||||
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
|
||||
->Property("AcceptMatch", BehaviorValueProperty(&AWSGameLiftAcceptMatchRequest::m_acceptMatch))
|
||||
|
||||
+5
@@ -40,6 +40,11 @@ namespace AWSGameLift
|
||||
|
||||
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->Class<AzFramework::StartMatchmakingRequest>("StartMatchmakingRequest")
|
||||
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
|
||||
// Expose base type to BehaviorContext, but hide it to be used directly
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All);
|
||||
|
||||
behaviorContext->Class<AWSGameLiftStartMatchmakingRequest>("AWSGameLiftStartMatchmakingRequest")
|
||||
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
|
||||
->Property("TicketId", BehaviorValueProperty(&AWSGameLiftStartMatchmakingRequest::m_ticketId))
|
||||
|
||||
+5
@@ -33,6 +33,11 @@ namespace AWSGameLift
|
||||
|
||||
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->Class<AzFramework::StopMatchmakingRequest>("StopMatchmakingRequest")
|
||||
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
|
||||
// Expose base type to BehaviorContext, but hide it to be used directly
|
||||
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All);
|
||||
|
||||
behaviorContext->Class<AWSGameLiftStopMatchmakingRequest>("AWSGameLiftStopMatchmakingRequest")
|
||||
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
|
||||
->Property("TicketId", BehaviorValueProperty(&AWSGameLiftStopMatchmakingRequest::m_ticketId));
|
||||
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/RTTI/RTTI.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
|
||||
namespace AWSGameLift
|
||||
{
|
||||
//! IAWSGameLiftMatchmakingInternalRequests
|
||||
//! GameLift Gem matchmaking internal interfaces which is used to communicate
|
||||
//! with client side ticket tracker to sync matchmaking ticket data and join
|
||||
//! player to the match
|
||||
class IAWSGameLiftMatchmakingInternalRequests
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(IAWSGameLiftMatchmakingInternalRequests, "{C2DA440E-74E0-411E-813D-5880B50B0C9E}");
|
||||
|
||||
IAWSGameLiftMatchmakingInternalRequests() = default;
|
||||
virtual ~IAWSGameLiftMatchmakingInternalRequests() = default;
|
||||
|
||||
//! StartPolling
|
||||
//! Request to start process for polling matchmaking ticket based on given ticket id and player id
|
||||
//! @param ticketId The requested matchmaking ticket id
|
||||
//! @param playerId The requested matchmaking player id
|
||||
virtual void StartPolling(const AZStd::string& ticketId, const AZStd::string& playerId) = 0;
|
||||
|
||||
//! StopPolling
|
||||
//! Request to stop process for polling matchmaking ticket
|
||||
virtual void StopPolling() = 0;
|
||||
};
|
||||
} // namespace AWSGameLift
|
||||
@@ -36,8 +36,6 @@
|
||||
#include <aws/gamelift/model/StopMatchmakingRequest.h>
|
||||
#include <aws/gamelift/model/StopMatchmakingResult.h>
|
||||
|
||||
#include <Request/IAWSGameLiftMatchmakingInternalRequests.h>
|
||||
|
||||
using namespace Aws::GameLift;
|
||||
|
||||
class GameLiftClientMock
|
||||
|
||||
@@ -32,12 +32,12 @@ set(FILES
|
||||
Source/Activity/AWSGameLiftLeaveSessionActivity.h
|
||||
Source/Activity/AWSGameLiftSearchSessionsActivity.cpp
|
||||
Source/Activity/AWSGameLiftSearchSessionsActivity.h
|
||||
Source/AWSGameLiftClientLocalTicketTracker.cpp
|
||||
Source/AWSGameLiftClientLocalTicketTracker.h
|
||||
Source/Activity/AWSGameLiftStartMatchmakingActivity.cpp
|
||||
Source/Activity/AWSGameLiftStartMatchmakingActivity.h
|
||||
Source/Activity/AWSGameLiftStopMatchmakingActivity.cpp
|
||||
Source/Activity/AWSGameLiftStopMatchmakingActivity.h
|
||||
Source/AWSGameLiftClientLocalTicketTracker.cpp
|
||||
Source/AWSGameLiftClientLocalTicketTracker.h
|
||||
Source/AWSGameLiftClientManager.cpp
|
||||
Source/AWSGameLiftClientManager.h
|
||||
Source/AWSGameLiftClientSystemComponent.cpp
|
||||
@@ -50,5 +50,4 @@ set(FILES
|
||||
Source/Request/AWSGameLiftStartMatchmakingRequest.cpp
|
||||
Source/Request/AWSGameLiftStopMatchmakingRequest.cpp
|
||||
Source/Request/IAWSGameLiftInternalRequests.h
|
||||
Source/Request/IAWSGameLiftMatchmakingInternalRequests.h
|
||||
)
|
||||
|
||||
@@ -336,14 +336,11 @@ namespace AWSGameLift
|
||||
BuildServerMatchBackfillPlayerAttributes(
|
||||
players[playerIndex][AWSGameLiftMatchmakingPlayerAttributesKeyName], outPlayer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void AWSGameLiftServerManager::BuildServerMatchBackfillPlayerAttributes(
|
||||
@@ -461,12 +458,16 @@ namespace AWSGameLift
|
||||
|
||||
AZ_TracePrintf(AWSGameLiftServerManagerName, "Notifying GameLift server process is ending ...");
|
||||
Aws::GameLift::GenericOutcome processEndingOutcome = m_gameLiftServerSDKWrapper->ProcessEnding();
|
||||
AZ_TracePrintf(AWSGameLiftServerManagerName, "ProcessEnding request against Amazon GameLift service is complete.");
|
||||
|
||||
[[maybe_unused]] bool processEndingIsSuccess = processEndingOutcome.IsSuccess();
|
||||
|
||||
AZ_Error(AWSGameLiftServerManagerName, processEndingIsSuccess, AWSGameLiftServerProcessEndingErrorMessage,
|
||||
processEndingOutcome.GetError().GetErrorMessage().c_str());
|
||||
if (processEndingOutcome.IsSuccess())
|
||||
{
|
||||
AZ_TracePrintf(AWSGameLiftServerManagerName, "ProcessEnding request against Amazon GameLift service succeeded.");
|
||||
AzFramework::SessionNotificationBus::Broadcast(&AzFramework::SessionNotifications::OnDestroySessionEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error(AWSGameLiftServerManagerName, false, AWSGameLiftServerProcessEndingErrorMessage,
|
||||
processEndingOutcome.GetError().GetErrorMessage().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void AWSGameLiftServerManager::HandlePlayerLeaveSession(const AzFramework::PlayerConnectionConfig& playerConnectionConfig)
|
||||
@@ -546,15 +547,16 @@ namespace AWSGameLift
|
||||
{
|
||||
AZ_TracePrintf(AWSGameLiftServerManagerName, "Activating GameLift game session ...");
|
||||
Aws::GameLift::GenericOutcome activationOutcome = m_gameLiftServerSDKWrapper->ActivateGameSession();
|
||||
AZ_TracePrintf(AWSGameLiftServerManagerName, "ActivateGameSession request against Amazon GameLift service is complete.");
|
||||
|
||||
if (activationOutcome.IsSuccess())
|
||||
{
|
||||
AZ_TracePrintf(AWSGameLiftServerManagerName, "ActivateGameSession request against Amazon GameLift service succeeded.");
|
||||
// Register server manager as handler once game session has been activated
|
||||
if (!AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get())
|
||||
{
|
||||
AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Register(this);
|
||||
}
|
||||
AzFramework::SessionNotificationBus::Broadcast(&AzFramework::SessionNotifications::OnCreateSessionEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -588,17 +590,18 @@ namespace AWSGameLift
|
||||
|
||||
void AWSGameLiftServerManager::OnUpdateGameSession(const Aws::GameLift::Server::Model::UpdateGameSession& updateGameSession)
|
||||
{
|
||||
AzFramework::SessionConfig sessionConfig = BuildSessionConfig(updateGameSession.GetGameSession());
|
||||
Aws::GameLift::Server::Model::UpdateReason updateReason = updateGameSession.GetUpdateReason();
|
||||
AzFramework::SessionNotificationBus::Broadcast(&AzFramework::SessionNotifications::OnUpdateSessionBegin,
|
||||
sessionConfig, Aws::GameLift::Server::Model::UpdateReasonMapper::GetNameForUpdateReason(updateReason).c_str());
|
||||
|
||||
// Update game session data locally
|
||||
if (updateReason == Aws::GameLift::Server::Model::UpdateReason::MATCHMAKING_DATA_UPDATED)
|
||||
{
|
||||
UpdateGameSessionData(updateGameSession.GetGameSession());
|
||||
}
|
||||
AzFramework::SessionConfig sessionConfig = BuildSessionConfig(updateGameSession.GetGameSession());
|
||||
|
||||
AzFramework::SessionNotificationBus::Broadcast(
|
||||
&AzFramework::SessionNotifications::OnUpdateSessionBegin,
|
||||
sessionConfig,
|
||||
Aws::GameLift::Server::Model::UpdateReasonMapper::GetNameForUpdateReason(updateReason).c_str());
|
||||
AzFramework::SessionNotificationBus::Broadcast(&AzFramework::SessionNotifications::OnUpdateSessionEnd);
|
||||
}
|
||||
|
||||
bool AWSGameLiftServerManager::RemoveConnectedPlayer(uint32_t playerConnectionId, AZStd::string& outPlayerSessionId)
|
||||
@@ -654,7 +657,7 @@ namespace AWSGameLift
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_TracePrintf(AWSGameLiftServerManagerName, "StartMatchBackfill request against Amazon GameLift service is complete.");
|
||||
AZ_TracePrintf(AWSGameLiftServerManagerName, "StartMatchBackfill request against Amazon GameLift service succeeded.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -686,7 +689,7 @@ namespace AWSGameLift
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_TracePrintf(AWSGameLiftServerManagerName, "StopMatchBackfill request against Amazon GameLift service is complete.");
|
||||
AZ_TracePrintf(AWSGameLiftServerManagerName, "StopMatchBackfill request against Amazon GameLift service succeeded.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace AWSGameLift
|
||||
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeSTypeName[] = "S";
|
||||
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeSServerTypeName[] = "STRING";
|
||||
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeNTypeName[] = "N";
|
||||
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeNServerTypeName[] = "NUMBER";
|
||||
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeNServerTypeName[] = "DOUBLE";
|
||||
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeSLTypeName[] = "SL";
|
||||
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeSLServerTypeName[] = "STRING_LIST";
|
||||
static constexpr const char AWSGameLiftMatchmakingPlayerAttributeSDMTypeName[] = "SDM";
|
||||
|
||||
@@ -34,13 +34,20 @@ R"({
|
||||
"valueAttribute":"testmode"
|
||||
},
|
||||
"level":{
|
||||
"attributeType":"NUMBER",
|
||||
"attributeType":"DOUBLE",
|
||||
"valueAttribute":10.0
|
||||
},
|
||||
"items":{
|
||||
"attributeType":"STRING_LIST",
|
||||
"valueAttribute":["test1","test2","test3"]
|
||||
}
|
||||
}},
|
||||
{"playerId":"secondplayer",
|
||||
"attributes":{
|
||||
"mode":{
|
||||
"attributeType":"STRING",
|
||||
"valueAttribute":"testmode"
|
||||
}
|
||||
}}
|
||||
]}
|
||||
]
|
||||
@@ -162,8 +169,11 @@ R"({
|
||||
|
||||
MOCK_METHOD0(OnSessionHealthCheck, bool());
|
||||
MOCK_METHOD1(OnCreateSessionBegin, bool(const AzFramework::SessionConfig&));
|
||||
MOCK_METHOD0(OnCreateSessionEnd, void());
|
||||
MOCK_METHOD0(OnDestroySessionBegin, bool());
|
||||
MOCK_METHOD0(OnDestroySessionEnd, void());
|
||||
MOCK_METHOD2(OnUpdateSessionBegin, void(const AzFramework::SessionConfig&, const AZStd::string&));
|
||||
MOCK_METHOD0(OnUpdateSessionEnd, void());
|
||||
};
|
||||
|
||||
class GameLiftServerManagerTest
|
||||
@@ -254,6 +264,7 @@ R"({
|
||||
EXPECT_CALL(handlerMock, OnDestroySessionBegin()).Times(1).WillOnce(testing::Return(false));
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), GetTerminationTime()).Times(1);
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ProcessEnding()).Times(0);
|
||||
EXPECT_CALL(handlerMock, OnDestroySessionEnd()).Times(0);
|
||||
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onProcessTerminateFunc();
|
||||
@@ -274,13 +285,40 @@ R"({
|
||||
SessionNotificationsHandlerMock handlerMock;
|
||||
EXPECT_CALL(handlerMock, OnDestroySessionBegin()).Times(1).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), GetTerminationTime()).Times(1);
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ProcessEnding()).Times(1);
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ProcessEnding())
|
||||
.Times(1)
|
||||
.WillOnce(testing::Return(Aws::GameLift::GenericOutcome(nullptr)));
|
||||
EXPECT_CALL(handlerMock, OnDestroySessionEnd()).Times(1);
|
||||
|
||||
m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onProcessTerminateFunc();
|
||||
|
||||
EXPECT_FALSE(AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get());
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, OnProcessTerminate_OnDestroySessionBeginReturnsTrue_TerminationNotificationSentButFail)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->NotifyGameLiftProcessReady();
|
||||
if (!AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get())
|
||||
{
|
||||
AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Register(m_serverManager.get());
|
||||
}
|
||||
|
||||
SessionNotificationsHandlerMock handlerMock;
|
||||
EXPECT_CALL(handlerMock, OnDestroySessionBegin()).Times(1).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), GetTerminationTime()).Times(1);
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ProcessEnding())
|
||||
.Times(1)
|
||||
.WillOnce(testing::Return(Aws::GameLift::GenericOutcome()));
|
||||
EXPECT_CALL(handlerMock, OnDestroySessionEnd()).Times(0);
|
||||
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onProcessTerminateFunc();
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
|
||||
EXPECT_FALSE(AZ::Interface<AzFramework::ISessionHandlingProviderRequests>::Get());
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, OnHealthCheck_OnSessionHealthCheckReturnsTrue_CallbackFunctionReturnsTrue)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
@@ -316,6 +354,7 @@ R"({
|
||||
m_serverManager->NotifyGameLiftProcessReady();
|
||||
SessionNotificationsHandlerMock handlerMock;
|
||||
EXPECT_CALL(handlerMock, OnCreateSessionBegin(testing::_)).Times(1).WillOnce(testing::Return(false));
|
||||
EXPECT_CALL(handlerMock, OnCreateSessionEnd()).Times(0);
|
||||
EXPECT_CALL(handlerMock, OnDestroySessionBegin()).Times(1).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ProcessEnding()).Times(1);
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
@@ -329,6 +368,7 @@ R"({
|
||||
m_serverManager->NotifyGameLiftProcessReady();
|
||||
SessionNotificationsHandlerMock handlerMock;
|
||||
EXPECT_CALL(handlerMock, OnCreateSessionBegin(testing::_)).Times(1).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(handlerMock, OnCreateSessionEnd()).Times(1);
|
||||
EXPECT_CALL(handlerMock, OnDestroySessionBegin()).Times(1).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ActivateGameSession())
|
||||
.Times(1)
|
||||
@@ -349,6 +389,7 @@ R"({
|
||||
m_serverManager->NotifyGameLiftProcessReady();
|
||||
SessionNotificationsHandlerMock handlerMock;
|
||||
EXPECT_CALL(handlerMock, OnCreateSessionBegin(testing::_)).Times(1).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(handlerMock, OnCreateSessionEnd()).Times(0);
|
||||
EXPECT_CALL(handlerMock, OnDestroySessionBegin()).Times(1).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(*(m_serverManager->m_gameLiftServerSDKWrapperMockPtr), ActivateGameSession())
|
||||
.Times(1)
|
||||
@@ -359,12 +400,13 @@ R"({
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithUnknownReason_OnUpdateSessionBeginGetCalledOnce)
|
||||
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithUnknownReason_OnUpdateSessionGetCalledOnce)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->NotifyGameLiftProcessReady();
|
||||
SessionNotificationsHandlerMock handlerMock;
|
||||
EXPECT_CALL(handlerMock, OnUpdateSessionBegin(testing::_, testing::_)).Times(1);
|
||||
EXPECT_CALL(handlerMock, OnUpdateSessionEnd()).Times(1);
|
||||
|
||||
m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onUpdateGameSessionFunc(
|
||||
Aws::GameLift::Server::Model::UpdateGameSession(
|
||||
@@ -373,12 +415,13 @@ R"({
|
||||
"testticket"));
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithEmptyMatchmakingData_OnUpdateSessionBeginGetCalledOnce)
|
||||
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithEmptyMatchmakingData_OnUpdateSessionGetCalledOnce)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->NotifyGameLiftProcessReady();
|
||||
SessionNotificationsHandlerMock handlerMock;
|
||||
EXPECT_CALL(handlerMock, OnUpdateSessionBegin(testing::_, testing::_)).Times(1);
|
||||
EXPECT_CALL(handlerMock, OnUpdateSessionEnd()).Times(1);
|
||||
|
||||
m_serverManager->m_gameLiftServerSDKWrapperMockPtr->m_onUpdateGameSessionFunc(
|
||||
Aws::GameLift::Server::Model::UpdateGameSession(
|
||||
@@ -387,12 +430,13 @@ R"({
|
||||
"testticket"));
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithValidMatchmakingData_OnUpdateSessionBeginGetCalledOnce)
|
||||
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithValidMatchmakingData_OnUpdateSessionGetCalledOnce)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->NotifyGameLiftProcessReady();
|
||||
SessionNotificationsHandlerMock handlerMock;
|
||||
EXPECT_CALL(handlerMock, OnUpdateSessionBegin(testing::_, testing::_)).Times(1);
|
||||
EXPECT_CALL(handlerMock, OnUpdateSessionEnd()).Times(1);
|
||||
|
||||
Aws::GameLift::Server::Model::GameSession gameSession;
|
||||
gameSession.SetMatchmakerData(TEST_SERVER_MATCHMAKING_DATA);
|
||||
@@ -401,12 +445,13 @@ R"({
|
||||
gameSession, Aws::GameLift::Server::Model::UpdateReason::MATCHMAKING_DATA_UPDATED, "testticket"));
|
||||
}
|
||||
|
||||
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithInvalidMatchmakingData_OnUpdateSessionBeginGetCalledOnce)
|
||||
TEST_F(GameLiftServerManagerTest, OnUpdateGameSession_TriggerWithInvalidMatchmakingData_OnUpdateSessionGetCalledOnce)
|
||||
{
|
||||
m_serverManager->InitializeGameLiftServerSDK();
|
||||
m_serverManager->NotifyGameLiftProcessReady();
|
||||
SessionNotificationsHandlerMock handlerMock;
|
||||
EXPECT_CALL(handlerMock, OnUpdateSessionBegin(testing::_, testing::_)).Times(1);
|
||||
EXPECT_CALL(handlerMock, OnUpdateSessionEnd()).Times(1);
|
||||
|
||||
Aws::GameLift::Server::Model::GameSession gameSession;
|
||||
gameSession.SetMatchmakerData("{invalid}");
|
||||
|
||||
@@ -122,14 +122,45 @@ namespace ImageProcessingAtom
|
||||
{
|
||||
format = ePixelFormat_BC1;
|
||||
}
|
||||
else if (header.ddspf.dwFourCC == FOURCC_DXT5 || header.ddspf.dwFourCC == FOURCC_DXT4)
|
||||
else if (header.ddspf.dwFourCC == FOURCC_DXT5)
|
||||
{
|
||||
format = ePixelFormat_BC3;
|
||||
}
|
||||
else
|
||||
else if (header.ddspf.dwFourCC == FOURCC_3DCP)
|
||||
{
|
||||
AZ_Error("Image Processing", false, "unsupported fourCC format: 0x%x", header.ddspf.dwFourCC);
|
||||
return nullptr;
|
||||
format = ePixelFormat_BC4;
|
||||
}
|
||||
else if (header.ddspf.dwFourCC == FOURCC_3DC)
|
||||
{
|
||||
format = ePixelFormat_BC5;
|
||||
}
|
||||
else if (header.ddspf.dwFourCC == DDS_FOURCC_R32F)
|
||||
{
|
||||
format = ePixelFormat_R32F;
|
||||
}
|
||||
else if (header.ddspf.dwFourCC == DDS_FOURCC_G32R32F)
|
||||
{
|
||||
format = ePixelFormat_R32G32F;
|
||||
}
|
||||
else if (header.ddspf.dwFourCC == DDS_FOURCC_A32B32G32R32F)
|
||||
{
|
||||
format = ePixelFormat_R32G32B32A32F;
|
||||
}
|
||||
else if (header.ddspf.dwFourCC == DDS_FOURCC_R16F)
|
||||
{
|
||||
format = ePixelFormat_R16F;
|
||||
}
|
||||
else if (header.ddspf.dwFourCC == DDS_FOURCC_G16R16F)
|
||||
{
|
||||
format = ePixelFormat_R16G16F;
|
||||
}
|
||||
else if (header.ddspf.dwFourCC == DDS_FOURCC_A16B16G16R16F)
|
||||
{
|
||||
format = ePixelFormat_R16G16B16A16F;
|
||||
}
|
||||
else if (header.ddspf.dwFourCC == DDS_FOURCC_A16B16G16R16)
|
||||
{
|
||||
format = ePixelFormat_R16G16B16A16;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -260,4 +260,6 @@ namespace ImageProcessingAtom
|
||||
const static AZ::u32 FOURCC_DXT3 = IMAGE_BUIDER_MAKEFOURCC('D', 'X', 'T', '3');
|
||||
const static AZ::u32 FOURCC_DXT4 = IMAGE_BUIDER_MAKEFOURCC('D', 'X', 'T', '4');
|
||||
const static AZ::u32 FOURCC_DXT5 = IMAGE_BUIDER_MAKEFOURCC('D', 'X', 'T', '5');
|
||||
const static AZ::u32 FOURCC_3DCP = IMAGE_BUIDER_MAKEFOURCC('A', 'T', 'I', '1');
|
||||
const static AZ::u32 FOURCC_3DC = IMAGE_BUIDER_MAKEFOURCC('A', 'T', 'I', '2');
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
},
|
||||
{
|
||||
"Name": "m_colorGradingPreSaturation",
|
||||
"Value": 1.0 // -100 ... 100
|
||||
"Value": 1.0 // 0 ... 2
|
||||
},
|
||||
{
|
||||
"Name": "m_colorFilterIntensity",
|
||||
@@ -101,7 +101,7 @@
|
||||
},
|
||||
{
|
||||
"Name": "m_colorGradingPostSaturation",
|
||||
"Value": 1.0 // -100 ... 100
|
||||
"Value": 1.0 // 0 ... 2
|
||||
},
|
||||
{
|
||||
"Name": "m_smhShadowsStart",
|
||||
|
||||
@@ -105,6 +105,11 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Name": "LutGenerationPass",
|
||||
"TemplateName": "LutGenerationTemplate",
|
||||
"Enabled": true
|
||||
},
|
||||
{
|
||||
"Name": "LookModificationTransformPass",
|
||||
"TemplateName": "LookModificationTransformTemplate",
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"Type": "JsonSerialization",
|
||||
"Version": 1,
|
||||
"ClassName": "PassAsset",
|
||||
"ClassData": {
|
||||
"PassTemplate": {
|
||||
"Name": "LutGenerationTemplate",
|
||||
"PassClass": "LutGenerationPass",
|
||||
"Slots": [
|
||||
{
|
||||
"Name": "LutOutput",
|
||||
"SlotType": "Output",
|
||||
"ScopeAttachmentUsage": "RenderTarget",
|
||||
"LoadStoreAction": {
|
||||
"LoadAction": "DontCare"
|
||||
}
|
||||
}
|
||||
],
|
||||
"ImageAttachments": [
|
||||
{
|
||||
"Name": "ColorGradingLut",
|
||||
"ImageDescriptor": {
|
||||
"Format": "R32G32B32A32_FLOAT",
|
||||
"BindFlags": [
|
||||
"ShaderWrite"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"Connections": [
|
||||
{
|
||||
"LocalSlot": "LutOutput",
|
||||
"AttachmentRef": {
|
||||
"Pass": "This",
|
||||
"Attachment": "ColorGradingLut"
|
||||
}
|
||||
}
|
||||
],
|
||||
"PassData": {
|
||||
"$type": "FullscreenTrianglePassData",
|
||||
"ShaderAsset": {
|
||||
"FilePath": "Shaders/ColorGrading/LutGeneration.shader"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -511,7 +511,11 @@
|
||||
{
|
||||
"Name": "HDRColorGradingTemplate",
|
||||
"Path": "Passes/HDRColorGrading.pass"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "LutGenerationTemplate",
|
||||
"Path": "Passes/LutGeneration.pass"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ UNDISCLOSED.
|
||||
|
||||
#pragma once
|
||||
|
||||
static const float HALF_MAX = 65504.0f;
|
||||
#include <Atom/Features/PostProcessing/Aces.azsli>
|
||||
|
||||
float AcesCcToLinear(float value)
|
||||
{
|
||||
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Atom/RPI/Math.azsli>
|
||||
#include <Atom/Features/ColorManagement/TransformColor.azsli>
|
||||
#include <Atom/Features/PostProcessing/AcesColorSpaceConversion.azsli>
|
||||
#include <3rdParty/Features/PostProcessing/PSstyleColorBlends_Separable.azsli>
|
||||
#include <3rdParty/Features/PostProcessing/PSstyleColorBlends_NonSeparable.azsli>
|
||||
#include <3rdParty/Features/PostProcessing/KelvinToRgb.azsli>
|
||||
|
||||
static const float FloatEpsilon = 1.192092896e-07; // 1.0 + FloatEpsilon != 1.0, smallest positive float
|
||||
static const float FloatMin = FLOAT_32_MIN; // Min float number that is positive
|
||||
static const float FloatMax = FLOAT_32_MAX; // Max float number representable
|
||||
|
||||
static const float AcesCcMidGrey = 0.4135884;
|
||||
|
||||
float SaturateWithEpsilon(float value)
|
||||
{
|
||||
return clamp(value, FloatEpsilon, 1.0f);
|
||||
}
|
||||
|
||||
// Below are the color grading functions. These expect the frame color to be in ACEScg space.
|
||||
// Note that some functions may have some quirks in their implementation and is subject to change.
|
||||
float3 ColorGradePostExposure (float3 frameColor, float exposure)
|
||||
{
|
||||
frameColor *= pow(2.0f, exposure);
|
||||
return frameColor;
|
||||
}
|
||||
|
||||
// The contrast equation is performed in ACEScc (logarithmic) color space.
|
||||
float3 ColorGradingContrast (float3 frameColor, float midgrey, float amount)
|
||||
{
|
||||
const float contrastAdjustment = amount * 0.01f + 1.0f;
|
||||
frameColor = TransformColor(frameColor.rgb, ColorSpaceId::ACEScg, ColorSpaceId::ACEScc);
|
||||
frameColor = (frameColor - midgrey) * contrastAdjustment + midgrey;
|
||||
return frameColor = TransformColor(frameColor.rgb, ColorSpaceId::ACEScc, ColorSpaceId::ACEScg);
|
||||
}
|
||||
|
||||
// The swatchColor param expects a linear RGB value.
|
||||
float3 ColorGradeColorFilter (float3 frameColor, float3 swatchColor, float alpha, float colorFilterIntensity)
|
||||
{
|
||||
swatchColor = TransformColor(swatchColor, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
|
||||
swatchColor *= pow(2.0f, colorFilterIntensity);
|
||||
const float3 frameAdjust = frameColor * swatchColor;
|
||||
return frameColor = lerp(frameColor, frameAdjust, alpha);
|
||||
}
|
||||
|
||||
float3 ColorGradeHueShift (float3 frameColor, float amount)
|
||||
{
|
||||
float3 frameHsv = RgbToHsv(frameColor);
|
||||
const float hue = frameHsv.x + amount;
|
||||
frameHsv.x = RotateHue(hue, 0.0, 1.0);
|
||||
return HsvToRgb(frameHsv);
|
||||
}
|
||||
|
||||
float3 ColorGradeSaturation (float3 frameColor, float control)
|
||||
{
|
||||
const float vLuminance = CalculateLuminance(frameColor, ColorSpaceId::ACEScg);
|
||||
return (frameColor - vLuminance) * control + vLuminance;
|
||||
}
|
||||
|
||||
float3 ColorGradeKelvinColorTemp(float3 frameColor, float kelvin)
|
||||
{
|
||||
const float3 kColor = TransformColor(KelvinToRgb(kelvin), ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
|
||||
const float luminance = CalculateLuminance(frameColor, ColorSpaceId::ACEScg);
|
||||
const float3 resHsl = RgbToHsl(frameColor.rgb * kColor.rgb); // Apply Kelvin color and convert to HSL
|
||||
return HslToRgb(float3(resHsl.xy, luminance)); // Preserve luminance
|
||||
}
|
||||
|
||||
// pow(f, e) won't work if f is negative, or may cause inf/NAN.
|
||||
float3 NoNanPow(float3 base, float3 power)
|
||||
{
|
||||
return pow(max(abs(base), float3(FloatEpsilon, FloatEpsilon, FloatEpsilon)), power);
|
||||
}
|
||||
|
||||
float3 ColorGradeSplitTone (
|
||||
float3 frameColor,
|
||||
float balance,
|
||||
float weight,
|
||||
float3 splitToneShadowsColor,
|
||||
float3 splitToneHighlightsColor)
|
||||
{
|
||||
float3 frameSplitTone = NoNanPow(frameColor, 1.0 / 2.2);
|
||||
const float t = SaturateWithEpsilon(CalculateLuminance(SaturateWithEpsilon(frameSplitTone), ColorSpaceId::ACEScg) + balance);
|
||||
const float3 shadows = lerp(0.5, splitToneShadowsColor, 1.0 - t);
|
||||
const float3 highlights = lerp(0.5, splitToneHighlightsColor, t);
|
||||
frameSplitTone = BlendMode_SoftLight(frameSplitTone, shadows);
|
||||
frameSplitTone = BlendMode_SoftLight(frameSplitTone, highlights);
|
||||
frameSplitTone = NoNanPow(frameSplitTone, 2.2);
|
||||
return lerp(frameColor.rgb, frameSplitTone.rgb, weight);
|
||||
}
|
||||
|
||||
float3 ColorGradeChannelMixer (
|
||||
float3 frameColor,
|
||||
float3 channelMixingRed,
|
||||
float3 channelMixingGreen,
|
||||
float3 channelMixingBlue)
|
||||
{
|
||||
return mul(float3x3(channelMixingRed,
|
||||
channelMixingGreen,
|
||||
channelMixingBlue),
|
||||
frameColor);
|
||||
}
|
||||
|
||||
float3 ColorGradeShadowsMidtonesHighlights (float3 frameColor, float shadowsStart, float shadowsEnd,
|
||||
float highlightsStart, float highlightsEnd, float weight,
|
||||
float4 shadowsColor, float4 midtonesColor, float4 highlightsColor)
|
||||
{
|
||||
const float3 shadowsColorACEScg = TransformColor(shadowsColor.rgb, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
|
||||
const float3 midtonesColorACEScg = TransformColor(midtonesColor.rgb, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
|
||||
const float3 highlightsColorACEScg = TransformColor(highlightsColor.rgb, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
|
||||
|
||||
const float cLuminance = CalculateLuminance(frameColor, ColorSpaceId::ACEScg);
|
||||
const float shadowsWeight = 1.0 - smoothstep(shadowsStart, shadowsEnd, cLuminance);
|
||||
const float highlightsWeight = smoothstep(highlightsStart, highlightsEnd, cLuminance);
|
||||
const float midtonesWeight = 1.0 - shadowsWeight - highlightsWeight;
|
||||
|
||||
const float3 frameSmh = frameColor * shadowsColorACEScg * shadowsWeight +
|
||||
frameColor * midtonesColorACEScg * midtonesWeight +
|
||||
frameColor * highlightsColorACEScg * highlightsWeight;
|
||||
return lerp(frameColor.rgb, frameSmh.rgb, weight);
|
||||
}
|
||||
|
||||
// perform color grading in ACEScg space
|
||||
float3 ColorGrade(float3 frameColor)
|
||||
{
|
||||
frameColor = lerp(frameColor, ColorGradePostExposure(frameColor, PassSrg::m_colorGradingExposure), PassSrg::m_colorAdjustmentWeight);
|
||||
frameColor = lerp(frameColor, ColorGradeKelvinColorTemp(frameColor, PassSrg::m_whiteBalanceKelvin), PassSrg::m_whiteBalanceWeight);
|
||||
frameColor = lerp(frameColor, ColorGradingContrast(frameColor, AcesCcMidGrey, PassSrg::m_colorGradingContrast), PassSrg::m_colorAdjustmentWeight);
|
||||
frameColor = lerp(frameColor, ColorGradeColorFilter(frameColor, PassSrg::m_colorFilterSwatch.rgb,
|
||||
PassSrg::m_colorFilterMultiply, PassSrg::m_colorFilterIntensity), PassSrg::m_colorAdjustmentWeight);
|
||||
frameColor = max(frameColor, 0.0);
|
||||
frameColor = lerp(frameColor, ColorGradeSaturation(frameColor, PassSrg::m_colorGradingPreSaturation), PassSrg::m_colorAdjustmentWeight);
|
||||
|
||||
frameColor = ColorGradeSplitTone(frameColor, PassSrg::m_splitToneBalance, PassSrg::m_splitToneWeight,
|
||||
PassSrg::m_splitToneShadowsColor, PassSrg::m_splitToneHighlightsColor);
|
||||
frameColor = ColorGradeChannelMixer(frameColor, PassSrg::m_channelMixingRed, PassSrg::m_channelMixingGreen, PassSrg::m_channelMixingBlue);
|
||||
frameColor = max(frameColor, 0.0);
|
||||
frameColor = ColorGradeShadowsMidtonesHighlights(frameColor, PassSrg::m_smhShadowsStart, PassSrg::m_smhShadowsEnd,
|
||||
PassSrg::m_smhHighlightsStart, PassSrg::m_smhHighlightsEnd, PassSrg::m_smhWeight,
|
||||
PassSrg::m_smhShadowsColor, PassSrg::m_smhMidtonesColor, PassSrg::m_smhHighlightsColor);
|
||||
|
||||
|
||||
frameColor = lerp(frameColor, ColorGradeSaturation(frameColor, PassSrg::m_colorGradingPostSaturation), PassSrg::m_finalAdjustmentWeight);
|
||||
frameColor = lerp(frameColor, ColorGradeHueShift(frameColor, PassSrg::m_colorGradingHueShift), PassSrg::m_finalAdjustmentWeight);
|
||||
return max(frameColor.rgb, 0.0);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Atom/RPI/Math.azsli>
|
||||
|
||||
#include <Atom/Features/SrgSemantics.azsli>
|
||||
|
||||
#include <Atom/Features/PostProcessing/FullscreenVertex.azsli>
|
||||
|
||||
#include <Atom/Features/PostProcessing/Aces.azsli>
|
||||
#include <Atom/Features/PostProcessing/Shapers.azsli>
|
||||
|
||||
float3 convert2Dto3DLutCoords(float2 uv, float width, float height)
|
||||
{
|
||||
// convert from center pixel uvs to [0,1]
|
||||
float offset = 1.0/height/2.0;
|
||||
float scale = 1.0 - offset*2.0;
|
||||
|
||||
float2 adjustedUv = float2(uv.x * width, uv.y * height);
|
||||
float3 coords = float3(adjustedUv.x%height, 0.5 + int(adjustedUv.x/height), adjustedUv.y)/height;
|
||||
return (coords - offset)/scale;
|
||||
}
|
||||
|
||||
ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
|
||||
{
|
||||
// framebuffer sampler
|
||||
Sampler PointSampler
|
||||
{
|
||||
MinFilter = Point;
|
||||
MagFilter = Point;
|
||||
MipFilter = Point;
|
||||
AddressU = Clamp;
|
||||
AddressV = Clamp;
|
||||
AddressW = Clamp;
|
||||
};
|
||||
|
||||
int m_lutResolution;
|
||||
int m_shaperType;
|
||||
float m_shaperBias;
|
||||
float m_shaperScale;
|
||||
|
||||
float m_colorAdjustmentWeight;
|
||||
float m_colorGradingExposure;
|
||||
float m_colorGradingContrast;
|
||||
float m_colorGradingPreSaturation;
|
||||
float m_colorFilterIntensity;
|
||||
float m_colorFilterMultiply;
|
||||
float4 m_colorFilterSwatch;
|
||||
|
||||
float m_whiteBalanceWeight;
|
||||
float m_whiteBalanceKelvin;
|
||||
float m_whiteBalanceTint;
|
||||
|
||||
float m_splitToneBalance;
|
||||
float m_splitToneWeight;
|
||||
float4 m_splitToneShadowsColor;
|
||||
float4 m_splitToneHighlightsColor;
|
||||
|
||||
float m_smhShadowsStart;
|
||||
float m_smhShadowsEnd;
|
||||
float m_smhHighlightsStart;
|
||||
float m_smhHighlightsEnd;
|
||||
float m_smhWeight;
|
||||
float4 m_smhShadowsColor;
|
||||
float4 m_smhMidtonesColor;
|
||||
float4 m_smhHighlightsColor;
|
||||
|
||||
float3 m_channelMixingRed;
|
||||
float3 m_channelMixingGreen;
|
||||
float3 m_channelMixingBlue;
|
||||
|
||||
float m_finalAdjustmentWeight;
|
||||
float m_colorGradingPostSaturation;
|
||||
float m_colorGradingHueShift;
|
||||
}
|
||||
|
||||
#include <Atom/Features/PostProcessing/HDRColorGradingCommon.azsl>
|
||||
|
||||
struct PSOutput
|
||||
{
|
||||
float4 m_lutOutput : SV_Target0;
|
||||
};
|
||||
|
||||
PSOutput MainPS(VSOutput IN)
|
||||
{
|
||||
ShaperType shaperType = (ShaperType)PassSrg::m_shaperType;
|
||||
int lutResolution = PassSrg::m_lutResolution;
|
||||
|
||||
PSOutput OUT;
|
||||
|
||||
// baseCoords are from 0-1
|
||||
float3 baseCoords = convert2Dto3DLutCoords(IN.m_texCoord, lutResolution*lutResolution, lutResolution);
|
||||
|
||||
float3 linearColor = ShaperToLinear(baseCoords, shaperType, PassSrg::m_shaperBias, PassSrg::m_shaperScale);
|
||||
|
||||
linearColor = TransformColor(linearColor, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
|
||||
float3 gradedColor = ColorGrade(linearColor);
|
||||
gradedColor = TransformColor(gradedColor, ColorSpaceId::ACEScg, ColorSpaceId::LinearSRGB);
|
||||
|
||||
// Bring back coordinates into 0-1
|
||||
float3 shapedColor = LinearToShaper(gradedColor, shaperType, PassSrg::m_shaperBias, PassSrg::m_shaperScale);
|
||||
shapedColor = saturate(shapedColor);
|
||||
|
||||
OUT.m_lutOutput = float4(shapedColor, 1.0);
|
||||
return OUT;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"Source" : "LutGeneration",
|
||||
|
||||
"DepthStencilState" : {
|
||||
"Depth" : { "Enable" : false }
|
||||
},
|
||||
|
||||
"ProgramSettings":
|
||||
{
|
||||
"EntryPoints":
|
||||
[
|
||||
{
|
||||
"name": "MainVS",
|
||||
"type": "Vertex"
|
||||
},
|
||||
{
|
||||
"name": "MainPS",
|
||||
"type": "Fragment"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -13,18 +13,6 @@
|
||||
#include <Atom/Features/PostProcessing/FullscreenPixelInfo.azsli>
|
||||
#include <Atom/Features/PostProcessing/FullscreenVertex.azsli>
|
||||
|
||||
#include <Atom/Features/ColorManagement/TransformColor.azsli>
|
||||
#include <Atom/Features/PostProcessing/AcesColorSpaceConversion.azsli>
|
||||
#include <3rdParty/Features/PostProcessing/PSstyleColorBlends_Separable.azsli>
|
||||
#include <3rdParty/Features/PostProcessing/PSstyleColorBlends_NonSeparable.azsli>
|
||||
#include <3rdParty/Features/PostProcessing/KelvinToRgb.azsli>
|
||||
|
||||
static const float FloatEpsilon = 1.192092896e-07; // 1.0 + FloatEpsilon != 1.0, smallest positive float
|
||||
static const float FloatMin = FLOAT_32_MIN; // Min float number that is positive
|
||||
static const float FloatMax = FLOAT_32_MAX; // Max float number representable
|
||||
|
||||
static const float AcesCcMidGrey = 0.4135884;
|
||||
|
||||
ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
|
||||
{
|
||||
// get the framebuffer
|
||||
@@ -41,153 +29,42 @@ ShaderResourceGroup PassSrg : SRG_PerPass_WithFallback
|
||||
AddressW = Clamp;
|
||||
};
|
||||
|
||||
float m_colorAdjustmentWeight;
|
||||
float m_colorGradingExposure;
|
||||
float m_colorGradingContrast;
|
||||
float m_colorGradingHueShift;
|
||||
float m_colorGradingPreSaturation;
|
||||
float m_colorFilterIntensity;
|
||||
float m_colorFilterMultiply;
|
||||
float4 m_colorFilterSwatch;
|
||||
|
||||
float m_whiteBalanceWeight;
|
||||
float m_whiteBalanceKelvin;
|
||||
float m_whiteBalanceTint;
|
||||
|
||||
float m_splitToneBalance;
|
||||
float m_splitToneWeight;
|
||||
float m_colorGradingPostSaturation;
|
||||
float4 m_splitToneShadowsColor;
|
||||
float4 m_splitToneHighlightsColor;
|
||||
|
||||
float m_smhShadowsStart;
|
||||
float m_smhShadowsEnd;
|
||||
float m_smhHighlightsStart;
|
||||
float m_smhHighlightsEnd;
|
||||
float m_smhWeight;
|
||||
float4 m_smhShadowsColor;
|
||||
float4 m_smhMidtonesColor;
|
||||
float4 m_smhHighlightsColor;
|
||||
|
||||
float3 m_channelMixingRed;
|
||||
float3 m_channelMixingGreen;
|
||||
float3 m_channelMixingBlue;
|
||||
|
||||
float4 m_colorFilterSwatch;
|
||||
float4 m_splitToneShadowsColor;
|
||||
float4 m_splitToneHighlightsColor;
|
||||
|
||||
float4 m_smhShadowsColor;
|
||||
float4 m_smhMidtonesColor;
|
||||
float4 m_smhHighlightsColor;
|
||||
float m_finalAdjustmentWeight;
|
||||
float m_colorGradingPostSaturation;
|
||||
float m_colorGradingHueShift;
|
||||
}
|
||||
|
||||
float SaturateWithEpsilon(float value)
|
||||
{
|
||||
return clamp(value, FloatEpsilon, 1.0f);
|
||||
}
|
||||
|
||||
// Below are the color grading functions. These expect the frame color to be in ACEScg space.
|
||||
// Note that some functions may have some quirks in their implementation and is subject to change.
|
||||
float3 ColorGradePostExposure (float3 frameColor, float exposure)
|
||||
{
|
||||
frameColor *= pow(2.0f, exposure);
|
||||
return frameColor;
|
||||
}
|
||||
|
||||
// The contrast equation is performed in ACEScc (logarithmic) color space.
|
||||
float3 ColorGradingContrast (float3 frameColor, float midgrey, float amount)
|
||||
{
|
||||
const float contrastAdjustment = amount * 0.01f + 1.0f;
|
||||
frameColor = TransformColor(frameColor.rgb, ColorSpaceId::ACEScg, ColorSpaceId::ACEScc);
|
||||
frameColor = (frameColor - midgrey) * contrastAdjustment + midgrey;
|
||||
return frameColor = TransformColor(frameColor.rgb, ColorSpaceId::ACEScc, ColorSpaceId::ACEScg);
|
||||
}
|
||||
|
||||
// The swatchColor param expects a linear RGB value.
|
||||
float3 ColorGradeColorFilter (float3 frameColor, float3 swatchColor, float alpha)
|
||||
{
|
||||
swatchColor = TransformColor(swatchColor, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
|
||||
swatchColor *= pow(2.0f, PassSrg::m_colorFilterIntensity);
|
||||
const float3 frameAdjust = frameColor * swatchColor;
|
||||
return frameColor = lerp(frameColor, frameAdjust, alpha);
|
||||
}
|
||||
|
||||
float3 ColorGradeHueShift (float3 frameColor, float amount)
|
||||
{
|
||||
float3 frameHsv = RgbToHsv(frameColor);
|
||||
const float hue = frameHsv.x + amount;
|
||||
frameHsv.x = RotateHue(hue, 0.0, 1.0);
|
||||
return HsvToRgb(frameHsv);
|
||||
}
|
||||
|
||||
float3 ColorGradeSaturation (float3 frameColor, float control)
|
||||
{
|
||||
const float vLuminance = CalculateLuminance(frameColor, ColorSpaceId::ACEScg);
|
||||
return (frameColor - vLuminance) * control + vLuminance;
|
||||
}
|
||||
|
||||
float3 ColorGradeKelvinColorTemp(float3 frameColor, float kelvin)
|
||||
{
|
||||
const float3 kColor = TransformColor(KelvinToRgb(kelvin), ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
|
||||
const float luminance = CalculateLuminance(frameColor, ColorSpaceId::ACEScg);
|
||||
const float3 resHsl = RgbToHsl(frameColor.rgb * kColor.rgb); // Apply Kelvin color and convert to HSL
|
||||
return HslToRgb(float3(resHsl.xy, luminance)); // Preserve luminance
|
||||
}
|
||||
|
||||
// pow(f, e) won't work if f is negative, or may cause inf/NAN.
|
||||
float3 NoNanPow(float3 base, float3 power)
|
||||
{
|
||||
return pow(max(abs(base), float3(FloatEpsilon, FloatEpsilon, FloatEpsilon)), power);
|
||||
}
|
||||
|
||||
float3 ColorGradeSplitTone (float3 frameColor, float balance, float weight)
|
||||
{
|
||||
float3 frameSplitTone = NoNanPow(frameColor, 1.0 / 2.2);
|
||||
const float t = SaturateWithEpsilon(CalculateLuminance(SaturateWithEpsilon(frameSplitTone), ColorSpaceId::ACEScg) + balance);
|
||||
const float3 shadows = lerp(0.5, PassSrg::m_splitToneShadowsColor.rgb, 1.0 - t);
|
||||
const float3 highlights = lerp(0.5, PassSrg::m_splitToneHighlightsColor.rgb, t);
|
||||
frameSplitTone = BlendMode_SoftLight(frameSplitTone, shadows);
|
||||
frameSplitTone = BlendMode_SoftLight(frameSplitTone, highlights);
|
||||
frameSplitTone = NoNanPow(frameSplitTone, 2.2);
|
||||
return lerp(frameColor.rgb, frameSplitTone.rgb, weight);
|
||||
}
|
||||
|
||||
float3 ColorGradeChannelMixer (float3 frameColor)
|
||||
{
|
||||
return mul(float3x3(PassSrg::m_channelMixingRed.rgb,
|
||||
PassSrg::m_channelMixingGreen.rgb,
|
||||
PassSrg::m_channelMixingBlue.rgb),
|
||||
frameColor);
|
||||
}
|
||||
|
||||
float3 ColorGradeShadowsMidtonesHighlights (float3 frameColor, float shadowsStart, float shadowsEnd,
|
||||
float highlightsStart, float highlightsEnd, float weight,
|
||||
float4 shadowsColor, float4 midtonesColor, float4 highlightsColor)
|
||||
{
|
||||
const float3 shadowsColorACEScg = TransformColor(shadowsColor.rgb, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
|
||||
const float3 midtonesColorACEScg = TransformColor(midtonesColor.rgb, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
|
||||
const float3 highlightsColorACEScg = TransformColor(highlightsColor.rgb, ColorSpaceId::LinearSRGB, ColorSpaceId::ACEScg);
|
||||
|
||||
const float cLuminance = CalculateLuminance(frameColor, ColorSpaceId::ACEScg);
|
||||
const float shadowsWeight = 1.0 - smoothstep(shadowsStart, shadowsEnd, cLuminance);
|
||||
const float highlightsWeight = smoothstep(highlightsStart, highlightsEnd, cLuminance);
|
||||
const float midtonesWeight = 1.0 - shadowsWeight - highlightsWeight;
|
||||
|
||||
const float3 frameSmh = frameColor * shadowsColorACEScg * shadowsWeight +
|
||||
frameColor * midtonesColorACEScg * midtonesWeight +
|
||||
frameColor * highlightsColorACEScg * highlightsWeight;
|
||||
return lerp(frameColor.rgb, frameSmh.rgb, weight);
|
||||
}
|
||||
|
||||
float3 ColorGrade (float3 frameColor)
|
||||
{
|
||||
frameColor = ColorGradePostExposure(frameColor, PassSrg::m_colorGradingExposure);
|
||||
frameColor = ColorGradeKelvinColorTemp(frameColor, PassSrg::m_whiteBalanceKelvin);
|
||||
frameColor = ColorGradingContrast(frameColor, AcesCcMidGrey, PassSrg::m_colorGradingContrast);
|
||||
frameColor = ColorGradeColorFilter(frameColor, PassSrg::m_colorFilterSwatch.rgb,
|
||||
PassSrg::m_colorFilterMultiply);
|
||||
frameColor = max(frameColor, 0.0);
|
||||
frameColor = ColorGradeSaturation(frameColor, PassSrg::m_colorGradingPreSaturation);
|
||||
frameColor = ColorGradeSplitTone(frameColor, PassSrg::m_splitToneBalance, PassSrg::m_splitToneWeight);
|
||||
frameColor = ColorGradeChannelMixer(frameColor);
|
||||
frameColor = max(frameColor, 0.0);
|
||||
frameColor = ColorGradeShadowsMidtonesHighlights(frameColor, PassSrg::m_smhShadowsStart, PassSrg::m_smhShadowsEnd,
|
||||
PassSrg::m_smhHighlightsStart, PassSrg::m_smhHighlightsEnd, PassSrg::m_smhWeight,
|
||||
PassSrg::m_smhShadowsColor, PassSrg::m_smhMidtonesColor, PassSrg::m_smhHighlightsColor);
|
||||
frameColor = ColorGradeHueShift(frameColor, PassSrg::m_colorGradingHueShift);
|
||||
frameColor = ColorGradeSaturation(frameColor, PassSrg::m_colorGradingPostSaturation);
|
||||
return frameColor.rgb;
|
||||
}
|
||||
#include <Atom/Features/PostProcessing/HDRColorGradingCommon.azsl>
|
||||
|
||||
PSOutput MainPS(VSOutput IN)
|
||||
{
|
||||
|
||||
@@ -86,7 +86,6 @@ set(FILES
|
||||
Passes/CascadedShadowmaps.pass
|
||||
Passes/CheckerboardResolveColor.pass
|
||||
Passes/CheckerboardResolveDepth.pass
|
||||
Passes/HDRColorGrading.pass
|
||||
Passes/ContrastAdaptiveSharpening.pass
|
||||
Passes/ConvertToAcescg.pass
|
||||
Passes/DebugOverlayParent.pass
|
||||
@@ -144,6 +143,7 @@ set(FILES
|
||||
Passes/ForwardSubsurfaceMSAA.pass
|
||||
Passes/FullscreenCopy.pass
|
||||
Passes/FullscreenOutputOnly.pass
|
||||
Passes/HDRColorGrading.pass
|
||||
Passes/ImGui.pass
|
||||
Passes/KawaseShadowBlur.pass
|
||||
Passes/LightAdaptationParent.pass
|
||||
@@ -158,6 +158,7 @@ set(FILES
|
||||
Passes/LowEndPipeline.pass
|
||||
Passes/LuminanceHeatmap.pass
|
||||
Passes/LuminanceHistogramGenerator.pass
|
||||
Passes/LutGeneration.pass
|
||||
Passes/MainPipeline.pass
|
||||
Passes/MainPipelineRenderToTexture.pass
|
||||
Passes/ThumbnailPipeline.pass
|
||||
@@ -281,6 +282,7 @@ set(FILES
|
||||
ShaderLib/Atom/Features/PostProcessing/FullscreenVertexUtil.azsli
|
||||
ShaderLib/Atom/Features/PostProcessing/GlyphData.azsli
|
||||
ShaderLib/Atom/Features/PostProcessing/GlyphRender.azsli
|
||||
ShaderLib/Atom/Features/PostProcessing/HDRColorGradingCommon.azsl
|
||||
ShaderLib/Atom/Features/PostProcessing/PostProcessUtil.azsli
|
||||
ShaderLib/Atom/Features/RayTracing/RayTracingSceneSrg.azsli
|
||||
ShaderLib/Atom/Features/ScreenSpace/ScreenSpaceUtil.azsli
|
||||
@@ -315,6 +317,8 @@ set(FILES
|
||||
Shaders/BRDFTexture/BRDFTextureCS.shader
|
||||
Shaders/Checkerboard/CheckerboardColorResolveCS.azsl
|
||||
Shaders/Checkerboard/CheckerboardColorResolveCS.shader
|
||||
Shaders/ColorGrading/LutGeneration.azsl
|
||||
Shaders/ColorGrading/LutGeneration.shader
|
||||
Shaders/Depth/DepthPass.azsl
|
||||
Shaders/Depth/DepthPass.shader
|
||||
Shaders/Depth/DepthPassTransparentMax.shader
|
||||
|
||||
@@ -39,6 +39,7 @@ ly_add_target(
|
||||
Gem::Atom_Utils.Static
|
||||
Gem::Atom_Feature_Common.Public
|
||||
Gem::ImGui.imguilib
|
||||
3rdParty::TIFF
|
||||
#3rdParty::lux_core # AZ_TRAIT_LUXCORE_SUPPORTED is disabled in every platform, Issue #3915 will remove
|
||||
RUNTIME_DEPENDENCIES
|
||||
Gem::ImGui.imguilib
|
||||
@@ -75,6 +76,7 @@ ly_add_target(
|
||||
FILES_CMAKE
|
||||
atom_feature_common_shared_files.cmake
|
||||
../Assets/atom_feature_common_asset_files.cmake
|
||||
../Editor/atom_feature_common_editor_script_files.cmake
|
||||
PLATFORM_INCLUDE_FILES
|
||||
${pal_source_dir}/runtime_dependencies_clients.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Render
|
||||
{
|
||||
enum class LutResolution
|
||||
{
|
||||
Lut16x16x16 = 16,
|
||||
Lut32x32x32 = 32,
|
||||
Lut64x64x64 = 64
|
||||
};
|
||||
}
|
||||
}
|
||||
+25
-11
@@ -10,28 +10,42 @@
|
||||
// PARAM(NAME, MEMBER_NAME, DEFAULT_VALUE, ...)
|
||||
|
||||
AZ_GFX_BOOL_PARAM(Enabled, m_enabled, false)
|
||||
AZ_GFX_BOOL_PARAM(GenerateLut, m_generateLut, false)
|
||||
AZ_GFX_FLOAT_PARAM(ColorAdjustmentWeight, m_colorAdjustmentWeight, 1.0)
|
||||
AZ_GFX_FLOAT_PARAM(ColorGradingExposure, m_colorGradingExposure, 0.0)
|
||||
AZ_GFX_FLOAT_PARAM(ColorGradingContrast, m_colorGradingContrast, 0.0)
|
||||
AZ_GFX_FLOAT_PARAM(ColorGradingHueShift, m_colorGradingHueShift, 0.0)
|
||||
AZ_GFX_FLOAT_PARAM(ColorGradingPreSaturation, m_colorGradingPreSaturation, 1.0)
|
||||
AZ_GFX_FLOAT_PARAM(ColorGradingPreSaturation, m_colorGradingPreSaturation, 0.0)
|
||||
AZ_GFX_FLOAT_PARAM(ColorGradingFilterIntensity, m_colorGradingFilterIntensity, 1.0)
|
||||
AZ_GFX_FLOAT_PARAM(ColorGradingFilterMultiply, m_colorGradingFilterMultiply, 0.0)
|
||||
AZ_GFX_FLOAT_PARAM(ColorGradingPostSaturation, m_colorGradingPostSaturation, 1.0)
|
||||
AZ_GFX_VEC3_PARAM(ColorFilterSwatch, m_colorFilterSwatch, AZ::Vector3(1.0f, 0.5f, 0.5f))
|
||||
|
||||
AZ_GFX_FLOAT_PARAM(WhiteBalanceWeight, m_whiteBalanceWeight, 0.0)
|
||||
AZ_GFX_FLOAT_PARAM(WhiteBalanceKelvin, m_whiteBalanceKelvin, 6600.0)
|
||||
AZ_GFX_FLOAT_PARAM(WhiteBalanceTint, m_whiteBalanceTint, 0.0)
|
||||
AZ_GFX_FLOAT_PARAM(SplitToneBalance, m_splitToneBalance, 0.0)
|
||||
|
||||
AZ_GFX_FLOAT_PARAM(SplitToneWeight, m_splitToneWeight, 0.0)
|
||||
AZ_GFX_FLOAT_PARAM(SplitToneBalance, m_splitToneBalance, 0.0)
|
||||
AZ_GFX_VEC3_PARAM(SplitToneShadowsColor, m_splitToneShadowsColor, AZ::Vector3(1.0f, 0.5f, 0.5f))
|
||||
AZ_GFX_VEC3_PARAM(SplitToneHighlightsColor, m_splitToneHighlightsColor, AZ::Vector3(0.1f, 1.0f, 0.1f))
|
||||
|
||||
AZ_GFX_FLOAT_PARAM(SmhWeight, m_smhWeight, 0.0)
|
||||
AZ_GFX_FLOAT_PARAM(SmhShadowsStart, m_smhShadowsStart, 0.0)
|
||||
AZ_GFX_FLOAT_PARAM(SmhShadowsEnd, m_smhShadowsEnd, 0.3)
|
||||
AZ_GFX_FLOAT_PARAM(SmhHighlightsStart, m_smhHighlightsStart, 0.55)
|
||||
AZ_GFX_FLOAT_PARAM(SmhHighlightsEnd, m_smhHighlightsEnd, 1.0)
|
||||
AZ_GFX_FLOAT_PARAM(SmhWeight, m_smhWeight, 0.0)
|
||||
AZ_GFX_VEC3_PARAM(ChannelMixingRed, m_channelMixingRed, AZ::Vector3(1.0f, 0.0f, 0.0f))
|
||||
AZ_GFX_VEC3_PARAM(ChannelMixingGreen, m_channelMixingGreen, AZ::Vector3(0.0f, 1.0f, 0.0f))
|
||||
AZ_GFX_VEC3_PARAM(ChannelMixingBlue, m_channelMixingBlue, AZ::Vector3(0.0f, 0.f, 1.0f))
|
||||
AZ_GFX_VEC3_PARAM(ColorFilterSwatch, m_colorFilterSwatch, AZ::Vector3(1.0f, 0.5f, 0.5f))
|
||||
AZ_GFX_VEC3_PARAM(SplitToneShadowsColor, m_splitToneShadowsColor, AZ::Vector3(1.0f, 0.5f, 0.5f))
|
||||
AZ_GFX_VEC3_PARAM(SplitToneHighlightsColor, m_splitToneHighlightsColor, AZ::Vector3(0.1f, 1.0f, 0.1f))
|
||||
AZ_GFX_VEC3_PARAM(SmhShadowsColor, m_smhShadowsColor, AZ::Vector3(1.0f, 0.25f, 0.25f))
|
||||
AZ_GFX_VEC3_PARAM(SmhMidtonesColor, m_smhMidtonesColor, AZ::Vector3(0.1f, 0.1f, 1.0f))
|
||||
AZ_GFX_VEC3_PARAM(SmhHighlightsColor, m_smhHighlightsColor, AZ::Vector3(1.0f, 0.0f, 1.0f))
|
||||
|
||||
AZ_GFX_VEC3_PARAM(ChannelMixingRed, m_channelMixingRed, AZ::Vector3(1.0f, 0.0f, 0.0f))
|
||||
AZ_GFX_VEC3_PARAM(ChannelMixingGreen, m_channelMixingGreen, AZ::Vector3(0.0f, 1.0f, 0.0f))
|
||||
AZ_GFX_VEC3_PARAM(ChannelMixingBlue, m_channelMixingBlue, AZ::Vector3(0.0f, 0.f, 1.0f))
|
||||
|
||||
AZ_GFX_COMMON_PARAM(AZ::Render::LutResolution, LutResolution, m_lutResolution, AZ::Render::LutResolution::Lut16x16x16)
|
||||
AZ_GFX_COMMON_PARAM(AZ::Render::ShaperPresetType, ShaperPresetType, m_shaperPresetType, AZ::Render::ShaperPresetType::None)
|
||||
AZ_GFX_COMMON_PARAM(float, CustomMinExposure, m_customMinExposure, -6.5)
|
||||
AZ_GFX_COMMON_PARAM(float, CustomMaxExposure, m_customMaxExposure, 6.5)
|
||||
|
||||
AZ_GFX_FLOAT_PARAM(FinalAdjustmentWeight, m_finalAdjustmentWeight, 1.0)
|
||||
AZ_GFX_FLOAT_PARAM(ColorGradingPostSaturation, m_colorGradingPostSaturation, 0.0)
|
||||
AZ_GFX_FLOAT_PARAM(ColorGradingHueShift, m_colorGradingHueShift, 0.0)
|
||||
|
||||
+2
@@ -12,6 +12,8 @@
|
||||
#include <AzCore/Math/Vector4.h>
|
||||
#include <AzCore/RTTI/RTTI.h>
|
||||
#include <AzCore/Component/EntityId.h>
|
||||
#include <Atom/Feature/ColorGrading/LutResolution.h>
|
||||
#include <ACES/Aces.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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 <ColorGrading/LutGenerationPass.h>
|
||||
#include <Atom/Feature/ACES/AcesDisplayMapperFeatureProcessor.h>
|
||||
|
||||
#include <Atom/RPI.Public/Scene.h>
|
||||
#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Render
|
||||
{
|
||||
|
||||
RPI::Ptr<LutGenerationPass> LutGenerationPass::Create(const RPI::PassDescriptor& descriptor)
|
||||
{
|
||||
RPI::Ptr<LutGenerationPass> pass = aznew LutGenerationPass(descriptor);
|
||||
return AZStd::move(pass);
|
||||
}
|
||||
|
||||
LutGenerationPass::LutGenerationPass(const RPI::PassDescriptor& descriptor)
|
||||
: HDRColorGradingPass(descriptor)
|
||||
{
|
||||
}
|
||||
|
||||
void LutGenerationPass::InitializeInternal()
|
||||
{
|
||||
HDRColorGradingPass::InitializeInternal();
|
||||
|
||||
m_lutResolutionIndex.Reset();
|
||||
m_lutShaperTypeIndex.Reset();
|
||||
m_lutShaperScaleIndex.Reset();
|
||||
}
|
||||
|
||||
void LutGenerationPass::FrameBeginInternal(FramePrepareParams params)
|
||||
{
|
||||
const auto* colorGradingSettings = GetHDRColorGradingSettings();
|
||||
if (colorGradingSettings)
|
||||
{
|
||||
m_shaderResourceGroup->SetConstant(m_lutResolutionIndex, colorGradingSettings->GetLutResolution());
|
||||
|
||||
auto shaperParams = AcesDisplayMapperFeatureProcessor::GetShaperParameters(
|
||||
colorGradingSettings->GetShaperPresetType(),
|
||||
colorGradingSettings->GetCustomMinExposure(),
|
||||
colorGradingSettings->GetCustomMaxExposure());
|
||||
m_shaderResourceGroup->SetConstant(m_lutShaperTypeIndex, shaperParams.m_type);
|
||||
m_shaderResourceGroup->SetConstant(m_lutShaperBiasIndex, shaperParams.m_bias);
|
||||
m_shaderResourceGroup->SetConstant(m_lutShaperScaleIndex, shaperParams.m_scale);
|
||||
}
|
||||
|
||||
HDRColorGradingPass::FrameBeginInternal(params);
|
||||
}
|
||||
|
||||
void LutGenerationPass::BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context)
|
||||
{
|
||||
const auto* colorGradingSettings = GetHDRColorGradingSettings();
|
||||
if (colorGradingSettings)
|
||||
{
|
||||
uint32_t lutResolution = aznumeric_cast<uint32_t>(colorGradingSettings->GetLutResolution());
|
||||
RHI::Size lutSize{ lutResolution * lutResolution, lutResolution, 1 };
|
||||
|
||||
RPI::Ptr<RPI::PassAttachment> attachment = FindOwnedAttachment(Name{ "ColorGradingLut" });
|
||||
RHI::ImageDescriptor& imageDescriptor = attachment->m_descriptor.m_image;
|
||||
imageDescriptor.m_size = lutSize;
|
||||
SetViewportScissorFromImageSize(lutSize);
|
||||
}
|
||||
HDRColorGradingPass::BuildCommandListInternal(context);
|
||||
}
|
||||
|
||||
bool LutGenerationPass::IsEnabled() const
|
||||
{
|
||||
const auto* colorGradingSettings = GetHDRColorGradingSettings();
|
||||
return colorGradingSettings ? colorGradingSettings->GetGenerateLut() : false;
|
||||
}
|
||||
|
||||
void LutGenerationPass::SetViewportScissorFromImageSize(const RHI::Size& imageSize)
|
||||
{
|
||||
const RHI::Viewport viewport(0.f, imageSize.m_width * 1.f, 0.f, imageSize.m_height * 1.f);
|
||||
const RHI::Scissor scissor(0, 0, imageSize.m_width, imageSize.m_height);
|
||||
m_viewportState = viewport;
|
||||
m_scissorState = scissor;
|
||||
}
|
||||
|
||||
} // namespace Render
|
||||
} // namespace AZ
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <PostProcessing/HDRColorGradingPass.h>
|
||||
#include <Atom/RPI.Public/Shader/Shader.h>
|
||||
#include <Atom/RPI.Public/Shader/ShaderResourceGroup.h>
|
||||
#include <Atom/RPI.Reflect/Shader/ShaderVariantKey.h>
|
||||
|
||||
#include <PostProcess/ColorGrading/HDRColorGradingSettings.h>
|
||||
#include <Atom/Feature/DisplayMapper/DisplayMapperFeatureProcessorInterface.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
namespace Render
|
||||
{
|
||||
// Performs color grading on an identity LUT strip
|
||||
class LutGenerationPass
|
||||
: public AZ::Render::HDRColorGradingPass
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(LutGenerationPass, "{C21DABA8-B538-4C80-BA18-5B97CC9259E5}", AZ::RPI::FullscreenTrianglePass);
|
||||
AZ_CLASS_ALLOCATOR(LutGenerationPass, SystemAllocator, 0);
|
||||
|
||||
virtual ~LutGenerationPass() = default;
|
||||
|
||||
//! Creates a ColorGradingPass
|
||||
static RPI::Ptr<LutGenerationPass> Create(const RPI::PassDescriptor& descriptor);
|
||||
|
||||
protected:
|
||||
LutGenerationPass(const RPI::PassDescriptor& descriptor);
|
||||
|
||||
void InitializeInternal() override;
|
||||
void FrameBeginInternal(FramePrepareParams params) override;
|
||||
void BuildCommandListInternal(const RHI::FrameGraphExecuteContext& context) override;
|
||||
bool IsEnabled() const override;
|
||||
private:
|
||||
// Set viewport scissor based on output LUT resolution
|
||||
void SetViewportScissorFromImageSize(const RHI::Size& imageSize);
|
||||
|
||||
RHI::ShaderInputNameIndex m_lutResolutionIndex = "m_lutResolution";
|
||||
RHI::ShaderInputNameIndex m_lutShaperTypeIndex = "m_shaperType";
|
||||
RHI::ShaderInputNameIndex m_lutShaperBiasIndex = "m_shaperBias";
|
||||
RHI::ShaderInputNameIndex m_lutShaperScaleIndex = "m_shaperScale";
|
||||
};
|
||||
|
||||
} // namespace Render
|
||||
} // namespace AZ
|
||||
@@ -39,6 +39,7 @@
|
||||
#include <Atom/Feature/AuxGeom/AuxGeomFeatureProcessor.h>
|
||||
#include <Atom/Feature/Utils/LightingPreset.h>
|
||||
#include <Atom/Feature/Utils/ModelPreset.h>
|
||||
#include <ColorGrading/LutGenerationPass.h>
|
||||
#include <PostProcess/PostProcessFeatureProcessor.h>
|
||||
#include <PostProcessing/BlendColorGradingLutsPass.h>
|
||||
#include <PostProcessing/BloomParentPass.h>
|
||||
@@ -230,6 +231,7 @@ namespace AZ
|
||||
passSystem->AddPassCreator(Name("HDRColorGradingPass"), &HDRColorGradingPass::Create);
|
||||
passSystem->AddPassCreator(Name("LookModificationCompositePass"), &LookModificationCompositePass::Create);
|
||||
passSystem->AddPassCreator(Name("LookModificationTransformPass"), &LookModificationPass::Create);
|
||||
passSystem->AddPassCreator(Name("LutGenerationPass"), &LutGenerationPass::Create);
|
||||
passSystem->AddPassCreator(Name("SMAAEdgeDetectionPass"), &SMAAEdgeDetectionPass::Create);
|
||||
passSystem->AddPassCreator(Name("SMAABlendingWeightCalculationPass"), &SMAABlendingWeightCalculationPass::Create);
|
||||
passSystem->AddPassCreator(Name("SMAANeighborhoodBlendingPass"), &SMAANeighborhoodBlendingPass::Create);
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <AzCore/Preprocessor/EnumReflectUtils.h>
|
||||
#include <AzCore/Console/Console.h>
|
||||
|
||||
#include <tiffio.h>
|
||||
namespace AZ
|
||||
{
|
||||
namespace Render
|
||||
@@ -110,6 +111,48 @@ namespace AZ
|
||||
return FrameCaptureOutputResult{FrameCaptureResult::InternalError, "Unable to save frame capture output to '" + outputFilePath + "'"};
|
||||
}
|
||||
|
||||
FrameCaptureOutputResult TiffFrameCaptureOutput(
|
||||
const AZStd::string& outputFilePath, const AZ::RPI::AttachmentReadback::ReadbackResult& readbackResult)
|
||||
{
|
||||
AZStd::shared_ptr<AZStd::vector<uint8_t>> buffer = readbackResult.m_dataBuffer;
|
||||
const uint32_t width = readbackResult.m_imageDescriptor.m_size.m_width;
|
||||
const uint32_t height = readbackResult.m_imageDescriptor.m_size.m_height;
|
||||
const uint32_t numChannels = AZ::RHI::GetFormatComponentCount(readbackResult.m_imageDescriptor.m_format);
|
||||
const uint32_t bytesPerChannel = AZ::RHI::GetFormatSize(readbackResult.m_imageDescriptor.m_format) / numChannels;
|
||||
const uint32_t bitsPerChannel = bytesPerChannel * 8;
|
||||
|
||||
TIFF* out = TIFFOpen(outputFilePath.c_str(), "w");
|
||||
TIFFSetField(out, TIFFTAG_IMAGEWIDTH, width);
|
||||
TIFFSetField(out, TIFFTAG_IMAGELENGTH, height);
|
||||
TIFFSetField(out, TIFFTAG_SAMPLESPERPIXEL, numChannels);
|
||||
TIFFSetField(out, TIFFTAG_BITSPERSAMPLE, bitsPerChannel);
|
||||
TIFFSetField(out, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
|
||||
TIFFSetField(out, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
|
||||
TIFFSetField(out, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
|
||||
TIFFSetField(out, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
|
||||
TIFFSetField(out, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP); // interpret each pixel as a float
|
||||
|
||||
size_t pitch = width * numChannels * bytesPerChannel;
|
||||
AZ_Assert((pitch * height) == buffer->size(), "Image buffer does not match allocated bytes for tiff saving.")
|
||||
unsigned char* raster = (unsigned char*)_TIFFmalloc((tsize_t)(pitch * height));
|
||||
memcpy(raster, buffer->data(), pitch * height);
|
||||
bool success = true;
|
||||
for (uint32_t h = 0; h < height; ++h)
|
||||
{
|
||||
size_t offset = h * pitch;
|
||||
int err = TIFFWriteScanline(out, raster + offset, h, 0);
|
||||
if (err < 0)
|
||||
{
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
_TIFFfree(raster);
|
||||
TIFFClose(out);
|
||||
return success ? FrameCaptureOutputResult{ FrameCaptureResult::Success, AZStd::nullopt }
|
||||
: FrameCaptureOutputResult{ FrameCaptureResult::InternalError, "Unable to save tif frame capture output to " + outputFilePath };
|
||||
}
|
||||
|
||||
FrameCaptureOutputResult DdsFrameCaptureOutput(
|
||||
const AZStd::string& outputFilePath, const AZ::RPI::AttachmentReadback::ReadbackResult& readbackResult)
|
||||
{
|
||||
@@ -492,6 +535,12 @@ namespace AZ
|
||||
m_result = ddsFrameCapture.m_result;
|
||||
m_latestCaptureInfo = ddsFrameCapture.m_errorMessage.value_or("");
|
||||
}
|
||||
else if (extension == "tiff" || extension == "tif")
|
||||
{
|
||||
const auto tifFrameCapture = TiffFrameCaptureOutput(m_outputFilePath, readbackResult);
|
||||
m_result = tifFrameCapture.m_result;
|
||||
m_latestCaptureInfo = tifFrameCapture.m_errorMessage.value_or("");
|
||||
}
|
||||
else if (extension == "png")
|
||||
{
|
||||
if (readbackResult.m_imageDescriptor.m_format == RHI::Format::R8G8B8A8_UNORM ||
|
||||
|
||||
+1
@@ -33,6 +33,7 @@ namespace AZ
|
||||
target->m_enabled = m_enabled;
|
||||
|
||||
#define AZ_GFX_BOOL_PARAM(NAME, MEMBER_NAME, DefaultValue) ;
|
||||
#define AZ_GFX_COMMON_PARAM(ValueType, Name, MemberName, DefaultValue) ;
|
||||
#define AZ_GFX_FLOAT_PARAM(NAME, MEMBER_NAME, DefaultValue) \
|
||||
{ \
|
||||
target->Set##NAME(AZ::Lerp(target->MEMBER_NAME, MEMBER_NAME, alpha)); \
|
||||
|
||||
+1
@@ -13,6 +13,7 @@
|
||||
#include <Atom/Feature/PostProcess/ColorGrading/HDRColorGradingSettingsInterface.h>
|
||||
|
||||
#include <PostProcess/PostProcessBase.h>
|
||||
#include <ACES/Aces.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
|
||||
@@ -32,33 +32,39 @@
|
||||
{
|
||||
FullscreenTrianglePass::InitializeInternal();
|
||||
|
||||
m_colorAdjustmentWeightIndex.Reset();
|
||||
m_colorGradingExposureIndex.Reset();
|
||||
m_colorGradingContrastIndex.Reset();
|
||||
m_colorGradingHueShiftIndex.Reset();
|
||||
m_colorGradingPreSaturationIndex.Reset();
|
||||
m_colorFilterIntensityIndex.Reset();
|
||||
m_colorFilterMultiplyIndex.Reset();
|
||||
m_colorFilterSwatchIndex.Reset();
|
||||
|
||||
m_whiteBalanceWeightIndex.Reset();
|
||||
m_whiteBalanceKelvinIndex.Reset();
|
||||
m_whiteBalanceTintIndex.Reset();
|
||||
|
||||
m_splitToneBalanceIndex.Reset();
|
||||
m_splitToneWeightIndex.Reset();
|
||||
m_colorGradingPostSaturationIndex.Reset();
|
||||
m_splitToneShadowsColorIndex.Reset();
|
||||
m_splitToneHighlightsColorIndex.Reset();
|
||||
|
||||
m_smhShadowsStartIndex.Reset();
|
||||
m_smhShadowsEndIndex.Reset();
|
||||
m_smhHighlightsStartIndex.Reset();
|
||||
m_smhHighlightsEndIndex.Reset();
|
||||
m_smhWeightIndex.Reset();
|
||||
m_smhShadowsColorIndex.Reset();
|
||||
m_smhMidtonesColorIndex.Reset();
|
||||
m_smhHighlightsColorIndex.Reset();
|
||||
|
||||
m_channelMixingRedIndex.Reset();
|
||||
m_channelMixingGreenIndex.Reset();
|
||||
m_channelMixingBlueIndex.Reset();
|
||||
|
||||
m_colorFilterSwatchIndex.Reset();
|
||||
m_splitToneShadowsColorIndex.Reset();
|
||||
m_splitToneHighlightsColorIndex.Reset();
|
||||
m_smhShadowsColorIndex.Reset();
|
||||
m_smhMidtonesColorIndex.Reset();
|
||||
m_smhHighlightsColorIndex.Reset();
|
||||
m_finalAdjustmentWeightIndex.Reset();
|
||||
m_colorGradingPostSaturationIndex.Reset();
|
||||
m_colorGradingHueShiftIndex.Reset();
|
||||
}
|
||||
|
||||
void HDRColorGradingPass::FrameBeginInternal(FramePrepareParams params)
|
||||
@@ -79,33 +85,39 @@
|
||||
const HDRColorGradingSettings* settings = GetHDRColorGradingSettings();
|
||||
if (settings)
|
||||
{
|
||||
m_shaderResourceGroup->SetConstant(m_colorAdjustmentWeightIndex, settings->GetColorAdjustmentWeight());
|
||||
m_shaderResourceGroup->SetConstant(m_colorGradingExposureIndex, settings->GetColorGradingExposure());
|
||||
m_shaderResourceGroup->SetConstant(m_colorGradingContrastIndex, settings->GetColorGradingContrast());
|
||||
m_shaderResourceGroup->SetConstant(m_colorGradingHueShiftIndex, settings->GetColorGradingHueShift());
|
||||
m_shaderResourceGroup->SetConstant(m_colorGradingPreSaturationIndex, settings->GetColorGradingPreSaturation());
|
||||
m_shaderResourceGroup->SetConstant(m_colorGradingPreSaturationIndex, settings->GetColorGradingPreSaturation() * 0.01f + 1.0f);
|
||||
m_shaderResourceGroup->SetConstant(m_colorFilterIntensityIndex, settings->GetColorGradingFilterIntensity());
|
||||
m_shaderResourceGroup->SetConstant(m_colorFilterMultiplyIndex, settings->GetColorGradingFilterMultiply());
|
||||
m_shaderResourceGroup->SetConstant(m_colorFilterSwatchIndex, AZ::Vector4(settings->GetColorFilterSwatch()));
|
||||
|
||||
m_shaderResourceGroup->SetConstant(m_whiteBalanceWeightIndex, settings->GetWhiteBalanceWeight());
|
||||
m_shaderResourceGroup->SetConstant(m_whiteBalanceKelvinIndex, settings->GetWhiteBalanceKelvin());
|
||||
m_shaderResourceGroup->SetConstant(m_whiteBalanceTintIndex, settings->GetWhiteBalanceTint());
|
||||
|
||||
m_shaderResourceGroup->SetConstant(m_splitToneBalanceIndex, settings->GetSplitToneBalance());
|
||||
m_shaderResourceGroup->SetConstant(m_splitToneWeightIndex, settings->GetSplitToneWeight());
|
||||
m_shaderResourceGroup->SetConstant(m_colorGradingPostSaturationIndex, settings->GetColorGradingPostSaturation());
|
||||
m_shaderResourceGroup->SetConstant(m_splitToneShadowsColorIndex, AZ::Vector4(settings->GetSplitToneShadowsColor()));
|
||||
m_shaderResourceGroup->SetConstant(m_splitToneHighlightsColorIndex, AZ::Vector4(settings->GetSplitToneHighlightsColor()));
|
||||
|
||||
m_shaderResourceGroup->SetConstant(m_smhShadowsStartIndex, settings->GetSmhShadowsStart());
|
||||
m_shaderResourceGroup->SetConstant(m_smhShadowsEndIndex, settings->GetSmhShadowsEnd());
|
||||
m_shaderResourceGroup->SetConstant(m_smhHighlightsStartIndex, settings->GetSmhHighlightsStart());
|
||||
m_shaderResourceGroup->SetConstant(m_smhHighlightsEndIndex, settings->GetSmhHighlightsEnd());
|
||||
m_shaderResourceGroup->SetConstant(m_smhWeightIndex, settings->GetSmhWeight());
|
||||
m_shaderResourceGroup->SetConstant(m_smhShadowsColorIndex, AZ::Vector4(settings->GetSmhShadowsColor()));
|
||||
m_shaderResourceGroup->SetConstant(m_smhMidtonesColorIndex, AZ::Vector4(settings->GetSmhMidtonesColor()));
|
||||
m_shaderResourceGroup->SetConstant(m_smhHighlightsColorIndex, AZ::Vector4(settings->GetSmhHighlightsColor()));
|
||||
|
||||
m_shaderResourceGroup->SetConstant(m_channelMixingRedIndex, settings->GetChannelMixingRed());
|
||||
m_shaderResourceGroup->SetConstant(m_channelMixingGreenIndex, settings->GetChannelMixingGreen());
|
||||
m_shaderResourceGroup->SetConstant(m_channelMixingBlueIndex, settings->GetChannelMixingBlue());
|
||||
|
||||
m_shaderResourceGroup->SetConstant(m_colorFilterSwatchIndex, AZ::Vector4(settings->GetColorFilterSwatch()));
|
||||
m_shaderResourceGroup->SetConstant(m_splitToneShadowsColorIndex, AZ::Vector4(settings->GetSplitToneShadowsColor()));
|
||||
m_shaderResourceGroup->SetConstant(m_splitToneHighlightsColorIndex, AZ::Vector4(settings->GetSplitToneHighlightsColor()));
|
||||
m_shaderResourceGroup->SetConstant(m_smhShadowsColorIndex, AZ::Vector4(settings->GetSmhShadowsColor()));
|
||||
m_shaderResourceGroup->SetConstant(m_smhMidtonesColorIndex, AZ::Vector4(settings->GetSmhMidtonesColor()));
|
||||
m_shaderResourceGroup->SetConstant(m_smhHighlightsColorIndex, AZ::Vector4(settings->GetSmhHighlightsColor()));
|
||||
m_shaderResourceGroup->SetConstant(m_finalAdjustmentWeightIndex, settings->GetFinalAdjustmentWeight());
|
||||
m_shaderResourceGroup->SetConstant(m_colorGradingPostSaturationIndex, settings->GetColorGradingPostSaturation() * 0.01f + 1.0f);
|
||||
m_shaderResourceGroup->SetConstant(m_colorGradingHueShiftIndex, settings->GetColorGradingHueShift());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user