Merge branch 'development' of https://github.com/o3de/o3de into Network/olexl/multiplayer_gem_profiler_markers_cr
commit
4d8c8fdda3
@ -0,0 +1,59 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
import editor_python_test_tools.hydra_test_utils as hydra
|
||||
from ly_test_tools.benchmark.data_aggregator import BenchmarkDataAggregator
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('rhi', ['dx12', 'vulkan'])
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
@pytest.mark.parametrize("launcher_platform", ["windows_editor"])
|
||||
@pytest.mark.parametrize("level", ["AtomFeatureIntegrationBenchmark"])
|
||||
class TestPerformanceBenchmarkSuite(object):
|
||||
def test_AtomFeatureIntegrationBenchmarkTest_UploadMetrics(
|
||||
self, request, editor, workspace, rhi, project, launcher_platform, level):
|
||||
"""
|
||||
Please review the hydra script run by this test for more specific test info.
|
||||
Tests the performance of the Simple level.
|
||||
"""
|
||||
expected_lines = [
|
||||
"Benchmark metadata captured.",
|
||||
"Pass timestamps captured.",
|
||||
"CPU frame time captured.",
|
||||
"Captured data successfully.",
|
||||
"Exited game mode"
|
||||
]
|
||||
|
||||
unexpected_lines = [
|
||||
"Failed to capture data.",
|
||||
"Failed to capture pass timestamps.",
|
||||
"Failed to capture CPU frame time.",
|
||||
"Failed to capture benchmark metadata."
|
||||
]
|
||||
|
||||
hydra.launch_and_validate_results(
|
||||
request,
|
||||
os.path.join(os.path.dirname(__file__), "tests"),
|
||||
editor,
|
||||
"hydra_GPUTest_AtomFeatureIntegrationBenchmark.py",
|
||||
timeout=600,
|
||||
expected_lines=expected_lines,
|
||||
unexpected_lines=unexpected_lines,
|
||||
halt_on_unexpected=True,
|
||||
cfg_args=[level],
|
||||
null_renderer=False,
|
||||
enable_prefab_system=False,
|
||||
)
|
||||
|
||||
aggregator = BenchmarkDataAggregator(workspace, logger, 'periodic')
|
||||
aggregator.upload_metrics(rhi)
|
||||
@ -0,0 +1,39 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
if(NOT PAL_TRAIT_BUILD_TESTS_SUPPORTED OR NOT PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
return()
|
||||
endif()
|
||||
|
||||
ly_add_pytest(
|
||||
NAME AutomatedTesting::EditorLevelLoadingPerfTests_DX12
|
||||
TEST_SUITE periodic
|
||||
TEST_REQUIRES gpu
|
||||
TEST_SERIAL
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Periodic_DX12.py
|
||||
RUNTIME_DEPENDENCIES
|
||||
Legacy::Editor
|
||||
AZ::AssetProcessor
|
||||
AutomatedTesting.Assets
|
||||
COMPONENT
|
||||
Performance
|
||||
)
|
||||
|
||||
ly_add_pytest(
|
||||
NAME AutomatedTesting::EditorLevelLoadingPerfTests_Vulkan
|
||||
TEST_SUITE periodic
|
||||
TEST_REQUIRES gpu
|
||||
TEST_SERIAL
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Periodic_Vulkan.py
|
||||
RUNTIME_DEPENDENCIES
|
||||
Legacy::Editor
|
||||
AZ::AssetProcessor
|
||||
AutomatedTesting.Assets
|
||||
COMPONENT
|
||||
Performance
|
||||
)
|
||||
@ -0,0 +1,31 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
|
||||
"""
|
||||
|
||||
# This suite consists of all test cases that are passing and have been verified.
|
||||
|
||||
import pytest
|
||||
import os
|
||||
import sys
|
||||
|
||||
from ly_test_tools.o3de.editor_test import EditorTestSuite, EditorSingleTest
|
||||
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
class TestAutomation(EditorTestSuite):
|
||||
|
||||
class Time_EditorLevelLoading_10KEntityCpuPerfTest(EditorSingleTest):
|
||||
extra_cmdline_args = ['-rhi=dx12']
|
||||
use_null_renderer = False # needs renderer to validate test
|
||||
|
||||
from .tests import EditorLevelLoading_10KEntityCpuPerfTest as test_module
|
||||
|
||||
class Time_EditorLevelLoading_10kVegInstancesTest(EditorSingleTest):
|
||||
extra_cmdline_args = ['-rhi=dx12']
|
||||
use_null_renderer = False # needs renderer to validate test
|
||||
|
||||
from .tests import EditorLevelLoading_10kVegInstancesTest as test_module
|
||||
@ -0,0 +1,34 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
|
||||
"""
|
||||
|
||||
# This suite consists of all test cases that are passing and have been verified.
|
||||
|
||||
import pytest
|
||||
import os
|
||||
import sys
|
||||
|
||||
from ly_test_tools.o3de.editor_test import EditorTestSuite, EditorSingleTest
|
||||
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
class TestAutomation(EditorTestSuite):
|
||||
|
||||
class Time_EditorLevelLoading_10KEntityCpuPerfTest(EditorSingleTest):
|
||||
# there is currently a huge discrepancy loading this level with vulkan compared to dx12 which requires the 10 min timeout
|
||||
# this should be removed once that issue has been sorted out
|
||||
timeout = 600
|
||||
extra_cmdline_args = ['-rhi=vulkan']
|
||||
use_null_renderer = False # needs renderer to validate test
|
||||
|
||||
from .tests import EditorLevelLoading_10KEntityCpuPerfTest as test_module
|
||||
|
||||
class Time_EditorLevelLoading_10kVegInstancesTest(EditorSingleTest):
|
||||
extra_cmdline_args = ['-rhi=vulkan']
|
||||
use_null_renderer = False # needs renderer to validate test
|
||||
|
||||
from .tests import EditorLevelLoading_10kVegInstancesTest as test_module
|
||||
@ -0,0 +1,6 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
@ -0,0 +1,15 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
from Performance.utils.perf_timer import time_editor_level_loading
|
||||
|
||||
def EditorLevelLoading_10KEntityCpuPerfTest():
|
||||
time_editor_level_loading('Performance', '10KEntityCpuPerfTest')
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(EditorLevelLoading_10KEntityCpuPerfTest)
|
||||
@ -0,0 +1,15 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
from Performance.utils.perf_timer import time_editor_level_loading
|
||||
|
||||
def EditorLevelLoading_10kVegInstancesTest():
|
||||
time_editor_level_loading('Performance', '10kVegInstancesTest')
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(EditorLevelLoading_10kVegInstancesTest)
|
||||
@ -0,0 +1,6 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
@ -0,0 +1,6 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
@ -0,0 +1,72 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
ENTER_MSG = ("Entered game mode", "Failed to enter game mode")
|
||||
EXIT_MSG = ("Exited game mode", "Couldn't exit game mode")
|
||||
|
||||
class Timer:
|
||||
unit_divisor = 60
|
||||
hour_divisor = unit_divisor * unit_divisor
|
||||
|
||||
def start(self):
|
||||
self._start_time = time.perf_counter()
|
||||
|
||||
def log_time(self, message):
|
||||
from editor_python_test_tools.utils import Report
|
||||
|
||||
elapsed_time = time.perf_counter() - self._start_time
|
||||
hours = int(elapsed_time / Timer.hour_divisor)
|
||||
minutes = int(elapsed_time % Timer.hour_divisor / Timer.unit_divisor)
|
||||
seconds = elapsed_time % Timer.unit_divisor
|
||||
|
||||
Report.info(f'{message}: {hours:0>2d}:{minutes:0>2d}:{seconds:0>5.2f}\n')
|
||||
|
||||
def time_editor_level_loading(level_dir, level_name):
|
||||
|
||||
"""
|
||||
Summary:
|
||||
Time how long it takes to load an arbitrary level, entering game mode, and exiting game mode
|
||||
|
||||
Level Description:
|
||||
Preferably a level with a large number of entities
|
||||
|
||||
Expected Behavior:
|
||||
Level loads within a reasonable time frame e.i. doesn't trip the framework timeout
|
||||
|
||||
Benchmark Steps:
|
||||
1) Time opening the level
|
||||
2) Time entering game mode
|
||||
3) Time exiting game mode
|
||||
4) Close the editor
|
||||
|
||||
:return: None
|
||||
"""
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
timer = Timer()
|
||||
|
||||
helper.init_idle()
|
||||
|
||||
# 1) Open level
|
||||
timer.start()
|
||||
helper.open_level(level_dir, level_name)
|
||||
timer.log_time('Level load time')
|
||||
|
||||
# 2) Time how long it takes to enter game mode
|
||||
timer.start()
|
||||
helper.enter_game_mode(ENTER_MSG)
|
||||
timer.log_time('Enter game mode')
|
||||
|
||||
# 3) Exit game mode
|
||||
timer.start()
|
||||
helper.exit_game_mode(EXIT_MSG)
|
||||
timer.log_time('Exit game mode')
|
||||
|
||||
# 4) Close the editor
|
||||
helper.close_editor()
|
||||
@ -0,0 +1,114 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
|
||||
class Tests:
|
||||
image_gradient_entity_created = (
|
||||
"Image Gradient entity created",
|
||||
"Failed to create Image Gradient entity",
|
||||
)
|
||||
image_gradient_assigned = (
|
||||
"Successfully assigned image gradient asset",
|
||||
"Failed to assign image gradient asset"
|
||||
)
|
||||
instance_validation = (
|
||||
"Found the expected number of instances",
|
||||
"Found an unexpected number of instances"
|
||||
)
|
||||
|
||||
|
||||
def ImageGradient_ModifiesSurfaces():
|
||||
"""
|
||||
Summary:
|
||||
This test verifies that an Image Gradient + Gradient Surface Tag Emitter properly modifies surfaces.
|
||||
|
||||
Expected Behavior:
|
||||
Vegetation is used to verify expected surface modification.
|
||||
|
||||
Test Steps:
|
||||
1) Open a level
|
||||
2) Create an entity with Image Gradient, Gradient Transform Modifier, Shape Reference, and Gradient Surface Tag
|
||||
Emitter components with an Image asset assigned.
|
||||
3) Create a Vegetation Layer Spawner setup to plant on the generated surface.
|
||||
4) Update all surface tag references
|
||||
5) Validate expected instances planted on the modified surface.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import azlmbr.bus as bus
|
||||
import azlmbr.entity as EntityId
|
||||
import azlmbr.editor as editor
|
||||
import azlmbr.math as math
|
||||
import azlmbr.surface_data as surface_data
|
||||
|
||||
import editor_python_test_tools.hydra_editor_utils as hydra
|
||||
from editor_python_test_tools.asset_utils import Asset
|
||||
from editor_python_test_tools.editor_entity_utils import EditorEntity
|
||||
from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
|
||||
from editor_python_test_tools.utils import Report
|
||||
from editor_python_test_tools.utils import TestHelper as helper
|
||||
|
||||
# 1) Open an existing simple level
|
||||
hydra.open_base_level()
|
||||
|
||||
# 2) Create an entity with required Image Gradient + Surface Tag Emitter components and assign image asset
|
||||
components_to_add = ["Image Gradient", "Gradient Transform Modifier", "Shape Reference",
|
||||
"Gradient Surface Tag Emitter"]
|
||||
entity_position = math.Vector3(0.0, 0.0, 0.0)
|
||||
new_entity_id = editor.ToolsApplicationRequestBus(
|
||||
bus.Broadcast, "CreateNewEntityAtPosition", entity_position, EntityId.EntityId()
|
||||
)
|
||||
Report.critical_result(Tests.image_gradient_entity_created, new_entity_id.IsValid())
|
||||
image_gradient_entity = EditorEntity.create_editor_entity_at(entity_position, "Image Gradient")
|
||||
image_gradient_entity.add_components(components_to_add)
|
||||
test_img_gradient_path = os.path.join("Assets", "ImageGradients", "image_grad_test_gsi.png.streamingimage")
|
||||
asset = Asset.find_asset_by_path(test_img_gradient_path)
|
||||
image_gradient_entity.components[0].set_component_property_value("Configuration|Image Asset", asset.id)
|
||||
success = image_gradient_entity.components[0].get_component_property_value("Configuration|Image Asset") == asset.id
|
||||
Report.result(Tests.image_gradient_assigned, success)
|
||||
|
||||
# 3) Create vegetation and planting surface entities, and assign the Image Gradient entity's Shape Reference
|
||||
|
||||
# Create vegetation entity
|
||||
purple_flower_prefab_path = os.path.join("assets", "prefabs", "PurpleFlower.spawnable")
|
||||
spawner_entity = dynveg.create_prefab_vegetation_area("Instance Spawner", entity_position, 50.0, 50.0, 10.0,
|
||||
purple_flower_prefab_path)
|
||||
spawner_entity.add_component("Vegetation Surface Mask Filter")
|
||||
|
||||
# Create surface entity
|
||||
dynveg.create_surface_entity("Box Shape", entity_position, 50.0, 50.0, 1.0)
|
||||
|
||||
# Assign Image Gradient entity's Shape Reference
|
||||
image_gradient_entity.components[2].set_component_property_value("Configuration|Shape Entity Id", spawner_entity.id)
|
||||
|
||||
# 4) Assign surface tags to the required components
|
||||
tag_list = [surface_data.SurfaceTag("terrain")]
|
||||
|
||||
# Set the Veg Spawner entity's Surface Tag Mask Filter component to include the "terrain" tag
|
||||
hydra.get_set_test(spawner_entity, 3, "Configuration|Inclusion|Surface Tags", tag_list)
|
||||
|
||||
# Set the Image Gradient entity's Gradient Surface Tag Emitter component to modify the "terrain" tag
|
||||
# NOTE: This requires a disable/re-enable of the component to force a refresh as assigning a tag via script does not
|
||||
grad_surf_tag_emitter_component = image_gradient_entity.components[3]
|
||||
grad_surf_tag_emitter_component.add_container_item("Configuration|Extended Tags", 0, tag_list[0])
|
||||
grad_surf_tag_emitter_component.set_enabled(False)
|
||||
grad_surf_tag_emitter_component.set_enabled(True)
|
||||
|
||||
# 5) Validate the expected number of vegetation instances. Instances should only spawn on the modified surface
|
||||
num_expected_instances = 168
|
||||
success = helper.wait_for_condition(lambda: dynveg.validate_instance_count_in_entity_shape(
|
||||
spawner_entity.id, num_expected_instances), 5.0)
|
||||
Report.result(Tests.instance_validation, success)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(ImageGradient_ModifiesSurfaces)
|
||||
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
// Qt
|
||||
#include <QColor>
|
||||
#include <CryCommon/Cry_Color.h>
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
QColor ColorLinearToGamma(ColorF col)
|
||||
{
|
||||
float r = clamp_tpl(col.r, 0.0f, 1.0f);
|
||||
float g = clamp_tpl(col.g, 0.0f, 1.0f);
|
||||
float b = clamp_tpl(col.b, 0.0f, 1.0f);
|
||||
float a = clamp_tpl(col.a, 0.0f, 1.0f);
|
||||
|
||||
r = (float)(r <= 0.0031308 ? (12.92 * r) : (1.055 * pow((double)r, 1.0 / 2.4) - 0.055));
|
||||
g = (float)(g <= 0.0031308 ? (12.92 * g) : (1.055 * pow((double)g, 1.0 / 2.4) - 0.055));
|
||||
b = (float)(b <= 0.0031308 ? (12.92 * b) : (1.055 * pow((double)b, 1.0 / 2.4) - 0.055));
|
||||
|
||||
return QColor(int(r * 255.0f), int(g * 255.0f), int(b * 255.0f), int(a * 255.0f));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ColorF ColorGammaToLinear(const QColor& col)
|
||||
{
|
||||
float r = (float)col.red() / 255.0f;
|
||||
float g = (float)col.green() / 255.0f;
|
||||
float b = (float)col.blue() / 255.0f;
|
||||
float a = (float)col.alpha() / 255.0f;
|
||||
|
||||
return ColorF((float)(r <= 0.04045 ? (r / 12.92) : pow(((double)r + 0.055) / 1.055, 2.4)),
|
||||
(float)(g <= 0.04045 ? (g / 12.92) : pow(((double)g + 0.055) / 1.055, 2.4)),
|
||||
(float)(b <= 0.04045 ? (b / 12.92) : pow(((double)b + 0.055) / 1.055, 2.4)), a);
|
||||
}
|
||||
|
||||
QColor ColorToQColor(uint32 color)
|
||||
{
|
||||
return QColor::fromRgbF((float)GetRValue(color) / 255.0f,
|
||||
(float)GetGValue(color) / 255.0f,
|
||||
(float)GetBValue(color) / 255.0f);
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/DOM/DomPath.h>
|
||||
#include <AzCore/std/containers/stack.h>
|
||||
#include <AzCore/std/containers/unordered_map.h>
|
||||
#include <AzCore/std/optional.h>
|
||||
#include <AzCore/std/ranges/ranges.h>
|
||||
#include <AzCore/std/tuple.h>
|
||||
|
||||
namespace AZ::Dom
|
||||
{
|
||||
//! Specifies how a path matches against a DomPrefixTree
|
||||
enum class PrefixTreeMatch
|
||||
{
|
||||
//! Only an exact path will match.
|
||||
//! For the path "/foo/bar" only "/foo/bar" will match while
|
||||
//! "/foo" and "/foo/bar/baz" will not.
|
||||
ExactPath,
|
||||
//! The path, and any of its subpaths, will match.
|
||||
//! For the path "/foo/bar" both "/foo/bar" and any subpaths like
|
||||
//! "/foo/bar/0" will match, while "/foo" and orthogonal paths like
|
||||
//! "/bar" will not
|
||||
PathAndSubpaths,
|
||||
//! Any of the path's subpaths will match, excepting the path itself.
|
||||
//! For the path "/foo/bar", "/foo/bar" will not match but "/foo/bar/0"
|
||||
//! will.
|
||||
SubpathsOnly,
|
||||
};
|
||||
|
||||
template<class Range, class T, class = void>
|
||||
constexpr bool RangeConvertibleToPrefixTree = false;
|
||||
|
||||
template<class Range, class T>
|
||||
constexpr bool RangeConvertibleToPrefixTree<
|
||||
Range,
|
||||
T,
|
||||
AZStd::enable_if_t<
|
||||
AZStd::ranges::input_range<Range> && AZStd::tuple_size<AZStd::ranges::range_value_t<Range>>::value == 2 &&
|
||||
AZStd::convertible_to<AZStd::tuple_element_t<0, AZStd::ranges::range_value_t<Range>>, Path> &&
|
||||
AZStd::convertible_to<AZStd::tuple_element_t<1, AZStd::ranges::range_value_t<Range>>, T>>> = true;
|
||||
|
||||
//! A prefix tree that maps DOM paths to some arbitrary value.
|
||||
template<class T>
|
||||
class DomPrefixTree
|
||||
{
|
||||
public:
|
||||
DomPrefixTree() = default;
|
||||
DomPrefixTree(const DomPrefixTree&) = default;
|
||||
DomPrefixTree(DomPrefixTree&&) = default;
|
||||
explicit DomPrefixTree(AZStd::initializer_list<AZStd::pair<Path, T>> init);
|
||||
|
||||
template<class Range, class = AZStd::enable_if_t<RangeConvertibleToPrefixTree<Range, T>>>
|
||||
explicit DomPrefixTree(Range&& range);
|
||||
|
||||
DomPrefixTree& operator=(const DomPrefixTree&) = default;
|
||||
DomPrefixTree& operator=(DomPrefixTree&&) = default;
|
||||
|
||||
using VisitorFunction = AZStd::function<void(const Path&, const T&)>;
|
||||
|
||||
//! Visits a path and calls a visitor for each matching path and value.
|
||||
void VisitPath(const Path& path, PrefixTreeMatch match, const VisitorFunction& visitor) const;
|
||||
//! Visits a path and returns the most specific matching value, or null if none was found.
|
||||
T* ValueAtPath(const Path& path, PrefixTreeMatch match);
|
||||
//! \see ValueAtPath
|
||||
const T* ValueAtPath(const Path& path, PrefixTreeMatch match) const;
|
||||
//! Visits a path and returns the most specific matching value or some default value.
|
||||
template<class Deduced>
|
||||
T ValueAtPathOrDefault(const Path& path, Deduced&& defaultValue, PrefixTreeMatch match) const;
|
||||
|
||||
//! Sets the value stored at path.
|
||||
template<class Deduced>
|
||||
void SetValue(const Path& path, Deduced&& value);
|
||||
//! Removes the value stored at path. If removeChildren is true, also removes any values stored at subpaths.
|
||||
void EraseValue(const Path& path, bool removedChildren = false);
|
||||
//! Removes all entries from this tree.
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
struct Node
|
||||
{
|
||||
AZStd::unordered_map<PathEntry, Node> m_values;
|
||||
AZStd::optional<T> m_data;
|
||||
};
|
||||
|
||||
Node* GetNodeForPath(const Path& path);
|
||||
const Node* GetNodeForPath(const Path& path) const;
|
||||
|
||||
Node m_rootNode;
|
||||
};
|
||||
} // namespace AZ::Dom
|
||||
|
||||
#include <AzCore/DOM/DomPrefixTree.inl>
|
||||
@ -0,0 +1,234 @@
|
||||
/*
|
||||
* 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::Dom
|
||||
{
|
||||
template<class T>
|
||||
DomPrefixTree<T>::DomPrefixTree(AZStd::initializer_list<AZStd::pair<Path, T>> init)
|
||||
{
|
||||
for (const auto& [path, value] : init)
|
||||
{
|
||||
SetValue(path, value);
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
template<class Range, class>
|
||||
DomPrefixTree<T>::DomPrefixTree(Range&& range)
|
||||
{
|
||||
for (auto&& [path, value] : AZStd::forward<Range>(range))
|
||||
{
|
||||
SetValue(path, value);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
auto DomPrefixTree<T>::GetNodeForPath(const Path& path) -> Node*
|
||||
{
|
||||
Node* node = &m_rootNode;
|
||||
for (const auto& entry : path)
|
||||
{
|
||||
auto entryIt = node->m_values.find(entry);
|
||||
if (entryIt == node->m_values.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
node = &entryIt->second;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
auto DomPrefixTree<T>::GetNodeForPath(const Path& path) const -> const Node*
|
||||
{
|
||||
const Node* node = &m_rootNode;
|
||||
for (const auto& entry : path)
|
||||
{
|
||||
auto entryIt = node->m_values.find(entry);
|
||||
if (entryIt == node->m_values.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
node = &entryIt->second;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void DomPrefixTree<T>::VisitPath(const Path& path, PrefixTreeMatch match, const VisitorFunction& visitor) const
|
||||
{
|
||||
const Node* rootNode = GetNodeForPath(path);
|
||||
if (rootNode == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((match == PrefixTreeMatch::ExactPath || match == PrefixTreeMatch::PathAndSubpaths) && rootNode->m_data.has_value())
|
||||
{
|
||||
visitor(path, rootNode->m_data.value());
|
||||
}
|
||||
|
||||
if (match == PrefixTreeMatch::ExactPath)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Path currentPath = path;
|
||||
struct PopPathEntry
|
||||
{
|
||||
};
|
||||
using StackEntry = AZStd::variant<const Node*, PathEntry, PopPathEntry>;
|
||||
AZStd::stack<StackEntry> stack({ rootNode });
|
||||
while (!stack.empty())
|
||||
{
|
||||
StackEntry entry = AZStd::move(stack.top());
|
||||
stack.pop();
|
||||
AZStd::visit(
|
||||
[&](auto&& value)
|
||||
{
|
||||
using CurrentType = AZStd::decay_t<decltype(value)>;
|
||||
if constexpr (AZStd::is_same_v<CurrentType, const Node*>)
|
||||
{
|
||||
if (value != rootNode && value->m_data.has_value())
|
||||
{
|
||||
visitor(currentPath, value->m_data.value());
|
||||
}
|
||||
|
||||
for (const auto& entry : value->m_values)
|
||||
{
|
||||
// The stack runs this in reverse order, so we'll:
|
||||
// 1) Push the current path entry to currentPath
|
||||
// 2a) Process the value at the path (if any)
|
||||
// 2b) Process the value's descendants at the path (if any)
|
||||
// 3) Pop the path entry from the stack
|
||||
stack.push(PopPathEntry{});
|
||||
stack.push(&entry.second);
|
||||
stack.push(entry.first);
|
||||
}
|
||||
}
|
||||
else if constexpr (AZStd::is_same_v<CurrentType, PathEntry>)
|
||||
{
|
||||
currentPath.Push(value);
|
||||
}
|
||||
else if constexpr (AZStd::is_same_v<CurrentType, PopPathEntry>)
|
||||
{
|
||||
currentPath.Pop();
|
||||
}
|
||||
},
|
||||
entry);
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T* DomPrefixTree<T>::ValueAtPath(const Path& path, PrefixTreeMatch match)
|
||||
{
|
||||
// Just look up the node if we're looking for an exact path
|
||||
if (match == PrefixTreeMatch::ExactPath)
|
||||
{
|
||||
if (Node* node = GetNodeForPath(path); node != nullptr && node->m_data.has_value())
|
||||
{
|
||||
return &node->m_data.value();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// Otherwise, walk to find the closest anscestor with a value
|
||||
Node* node = &m_rootNode;
|
||||
T* result = nullptr;
|
||||
const size_t lengthToIterate = match == PrefixTreeMatch::SubpathsOnly ? path.Size() - 1 : path.Size();
|
||||
for (size_t i = 0; i < lengthToIterate; ++i)
|
||||
{
|
||||
if (node->m_data.has_value())
|
||||
{
|
||||
result = &node->m_data.value();
|
||||
}
|
||||
|
||||
const PathEntry& entry = path[i];
|
||||
auto entryIt = node->m_values.find(entry);
|
||||
if (entryIt == node->m_values.end())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
node = &entryIt->second;
|
||||
}
|
||||
|
||||
if (node->m_data.has_value())
|
||||
{
|
||||
result = &node->m_data.value();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
const T* DomPrefixTree<T>::ValueAtPath(const Path& path, PrefixTreeMatch match) const
|
||||
{
|
||||
// Const coerce the ValueAtPath result, which doesn't mutate but returns a mutable pointer
|
||||
return const_cast<DomPrefixTree<T>*>(this)->ValueAtPath(path, match);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
template<class Deduced>
|
||||
T DomPrefixTree<T>::ValueAtPathOrDefault(const Path& path, Deduced&& defaultValue, PrefixTreeMatch match) const
|
||||
{
|
||||
const T* value = ValueAtPath(path, match);
|
||||
return value == nullptr ? AZStd::forward<Deduced>(defaultValue) : *value;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
template<class Deduced>
|
||||
void DomPrefixTree<T>::SetValue(const Path& path, Deduced&& value)
|
||||
{
|
||||
Node* node = &m_rootNode;
|
||||
for (const PathEntry& entry : path)
|
||||
{
|
||||
// Get or create an entry in this node
|
||||
node = &node->m_values[entry];
|
||||
}
|
||||
node->m_data = AZStd::forward<Deduced>(value);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void DomPrefixTree<T>::EraseValue(const Path& path, bool removeChildren)
|
||||
{
|
||||
Node* node = &m_rootNode;
|
||||
const size_t entriesToIterate = path.Size() - 1;
|
||||
for (size_t i = 0; i < entriesToIterate; ++i)
|
||||
{
|
||||
const PathEntry& entry = path[i];
|
||||
auto nodeIt = node->m_values.find(entry);
|
||||
if (nodeIt == node->m_values.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
node = &nodeIt->second;
|
||||
}
|
||||
|
||||
auto nodeIt = node->m_values.find(path[path.Size() - 1]);
|
||||
if (nodeIt != node->m_values.end())
|
||||
{
|
||||
if (removeChildren)
|
||||
{
|
||||
node->m_values.erase(nodeIt);
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeIt->second.m_data = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void DomPrefixTree<T>::Clear()
|
||||
{
|
||||
m_rootNode = Node();
|
||||
}
|
||||
} // namespace AZ::Dom
|
||||
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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/Memory/SystemAllocator.h>
|
||||
#include <AzCore/Settings/ConfigurableStack.h>
|
||||
#include <AzCore/Serialization/Json/RegistrationContext.h>
|
||||
#include <AzCore/std/containers/queue.h>
|
||||
#include <AzCore/std/tuple.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
AZ_CLASS_ALLOCATOR_IMPL(JsonConfigurableStackSerializer, AZ::SystemAllocator, 0);
|
||||
|
||||
JsonSerializationResult::Result JsonConfigurableStackSerializer::Load(
|
||||
void* outputValue,
|
||||
[[maybe_unused]] const Uuid& outputValueTypeId,
|
||||
const rapidjson::Value& inputValue,
|
||||
JsonDeserializerContext& context)
|
||||
{
|
||||
namespace JSR = JsonSerializationResult; // Used remove name conflicts in AzCore in uber builds.
|
||||
|
||||
switch (inputValue.GetType())
|
||||
{
|
||||
case rapidjson::kArrayType:
|
||||
return LoadFromArray(outputValue, inputValue, context);
|
||||
case rapidjson::kObjectType:
|
||||
return LoadFromObject(outputValue, inputValue, context);
|
||||
|
||||
case rapidjson::kNullType:
|
||||
[[fallthrough]];
|
||||
case rapidjson::kFalseType:
|
||||
[[fallthrough]];
|
||||
case rapidjson::kTrueType:
|
||||
[[fallthrough]];
|
||||
case rapidjson::kStringType:
|
||||
[[fallthrough]];
|
||||
case rapidjson::kNumberType:
|
||||
return context.Report(
|
||||
JSR::Tasks::ReadField, JSR::Outcomes::Unsupported,
|
||||
"Unsupported type. Configurable stack values can only be read from arrays or objects.");
|
||||
default:
|
||||
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unknown, "Unknown json type encountered for string value.");
|
||||
}
|
||||
}
|
||||
|
||||
JsonSerializationResult::Result JsonConfigurableStackSerializer::Store(
|
||||
[[maybe_unused]] rapidjson::Value& outputValue,
|
||||
[[maybe_unused]] const void* inputValue,
|
||||
[[maybe_unused]] const void* defaultValue,
|
||||
[[maybe_unused]] const Uuid& valueTypeId,
|
||||
JsonSerializerContext& context)
|
||||
{
|
||||
return context.Report(
|
||||
JsonSerializationResult::Tasks::WriteValue, JsonSerializationResult::Outcomes::Unsupported,
|
||||
"Configuration stacks can not be written out.");
|
||||
}
|
||||
|
||||
void JsonConfigurableStackSerializer::Reflect(ReflectContext* context)
|
||||
{
|
||||
if (JsonRegistrationContext* jsonContext = azrtti_cast<JsonRegistrationContext*>(context))
|
||||
{
|
||||
jsonContext->Serializer<JsonConfigurableStackSerializer>()->HandlesType<ConfigurableStack>();
|
||||
}
|
||||
}
|
||||
|
||||
JsonSerializationResult::Result JsonConfigurableStackSerializer::LoadFromArray(
|
||||
void* outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context)
|
||||
{
|
||||
namespace JSR = JsonSerializationResult; // Used remove name conflicts in AzCore in uber builds.
|
||||
|
||||
auto stack = reinterpret_cast<ConfigurableStackInterface*>(outputValue);
|
||||
const Uuid& nodeValueType = stack->GetNodeType();
|
||||
|
||||
JSR::ResultCode result(JSR::Tasks::ReadField);
|
||||
uint32_t counter = 0;
|
||||
for (auto& it : inputValue.GetArray())
|
||||
{
|
||||
ScopedContextPath subPath(context, counter);
|
||||
void* value = stack->AddNode(AZStd::to_string(counter));
|
||||
result.Combine(ContinueLoading(value, nodeValueType, it, context));
|
||||
counter++;
|
||||
}
|
||||
|
||||
return context.Report(
|
||||
result,
|
||||
result.GetProcessing() != JSR::Processing::Halted ? "Loaded configurable stack from array."
|
||||
: "Failed to load configurable stack from array.");
|
||||
}
|
||||
|
||||
JsonSerializationResult::Result JsonConfigurableStackSerializer::LoadFromObject(
|
||||
void* outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context)
|
||||
{
|
||||
namespace JSR = JsonSerializationResult; // Used remove name conflicts in AzCore in uber builds.
|
||||
|
||||
auto stack = reinterpret_cast<ConfigurableStackInterface*>(outputValue);
|
||||
const Uuid& nodeValueType = stack->GetNodeType();
|
||||
|
||||
AZStd::queue<AZStd::tuple<ConfigurableStackInterface::InsertPosition, AZStd::string_view, rapidjson::Value::ConstMemberIterator>>
|
||||
delayedEntries;
|
||||
JSR::ResultCode result(JSR::Tasks::ReadField);
|
||||
|
||||
auto add = [&](ConfigurableStackInterface::InsertPosition position, rapidjson::Value::ConstMemberIterator target,
|
||||
rapidjson::Value::ConstMemberIterator it)
|
||||
{
|
||||
if (target->value.IsString())
|
||||
{
|
||||
delayedEntries.emplace(position, AZStd::string_view(target->value.GetString(), target->value.GetStringLength()), it);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Combine(context.Report(
|
||||
JSR::Tasks::ReadField, JSR::Outcomes::Skipped,
|
||||
"Skipped value for the Configurable Stack because the target wasn't a string."));
|
||||
}
|
||||
};
|
||||
|
||||
// Load all the regular entries into the stack and store any with a before or after binding for
|
||||
// later inserting.
|
||||
for (auto it = inputValue.MemberBegin(); it != inputValue.MemberEnd(); ++it)
|
||||
{
|
||||
AZStd::string_view name(it->name.GetString(), it->name.GetStringLength());
|
||||
ScopedContextPath subPath(context, name);
|
||||
if (it->value.IsObject())
|
||||
{
|
||||
if (auto target = it->value.FindMember(StackBefore); target != it->value.MemberEnd())
|
||||
{
|
||||
add(ConfigurableStackInterface::InsertPosition::Before, target, it);
|
||||
continue;
|
||||
}
|
||||
if (auto target = it->value.FindMember(StackAfter); target != it->value.MemberEnd())
|
||||
{
|
||||
add(ConfigurableStackInterface::InsertPosition::After, target, it);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
void* value = stack->AddNode(name);
|
||||
result.Combine(ContinueLoading(value, nodeValueType, it->value, context));
|
||||
}
|
||||
|
||||
// Insert the entries that have been delayed.
|
||||
while (!delayedEntries.empty())
|
||||
{
|
||||
auto&& [insertPosition, target, valueStore] = delayedEntries.front();
|
||||
AZStd::string_view name(valueStore->name.GetString(), valueStore->name.GetStringLength());
|
||||
ScopedContextPath subPath(context, name);
|
||||
void* value = stack->AddNode(name, target, insertPosition);
|
||||
if (value != nullptr)
|
||||
{
|
||||
result.Combine(ContinueLoading(value, nodeValueType, valueStore->value, context));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Combine(context.Report(
|
||||
JSR::Tasks::ReadField, JSR::Outcomes::Skipped,
|
||||
AZStd::string::format(
|
||||
"Skipped value for the Configurable Stack because the target '%.*s' couldn't be found.", AZ_STRING_ARG(name))));
|
||||
}
|
||||
delayedEntries.pop();
|
||||
}
|
||||
|
||||
return context.Report(
|
||||
result,
|
||||
result.GetProcessing() != JSR::Processing::Halted ? "Loaded configurable stack from array."
|
||||
: "Failed to load configurable stack from array.");
|
||||
}
|
||||
} // namespace AZ
|
||||
@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/base.h>
|
||||
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzCore/std/utils.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
//! The ConfigurableStack makes configuring stacks and arrays through the Settings Registry easier than using direct serialization
|
||||
//! to a container like AZStd::vector. It does this by using JSON Objects rather than JSON arrays, although arrays are supported
|
||||
//! for backwards compatibility. Two key words were added:
|
||||
//! - $stack_before : Insert the new entry before the referenced entry. Referencing is done by name.
|
||||
//! - $stack_after : Insert the new entry after the referenced entry. Referencing is done by name.
|
||||
//! to allow inserting new entries at specific locations. An example of a .setreg file at updates existing settings would be:
|
||||
//! // Original settings
|
||||
//! {
|
||||
//! "Settings in a stack":
|
||||
//! {
|
||||
//! "AnOriginalEntry":
|
||||
//! {
|
||||
//! "MyValue": "hello",
|
||||
//! "ExampleValue": 84
|
||||
//! },
|
||||
//! "TheSecondEntry":
|
||||
//! {
|
||||
//! "MyValue": "world"
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! // Customized settings.
|
||||
//! {
|
||||
//! "Settings in a stack":
|
||||
//! {
|
||||
//! // Add a new entry before "AnOriginalEntry" in the original document.
|
||||
//! "NewEntry":
|
||||
//! {
|
||||
//! "$stack_before": "AnOriginalEntry",
|
||||
//! "MyValue": 42
|
||||
//! },
|
||||
//! // Add a second entry after "AnOriginalEntry" in the original document.
|
||||
//! "SecondNewEntry":
|
||||
//! {
|
||||
//! "$stack_after": "AnOriginalEntry",
|
||||
//! "MyValue": "FortyTwo".
|
||||
//! },
|
||||
//! // Update a value in "AnOriginalEntry".
|
||||
//! "AnOriginalEntry":
|
||||
//! {
|
||||
//! "ExampleValue": 42
|
||||
//! },
|
||||
//! // Delete the "TheSecondEntry" from the settings.
|
||||
//! "TheSecondEntry" : null,
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! The ConfigurableStack uses an AZStd::shared_ptr to store the values. This supports settings up a base class and specifying
|
||||
//! derived classes in the settings, but requires that the base and derived classes all have a memory allocator associated with
|
||||
//! them (i.e. by using the "AZ_CLASS_ALLOCATOR" macro) and that the relation of the classes is reflected. Loading a
|
||||
//! ConfigurableStack can be done using the GetObject call on the SettingsRegistryInterface.
|
||||
|
||||
class ReflectContext;
|
||||
|
||||
class ConfigurableStackInterface
|
||||
{
|
||||
public:
|
||||
friend class JsonConfigurableStackSerializer;
|
||||
|
||||
virtual ~ConfigurableStackInterface() = default;
|
||||
|
||||
virtual const TypeId& GetNodeType() const = 0;
|
||||
|
||||
protected:
|
||||
enum class InsertPosition
|
||||
{
|
||||
Before,
|
||||
After
|
||||
};
|
||||
virtual void* AddNode(AZStd::string name) = 0;
|
||||
virtual void* AddNode(AZStd::string name, AZStd::string_view target, InsertPosition position) = 0;
|
||||
};
|
||||
|
||||
template<typename StackBaseType>
|
||||
class ConfigurableStack final : public ConfigurableStackInterface
|
||||
{
|
||||
public:
|
||||
using NodeValue = AZStd::shared_ptr<StackBaseType>;
|
||||
using Node = AZStd::pair<AZStd::string, NodeValue>;
|
||||
using NodeContainer = AZStd::vector<Node>;
|
||||
using Iterator = typename NodeContainer::iterator;
|
||||
using ConstIterator = typename NodeContainer::const_iterator;
|
||||
|
||||
~ConfigurableStack() override = default;
|
||||
|
||||
const TypeId& GetNodeType() const override;
|
||||
|
||||
Iterator begin();
|
||||
Iterator end();
|
||||
ConstIterator begin() const;
|
||||
ConstIterator end() const;
|
||||
|
||||
size_t size() const;
|
||||
|
||||
protected:
|
||||
void* AddNode(AZStd::string name) override;
|
||||
void* AddNode(AZStd::string name, AZStd::string_view target, InsertPosition position) override;
|
||||
|
||||
private:
|
||||
NodeContainer m_nodes;
|
||||
};
|
||||
|
||||
AZ_TYPE_INFO_TEMPLATE(ConfigurableStack, "{0A3D2038-6E6A-4EFD-A1B4-F30D947E21B1}", AZ_TYPE_INFO_TYPENAME);
|
||||
|
||||
template<typename StackBaseType>
|
||||
struct SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>
|
||||
{
|
||||
using ConfigurableStackType = ConfigurableStack<StackBaseType>;
|
||||
|
||||
class GenericConfigurableStackInfo : public GenericClassInfo
|
||||
{
|
||||
public:
|
||||
AZ_TYPE_INFO(GenericConfigurableStackInfo, "{FC5A9353-D0DE-48F6-81B5-1CB2985C5F65}");
|
||||
|
||||
GenericConfigurableStackInfo();
|
||||
|
||||
SerializeContext::ClassData* GetClassData() override;
|
||||
size_t GetNumTemplatedArguments() override;
|
||||
const Uuid& GetTemplatedTypeId([[maybe_unused]] size_t element) override;
|
||||
const Uuid& GetSpecializedTypeId() const override;
|
||||
const Uuid& GetGenericTypeId() const override;
|
||||
|
||||
void Reflect(SerializeContext* serializeContext) override;
|
||||
|
||||
SerializeContext::ClassData m_classData;
|
||||
};
|
||||
|
||||
using ClassInfoType = GenericConfigurableStackInfo;
|
||||
|
||||
static ClassInfoType* GetGenericInfo();
|
||||
static const Uuid& GetClassTypeId();
|
||||
};
|
||||
|
||||
class JsonConfigurableStackSerializer : public BaseJsonSerializer
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(JsonConfigurableStackSerializer, "{45A31805-9058-41A9-B1A3-71E2CB4D9237}", BaseJsonSerializer);
|
||||
AZ_CLASS_ALLOCATOR_DECL;
|
||||
|
||||
JsonSerializationResult::Result Load(
|
||||
void* outputValue,
|
||||
const Uuid& outputValueTypeId,
|
||||
const rapidjson::Value& inputValue,
|
||||
JsonDeserializerContext& context) override;
|
||||
|
||||
JsonSerializationResult::Result Store(
|
||||
rapidjson::Value& outputValue,
|
||||
const void* inputValue,
|
||||
const void* defaultValue,
|
||||
const Uuid& valueTypeId,
|
||||
JsonSerializerContext& context) override;
|
||||
|
||||
static void Reflect(ReflectContext* context);
|
||||
|
||||
private:
|
||||
JsonSerializationResult::Result LoadFromArray(
|
||||
void* outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context);
|
||||
JsonSerializationResult::Result LoadFromObject(
|
||||
void* outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context);
|
||||
|
||||
static constexpr const char* StackBefore = "$stack_before";
|
||||
static constexpr const char* StackAfter = "$stack_after";
|
||||
};
|
||||
} // namespace AZ
|
||||
|
||||
#include <AzCore/Settings/ConfigurableStack.inl>
|
||||
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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
|
||||
{
|
||||
//
|
||||
// ConfigurableStack
|
||||
//
|
||||
|
||||
template<typename StackBaseType>
|
||||
const TypeId& ConfigurableStack<StackBaseType>::GetNodeType() const
|
||||
{
|
||||
return azrtti_typeid<NodeValue>();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
auto ConfigurableStack<StackBaseType>::begin() -> Iterator
|
||||
{
|
||||
return m_nodes.begin();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
auto ConfigurableStack<StackBaseType>::end() -> Iterator
|
||||
{
|
||||
return m_nodes.end();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
auto ConfigurableStack<StackBaseType>::begin() const -> ConstIterator
|
||||
{
|
||||
return m_nodes.begin();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
auto ConfigurableStack<StackBaseType>::end() const -> ConstIterator
|
||||
{
|
||||
return m_nodes.end();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
size_t ConfigurableStack<StackBaseType>::size() const
|
||||
{
|
||||
return m_nodes.size();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
void* ConfigurableStack<StackBaseType>::AddNode(AZStd::string name)
|
||||
{
|
||||
Node& result = m_nodes.emplace_back(AZStd::move(name));
|
||||
return &result.second;
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
void* ConfigurableStack<StackBaseType>::AddNode(AZStd::string name, AZStd::string_view target, InsertPosition position)
|
||||
{
|
||||
auto end = m_nodes.end();
|
||||
for (auto it = m_nodes.begin(); it != end; ++it)
|
||||
{
|
||||
if (it->first == target)
|
||||
{
|
||||
if (position == InsertPosition::After)
|
||||
{
|
||||
++it;
|
||||
}
|
||||
auto result = m_nodes.insert(it, Node(AZStd::move(name), {}));
|
||||
return &result->second;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// SerializeGenericTypeInfo
|
||||
//
|
||||
|
||||
|
||||
template<typename StackBaseType>
|
||||
SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::GenericConfigurableStackInfo()
|
||||
: m_classData{ SerializeContext::ClassData::Create<ConfigurableStackType>(
|
||||
"AZ::ConfigurableStack", GetSpecializedTypeId(), Internal::NullFactory::GetInstance(), nullptr, nullptr) }
|
||||
{
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
SerializeContext::ClassData* SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::GetClassData()
|
||||
{
|
||||
return &m_classData;
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
size_t SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::GetNumTemplatedArguments()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
const Uuid& SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::GetTemplatedTypeId(
|
||||
[[maybe_unused]] size_t element)
|
||||
{
|
||||
return SerializeGenericTypeInfo<StackBaseType>::GetClassTypeId();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
const Uuid& SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::GetSpecializedTypeId() const
|
||||
{
|
||||
return azrtti_typeid<ConfigurableStackType>();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
const Uuid& SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::GetGenericTypeId() const
|
||||
{
|
||||
return TYPEINFO_Uuid();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
void SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::Reflect(SerializeContext* serializeContext)
|
||||
{
|
||||
if (serializeContext)
|
||||
{
|
||||
serializeContext->RegisterGenericClassInfo(GetSpecializedTypeId(), this, &AnyTypeInfoConcept<ConfigurableStackType>::CreateAny);
|
||||
if (serializeContext->FindClassData(azrtti_typeid<AZStd::shared_ptr<StackBaseType>>()) == nullptr)
|
||||
{
|
||||
serializeContext->RegisterGenericType<AZStd::shared_ptr<StackBaseType>>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
auto SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GetGenericInfo() -> ClassInfoType*
|
||||
{
|
||||
return GetCurrentSerializeContextModule().CreateGenericClassInfo<ConfigurableStackType>();
|
||||
}
|
||||
|
||||
template<typename StackBaseType>
|
||||
const Uuid& SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GetClassTypeId()
|
||||
{
|
||||
return GetGenericInfo()->GetClassData()->m_typeId;
|
||||
}
|
||||
} // namespace AZ
|
||||
|
||||
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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 <Tests/DOM/DomFixtures.h>
|
||||
#include <AzCore/DOM/DomPrefixTree.h>
|
||||
|
||||
#define REGISTER_TREE_BENCHMARK(BaseClass, Method) \
|
||||
BENCHMARK_REGISTER_F(BaseClass, Method)->Args({10, 1})->Args({1000, 1})->Args({10, 5})->Args({1000, 5})
|
||||
|
||||
namespace AZ::Dom::Benchmark
|
||||
{
|
||||
class DomPrefixTreeBenchmark : public Tests::DomBenchmarkFixture
|
||||
{
|
||||
public:
|
||||
void SetUpHarness() override
|
||||
{
|
||||
Tests::DomBenchmarkFixture::SetUpHarness();
|
||||
m_registeredPaths = AZStd::make_unique<AZStd::vector<Path>>();
|
||||
}
|
||||
|
||||
void TearDownHarness() override
|
||||
{
|
||||
m_registeredPaths.reset();
|
||||
m_tree.Clear();
|
||||
Tests::DomBenchmarkFixture::TearDownHarness();
|
||||
}
|
||||
|
||||
void SetupTree(benchmark::State& state)
|
||||
{
|
||||
const size_t numPaths = aznumeric_cast<size_t>(state.range(0));
|
||||
const size_t depth = aznumeric_cast<size_t>(state.range(1));
|
||||
|
||||
Path path("/root");
|
||||
for (size_t i = 0; i < numPaths; ++i)
|
||||
{
|
||||
for (size_t c = 0; c < depth; ++c)
|
||||
{
|
||||
path.Push(i % 4);
|
||||
m_tree.SetValue(path, AZStd::string::format("entry%zu", i));
|
||||
m_registeredPaths->push_back(path);
|
||||
}
|
||||
|
||||
for (size_t c = 0; c < depth; ++c)
|
||||
{
|
||||
path.Pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DomPrefixTree<AZStd::string> m_tree;
|
||||
AZStd::unique_ptr<AZStd::vector<Path>> m_registeredPaths;
|
||||
};
|
||||
|
||||
BENCHMARK_DEFINE_F(DomPrefixTreeBenchmark, FindValue_ExactPath)(benchmark::State& state)
|
||||
{
|
||||
SetupTree(state);
|
||||
for (auto _ : state)
|
||||
{
|
||||
for (const auto& pathToCheck : *m_registeredPaths)
|
||||
{
|
||||
benchmark::DoNotOptimize(m_tree.ValueAtPath(pathToCheck, PrefixTreeMatch::ExactPath));
|
||||
}
|
||||
}
|
||||
state.SetItemsProcessed(m_registeredPaths->size() * state.iterations());
|
||||
}
|
||||
REGISTER_TREE_BENCHMARK(DomPrefixTreeBenchmark, FindValue_ExactPath);
|
||||
|
||||
BENCHMARK_DEFINE_F(DomPrefixTreeBenchmark, FindValue_InexactPath)(benchmark::State& state)
|
||||
{
|
||||
SetupTree(state);
|
||||
for (auto _ : state)
|
||||
{
|
||||
for (const auto& pathToCheck : *m_registeredPaths)
|
||||
{
|
||||
benchmark::DoNotOptimize(m_tree.ValueAtPath(pathToCheck, PrefixTreeMatch::PathAndSubpaths));
|
||||
}
|
||||
}
|
||||
state.SetItemsProcessed(m_registeredPaths->size() * state.iterations());
|
||||
}
|
||||
REGISTER_TREE_BENCHMARK(DomPrefixTreeBenchmark, FindValue_InexactPath);
|
||||
|
||||
BENCHMARK_DEFINE_F(DomPrefixTreeBenchmark, FindValue_VisitEntries)(benchmark::State& state)
|
||||
{
|
||||
SetupTree(state);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
m_tree.VisitPath(Path(), PrefixTreeMatch::PathAndSubpaths, [](const Path& path, const AZStd::string& value)
|
||||
{
|
||||
benchmark::DoNotOptimize(path);
|
||||
benchmark::DoNotOptimize(value);
|
||||
});
|
||||
}
|
||||
state.SetItemsProcessed(m_registeredPaths->size() * state.iterations());
|
||||
}
|
||||
REGISTER_TREE_BENCHMARK(DomPrefixTreeBenchmark, FindValue_VisitEntries);
|
||||
}
|
||||
|
||||
#undef REGISTER_TREE_BENCHMARK
|
||||
@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/DOM/DomPrefixTree.h>
|
||||
#include <Tests/DOM/DomFixtures.h>
|
||||
|
||||
namespace AZ::Dom::Tests
|
||||
{
|
||||
using DomPrefixTreeTests = DomTestFixture;
|
||||
|
||||
static_assert(!RangeConvertibleToPrefixTree<AZStd::vector<int>, int>, "Non-pair range should not convert to tree");
|
||||
static_assert(
|
||||
!RangeConvertibleToPrefixTree<AZStd::vector<AZStd::pair<Path, AZStd::string>>, int>,
|
||||
"Mismatched value type should not convert to tree");
|
||||
static_assert(
|
||||
!RangeConvertibleToPrefixTree<AZStd::vector<AZStd::pair<AZStd::string, int>>, int>,
|
||||
"Mismatched value type should not convert to tree");
|
||||
static_assert(
|
||||
RangeConvertibleToPrefixTree<AZStd::vector<AZStd::pair<Path, int>>, int>,
|
||||
"Vector with path / key type pairs should convert to tree");
|
||||
|
||||
TEST_F(DomPrefixTreeTests, InitializeFromInitializerList)
|
||||
{
|
||||
DomPrefixTree<int> tree({
|
||||
{ Path(), 0 },
|
||||
{ Path("/foo/bar"), 1 },
|
||||
});
|
||||
|
||||
EXPECT_EQ(0, *tree.ValueAtPath(Path(), PrefixTreeMatch::ExactPath));
|
||||
EXPECT_EQ(1, *tree.ValueAtPath(Path("/foo/bar"), PrefixTreeMatch::ExactPath));
|
||||
}
|
||||
|
||||
TEST_F(DomPrefixTreeTests, InitializeFromRange)
|
||||
{
|
||||
AZStd::vector<AZStd::pair<Path, int>> container({
|
||||
{ Path(), 21 },
|
||||
{ Path("/foo/bar"), 42 },
|
||||
});
|
||||
DomPrefixTree<int> tree(container);
|
||||
|
||||
EXPECT_EQ(21, *tree.ValueAtPath(Path(), PrefixTreeMatch::ExactPath));
|
||||
EXPECT_EQ(42, *tree.ValueAtPath(Path("/foo/bar"), PrefixTreeMatch::ExactPath));
|
||||
}
|
||||
|
||||
TEST_F(DomPrefixTreeTests, GetAndSetRoot)
|
||||
{
|
||||
DomPrefixTree<AZStd::string> tree;
|
||||
tree.SetValue(Path(), "root");
|
||||
EXPECT_EQ("root", *tree.ValueAtPath(Path(), PrefixTreeMatch::ExactPath));
|
||||
}
|
||||
|
||||
TEST_F(DomPrefixTreeTests, GetExactPath)
|
||||
{
|
||||
DomPrefixTree<int> tree;
|
||||
|
||||
tree.SetValue(Path("/foo/0"), 0);
|
||||
tree.SetValue(Path("/foo/1"), 42);
|
||||
tree.SetValue(Path("/foo/foo"), 1);
|
||||
tree.SetValue(Path("/foo/bar"), 2);
|
||||
|
||||
EXPECT_EQ(0, *tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::ExactPath));
|
||||
EXPECT_EQ(42, *tree.ValueAtPath(Path("/foo/1"), PrefixTreeMatch::ExactPath));
|
||||
EXPECT_EQ(1, *tree.ValueAtPath(Path("/foo/foo"), PrefixTreeMatch::ExactPath));
|
||||
EXPECT_EQ(2, *tree.ValueAtPath(Path("/foo/bar"), PrefixTreeMatch::ExactPath));
|
||||
|
||||
EXPECT_EQ(nullptr, tree.ValueAtPath(Path(), PrefixTreeMatch::ExactPath));
|
||||
EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::ExactPath));
|
||||
EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/foo/0/subpath"), PrefixTreeMatch::ExactPath));
|
||||
}
|
||||
|
||||
TEST_F(DomPrefixTreeTests, GetSubpath)
|
||||
{
|
||||
DomPrefixTree<int> tree;
|
||||
|
||||
tree.SetValue(Path("/foo/0"), 0);
|
||||
tree.SetValue(Path("/foo/1"), 42);
|
||||
|
||||
EXPECT_EQ(0, *tree.ValueAtPath(Path("/foo/0/bar"), PrefixTreeMatch::SubpathsOnly));
|
||||
EXPECT_EQ(0, *tree.ValueAtPath(Path("/foo/0/bar/baz"), PrefixTreeMatch::SubpathsOnly));
|
||||
EXPECT_EQ(42, *tree.ValueAtPath(Path("/foo/1/0"), PrefixTreeMatch::SubpathsOnly));
|
||||
|
||||
EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::SubpathsOnly));
|
||||
EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/foo/1"), PrefixTreeMatch::SubpathsOnly));
|
||||
}
|
||||
|
||||
TEST_F(DomPrefixTreeTests, GetPathOrSubpath)
|
||||
{
|
||||
DomPrefixTree<int> tree;
|
||||
|
||||
tree.SetValue(Path("/foo/0"), 0);
|
||||
tree.SetValue(Path("/foo/1"), 42);
|
||||
|
||||
EXPECT_EQ(0, *tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::PathAndSubpaths));
|
||||
EXPECT_EQ(0, *tree.ValueAtPath(Path("/foo/0/bar"), PrefixTreeMatch::PathAndSubpaths));
|
||||
EXPECT_EQ(0, *tree.ValueAtPath(Path("/foo/0/bar/baz"), PrefixTreeMatch::PathAndSubpaths));
|
||||
EXPECT_EQ(42, *tree.ValueAtPath(Path("/foo/1"), PrefixTreeMatch::PathAndSubpaths));
|
||||
EXPECT_EQ(42, *tree.ValueAtPath(Path("/foo/1/0"), PrefixTreeMatch::PathAndSubpaths));
|
||||
|
||||
EXPECT_EQ(nullptr, tree.ValueAtPath(Path(), PrefixTreeMatch::PathAndSubpaths));
|
||||
EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::PathAndSubpaths));
|
||||
EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/path/0"), PrefixTreeMatch::PathAndSubpaths));
|
||||
}
|
||||
|
||||
TEST_F(DomPrefixTreeTests, RemovePath)
|
||||
{
|
||||
DomPrefixTree<int> tree;
|
||||
|
||||
tree.SetValue(Path(), 20);
|
||||
tree.SetValue(Path("/foo"), 40);
|
||||
tree.SetValue(Path("/foo/0"), 80);
|
||||
|
||||
tree.EraseValue(Path("/foo"));
|
||||
|
||||
EXPECT_EQ(20, *tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::PathAndSubpaths));
|
||||
EXPECT_EQ(80, *tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::PathAndSubpaths));
|
||||
}
|
||||
|
||||
TEST_F(DomPrefixTreeTests, RemovePathAndChildren)
|
||||
{
|
||||
DomPrefixTree<int> tree;
|
||||
|
||||
tree.SetValue(Path(), 20);
|
||||
tree.SetValue(Path("/foo"), 40);
|
||||
tree.SetValue(Path("/foo/0"), 80);
|
||||
|
||||
tree.EraseValue(Path("/foo"), true);
|
||||
|
||||
EXPECT_EQ(20, *tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::PathAndSubpaths));
|
||||
EXPECT_EQ(20, *tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::PathAndSubpaths));
|
||||
}
|
||||
|
||||
TEST_F(DomPrefixTreeTests, ClearTree)
|
||||
{
|
||||
DomPrefixTree<int> tree;
|
||||
|
||||
tree.SetValue(Path(), 20);
|
||||
tree.SetValue(Path("/foo"), 40);
|
||||
|
||||
tree.Clear();
|
||||
|
||||
EXPECT_EQ(-10, tree.ValueAtPathOrDefault(Path("/foo"), -10, PrefixTreeMatch::PathAndSubpaths));
|
||||
}
|
||||
|
||||
TEST_F(DomPrefixTreeTests, Visit)
|
||||
{
|
||||
DomPrefixTree<int> tree;
|
||||
|
||||
AZStd::vector<AZStd::pair<Path, int>> results;
|
||||
auto visitorFn = [&results](const Path& path, int n)
|
||||
{
|
||||
results.emplace_back(path, n);
|
||||
};
|
||||
|
||||
auto validateResult = [&results](const Path& path, int n)
|
||||
{
|
||||
for (const auto& pair : results)
|
||||
{
|
||||
if (pair.first == path)
|
||||
{
|
||||
return pair.second == n;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
tree.SetValue(Path("/foo"), 99);
|
||||
tree.SetValue(Path("/foo/0"), 0);
|
||||
tree.SetValue(Path("/foo/1"), 42);
|
||||
tree.SetValue(Path("/bar/bat"), 1);
|
||||
tree.SetValue(Path("/bar/baz"), 2);
|
||||
|
||||
tree.VisitPath(Path("/bar"), PrefixTreeMatch::ExactPath, visitorFn);
|
||||
EXPECT_EQ(0, results.size());
|
||||
results.clear();
|
||||
|
||||
tree.VisitPath(Path("/foo/0"), PrefixTreeMatch::ExactPath, visitorFn);
|
||||
EXPECT_EQ(1, results.size());
|
||||
EXPECT_TRUE(validateResult(Path("/foo/0"), 0));
|
||||
results.clear();
|
||||
|
||||
tree.VisitPath(Path("/foo/1"), PrefixTreeMatch::ExactPath, visitorFn);
|
||||
EXPECT_EQ(1, results.size());
|
||||
EXPECT_TRUE(validateResult(Path("/foo/1"), 42));
|
||||
results.clear();
|
||||
|
||||
tree.VisitPath(Path("/foo"), PrefixTreeMatch::SubpathsOnly, visitorFn);
|
||||
EXPECT_EQ(2, results.size());
|
||||
EXPECT_TRUE(validateResult(Path("/foo/0"), 0));
|
||||
EXPECT_TRUE(validateResult(Path("/foo/1"), 42));
|
||||
results.clear();
|
||||
|
||||
tree.VisitPath(Path("/foo"), PrefixTreeMatch::PathAndSubpaths, visitorFn);
|
||||
EXPECT_EQ(3, results.size());
|
||||
EXPECT_TRUE(validateResult(Path("/foo"), 99));
|
||||
EXPECT_TRUE(validateResult(Path("/foo/0"), 0));
|
||||
EXPECT_TRUE(validateResult(Path("/foo/1"), 42));
|
||||
results.clear();
|
||||
}
|
||||
} // namespace AZ::Dom::Tests
|
||||
@ -0,0 +1,281 @@
|
||||
/*
|
||||
* 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/JSON/document.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/Settings/ConfigurableStack.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
#include <AzCore/Serialization/Json/RegistrationContext.h>
|
||||
#include <AzCore/Serialization/Json/JsonSystemComponent.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
struct ConfigInt
|
||||
{
|
||||
AZ_TYPE_INFO(UnitTest::ConfigInt, "{1FAF6E55-7FA4-4FFA-8C41-34F422B8E8AB}");
|
||||
AZ_CLASS_ALLOCATOR(ConfigInt, AZ::SystemAllocator, 0);
|
||||
|
||||
int m_value;
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto sc = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
sc->Class<ConfigInt>()->Field("Value", &ConfigInt::m_value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ConfigurableStackTests : public ScopedAllocatorSetupFixture
|
||||
{
|
||||
void Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto sc = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
AZ::JsonSystemComponent::Reflect(sc);
|
||||
ConfigInt::Reflect(sc);
|
||||
sc->RegisterGenericType<AZ::ConfigurableStack<ConfigInt>>();
|
||||
}
|
||||
else if (auto jrc = azrtti_cast<AZ::JsonRegistrationContext*>(context))
|
||||
{
|
||||
AZ::JsonSystemComponent::Reflect(jrc);
|
||||
}
|
||||
}
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
ScopedAllocatorSetupFixture::SetUp();
|
||||
|
||||
m_serializeContext = AZStd::make_unique<AZ::SerializeContext>();
|
||||
m_jsonRegistrationContext = AZStd::make_unique<AZ::JsonRegistrationContext>();
|
||||
|
||||
Reflect(m_serializeContext.get());
|
||||
Reflect(m_jsonRegistrationContext.get());
|
||||
|
||||
m_deserializationSettings.m_registrationContext = m_jsonRegistrationContext.get();
|
||||
m_deserializationSettings.m_serializeContext = m_serializeContext.get();
|
||||
}
|
||||
|
||||
void TearDown()
|
||||
{
|
||||
m_jsonRegistrationContext->EnableRemoveReflection();
|
||||
Reflect(m_jsonRegistrationContext.get());
|
||||
m_jsonRegistrationContext->DisableRemoveReflection();
|
||||
|
||||
m_serializeContext->EnableRemoveReflection();
|
||||
Reflect(m_serializeContext.get());
|
||||
m_serializeContext->DisableRemoveReflection();
|
||||
|
||||
ScopedAllocatorSetupFixture::TearDown();
|
||||
}
|
||||
|
||||
void ObjectTest(AZStd::string_view jsonText)
|
||||
{
|
||||
AZ::ConfigurableStack<ConfigInt> stack;
|
||||
|
||||
rapidjson::Document document;
|
||||
document.Parse(jsonText.data(), jsonText.length());
|
||||
ASSERT_FALSE(document.HasParseError());
|
||||
AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::Load(stack, document, m_deserializationSettings);
|
||||
ASSERT_EQ(AZ::JsonSerializationResult::Processing::Completed, result.GetProcessing());
|
||||
ASSERT_EQ(4, stack.size());
|
||||
|
||||
int numberCounter = 0;
|
||||
int valueCounter = 42;
|
||||
for (auto& [name, value] : stack)
|
||||
{
|
||||
EXPECT_STREQ(AZStd::string::format("Value%i", numberCounter).c_str(), name.c_str());
|
||||
EXPECT_EQ(valueCounter, value->m_value);
|
||||
numberCounter++;
|
||||
valueCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::SerializeContext> m_serializeContext;
|
||||
AZStd::unique_ptr<AZ::JsonRegistrationContext> m_jsonRegistrationContext;
|
||||
AZ::JsonDeserializerSettings m_deserializationSettings;
|
||||
};
|
||||
|
||||
TEST_F(ConfigurableStackTests, DeserializeArray)
|
||||
{
|
||||
AZ::ConfigurableStack<ConfigInt> stack;
|
||||
|
||||
rapidjson::Document document;
|
||||
document.Parse(
|
||||
R"([
|
||||
{ "Value": 42 },
|
||||
{ "Value": 43 },
|
||||
{ "Value": 44 },
|
||||
{ "Value": 45 }
|
||||
])");
|
||||
ASSERT_FALSE(document.HasParseError());
|
||||
AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::Load(stack, document, m_deserializationSettings);
|
||||
ASSERT_EQ(AZ::JsonSerializationResult::Processing::Completed, result.GetProcessing());
|
||||
ASSERT_EQ(4, stack.size());
|
||||
|
||||
int numberCounter = 0;
|
||||
int valueCounter = 42;
|
||||
for (auto& [name, value] : stack)
|
||||
{
|
||||
EXPECT_STREQ(AZStd::to_string(numberCounter).c_str(), name.c_str());
|
||||
EXPECT_EQ(valueCounter, value->m_value);
|
||||
numberCounter++;
|
||||
valueCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ConfigurableStackTests, DeserializeObject)
|
||||
{
|
||||
ObjectTest(
|
||||
R"({
|
||||
"Value0": { "Value": 42 },
|
||||
"Value1": { "Value": 43 },
|
||||
"Value2": { "Value": 44 },
|
||||
"Value3": { "Value": 45 }
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(ConfigurableStackTests, DeserializeObjectWithLateBefore)
|
||||
{
|
||||
ObjectTest(
|
||||
R"({
|
||||
"Value0": { "Value": 42 },
|
||||
"Value2": { "Value": 44 },
|
||||
"Value3": { "Value": 45 },
|
||||
"Value1":
|
||||
{
|
||||
"$stack_before": "Value2",
|
||||
"Value": 43
|
||||
}
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(ConfigurableStackTests, DeserializeObjectWithEarlyBefore)
|
||||
{
|
||||
ObjectTest(
|
||||
R"({
|
||||
"Value1":
|
||||
{
|
||||
"$stack_before": "Value2",
|
||||
"Value": 43
|
||||
},
|
||||
"Value0": { "Value": 42 },
|
||||
"Value2": { "Value": 44 },
|
||||
"Value3": { "Value": 45 }
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(ConfigurableStackTests, DeserializeObjectWithLateAfter)
|
||||
{
|
||||
ObjectTest(
|
||||
R"({
|
||||
"Value0": { "Value": 42 },
|
||||
"Value2": { "Value": 44 },
|
||||
"Value3": { "Value": 45 },
|
||||
"Value1":
|
||||
{
|
||||
"$stack_after": "Value0",
|
||||
"Value": 43
|
||||
}
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(ConfigurableStackTests, DeserializeObjectWithEarlyAfter)
|
||||
{
|
||||
ObjectTest(
|
||||
R"({
|
||||
"Value1":
|
||||
{
|
||||
"$stack_after": "Value0",
|
||||
"Value": 43
|
||||
},
|
||||
"Value0": { "Value": 42 },
|
||||
"Value2": { "Value": 44 },
|
||||
"Value3": { "Value": 45 }
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(ConfigurableStackTests, DeserializeObjectWithBeforeFirst)
|
||||
{
|
||||
ObjectTest(
|
||||
R"({
|
||||
"Value1": { "Value": 43 },
|
||||
"Value2": { "Value": 44 },
|
||||
"Value3": { "Value": 45 },
|
||||
"Value0":
|
||||
{
|
||||
"$stack_before": "Value1",
|
||||
"Value": 42
|
||||
}
|
||||
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(ConfigurableStackTests, DeserializeObjectWithInsertAfterLast)
|
||||
{
|
||||
ObjectTest(
|
||||
R"({
|
||||
"Value3":
|
||||
{
|
||||
"$stack_after": "Value2",
|
||||
"Value": 45
|
||||
},
|
||||
"Value0": { "Value": 42 },
|
||||
"Value1": { "Value": 43 },
|
||||
"Value2": { "Value": 44 }
|
||||
})");
|
||||
}
|
||||
|
||||
TEST_F(ConfigurableStackTests, DeserializeObjectWithInvalidTarget)
|
||||
{
|
||||
AZ::ConfigurableStack<ConfigInt> stack;
|
||||
|
||||
rapidjson::Document document;
|
||||
document.Parse(
|
||||
R"({
|
||||
"Value1":
|
||||
{
|
||||
"$stack_after": "airplane",
|
||||
"Value": 43
|
||||
},
|
||||
"Value0": { "Value": 42 },
|
||||
"Value2": { "Value": 44 },
|
||||
"Value3": { "Value": 45 }
|
||||
})");
|
||||
ASSERT_FALSE(document.HasParseError());
|
||||
AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::Load(stack, document, m_deserializationSettings);
|
||||
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, result.GetProcessing());
|
||||
EXPECT_EQ(AZ::JsonSerializationResult::Outcomes::PartialSkip, result.GetOutcome());
|
||||
EXPECT_EQ(3, stack.size());
|
||||
}
|
||||
|
||||
TEST_F(ConfigurableStackTests, DeserializeObjectWithInvalidTargetType)
|
||||
{
|
||||
AZ::ConfigurableStack<ConfigInt> stack;
|
||||
|
||||
rapidjson::Document document;
|
||||
document.Parse(
|
||||
R"({
|
||||
"Value1":
|
||||
{
|
||||
"$stack_after": 42,
|
||||
"Value": 43
|
||||
},
|
||||
"Value0": { "Value": 42 },
|
||||
"Value2": { "Value": 44 },
|
||||
"Value3": { "Value": 45 }
|
||||
})");
|
||||
ASSERT_FALSE(document.HasParseError());
|
||||
AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::Load(stack, document, m_deserializationSettings);
|
||||
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, result.GetProcessing());
|
||||
EXPECT_EQ(AZ::JsonSerializationResult::Outcomes::PartialSkip, result.GetOutcome());
|
||||
EXPECT_EQ(3, stack.size());
|
||||
}
|
||||
} // namespace UnitTest
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue