Merge branch 'development' into Atom/jromnoa/fix-asset-path-calls-to-use-new-asset-test-class
This commit is contained in:
@@ -29,6 +29,10 @@ class TestAutomation(EditorTestSuite):
|
||||
class AtomEditorComponents_DepthOfFieldAdded(EditorSharedTest):
|
||||
from Atom.tests import hydra_AtomEditorComponents_DepthOfFieldAdded as test_module
|
||||
|
||||
@pytest.mark.test_case_id("C36525659")
|
||||
class AtomEditorComponents_DiffuseProbeGridAdded(EditorSharedTest):
|
||||
from Atom.tests import hydra_AtomEditorComponents_DiffuseProbeGridAdded as test_module
|
||||
|
||||
@pytest.mark.test_case_id("C32078120")
|
||||
class AtomEditorComponents_DirectionalLightAdded(EditorSharedTest):
|
||||
from Atom.tests import hydra_AtomEditorComponents_DirectionalLightAdded as test_module
|
||||
|
||||
@@ -116,7 +116,7 @@ class AtomComponentProperties:
|
||||
return properties[property]
|
||||
|
||||
@staticmethod
|
||||
def diffuse_probe(property: str = 'name') -> str:
|
||||
def diffuse_probe_grid(property: str = 'name') -> str:
|
||||
"""
|
||||
Diffuse Probe Grid component properties. Requires one of 'shapes'.
|
||||
- 'shapes' a list of supported shapes as component names.
|
||||
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
|
||||
class Tests:
|
||||
creation_undo = (
|
||||
"UNDO Entity creation success",
|
||||
"UNDO Entity creation failed")
|
||||
creation_redo = (
|
||||
"REDO Entity creation success",
|
||||
"REDO Entity creation failed")
|
||||
diffuse_probe_grid_creation = (
|
||||
"Diffuse Probe Grid Entity successfully created",
|
||||
"Diffuse Probe Grid Entity failed to be created")
|
||||
diffuse_probe_grid_component = (
|
||||
"Entity has a Diffuse Probe Grid component",
|
||||
"Entity failed to find Diffuse Probe Grid component")
|
||||
diffuse_probe_grid_disabled = (
|
||||
"Diffuse Probe Grid component disabled",
|
||||
"Diffuse Probe Grid component was not disabled")
|
||||
diffuse_probe_grid_enabled = (
|
||||
"Diffuse Probe Grid component enabled",
|
||||
"Diffuse Probe Grid component was not enabled")
|
||||
enter_game_mode = (
|
||||
"Entered game mode",
|
||||
"Failed to enter game mode")
|
||||
exit_game_mode = (
|
||||
"Exited game mode",
|
||||
"Couldn't exit game mode")
|
||||
is_visible = (
|
||||
"Entity is visible",
|
||||
"Entity was not visible")
|
||||
is_hidden = (
|
||||
"Entity is hidden",
|
||||
"Entity was not hidden")
|
||||
entity_deleted = (
|
||||
"Entity deleted",
|
||||
"Entity was not deleted")
|
||||
deletion_undo = (
|
||||
"UNDO deletion success",
|
||||
"UNDO deletion failed")
|
||||
deletion_redo = (
|
||||
"REDO deletion success",
|
||||
"REDO deletion failed")
|
||||
|
||||
|
||||
def AtomEditorComponents_DiffuseProbeGrid_AddedToEntity():
|
||||
"""
|
||||
Summary:
|
||||
Tests the Diffuse Probe Grid component can be added to an entity and has the expected functionality.
|
||||
|
||||
Test setup:
|
||||
- Wait for Editor idle loop.
|
||||
- Open the "Base" level.
|
||||
|
||||
Expected Behavior:
|
||||
The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components.
|
||||
Creation and deletion undo/redo should also work.
|
||||
|
||||
Test Steps:
|
||||
1) Create a Diffuse Probe Grid entity with no components.
|
||||
2) Add a Diffuse Probe Grid component to Diffuse Probe Grid entity.
|
||||
3) UNDO the entity creation and component addition.
|
||||
4) REDO the entity creation and component addition.
|
||||
5) Verify Diffuse Probe Grid component not enabled.
|
||||
6) Add Shape component since it is required by the Diffuse Probe Grid component.
|
||||
7) Verify Diffuse Probe Grid component is enabled.
|
||||
8) Enter/Exit game mode.
|
||||
9) Test IsHidden.
|
||||
10) Test IsVisible.
|
||||
11) Delete Diffuse Probe Grid entity.
|
||||
12) UNDO deletion.
|
||||
13) REDO deletion.
|
||||
14) Look for errors.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
import azlmbr.legacy.general as general
|
||||
|
||||
from editor_python_test_tools.editor_entity_utils import EditorEntity
|
||||
from editor_python_test_tools.utils import Report, Tracer, TestHelper
|
||||
from Atom.atom_utils.atom_constants import AtomComponentProperties
|
||||
|
||||
with Tracer() as error_tracer:
|
||||
# Test setup begins.
|
||||
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
|
||||
TestHelper.init_idle()
|
||||
TestHelper.open_level("", "Base")
|
||||
|
||||
# Test steps begin.
|
||||
# 1. Create a Diffuse Probe Grid entity with no components.
|
||||
diffuse_probe_grid_entity = EditorEntity.create_editor_entity(AtomComponentProperties.diffuse_probe_grid())
|
||||
Report.critical_result(Tests.diffuse_probe_grid_creation, diffuse_probe_grid_entity.exists())
|
||||
|
||||
# 2. Add a Diffuse Probe Grid component to Diffuse Probe Grid entity.
|
||||
diffuse_probe_grid_component = diffuse_probe_grid_entity.add_component(
|
||||
AtomComponentProperties.diffuse_probe_grid())
|
||||
Report.critical_result(
|
||||
Tests.diffuse_probe_grid_component,
|
||||
diffuse_probe_grid_entity.has_component(AtomComponentProperties.diffuse_probe_grid()))
|
||||
|
||||
# 3. UNDO the entity creation and component addition.
|
||||
# -> UNDO component addition.
|
||||
general.undo()
|
||||
# -> UNDO naming entity.
|
||||
general.undo()
|
||||
# -> UNDO selecting entity.
|
||||
general.undo()
|
||||
# -> UNDO entity creation.
|
||||
general.undo()
|
||||
general.idle_wait_frames(1)
|
||||
Report.result(Tests.creation_undo, not diffuse_probe_grid_entity.exists())
|
||||
|
||||
# 4. REDO the entity creation and component addition.
|
||||
# -> REDO entity creation.
|
||||
general.redo()
|
||||
# -> REDO selecting entity.
|
||||
general.redo()
|
||||
# -> REDO naming entity.
|
||||
general.redo()
|
||||
# -> REDO component addition.
|
||||
general.redo()
|
||||
general.idle_wait_frames(1)
|
||||
Report.result(Tests.creation_redo, diffuse_probe_grid_entity.exists())
|
||||
|
||||
# 5. Verify Diffuse Probe Grid component not enabled.
|
||||
Report.result(Tests.diffuse_probe_grid_disabled, not diffuse_probe_grid_component.is_enabled())
|
||||
|
||||
# 6. Add Shape component since it is required by the Diffuse Probe Grid component.
|
||||
for shape in AtomComponentProperties.diffuse_probe_grid('shapes'):
|
||||
diffuse_probe_grid_entity.add_component(shape)
|
||||
test_shape = (
|
||||
f"Entity has a {shape} component",
|
||||
f"Entity did not have a {shape} component")
|
||||
Report.result(test_shape, diffuse_probe_grid_entity.has_component(shape))
|
||||
|
||||
# 7. Check if required shape allows Diffuse Probe Grid to be enabled
|
||||
Report.result(Tests.diffuse_probe_grid_enabled, diffuse_probe_grid_component.is_enabled())
|
||||
|
||||
# Undo to remove each added shape except the last one and verify Diffuse Probe Grid is not enabled.
|
||||
if not (shape == AtomComponentProperties.diffuse_probe_grid('shapes')[-1]):
|
||||
general.undo()
|
||||
TestHelper.wait_for_condition(lambda: not diffuse_probe_grid_entity.has_component(shape), 1.0)
|
||||
Report.result(Tests.diffuse_probe_grid_disabled, not diffuse_probe_grid_component.is_enabled())
|
||||
|
||||
# 8. Enter/Exit game mode.
|
||||
TestHelper.enter_game_mode(Tests.enter_game_mode)
|
||||
general.idle_wait_frames(1)
|
||||
TestHelper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
# 9. Test IsHidden.
|
||||
diffuse_probe_grid_entity.set_visibility_state(False)
|
||||
Report.result(Tests.is_hidden, diffuse_probe_grid_entity.is_hidden() is True)
|
||||
|
||||
# 10. Test IsVisible.
|
||||
diffuse_probe_grid_entity.set_visibility_state(True)
|
||||
general.idle_wait_frames(1)
|
||||
Report.result(Tests.is_visible, diffuse_probe_grid_entity.is_visible() is True)
|
||||
|
||||
# 11. Delete Diffuse Probe Grid entity.
|
||||
diffuse_probe_grid_entity.delete()
|
||||
Report.result(Tests.entity_deleted, not diffuse_probe_grid_entity.exists())
|
||||
|
||||
# 12. UNDO deletion.
|
||||
general.undo()
|
||||
Report.result(Tests.deletion_undo, diffuse_probe_grid_entity.exists())
|
||||
|
||||
# 13. REDO deletion.
|
||||
general.redo()
|
||||
Report.result(Tests.deletion_redo, not diffuse_probe_grid_entity.exists())
|
||||
|
||||
# 14. Look for errors or asserts.
|
||||
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
|
||||
for error_info in error_tracer.errors:
|
||||
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
|
||||
for assert_info in error_tracer.asserts:
|
||||
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(AtomEditorComponents_DiffuseProbeGrid_AddedToEntity)
|
||||
+11
-8
@@ -50,6 +50,7 @@ def NvCloth_AddClothSimulationToActor():
|
||||
|
||||
# Constants
|
||||
FRAMES_IN_GAME_MODE = 200
|
||||
CLOTH_GEM_ERROR_WARNING_LIST = ["Cloth", "NvCloth", "ClothComponentMesh", "ActorClothSkinning", "ActorClothSkinning", "TangentSpaceHelper", "MeshAssetHelper", "ActorAssetHelper", "ClothDebugDisplay"]
|
||||
|
||||
helper.init_idle()
|
||||
# 1) Load the level
|
||||
@@ -64,14 +65,16 @@ def NvCloth_AddClothSimulationToActor():
|
||||
general.idle_wait_frames(FRAMES_IN_GAME_MODE)
|
||||
|
||||
# 5) Verify there are no errors and warnings in the logs
|
||||
success_condition = not (section_tracer.has_errors or section_tracer.has_warnings)
|
||||
Report.result(Tests.no_errors_and_warnings_found, success_condition)
|
||||
if not success_condition:
|
||||
if section_tracer.has_warnings:
|
||||
Report.info(f"Warnings found: {section_tracer.warnings}")
|
||||
if section_tracer.has_errors:
|
||||
Report.info(f"Errors found: {section_tracer.errors}")
|
||||
Report.failure(Tests.no_errors_and_warnings_found)
|
||||
has_errors_or_warnings = False
|
||||
for error_msg in section_tracer.errors:
|
||||
if error_msg.window in CLOTH_GEM_ERROR_WARNING_LIST:
|
||||
has_errors_or_warnings = True
|
||||
Report.info(f"Cloth error found: {error_msg}")
|
||||
for warning_msg in section_tracer.warnings:
|
||||
if warning_msg.window in CLOTH_GEM_ERROR_WARNING_LIST:
|
||||
has_errors_or_warnings = True
|
||||
Report.info(f"Cloth warning found: {warning_msg}")
|
||||
Report.result(Tests.no_errors_and_warnings_found, not has_errors_or_warnings)
|
||||
|
||||
# 6) Exit game mode
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
@@ -50,6 +50,7 @@ def NvCloth_AddClothSimulationToMesh():
|
||||
|
||||
# Constants
|
||||
FRAMES_IN_GAME_MODE = 200
|
||||
CLOTH_GEM_ERROR_WARNING_LIST = ["Cloth", "NvCloth", "ClothComponentMesh", "ActorClothSkinning", "ActorClothSkinning", "TangentSpaceHelper", "MeshAssetHelper", "ActorAssetHelper", "ClothDebugDisplay"]
|
||||
|
||||
helper.init_idle()
|
||||
# 1) Load the level
|
||||
@@ -64,14 +65,16 @@ def NvCloth_AddClothSimulationToMesh():
|
||||
general.idle_wait_frames(FRAMES_IN_GAME_MODE)
|
||||
|
||||
# 5) Verify there are no errors and warnings in the logs
|
||||
success_condition = not (section_tracer.has_errors or section_tracer.has_warnings)
|
||||
Report.result(Tests.no_errors_and_warnings_found, success_condition)
|
||||
if not success_condition:
|
||||
if section_tracer.has_warnings:
|
||||
Report.info(f"Warnings found: {section_tracer.warnings}")
|
||||
if section_tracer.has_errors:
|
||||
Report.info(f"Errors found: {section_tracer.errors}")
|
||||
Report.failure(Tests.no_errors_and_warnings_found)
|
||||
has_errors_or_warnings = False
|
||||
for error_msg in section_tracer.errors:
|
||||
if error_msg.window in CLOTH_GEM_ERROR_WARNING_LIST:
|
||||
has_errors_or_warnings = True
|
||||
Report.info(f"Cloth error found: {error_msg}")
|
||||
for warning_msg in section_tracer.warnings:
|
||||
if warning_msg.window in CLOTH_GEM_ERROR_WARNING_LIST:
|
||||
has_errors_or_warnings = True
|
||||
Report.info(f"Cloth warning found: {warning_msg}")
|
||||
Report.result(Tests.no_errors_and_warnings_found, not has_errors_or_warnings)
|
||||
|
||||
# 6) Exit game mode
|
||||
helper.exit_game_mode(Tests.exit_game_mode)
|
||||
|
||||
+1
-1
@@ -158,7 +158,7 @@ def bundler_batch_setup_fixture(request, workspace, asset_processor, timeout) ->
|
||||
else:
|
||||
cmd.append(f"--{key}")
|
||||
if append_defaults:
|
||||
cmd.append(f"--project-path={workspace.project}")
|
||||
cmd.append(f"--project-path={os.path.join(workspace.paths.engine_root(), workspace.project)}")
|
||||
return cmd
|
||||
|
||||
# ******
|
||||
|
||||
@@ -14,6 +14,7 @@ include(cmake/Version.cmake)
|
||||
include(cmake/OutputDirectory.cmake)
|
||||
|
||||
if(NOT PROJECT_NAME)
|
||||
include(cmake/CompilerSettings.cmake)
|
||||
project(O3DE
|
||||
LANGUAGES C CXX
|
||||
VERSION ${LY_VERSION_STRING}
|
||||
|
||||
@@ -249,38 +249,9 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
RUNTIME_DEPENDENCIES
|
||||
Gem::LmbrCentral
|
||||
)
|
||||
|
||||
ly_add_googletest(
|
||||
NAME Legacy::EditorLib.Tests
|
||||
)
|
||||
|
||||
ly_add_target(
|
||||
NAME EditorLib.Camera.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
|
||||
NAMESPACE Legacy
|
||||
FILES_CMAKE
|
||||
Lib/Tests/Camera/editor_lib_camera_test_files.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PRIVATE
|
||||
.
|
||||
BUILD_DEPENDENCIES
|
||||
PRIVATE
|
||||
AZ::AzCore
|
||||
AZ::AzTest
|
||||
AZ::AzToolsFramework
|
||||
AZ::AzTestShared
|
||||
Legacy::EditorLib
|
||||
Gem::Camera.Editor
|
||||
Gem::AtomToolsFramework.Static
|
||||
RUNTIME_DEPENDENCIES
|
||||
Legacy::EditorLib
|
||||
)
|
||||
|
||||
ly_add_source_properties(
|
||||
SOURCES Lib/Tests/Camera/test_EditorCamera.cpp
|
||||
PROPERTY COMPILE_DEFINITIONS
|
||||
VALUES CAMERA_EDITOR_MODULE="$<TARGET_FILE_BASE_NAME:Camera.Editor>"
|
||||
)
|
||||
|
||||
ly_add_googletest(
|
||||
NAME Legacy::EditorLib.Camera.Tests
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -3827,7 +3827,8 @@ void CCryEditApp::OnOpenQuickAccessBar()
|
||||
}
|
||||
|
||||
QRect geo = m_pQuickAccessBar->geometry();
|
||||
geo.moveCenter(MainWindow::instance()->geometry().center());
|
||||
auto mainWindow = MainWindow::instance();
|
||||
geo.moveCenter(mainWindow->mapToGlobal(mainWindow->geometry().center()));
|
||||
m_pQuickAccessBar->setGeometry(geo);
|
||||
m_pQuickAccessBar->setVisible(true);
|
||||
m_pQuickAccessBar->setFocus();
|
||||
|
||||
@@ -17,7 +17,7 @@ void SetEditorEnvironment(SSystemGlobalEnvironment* pEnv)
|
||||
|
||||
void AttachEditorAZEnvironment(AZ::EnvironmentInstance azEnv)
|
||||
{
|
||||
AZ::Environment::Attach(azEnv, true);
|
||||
AZ::Environment::Attach(azEnv);
|
||||
}
|
||||
|
||||
void DetachEditorAZEnvironment()
|
||||
|
||||
@@ -13,9 +13,32 @@
|
||||
#include <AzCore/std/smart_ptr/make_shared.h>
|
||||
#include <AzFramework/Render/IntersectorInterface.h>
|
||||
#include <AzToolsFramework/Viewport/ViewportMessages.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h>
|
||||
#include <EditorViewportSettings.h>
|
||||
|
||||
AZ_CVAR(
|
||||
bool,
|
||||
ed_cameraPinDefaultOrbit,
|
||||
true,
|
||||
nullptr,
|
||||
AZ::ConsoleFunctorFlags::Null,
|
||||
"Sets whether the default orbit point moves with the camera or not");
|
||||
AZ_CVAR(
|
||||
bool,
|
||||
ed_cameraDefaultOrbitAxesOrtho,
|
||||
true,
|
||||
nullptr,
|
||||
AZ::ConsoleFunctorFlags::Null,
|
||||
"Sets whether to draw the default orbit point as orthographic or not");
|
||||
AZ_CVAR(
|
||||
float,
|
||||
ed_cameraDefaultOrbitFadeDuration,
|
||||
0.5f,
|
||||
nullptr,
|
||||
AZ::ConsoleFunctorFlags::Null,
|
||||
"Sets how long the default orbit point should take to appear and disappear");
|
||||
|
||||
namespace SandboxEditor
|
||||
{
|
||||
static AzFramework::TranslateCameraInputChannelIds BuildTranslateCameraInputChannelIds()
|
||||
@@ -174,7 +197,7 @@ namespace SandboxEditor
|
||||
return SandboxEditor::CameraScrollSpeed();
|
||||
};
|
||||
|
||||
const auto pivotFn = []
|
||||
const auto pivotFn = []() -> AZStd::optional<AZ::Vector3>
|
||||
{
|
||||
// use the manipulator transform as the pivot point
|
||||
AZStd::optional<AZ::Transform> entityPivot;
|
||||
@@ -187,8 +210,7 @@ namespace SandboxEditor
|
||||
return entityPivot->GetTranslation();
|
||||
}
|
||||
|
||||
// otherwise just use the identity
|
||||
return AZ::Vector3::CreateZero();
|
||||
return AZStd::nullopt;
|
||||
};
|
||||
|
||||
m_firstPersonFocusCamera =
|
||||
@@ -199,9 +221,26 @@ namespace SandboxEditor
|
||||
m_orbitCamera = AZStd::make_shared<AzFramework::OrbitCameraInput>(SandboxEditor::CameraOrbitChannelId());
|
||||
|
||||
m_orbitCamera->SetPivotFn(
|
||||
[pivotFn]([[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction)
|
||||
[this, pivotFn](const AZ::Vector3& position, const AZ::Vector3& direction)
|
||||
{
|
||||
return pivotFn();
|
||||
// return the pivot
|
||||
if (auto pivot = pivotFn())
|
||||
{
|
||||
return pivot.value();
|
||||
}
|
||||
|
||||
// start ticking and drawing (for the default pivot)
|
||||
AZ::TickBus::Handler::BusConnect();
|
||||
AzFramework::ViewportDebugDisplayEventBus::Handler::BusConnect(AzToolsFramework::GetEntityContextId());
|
||||
|
||||
m_defaultOrbiting = true;
|
||||
// calculate the default orbit point
|
||||
if (!ed_cameraPinDefaultOrbit || m_orbitCamera->Beginning())
|
||||
{
|
||||
m_defaultOrbitPoint = position + direction * SandboxEditor::CameraDefaultOrbitDistance();
|
||||
}
|
||||
|
||||
return m_defaultOrbitPoint;
|
||||
});
|
||||
|
||||
m_orbitRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(SandboxEditor::CameraOrbitLookChannelId());
|
||||
@@ -306,4 +345,67 @@ namespace SandboxEditor
|
||||
m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::ClearReferenceFrame);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorModularViewportCameraComposer::OnTick(const float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
|
||||
{
|
||||
const float delta = [duration = &ed_cameraDefaultOrbitFadeDuration, deltaTime] {
|
||||
if (*duration == 0.0f) {
|
||||
return 1.0f;
|
||||
}
|
||||
return deltaTime / *duration;
|
||||
}();
|
||||
|
||||
if (m_defaultOrbiting)
|
||||
{
|
||||
m_defaultOrbitOpacity = AZStd::min(m_defaultOrbitOpacity + delta, 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_defaultOrbitOpacity = AZStd::max(m_defaultOrbitOpacity - delta, 0.0f);
|
||||
if (m_defaultOrbitOpacity == 0.0f)
|
||||
{
|
||||
AZ::TickBus::Handler::BusDisconnect();
|
||||
AzFramework::ViewportDebugDisplayEventBus::Handler::BusDisconnect();
|
||||
}
|
||||
}
|
||||
|
||||
m_defaultOrbiting = false;
|
||||
}
|
||||
|
||||
static void DrawTransformAxis(
|
||||
AzFramework::DebugDisplayRequests& display,
|
||||
const AzFramework::CameraState& cameraState,
|
||||
const AZ::Vector3& pivot,
|
||||
const float axisLength,
|
||||
const float alpha)
|
||||
{
|
||||
const int prevState = display.GetState();
|
||||
|
||||
display.DepthWriteOff();
|
||||
display.DepthTestOff();
|
||||
display.CullOff();
|
||||
|
||||
const float orthoScale =
|
||||
ed_cameraDefaultOrbitAxesOrtho ? AzToolsFramework::CalculateScreenToWorldMultiplier(pivot, cameraState) : 1.0f;
|
||||
|
||||
display.SetColor(AZ::Color::CreateFromVector3AndFloat(AZ::Colors::Red.GetAsVector3(), alpha));
|
||||
display.DrawLine(pivot, pivot + AZ::Vector3::CreateAxisX() * axisLength * orthoScale);
|
||||
display.SetColor(AZ::Color::CreateFromVector3AndFloat(AZ::Colors::LawnGreen.GetAsVector3(), alpha));
|
||||
display.DrawLine(pivot, pivot + AZ::Vector3::CreateAxisY() * axisLength * orthoScale);
|
||||
display.SetColor(AZ::Color::CreateFromVector3AndFloat(AZ::Colors::Blue.GetAsVector3(), alpha));
|
||||
display.DrawLine(pivot, pivot + AZ::Vector3::CreateAxisZ() * axisLength * orthoScale);
|
||||
|
||||
display.DepthWriteOn();
|
||||
display.DepthTestOn();
|
||||
display.CullOn();
|
||||
|
||||
display.SetState(prevState);
|
||||
}
|
||||
|
||||
void EditorModularViewportCameraComposer::DisplayViewport(
|
||||
[[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay)
|
||||
{
|
||||
DrawTransformAxis(
|
||||
debugDisplay, AzToolsFramework::GetCameraState(viewportInfo.m_viewportId), m_defaultOrbitPoint, 1.0f, m_defaultOrbitOpacity);
|
||||
}
|
||||
} // namespace SandboxEditor
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <AtomToolsFramework/Viewport/ModularViewportCameraController.h>
|
||||
#include <AzCore/Component/TickBus.h>
|
||||
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
|
||||
#include <AzFramework/Viewport/CameraInput.h>
|
||||
#include <AzToolsFramework/API/EditorCameraBus.h>
|
||||
#include <EditorModularViewportCameraComposerBus.h>
|
||||
@@ -20,6 +22,8 @@ namespace SandboxEditor
|
||||
class EditorModularViewportCameraComposer
|
||||
: private EditorModularViewportCameraComposerNotificationBus::Handler
|
||||
, private Camera::EditorCameraNotificationBus::Handler
|
||||
, private AzFramework::ViewportDebugDisplayEventBus::Handler
|
||||
, private AZ::TickBus::Handler
|
||||
{
|
||||
public:
|
||||
SANDBOX_API explicit EditorModularViewportCameraComposer(AzFramework::ViewportId viewportId);
|
||||
@@ -29,6 +33,12 @@ namespace SandboxEditor
|
||||
SANDBOX_API AZStd::shared_ptr<AtomToolsFramework::ModularViewportCameraController> CreateModularViewportCameraController();
|
||||
|
||||
private:
|
||||
// AzFramework::ViewportDebugDisplayEventBus overrides ...
|
||||
void DisplayViewport(const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay) override;
|
||||
|
||||
// AZ::TickBus overrides ...
|
||||
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
|
||||
|
||||
//! Setup all internal camera inputs.
|
||||
void SetupCameras();
|
||||
|
||||
@@ -52,5 +62,9 @@ namespace SandboxEditor
|
||||
AZStd::shared_ptr<AzFramework::FocusCameraInput> m_orbitFocusCamera;
|
||||
|
||||
AzFramework::ViewportId m_viewportId;
|
||||
|
||||
float m_defaultOrbitOpacity = 0.0f; //!< The default orbit axes opacity (to fade in and out).
|
||||
AZ::Vector3 m_defaultOrbitPoint = AZ::Vector3::CreateZero(); //!< The orbit point to use when no entity is selected.
|
||||
bool m_defaultOrbiting = false; //!< Is the camera default orbiting (orbiting when there's no selected entity).
|
||||
};
|
||||
} // namespace SandboxEditor
|
||||
|
||||
@@ -61,7 +61,7 @@ static AZStd::vector<AZStd::string> GetEditorInputNames()
|
||||
void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serialize)
|
||||
{
|
||||
serialize.Class<CameraMovementSettings>()
|
||||
->Version(3)
|
||||
->Version(4)
|
||||
->Field("TranslateSpeed", &CameraMovementSettings::m_translateSpeed)
|
||||
->Field("RotateSpeed", &CameraMovementSettings::m_rotateSpeed)
|
||||
->Field("BoostMultiplier", &CameraMovementSettings::m_boostMultiplier)
|
||||
@@ -76,9 +76,8 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial
|
||||
->Field("OrbitYawRotationInverted", &CameraMovementSettings::m_orbitYawRotationInverted)
|
||||
->Field("PanInvertedX", &CameraMovementSettings::m_panInvertedX)
|
||||
->Field("PanInvertedY", &CameraMovementSettings::m_panInvertedY)
|
||||
->Field("DefaultPositionX", &CameraMovementSettings::m_defaultCameraPositionX)
|
||||
->Field("DefaultPositionY", &CameraMovementSettings::m_defaultCameraPositionY)
|
||||
->Field("DefaultPositionZ", &CameraMovementSettings::m_defaultCameraPositionZ);
|
||||
->Field("DefaultPosition", &CameraMovementSettings::m_defaultPosition)
|
||||
->Field("DefaultOrbitDistance", &CameraMovementSettings::m_defaultOrbitDistance);
|
||||
|
||||
serialize.Class<CameraInputSettings>()
|
||||
->Version(2)
|
||||
@@ -159,14 +158,12 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial
|
||||
AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_captureCursorLook, "Camera Capture Look Cursor",
|
||||
"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")
|
||||
AZ::Edit::UIHandlers::Vector3, &CameraMovementSettings::m_defaultPosition, "Default Camera Position",
|
||||
"Default Camera Position when a level is first 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");
|
||||
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_defaultOrbitDistance, "Default Orbit Distance",
|
||||
"The default distance to orbit about when there is no entity selected")
|
||||
->Attribute(AZ::Edit::Attributes::Min, minValue);
|
||||
|
||||
editContext->Class<CameraInputSettings>("Camera Input Settings", "")
|
||||
->DataElement(
|
||||
@@ -283,12 +280,8 @@ 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::SetCameraDefaultEditorPosition(m_cameraMovementSettings.m_defaultPosition);
|
||||
SandboxEditor::SetCameraDefaultOrbitDistance(m_cameraMovementSettings.m_defaultOrbitDistance);
|
||||
|
||||
SandboxEditor::SetCameraTranslateForwardChannelId(m_cameraInputSettings.m_translateForwardChannelId);
|
||||
SandboxEditor::SetCameraTranslateBackwardChannelId(m_cameraInputSettings.m_translateBackwardChannelId);
|
||||
@@ -325,11 +318,8 @@ void CEditorPreferencesPage_ViewportCamera::InitializeSettings()
|
||||
m_cameraMovementSettings.m_orbitYawRotationInverted = SandboxEditor::CameraOrbitYawRotationInverted();
|
||||
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_cameraMovementSettings.m_defaultPosition = SandboxEditor::CameraDefaultEditorPosition();
|
||||
m_cameraMovementSettings.m_defaultOrbitDistance = SandboxEditor::CameraDefaultOrbitDistance();
|
||||
|
||||
m_cameraInputSettings.m_translateForwardChannelId = SandboxEditor::CameraTranslateForwardChannelId().GetName();
|
||||
m_cameraInputSettings.m_translateBackwardChannelId = SandboxEditor::CameraTranslateBackwardChannelId().GetName();
|
||||
|
||||
@@ -9,9 +9,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "Include/IPreferencesPage.h"
|
||||
|
||||
#include <AzCore/Math/Vector3.h>
|
||||
#include <AzCore/RTTI/RTTI.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
|
||||
#include <QIcon>
|
||||
|
||||
inline AZ::Crc32 EditorPropertyVisibility(const bool enabled)
|
||||
@@ -43,6 +46,7 @@ private:
|
||||
{
|
||||
AZ_TYPE_INFO(CameraMovementSettings, "{60B8C07E-5F48-4171-A50B-F45558B5CCA1}")
|
||||
|
||||
AZ::Vector3 m_defaultPosition;
|
||||
float m_translateSpeed;
|
||||
float m_rotateSpeed;
|
||||
float m_scrollSpeed;
|
||||
@@ -50,16 +54,14 @@ private:
|
||||
float m_panSpeed;
|
||||
float m_boostMultiplier;
|
||||
float m_rotateSmoothness;
|
||||
bool m_rotateSmoothing;
|
||||
float m_translateSmoothness;
|
||||
bool m_translateSmoothing;
|
||||
float m_defaultOrbitDistance;
|
||||
bool m_captureCursorLook;
|
||||
bool m_orbitYawRotationInverted;
|
||||
bool m_panInvertedX;
|
||||
bool m_panInvertedY;
|
||||
float m_defaultCameraPositionX;
|
||||
float m_defaultCameraPositionY;
|
||||
float m_defaultCameraPositionZ;
|
||||
bool m_rotateSmoothing;
|
||||
bool m_translateSmoothing;
|
||||
|
||||
AZ::Crc32 RotateSmoothingVisibility() const
|
||||
{
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace SandboxEditor
|
||||
constexpr AZStd::string_view CameraTranslateSmoothingSetting = "/Amazon/Preferences/Editor/Camera/TranslateSmoothing";
|
||||
constexpr AZStd::string_view CameraRotateSmoothingSetting = "/Amazon/Preferences/Editor/Camera/RotateSmoothing";
|
||||
constexpr AZStd::string_view CameraCaptureCursorLookSetting = "/Amazon/Preferences/Editor/Camera/CaptureCursorLook";
|
||||
constexpr AZStd::string_view CameraDefaultOrbitDistanceSetting = "/Amazon/Preferences/Editor/Camera/DefaultOrbitDistance";
|
||||
constexpr AZStd::string_view CameraTranslateForwardIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateForwardId";
|
||||
constexpr AZStd::string_view CameraTranslateBackwardIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateBackwardId";
|
||||
constexpr AZStd::string_view CameraTranslateLeftIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateLeftId";
|
||||
@@ -114,15 +115,15 @@ namespace SandboxEditor
|
||||
return AZStd::make_unique<EditorViewportSettingsCallbacksImpl>();
|
||||
}
|
||||
|
||||
AZ::Vector3 DefaultEditorCameraPosition()
|
||||
AZ::Vector3 CameraDefaultEditorPosition()
|
||||
{
|
||||
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);
|
||||
return AZ::Vector3(
|
||||
aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionX, 0.0)),
|
||||
aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionY, -10.0)),
|
||||
aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionZ, 4.0)));
|
||||
}
|
||||
|
||||
void SetDefaultCameraEditorPosition(const AZ::Vector3 defaultCameraPosition)
|
||||
void SetCameraDefaultEditorPosition(const AZ::Vector3& defaultCameraPosition)
|
||||
{
|
||||
SetRegistry(CameraDefaultStartingPositionX, defaultCameraPosition.GetX());
|
||||
SetRegistry(CameraDefaultStartingPositionY, defaultCameraPosition.GetY());
|
||||
@@ -359,6 +360,16 @@ namespace SandboxEditor
|
||||
SetRegistry(CameraCaptureCursorLookSetting, capture);
|
||||
}
|
||||
|
||||
float CameraDefaultOrbitDistance()
|
||||
{
|
||||
return aznumeric_cast<float>(GetRegistry(CameraDefaultOrbitDistanceSetting, 20.0));
|
||||
}
|
||||
|
||||
void SetCameraDefaultOrbitDistance(const float distance)
|
||||
{
|
||||
SetRegistry(CameraDefaultOrbitDistanceSetting, distance);
|
||||
}
|
||||
|
||||
AzFramework::InputChannelId CameraTranslateForwardChannelId()
|
||||
{
|
||||
return AzFramework::InputChannelId(
|
||||
|
||||
@@ -33,9 +33,6 @@ 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);
|
||||
|
||||
@@ -105,6 +102,12 @@ namespace SandboxEditor
|
||||
SANDBOX_API bool CameraCaptureCursorForLook();
|
||||
SANDBOX_API void SetCameraCaptureCursorForLook(bool capture);
|
||||
|
||||
SANDBOX_API AZ::Vector3 CameraDefaultEditorPosition();
|
||||
SANDBOX_API void SetCameraDefaultEditorPosition(const AZ::Vector3& position);
|
||||
|
||||
SANDBOX_API float CameraDefaultOrbitDistance();
|
||||
SANDBOX_API void SetCameraDefaultOrbitDistance(float distance);
|
||||
|
||||
SANDBOX_API AzFramework::InputChannelId CameraTranslateForwardChannelId();
|
||||
SANDBOX_API void SetCameraTranslateForwardChannelId(AZStd::string_view cameraTranslateForwardId);
|
||||
|
||||
|
||||
@@ -620,9 +620,9 @@ void EditorViewportWidget::OnEditorNotifyEvent(EEditorNotifyEvent event)
|
||||
break;
|
||||
|
||||
case eNotify_OnEndNewScene:
|
||||
PopDisableRendering();
|
||||
|
||||
{
|
||||
PopDisableRendering();
|
||||
|
||||
Matrix34 viewTM;
|
||||
viewTM.SetIdentity();
|
||||
|
||||
@@ -638,9 +638,9 @@ void EditorViewportWidget::OnEditorNotifyEvent(EEditorNotifyEvent event)
|
||||
break;
|
||||
|
||||
case eNotify_OnEndTerrainCreate:
|
||||
PopDisableRendering();
|
||||
|
||||
{
|
||||
PopDisableRendering();
|
||||
|
||||
Matrix34 viewTM;
|
||||
viewTM.SetIdentity();
|
||||
|
||||
@@ -2021,10 +2021,6 @@ void EditorViewportWidget::SetDefaultCamera()
|
||||
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
|
||||
if (m_viewPane)
|
||||
{
|
||||
@@ -2041,6 +2037,10 @@ void EditorViewportWidget::SetDefaultCamera()
|
||||
atomViewportRequests->PushView(contextName, m_defaultView);
|
||||
}
|
||||
|
||||
// Set the default Editor Camera position.
|
||||
m_defaultViewTM.SetTranslation(Vec3(m_editorViewportSettings.DefaultEditorCameraPosition()));
|
||||
SetViewTM(m_defaultViewTM);
|
||||
|
||||
PostCameraSet();
|
||||
}
|
||||
|
||||
@@ -2527,7 +2527,7 @@ bool EditorViewportSettings::StickySelectEnabled() const
|
||||
|
||||
AZ::Vector3 EditorViewportSettings::DefaultEditorCameraPosition() const
|
||||
{
|
||||
return SandboxEditor::DefaultEditorCameraPosition();
|
||||
return SandboxEditor::CameraDefaultEditorPosition();
|
||||
}
|
||||
|
||||
AZ_CVAR_EXTERNED(bool, ed_previewGameInFullscreen_once);
|
||||
|
||||
@@ -49,9 +49,6 @@
|
||||
#include "Include/IObjectManager.h"
|
||||
#include "ActionManager.h"
|
||||
|
||||
// Including this too early will result in a linker error
|
||||
#include <CryCommon/CryLibrary.h>
|
||||
|
||||
// Implementation of System Callback structure.
|
||||
struct SSystemUserCallback
|
||||
: public ISystemUserCallback
|
||||
@@ -242,8 +239,7 @@ private:
|
||||
|
||||
AZ_PUSH_DISABLE_WARNING(4273, "-Wunknown-warning-option")
|
||||
CGameEngine::CGameEngine()
|
||||
: m_gameDll(nullptr)
|
||||
, m_bIgnoreUpdates(false)
|
||||
: m_bIgnoreUpdates(false)
|
||||
, m_ePendingGameMode(ePGM_NotPending)
|
||||
, m_modalWindowDismisser(nullptr)
|
||||
AZ_POP_DISABLE_WARNING
|
||||
@@ -253,7 +249,7 @@ AZ_POP_DISABLE_WARNING
|
||||
m_bInGameMode = false;
|
||||
m_bSimulationMode = false;
|
||||
m_bSyncPlayerPosition = true;
|
||||
m_hSystemHandle = nullptr;
|
||||
m_hSystemHandle.reset(nullptr);
|
||||
m_bJustCreated = false;
|
||||
m_levelName = "Untitled";
|
||||
m_levelExtension = EditorUtils::LevelFile::GetDefaultFileExtension();
|
||||
@@ -268,18 +264,10 @@ AZ_POP_DISABLE_WARNING
|
||||
GetIEditor()->UnregisterNotifyListener(this);
|
||||
m_pISystem->GetIMovieSystem()->SetCallback(nullptr);
|
||||
|
||||
if (m_gameDll)
|
||||
{
|
||||
CryFreeLibrary(m_gameDll);
|
||||
}
|
||||
|
||||
delete m_pISystem;
|
||||
m_pISystem = nullptr;
|
||||
|
||||
if (m_hSystemHandle)
|
||||
{
|
||||
CryFreeLibrary(m_hSystemHandle);
|
||||
}
|
||||
m_hSystemHandle.reset(nullptr);
|
||||
|
||||
delete m_pSystemUserCallback;
|
||||
}
|
||||
@@ -347,18 +335,19 @@ AZ::Outcome<void, AZStd::string> CGameEngine::Init(
|
||||
HWND hwndForInputSystem)
|
||||
{
|
||||
m_pSystemUserCallback = new SSystemUserCallback(logo);
|
||||
m_hSystemHandle = CryLoadLibraryDefName("CrySystem");
|
||||
|
||||
if (!m_hSystemHandle)
|
||||
constexpr const char* crySystemLibraryName = AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX "CrySystem" AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION;
|
||||
|
||||
m_hSystemHandle = AZ::DynamicModuleHandle::Create(crySystemLibraryName);
|
||||
if (!m_hSystemHandle->Load(true))
|
||||
{
|
||||
auto errorMessage = AZStd::string::format("%s Loading Failed", CryLibraryDefName("CrySystem"));
|
||||
auto errorMessage = AZStd::string::format("%s Loading Failed", crySystemLibraryName);
|
||||
Error(errorMessage.c_str());
|
||||
return AZ::Failure(errorMessage);
|
||||
}
|
||||
|
||||
PFNCREATESYSTEMINTERFACE pfnCreateSystemInterface =
|
||||
(PFNCREATESYSTEMINTERFACE)CryGetProcAddress(m_hSystemHandle, "CreateSystemInterface");
|
||||
|
||||
m_hSystemHandle->GetFunction<PFNCREATESYSTEMINTERFACE>("CreateSystemInterface");
|
||||
|
||||
SSystemInitParams sip;
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ struct IInitializeUIInfo;
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/Math/Vector3.h>
|
||||
|
||||
#include <AzCore/Module/DynamicModuleHandle.h>
|
||||
|
||||
class ThreadedOnErrorHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -124,11 +126,6 @@ public:
|
||||
return s_pakModifyMutex;
|
||||
}
|
||||
|
||||
inline HMODULE GetGameModule() const
|
||||
{
|
||||
return m_gameDll;
|
||||
}
|
||||
|
||||
private:
|
||||
void SetGameMode(bool inGame);
|
||||
void SwitchToInGame();
|
||||
@@ -150,8 +147,7 @@ private:
|
||||
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
|
||||
Matrix34 m_playerViewTM;
|
||||
struct SSystemUserCallback* m_pSystemUserCallback;
|
||||
HMODULE m_hSystemHandle;
|
||||
HMODULE m_gameDll;
|
||||
AZStd::unique_ptr<AZ::DynamicModuleHandle> m_hSystemHandle;
|
||||
enum EPendingGameMode
|
||||
{
|
||||
ePGM_NotPending,
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
set(FILES
|
||||
test_EditorCamera.cpp
|
||||
)
|
||||
@@ -17,43 +17,33 @@
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class EditorCameraTestEnvironment : public AZ::Test::GemTestEnvironment
|
||||
{
|
||||
// AZ::Test::GemTestEnvironment overrides ...
|
||||
void AddGemsAndComponents() override;
|
||||
};
|
||||
|
||||
void EditorCameraTestEnvironment::AddGemsAndComponents()
|
||||
{
|
||||
AddDynamicModulePaths({ CAMERA_EDITOR_MODULE });
|
||||
AddComponentDescriptors({ AzToolsFramework::Components::TransformComponent::CreateDescriptor() });
|
||||
}
|
||||
|
||||
class EditorCameraFixture : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
AZ::ComponentApplication* m_application = nullptr;
|
||||
AtomToolsFramework::ModularCameraViewportContext* m_cameraViewportContextView = nullptr;
|
||||
AZStd::unique_ptr<SandboxEditor::EditorModularViewportCameraComposer> m_editorModularViewportCameraComposer;
|
||||
AZStd::unique_ptr<AZ::DynamicModuleHandle> m_editorLibHandle;
|
||||
AzFramework::ViewportControllerListPtr m_controllerList;
|
||||
AZStd::unique_ptr<AZ::Entity> m_entity;
|
||||
AZ::Entity* m_entity = nullptr;
|
||||
AZ::ComponentDescriptor* m_transformComponent = nullptr;
|
||||
|
||||
static const AzFramework::ViewportId TestViewportId;
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
m_editorLibHandle = AZ::DynamicModuleHandle::Create("EditorLib");
|
||||
[[maybe_unused]] const bool loaded = m_editorLibHandle->Load(true);
|
||||
AZ_Assert(loaded, "EditorLib could not be loaded");
|
||||
m_application = aznew AZ::ComponentApplication;
|
||||
AZ::ComponentApplication::Descriptor appDesc;
|
||||
m_entity = m_application->Create(appDesc);
|
||||
m_transformComponent = AzToolsFramework::Components::TransformComponent::CreateDescriptor();
|
||||
m_application->RegisterComponentDescriptor(m_transformComponent);
|
||||
|
||||
m_controllerList = AZStd::make_shared<AzFramework::ViewportControllerList>();
|
||||
m_controllerList->RegisterViewportContext(TestViewportId);
|
||||
|
||||
m_entity = AZStd::make_unique<AZ::Entity>();
|
||||
m_entity->Init();
|
||||
m_entity->CreateComponent<AzToolsFramework::Components::TransformComponent>();
|
||||
m_entity->Activate();
|
||||
|
||||
m_controllerList = AZStd::make_shared<AzFramework::ViewportControllerList>();
|
||||
m_controllerList->RegisterViewportContext(TestViewportId);
|
||||
|
||||
m_editorModularViewportCameraComposer = AZStd::make_unique<SandboxEditor::EditorModularViewportCameraComposer>(TestViewportId);
|
||||
|
||||
auto controller = m_editorModularViewportCameraComposer->CreateModularViewportCameraController();
|
||||
@@ -72,8 +62,17 @@ namespace UnitTest
|
||||
{
|
||||
m_editorModularViewportCameraComposer.reset();
|
||||
m_cameraViewportContextView = nullptr;
|
||||
m_entity.reset();
|
||||
m_editorLibHandle = {};
|
||||
|
||||
if (m_application)
|
||||
{
|
||||
m_application->UnregisterComponentDescriptor(m_transformComponent);
|
||||
delete m_transformComponent;
|
||||
m_transformComponent = nullptr;
|
||||
|
||||
m_application->Destroy();
|
||||
delete m_application;
|
||||
m_application = nullptr;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -211,15 +210,3 @@ namespace UnitTest
|
||||
EXPECT_THAT(currentReferenceFrame, IsClose(AZ::Transform::CreateIdentity()));
|
||||
}
|
||||
} // namespace UnitTest
|
||||
|
||||
// required to support running integration tests with the Camera Gem
|
||||
AZTEST_EXPORT int AZ_UNIT_TEST_HOOK_NAME(int argc, char** argv)
|
||||
{
|
||||
::testing::InitGoogleMock(&argc, argv);
|
||||
AZ::Test::printUnusedParametersWarning(argc, argv);
|
||||
AZ::Test::addTestEnvironments({ new UnitTest::EditorCameraTestEnvironment() });
|
||||
int result = RUN_ALL_TESTS();
|
||||
return result;
|
||||
}
|
||||
|
||||
IMPLEMENT_TEST_EXECUTABLE_MAIN();
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
#include "EditorDefs.h"
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/UnitTest/UnitTest.h>
|
||||
#include <EditorEnvironment.h>
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
class EditorLibTestEnvironment
|
||||
: public AZ::Test::ITestEnvironment
|
||||
: public ::UnitTest::TraceBusHook
|
||||
{
|
||||
public:
|
||||
~EditorLibTestEnvironment() override = default;
|
||||
@@ -22,16 +23,20 @@ public:
|
||||
protected:
|
||||
void SetupEnvironment() override
|
||||
{
|
||||
::UnitTest::TraceBusHook::SetupEnvironment();
|
||||
|
||||
AZ::Environment::Create(nullptr);
|
||||
AttachEditorAZEnvironment(AZ::Environment::GetInstance());
|
||||
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
|
||||
AttachEditorAZEnvironment(AZ::Environment::GetInstance());
|
||||
}
|
||||
|
||||
void TeardownEnvironment() override
|
||||
{
|
||||
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
|
||||
DetachEditorAZEnvironment();
|
||||
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
|
||||
AZ::Environment::Destroy();
|
||||
|
||||
::UnitTest::TraceBusHook::TeardownEnvironment();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace LyViewPane
|
||||
static const char* const SubstanceEditor = "Substance Editor";
|
||||
static const char* const VegetationEditor = "Vegetation Editor";
|
||||
static const char* const LandscapeCanvas = "Landscape Canvas";
|
||||
static const char* const AnimationEditor = "EMotion FX Animation Editor (PREVIEW)";
|
||||
static const char* const AnimationEditor = "EMotion FX Animation Editor";
|
||||
static const char* const PhysXConfigurationEditor = "PhysX Configuration (PREVIEW)";
|
||||
|
||||
static const char* const SliceRelationships = "Slice Relationship View (PREVIEW)";
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
|
||||
// Editor
|
||||
#include "MainStatusBarItems.h"
|
||||
#include "CryLibrary.h"
|
||||
#include "ProcessInfo.h"
|
||||
|
||||
|
||||
|
||||
@@ -1061,7 +1061,7 @@ void MainWindow::InitActions()
|
||||
if (emfxEnabled.value)
|
||||
{
|
||||
QAction* action = am->AddAction(ID_OPEN_EMOTIONFX_EDITOR, tr("Animation Editor"))
|
||||
.SetToolTip(tr("Open Animation Editor (PREVIEW)"))
|
||||
.SetToolTip(tr("Open Animation Editor"))
|
||||
.SetIcon(QIcon(":/EMotionFX/EMFX_icon_32x32.png"))
|
||||
.SetApplyHoverEffect();
|
||||
QObject::connect(action, &QAction::triggered, this, []() {
|
||||
|
||||
@@ -22,6 +22,7 @@ set(FILES
|
||||
Lib/Tests/test_DisplaySettingsPythonBindings.cpp
|
||||
Lib/Tests/test_ViewportManipulatorController.cpp
|
||||
Lib/Tests/test_ModularViewportCameraController.cpp
|
||||
Lib/Tests/Camera/test_EditorCamera.cpp
|
||||
DisplaySettingsPythonFuncs.cpp
|
||||
DisplaySettingsPythonFuncs.h
|
||||
)
|
||||
|
||||
@@ -70,6 +70,9 @@ namespace AZ::Data
|
||||
|
||||
const char* GetFilename() const override { return m_filePath.c_str(); }
|
||||
|
||||
AZStd::chrono::milliseconds GetStreamingDeadline() const { return m_curDeadline; }
|
||||
AZ::IO::IStreamerTypes::Priority GetStreamingPriority() const { return m_curPriority; }
|
||||
|
||||
// AssetDataStream specific APIs
|
||||
|
||||
//! Whether or not all data has been loaded.
|
||||
|
||||
@@ -3408,7 +3408,14 @@ LUA_API const Node* lua_getDummyNode()
|
||||
const BehaviorParameter* arg = method->GetArgument(iArg);
|
||||
BehaviorClass* argClass = nullptr;
|
||||
LuaLoadFromStack fromStack = FromLuaStack(context, arg, argClass);
|
||||
AZ_Assert(fromStack, "Argument %s for Method %s doesn't have support to be converted to Lua!", arg->m_name, method->m_name.c_str());
|
||||
AZ_Assert(fromStack,
|
||||
"The argument type: %s for method: %s is not serialized and/or reflected for scripting.\n"
|
||||
"Make sure %s is added to the SerializeContext and reflected to the BehaviorContext\n"
|
||||
"For example, verify these two exist and are being called in a Reflect function:\n"
|
||||
"serializeContext->Class<%s>();\n"
|
||||
"behaviorContext->Class<%s>();\n"
|
||||
"%s will not be available for scripting unless these requirements are met."
|
||||
, arg->m_name, method->m_name.c_str(), arg->m_name, arg->m_name, arg->m_name, method->m_name.c_str());
|
||||
|
||||
m_fromLua.push_back(AZStd::make_pair(fromStack, argClass));
|
||||
}
|
||||
|
||||
@@ -27,28 +27,4 @@ namespace AzFramework
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const char* InputChannelId::GetName() const
|
||||
{
|
||||
return m_name.c_str();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const AZ::Crc32& InputChannelId::GetNameCrc32() const
|
||||
{
|
||||
return m_crc32;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputChannelId::operator==(const InputChannelId& other) const
|
||||
{
|
||||
return (m_crc32 == other.m_crc32);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputChannelId::operator!=(const InputChannelId& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -39,53 +39,58 @@ namespace AzFramework
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Constructor
|
||||
//! \param[in] name Name of the input channel (will be ignored if exceeds MAX_NAME_LENGTH)
|
||||
//! \param[in] name Name of the input channel (will be truncated if exceeds MAX_NAME_LENGTH)
|
||||
explicit constexpr InputChannelId(AZStd::string_view name = "")
|
||||
: m_name(name)
|
||||
, m_crc32(name)
|
||||
: m_name(name.substr(0, MAX_NAME_LENGTH))
|
||||
, m_crc32(name.substr(0, MAX_NAME_LENGTH))
|
||||
{
|
||||
}
|
||||
|
||||
constexpr InputChannelId(const InputChannelId& other) = default;
|
||||
constexpr InputChannelId(InputChannelId&& other) = default;
|
||||
constexpr InputChannelId& operator=(const InputChannelId& other)
|
||||
{
|
||||
m_name = other.m_name;
|
||||
m_crc32 = other.m_crc32;
|
||||
return *this;
|
||||
}
|
||||
constexpr InputChannelId& operator=(InputChannelId&& other)
|
||||
{
|
||||
m_name = AZStd::move(other.m_name);
|
||||
m_crc32 = AZStd::move(other.m_crc32);
|
||||
other.m_crc32 = 0;
|
||||
return *this;
|
||||
}
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Default copying and moving
|
||||
AZ_DEFAULT_COPY_MOVE(InputChannelId);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Default destructor
|
||||
~InputChannelId() = default;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Access to the input channel's name
|
||||
//! \return Name of the input channel
|
||||
const char* GetName() const;
|
||||
constexpr const char* GetName() const
|
||||
{
|
||||
return m_name.c_str();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Access to the crc32 of the input channel's name
|
||||
//! \return crc32 of the input channel name
|
||||
const AZ::Crc32& GetNameCrc32() const;
|
||||
constexpr const AZ::Crc32& GetNameCrc32() const
|
||||
{
|
||||
return m_crc32;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///@{
|
||||
//! Equality comparison operator
|
||||
//! \param[in] other Another instance of the class to compare for equality
|
||||
bool operator==(const InputChannelId& other) const;
|
||||
bool operator!=(const InputChannelId& other) const;
|
||||
///@}
|
||||
constexpr bool operator==(const InputChannelId& other) const
|
||||
{
|
||||
return m_crc32 == other.m_crc32;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Inequality comparison operator
|
||||
//! \param[in] other Another instance of the class to compare for inequality
|
||||
constexpr bool operator!=(const InputChannelId& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
private:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Variables
|
||||
AZStd::fixed_string<MAX_NAME_LENGTH> m_name; //!< Name of the input channel
|
||||
AZ::Crc32 m_crc32; //!< Crc32 of the input channel
|
||||
AZ::Crc32 m_crc32; //!< Crc32 of the input channel name
|
||||
};
|
||||
} // namespace AzFramework
|
||||
|
||||
|
||||
@@ -14,14 +14,6 @@
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const char* InputDeviceGamepad::Name("gamepad");
|
||||
const InputDeviceId InputDeviceGamepad::IdForIndex0(Name, 0);
|
||||
const InputDeviceId InputDeviceGamepad::IdForIndex1(Name, 1);
|
||||
const InputDeviceId InputDeviceGamepad::IdForIndex2(Name, 2);
|
||||
const InputDeviceId InputDeviceGamepad::IdForIndex3(Name, 3);
|
||||
const InputDeviceId InputDeviceGamepad::IdForIndexN(AZ::u32 n) { return InputDeviceId(Name, n); }
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceGamepad::IsGamepadDevice(const InputDeviceId& inputDeviceId)
|
||||
{
|
||||
|
||||
@@ -32,16 +32,16 @@ namespace AzFramework
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! The name used to identify any game-pad input device
|
||||
static const char* Name;
|
||||
static constexpr inline const char* Name{"gamepad"};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! The id used to identify a game-pad input device with a specific index
|
||||
///@{
|
||||
static const InputDeviceId IdForIndex0;
|
||||
static const InputDeviceId IdForIndex1;
|
||||
static const InputDeviceId IdForIndex2;
|
||||
static const InputDeviceId IdForIndex3;
|
||||
static const InputDeviceId IdForIndexN(AZ::u32 n);
|
||||
static constexpr inline InputDeviceId IdForIndex0{Name, 0};
|
||||
static constexpr inline InputDeviceId IdForIndex1{Name, 1};
|
||||
static constexpr inline InputDeviceId IdForIndex2{Name, 2};
|
||||
static constexpr inline InputDeviceId IdForIndex3{Name, 3};
|
||||
static constexpr inline InputDeviceId IdForIndexN(AZ::u32 n) { return InputDeviceId(Name, n); }
|
||||
///@}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -29,71 +29,4 @@ namespace AzFramework
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
InputDeviceId::InputDeviceId(const char* name, AZ::u32 index)
|
||||
: m_crc32(name)
|
||||
, m_index(index)
|
||||
{
|
||||
memset(m_name, 0, AZ_ARRAY_SIZE(m_name));
|
||||
azstrncpy(m_name, NAME_BUFFER_SIZE, name, MAX_NAME_LENGTH);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
InputDeviceId::InputDeviceId(const InputDeviceId& other)
|
||||
: m_crc32(other.m_crc32)
|
||||
, m_index(other.m_index)
|
||||
{
|
||||
memset(m_name, 0, AZ_ARRAY_SIZE(m_name));
|
||||
azstrcpy(m_name, NAME_BUFFER_SIZE, other.m_name);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
InputDeviceId& InputDeviceId::operator=(const InputDeviceId& other)
|
||||
{
|
||||
azstrcpy(m_name, NAME_BUFFER_SIZE, other.m_name);
|
||||
m_crc32 = other.m_crc32;
|
||||
m_index = other.m_index;
|
||||
return *this;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const char* InputDeviceId::GetName() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const AZ::Crc32& InputDeviceId::GetNameCrc32() const
|
||||
{
|
||||
return m_crc32;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
AZ::u32 InputDeviceId::GetIndex() const
|
||||
{
|
||||
return m_index;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceId::operator==(const InputDeviceId& other) const
|
||||
{
|
||||
return (m_crc32 == other.m_crc32) && (m_index == other.m_index);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceId::operator!=(const InputDeviceId& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceId::operator<(const InputDeviceId& other) const
|
||||
{
|
||||
if (m_index == other.m_index)
|
||||
{
|
||||
return m_crc32 < other.m_crc32;
|
||||
}
|
||||
return m_index < other.m_index;
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <AzCore/Math/Crc.h>
|
||||
#include <AzCore/RTTI/ReflectContext.h>
|
||||
#include <AzCore/std/hash.h>
|
||||
#include <AzCore/std/string/fixed_string.h>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
@@ -22,8 +23,7 @@ namespace AzFramework
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constants
|
||||
static const int NAME_BUFFER_SIZE = 64;
|
||||
static const int MAX_NAME_LENGTH = NAME_BUFFER_SIZE - 1;
|
||||
static constexpr int MAX_NAME_LENGTH = 64;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Allocator
|
||||
@@ -41,17 +41,16 @@ namespace AzFramework
|
||||
//! Constructor
|
||||
//! \param[in] name Name of the input device (will be truncated if exceeds MAX_NAME_LENGTH)
|
||||
//! \param[in] index Index of the input device (optional)
|
||||
explicit InputDeviceId(const char* name, AZ::u32 index = 0);
|
||||
explicit constexpr InputDeviceId(AZStd::string_view name, AZ::u32 index = 0)
|
||||
: m_name(name.substr(0, MAX_NAME_LENGTH))
|
||||
, m_crc32(name.substr(0, MAX_NAME_LENGTH))
|
||||
, m_index(index)
|
||||
{
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Copy constructor
|
||||
//! \param[in] other Another instance of the class to copy from
|
||||
InputDeviceId(const InputDeviceId& other);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Copy assignment operator
|
||||
//! \param[in] other Another instance of the class to copy from
|
||||
InputDeviceId& operator=(const InputDeviceId& other);
|
||||
// Default copying and moving
|
||||
AZ_DEFAULT_COPY_MOVE(InputDeviceId);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Default destructor
|
||||
@@ -60,12 +59,18 @@ namespace AzFramework
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Access to the input device's name
|
||||
//! \return Name of the input device
|
||||
const char* GetName() const;
|
||||
constexpr const char* GetName() const
|
||||
{
|
||||
return m_name.c_str();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Access to the crc32 of the input device's name
|
||||
//! \return crc32 of the input device name
|
||||
const AZ::Crc32& GetNameCrc32() const;
|
||||
constexpr const AZ::Crc32& GetNameCrc32() const
|
||||
{
|
||||
return m_crc32;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Access to the input device's index. Used for differentiating between multiple instances
|
||||
@@ -75,27 +80,45 @@ namespace AzFramework
|
||||
//! at startup using indicies 0->3. As gamepads connect/disconnect at runtime we assign the
|
||||
//! appropriate (system dependent) local user id (see InputDevice::GetAssignedLocalUserId).
|
||||
//! \return Index of the input device
|
||||
AZ::u32 GetIndex() const;
|
||||
constexpr AZ::u32 GetIndex() const
|
||||
{
|
||||
return m_index;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///@{
|
||||
//! Equality comparison operator
|
||||
//! \param[in] other Another instance of the class to compare for equality
|
||||
bool operator==(const InputDeviceId& other) const;
|
||||
bool operator!=(const InputDeviceId& other) const;
|
||||
///@}
|
||||
constexpr bool operator==(const InputDeviceId& other) const
|
||||
{
|
||||
return (m_crc32 == other.m_crc32) && (m_index == other.m_index);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Inequality comparison operator
|
||||
//! \param[in] other Another instance of the class to compare for inequality
|
||||
constexpr bool operator!=(const InputDeviceId& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Less than comparison operator
|
||||
//! \param[in] other Another instance of the class to compare
|
||||
bool operator<(const InputDeviceId& other) const;
|
||||
constexpr bool operator<(const InputDeviceId& other) const
|
||||
{
|
||||
if (m_index == other.m_index)
|
||||
{
|
||||
return m_crc32 < other.m_crc32;
|
||||
}
|
||||
return m_index < other.m_index;
|
||||
}
|
||||
|
||||
private:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Variables
|
||||
char m_name[NAME_BUFFER_SIZE]; //!< Name of the input device
|
||||
AZ::Crc32 m_crc32; //!< Crc32 of the input device
|
||||
AZ::u32 m_index; //!< Index of the input device
|
||||
AZStd::fixed_string<MAX_NAME_LENGTH> m_name; //!< Name of the input device
|
||||
AZ::Crc32 m_crc32; //!< Crc32 of the input device name
|
||||
AZ::u32 m_index; //!< Index of the input device
|
||||
};
|
||||
} // namespace AzFramework
|
||||
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const InputDeviceId InputDeviceKeyboard::Id("keyboard");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceKeyboard::IsKeyboardDevice(const InputDeviceId& inputDeviceId)
|
||||
{
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace AzFramework
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! The id used to identify the primary physical keyboard input device
|
||||
static const InputDeviceId Id;
|
||||
static constexpr inline InputDeviceId Id{"keyboard"};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Check whether an input device id identifies a physical keyboard (regardless of index)
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const InputDeviceId InputDeviceMotion::Id("motion");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceMotion::IsMotionDevice(const InputDeviceId& inputDeviceId)
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace AzFramework
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! The id used to identify the primary motion input device
|
||||
static const InputDeviceId Id;
|
||||
static constexpr inline InputDeviceId Id{"motion"};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Check whether an input device id identifies a motion device (regardless of index)
|
||||
|
||||
@@ -15,18 +15,6 @@
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const AZ::u32 InputDeviceMouse::MovementSampleRateDefault = 60;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const AZ::u32 InputDeviceMouse::MovementSampleRateQueueAll = std::numeric_limits<AZ::u32>::max();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const AZ::u32 InputDeviceMouse::MovementSampleRateAccumulateAll = 0;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const InputDeviceId InputDeviceMouse::Id("mouse");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceMouse::IsMouseDevice(const InputDeviceId& inputDeviceId)
|
||||
{
|
||||
|
||||
@@ -31,23 +31,23 @@ namespace AzFramework
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Default sample rate for raw mouse movement events that aims to strike a balance between
|
||||
//! responsiveness and performance.
|
||||
static const AZ::u32 MovementSampleRateDefault;
|
||||
static constexpr inline AZ::u32 MovementSampleRateDefault{60};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Sample rate for raw mouse movement that will cause all events received in the same frame
|
||||
//! to be queued and dispatched as individual events. This results in maximum responsiveness
|
||||
//! but may potentially impact performance depending how many events happen over each frame.
|
||||
static const AZ::u32 MovementSampleRateQueueAll;
|
||||
static constexpr inline AZ::u32 MovementSampleRateQueueAll{std::numeric_limits<AZ::u32>::max()};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Sample rate for raw mouse movement that will cause all events received in the same frame
|
||||
//! to be accumulated and dispatched as a single event. Optimal for performance, but results
|
||||
//! in sluggish/unresponsive mouse movement, especially when running at low frame rates.
|
||||
static const AZ::u32 MovementSampleRateAccumulateAll;
|
||||
static constexpr inline AZ::u32 MovementSampleRateAccumulateAll{0};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! The id used to identify the primary mouse input device
|
||||
static const InputDeviceId Id;
|
||||
static constexpr inline InputDeviceId Id{"mouse"};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Check whether an input device id identifies a mouse (regardless of index)
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const InputDeviceId InputDeviceTouch::Id("touch");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceTouch::IsTouchDevice(const InputDeviceId& inputDeviceId)
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace AzFramework
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! The id used to identify the primary touch input device
|
||||
static const InputDeviceId Id;
|
||||
static constexpr inline InputDeviceId Id{"touch"};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Check whether an input device id identifies a touch device (regardless of index)
|
||||
|
||||
-3
@@ -14,9 +14,6 @@
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
const InputDeviceId InputDeviceVirtualKeyboard::Id("virtual_keyboard");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
bool InputDeviceVirtualKeyboard::IsVirtualKeyboardDevice(const InputDeviceId& inputDeviceId)
|
||||
{
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ namespace AzFramework
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! The id used to identify the primary virtual keyboard input device
|
||||
static const InputDeviceId Id;
|
||||
static constexpr inline InputDeviceId Id{"virtual_keyboard"};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Check whether an input device id identifies a virtual keyboard (regardless of index)
|
||||
|
||||
@@ -30,12 +30,22 @@ namespace AzFramework
|
||||
|
||||
//! Called when the root spawnable has been assigned a new value. This may be called several times without a call to release
|
||||
//! in between.
|
||||
//! @note: The callback is not queued but immediately called from a random thread. This is done because this callback is typically
|
||||
//! used before entities are spawned and if it's queued then the entities spawn before this callback is called.
|
||||
//! @param rootSpawnable The new root spawnable that was assigned.
|
||||
//! @param generation The generation of the root spawnable. This will increment every time a new spawnable is assigned.
|
||||
virtual void OnRootSpawnableAssigned([[maybe_unused]] AZ::Data::Asset<Spawnable> rootSpawnable,
|
||||
[[maybe_unused]] uint32_t generation) {}
|
||||
//! Called when the root spawnable has completed spawning of entities. This may be called several times without a call to release
|
||||
//! in between.
|
||||
//! @note: This callback is queued and will be called with a delay and from the main thread.
|
||||
//! @param rootSpawnable The new root spawnable that was used to spawn entities from.
|
||||
//! @param generation The generation of the root spawnable. This will increment every time a new spawnable is assigned.
|
||||
virtual void OnRootSpawnableReady(
|
||||
[[maybe_unused]] AZ::Data::Asset<Spawnable> rootSpawnable, [[maybe_unused]] uint32_t generation) {}
|
||||
//! Called when the root spawnable has Released. This will only be called if there's no root spawnable assigned to take the
|
||||
//! place of the original root spawnable.
|
||||
//! Note: This callback is queued and will be called with a delay and from the main thread.
|
||||
//! @param generation The generation of the root spawnable that was released.
|
||||
virtual void OnRootSpawnableReleased([[maybe_unused]] uint32_t generation) {}
|
||||
};
|
||||
|
||||
@@ -6,12 +6,473 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Asset/AssetSerializer.h>
|
||||
#include <AzCore/RTTI/ReflectContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/std/numeric.h>
|
||||
#include <AzCore/std/sort.h>
|
||||
#include <AzCore/std/typetraits/typetraits.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
//
|
||||
// EntityAlias
|
||||
//
|
||||
|
||||
bool Spawnable::EntityAlias::HasLowerIndex(const EntityAlias& other) const
|
||||
{
|
||||
return m_sourceIndex == other.m_sourceIndex ?
|
||||
m_aliasType < other.m_aliasType :
|
||||
m_sourceIndex < other.m_sourceIndex;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// EntityAliasVisitorBase
|
||||
//
|
||||
|
||||
bool Spawnable::EntityAliasVisitorBase::IsValid(const EntityAliasList* aliases) const
|
||||
{
|
||||
return aliases != nullptr;
|
||||
}
|
||||
|
||||
bool Spawnable::EntityAliasVisitorBase::HasAliases(const EntityAliasList* aliases) const
|
||||
{
|
||||
AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
return !aliases->empty();
|
||||
}
|
||||
|
||||
bool Spawnable::EntityAliasVisitorBase::AreAllSpawnablesReady(const EntityAliasList* aliases) const
|
||||
{
|
||||
AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
for (const EntityAlias& alias : *aliases)
|
||||
{
|
||||
if (!alias.m_queueLoad ||
|
||||
alias.m_aliasType == Spawnable::EntityAliasType::Original ||
|
||||
alias.m_aliasType == Spawnable::EntityAliasType::Disable)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!alias.m_spawnable.IsReady() && !alias.m_spawnable.IsError())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasVisitorBase::begin(const EntityAliasList* aliases) const -> EntityAliasList::const_iterator
|
||||
{
|
||||
AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
return aliases->cbegin();
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasVisitorBase::end(const EntityAliasList* aliases) const -> EntityAliasList::const_iterator
|
||||
{
|
||||
AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
return aliases->cend();
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasVisitorBase::cbegin(const EntityAliasList* aliases) const -> EntityAliasList::const_iterator
|
||||
{
|
||||
AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
return aliases->cbegin();
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasVisitorBase::cend(const EntityAliasList* aliases) const -> EntityAliasList::const_iterator
|
||||
{
|
||||
AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
return aliases->cend();
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitorBase::ListTargetSpawnables(
|
||||
const EntityAliasList* aliases, const ListTargetSpawanblesCallback& callback) const
|
||||
{
|
||||
AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
AZStd::unordered_set<AZ::Data::AssetId> spawnableIds;
|
||||
for (const Spawnable::EntityAlias& alias : *aliases)
|
||||
{
|
||||
// If the spawnable id is not valid it means that the alias is referencing the spawnable it's stored on.
|
||||
if (alias.m_spawnable.GetId().IsValid())
|
||||
{
|
||||
auto it = spawnableIds.find(alias.m_spawnable.GetId());
|
||||
if (it == spawnableIds.end())
|
||||
{
|
||||
callback(alias.m_spawnable);
|
||||
spawnableIds.emplace(alias.m_spawnable.GetId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitorBase::ListTargetSpawnables(
|
||||
const EntityAliasList* aliases, AZ::Crc32 tag, const ListTargetSpawanblesCallback& callback) const
|
||||
{
|
||||
AZ_Assert(aliases, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
AZStd::unordered_set<AZ::Data::AssetId> spawnableIds;
|
||||
for (const Spawnable::EntityAlias& alias : *aliases)
|
||||
{
|
||||
// If the spawnable id is not valid it means that the alias is referencing the spawnable it's stored on.
|
||||
if (alias.m_tag == tag && alias.m_spawnable.GetId().IsValid())
|
||||
{
|
||||
auto it = spawnableIds.find(alias.m_spawnable.GetId());
|
||||
if (it == spawnableIds.end())
|
||||
{
|
||||
callback(alias.m_spawnable);
|
||||
spawnableIds.emplace(alias.m_spawnable.GetId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// EntityAliasVisitor
|
||||
//
|
||||
|
||||
Spawnable::EntityAliasVisitor::EntityAliasVisitor(Spawnable& owner, EntityAliasList* entityAliasList)
|
||||
: m_owner(owner)
|
||||
, m_entityAliasList(entityAliasList)
|
||||
{
|
||||
}
|
||||
|
||||
Spawnable::EntityAliasVisitor::~EntityAliasVisitor()
|
||||
{
|
||||
if (IsValid())
|
||||
{
|
||||
Optimize();
|
||||
|
||||
AZ_Assert(
|
||||
m_owner.m_shareState == ShareState::ReadWrite, "Attempting to unlock a spawnable that's not in the locked state (%i).",
|
||||
m_owner.m_shareState.load());
|
||||
m_owner.m_shareState = ShareState::NotShared;
|
||||
}
|
||||
}
|
||||
|
||||
Spawnable::EntityAliasVisitor::EntityAliasVisitor(EntityAliasVisitor&& rhs)
|
||||
: m_owner(rhs.m_owner)
|
||||
, m_entityAliasList(rhs.m_entityAliasList)
|
||||
{
|
||||
m_dirty = rhs.m_dirty;
|
||||
|
||||
rhs.m_entityAliasList = nullptr;
|
||||
rhs.m_dirty = false;
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasVisitor::operator=(EntityAliasVisitor&& rhs) -> EntityAliasVisitor&
|
||||
{
|
||||
if (this != &rhs)
|
||||
{
|
||||
this->~EntityAliasVisitor();
|
||||
new(this) EntityAliasVisitor(AZStd::move(rhs));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Spawnable::EntityAliasVisitor::IsValid() const
|
||||
{
|
||||
return EntityAliasVisitorBase::IsValid(m_entityAliasList);
|
||||
}
|
||||
|
||||
bool Spawnable::EntityAliasVisitor::HasAliases() const
|
||||
{
|
||||
return EntityAliasVisitorBase::HasAliases(m_entityAliasList);
|
||||
}
|
||||
|
||||
bool Spawnable::EntityAliasVisitor::AreAllSpawnablesReady() const
|
||||
{
|
||||
return EntityAliasVisitorBase::AreAllSpawnablesReady(m_entityAliasList);
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasVisitor::begin() const -> EntityAliasList::const_iterator
|
||||
{
|
||||
return EntityAliasVisitorBase::begin(m_entityAliasList);
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasVisitor::end() const -> EntityAliasList::const_iterator
|
||||
{
|
||||
return EntityAliasVisitorBase::end(m_entityAliasList);
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasVisitor::cbegin() const -> EntityAliasList::const_iterator
|
||||
{
|
||||
return EntityAliasVisitorBase::cbegin(m_entityAliasList);
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasVisitor::cend() const -> EntityAliasList::const_iterator
|
||||
{
|
||||
return EntityAliasVisitorBase::cend(m_entityAliasList);
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitor::ListTargetSpawnables(const ListTargetSpawanblesCallback& callback) const
|
||||
{
|
||||
EntityAliasVisitorBase::ListTargetSpawnables(m_entityAliasList, callback);
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitor::ListTargetSpawnables(AZ::Crc32 tag, const ListTargetSpawanblesCallback& callback) const
|
||||
{
|
||||
EntityAliasVisitorBase::ListTargetSpawnables(m_entityAliasList, tag, callback);
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitor::AddAlias(
|
||||
AZ::Data::Asset<Spawnable> targetSpawnable,
|
||||
AZ::Crc32 tag,
|
||||
uint32_t sourceIndex,
|
||||
uint32_t targetIndex,
|
||||
Spawnable::EntityAliasType aliasType,
|
||||
bool queueLoad)
|
||||
{
|
||||
AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
AZ_Assert(sourceIndex < m_owner.GetEntities().size(), "Invalid source index (%i) for entity alias", sourceIndex);
|
||||
if (targetSpawnable.IsReady())
|
||||
{
|
||||
AZ_Assert(
|
||||
targetIndex < targetSpawnable->GetEntities().size(), "Invalid target index (%i) for entity alias '%s'", targetIndex,
|
||||
targetSpawnable.GetHint().c_str());
|
||||
}
|
||||
|
||||
m_entityAliasList->push_back(Spawnable::EntityAlias{ targetSpawnable, tag, sourceIndex, targetIndex, aliasType, queueLoad });
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitor::ListSpawnablesRequiringLoad(const ListSpawnablesRequiringLoadCallback& callback)
|
||||
{
|
||||
AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
for (Spawnable::EntityAlias& alias : *m_entityAliasList)
|
||||
{
|
||||
if (alias.m_queueLoad &&
|
||||
alias.m_aliasType != Spawnable::EntityAliasType::Original &&
|
||||
alias.m_aliasType != Spawnable::EntityAliasType::Disable &&
|
||||
!alias.m_spawnable.IsLoading() &&
|
||||
!alias.m_spawnable.IsReady() &&
|
||||
!alias.m_spawnable.IsError())
|
||||
{
|
||||
callback(alias.m_spawnable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitor::UpdateAliases(const UpdateCallback& callback)
|
||||
{
|
||||
AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
for (Spawnable::EntityAlias& alias : *m_entityAliasList)
|
||||
{
|
||||
AZ::Data::Asset<Spawnable> targetSpawnable(alias.m_spawnable);
|
||||
callback(alias.m_aliasType, alias.m_queueLoad, targetSpawnable, alias.m_tag, alias.m_sourceIndex, alias.m_targetIndex);
|
||||
}
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitor::UpdateAliases(AZ::Crc32 tag, const UpdateCallback& callback)
|
||||
{
|
||||
AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
for (Spawnable::EntityAlias& alias : *m_entityAliasList)
|
||||
{
|
||||
if (alias.m_tag == tag)
|
||||
{
|
||||
AZ::Data::Asset<Spawnable> targetSpawnable(alias.m_spawnable);
|
||||
callback(alias.m_aliasType, alias.m_queueLoad, targetSpawnable, alias.m_tag, alias.m_sourceIndex, alias.m_targetIndex);
|
||||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitor::UpdateAliasType(uint32_t index, Spawnable::EntityAliasType newType)
|
||||
{
|
||||
AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
AZ_Assert(
|
||||
index < m_entityAliasList->size(), "Unable to update entity alias at index %i as there are only %zu aliases in spawnable.",
|
||||
index, m_entityAliasList->size());
|
||||
(*m_entityAliasList)[index].m_aliasType = newType;
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasVisitor::Optimize()
|
||||
{
|
||||
AZ_Assert(m_entityAliasList, "Attempting to visit entity aliases on a spawnable that wasn't locked.");
|
||||
if (m_dirty)
|
||||
{
|
||||
AZStd::stable_sort(
|
||||
m_entityAliasList->begin(), m_entityAliasList->end(),
|
||||
[](const Spawnable::EntityAlias& lhs, const Spawnable::EntityAlias& rhs)
|
||||
{
|
||||
// Sort by source index from smallest to largest so during spawning the entities can be iterated linearly over.
|
||||
// If the source index is the same then sort by alias type so the next steps can optimize away superfluous steps.
|
||||
return lhs.HasLowerIndex(rhs);
|
||||
});
|
||||
|
||||
// Remove aliases that are not going to have any practical effect and insert aliases where needed to simplify the spawning.
|
||||
// This is done at runtime rather than at build time because the above ebus allows other systems to make adjustments to the
|
||||
// aliases, for instance Networking can decide to disable certain aliases when running on a client. This in turn also requires
|
||||
// the aliases to be in their recorded order during building as the ebus handlers may depend on that order to determine what
|
||||
// entities need to be updated.
|
||||
uint32_t previousIndex = AZStd::numeric_limits<uint32_t>::max();
|
||||
Spawnable::EntityAliasType previousType =
|
||||
static_cast<Spawnable::EntityAliasType>(AZStd::numeric_limits<AZStd::underlying_type_t<Spawnable::EntityAliasType>>::max());
|
||||
Spawnable::EntityAlias* it = m_entityAliasList->begin();
|
||||
Spawnable::EntityAlias* end = m_entityAliasList->end();
|
||||
while (it < end)
|
||||
{
|
||||
// If there's a switch to a new source index and the previous index only had an original it can
|
||||
// be removed.
|
||||
if (previousType == Spawnable::EntityAliasType::Original && previousIndex != it->m_sourceIndex)
|
||||
{
|
||||
it = m_entityAliasList->erase(it - 1);
|
||||
end = m_entityAliasList->end();
|
||||
if (it == end)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (it->m_aliasType)
|
||||
{
|
||||
case Spawnable::EntityAliasType::Original:
|
||||
[[fallthrough]];
|
||||
case Spawnable::EntityAliasType::Disable:
|
||||
[[fallthrough]];
|
||||
case Spawnable::EntityAliasType::Replace:
|
||||
// If the previous entry was a disabled, original or replace alias then remove it as it will be overwritten by the
|
||||
// current entry.
|
||||
if (previousIndex == it->m_sourceIndex &&
|
||||
(previousType == Spawnable::EntityAliasType::Original ||
|
||||
previousType == Spawnable::EntityAliasType::Disable ||
|
||||
previousType == Spawnable::EntityAliasType::Replace))
|
||||
{
|
||||
previousIndex = it->m_sourceIndex;
|
||||
previousType = it->m_aliasType;
|
||||
// Erase instead of a swap-and-pop in order to preserver the order.
|
||||
it = m_entityAliasList->erase(it - 1) + 1;
|
||||
end = m_entityAliasList->end();
|
||||
}
|
||||
else
|
||||
{
|
||||
previousIndex = it->m_sourceIndex;
|
||||
previousType = it->m_aliasType;
|
||||
++it;
|
||||
}
|
||||
break;
|
||||
case Spawnable::EntityAliasType::Additional:
|
||||
[[fallthrough]];
|
||||
case Spawnable::EntityAliasType::Merge:
|
||||
// If this is the first entry for this index then insert an original in front of it so the spawnable entity manager
|
||||
// doesn't have to check for the case there's a merge and/or addition without an entity to extend.
|
||||
if (previousIndex != it->m_sourceIndex)
|
||||
{
|
||||
Spawnable::EntityAlias insert;
|
||||
// No load, as the asset is already loaded.
|
||||
insert.m_spawnable = AZ::Data::Asset<Spawnable>({}, azrtti_typeid<Spawnable>());
|
||||
insert.m_sourceIndex = it->m_sourceIndex;
|
||||
insert.m_targetIndex = it->m_sourceIndex; // Source index as the original entry for this slot is added.
|
||||
insert.m_aliasType = Spawnable::EntityAliasType::Original;
|
||||
|
||||
previousIndex = it->m_sourceIndex;
|
||||
previousType = it->m_aliasType;
|
||||
|
||||
// Insert to maintain the order.
|
||||
it = m_entityAliasList->insert(it, AZStd::move(insert));
|
||||
it += 2;
|
||||
end = m_entityAliasList->end();
|
||||
}
|
||||
else
|
||||
{
|
||||
previousType = it->m_aliasType;
|
||||
++it;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
AZ_Assert(false, "Invalid Spawnable entity alias type found during asset loading: %i", it->m_aliasType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the last entry is an "Original" in which case it can be removed.
|
||||
if (!m_entityAliasList->empty() && m_entityAliasList->back().m_aliasType == Spawnable::EntityAliasType::Original)
|
||||
{
|
||||
m_entityAliasList->pop_back();
|
||||
}
|
||||
|
||||
// Reclaim memory because after this point the aliases will not change anymore.
|
||||
m_entityAliasList->shrink_to_fit();
|
||||
m_dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// EntityAliasConstVisitor
|
||||
//
|
||||
|
||||
Spawnable::EntityAliasConstVisitor::EntityAliasConstVisitor(const Spawnable& owner, const EntityAliasList* entityAliasList)
|
||||
: m_owner(owner)
|
||||
, m_entityAliasList(entityAliasList)
|
||||
{
|
||||
}
|
||||
|
||||
Spawnable::EntityAliasConstVisitor::~EntityAliasConstVisitor()
|
||||
{
|
||||
if (IsValid())
|
||||
{
|
||||
AZ_Assert(
|
||||
m_owner.m_shareState <= ShareState::Read, "Attempting to unlock a read shared spawnable that was not in a read shared mode (%i).",
|
||||
m_owner.m_shareState.load());
|
||||
m_owner.m_shareState++;
|
||||
}
|
||||
}
|
||||
|
||||
bool Spawnable::EntityAliasConstVisitor::IsValid() const
|
||||
{
|
||||
return EntityAliasVisitorBase::IsValid(m_entityAliasList);
|
||||
}
|
||||
|
||||
bool Spawnable::EntityAliasConstVisitor::HasAliases() const
|
||||
{
|
||||
return EntityAliasVisitorBase::HasAliases(m_entityAliasList);
|
||||
}
|
||||
|
||||
bool Spawnable::EntityAliasConstVisitor::AreAllSpawnablesReady() const
|
||||
{
|
||||
return EntityAliasVisitorBase::AreAllSpawnablesReady(m_entityAliasList);
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasConstVisitor::begin() const -> EntityAliasList::const_iterator
|
||||
{
|
||||
return EntityAliasVisitorBase::begin(m_entityAliasList);
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasConstVisitor::end() const -> EntityAliasList::const_iterator
|
||||
{
|
||||
return EntityAliasVisitorBase::end(m_entityAliasList);
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasConstVisitor::cbegin() const -> EntityAliasList::const_iterator
|
||||
{
|
||||
return EntityAliasVisitorBase::cbegin(m_entityAliasList);
|
||||
}
|
||||
|
||||
auto Spawnable::EntityAliasConstVisitor::cend() const -> EntityAliasList::const_iterator
|
||||
{
|
||||
return EntityAliasVisitorBase::cend(m_entityAliasList);
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasConstVisitor::ListTargetSpawnables(const ListTargetSpawanblesCallback& callback) const
|
||||
{
|
||||
EntityAliasVisitorBase::ListTargetSpawnables(m_entityAliasList, callback);
|
||||
}
|
||||
|
||||
void Spawnable::EntityAliasConstVisitor::ListTargetSpawnables(AZ::Crc32 tag, const ListTargetSpawanblesCallback& callback) const
|
||||
{
|
||||
EntityAliasVisitorBase::ListTargetSpawnables(m_entityAliasList, tag, callback);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Spawnable
|
||||
//
|
||||
|
||||
Spawnable::Spawnable(const AZ::Data::AssetId& id, AssetStatus status)
|
||||
: AZ::Data::AssetData(id, status)
|
||||
{
|
||||
@@ -27,6 +488,33 @@ namespace AzFramework
|
||||
return m_entities;
|
||||
}
|
||||
|
||||
auto Spawnable::TryGetAliasesConst() const -> EntityAliasConstVisitor
|
||||
{
|
||||
int32_t expected = ShareState::NotShared;
|
||||
do
|
||||
{
|
||||
// Try to set the lock to a negative number to indicate a shared read.
|
||||
if (m_shareState.compare_exchange_strong(expected, expected - 1))
|
||||
{
|
||||
return EntityAliasConstVisitor(*this, &m_entityAliases);
|
||||
}
|
||||
// as long as the value is negative or not shared then keep trying to get a shared read lock.
|
||||
} while (expected <= 0);
|
||||
return EntityAliasConstVisitor(*this, nullptr);
|
||||
}
|
||||
|
||||
auto Spawnable::TryGetAliases() const -> EntityAliasConstVisitor
|
||||
{
|
||||
return TryGetAliasesConst();
|
||||
}
|
||||
|
||||
auto Spawnable::TryGetAliases() -> EntityAliasVisitor
|
||||
{
|
||||
int32_t expected = ShareState::NotShared;
|
||||
return m_shareState.compare_exchange_strong(expected, ShareState::ReadWrite) ? EntityAliasVisitor(*this, &m_entityAliases)
|
||||
: EntityAliasVisitor(*this, nullptr);
|
||||
}
|
||||
|
||||
bool Spawnable::IsEmpty() const
|
||||
{
|
||||
return m_entities.empty();
|
||||
@@ -44,11 +532,29 @@ namespace AzFramework
|
||||
|
||||
void Spawnable::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
EntityAlias::Reflect(context);
|
||||
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
|
||||
{
|
||||
serializeContext->Class<Spawnable, AZ::Data::AssetData>()->Version(1)
|
||||
serializeContext->Class<Spawnable, AZ::Data::AssetData>()->Version(2)
|
||||
->Field("Meta data", &Spawnable::m_metaData)
|
||||
->Field("Entity aliases", &Spawnable::m_entityAliases)
|
||||
->Field("Entities", &Spawnable::m_entities);
|
||||
}
|
||||
}
|
||||
|
||||
void Spawnable::EntityAlias::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
|
||||
{
|
||||
serializeContext->Class<Spawnable::EntityAlias>()
|
||||
->Version(1)
|
||||
->Field("Spawnable", &EntityAlias::m_spawnable)
|
||||
->Field("Tag", &EntityAlias::m_tag)
|
||||
->Field("Source Index", &EntityAlias::m_sourceIndex)
|
||||
->Field("Target Index", &EntityAlias::m_targetIndex)
|
||||
->Field("Alias Type", &EntityAlias::m_aliasType)
|
||||
->Field("Queue Load", &EntityAlias::m_queueLoad);
|
||||
}
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/std/parallel/atomic.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzFramework/Spawnable/SpawnableMetaData.h>
|
||||
@@ -29,7 +30,148 @@ namespace AzFramework
|
||||
AZ_CLASS_ALLOCATOR(Spawnable, AZ::SystemAllocator, 0);
|
||||
AZ_RTTI(AzFramework::Spawnable, "{855E3021-D305-4845-B284-20C3F7FDF16B}", AZ::Data::AssetData);
|
||||
|
||||
// The order is important for sorting in the SpawnableAssetHandler.
|
||||
enum class EntityAliasType : uint8_t
|
||||
{
|
||||
Original, //!< The original entity is spawned.
|
||||
Disable, //!< No entity will be spawned.
|
||||
Replace, //!< The entity alias is spawned instead of the original.
|
||||
Additional, //!< The original entity is spawned as well as the alias. The alias will get a new entity id.
|
||||
Merge //!< The original entity is spawned and the components of the alias are added. The caller is responsible for
|
||||
//!< maintaining a valid component list.
|
||||
};
|
||||
|
||||
enum ShareState : int32_t
|
||||
{
|
||||
Read = -1,
|
||||
NotShared = 0,
|
||||
ReadWrite = 1
|
||||
};
|
||||
|
||||
//! An entity alias redirects the spawning of an entity to another entity, possibly in another spawnable.
|
||||
struct EntityAlias
|
||||
{
|
||||
AZ_CLASS_ALLOCATOR(EntityAlias, AZ::SystemAllocator, 0);
|
||||
AZ_TYPE_INFO(AzFramework::Spawnable::EntityAlias, "{C8D0C5BC-1F0B-4572-98C1-73B2CA8C9356}");
|
||||
|
||||
bool HasLowerIndex(const EntityAlias& other) const;
|
||||
|
||||
AZ::Data::Asset<Spawnable> m_spawnable; //!< The spawnable containing the target entity to spawn.
|
||||
uint32_t m_tag{ 0 }; //!< A unique tag to identify this alias with.
|
||||
uint32_t m_sourceIndex{ 0 }; //!< The index of the entity in the original spawnable that will be replaced.
|
||||
uint32_t m_targetIndex{ 0 }; //!< The index of the entity in the target spawnable that will be used to replace the original.
|
||||
EntityAliasType m_aliasType{ EntityAliasType::Original }; //!< The kind of replacement.
|
||||
bool m_queueLoad{ false }; //!< Whether or not to automatically queue the spawnable for loading.
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
};
|
||||
|
||||
using EntityList = AZStd::vector<AZStd::unique_ptr<AZ::Entity>>;
|
||||
using EntityAliasList = AZStd::vector<EntityAlias>;
|
||||
|
||||
private:
|
||||
class EntityAliasVisitorBase
|
||||
{
|
||||
protected:
|
||||
bool IsValid(const EntityAliasList* aliases) const;
|
||||
|
||||
bool HasAliases(const EntityAliasList* aliases) const;
|
||||
bool AreAllSpawnablesReady(const EntityAliasList* aliases) const;
|
||||
|
||||
EntityAliasList::const_iterator begin(const EntityAliasList* aliases) const;
|
||||
EntityAliasList::const_iterator end(const EntityAliasList* aliases) const;
|
||||
EntityAliasList::const_iterator cbegin(const EntityAliasList* aliases) const;
|
||||
EntityAliasList::const_iterator cend(const EntityAliasList* aliases) const;
|
||||
|
||||
using ListTargetSpawanblesCallback = AZStd::function<void(const AZ::Data::Asset<Spawnable>& targetSpawnable)>;
|
||||
void ListTargetSpawnables(const EntityAliasList* aliases, const ListTargetSpawanblesCallback& callback) const;
|
||||
void ListTargetSpawnables(const EntityAliasList* aliases, AZ::Crc32 tag, const ListTargetSpawanblesCallback& callback) const;
|
||||
};
|
||||
|
||||
public:
|
||||
class EntityAliasVisitor final : public EntityAliasVisitorBase
|
||||
{
|
||||
public:
|
||||
EntityAliasVisitor(Spawnable& owner, EntityAliasList* m_entityAliasList);
|
||||
~EntityAliasVisitor();
|
||||
|
||||
EntityAliasVisitor(EntityAliasVisitor&& rhs);
|
||||
EntityAliasVisitor& operator=(EntityAliasVisitor&& rhs);
|
||||
|
||||
EntityAliasVisitor(const EntityAliasVisitor& rhs) = delete;
|
||||
EntityAliasVisitor& operator=(const EntityAliasVisitor& rhs) = delete;
|
||||
|
||||
//! Checks if the visitor was able to retrieve data. This needs to be checked before calling any other functions.
|
||||
bool IsValid() const;
|
||||
|
||||
bool HasAliases() const;
|
||||
bool AreAllSpawnablesReady() const;
|
||||
|
||||
// Modification of aliases is limited to specific changes that can only be done through the available modification functions.
|
||||
// For this reason access through iterators is limited to unmodifiable constant iterators.
|
||||
|
||||
EntityAliasList::const_iterator begin() const;
|
||||
EntityAliasList::const_iterator end() const;
|
||||
EntityAliasList::const_iterator cbegin() const;
|
||||
EntityAliasList::const_iterator cend() const;
|
||||
|
||||
void ListTargetSpawnables(const ListTargetSpawanblesCallback& callback) const;
|
||||
void ListTargetSpawnables(AZ::Crc32 tag, const ListTargetSpawanblesCallback& callback) const;
|
||||
|
||||
void AddAlias(
|
||||
AZ::Data::Asset<Spawnable> targetSpawnable,
|
||||
AZ::Crc32 tag,
|
||||
uint32_t sourceIndex,
|
||||
uint32_t targetIndex,
|
||||
Spawnable::EntityAliasType aliasType,
|
||||
bool queueLoad);
|
||||
|
||||
using ListSpawnablesRequiringLoadCallback = AZStd::function<void(AZ::Data::Asset<Spawnable>& spawnablePendingLoad)>;
|
||||
void ListSpawnablesRequiringLoad(const ListSpawnablesRequiringLoadCallback& callback);
|
||||
|
||||
using UpdateCallback = AZStd::function<void(
|
||||
Spawnable::EntityAliasType& aliasType,
|
||||
bool& queueLoad,
|
||||
const AZ::Data::Asset<Spawnable>& aliasedSpawnable,
|
||||
const AZ::Crc32 tag,
|
||||
const uint32_t sourceIndex,
|
||||
const uint32_t targetIndex)>;
|
||||
void UpdateAliases(const UpdateCallback& callback);
|
||||
void UpdateAliases(AZ::Crc32 tag, const UpdateCallback& callback);
|
||||
void UpdateAliasType(uint32_t index, Spawnable::EntityAliasType newType);
|
||||
|
||||
void Optimize();
|
||||
|
||||
private:
|
||||
Spawnable& m_owner;
|
||||
EntityAliasList* m_entityAliasList{ nullptr };
|
||||
bool m_dirty{ false };
|
||||
};
|
||||
|
||||
class EntityAliasConstVisitor final : public EntityAliasVisitorBase
|
||||
{
|
||||
public:
|
||||
EntityAliasConstVisitor(const Spawnable& owner, const EntityAliasList* entityAliasList);
|
||||
~EntityAliasConstVisitor();
|
||||
|
||||
//! Checks if the visitor was able to retrieve data. This needs to be checked before calling any other functions.
|
||||
bool IsValid() const;
|
||||
|
||||
bool HasAliases() const;
|
||||
bool AreAllSpawnablesReady() const;
|
||||
|
||||
EntityAliasList::const_iterator begin() const;
|
||||
EntityAliasList::const_iterator end() const;
|
||||
EntityAliasList::const_iterator cbegin() const;
|
||||
EntityAliasList::const_iterator cend() const;
|
||||
|
||||
void ListTargetSpawnables(const ListTargetSpawanblesCallback& callback) const;
|
||||
void ListTargetSpawnables(AZ::Crc32 tag, const ListTargetSpawanblesCallback& callback) const;
|
||||
|
||||
private:
|
||||
const Spawnable& m_owner;
|
||||
const EntityAliasList* m_entityAliasList;
|
||||
};
|
||||
|
||||
inline static constexpr const char* FileExtension = "spawnable";
|
||||
inline static constexpr const char* DotFileExtension = ".spawnable";
|
||||
@@ -45,6 +187,9 @@ namespace AzFramework
|
||||
|
||||
const EntityList& GetEntities() const;
|
||||
EntityList& GetEntities();
|
||||
EntityAliasConstVisitor TryGetAliasesConst() const;
|
||||
EntityAliasConstVisitor TryGetAliases() const;
|
||||
EntityAliasVisitor TryGetAliases();
|
||||
bool IsEmpty() const;
|
||||
|
||||
SpawnableMetaData& GetMetaData();
|
||||
@@ -55,11 +200,12 @@ namespace AzFramework
|
||||
private:
|
||||
SpawnableMetaData m_metaData;
|
||||
|
||||
// Aliases that optionally replace the ones stored in this spawnable.
|
||||
EntityAliasList m_entityAliases;
|
||||
// Container for keeping all entities of the prefab the Spawnable was created from.
|
||||
// Includes both direct and nested entities of the prefab.
|
||||
EntityList m_entities;
|
||||
|
||||
mutable AZStd::atomic<int32_t> m_shareState{ ShareState::NotShared };
|
||||
};
|
||||
|
||||
using SpawnableList = AZStd::vector<Spawnable>;
|
||||
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
#include <AzCore/std/parallel/mutex.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class SpawnableAssetEvents : public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
using MutexType = AZStd::recursive_mutex;
|
||||
|
||||
//! Callback to allow the entity aliases in a spawnable to adjusted based on runtime requirements.
|
||||
//! This will be called by the Asset Manager as part of the creation of the spawnable asset from loaded file data. Any work done
|
||||
//! in this callback will be counted towards the maximum amount of time allocated to asset handlers to construct their assets,
|
||||
//! it's recommended to keep work done in this callback to a minimum and prefer delaying any complex processing.
|
||||
//!
|
||||
//! ALERT: Do not start blocking asset requests in this callback.
|
||||
//! Since this is part of the Asset Manager's asset streaming, doing a blocking load in this callback will cause the job
|
||||
//! processing the spawnable asset to locked out of doing any asset streaming work. If there are more spawnables doing
|
||||
//! this than there are job threads available the engine will enter a deadlock situation as no more assets can complete
|
||||
//! loading and no job threads become free as they're all waiting for assets to complete. It is however safe to queue
|
||||
//! an asset for loading.
|
||||
virtual void OnResolveAliases(
|
||||
Spawnable::EntityAliasVisitor& aliases, const SpawnableMetaData& metadata, const Spawnable::EntityList& entities) = 0;
|
||||
};
|
||||
|
||||
using SpawnableAssetEventsBus = AZ::EBus<SpawnableAssetEvents>;
|
||||
} // namespace AzFramework
|
||||
@@ -9,8 +9,10 @@
|
||||
#include <AzCore/Casting/lossy_cast.h>
|
||||
#include <AzCore/Serialization/Utils.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzCore/std/sort.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
#include <AzFramework/Spawnable/SpawnableAssetHandler.h>
|
||||
#include <AzFramework/Spawnable/SpawnableAssetBus.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
@@ -52,6 +54,7 @@ namespace AzFramework
|
||||
AZ::ObjectStream::FilterDescriptor filter(assetLoadFilterCB);
|
||||
if (AZ::Utils::LoadObjectFromStreamInPlace(*stream, *spawnable, nullptr /*SerializeContext*/, filter))
|
||||
{
|
||||
ResolveEntityAliases(spawnable, asset, stream->GetStreamingDeadline(), stream->GetStreamingPriority(), assetLoadFilterCB);
|
||||
return AZ::Data::AssetHandler::LoadResult::LoadComplete;
|
||||
}
|
||||
else
|
||||
@@ -91,4 +94,41 @@ namespace AzFramework
|
||||
AZ::Uuid subIdHash = AZ::Uuid::CreateData(id.data(), id.size());
|
||||
return azlossy_caster(subIdHash.GetHash());
|
||||
}
|
||||
|
||||
void SpawnableAssetHandler::ResolveEntityAliases(
|
||||
Spawnable* spawnable,
|
||||
[[maybe_unused]] const AZ::Data::Asset<AZ::Data::AssetData>& asset,
|
||||
AZStd::chrono::milliseconds streamingDeadline,
|
||||
AZ::IO::IStreamerTypes::Priority streamingPriority,
|
||||
const AZ::Data::AssetFilterCB& assetLoadFilterCB)
|
||||
{
|
||||
Spawnable::EntityAliasVisitor aliases = spawnable->TryGetAliases();
|
||||
AZ_Assert(aliases.IsValid(), "Newly created Spawnable '%s' was already locked.", asset.GetHint().c_str());
|
||||
if (aliases.HasAliases())
|
||||
{
|
||||
AZ_Assert(
|
||||
AZStd::is_sorted(
|
||||
aliases.begin(), aliases.end(),
|
||||
[](const Spawnable::EntityAlias& lhs, const Spawnable::EntityAlias& rhs)
|
||||
{
|
||||
return lhs.HasLowerIndex(rhs);
|
||||
}),
|
||||
"Spawnable '%s' has an unsorted entity alias list.", asset.GetHint().c_str());
|
||||
|
||||
SpawnableAssetEventsBus::Broadcast(
|
||||
&SpawnableAssetEvents::OnResolveAliases, aliases, spawnable->GetMetaData(), spawnable->GetEntities());
|
||||
|
||||
// The aliases will only be optimized if OnResolveAliases has made any changes.
|
||||
aliases.Optimize();
|
||||
aliases.ListSpawnablesRequiringLoad(
|
||||
[&assetLoadFilterCB, streamingDeadline, streamingPriority](AZ::Data::Asset<Spawnable>& assetPendingLoad)
|
||||
{
|
||||
AZ::Data::AssetLoadParameters loadInfo;
|
||||
loadInfo.m_assetLoadFilterCB = assetLoadFilterCB;
|
||||
loadInfo.m_deadline = streamingDeadline;
|
||||
loadInfo.m_priority = streamingPriority;
|
||||
assetPendingLoad.QueueLoad(loadInfo);
|
||||
});
|
||||
}
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -50,5 +50,13 @@ namespace AzFramework
|
||||
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
|
||||
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
|
||||
const AZ::Data::AssetFilterCB& assetLoadFilterCB) override;
|
||||
|
||||
private:
|
||||
void ResolveEntityAliases(
|
||||
class Spawnable* spawnable,
|
||||
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
|
||||
AZStd::chrono::milliseconds streamingDeadline,
|
||||
AZ::IO::IStreamerTypes::Priority streamingPriority,
|
||||
const AZ::Data::AssetFilterCB& assetLoadFilterCB);
|
||||
};
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace AzFramework
|
||||
return m_threadData != nullptr;
|
||||
}
|
||||
|
||||
uint64_t SpawnableEntitiesContainer::GetCurrentGeneration() const
|
||||
uint32_t SpawnableEntitiesContainer::GetCurrentGeneration() const
|
||||
{
|
||||
return m_currentGeneration;
|
||||
}
|
||||
@@ -37,7 +37,7 @@ namespace AzFramework
|
||||
SpawnableEntitiesInterface::Get()->SpawnAllEntities(m_threadData->m_spawnedEntitiesTicket);
|
||||
}
|
||||
|
||||
void SpawnableEntitiesContainer::SpawnEntities(AZStd::vector<size_t> entityIndices)
|
||||
void SpawnableEntitiesContainer::SpawnEntities(AZStd::vector<uint32_t> entityIndices)
|
||||
{
|
||||
AZ_Assert(m_threadData, "Calling SpawnEntities on a Spawnable container that's not set.");
|
||||
SpawnableEntitiesInterface::Get()->SpawnEntities(
|
||||
@@ -78,15 +78,21 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnableEntitiesContainer::Alert(AlertCallback callback)
|
||||
void SpawnableEntitiesContainer::Alert(AlertCallback callback, CheckIfSpawnableIsLoaded spawnableCheck)
|
||||
{
|
||||
AZ_Assert(m_threadData, "Calling DespawnEntities on a Spawnable container that's not set.");
|
||||
SpawnableEntitiesInterface::Get()->Barrier(
|
||||
m_threadData->m_spawnedEntitiesTicket,
|
||||
[generation = m_threadData->m_generation, callback = AZStd::move(callback)](EntitySpawnTicket::Id)
|
||||
{
|
||||
callback(generation);
|
||||
});
|
||||
auto callbackWrapper = [generation = m_threadData->m_generation, callback = AZStd::move(callback)](EntitySpawnTicket::Id)
|
||||
{
|
||||
callback(generation);
|
||||
};
|
||||
if (spawnableCheck == CheckIfSpawnableIsLoaded::No)
|
||||
{
|
||||
SpawnableEntitiesInterface::Get()->Barrier(m_threadData->m_spawnedEntitiesTicket, AZStd::move(callbackWrapper));
|
||||
}
|
||||
else
|
||||
{
|
||||
SpawnableEntitiesInterface::Get()->LoadBarrier(m_threadData->m_spawnedEntitiesTicket, AZStd::move(callbackWrapper));
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnableEntitiesContainer::Connect(AZ::Data::Asset<Spawnable> spawnable)
|
||||
|
||||
@@ -36,6 +36,12 @@ namespace AzFramework
|
||||
public:
|
||||
using AlertCallback = AZStd::function<void(uint32_t generation)>;
|
||||
|
||||
enum class CheckIfSpawnableIsLoaded : bool
|
||||
{
|
||||
Yes,
|
||||
No
|
||||
};
|
||||
|
||||
//! Constructs a new spawnables entity container that has not been connected.
|
||||
SpawnableEntitiesContainer() = default;
|
||||
//! Constructs a new spawnables entity container that connects to the provided spawnable.
|
||||
@@ -48,13 +54,13 @@ namespace AzFramework
|
||||
//! Returns a number that identifies the current generation of the container with. The completion callback can still receive
|
||||
//! calls from older generations as processing completes on those. The returned value can be used to help calls tell
|
||||
//! older versions apart from newer ones.
|
||||
[[nodiscard]] uint64_t GetCurrentGeneration() const;
|
||||
[[nodiscard]] uint32_t GetCurrentGeneration() const;
|
||||
|
||||
//! Puts in a request to spawn entities using all entities in the provided spawnable as a template.
|
||||
void SpawnAllEntities();
|
||||
//! Puts in a request to spawn entities using the entities found in the spawnable at the provided indices as a template.
|
||||
//! @param entityIndices A list of indices to the entities in the spawnable.
|
||||
void SpawnEntities(AZStd::vector<size_t> entityIndices);
|
||||
void SpawnEntities(AZStd::vector<uint32_t> entityIndices);
|
||||
//! Puts in a request to despawn all previous spawned entities.
|
||||
void DespawnAllEntities();
|
||||
|
||||
@@ -73,7 +79,11 @@ namespace AzFramework
|
||||
//! other than the calling thread including the main thread. Note that because the alert is queued it can still be called
|
||||
//! after the container has been deleted or can be called for a previously assigned spawnable. In the latter case check
|
||||
//! if the current generation matches the generation provided with the callback.
|
||||
void Alert(AlertCallback callback);
|
||||
//! @param callback The function called when the alert triggers. This can be called from a different thread than the one that
|
||||
//! the one that made the call to Alert.
|
||||
//! @param checkSpawnableIsLoaded If true the alert will also block until the spawnable has been loaded. If false then it will
|
||||
//! be called after all previous calls have completed, but the spawnable may not be loaded at that point.
|
||||
void Alert(AlertCallback callback, CheckIfSpawnableIsLoaded spawnableCheck = CheckIfSpawnableIsLoaded::No);
|
||||
|
||||
private:
|
||||
void Connect(AZ::Data::Asset<Spawnable> spawnable);
|
||||
|
||||
@@ -152,7 +152,7 @@ namespace AzFramework
|
||||
// SpawnableIndexEntityPair
|
||||
//
|
||||
|
||||
SpawnableIndexEntityPair::SpawnableIndexEntityPair(AZ::Entity** entityIterator, size_t* indexIterator)
|
||||
SpawnableIndexEntityPair::SpawnableIndexEntityPair(AZ::Entity** entityIterator, uint32_t* indexIterator)
|
||||
: m_entity(entityIterator)
|
||||
, m_index(indexIterator)
|
||||
{
|
||||
@@ -168,7 +168,7 @@ namespace AzFramework
|
||||
return *m_entity;
|
||||
}
|
||||
|
||||
size_t SpawnableIndexEntityPair::GetIndex() const
|
||||
uint32_t SpawnableIndexEntityPair::GetIndex() const
|
||||
{
|
||||
return *m_index;
|
||||
}
|
||||
@@ -177,7 +177,7 @@ namespace AzFramework
|
||||
// SpawnableIndexEntityIterator
|
||||
//
|
||||
|
||||
SpawnableIndexEntityIterator::SpawnableIndexEntityIterator(AZ::Entity** entityIterator, size_t* indexIterator)
|
||||
SpawnableIndexEntityIterator::SpawnableIndexEntityIterator(AZ::Entity** entityIterator, uint32_t* indexIterator)
|
||||
: m_value(entityIterator, indexIterator)
|
||||
{
|
||||
}
|
||||
@@ -248,7 +248,7 @@ namespace AzFramework
|
||||
//
|
||||
|
||||
SpawnableConstIndexEntityContainerView::SpawnableConstIndexEntityContainerView(
|
||||
AZ::Entity** beginEntity, size_t* beginIndices, size_t length)
|
||||
AZ::Entity** beginEntity, uint32_t* beginIndices, size_t length)
|
||||
: m_begin(beginEntity, beginIndices)
|
||||
, m_end(beginEntity + length, beginIndices + length)
|
||||
{
|
||||
|
||||
@@ -85,19 +85,19 @@ namespace AzFramework
|
||||
|
||||
AZ::Entity* GetEntity();
|
||||
const AZ::Entity* GetEntity() const;
|
||||
size_t GetIndex() const;
|
||||
uint32_t GetIndex() const;
|
||||
|
||||
private:
|
||||
SpawnableIndexEntityPair() = default;
|
||||
SpawnableIndexEntityPair(const SpawnableIndexEntityPair&) = default;
|
||||
SpawnableIndexEntityPair(SpawnableIndexEntityPair&&) = default;
|
||||
SpawnableIndexEntityPair(AZ::Entity** entityIterator, size_t* indexIterator);
|
||||
SpawnableIndexEntityPair(AZ::Entity** entityIterator, uint32_t* indexIterator);
|
||||
|
||||
SpawnableIndexEntityPair& operator=(const SpawnableIndexEntityPair&) = default;
|
||||
SpawnableIndexEntityPair& operator=(SpawnableIndexEntityPair&&) = default;
|
||||
|
||||
AZ::Entity** m_entity { nullptr };
|
||||
size_t* m_index { nullptr };
|
||||
uint32_t* m_index { nullptr };
|
||||
};
|
||||
|
||||
class SpawnableIndexEntityIterator
|
||||
@@ -110,7 +110,7 @@ namespace AzFramework
|
||||
using pointer = SpawnableIndexEntityPair*;
|
||||
using reference = SpawnableIndexEntityPair&;
|
||||
|
||||
SpawnableIndexEntityIterator(AZ::Entity** entityIterator, size_t* indexIterator);
|
||||
SpawnableIndexEntityIterator(AZ::Entity** entityIterator, uint32_t* indexIterator);
|
||||
|
||||
SpawnableIndexEntityIterator& operator++();
|
||||
SpawnableIndexEntityIterator operator++(int);
|
||||
@@ -132,7 +132,7 @@ namespace AzFramework
|
||||
class SpawnableConstIndexEntityContainerView
|
||||
{
|
||||
public:
|
||||
SpawnableConstIndexEntityContainerView(AZ::Entity** beginEntity, size_t* beginIndices, size_t length);
|
||||
SpawnableConstIndexEntityContainerView(AZ::Entity** beginEntity, uint32_t* beginIndices, size_t length);
|
||||
|
||||
const SpawnableIndexEntityIterator& begin();
|
||||
const SpawnableIndexEntityIterator& end();
|
||||
@@ -144,6 +144,16 @@ namespace AzFramework
|
||||
SpawnableIndexEntityIterator m_end;
|
||||
};
|
||||
|
||||
//! Information used when updating the type of an entity alias.
|
||||
struct EntityAliasTypeChange
|
||||
{
|
||||
//! The index of the alias in the spawnable. Note that due to optimizations done on the entity aliases the index of an alias
|
||||
//! can change over time.
|
||||
uint32_t m_aliasIndex;
|
||||
//! The type to replace type stored in the spawnable at the index provided by m_aliasIndex.
|
||||
Spawnable::EntityAliasType m_newAliasType;
|
||||
};
|
||||
|
||||
//! Requests to the SpawnableEntitiesInterface require a ticket with a valid spawnable that is used as a template. A ticket can
|
||||
//! be reused for multiple calls on the same spawnable and is safe to be used by multiple threads at the same time. Entities created
|
||||
//! from the spawnable may be tracked by the ticket and so using the same ticket is needed to despawn the exact entities created
|
||||
@@ -178,6 +188,7 @@ namespace AzFramework
|
||||
using EntityDespawnCallback = AZStd::function<void(EntitySpawnTicket::Id)>;
|
||||
using RetrieveEntitySpawnTicketCallback = AZStd::function<void(EntitySpawnTicket*)>;
|
||||
using ReloadSpawnableCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstEntityContainerView)>;
|
||||
using UpdateEntityAliasTypesCallback = AZStd::function<void(EntitySpawnTicket::Id)>;
|
||||
using ListEntitiesCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstEntityContainerView)>;
|
||||
using ListIndicesEntitiesCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableConstIndexEntityContainerView)>;
|
||||
using ClaimEntitiesCallback = AZStd::function<void(EntitySpawnTicket::Id, SpawnableEntityContainerView)>;
|
||||
@@ -247,6 +258,15 @@ namespace AzFramework
|
||||
SpawnablePriority m_priority { SpawnablePriority_Default };
|
||||
};
|
||||
|
||||
struct UpdateEntityAliasTypesOptionalArgs final
|
||||
{
|
||||
//! Callback that's called when entity aliases are updated. This can be triggered from a different thread than the one that
|
||||
//! made the function call to update.
|
||||
UpdateEntityAliasTypesCallback m_completionCallback;
|
||||
//! The priority at which this call will be executed.
|
||||
SpawnablePriority m_priority{ SpawnablePriority_Default };
|
||||
};
|
||||
|
||||
struct ListEntitiesOptionalArgs final
|
||||
{
|
||||
//! The priority at which this call will be executed.
|
||||
@@ -265,6 +285,14 @@ namespace AzFramework
|
||||
SpawnablePriority m_priority{ SpawnablePriority_Default };
|
||||
};
|
||||
|
||||
struct LoadBarrierOptionalArgs final
|
||||
{
|
||||
//! The priority at which this call will be executed.
|
||||
SpawnablePriority m_priority{ SpawnablePriority_Default };
|
||||
//! Also checks if the spawnables referenced in the entity aliases that are marked to be loaded are loaded.
|
||||
bool m_checkAliasSpawnables{ true };
|
||||
};
|
||||
|
||||
//! Interface definition to (de)spawn entities from a spawnable into the game world.
|
||||
//!
|
||||
//! While the callbacks of the individual calls are being processed they will block processing any other request. Callbacks can be
|
||||
@@ -298,7 +326,7 @@ namespace AzFramework
|
||||
//! @param entityIndices The indices into the template entities stored in the spawnable that will be used to spawn entities from.
|
||||
//! @param optionalArgs Optional additional arguments, see SpawnEntitiesOptionalArgs.
|
||||
virtual void SpawnEntities(
|
||||
EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) = 0;
|
||||
EntitySpawnTicket& ticket, AZStd::vector<uint32_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) = 0;
|
||||
//! Removes all entities in the provided list from the environment.
|
||||
//! @param ticket The ticket previously used to spawn entities with.
|
||||
//! @param optionalArgs Optional additional arguments, see DespawnAllEntitiesOptionalArgs.
|
||||
@@ -320,6 +348,16 @@ namespace AzFramework
|
||||
virtual void ReloadSpawnable(
|
||||
EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable, ReloadSpawnableOptionalArgs optionalArgs = {}) = 0;
|
||||
|
||||
//! Allows updating the entity alias on a spawnable. This allows the spawning behavior for all entities spawned from the used
|
||||
//! spawnable to be changed and is not restricted to this ticket alone.
|
||||
//! @param ticket Holds the information for the spawnable.
|
||||
//! @param updateAliases An array of index and alias type values used to update the entity alias list.
|
||||
//! @param optionalArgs Optional additional arguments, see UpdateEntityAliasTypesOptionalArgs.
|
||||
virtual void UpdateEntityAliasTypes(
|
||||
EntitySpawnTicket& ticket,
|
||||
AZStd::vector<EntityAliasTypeChange> updatedAliases,
|
||||
UpdateEntityAliasTypesOptionalArgs optionalArgs = {}) = 0;
|
||||
|
||||
//! List all entities that are spawned using this ticket.
|
||||
//! @param ticket Only the entities associated with this ticket will be listed.
|
||||
//! @param listCallback Required callback that will be called to list the entities on.
|
||||
@@ -351,31 +389,37 @@ namespace AzFramework
|
||||
//! @param completionCallback Required callback that will be called as soon as the barrier has been reached.
|
||||
//! @param optionalArgs Optional additional arguments, see BarrierOptionalArgs.
|
||||
virtual void Barrier(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) = 0;
|
||||
//! Blocks until the spawnable is loaded and all operations made on the provided ticket before the barrier call have completed.
|
||||
//! @param ticket The ticket to monitor.
|
||||
//! @param completionCallback Required callback that will be called as soon as the barrier has been reached.
|
||||
//! @param optionalArgs Optional additional arguments, see BarrierOptionalArgs.
|
||||
virtual void LoadBarrier(
|
||||
EntitySpawnTicket& ticket, BarrierCallback completionCallback, LoadBarrierOptionalArgs optionalArgs = {}) = 0;
|
||||
|
||||
protected:
|
||||
[[nodiscard]] virtual AZStd::pair<EntitySpawnTicket::Id, void*> CreateTicket(AZ::Data::Asset<Spawnable>&& spawnable) = 0;
|
||||
virtual void DestroyTicket(void* ticket) = 0;
|
||||
|
||||
template<typename T>
|
||||
static T& GetTicketPayload(EntitySpawnTicket& ticket)
|
||||
[[nodiscard]] static T& GetTicketPayload(EntitySpawnTicket& ticket)
|
||||
{
|
||||
return *reinterpret_cast<T*>(ticket.m_payload);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static const T& GetTicketPayload(const EntitySpawnTicket& ticket)
|
||||
[[nodiscard]] static const T& GetTicketPayload(const EntitySpawnTicket& ticket)
|
||||
{
|
||||
return *reinterpret_cast<const T*>(ticket.m_payload);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static T* GetTicketPayload(EntitySpawnTicket* ticket)
|
||||
[[nodiscard]] static T* GetTicketPayload(EntitySpawnTicket* ticket)
|
||||
{
|
||||
return reinterpret_cast<T*>(ticket->m_payload);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static const T* GetTicketPayload(const EntitySpawnTicket* ticket)
|
||||
[[nodiscard]] static const T* GetTicketPayload(const EntitySpawnTicket* ticket)
|
||||
{
|
||||
return reinterpret_cast<const T*>(ticket->m_payload);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::SpawnEntities(
|
||||
EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs)
|
||||
EntitySpawnTicket& ticket, AZStd::vector<uint32_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs)
|
||||
{
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to SpawnEntities hasn't been initialized.");
|
||||
|
||||
@@ -128,6 +128,20 @@ namespace AzFramework
|
||||
QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::UpdateEntityAliasTypes(
|
||||
EntitySpawnTicket& ticket,
|
||||
AZStd::vector<EntityAliasTypeChange> updatedAliases,
|
||||
UpdateEntityAliasTypesOptionalArgs optionalArgs)
|
||||
{
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to ReloadSpawnable hasn't been initialized.");
|
||||
|
||||
UpdateEntityAliasTypesCommand queueEntry;
|
||||
queueEntry.m_entityAliases = AZStd::move(updatedAliases);
|
||||
queueEntry.m_ticketId = ticket.GetId();
|
||||
queueEntry.m_completionCallback = AZStd::move(optionalArgs.m_completionCallback);
|
||||
QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::ListEntities(
|
||||
EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs)
|
||||
{
|
||||
@@ -175,6 +189,19 @@ namespace AzFramework
|
||||
QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::LoadBarrier(
|
||||
EntitySpawnTicket& ticket, BarrierCallback completionCallback, LoadBarrierOptionalArgs optionalArgs)
|
||||
{
|
||||
AZ_Assert(completionCallback, "Load barrier on spawnable entities called without a valid callback to use.");
|
||||
AZ_Assert(ticket.IsValid(), "Ticket provided to LoadBarrier hasn't been initialized.");
|
||||
|
||||
LoadBarrierCommand queueEntry;
|
||||
queueEntry.m_ticketId = ticket.GetId();
|
||||
queueEntry.m_completionCallback = AZStd::move(completionCallback);
|
||||
queueEntry.m_checkAliasSpawnables = optionalArgs.m_checkAliasSpawnables;
|
||||
QueueRequest(ticket, optionalArgs.m_priority, AZStd::move(queueEntry));
|
||||
}
|
||||
|
||||
auto SpawnableEntitiesManager::ProcessQueue(CommandQueuePriority priority) -> CommandQueueStatus
|
||||
{
|
||||
CommandQueueStatus result = CommandQueueStatus::NoCommandsLeft;
|
||||
@@ -203,13 +230,13 @@ namespace AzFramework
|
||||
for (size_t i = 0; i < delayedSize; ++i)
|
||||
{
|
||||
Requests& request = queue.m_delayed.front();
|
||||
bool result = AZStd::visit(
|
||||
[this](auto&& args) -> bool
|
||||
CommandResult result = AZStd::visit(
|
||||
[this](auto&& args) -> CommandResult
|
||||
{
|
||||
return ProcessRequest(args);
|
||||
},
|
||||
request);
|
||||
if (!result)
|
||||
if (result == CommandResult::Requeue)
|
||||
{
|
||||
queue.m_delayed.emplace_back(AZStd::move(request));
|
||||
}
|
||||
@@ -230,13 +257,13 @@ namespace AzFramework
|
||||
while (!pendingRequestQueue.empty())
|
||||
{
|
||||
Requests& request = pendingRequestQueue.front();
|
||||
bool result = AZStd::visit(
|
||||
[this](auto&& args) -> bool
|
||||
CommandResult result = AZStd::visit(
|
||||
[this](auto&& args) -> CommandResult
|
||||
{
|
||||
return ProcessRequest(args);
|
||||
},
|
||||
request);
|
||||
if (!result)
|
||||
if (result == CommandResult::Requeue)
|
||||
{
|
||||
queue.m_delayed.emplace_back(AZStd::move(request));
|
||||
}
|
||||
@@ -273,14 +300,73 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
AZ::Entity* SpawnableEntitiesManager::CloneSingleEntity(const AZ::Entity& entityTemplate,
|
||||
EntityIdMap& templateToCloneMap, AZ::SerializeContext& serializeContext)
|
||||
AZ::Entity* SpawnableEntitiesManager::CloneSingleEntity(const AZ::Entity& entityPrototype,
|
||||
EntityIdMap& prototypeToCloneMap, AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
// If the same ID gets remapped more than once, preserve the original remapping instead of overwriting it.
|
||||
constexpr bool allowDuplicateIds = false;
|
||||
|
||||
return AZ::IdUtils::Remapper<AZ::EntityId, allowDuplicateIds>::CloneObjectAndGenerateNewIdsAndFixRefs(
|
||||
&entityTemplate, templateToCloneMap, &serializeContext);
|
||||
&entityPrototype, prototypeToCloneMap, &serializeContext);
|
||||
}
|
||||
|
||||
AZ::Entity* SpawnableEntitiesManager::CloneSingleAliasedEntity(
|
||||
const AZ::Entity& entityPrototype,
|
||||
const Spawnable::EntityAlias& alias,
|
||||
EntityIdMap& prototypeToCloneMap,
|
||||
AZ::Entity* previouslySpawnedEntity,
|
||||
AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
AZ::Entity* clone = nullptr;
|
||||
switch (alias.m_aliasType)
|
||||
{
|
||||
case Spawnable::EntityAliasType::Original:
|
||||
// Behave as the original version.
|
||||
clone = CloneSingleEntity(entityPrototype, prototypeToCloneMap, serializeContext);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
return clone;
|
||||
case Spawnable::EntityAliasType::Disable:
|
||||
// Do nothing.
|
||||
return nullptr;
|
||||
case Spawnable::EntityAliasType::Replace:
|
||||
clone = CloneSingleEntity(*(alias.m_spawnable->GetEntities()[alias.m_targetIndex]), prototypeToCloneMap, serializeContext);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
return clone;
|
||||
case Spawnable::EntityAliasType::Additional:
|
||||
// The asset handler will have sorted and inserted a Spawnable::EntityAliasType::Original, so the just
|
||||
// spawn the additional entity.
|
||||
clone = CloneSingleEntity(*(alias.m_spawnable->GetEntities()[alias.m_targetIndex]), prototypeToCloneMap, serializeContext);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
return clone;
|
||||
case Spawnable::EntityAliasType::Merge:
|
||||
AZ_Assert(previouslySpawnedEntity != nullptr, "Merging components but there's no entity to add to yet.");
|
||||
AppendComponents(
|
||||
*previouslySpawnedEntity, alias.m_spawnable->GetEntities()[alias.m_targetIndex]->GetComponents(), prototypeToCloneMap,
|
||||
serializeContext);
|
||||
return nullptr;
|
||||
default:
|
||||
AZ_Assert(false, "Unsupported spawnable entity alias type: %i", alias.m_aliasType);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::AppendComponents(
|
||||
AZ::Entity& target,
|
||||
const AZ::Entity::ComponentArrayType& componentPrototypes,
|
||||
EntityIdMap& prototypeToCloneMap,
|
||||
AZ::SerializeContext& serializeContext)
|
||||
{
|
||||
// Only components are added and entities are looked up so no duplicate entity ids should be encountered.
|
||||
constexpr bool allowDuplicateIds = false;
|
||||
|
||||
for (const AZ::Component* component : componentPrototypes)
|
||||
{
|
||||
AZ::Component* clone = AZ::IdUtils::Remapper<AZ::EntityId, allowDuplicateIds>::CloneObjectAndGenerateNewIdsAndFixRefs(
|
||||
component, prototypeToCloneMap, &serializeContext);
|
||||
AZ_Assert(clone, "Unable to clone component for entity '%s' (%zu).", target.GetName().c_str(), target.GetId());
|
||||
[[maybe_unused]] bool result = target.AddComponent(clone);
|
||||
AZ_Assert(result, "Unable to add cloned component to entity '%s' (%zu).", target.GetName().c_str(), target.GetId());
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnableEntitiesManager::InitializeEntityIdMappings(
|
||||
@@ -316,161 +402,264 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(SpawnAllEntitiesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
AZStd::vector<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
|
||||
AZStd::vector<size_t>& spawnedEntityIndices = ticket.m_spawnedEntityIndices;
|
||||
|
||||
// Keep track how many entities there were in the array initially
|
||||
size_t spawnedEntitiesInitialCount = spawnedEntities.size();
|
||||
|
||||
// These are 'template' entities we'll be cloning from
|
||||
const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities();
|
||||
size_t entitiesToSpawnSize = entitiesToSpawn.size();
|
||||
|
||||
// Reserve buffers
|
||||
spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize);
|
||||
spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize);
|
||||
|
||||
// Pre-generate the full set of entity id to new entity id mappings, so that during the clone operation below,
|
||||
// any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly.
|
||||
// We clear out and regenerate the set of IDs on every SpawnAllEntities call, because presumably every entity reference
|
||||
// in every entity we're about to instantiate is intended to point to an entity in our newly-instantiated batch, regardless
|
||||
// of spawn order. If we didn't clear out the map, it would be possible for some entities here to have references to
|
||||
// previously-spawned entities from a previous SpawnEntities or SpawnAllEntities call.
|
||||
InitializeEntityIdMappings(entitiesToSpawn, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
|
||||
for (size_t i = 0; i < entitiesToSpawnSize; ++i)
|
||||
if (Spawnable::EntityAliasConstVisitor aliases = ticket.m_spawnable->TryGetAliasesConst();
|
||||
aliases.IsValid() && aliases.AreAllSpawnablesReady())
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(entitiesToSpawn[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
AZStd::vector<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
|
||||
AZStd::vector<uint32_t>& spawnedEntityIndices = ticket.m_spawnedEntityIndices;
|
||||
|
||||
AZ::Entity* clone = CloneSingleEntity(*entitiesToSpawn[i], ticket.m_entityIdReferenceMap, *request.m_serializeContext);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
// Keep track how many entities there were in the array initially
|
||||
size_t spawnedEntitiesInitialCount = spawnedEntities.size();
|
||||
|
||||
spawnedEntities.emplace_back(clone);
|
||||
spawnedEntityIndices.push_back(i);
|
||||
}
|
||||
// These are 'prototype' entities we'll be cloning from
|
||||
const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities();
|
||||
uint32_t entitiesToSpawnSize = aznumeric_caster(entitiesToSpawn.size());
|
||||
|
||||
// loadAll is true if every entity has been spawned only once
|
||||
ticket.m_loadAll = (spawnedEntities.size() == entitiesToSpawnSize);
|
||||
|
||||
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
|
||||
if (request.m_preInsertionCallback)
|
||||
{
|
||||
request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
// Reserve buffers
|
||||
spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize);
|
||||
spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize);
|
||||
|
||||
// Add to the game context, now the entities are active
|
||||
for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it)
|
||||
{
|
||||
(*it)->SetSpawnTicketId(request.m_ticketId);
|
||||
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it);
|
||||
}
|
||||
|
||||
// Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context.
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(SpawnEntitiesCommand& request)
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
AZStd::vector<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
|
||||
AZStd::vector<size_t>& spawnedEntityIndices = ticket.m_spawnedEntityIndices;
|
||||
AZ_Assert(
|
||||
spawnedEntities.size() == spawnedEntityIndices.size(),
|
||||
"The indices for the spawned entities has gone out of sync with the entities.");
|
||||
|
||||
// Keep track of how many entities there were in the array initially
|
||||
size_t spawnedEntitiesInitialCount = spawnedEntities.size();
|
||||
|
||||
// These are 'template' entities we'll be cloning from
|
||||
const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities();
|
||||
size_t entitiesToSpawnSize = request.m_entityIndices.size();
|
||||
|
||||
if (ticket.m_entityIdReferenceMap.empty() || !request.m_referencePreviouslySpawnedEntities)
|
||||
{
|
||||
// This map keeps track of ids from template (spawnable) to clone (instance) allowing patch ups of fields referring
|
||||
// to entityIds outside of a given entity.
|
||||
// We pre-generate the full set of entity id to new entity id mappings, so that during the clone operation below,
|
||||
// Pre-generate the full set of entity-id-to-new-entity-id mappings, so that during the clone operation below,
|
||||
// any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly.
|
||||
// By default, we only initialize this map once because it needs to persist across multiple SpawnEntities calls, so
|
||||
// that reference fixups work even when the entity being referenced is spawned in a different SpawnEntities
|
||||
// (or SpawnAllEntities) call.
|
||||
// However, the caller can also choose to reset the map by passing in "m_referencePreviouslySpawnedEntities = false".
|
||||
// We clear out and regenerate the set of IDs on every SpawnAllEntities call, because presumably every entity reference
|
||||
// in every entity we're about to instantiate is intended to point to an entity in our newly-instantiated batch, regardless
|
||||
// of spawn order. If we didn't clear out the map, it would be possible for some entities here to have references to
|
||||
// previously-spawned entities from a previous SpawnEntities or SpawnAllEntities call.
|
||||
InitializeEntityIdMappings(entitiesToSpawn, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
}
|
||||
|
||||
spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize);
|
||||
spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize);
|
||||
|
||||
for (size_t index : request.m_entityIndices)
|
||||
{
|
||||
if (index < entitiesToSpawn.size())
|
||||
auto aliasIt = aliases.begin();
|
||||
auto aliasEnd = aliases.end();
|
||||
if (aliasIt == aliasEnd)
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(
|
||||
entitiesToSpawn[index].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
for (uint32_t i = 0; i < entitiesToSpawnSize; ++i)
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(
|
||||
entitiesToSpawn[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
|
||||
AZ::Entity* clone =
|
||||
CloneSingleEntity(*entitiesToSpawn[index], ticket.m_entityIdReferenceMap, *request.m_serializeContext);
|
||||
AZ_Assert(clone != nullptr, "Failed to clone spawnable entity.");
|
||||
|
||||
spawnedEntities.push_back(clone);
|
||||
spawnedEntityIndices.push_back(index);
|
||||
spawnedEntities.emplace_back(
|
||||
CloneSingleEntity(*entitiesToSpawn[i], ticket.m_entityIdReferenceMap, *request.m_serializeContext));
|
||||
spawnedEntityIndices.push_back(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
ticket.m_loadAll = false;
|
||||
else
|
||||
{
|
||||
for (uint32_t i = 0; i < entitiesToSpawnSize; ++i)
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(
|
||||
entitiesToSpawn[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
|
||||
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
|
||||
if (request.m_preInsertionCallback)
|
||||
{
|
||||
request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
if (aliasIt == aliasEnd || aliasIt->m_sourceIndex != i)
|
||||
{
|
||||
spawnedEntities.emplace_back(
|
||||
CloneSingleEntity(*entitiesToSpawn[i], ticket.m_entityIdReferenceMap, *request.m_serializeContext));
|
||||
spawnedEntityIndices.push_back(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The list of entities has already been sorted and optimized (See SpawnableEntitiesAliasList:Optimize) so can
|
||||
// be safely executed in order without risking an invalid state.
|
||||
AZ::Entity* previousEntity = nullptr;
|
||||
do
|
||||
{
|
||||
AZ::Entity* clone = CloneSingleAliasedEntity(
|
||||
*entitiesToSpawn[i], *aliasIt, ticket.m_entityIdReferenceMap, previousEntity,
|
||||
*request.m_serializeContext);
|
||||
previousEntity = clone;
|
||||
if (clone)
|
||||
{
|
||||
spawnedEntities.emplace_back(clone);
|
||||
spawnedEntityIndices.push_back(i);
|
||||
}
|
||||
++aliasIt;
|
||||
} while (aliasIt != aliasEnd && aliasIt->m_sourceIndex == i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add to the game context, now the entities are active
|
||||
for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it)
|
||||
{
|
||||
(*it)->SetSpawnTicketId(request.m_ticketId);
|
||||
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it);
|
||||
}
|
||||
// There were no initial entities then the ticket now holds exactly all entities. If there were already entities then
|
||||
// a new set are not added so it no longer holds exactly the number of entities.
|
||||
ticket.m_loadAll = spawnedEntitiesInitialCount == 0;
|
||||
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
auto newEntitiesBegin = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount;
|
||||
auto newEntitiesEnd = ticket.m_spawnedEntities.end();
|
||||
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
|
||||
if (request.m_preInsertionCallback)
|
||||
{
|
||||
request.m_preInsertionCallback(request.m_ticketId, SpawnableEntityContainerView(newEntitiesBegin, newEntitiesEnd));
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
// Add to the game context, now the entities are active
|
||||
for (auto it = newEntitiesBegin; it != newEntitiesEnd; ++it)
|
||||
{
|
||||
AZ::Entity* clone = (*it);
|
||||
// The entity component framework doesn't handle entities without TransformComponent safely.
|
||||
if (!clone->GetComponents().empty())
|
||||
{
|
||||
clone->SetSpawnTicketId(request.m_ticketId);
|
||||
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it);
|
||||
}
|
||||
}
|
||||
|
||||
// Let other systems know about newly spawned entities for any post-processing after adding to the scene/game context.
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(request.m_ticketId, SpawnableConstEntityContainerView(newEntitiesBegin, newEntitiesEnd));
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
}
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(DespawnAllEntitiesCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(SpawnEntitiesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
if (Spawnable::EntityAliasConstVisitor aliases = ticket.m_spawnable->TryGetAliasesConst();
|
||||
aliases.IsValid() && aliases.AreAllSpawnablesReady())
|
||||
{
|
||||
AZStd::vector<AZ::Entity*>& spawnedEntities = ticket.m_spawnedEntities;
|
||||
AZStd::vector<uint32_t>& spawnedEntityIndices = ticket.m_spawnedEntityIndices;
|
||||
AZ_Assert(
|
||||
spawnedEntities.size() == spawnedEntityIndices.size(),
|
||||
"The indices for the spawned entities has gone out of sync with the entities.");
|
||||
|
||||
// Keep track of how many entities there were in the array initially
|
||||
size_t spawnedEntitiesInitialCount = spawnedEntities.size();
|
||||
|
||||
// These are 'prototype' entities we'll be cloning from
|
||||
const Spawnable::EntityList& entitiesToSpawn = ticket.m_spawnable->GetEntities();
|
||||
size_t entitiesToSpawnSize = request.m_entityIndices.size();
|
||||
|
||||
if (ticket.m_entityIdReferenceMap.empty() || !request.m_referencePreviouslySpawnedEntities)
|
||||
{
|
||||
// This map keeps track of ids from prototype (spawnable) to clone (instance) allowing patch ups of fields referring
|
||||
// to entityIds outside of a given entity.
|
||||
// We pre-generate the full set of entity id to new entity id mappings, so that during the clone operation below,
|
||||
// any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly.
|
||||
// By default, we only initialize this map once because it needs to persist across multiple SpawnEntities calls, so
|
||||
// that reference fixups work even when the entity being referenced is spawned in a different SpawnEntities
|
||||
// (or SpawnAllEntities) call.
|
||||
// However, the caller can also choose to reset the map by passing in "m_referencePreviouslySpawnedEntities = false".
|
||||
InitializeEntityIdMappings(entitiesToSpawn, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
}
|
||||
|
||||
spawnedEntities.reserve(spawnedEntities.size() + entitiesToSpawnSize);
|
||||
spawnedEntityIndices.reserve(spawnedEntityIndices.size() + entitiesToSpawnSize);
|
||||
|
||||
auto aliasBegin = aliases.begin();
|
||||
auto aliasEnd = aliases.end();
|
||||
if (aliasBegin == aliasEnd)
|
||||
{
|
||||
for (uint32_t index : request.m_entityIndices)
|
||||
{
|
||||
if (index < entitiesToSpawn.size())
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(
|
||||
entitiesToSpawn[index].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
|
||||
spawnedEntities.push_back(
|
||||
CloneSingleEntity(*entitiesToSpawn[index], ticket.m_entityIdReferenceMap, *request.m_serializeContext));
|
||||
spawnedEntityIndices.push_back(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (uint32_t index : request.m_entityIndices)
|
||||
{
|
||||
if (index < entitiesToSpawn.size())
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(
|
||||
entitiesToSpawn[index].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
|
||||
auto aliasIt = AZStd::lower_bound(
|
||||
aliasBegin, aliasEnd, index,
|
||||
[](const Spawnable::EntityAlias& lhs, uint32_t rhs)
|
||||
{
|
||||
return lhs.m_sourceIndex < rhs;
|
||||
});
|
||||
|
||||
if (aliasIt == aliasEnd || aliasIt->m_sourceIndex != index)
|
||||
{
|
||||
spawnedEntities.emplace_back(
|
||||
CloneSingleEntity(*entitiesToSpawn[index], ticket.m_entityIdReferenceMap, *request.m_serializeContext));
|
||||
spawnedEntityIndices.push_back(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The list of entities has already been sorted and optimized (See SpawnableEntitiesAliasList:Optimize) so
|
||||
// can be safely executed in order without risking an invalid state.
|
||||
AZ::Entity* previousEntity = nullptr;
|
||||
do
|
||||
{
|
||||
AZ::Entity* clone = CloneSingleAliasedEntity(
|
||||
*entitiesToSpawn[index], *aliasIt, ticket.m_entityIdReferenceMap, previousEntity,
|
||||
*request.m_serializeContext);
|
||||
previousEntity = clone;
|
||||
if (clone)
|
||||
{
|
||||
spawnedEntities.emplace_back(clone);
|
||||
spawnedEntityIndices.push_back(index);
|
||||
}
|
||||
|
||||
++aliasIt;
|
||||
} while (aliasIt != aliasEnd && aliasIt->m_sourceIndex == index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ticket.m_loadAll = false;
|
||||
|
||||
// Let other systems know about newly spawned entities for any pre-processing before adding to the scene/game context.
|
||||
if (request.m_preInsertionCallback)
|
||||
{
|
||||
request.m_preInsertionCallback(
|
||||
request.m_ticketId,
|
||||
SpawnableEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
// Add to the game context, now the entities are active
|
||||
for (auto it = ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount; it != ticket.m_spawnedEntities.end(); ++it)
|
||||
{
|
||||
AZ::Entity* clone = (*it);
|
||||
// The entity component framework doesn't handle entities without TransformComponent safely.
|
||||
if (!clone->GetComponents().empty())
|
||||
{
|
||||
clone->SetSpawnTicketId(request.m_ticketId);
|
||||
GameEntityContextRequestBus::Broadcast(&GameEntityContextRequestBus::Events::AddGameEntity, *it);
|
||||
}
|
||||
}
|
||||
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(
|
||||
request.m_ticketId,
|
||||
SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin() + spawnedEntitiesInitialCount, ticket.m_spawnedEntities.end()));
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
}
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
|
||||
auto SpawnableEntitiesManager::ProcessRequest(DespawnAllEntitiesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
@@ -495,15 +684,15 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(DespawnEntityCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(DespawnEntityCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
@@ -529,15 +718,15 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ReloadSpawnableCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(ReloadSpawnableCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
AZ_Assert(ticket.m_spawnable.GetId() == request.m_spawnable.GetId(),
|
||||
@@ -564,7 +753,7 @@ namespace AzFramework
|
||||
// Pre-generate the full set of entity id to new entity id mappings, so that during the clone operation below,
|
||||
// any entity references that point to a not-yet-cloned entity will still get their ids remapped correctly.
|
||||
// This map is intentionally cleared out and regenerated here to ensure that we're starting fresh with mappings that
|
||||
// match the new set of template entities getting spawned.
|
||||
// match the new set of prototype entities getting spawned.
|
||||
InitializeEntityIdMappings(entities, ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
|
||||
if (ticket.m_loadAll)
|
||||
@@ -574,7 +763,7 @@ namespace AzFramework
|
||||
ticket.m_spawnedEntityIndices.clear();
|
||||
size_t entitiesToSpawnSize = entities.size();
|
||||
|
||||
for (size_t i = 0; i < entitiesToSpawnSize; ++i)
|
||||
for (uint32_t i = 0; i < entitiesToSpawnSize; ++i)
|
||||
{
|
||||
// If this entity has previously been spawned, give it a new id in the reference map
|
||||
RefreshEntityIdMapping(entities[i].get()->GetId(), ticket.m_entityIdReferenceMap, ticket.m_previouslySpawned);
|
||||
@@ -590,7 +779,7 @@ namespace AzFramework
|
||||
{
|
||||
size_t entitiesSize = entities.size();
|
||||
|
||||
for (size_t index : ticket.m_spawnedEntityIndices)
|
||||
for (uint32_t index : ticket.m_spawnedEntityIndices)
|
||||
{
|
||||
// It's possible for the new spawnable to have a different number of entities, so guard against this.
|
||||
// It's also possible that the entities have moved within the spawnable to a new index. This can't be
|
||||
@@ -616,15 +805,40 @@ namespace AzFramework
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ListEntitiesCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(UpdateEntityAliasTypesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
if (Spawnable::EntityAliasVisitor aliases = ticket.m_spawnable->TryGetAliases(); aliases.IsValid())
|
||||
{
|
||||
for (EntityAliasTypeChange& replacement : request.m_entityAliases)
|
||||
{
|
||||
aliases.UpdateAliasType(replacement.m_aliasIndex, replacement.m_newAliasType);
|
||||
}
|
||||
aliases.Optimize();
|
||||
|
||||
if (request.m_completionCallback)
|
||||
{
|
||||
request.m_completionCallback(request.m_ticketId);
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
}
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
|
||||
auto SpawnableEntitiesManager::ProcessRequest(ListEntitiesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
@@ -632,15 +846,15 @@ namespace AzFramework
|
||||
request.m_listCallback(request.m_ticketId, SpawnableConstEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntities.end()));
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ListIndicesEntitiesCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(ListIndicesEntitiesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
@@ -651,15 +865,15 @@ namespace AzFramework
|
||||
request.m_listCallback(request.m_ticketId, SpawnableConstIndexEntityContainerView(
|
||||
ticket.m_spawnedEntities.begin(), ticket.m_spawnedEntityIndices.begin(), ticket.m_spawnedEntities.size()));
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(ClaimEntitiesCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(ClaimEntitiesCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
@@ -671,15 +885,15 @@ namespace AzFramework
|
||||
ticket.m_spawnedEntityIndices.clear();
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(BarrierCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (request.m_requestId == ticket.m_currentRequestId)
|
||||
@@ -690,15 +904,39 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
ticket.m_currentRequestId++;
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
bool SpawnableEntitiesManager::ProcessRequest(DestroyTicketCommand& request)
|
||||
auto SpawnableEntitiesManager::ProcessRequest(LoadBarrierCommand& request) -> CommandResult
|
||||
{
|
||||
Ticket& ticket = *request.m_ticket;
|
||||
if (ticket.m_spawnable.IsReady() && request.m_requestId == ticket.m_currentRequestId)
|
||||
{
|
||||
if (request.m_checkAliasSpawnables)
|
||||
{
|
||||
if (Spawnable::EntityAliasConstVisitor visitor = ticket.m_spawnable->TryGetAliasesConst();
|
||||
!visitor.IsValid() || !visitor.AreAllSpawnablesReady())
|
||||
{
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
request.m_completionCallback(request.m_ticketId);
|
||||
ticket.m_currentRequestId++;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
|
||||
auto SpawnableEntitiesManager::ProcessRequest(DestroyTicketCommand& request) -> CommandResult
|
||||
{
|
||||
if (request.m_requestId == request.m_ticket->m_currentRequestId)
|
||||
{
|
||||
@@ -706,19 +944,24 @@ namespace AzFramework
|
||||
{
|
||||
if (entity != nullptr)
|
||||
{
|
||||
// Setting it to 0 is needed to avoid the infite loop between GameEntityContext and SpawnableEntitiesManager.
|
||||
// Setting it to 0 is needed to avoid the infinite loop between GameEntityContext and SpawnableEntitiesManager.
|
||||
entity->SetSpawnTicketId(0);
|
||||
GameEntityContextRequestBus::Broadcast(
|
||||
&GameEntityContextRequestBus::Events::DestroyGameEntity, entity->GetId());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Entities without components wouldn't have been send to the GameEntityContext.
|
||||
delete entity;
|
||||
}
|
||||
}
|
||||
delete request.m_ticket;
|
||||
|
||||
return true;
|
||||
return CommandResult::Executed;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
return CommandResult::Requeue;
|
||||
}
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -55,13 +55,18 @@ namespace AzFramework
|
||||
|
||||
void SpawnAllEntities(EntitySpawnTicket& ticket, SpawnAllEntitiesOptionalArgs optionalArgs = {}) override;
|
||||
void SpawnEntities(
|
||||
EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) override;
|
||||
EntitySpawnTicket& ticket, AZStd::vector<uint32_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs = {}) override;
|
||||
void DespawnAllEntities(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs = {}) override;
|
||||
void DespawnEntity(AZ::EntityId entityId, EntitySpawnTicket& ticket, DespawnEntityOptionalArgs optionalArgs = {}) override;
|
||||
void RetrieveEntitySpawnTicket(EntitySpawnTicket::Id entitySpawnTicketId, RetrieveEntitySpawnTicketCallback callback) override;
|
||||
void ReloadSpawnable(
|
||||
EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable, ReloadSpawnableOptionalArgs optionalArgs = {}) override;
|
||||
|
||||
void UpdateEntityAliasTypes(
|
||||
EntitySpawnTicket& ticket,
|
||||
AZStd::vector<EntityAliasTypeChange> updatedAliases,
|
||||
UpdateEntityAliasTypesOptionalArgs optionalArgs = {}) override;
|
||||
|
||||
void ListEntities(
|
||||
EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs = {}) override;
|
||||
void ListIndicesAndEntities(
|
||||
@@ -70,6 +75,8 @@ namespace AzFramework
|
||||
EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs = {}) override;
|
||||
|
||||
void Barrier(EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs = {}) override;
|
||||
void LoadBarrier(
|
||||
EntitySpawnTicket& spawnInfo, BarrierCallback completionCallback, LoadBarrierOptionalArgs optionalArgs = {}) override;
|
||||
|
||||
//
|
||||
// The following function is thread safe but intended to be run from the main thread.
|
||||
@@ -78,14 +85,20 @@ namespace AzFramework
|
||||
CommandQueueStatus ProcessQueue(CommandQueuePriority priority);
|
||||
|
||||
protected:
|
||||
struct Ticket
|
||||
enum class CommandResult : bool
|
||||
{
|
||||
Executed,
|
||||
Requeue
|
||||
};
|
||||
|
||||
struct Ticket final
|
||||
{
|
||||
AZ_CLASS_ALLOCATOR(Ticket, AZ::ThreadPoolAllocator, 0);
|
||||
static constexpr uint32_t Processing = AZStd::numeric_limits<uint32_t>::max();
|
||||
|
||||
//! Map of template entity ids to their associated instance ids.
|
||||
//! Tickets can be used to spawn the same template entities multiple times, in any order, across multiple calls.
|
||||
//! Since template entities can reference other entities, this map is used to fix up those references across calls
|
||||
//! Map of prototype entity ids to their associated instance ids.
|
||||
//! Tickets can be used to spawn the same prototype entities multiple times, in any order, across multiple calls.
|
||||
//! Since prototype entities can reference other entities, this map is used to fix up those references across calls
|
||||
//! using the following policy:
|
||||
//! - Entities referencing an entity that hasn't been spawned yet will get a reference to the id that *will* be used
|
||||
//! the first time that entity will be spawned. The reference will be invalid until that entity is spawned, but
|
||||
@@ -100,14 +113,14 @@ namespace AzFramework
|
||||
AZStd::unordered_set<AZ::EntityId> m_previouslySpawned;
|
||||
|
||||
AZStd::vector<AZ::Entity*> m_spawnedEntities;
|
||||
AZStd::vector<size_t> m_spawnedEntityIndices;
|
||||
AZStd::vector<uint32_t> m_spawnedEntityIndices;
|
||||
AZ::Data::Asset<Spawnable> m_spawnable;
|
||||
uint32_t m_nextRequestId{ 0 }; //!< Next id for this ticket.
|
||||
uint32_t m_currentRequestId { 0 }; //!< The id for the command that should be executed.
|
||||
bool m_loadAll{ true };
|
||||
};
|
||||
|
||||
struct SpawnAllEntitiesCommand
|
||||
struct SpawnAllEntitiesCommand final
|
||||
{
|
||||
EntitySpawnCallback m_completionCallback;
|
||||
EntityPreInsertionCallback m_preInsertionCallback;
|
||||
@@ -116,9 +129,9 @@ namespace AzFramework
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct SpawnEntitiesCommand
|
||||
struct SpawnEntitiesCommand final
|
||||
{
|
||||
AZStd::vector<size_t> m_entityIndices;
|
||||
AZStd::vector<uint32_t> m_entityIndices;
|
||||
EntitySpawnCallback m_completionCallback;
|
||||
EntityPreInsertionCallback m_preInsertionCallback;
|
||||
AZ::SerializeContext* m_serializeContext;
|
||||
@@ -127,7 +140,7 @@ namespace AzFramework
|
||||
uint32_t m_requestId;
|
||||
bool m_referencePreviouslySpawnedEntities;
|
||||
};
|
||||
struct DespawnAllEntitiesCommand
|
||||
struct DespawnAllEntitiesCommand final
|
||||
{
|
||||
EntityDespawnCallback m_completionCallback;
|
||||
Ticket* m_ticket;
|
||||
@@ -142,7 +155,7 @@ namespace AzFramework
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ReloadSpawnableCommand
|
||||
struct ReloadSpawnableCommand final
|
||||
{
|
||||
AZ::Data::Asset<Spawnable> m_spawnable;
|
||||
ReloadSpawnableCallback m_completionCallback;
|
||||
@@ -151,35 +164,51 @@ namespace AzFramework
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ListEntitiesCommand
|
||||
struct UpdateEntityAliasTypesCommand final
|
||||
{
|
||||
AZStd::vector<EntityAliasTypeChange> m_entityAliases;
|
||||
UpdateEntityAliasTypesCallback m_completionCallback;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ListEntitiesCommand final
|
||||
{
|
||||
ListEntitiesCallback m_listCallback;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ListIndicesEntitiesCommand
|
||||
struct ListIndicesEntitiesCommand final
|
||||
{
|
||||
ListIndicesEntitiesCallback m_listCallback;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct ClaimEntitiesCommand
|
||||
struct ClaimEntitiesCommand final
|
||||
{
|
||||
ClaimEntitiesCallback m_listCallback;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct BarrierCommand
|
||||
struct BarrierCommand final
|
||||
{
|
||||
BarrierCallback m_completionCallback;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
};
|
||||
struct DestroyTicketCommand
|
||||
struct LoadBarrierCommand final
|
||||
{
|
||||
BarrierCallback m_completionCallback;
|
||||
Ticket* m_ticket;
|
||||
EntitySpawnTicket::Id m_ticketId;
|
||||
uint32_t m_requestId;
|
||||
bool m_checkAliasSpawnables;
|
||||
};
|
||||
struct DestroyTicketCommand final
|
||||
{
|
||||
Ticket* m_ticket;
|
||||
uint32_t m_requestId;
|
||||
@@ -191,10 +220,12 @@ namespace AzFramework
|
||||
DespawnAllEntitiesCommand,
|
||||
DespawnEntityCommand,
|
||||
ReloadSpawnableCommand,
|
||||
UpdateEntityAliasTypesCommand,
|
||||
ListEntitiesCommand,
|
||||
ListIndicesEntitiesCommand,
|
||||
ClaimEntitiesCommand,
|
||||
BarrierCommand,
|
||||
LoadBarrierCommand,
|
||||
DestroyTicketCommand>;
|
||||
|
||||
struct Queue
|
||||
@@ -212,18 +243,31 @@ namespace AzFramework
|
||||
CommandQueueStatus ProcessQueue(Queue& queue);
|
||||
|
||||
AZ::Entity* CloneSingleEntity(
|
||||
const AZ::Entity& entityTemplate, EntityIdMap& templateToCloneMap, AZ::SerializeContext& serializeContext);
|
||||
const AZ::Entity& entityPrototype, EntityIdMap& prototypeToCloneMap, AZ::SerializeContext& serializeContext);
|
||||
AZ::Entity* CloneSingleAliasedEntity(
|
||||
const AZ::Entity& entityPrototype,
|
||||
const Spawnable::EntityAlias& alias,
|
||||
EntityIdMap& prototypeToCloneMap,
|
||||
AZ::Entity* previouslySpawnedEntity,
|
||||
AZ::SerializeContext& serializeContext);
|
||||
void AppendComponents(
|
||||
AZ::Entity& target,
|
||||
const AZ::Entity::ComponentArrayType& componentPrototypes,
|
||||
EntityIdMap& prototypeToCloneMap,
|
||||
AZ::SerializeContext& serializeContext);
|
||||
|
||||
bool ProcessRequest(SpawnAllEntitiesCommand& request);
|
||||
bool ProcessRequest(SpawnEntitiesCommand& request);
|
||||
bool ProcessRequest(DespawnAllEntitiesCommand& request);
|
||||
bool ProcessRequest(DespawnEntityCommand& request);
|
||||
bool ProcessRequest(ReloadSpawnableCommand& request);
|
||||
bool ProcessRequest(ListEntitiesCommand& request);
|
||||
bool ProcessRequest(ListIndicesEntitiesCommand& request);
|
||||
bool ProcessRequest(ClaimEntitiesCommand& request);
|
||||
bool ProcessRequest(BarrierCommand& request);
|
||||
bool ProcessRequest(DestroyTicketCommand& request);
|
||||
CommandResult ProcessRequest(SpawnAllEntitiesCommand& request);
|
||||
CommandResult ProcessRequest(SpawnEntitiesCommand& request);
|
||||
CommandResult ProcessRequest(DespawnAllEntitiesCommand& request);
|
||||
CommandResult ProcessRequest(DespawnEntityCommand& request);
|
||||
CommandResult ProcessRequest(ReloadSpawnableCommand& request);
|
||||
CommandResult ProcessRequest(UpdateEntityAliasTypesCommand& request);
|
||||
CommandResult ProcessRequest(ListEntitiesCommand& request);
|
||||
CommandResult ProcessRequest(ListIndicesEntitiesCommand& request);
|
||||
CommandResult ProcessRequest(ClaimEntitiesCommand& request);
|
||||
CommandResult ProcessRequest(BarrierCommand& request);
|
||||
CommandResult ProcessRequest(LoadBarrierCommand& request);
|
||||
CommandResult ProcessRequest(DestroyTicketCommand& request);
|
||||
|
||||
//! Generate a base set of original-to-new entity ID mappings to use during spawning.
|
||||
//! Since Entity references get fixed up on an entity-by-entity basis while spawning, it's important to have the complete
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace AzFramework
|
||||
|
||||
uint64_t SpawnableSystemComponent::AssignRootSpawnable(AZ::Data::Asset<Spawnable> rootSpawnable)
|
||||
{
|
||||
uint64_t generation = 0;
|
||||
uint32_t generation = 0;
|
||||
|
||||
if (m_rootSpawnableId == rootSpawnable.GetId())
|
||||
{
|
||||
@@ -87,16 +87,25 @@ namespace AzFramework
|
||||
// Suspend and resume processing in the container that completion calls aren't received until
|
||||
// everything has been setup to accept callbacks from the call.
|
||||
m_rootSpawnableContainer.Reset(rootSpawnable);
|
||||
m_rootSpawnableContainer.SpawnAllEntities();
|
||||
generation = m_rootSpawnableContainer.GetCurrentGeneration();
|
||||
AZ_TracePrintf("Spawnables", "Root spawnable set to '%s' at generation %zu.\n", rootSpawnable.GetHint().c_str(),
|
||||
generation);
|
||||
|
||||
// Don't send out the alert that the root spawnable has been assigned until the spawnable itself is ready. The common
|
||||
// use case is for handlers to do something with the information in the spawnable before the entities get spawned.
|
||||
m_rootSpawnableContainer.Alert(
|
||||
[rootSpawnable](uint32_t generation)
|
||||
{
|
||||
RootSpawnableNotificationBus::Broadcast(
|
||||
&RootSpawnableNotificationBus::Events::OnRootSpawnableAssigned, AZStd::move(rootSpawnable), generation);
|
||||
}, SpawnableEntitiesContainer::CheckIfSpawnableIsLoaded::Yes);
|
||||
m_rootSpawnableContainer.SpawnAllEntities();
|
||||
m_rootSpawnableContainer.Alert(
|
||||
[newSpawnable = AZStd::move(rootSpawnable)](uint32_t generation)
|
||||
{
|
||||
RootSpawnableNotificationBus::QueueBroadcast(
|
||||
&RootSpawnableNotificationBus::Events::OnRootSpawnableAssigned, newSpawnable, generation);
|
||||
&RootSpawnableNotificationBus::Events::OnRootSpawnableReady, AZStd::move(newSpawnable), generation);
|
||||
});
|
||||
|
||||
AZ_TracePrintf("Spawnables", "Root spawnable set to '%s' at generation %zu.\n", rootSpawnable.GetHint().c_str(), generation);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -132,6 +141,12 @@ namespace AzFramework
|
||||
AZ_TracePrintf("Spawnables", "New root spawnable '%s' assigned (generation: %i).\n", rootSpawnable.GetHint().c_str(), generation);
|
||||
}
|
||||
|
||||
void SpawnableSystemComponent::OnRootSpawnableReady(
|
||||
[[maybe_unused]] AZ::Data::Asset<Spawnable> rootSpawnable, [[maybe_unused]] uint32_t generation)
|
||||
{
|
||||
AZ_TracePrintf("Spawnables", "Entities from new root spawnable '%s' are ready (generation: %i).\n", rootSpawnable.GetHint().c_str(), generation);
|
||||
}
|
||||
|
||||
void SpawnableSystemComponent::OnRootSpawnableReleased([[maybe_unused]] uint32_t generation)
|
||||
{
|
||||
AZ_TracePrintf("Spawnables", "Generation %i of the root spawnable has been released.\n", generation);
|
||||
|
||||
@@ -82,6 +82,7 @@ namespace AzFramework
|
||||
//
|
||||
|
||||
void OnRootSpawnableAssigned(AZ::Data::Asset<Spawnable> rootSpawnable, uint32_t generation) override;
|
||||
void OnRootSpawnableReady(AZ::Data::Asset<Spawnable> rootSpawnable, uint32_t generation) override;
|
||||
void OnRootSpawnableReleased(uint32_t generation) override;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -806,12 +806,20 @@ namespace AzFramework
|
||||
[[maybe_unused]] float scrollDelta,
|
||||
[[maybe_unused]] float deltaTime)
|
||||
{
|
||||
const auto pivot = m_pivotFn();
|
||||
|
||||
if (!pivot.has_value())
|
||||
{
|
||||
EndActivation();
|
||||
return targetCamera;
|
||||
}
|
||||
|
||||
if (Beginning())
|
||||
{
|
||||
// as the camera starts, record the camera we would like to end up as
|
||||
m_nextCamera.m_offset = m_offsetFn(m_pivotFn().GetDistance(targetCamera.Translation()));
|
||||
m_nextCamera.m_offset = m_offsetFn(pivot.value().GetDistance(targetCamera.Translation()));
|
||||
const auto angles =
|
||||
EulerAngles(AZ::Matrix3x3::CreateFromMatrix3x4(AZ::Matrix3x4::CreateLookAt(targetCamera.Translation(), m_pivotFn())));
|
||||
EulerAngles(AZ::Matrix3x3::CreateFromMatrix3x4(AZ::Matrix3x4::CreateLookAt(targetCamera.Translation(), pivot.value())));
|
||||
m_nextCamera.m_pitch = angles.GetX();
|
||||
m_nextCamera.m_yaw = angles.GetZ();
|
||||
m_nextCamera.m_pivot = targetCamera.m_pivot;
|
||||
|
||||
@@ -651,7 +651,7 @@ namespace AzFramework
|
||||
class FocusCameraInput : public CameraInput
|
||||
{
|
||||
public:
|
||||
using PivotFn = AZStd::function<AZ::Vector3()>;
|
||||
using PivotFn = AZStd::function<AZStd::optional<AZ::Vector3>()>;
|
||||
|
||||
FocusCameraInput(const InputChannelId& focusChannelId, FocusOffsetFn offsetFn);
|
||||
|
||||
|
||||
@@ -286,6 +286,7 @@ set(FILES
|
||||
Spawnable/RootSpawnableInterface.h
|
||||
Spawnable/Spawnable.cpp
|
||||
Spawnable/Spawnable.h
|
||||
Spawnable/SpawnableAssetBus.h
|
||||
Spawnable/SpawnableAssetHandler.h
|
||||
Spawnable/SpawnableAssetHandler.cpp
|
||||
Spawnable/SpawnableEntitiesContainer.h
|
||||
|
||||
+3
-2
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <AzCore/Module/DynamicModuleHandle.h>
|
||||
#include <AzCore/PlatformIncl.h>
|
||||
#include <AzCore/std/containers/array.h>
|
||||
#include <AzCore/std/string/conversions.h>
|
||||
|
||||
namespace AzFramework
|
||||
@@ -234,14 +235,14 @@ namespace AzFramework
|
||||
const UINT rawInputHeaderSize = sizeof(RAWINPUTHEADER);
|
||||
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &rawInputSize, rawInputHeaderSize);
|
||||
|
||||
LPBYTE rawInputBytes = new BYTE[rawInputSize];
|
||||
AZStd::array<BYTE, sizeof(RAWINPUT)> rawInputBytesArray;
|
||||
LPBYTE rawInputBytes = rawInputBytesArray.data();
|
||||
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize);
|
||||
|
||||
RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes;
|
||||
AzFramework::RawInputNotificationBusWindows::Broadcast(
|
||||
&AzFramework::RawInputNotificationBusWindows::Events::OnRawInputEvent, *rawInput);
|
||||
|
||||
delete [] rawInputBytes;
|
||||
break;
|
||||
}
|
||||
case WM_CHAR:
|
||||
|
||||
@@ -48,6 +48,24 @@ namespace InputUnitTests
|
||||
AZStd::unique_ptr<InputSystemComponent> m_inputSystemComponent;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
TEST_F(InputTest, InputChannelId_ConstExpression_CopyConstructorSuccessfull)
|
||||
{
|
||||
constexpr InputChannelId testInputChannelId1("TestInputChannelId");
|
||||
constexpr InputChannelId testInputChannelId2(testInputChannelId1);
|
||||
static_assert(testInputChannelId1 == testInputChannelId2);
|
||||
EXPECT_EQ(testInputChannelId1, testInputChannelId2);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
TEST_F(InputTest, InputDeviceId_ConstExpression_CopyConstructorSuccessfull)
|
||||
{
|
||||
constexpr InputDeviceId testInputDeviceId1("TestInputDeviceId");
|
||||
constexpr InputDeviceId testInputDeviceId2(testInputDeviceId1);
|
||||
static_assert(testInputDeviceId1 == testInputDeviceId2);
|
||||
EXPECT_EQ(testInputDeviceId1, testInputDeviceId2);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
TEST_F(InputTest, InputContext_InitWithDataStruct_InitializationSuccessfull)
|
||||
{
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace AzFramework
|
||||
|
||||
MOCK_METHOD3(
|
||||
SpawnEntities,
|
||||
void(EntitySpawnTicket& ticket, AZStd::vector<size_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs));
|
||||
void(EntitySpawnTicket& ticket, AZStd::vector<uint32_t> entityIndices, SpawnEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD2(DespawnAllEntities, void(EntitySpawnTicket& ticket, DespawnAllEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
@@ -49,6 +49,13 @@ namespace AzFramework
|
||||
ReloadSpawnable,
|
||||
void(EntitySpawnTicket& ticket, AZ::Data::Asset<Spawnable> spawnable, ReloadSpawnableOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD3(
|
||||
UpdateEntityAliasTypes,
|
||||
void(
|
||||
EntitySpawnTicket& ticket,
|
||||
AZStd::vector<EntityAliasTypeChange> updatedAliases,
|
||||
UpdateEntityAliasTypesOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD3(
|
||||
ListEntities, void(EntitySpawnTicket& ticket, ListEntitiesCallback listCallback, ListEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
@@ -61,6 +68,7 @@ namespace AzFramework
|
||||
void(EntitySpawnTicket& ticket, ClaimEntitiesCallback listCallback, ClaimEntitiesOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD3(Barrier, void(EntitySpawnTicket& ticket, BarrierCallback completionCallback, BarrierOptionalArgs optionalArgs));
|
||||
MOCK_METHOD3(LoadBarrier, void(EntitySpawnTicket& ticket, BarrierCallback completionCallback, LoadBarrierOptionalArgs optionalArgs));
|
||||
|
||||
MOCK_METHOD1(CreateTicket, AZStd::pair<EntitySpawnTicket::Id, void*>(AZ::Data::Asset<Spawnable>&& spawnable));
|
||||
MOCK_METHOD1(DestroyTicket, void(void* ticket));
|
||||
|
||||
@@ -55,6 +55,40 @@ namespace UnitTest
|
||||
AZ::EntityId m_entityReference;
|
||||
};
|
||||
|
||||
class SourceSpawnableComponent : public AZ::Component
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(SourceSpawnableComponent, "{47FF79CE-A95B-420E-8BEB-F1CC58087B87}");
|
||||
|
||||
void Activate() override {}
|
||||
void Deactivate() override {}
|
||||
|
||||
static void Reflect(AZ::ReflectContext* reflection)
|
||||
{
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
|
||||
{
|
||||
serializeContext->Class<SourceSpawnableComponent, AZ::Component>();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class TargetSpawnableComponent : public AZ::Component
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(TargetSpawnableComponent, "{B4041561-63A7-4E1E-80F1-78C08D497960}");
|
||||
|
||||
void Activate() override {}
|
||||
void Deactivate() override {}
|
||||
|
||||
static void Reflect(AZ::ReflectContext* reflection)
|
||||
{
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection))
|
||||
{
|
||||
serializeContext->Class<TargetSpawnableComponent, AZ::Component>();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SpawnableEntitiesManagerTest : public AllocatorsFixture
|
||||
{
|
||||
public:
|
||||
@@ -66,6 +100,8 @@ namespace UnitTest
|
||||
AZ::ComponentApplication::Descriptor descriptor;
|
||||
m_application->Start(descriptor);
|
||||
m_application->RegisterComponentDescriptor(ComponentWithEntityReference::CreateDescriptor());
|
||||
m_application->RegisterComponentDescriptor(SourceSpawnableComponent::CreateDescriptor());
|
||||
m_application->RegisterComponentDescriptor(TargetSpawnableComponent::CreateDescriptor());
|
||||
|
||||
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
|
||||
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
|
||||
@@ -109,10 +145,126 @@ namespace UnitTest
|
||||
entities.reserve(numElements);
|
||||
for (size_t i=0; i<numElements; ++i)
|
||||
{
|
||||
entities.push_back(AZStd::make_unique<AZ::Entity>());
|
||||
auto entry = AZStd::make_unique<AZ::Entity>();
|
||||
entry->AddComponent(aznew SourceSpawnableComponent());
|
||||
entities.push_back(AZStd::move(entry));
|
||||
}
|
||||
}
|
||||
|
||||
AZ::Data::Asset<AzFramework::Spawnable> CreateTargetSpawnable(size_t numElements)
|
||||
{
|
||||
auto target = aznew AzFramework::Spawnable(
|
||||
AZ::Data::AssetId(AZ::Uuid("{716CD8C3-0BA8-4F32-B579-0EC7C967796F}")), AZ::Data::AssetData::AssetStatus::Ready);
|
||||
|
||||
AzFramework::Spawnable::EntityList& entities = target->GetEntities();
|
||||
entities.reserve(numElements);
|
||||
for (size_t i = 0; i < numElements; ++i)
|
||||
{
|
||||
auto entry = AZStd::make_unique<AZ::Entity>();
|
||||
entry->AddComponent(aznew TargetSpawnableComponent());
|
||||
entities.push_back(AZStd::move(entry));
|
||||
}
|
||||
|
||||
return AZ::Data::Asset<AzFramework::Spawnable>(target, AZ::Data::AssetLoadBehavior::NoLoad);
|
||||
}
|
||||
|
||||
template<size_t AliasCount>
|
||||
void InsertEntityAliases(
|
||||
const AZStd::array<uint32_t, AliasCount>& sourceIds,
|
||||
const AZStd::array<uint32_t, AliasCount>& targetIds,
|
||||
const AZStd::array<AzFramework::Spawnable::EntityAliasType, AliasCount>& aliasTypes,
|
||||
AZ::Data::Asset<AzFramework::Spawnable>* target = nullptr)
|
||||
{
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
|
||||
for (uint32_t i = 0; i < AliasCount; ++i)
|
||||
{
|
||||
if (target)
|
||||
{
|
||||
visitor.AddAlias(*target, AZ::Crc32(i), sourceIds[i], targetIds[i], aliasTypes[i], false);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ::Data::Asset<AzFramework::Spawnable> spawnable(
|
||||
AZ::Data::AssetId(AZ::Uuid("{4CBEC17A-52D6-42D5-9037-F4C05B9CE1D9}"), i), azrtti_typeid<AzFramework::Spawnable>());
|
||||
visitor.AddAlias(AZStd::move(spawnable), AZ::Crc32(i), sourceIds[i], targetIds[i], aliasTypes[i], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool AreAllEntitiesReplaced(AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
for (const AZ::Entity* entity : entities)
|
||||
{
|
||||
if (entity)
|
||||
{
|
||||
if (entity->FindComponent<SourceSpawnableComponent>() != nullptr ||
|
||||
entity->FindComponent<TargetSpawnableComponent>() == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsEveryOtherEntityAReplacement(AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
bool onAlternative = true;
|
||||
for (const AZ::Entity* entity : entities)
|
||||
{
|
||||
if (entity)
|
||||
{
|
||||
if (onAlternative)
|
||||
{
|
||||
if (entity->FindComponent<SourceSpawnableComponent>() == nullptr ||
|
||||
entity->FindComponent<TargetSpawnableComponent>() != nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (entity->FindComponent<SourceSpawnableComponent>() != nullptr ||
|
||||
entity->FindComponent<TargetSpawnableComponent>() == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
onAlternative = !onAlternative;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool AreAllMerged(AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
for (const AZ::Entity* entity : entities)
|
||||
{
|
||||
if (entity)
|
||||
{
|
||||
if (entity->FindComponent<SourceSpawnableComponent>() == nullptr ||
|
||||
entity->FindComponent<TargetSpawnableComponent>() == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CreateRecursiveHierarchy()
|
||||
{
|
||||
AzFramework::Spawnable::EntityList& entities = m_spawnable->GetEntities();
|
||||
@@ -245,6 +397,30 @@ namespace UnitTest
|
||||
TestApplication* m_application { nullptr };
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Constructors
|
||||
//
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, EntitySpawnTicket_Move_Works)
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket1(*m_spawnableAsset);
|
||||
AzFramework::EntitySpawnTicket ticket2(*m_spawnableAsset);
|
||||
|
||||
const AzFramework::EntitySpawnTicket::Id ticket1Id = ticket1.GetId();
|
||||
const AzFramework::EntitySpawnTicket::Id ticket2Id = ticket2.GetId();
|
||||
|
||||
AzFramework::EntitySpawnTicket ticketMoveConstructor(AZStd::move(ticket1));
|
||||
EXPECT_TRUE(ticketMoveConstructor.IsValid());
|
||||
EXPECT_EQ(ticketMoveConstructor.GetId(), ticket1Id);
|
||||
|
||||
AzFramework::EntitySpawnTicket ticketMoveOperator;
|
||||
ticketMoveOperator = AZStd::move(ticket2);
|
||||
EXPECT_TRUE(ticketMoveOperator.IsValid());
|
||||
EXPECT_EQ(ticketMoveOperator.GetId(), ticket2Id);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// SpawnAllEntitities
|
||||
//
|
||||
@@ -366,24 +542,6 @@ namespace UnitTest
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, EntitySpawnTicket_Move_Works)
|
||||
{
|
||||
AzFramework::EntitySpawnTicket ticket1(*m_spawnableAsset);
|
||||
AzFramework::EntitySpawnTicket ticket2(*m_spawnableAsset);
|
||||
|
||||
const AzFramework::EntitySpawnTicket::Id ticket1Id = ticket1.GetId();
|
||||
const AzFramework::EntitySpawnTicket::Id ticket2Id = ticket2.GetId();
|
||||
|
||||
AzFramework::EntitySpawnTicket ticketMoveConstructor(AZStd::move(ticket1));
|
||||
EXPECT_TRUE(ticketMoveConstructor.IsValid());
|
||||
EXPECT_EQ(ticketMoveConstructor.GetId(), ticket1Id);
|
||||
|
||||
AzFramework::EntitySpawnTicket ticketMoveOperator;
|
||||
ticketMoveOperator = AZStd::move(ticket2);
|
||||
EXPECT_TRUE(ticketMoveOperator.IsValid());
|
||||
EXPECT_EQ(ticketMoveOperator.GetId(), ticket2Id);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_DeleteTicketBeforeCall_NoCrash)
|
||||
{
|
||||
{
|
||||
@@ -393,6 +551,135 @@ namespace UnitTest
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithDisabled_NoEntitiesSpawned)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
InsertEntityAliases<NumEntities>(
|
||||
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
|
||||
{ Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable,
|
||||
Spawnable::EntityAliasType::Disable });
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
};
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(0, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_SomeAliasesWithDisabled_RegularEntitiesAreSpawned)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
static constexpr size_t NumEntities = 8;
|
||||
FillSpawnable(NumEntities);
|
||||
InsertEntityAliases<2>({ 1, 3 }, { 1, 3 }, { Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable });
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
};
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(6, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithReplace_EntitiesSpawnedFromTarget)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
|
||||
InsertEntityAliases<4>(
|
||||
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
|
||||
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
|
||||
Spawnable::EntityAliasType::Replace },
|
||||
&target);
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
bool allReplaced = false;
|
||||
auto callback = [&spawnedEntitiesCount, &allReplaced](
|
||||
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
allReplaced = AreAllEntitiesReplaced(entities);
|
||||
};
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(4, spawnedEntitiesCount);
|
||||
EXPECT_TRUE(allReplaced);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithAdditional_SourceAndTargetComponentsMerged)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
|
||||
InsertEntityAliases<4>(
|
||||
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
|
||||
{ Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional,
|
||||
Spawnable::EntityAliasType::Additional },
|
||||
&target);
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
bool allAdded = false;
|
||||
auto callback = [&spawnedEntitiesCount, &allAdded](
|
||||
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
allAdded = IsEveryOtherEntityAReplacement(entities);
|
||||
};
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(8, spawnedEntitiesCount);
|
||||
EXPECT_TRUE(allAdded);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnAllEntities_AllAliasesWithMerge_SourceAndTargetComponentsMerged)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
|
||||
InsertEntityAliases<4>(
|
||||
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
|
||||
{ Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge,
|
||||
Spawnable::EntityAliasType::Merge },
|
||||
&target);
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
bool allMerged = false;
|
||||
auto callback = [&spawnedEntitiesCount, &allMerged](
|
||||
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
allMerged = AreAllMerged(entities);
|
||||
};
|
||||
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnAllEntities(*m_ticket, AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(4, spawnedEntitiesCount);
|
||||
EXPECT_TRUE(allMerged);
|
||||
}
|
||||
|
||||
//
|
||||
// SpawnEntities
|
||||
@@ -403,7 +690,7 @@ namespace UnitTest
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
AZStd::vector<size_t> indices = { 0, 2, 3, 1 };
|
||||
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
@@ -423,7 +710,7 @@ namespace UnitTest
|
||||
static constexpr size_t NumEntities = 1;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
AZStd::vector<size_t> indices = { 0, 0 };
|
||||
AZStd::vector<uint32_t> indices = { 0, 0 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback =
|
||||
@@ -444,7 +731,7 @@ namespace UnitTest
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
AZStd::vector<size_t> indices = { 0, 2, 3, 1 };
|
||||
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback =
|
||||
@@ -467,7 +754,7 @@ namespace UnitTest
|
||||
FillSpawnable(NumEntities);
|
||||
CreateSingleParent();
|
||||
|
||||
AZStd::vector<size_t> indices = { 0, 1, 2, 3 };
|
||||
AZStd::vector<uint32_t> indices = { 0, 1, 2, 3 };
|
||||
AZStd::vector<AZ::EntityId> parents;
|
||||
|
||||
auto callback = [&parents](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
@@ -499,7 +786,7 @@ namespace UnitTest
|
||||
FillSpawnable(NumEntities);
|
||||
CreateSingleParent();
|
||||
|
||||
AZStd::vector<size_t> indices = { 0, 1, 2, 3 };
|
||||
AZStd::vector<uint32_t> indices = { 0, 1, 2, 3 };
|
||||
AZStd::vector<AZ::EntityId> parents;
|
||||
|
||||
auto callback =
|
||||
@@ -754,6 +1041,148 @@ namespace UnitTest
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithDisabled_NoEntitiesSpawned)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
|
||||
InsertEntityAliases<NumEntities>(
|
||||
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
|
||||
{ Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable,
|
||||
Spawnable::EntityAliasType::Disable });
|
||||
|
||||
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
};
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(0, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_SomeAliasesWithDisabled_RegularEntitiesAreSpawned)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
FillSpawnable(8);
|
||||
InsertEntityAliases<3>(
|
||||
{ 1, 3, 6 }, { 1, 3, 6 },
|
||||
{ Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Disable });
|
||||
|
||||
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1, 2, 3, 0, 1, 6, 4, 5, 7, 4, 1, 0, 6 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
auto callback = [&spawnedEntitiesCount](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
};
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(9, spawnedEntitiesCount);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithReplace_EntitiesSpawnedFromTarget)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
|
||||
InsertEntityAliases<4>(
|
||||
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
|
||||
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
|
||||
Spawnable::EntityAliasType::Replace },
|
||||
&target);
|
||||
|
||||
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
bool allReplaced = false;
|
||||
auto callback = [&spawnedEntitiesCount, &allReplaced](
|
||||
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
allReplaced = AreAllEntitiesReplaced(entities);
|
||||
};
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(4, spawnedEntitiesCount);
|
||||
EXPECT_TRUE(allReplaced);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithAdditional_SourceAndTargetComponentsMerged)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
|
||||
InsertEntityAliases<4>(
|
||||
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
|
||||
{ Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional,
|
||||
Spawnable::EntityAliasType::Additional },
|
||||
&target);
|
||||
|
||||
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
bool allAdded = false;
|
||||
auto callback =
|
||||
[&spawnedEntitiesCount, &allAdded](
|
||||
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
allAdded = IsEveryOtherEntityAReplacement(entities);
|
||||
};
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(8, spawnedEntitiesCount);
|
||||
EXPECT_TRUE(allAdded);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableEntitiesManagerTest, SpawnEntities_AllAliasesWithMerge_SourceAndTargetComponentsMerged)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
static constexpr size_t NumEntities = 4;
|
||||
FillSpawnable(NumEntities);
|
||||
AZ::Data::Asset<Spawnable> target = CreateTargetSpawnable(4);
|
||||
InsertEntityAliases<4>(
|
||||
{ 0, 1, 2, 3 }, { 0, 1, 2, 3 },
|
||||
{ Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Merge,
|
||||
Spawnable::EntityAliasType::Merge },
|
||||
&target);
|
||||
|
||||
AZStd::vector<uint32_t> indices = { 0, 2, 3, 1 };
|
||||
|
||||
size_t spawnedEntitiesCount = 0;
|
||||
bool allMerged = false;
|
||||
auto callback = [&spawnedEntitiesCount, &allMerged](
|
||||
AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
|
||||
{
|
||||
spawnedEntitiesCount += entities.size();
|
||||
allMerged = AreAllMerged(entities);
|
||||
};
|
||||
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
|
||||
optionalArgs.m_completionCallback = AZStd::move(callback);
|
||||
m_manager->SpawnEntities(*m_ticket, AZStd::move(indices), AZStd::move(optionalArgs));
|
||||
m_manager->ProcessQueue(AzFramework::SpawnableEntitiesManager::CommandQueuePriority::Regular);
|
||||
|
||||
EXPECT_EQ(4, spawnedEntitiesCount);
|
||||
EXPECT_TRUE(allMerged);
|
||||
}
|
||||
|
||||
//
|
||||
// DespawnAllEntities
|
||||
|
||||
@@ -0,0 +1,513 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Casting/numeric_cast.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
#include <AzTest/AzTest.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class SpawnableTest : public AllocatorsFixture
|
||||
{
|
||||
public:
|
||||
static constexpr size_t DefaultEntityAliasTestCount = 8;
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
AllocatorsFixture::SetUp();
|
||||
|
||||
m_spawnable = aznew AzFramework::Spawnable();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
delete m_spawnable;
|
||||
m_spawnable = nullptr;
|
||||
|
||||
AllocatorsFixture::TearDown();
|
||||
}
|
||||
|
||||
void InsertEntities(size_t count)
|
||||
{
|
||||
AzFramework::Spawnable::EntityList& entities = m_spawnable->GetEntities();
|
||||
entities.reserve(entities.size() + count);
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
entities.emplace_back(AZStd::make_unique<AZ::Entity>());
|
||||
}
|
||||
}
|
||||
|
||||
template<size_t Count>
|
||||
void InsertEntityAliases(
|
||||
const AZStd::array<uint32_t, Count>& sourceIds,
|
||||
const AZStd::array<uint32_t, Count>& targetIds,
|
||||
const AZStd::array<AzFramework::Spawnable::EntityAliasType, Count>& aliasTypes,
|
||||
bool queueLoad = false)
|
||||
{
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
|
||||
for (uint32_t i = 0; i < Count; ++i)
|
||||
{
|
||||
AZ::Data::Asset<AzFramework::Spawnable> spawnable(
|
||||
AZ::Data::AssetId(AZ::Uuid("{4CBEC17A-52D6-42D5-9037-F4C05B9CE1D9}"), i), azrtti_typeid<AzFramework::Spawnable>());
|
||||
visitor.AddAlias(spawnable, AZ::Crc32(i), sourceIds[i], targetIds[i], aliasTypes[i], queueLoad);
|
||||
}
|
||||
}
|
||||
|
||||
template<size_t Count>
|
||||
void InsertEntityAliases(bool queueLoad)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
|
||||
AZStd::array<uint32_t, Count> ids;
|
||||
for (uint32_t i=0; i<aznumeric_cast<uint32_t>(Count); ++i)
|
||||
{
|
||||
ids[i] = i;
|
||||
}
|
||||
|
||||
AZStd::array<AzFramework::Spawnable::EntityAliasType, Count> aliasTypes;
|
||||
for (uint32_t i = 0; i < aznumeric_cast<uint32_t>(Count); ++i)
|
||||
{
|
||||
aliasTypes[i] = Spawnable::EntityAliasType::Replace;
|
||||
}
|
||||
|
||||
InsertEntityAliases<Count>(ids, ids, aliasTypes, queueLoad);
|
||||
}
|
||||
|
||||
template<size_t Count>
|
||||
void InsertEntityAliases()
|
||||
{
|
||||
InsertEntityAliases<Count>(false);
|
||||
}
|
||||
|
||||
protected:
|
||||
AzFramework::Spawnable* m_spawnable;
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// TryGetAliasesConst
|
||||
//
|
||||
|
||||
TEST_F(SpawnableTest, TryGetAliasesConst_GetVisitor_VisitorDataIsAvailable)
|
||||
{
|
||||
AzFramework::Spawnable::EntityAliasConstVisitor visitor = m_spawnable->TryGetAliasesConst();
|
||||
EXPECT_TRUE(visitor.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, TryGetAliasesConst_VisitorThatIsNotReadShared_VisitorDataIsNotAvailable)
|
||||
{
|
||||
AzFramework::Spawnable::EntityAliasVisitor readWriteVisitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(readWriteVisitor.IsValid());
|
||||
|
||||
AzFramework::Spawnable::EntityAliasConstVisitor visitor = m_spawnable->TryGetAliasesConst();
|
||||
EXPECT_FALSE(visitor.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, TryGetAliasesConst_VisitorThatIsAlreadyReadShared_VisitorDataIsAvailable)
|
||||
{
|
||||
AzFramework::Spawnable::EntityAliasConstVisitor readVisitor = m_spawnable->TryGetAliasesConst();
|
||||
ASSERT_TRUE(readVisitor.IsValid());
|
||||
|
||||
AzFramework::Spawnable::EntityAliasConstVisitor visitor = m_spawnable->TryGetAliasesConst();
|
||||
EXPECT_TRUE(visitor.IsValid());
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// TryGetAliases
|
||||
//
|
||||
|
||||
TEST_F(SpawnableTest, TryGetAliases_GetVisitor_VisitorDataIsAvailable)
|
||||
{
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
EXPECT_TRUE(visitor.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, TryGetAliasesConst_VisitorThatIsAlreadyShared_VisitorDataNotIsAvailable)
|
||||
{
|
||||
AzFramework::Spawnable::EntityAliasConstVisitor readVisitor = m_spawnable->TryGetAliasesConst();
|
||||
ASSERT_TRUE(readVisitor.IsValid());
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
EXPECT_FALSE(visitor.IsValid());
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// EntityAliasVisitor
|
||||
//
|
||||
|
||||
|
||||
//
|
||||
// HasAliases
|
||||
//
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_HasAliases_EmptyAliasList_ReturnsFalse)
|
||||
{
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
EXPECT_FALSE(visitor.HasAliases());
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_HasAliases_FilledInAliasList_ReturnsTrue)
|
||||
{
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>();
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
EXPECT_TRUE(visitor.HasAliases());
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Optimize
|
||||
//
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_SortEntityAliases_AliasesAreSortedBySourceAndTargetId)
|
||||
{
|
||||
InsertEntities(DefaultEntityAliasTestCount);
|
||||
InsertEntityAliases<DefaultEntityAliasTestCount>();
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
// Optimize doesn't need to be explicitly called because the setup of the aliases will cause the alias list to be sorted and optimized.
|
||||
|
||||
uint32_t sourceIndex = 0;
|
||||
uint32_t targetIndex = 0;
|
||||
for (const AzFramework::Spawnable::EntityAlias& alias : visitor)
|
||||
{
|
||||
if (alias.m_sourceIndex != sourceIndex)
|
||||
{
|
||||
ASSERT_LE(sourceIndex, alias.m_sourceIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT_LE(targetIndex, alias.m_targetIndex);
|
||||
}
|
||||
sourceIndex = alias.m_sourceIndex;
|
||||
targetIndex = alias.m_targetIndex;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
SpawnableTest, EntityAliasVisitor_Optimize_RemoveUnused_OnlySecondToLastAliasRemains)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>(
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
|
||||
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Disable,
|
||||
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Disable,
|
||||
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Original });
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
EXPECT_EQ(1, AZStd::distance(visitor.begin(), visitor.end()));
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Replace, visitor.begin()->m_aliasType);
|
||||
EXPECT_EQ(6, visitor.begin()->m_targetIndex);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_AddAdditional_ThreeAdditionalAliasesAreAdded)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>(
|
||||
{ 0, 0, 0, 0, 1, 2, 2, 2 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
|
||||
{ Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional,
|
||||
Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional,
|
||||
Spawnable::EntityAliasType::Additional, Spawnable::EntityAliasType::Additional });
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
EXPECT_EQ(11, AZStd::distance(visitor.begin(), visitor.end()));
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()->m_aliasType);
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()[5].m_aliasType);
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()[7].m_aliasType);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_OriginalsOnly_AliasListIsEmpty)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>(
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
|
||||
{ Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original,
|
||||
Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original,
|
||||
Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original });
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
EXPECT_EQ(0, AZStd::distance(visitor.begin(), visitor.end()));
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_MixedOriginals_AllOriginalsRemoved)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>(
|
||||
{ 0, 0, 0, 1, 1, 2, 2, 2 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
|
||||
{ Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original,
|
||||
Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Disable, Spawnable::EntityAliasType::Original,
|
||||
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Original });
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
EXPECT_EQ(2, AZStd::distance(visitor.begin(), visitor.end()));
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Disable, visitor.begin()->m_aliasType);
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Replace, visitor.begin()[1].m_aliasType);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_Optimize_MergeAfterOriginal_NoAdditionalOriginalIsInserted)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>(
|
||||
{ 0, 0, 1, 1, 2, 2, 2, 2 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
|
||||
{ Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Replace,
|
||||
Spawnable::EntityAliasType::Merge, Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original,
|
||||
Spawnable::EntityAliasType::Original, Spawnable::EntityAliasType::Original });
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
EXPECT_EQ(4, AZStd::distance(visitor.begin(), visitor.end()));
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()->m_aliasType);
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Merge, visitor.begin()[1].m_aliasType);
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Replace, visitor.begin()[2].m_aliasType);
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Merge, visitor.begin()[3].m_aliasType);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// UpdateAliasType
|
||||
//
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_UpdateAliasType_AllToOriginal_NoAliasesAfterOptimization)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>(
|
||||
{ 0, 1, 2, 3, 4, 5, 6, 7 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
|
||||
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
|
||||
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
|
||||
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace });
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
for (uint32_t i = 0; i < 8; ++i)
|
||||
{
|
||||
visitor.UpdateAliasType(i, Spawnable::EntityAliasType::Original);
|
||||
}
|
||||
|
||||
for (const Spawnable::EntityAlias& alias : visitor)
|
||||
{
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Original, alias.m_aliasType);
|
||||
}
|
||||
|
||||
visitor.Optimize();
|
||||
|
||||
EXPECT_EQ(0, AZStd::distance(visitor.begin(), visitor.end()));
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// UpdateAliases
|
||||
//
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_UpdateAliases_AllToOriginal_NoAliasesAfterOptimization)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>(
|
||||
{ 0, 1, 2, 3, 4, 5, 6, 7 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
|
||||
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
|
||||
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
|
||||
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace });
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
auto callback =
|
||||
[](Spawnable::EntityAliasType& aliasType, bool& /*queueLoad*/, const AZ::Data::Asset<Spawnable>& /*aliasedSpawnable*/,
|
||||
const AZ::Crc32 /*tag*/, const uint32_t /*sourceIndex*/, const uint32_t /*targetIndex*/)
|
||||
{
|
||||
aliasType = Spawnable::EntityAliasType::Original;
|
||||
};
|
||||
visitor.UpdateAliases(AZStd::move(callback));
|
||||
|
||||
for (const Spawnable::EntityAlias& alias : visitor)
|
||||
{
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Original, alias.m_aliasType);
|
||||
}
|
||||
|
||||
visitor.Optimize();
|
||||
|
||||
EXPECT_EQ(0, AZStd::distance(visitor.begin(), visitor.end()));
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_UpdateAliases_FilterByTag_OnlyOneAliasUpdated)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>(
|
||||
{ 0, 1, 2, 3, 4, 5, 6, 7 }, { 0, 1, 2, 3, 4, 5, 6, 7 },
|
||||
{ Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
|
||||
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace,
|
||||
Spawnable::EntityAliasType::Replace, Spawnable::EntityAliasType::Replace });
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
bool correctTag = false;
|
||||
size_t numberOfUpdates = 0;
|
||||
auto callback = [&correctTag, &numberOfUpdates](Spawnable::EntityAliasType& aliasType, bool& /*queueLoad*/,
|
||||
const AZ::Data::Asset<Spawnable>& /*aliasedSpawnable*/, const AZ::Crc32 tag, const uint32_t /*sourceIndex*/,
|
||||
const uint32_t /*targetIndex*/)
|
||||
{
|
||||
correctTag = (tag == AZ::Crc32(3));
|
||||
numberOfUpdates++;
|
||||
aliasType = Spawnable::EntityAliasType::Original;
|
||||
};
|
||||
visitor.UpdateAliases(AZ::Crc32(3), AZStd::move(callback));
|
||||
|
||||
EXPECT_EQ(Spawnable::EntityAliasType::Original, visitor.begin()[3].m_aliasType);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// AreAllSpawnablesReady
|
||||
//
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_AreAllSpawnablesReady_CheckFakeLoadedAssets_ReturnsTrue)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(DefaultEntityAliasTestCount);
|
||||
InsertEntityAliases<DefaultEntityAliasTestCount>();
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
EXPECT_TRUE(visitor.AreAllSpawnablesReady());
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_AreAllSpawnablesReady_CheckFakeNotLoadedAssets_ReturnsFalse)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(8);
|
||||
InsertEntityAliases<8>(true);
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
EXPECT_FALSE(visitor.AreAllSpawnablesReady());
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// ListTargetSpawnables
|
||||
//
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_ListTargetSpawnables_ListAllTargetAssets_AllTargetsListed)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(DefaultEntityAliasTestCount);
|
||||
InsertEntityAliases<DefaultEntityAliasTestCount>();
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
size_t count = 0;
|
||||
bool correctAssets = true;
|
||||
auto callback = [&count, &correctAssets](const AZ::Data::Asset<Spawnable>& targetSpawnable)
|
||||
{
|
||||
correctAssets = correctAssets && (targetSpawnable.GetId().m_subId == count);
|
||||
count++;
|
||||
};
|
||||
visitor.ListTargetSpawnables(callback);
|
||||
|
||||
EXPECT_EQ(8, count);
|
||||
EXPECT_TRUE(correctAssets);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_ListTargetSpawnables_ListTaggedTargetAssets_OneAssetListed)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(DefaultEntityAliasTestCount);
|
||||
InsertEntityAliases<DefaultEntityAliasTestCount>();
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
size_t count = 0;
|
||||
bool correctAsset = false;
|
||||
auto callback = [&count, &correctAsset](const AZ::Data::Asset<Spawnable>& targetSpawnable)
|
||||
{
|
||||
correctAsset = (targetSpawnable.GetId().m_subId == 3);
|
||||
count++;
|
||||
};
|
||||
visitor.ListTargetSpawnables(AZ::Crc32(3), callback);
|
||||
|
||||
EXPECT_EQ(1, count);
|
||||
EXPECT_TRUE(correctAsset);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// ListSpawnablesRequiringLoad
|
||||
//
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_ListSpawnablesRequiringLoad_AllSetToLoaded_AllTargetsListed)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(DefaultEntityAliasTestCount);
|
||||
InsertEntityAliases<DefaultEntityAliasTestCount>(true);
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
size_t count = 0;
|
||||
bool correctAssets = true;
|
||||
auto callback = [&count, &correctAssets](const AZ::Data::Asset<Spawnable>& targetSpawnable)
|
||||
{
|
||||
correctAssets = correctAssets && (targetSpawnable.GetId().m_subId == count);
|
||||
count++;
|
||||
};
|
||||
visitor.ListSpawnablesRequiringLoad(callback);
|
||||
|
||||
EXPECT_EQ(8, count);
|
||||
EXPECT_TRUE(correctAssets);
|
||||
}
|
||||
|
||||
TEST_F(SpawnableTest, EntityAliasVisitor_ListSpawnablesRequiringLoad_AllSetToNotLoaded_NoTargetsListed)
|
||||
{
|
||||
using namespace AzFramework;
|
||||
InsertEntities(DefaultEntityAliasTestCount);
|
||||
InsertEntityAliases<DefaultEntityAliasTestCount>(false);
|
||||
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = m_spawnable->TryGetAliases();
|
||||
ASSERT_TRUE(visitor.IsValid());
|
||||
|
||||
size_t count = 0;
|
||||
auto callback = [&count](const AZ::Data::Asset<Spawnable>& /*targetSpawnable*/)
|
||||
{
|
||||
count++;
|
||||
};
|
||||
visitor.ListSpawnablesRequiringLoad(callback);
|
||||
|
||||
EXPECT_EQ(0, count);
|
||||
}
|
||||
} // namespace UnitTest
|
||||
@@ -10,6 +10,7 @@ set(FILES
|
||||
Main.cpp
|
||||
Spawnable/SpawnableEntitiesInterfaceTests.cpp
|
||||
Spawnable/SpawnableEntitiesManagerTests.cpp
|
||||
Spawnable/SpawnableTests.cpp
|
||||
ArchiveCompressionTests.cpp
|
||||
ArchiveTests.cpp
|
||||
BehaviorEntityTests.cpp
|
||||
|
||||
@@ -2785,7 +2785,7 @@ namespace AzQtComponents
|
||||
RepaintFloatingIndicators();
|
||||
}
|
||||
break;
|
||||
case QEvent::WindowDeactivate:
|
||||
case QEvent::WindowBlocked:
|
||||
// If our main window is deactivated while we are in the middle of
|
||||
// a docking drag operation (e.g. popup dialog for new level), we
|
||||
// should cancel our drag operation because the mouse release event
|
||||
|
||||
@@ -140,6 +140,11 @@ namespace AZ
|
||||
|
||||
void GemTestEnvironment::TeardownEnvironment()
|
||||
{
|
||||
for (AZ::ComponentDescriptor* descriptor : m_parameters->m_componentDescriptors)
|
||||
{
|
||||
m_application->UnregisterComponentDescriptor(descriptor);
|
||||
}
|
||||
|
||||
const AZ::Entity::ComponentArrayType& components = m_gemEntity->GetComponents();
|
||||
for (auto itComponent = components.rbegin(); itComponent != components.rend(); ++itComponent)
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace AZ
|
||||
|
||||
/// A test environment which is intended to facilitate writing unit tests which require components from a gem.
|
||||
class GemTestEnvironment
|
||||
: public UnitTest::TraceBusHook
|
||||
: public ::UnitTest::TraceBusHook
|
||||
{
|
||||
public:
|
||||
GemTestEnvironment();
|
||||
|
||||
+2
-2
@@ -40,9 +40,9 @@ namespace AzToolsFramework
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
QModelIndex parent(const QModelIndex& child) const override;
|
||||
QModelIndex sibling(int row, int column, const QModelIndex& idx) const override;
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
|
||||
protected:
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role /* = Qt::DisplayRole */) const override;
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace AzToolsFramework
|
||||
private slots:
|
||||
void SourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
|
||||
private:
|
||||
AZ::u64 m_numberOfItemsDisplayed = 0;
|
||||
AZ::u64 m_numberOfItemsDisplayed = 50;
|
||||
int m_displayedItemsCounter = 0;
|
||||
QPointer<AssetBrowserFilterModel> m_filterModel;
|
||||
QMap<int, QModelIndex> m_indexMap;
|
||||
|
||||
+11
-11
@@ -581,15 +581,15 @@ namespace AzToolsFramework
|
||||
{
|
||||
if (m_rootInstance && m_playInEditorData.m_isEnabled)
|
||||
{
|
||||
AZ_Assert(m_playInEditorData.m_entities.IsSet(),
|
||||
AZ_Assert(
|
||||
m_playInEditorData.m_entities.IsSet(),
|
||||
"Invalid Game Mode Entities Container encountered after play-in-editor stopped. "
|
||||
"Confirm that the container was initialized correctly");
|
||||
|
||||
m_playInEditorData.m_entities.DespawnAllEntities();
|
||||
m_playInEditorData.m_entities.Alert(
|
||||
[assets = AZStd::move(m_playInEditorData.m_assets),
|
||||
deactivatedEntities = AZStd::move(m_playInEditorData.m_deactivatedEntities)]
|
||||
([[maybe_unused]]uint32_t generation) mutable
|
||||
deactivatedEntities = AZStd::move(m_playInEditorData.m_deactivatedEntities)]([[maybe_unused]] uint32_t generation) mutable
|
||||
{
|
||||
auto end = deactivatedEntities.rend();
|
||||
for (auto it = deactivatedEntities.rbegin(); it != end; ++it)
|
||||
@@ -614,15 +614,15 @@ namespace AzToolsFramework
|
||||
AzFramework::GameEntityContextEventBus::Broadcast(&AzFramework::GameEntityContextEventBus::Events::OnGameEntitiesReset);
|
||||
});
|
||||
m_playInEditorData.m_entities.Clear();
|
||||
}
|
||||
|
||||
// Game entity cleanup is queued onto the next tick via the DespawnEntities call.
|
||||
// To avoid both game entities and Editor entities active at the same time
|
||||
// we flush the tick queue to ensure the game entities are cleared first.
|
||||
// The Alert callback that follows the DespawnEntities call will then reactivate the editor entities
|
||||
// This should be considered temporary as a move to a less rigid event sequence that supports async entity clean up
|
||||
// is the desired direction forward.
|
||||
AZ::TickBus::ExecuteQueuedEvents();
|
||||
// Game entity cleanup is queued onto the next tick via the DespawnEntities call.
|
||||
// To avoid both game entities and Editor entities active at the same time
|
||||
// we flush the tick queue to ensure the game entities are cleared first.
|
||||
// The Alert callback that follows the DespawnEntities call will then reactivate the editor entities
|
||||
// This should be considered temporary as a move to a less rigid event sequence that supports async entity clean up
|
||||
// is the desired direction forward.
|
||||
AZ::TickBus::ExecuteQueuedEvents();
|
||||
}
|
||||
|
||||
m_playInEditorData.m_isEnabled = false;
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ namespace AzToolsFramework
|
||||
|
||||
void Instance::SetLinkId(LinkId linkId)
|
||||
{
|
||||
m_linkId = AZStd::move(linkId);
|
||||
m_linkId = linkId;
|
||||
}
|
||||
|
||||
LinkId Instance::GetLinkId() const
|
||||
@@ -154,23 +154,26 @@ namespace AzToolsFramework
|
||||
|
||||
bool Instance::AddEntity(AZ::Entity& entity)
|
||||
{
|
||||
EntityAlias newEntityAlias = GenerateEntityAlias();
|
||||
return AddEntity(entity, newEntityAlias);
|
||||
return AddEntity(entity, GenerateEntityAlias());
|
||||
}
|
||||
|
||||
bool Instance::AddEntity(AZStd::unique_ptr<AZ::Entity>&& entity)
|
||||
{
|
||||
return AddEntity(AZStd::move(entity), GenerateEntityAlias());
|
||||
}
|
||||
|
||||
bool Instance::AddEntity(AZ::Entity& entity, EntityAlias entityAlias)
|
||||
{
|
||||
if (!RegisterEntity(entity.GetId(), entityAlias))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return
|
||||
RegisterEntity(entity.GetId(), entityAlias) &&
|
||||
m_entities.emplace(AZStd::move(entityAlias), &entity).second;
|
||||
}
|
||||
|
||||
if (!m_entities.emplace(AZStd::make_pair(entityAlias, &entity)).second)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
bool Instance::AddEntity(AZStd::unique_ptr<AZ::Entity>&& entity, EntityAlias entityAlias)
|
||||
{
|
||||
return
|
||||
RegisterEntity(entity->GetId(), entityAlias) &&
|
||||
m_entities.emplace(AZStd::move(entityAlias), AZStd::move(entity)).second;
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::Entity> Instance::DetachEntity(const AZ::EntityId& entityId)
|
||||
@@ -228,6 +231,23 @@ namespace AzToolsFramework
|
||||
m_entities.clear();
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::Entity> Instance::ReplaceEntity(AZStd::unique_ptr<AZ::Entity>&& entity, EntityAliasView alias)
|
||||
{
|
||||
AZStd::unique_ptr<AZ::Entity> result;
|
||||
auto it = m_entities.find(alias);
|
||||
if (it != m_entities.end())
|
||||
{
|
||||
// Swap entity ids as these need to remain stable
|
||||
AZ::EntityId originalId = it->second->GetId();
|
||||
it->second->SetId(entity->GetId());
|
||||
entity->SetId(originalId);
|
||||
|
||||
result = AZStd::move(it->second);
|
||||
it->second = AZStd::move(entity);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Instance::RemoveNestedEntities(
|
||||
const AZStd::function<bool(const AZStd::unique_ptr<AZ::Entity>&)>& filter)
|
||||
{
|
||||
@@ -377,7 +397,12 @@ namespace AzToolsFramework
|
||||
return entityAliases;
|
||||
}
|
||||
|
||||
void Instance::GetNestedEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback)
|
||||
size_t Instance::GetEntityAliasCount() const
|
||||
{
|
||||
return m_entities.size();
|
||||
}
|
||||
|
||||
void Instance::GetNestedEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback) const
|
||||
{
|
||||
GetEntityIds(callback);
|
||||
|
||||
@@ -387,7 +412,7 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::GetEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback)
|
||||
void Instance::GetEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback) const
|
||||
{
|
||||
for (auto&&[entityAlias, entityId] : m_templateToInstanceEntityIdMap)
|
||||
{
|
||||
@@ -398,6 +423,17 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
void Instance::GetEntityIdToAlias(const AZStd::function<bool(AZ::EntityId, EntityAliasView)>& callback) const
|
||||
{
|
||||
for (auto&& [entityAlias, entityId] : m_templateToInstanceEntityIdMap)
|
||||
{
|
||||
if (!callback(entityId, entityAlias))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Instance::GetEntities_Impl(const AZStd::function<bool(AZStd::unique_ptr<AZ::Entity>&)>& callback)
|
||||
{
|
||||
for (auto& [entityAlias, entity] : m_entities)
|
||||
@@ -514,24 +550,81 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
EntityAliasOptionalReference Instance::GetEntityAlias(const AZ::EntityId& id)
|
||||
EntityAliasOptionalReference Instance::GetEntityAlias(AZ::EntityId id)
|
||||
{
|
||||
if (m_instanceToTemplateEntityIdMap.count(id))
|
||||
{
|
||||
return m_instanceToTemplateEntityIdMap[id];
|
||||
}
|
||||
|
||||
return AZStd::nullopt;
|
||||
auto it = m_instanceToTemplateEntityIdMap.find(id);
|
||||
return it != m_instanceToTemplateEntityIdMap.end() ? EntityAliasOptionalReference(it->second)
|
||||
: EntityAliasOptionalReference(AZStd::nullopt);
|
||||
}
|
||||
|
||||
AZ::EntityId Instance::GetEntityId(const EntityAlias& alias)
|
||||
EntityAliasView Instance::GetEntityAlias(AZ::EntityId id) const
|
||||
{
|
||||
if (m_templateToInstanceEntityIdMap.count(alias))
|
||||
auto it = m_instanceToTemplateEntityIdMap.find(id);
|
||||
return it != m_instanceToTemplateEntityIdMap.end() ? EntityAliasView(it->second) : EntityAliasView();
|
||||
}
|
||||
|
||||
AZStd::pair<Instance*, EntityAliasView> Instance::FindInstanceAndAlias(AZ::EntityId entity)
|
||||
{
|
||||
auto it = m_instanceToTemplateEntityIdMap.find(entity);
|
||||
if (it != m_instanceToTemplateEntityIdMap.end())
|
||||
{
|
||||
return m_templateToInstanceEntityIdMap[alias];
|
||||
return AZStd::pair<Instance*, EntityAliasView>(this, it->second);
|
||||
}
|
||||
|
||||
return AZ::EntityId();
|
||||
else
|
||||
{
|
||||
for (auto&& [_, instance] : m_nestedInstances)
|
||||
{
|
||||
AZStd::pair<Instance*, EntityAliasView> next = instance->FindInstanceAndAlias(entity);
|
||||
if (next.first != nullptr)
|
||||
{
|
||||
return next;
|
||||
}
|
||||
}
|
||||
}
|
||||
return AZStd::pair<Instance*, EntityAliasView>(nullptr, "");
|
||||
}
|
||||
|
||||
AZStd::pair<const Instance*, EntityAliasView> Instance::FindInstanceAndAlias(AZ::EntityId entity) const
|
||||
{
|
||||
return const_cast<Instance*>(this)->FindInstanceAndAlias(entity);
|
||||
}
|
||||
|
||||
EntityOptionalReference Instance::GetEntity(const EntityAlias& alias)
|
||||
{
|
||||
auto it = m_entities.find(alias);
|
||||
return it != m_entities.end() ? EntityOptionalReference(*it->second) : EntityOptionalReference(AZStd::nullopt);
|
||||
}
|
||||
|
||||
EntityOptionalConstReference Instance::GetEntity(const EntityAlias& alias) const
|
||||
{
|
||||
auto it = m_entities.find(alias);
|
||||
return it != m_entities.end() ? EntityOptionalConstReference(*it->second) : EntityOptionalConstReference(AZStd::nullopt);
|
||||
}
|
||||
|
||||
AZ::EntityId Instance::GetEntityId(const EntityAlias& alias) const
|
||||
{
|
||||
auto it = m_templateToInstanceEntityIdMap.find(alias);
|
||||
return it != m_templateToInstanceEntityIdMap.end() ? it->second : AZ::EntityId();
|
||||
}
|
||||
|
||||
AZ::EntityId Instance::GetEntityIdFromAliasPath(AliasPathView relativeAliasPath) const
|
||||
{
|
||||
const Instance* instance = this;
|
||||
AliasPathView path = relativeAliasPath.ParentPath();
|
||||
for (auto it : path)
|
||||
{
|
||||
InstanceOptionalConstReference child = instance->FindNestedInstance(it.Native());
|
||||
if (child.has_value())
|
||||
{
|
||||
instance = &(child->get());
|
||||
}
|
||||
else
|
||||
{
|
||||
return AZ::EntityId();
|
||||
}
|
||||
}
|
||||
|
||||
return instance->GetEntityId(relativeAliasPath.Filename().Native());
|
||||
}
|
||||
|
||||
AZStd::vector<InstanceAlias> Instance::GetNestedInstanceAliases(TemplateId templateId) const
|
||||
@@ -572,6 +665,32 @@ namespace AzToolsFramework
|
||||
return aliasPathResult;
|
||||
}
|
||||
|
||||
AliasPath Instance::GetAliasPathRelativeToInstance(const AZ::EntityId& entity) const
|
||||
{
|
||||
AliasPath result = AliasPath(s_aliasPathSeparator);
|
||||
auto&& [instance, alias] = FindInstanceAndAlias(entity);
|
||||
if (instance)
|
||||
{
|
||||
AZStd::vector<const Instance*> instanceChain;
|
||||
|
||||
while (instance && instance != this)
|
||||
{
|
||||
instanceChain.push_back(instance);
|
||||
instance = instance->m_parent;
|
||||
}
|
||||
|
||||
for (auto it = instanceChain.rbegin(); it != instanceChain.rend(); ++it)
|
||||
{
|
||||
result.Append((*it)->m_alias);
|
||||
}
|
||||
return result.Append(alias);
|
||||
}
|
||||
else
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
EntityAlias Instance::GenerateEntityAlias()
|
||||
{
|
||||
return AZStd::string::format("Entity_%s", AZ::Entity::MakeId().ToString().c_str());
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace AzToolsFramework
|
||||
using AliasPath = AZ::IO::Path;
|
||||
using AliasPathView = AZ::IO::PathView;
|
||||
using EntityAlias = AZStd::string;
|
||||
using EntityAliasView = AZStd::string_view;
|
||||
using InstanceAlias = AZStd::string;
|
||||
|
||||
class Instance;
|
||||
@@ -83,9 +84,17 @@ namespace AzToolsFramework
|
||||
void SetContainerEntityName(AZStd::string_view containerName);
|
||||
|
||||
bool AddEntity(AZ::Entity& entity);
|
||||
bool AddEntity(AZStd::unique_ptr<AZ::Entity>&& entity);
|
||||
bool AddEntity(AZ::Entity& entity, EntityAlias entityAlias);
|
||||
bool AddEntity(AZStd::unique_ptr<AZ::Entity>&& entity, EntityAlias entityAlias);
|
||||
AZStd::unique_ptr<AZ::Entity> DetachEntity(const AZ::EntityId& entityId);
|
||||
void DetachEntities(const AZStd::function<void(AZStd::unique_ptr<AZ::Entity>)>& callback);
|
||||
/**
|
||||
* Replaces the entity stored under the provided alias with a new one.
|
||||
*
|
||||
* @return The original entity or a nullptr if not found.
|
||||
*/
|
||||
AZStd::unique_ptr<AZ::Entity> ReplaceEntity(AZStd::unique_ptr<AZ::Entity>&& entity, EntityAliasView alias);
|
||||
|
||||
/**
|
||||
* Detaches all entities in the instance hierarchy.
|
||||
@@ -109,13 +118,15 @@ namespace AzToolsFramework
|
||||
* @return The list of EntityAliases
|
||||
*/
|
||||
AZStd::vector<EntityAlias> GetEntityAliases();
|
||||
size_t GetEntityAliasCount() const;
|
||||
|
||||
/**
|
||||
* Gets the ids for the entities in the Instance DOM. Can recursively trace all nested instances.
|
||||
*/
|
||||
void GetNestedEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback);
|
||||
void GetNestedEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback) const;
|
||||
|
||||
void GetEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback);
|
||||
void GetEntityIds(const AZStd::function<bool(AZ::EntityId)>& callback) const;
|
||||
void GetEntityIdToAlias(const AZStd::function<bool(AZ::EntityId, EntityAliasView)>& callback) const;
|
||||
|
||||
/**
|
||||
* Gets the entities in the Instance DOM. Can recursively trace all nested instances.
|
||||
@@ -131,14 +142,33 @@ namespace AzToolsFramework
|
||||
*
|
||||
* @return entityAlias via optional
|
||||
*/
|
||||
AZStd::optional<AZStd::reference_wrapper<EntityAlias>> GetEntityAlias(const AZ::EntityId& id);
|
||||
EntityAliasOptionalReference GetEntityAlias(AZ::EntityId id);
|
||||
EntityAliasView GetEntityAlias(AZ::EntityId id) const;
|
||||
/**
|
||||
* Searches for the entity in this instance and its nested instances.
|
||||
*
|
||||
* @return The instance that owns the entity and the alias under which the entity is known.
|
||||
* If the entity isn't found then the instance will be null and the alias empty.
|
||||
*/
|
||||
AZStd::pair<Instance*, EntityAliasView> FindInstanceAndAlias(AZ::EntityId entity);
|
||||
AZStd::pair<const Instance*, EntityAliasView> FindInstanceAndAlias(AZ::EntityId entity) const;
|
||||
|
||||
EntityOptionalReference GetEntity(const EntityAlias& alias);
|
||||
EntityOptionalConstReference GetEntity(const EntityAlias& alias) const;
|
||||
|
||||
/**
|
||||
* Gets the id for a given EnitityAlias in the Instance DOM.
|
||||
*
|
||||
* @return entityId, invalid ID if not found
|
||||
*/
|
||||
AZ::EntityId GetEntityId(const EntityAlias& alias);
|
||||
AZ::EntityId GetEntityId(const EntityAlias& alias) const;
|
||||
|
||||
/**
|
||||
* Retrieves the entity id from an alias path that's relative to this instance.
|
||||
*
|
||||
* @return entityId, invalid ID if not found
|
||||
*/
|
||||
AZ::EntityId GetEntityIdFromAliasPath(AliasPathView relativeAliasPath) const;
|
||||
|
||||
|
||||
/**
|
||||
@@ -180,6 +210,7 @@ namespace AzToolsFramework
|
||||
|
||||
static EntityAlias GenerateEntityAlias();
|
||||
AliasPath GetAbsoluteInstanceAliasPath() const;
|
||||
AliasPath GetAliasPathRelativeToInstance(const AZ::EntityId& entity) const;
|
||||
|
||||
static InstanceAlias GenerateInstanceAlias();
|
||||
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ namespace AzToolsFramework
|
||||
//! @param instanceToExclude An optional reference to an instance of the template being updated that should not be refreshes as part of propagation.
|
||||
//! Defaults to nullopt, which means that all instances will be refreshed.
|
||||
//! @return True if the template was patched correctly, false if the operation failed.
|
||||
virtual bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
|
||||
virtual bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalConstReference instanceToExclude = AZStd::nullopt) = 0;
|
||||
|
||||
virtual void ApplyPatchesToInstance(const AZ::EntityId& entityId, PrefabDom& patches, const Instance& instanceToAddPatches) = 0;
|
||||
|
||||
|
||||
+1
-1
@@ -156,7 +156,7 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
bool InstanceToTemplatePropagator::PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalReference instanceToExclude)
|
||||
bool InstanceToTemplatePropagator::PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalConstReference instanceToExclude)
|
||||
{
|
||||
PrefabDom& templateDomReference = m_prefabSystemComponentInterface->FindTemplateDom(templateId);
|
||||
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ namespace AzToolsFramework
|
||||
|
||||
InstanceOptionalReference GetTopMostInstanceInHierarchy(AZ::EntityId entityId) override;
|
||||
|
||||
bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
|
||||
bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalConstReference instanceToExclude = AZStd::nullopt) override;
|
||||
|
||||
void ApplyPatchesToInstance(const AZ::EntityId& entityId, PrefabDom& patches, const Instance& instanceToAddPatches) override;
|
||||
|
||||
|
||||
+2
-2
@@ -52,7 +52,7 @@ namespace AzToolsFramework
|
||||
AZ::Interface<InstanceUpdateExecutorInterface>::Unregister(this);
|
||||
}
|
||||
|
||||
void InstanceUpdateExecutor::AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalReference instanceToExclude)
|
||||
void InstanceUpdateExecutor::AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalConstReference instanceToExclude)
|
||||
{
|
||||
auto findInstancesResult =
|
||||
m_templateInstanceMapperInterface->FindInstancesOwnedByTemplate(instanceTemplateId);
|
||||
@@ -66,7 +66,7 @@ namespace AzToolsFramework
|
||||
return;
|
||||
}
|
||||
|
||||
Instance* instanceToExcludePtr = nullptr;
|
||||
const Instance* instanceToExcludePtr = nullptr;
|
||||
if (instanceToExclude.has_value())
|
||||
{
|
||||
instanceToExcludePtr = &(instanceToExclude->get());
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ namespace AzToolsFramework
|
||||
|
||||
explicit InstanceUpdateExecutor(int instanceCountToUpdateInBatch = 0);
|
||||
|
||||
void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
|
||||
void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalConstReference instanceToExclude = AZStd::nullopt) override;
|
||||
bool UpdateTemplateInstancesInQueue() override;
|
||||
virtual void RemoveTemplateInstanceFromQueue(const Instance* instance) override;
|
||||
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ namespace AzToolsFramework
|
||||
virtual ~InstanceUpdateExecutorInterface() = default;
|
||||
|
||||
// Add all Instances of Template with given Id into a queue for updating them later.
|
||||
virtual void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
|
||||
virtual void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalConstReference instanceToExclude = AZStd::nullopt) = 0;
|
||||
|
||||
// Update Instances in the waiting queue.
|
||||
virtual bool UpdateTemplateInstancesInQueue() = 0;
|
||||
|
||||
@@ -777,7 +777,7 @@ namespace AzToolsFramework
|
||||
linkUpdate->SetParent(undoBatch);
|
||||
linkUpdate->Capture(patch, linkId);
|
||||
|
||||
linkUpdate->Redo(parentInstance);
|
||||
linkUpdate->Redo(parentInstance->get());
|
||||
}
|
||||
|
||||
void PrefabPublicHandler::Internal_HandleEntityChange(
|
||||
@@ -788,8 +788,7 @@ namespace AzToolsFramework
|
||||
PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
|
||||
state->SetParent(undoBatch);
|
||||
state->Capture(beforeState, afterState, entityId);
|
||||
|
||||
state->Redo(instance);
|
||||
state->Redo(instance->get());
|
||||
}
|
||||
|
||||
void PrefabPublicHandler::Internal_HandleInstanceChange(
|
||||
@@ -1167,6 +1166,60 @@ namespace AzToolsFramework
|
||||
|
||||
ScopedUndoBatch undoBatch("Delete Selected");
|
||||
|
||||
AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DeleteEntities:UndoCaptureAndPurgeEntities");
|
||||
|
||||
Prefab::PrefabDom instanceDomBefore;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonOwningInstance->get());
|
||||
|
||||
if (deleteDescendants)
|
||||
{
|
||||
AZStd::vector<AZ::Entity*> entities;
|
||||
AZStd::vector<Instance*> instances;
|
||||
|
||||
PrefabOperationResult retrieveEntitiesAndInstancesOutcome =
|
||||
RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
|
||||
|
||||
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
|
||||
{
|
||||
return AZStd::move(retrieveEntitiesAndInstancesOutcome);
|
||||
}
|
||||
|
||||
for (AZ::Entity* entity : entities)
|
||||
{
|
||||
commonOwningInstance->get().DetachEntity(entity->GetId()).release();
|
||||
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entity->GetId());
|
||||
}
|
||||
|
||||
for (auto& nestedInstance : instances)
|
||||
{
|
||||
AZStd::unique_ptr<Instance> outInstance =
|
||||
commonOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias());
|
||||
RemoveLink(outInstance, commonOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
|
||||
outInstance.reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (AZ::EntityId entityId : entityIdsNoFocusContainer)
|
||||
{
|
||||
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
|
||||
// If this is the container entity, it actually represents the instance so get its owner
|
||||
if (owningInstance->get().GetContainerEntityId() == entityId)
|
||||
{
|
||||
auto instancePtr = commonOwningInstance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias());
|
||||
RemoveLink(instancePtr, commonOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
|
||||
}
|
||||
else
|
||||
{
|
||||
commonOwningInstance->get().DetachEntity(entityId);
|
||||
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entityId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Prefab::PrefabDom instanceDomAfter;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, commonOwningInstance->get());
|
||||
|
||||
// In order to undo DeleteSelected, we have to create a selection command which selects the current selection
|
||||
// and then add the deletion as children.
|
||||
// Commands always execute themselves first and then their children (when going forwards)
|
||||
@@ -1174,81 +1227,22 @@ namespace AzToolsFramework
|
||||
EntityIdList selectedEntities;
|
||||
ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities);
|
||||
SelectionCommand* selCommand = aznew SelectionCommand(selectedEntities, "Delete Entities");
|
||||
selCommand->SetParent(undoBatch.GetUndoBatch());
|
||||
AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DeleteEntities:RunRedo");
|
||||
selCommand->RunRedo();
|
||||
|
||||
// We insert a "deselect all" command before we delete the entities. This ensures the delete operations aren't changing
|
||||
// selection state, which triggers expensive UI updates. By deselecting up front, we are able to do those expensive
|
||||
// UI updates once at the start instead of once for each entity.
|
||||
{
|
||||
EntityIdList deselection;
|
||||
SelectionCommand* deselectAllCommand = aznew SelectionCommand(deselection, "Deselect Entities");
|
||||
deselectAllCommand->SetParent(selCommand);
|
||||
}
|
||||
|
||||
{
|
||||
AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DeleteEntities:UndoCaptureAndPurgeEntities");
|
||||
|
||||
Prefab::PrefabDom instanceDomBefore;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonOwningInstance->get());
|
||||
|
||||
if (deleteDescendants)
|
||||
{
|
||||
AZStd::vector<AZ::Entity*> entities;
|
||||
AZStd::vector<Instance*> instances;
|
||||
|
||||
PrefabOperationResult retrieveEntitiesAndInstancesOutcome =
|
||||
RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
|
||||
|
||||
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
|
||||
{
|
||||
return AZStd::move(retrieveEntitiesAndInstancesOutcome);
|
||||
}
|
||||
|
||||
for (AZ::Entity* entity : entities)
|
||||
{
|
||||
commonOwningInstance->get().DetachEntity(entity->GetId()).release();
|
||||
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entity->GetId());
|
||||
}
|
||||
|
||||
for (auto& nestedInstance : instances)
|
||||
{
|
||||
AZStd::unique_ptr<Instance> outInstance = commonOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias());
|
||||
RemoveLink(outInstance, commonOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
|
||||
outInstance.reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (AZ::EntityId entityId : entityIdsNoFocusContainer)
|
||||
{
|
||||
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
|
||||
// If this is the container entity, it actually represents the instance so get its owner
|
||||
if (owningInstance->get().GetContainerEntityId() == entityId)
|
||||
{
|
||||
auto instancePtr = commonOwningInstance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias());
|
||||
RemoveLink(instancePtr, commonOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
|
||||
}
|
||||
else
|
||||
{
|
||||
commonOwningInstance->get().DetachEntity(entityId);
|
||||
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entityId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Prefab::PrefabDom instanceDomAfter;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, commonOwningInstance->get());
|
||||
|
||||
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance deletion");
|
||||
command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId());
|
||||
command->SetParent(selCommand);
|
||||
}
|
||||
|
||||
selCommand->SetParent(undoBatch.GetUndoBatch());
|
||||
{
|
||||
AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DeleteEntities:RunRedo");
|
||||
selCommand->RunRedo();
|
||||
}
|
||||
EntityIdList deselection;
|
||||
SelectionCommand* deselectAllCommand = aznew SelectionCommand(deselection, "Deselect Entities");
|
||||
deselectAllCommand->SetParent(undoBatch.GetUndoBatch());
|
||||
|
||||
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance deletion");
|
||||
command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId());
|
||||
command->SetParent(undoBatch.GetUndoBatch());
|
||||
command->Redo(commonOwningInstance->get());
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
@@ -1338,7 +1332,7 @@ namespace AzToolsFramework
|
||||
command->SetParent(undoBatch.GetUndoBatch());
|
||||
{
|
||||
AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DetachPrefab:RunRedo");
|
||||
command->RunRedo();
|
||||
command->Redo(parentInstance);
|
||||
}
|
||||
|
||||
instancePtr->DetachNestedInstances(
|
||||
|
||||
@@ -159,11 +159,9 @@ namespace AzToolsFramework
|
||||
newInstance->SetTemplateId(newTemplateId);
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, InstanceOptionalReference instanceToExclude)
|
||||
|
||||
void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, InstanceOptionalConstReference instanceToExclude)
|
||||
{
|
||||
UpdatePrefabInstances(templateId, instanceToExclude);
|
||||
|
||||
auto templateIdToLinkIdsIterator = m_templateToLinkIdsMap.find(templateId);
|
||||
if (templateIdToLinkIdsIterator != m_templateToLinkIdsMap.end())
|
||||
{
|
||||
@@ -174,6 +172,7 @@ namespace AzToolsFramework
|
||||
templateIdToLinkIdsIterator->second.end()));
|
||||
UpdateLinkedInstances(linkIdsToUpdateQueue);
|
||||
}
|
||||
UpdatePrefabInstances(templateId, instanceToExclude);
|
||||
}
|
||||
|
||||
void PrefabSystemComponent::UpdatePrefabTemplate(TemplateId templateId, const PrefabDom& updatedDom)
|
||||
@@ -191,7 +190,7 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabSystemComponent::UpdatePrefabInstances(TemplateId templateId, InstanceOptionalReference instanceToExclude)
|
||||
void PrefabSystemComponent::UpdatePrefabInstances(TemplateId templateId, InstanceOptionalConstReference instanceToExclude)
|
||||
{
|
||||
m_instanceUpdateExecutor.AddTemplateInstancesToQueue(templateId, instanceToExclude);
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ namespace AzToolsFramework
|
||||
*/
|
||||
void UpdatePrefabTemplate(TemplateId templateId, const PrefabDom& updatedDom) override;
|
||||
|
||||
void PropagateTemplateChanges(TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
|
||||
void PropagateTemplateChanges(TemplateId templateId, InstanceOptionalConstReference instanceToExclude = AZStd::nullopt) override;
|
||||
|
||||
/**
|
||||
* Updates all Instances owned by a Template.
|
||||
@@ -240,7 +240,7 @@ namespace AzToolsFramework
|
||||
* @param instanceToExclude An optional reference to an instance of the template being updated that should not be refreshed
|
||||
* as part of propagation.Defaults to nullopt, which means that all instances will be refreshed.
|
||||
*/
|
||||
void UpdatePrefabInstances(TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt);
|
||||
void UpdatePrefabInstances(TemplateId templateId, InstanceOptionalConstReference instanceToExclude = AZStd::nullopt);
|
||||
|
||||
private:
|
||||
AZ_DISABLE_COPY_MOVE(PrefabSystemComponent);
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ namespace AzToolsFramework
|
||||
|
||||
virtual PrefabDom& FindTemplateDom(TemplateId templateId) = 0;
|
||||
virtual void UpdatePrefabTemplate(TemplateId templateId, const PrefabDom& updatedDom) = 0;
|
||||
virtual void PropagateTemplateChanges(TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
|
||||
virtual void PropagateTemplateChanges(TemplateId templateId, InstanceOptionalConstReference instanceToExclude = AZStd::nullopt) = 0;
|
||||
|
||||
virtual AZStd::unique_ptr<Instance> InstantiatePrefab(
|
||||
AZ::IO::PathView filePath, InstanceOptionalReference parent = AZStd::nullopt) = 0;
|
||||
|
||||
@@ -51,6 +51,10 @@ namespace AzToolsFramework
|
||||
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId);
|
||||
}
|
||||
|
||||
void PrefabUndoInstance::Redo(InstanceOptionalConstReference instance)
|
||||
{
|
||||
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, instance);
|
||||
}
|
||||
|
||||
//PrefabEntityUpdateUndo
|
||||
PrefabUndoEntityUpdate::PrefabUndoEntityUpdate(const AZStd::string& undoOperationName)
|
||||
@@ -110,10 +114,10 @@ namespace AzToolsFramework
|
||||
m_templateId);
|
||||
}
|
||||
|
||||
void PrefabUndoEntityUpdate::Redo(InstanceOptionalReference instanceToExclude)
|
||||
void PrefabUndoEntityUpdate::Redo(InstanceOptionalConstReference instance)
|
||||
{
|
||||
[[maybe_unused]] bool isPatchApplicationSuccessful =
|
||||
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, instanceToExclude);
|
||||
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, instance);
|
||||
|
||||
AZ_Error(
|
||||
"Prefab", isPatchApplicationSuccessful,
|
||||
@@ -310,12 +314,12 @@ namespace AzToolsFramework
|
||||
UpdateLink(m_linkDomNext);
|
||||
}
|
||||
|
||||
void PrefabUndoLinkUpdate::Redo(InstanceOptionalReference instanceToExclude)
|
||||
void PrefabUndoLinkUpdate::Redo(InstanceOptionalConstReference instanceToExclude)
|
||||
{
|
||||
UpdateLink(m_linkDomNext, instanceToExclude);
|
||||
}
|
||||
|
||||
void PrefabUndoLinkUpdate::UpdateLink(PrefabDom& linkDom, InstanceOptionalReference instanceToExclude)
|
||||
void PrefabUndoLinkUpdate::UpdateLink(PrefabDom& linkDom, InstanceOptionalConstReference instanceToExclude)
|
||||
{
|
||||
LinkReference link = m_prefabSystemComponentInterface->FindLink(m_linkId);
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ namespace AzToolsFramework
|
||||
|
||||
void Undo() override;
|
||||
void Redo() override;
|
||||
void Redo(InstanceOptionalConstReference instance);
|
||||
};
|
||||
|
||||
//! handles entity updates, such as when the values on an entity change
|
||||
@@ -72,7 +73,7 @@ namespace AzToolsFramework
|
||||
void Undo() override;
|
||||
void Redo() override;
|
||||
//! Overload to allow to apply the change, but prevent instanceToExclude from being refreshed.
|
||||
void Redo(InstanceOptionalReference instanceToExclude);
|
||||
void Redo(InstanceOptionalConstReference instanceToExclude);
|
||||
|
||||
private:
|
||||
InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr;
|
||||
@@ -137,10 +138,10 @@ namespace AzToolsFramework
|
||||
void Undo() override;
|
||||
void Redo() override;
|
||||
//! Overload to allow to apply the change, but prevent instanceToExclude from being refreshed.
|
||||
void Redo(InstanceOptionalReference instanceToExclude);
|
||||
void Redo(InstanceOptionalConstReference instanceToExclude);
|
||||
|
||||
private:
|
||||
void UpdateLink(PrefabDom& linkDom, InstanceOptionalReference instanceToExclude = AZStd::nullopt);
|
||||
void UpdateLink(PrefabDom& linkDom, InstanceOptionalConstReference instanceToExclude = AZStd::nullopt);
|
||||
|
||||
LinkId m_linkId;
|
||||
PrefabDom m_linkDomNext; //data for delete/update
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace AzToolsFramework
|
||||
PrefabUndoInstance* state = aznew Prefab::PrefabUndoInstance(undoMessage);
|
||||
state->Capture(instanceDomBeforeUpdate, instanceDomAfterUpdate, instance.GetTemplateId());
|
||||
state->SetParent(undoBatch);
|
||||
state->Redo();
|
||||
state->Redo(instance);
|
||||
}
|
||||
|
||||
LinkId CreateLink(
|
||||
|
||||
+33
-23
@@ -13,9 +13,12 @@
|
||||
#include <AzCore/Serialization/Utils.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/Instance.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
|
||||
#include <AzToolsFramework/Prefab/Spawnable/PrefabCatchmentProcessor.h>
|
||||
#include <AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h>
|
||||
|
||||
|
||||
namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
{
|
||||
void PrefabCatchmentProcessor::Process(PrefabProcessorContext& context)
|
||||
@@ -37,7 +40,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
->Value("Text", SerializationFormats::Text);
|
||||
|
||||
serializeContext->Class<PrefabCatchmentProcessor, PrefabProcessor>()
|
||||
->Version(2)
|
||||
->Version(3)
|
||||
->Field("SerializationFormat", &PrefabCatchmentProcessor::m_serializationFormat);
|
||||
}
|
||||
}
|
||||
@@ -45,6 +48,8 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
void PrefabCatchmentProcessor::ProcessPrefab(PrefabProcessorContext& context, AZStd::string_view prefabName, PrefabDom& prefab,
|
||||
AZ::DataStream::StreamType serializationFormat)
|
||||
{
|
||||
using namespace AzToolsFramework::Prefab::SpawnableUtils;
|
||||
|
||||
AZStd::string uniqueName = prefabName;
|
||||
uniqueName += AzFramework::Spawnable::DotFileExtension;
|
||||
|
||||
@@ -59,33 +64,38 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
AZStd::move(uniqueName), context.GetSourceUuid(), AZStd::move(serializer));
|
||||
AZ_Assert(spawnable, "Failed to create a new spawnable.");
|
||||
|
||||
bool result = SpawnableUtils::CreateSpawnable(*spawnable, prefab, object.GetReferencedAssets());
|
||||
if (result)
|
||||
Instance instance;
|
||||
if (Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(
|
||||
instance, prefab, object.GetReferencedAssets(),
|
||||
Prefab::PrefabDomUtils::LoadFlags::AssignRandomEntityId)) // Always assign random entity ids because the spawnable is
|
||||
// going to be used to create clones of the entities.
|
||||
{
|
||||
// Resolve entity aliases that store PrefabDOM information to use the spawnable instead. This is done before the entities are
|
||||
// moved from the instance as they'd otherwise can't be found.
|
||||
context.ResolveSpawnableEntityAliases(prefabName, *spawnable, instance);
|
||||
|
||||
AzFramework::Spawnable::EntityList& entities = spawnable->GetEntities();
|
||||
for (auto it = entities.begin(); it != entities.end(); )
|
||||
{
|
||||
if (*it)
|
||||
instance.DetachAllEntitiesInHierarchy(
|
||||
[&entities, &context](AZStd::unique_ptr<AZ::Entity> entity)
|
||||
{
|
||||
(*it)->InvalidateDependencies();
|
||||
AZ::Entity::DependencySortOutcome evaluation = (*it)->EvaluateDependenciesGetDetails();
|
||||
if (evaluation.IsSuccess())
|
||||
if (entity)
|
||||
{
|
||||
++it;
|
||||
entity->InvalidateDependencies();
|
||||
AZ::Entity::DependencySortOutcome evaluation = entity->EvaluateDependenciesGetDetails();
|
||||
if (evaluation.IsSuccess())
|
||||
{
|
||||
entities.emplace_back(AZStd::move(entity));
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error(
|
||||
"Prefabs", false, "Entity '%s' %s cannot be activated for the following reason: %s",
|
||||
entity->GetName().c_str(), entity->GetId().ToString().c_str(), evaluation.GetError().m_message.c_str());
|
||||
context.ErrorEncountered();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error(
|
||||
"Prefabs", false, "Entity '%s' %s cannot be activated for the following reason: %s", (*it)->GetName().c_str(),
|
||||
(*it)->GetId().ToString().c_str(), evaluation.GetError().m_message.c_str());
|
||||
it = entities.erase(it);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
it = entities.erase(it);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
SpawnableUtils::SortEntitiesByTransformHierarchy(*spawnable);
|
||||
context.GetProcessedObjects().push_back(AZStd::move(object));
|
||||
}
|
||||
|
||||
+1
@@ -54,6 +54,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
{
|
||||
processor->Process(context);
|
||||
}
|
||||
context.ResolveLinks();
|
||||
}
|
||||
size_t PrefabConversionPipeline::CalculateProcessorFingerprint(AZ::SerializeContext* context)
|
||||
{
|
||||
|
||||
+128
-8
@@ -6,12 +6,26 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
|
||||
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
|
||||
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.h>
|
||||
#include <AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h>
|
||||
|
||||
namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
{
|
||||
EntityAliasSpawnableLink::EntityAliasSpawnableLink(AzFramework::Spawnable& spawnable, AZ::EntityId index)
|
||||
: m_spawnable(spawnable)
|
||||
, m_index(index)
|
||||
{
|
||||
}
|
||||
|
||||
EntityAliasPrefabLink::EntityAliasPrefabLink(AZStd::string prefabName, AzToolsFramework::Prefab::AliasPath alias)
|
||||
: m_prefabName(AZStd::move(prefabName))
|
||||
, m_alias(AZStd::move(alias))
|
||||
{
|
||||
}
|
||||
|
||||
PrefabProcessorContext::PrefabProcessorContext(const AZ::Uuid& sourceUuid)
|
||||
: m_sourceUuid(sourceUuid)
|
||||
{}
|
||||
@@ -45,7 +59,8 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
return !m_prefabs.empty();
|
||||
}
|
||||
|
||||
bool PrefabProcessorContext::RegisterSpawnableProductAssetDependency(AZStd::string prefabName, AZStd::string dependentPrefabName)
|
||||
bool PrefabProcessorContext::RegisterSpawnableProductAssetDependency(
|
||||
AZStd::string prefabName, AZStd::string dependentPrefabName, EntityAliasSpawnableLoadBehavior loadBehavior)
|
||||
{
|
||||
using ConversionUtils = PrefabConversionUtils::ProcessedObjectStore;
|
||||
|
||||
@@ -55,10 +70,11 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
dependentPrefabName += AzFramework::Spawnable::DotFileExtension;
|
||||
uint32_t spawnablePrefabSubId = ConversionUtils::BuildSubId(AZStd::move(dependentPrefabName));
|
||||
|
||||
return RegisterSpawnableProductAssetDependency(spawnableSubId, spawnablePrefabSubId);
|
||||
return RegisterSpawnableProductAssetDependency(spawnableSubId, spawnablePrefabSubId, loadBehavior);
|
||||
}
|
||||
|
||||
bool PrefabProcessorContext::RegisterSpawnableProductAssetDependency(AZStd::string prefabName, const AZ::Data::AssetId& dependentAssetId)
|
||||
bool PrefabProcessorContext::RegisterSpawnableProductAssetDependency(
|
||||
AZStd::string prefabName, const AZ::Data::AssetId& dependentAssetId, EntityAliasSpawnableLoadBehavior loadBehavior)
|
||||
{
|
||||
using ConversionUtils = PrefabConversionUtils::ProcessedObjectStore;
|
||||
|
||||
@@ -67,20 +83,78 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
|
||||
AZ::Data::AssetId spawnableAssetId(GetSourceUuid(), spawnableSubId);
|
||||
|
||||
return RegisterProductAssetDependency(spawnableAssetId, dependentAssetId);
|
||||
return RegisterProductAssetDependency(spawnableAssetId, dependentAssetId, ToAssetLoadBehavior(loadBehavior));
|
||||
}
|
||||
|
||||
bool PrefabProcessorContext::RegisterSpawnableProductAssetDependency(uint32_t spawnableAssetSubId, uint32_t dependentSpawnableAssetSubId)
|
||||
bool PrefabProcessorContext::RegisterSpawnableProductAssetDependency(
|
||||
uint32_t spawnableAssetSubId, uint32_t dependentSpawnableAssetSubId, EntityAliasSpawnableLoadBehavior loadBehavior)
|
||||
{
|
||||
AZ::Data::AssetId spawnableAssetId(GetSourceUuid(), spawnableAssetSubId);
|
||||
AZ::Data::AssetId dependentSpawnableAssetId(GetSourceUuid(), dependentSpawnableAssetSubId);
|
||||
|
||||
return RegisterProductAssetDependency(spawnableAssetId, dependentSpawnableAssetId);
|
||||
return RegisterProductAssetDependency(spawnableAssetId, dependentSpawnableAssetId, ToAssetLoadBehavior(loadBehavior));
|
||||
}
|
||||
|
||||
bool PrefabProcessorContext::RegisterProductAssetDependency(const AZ::Data::AssetId& assetId, const AZ::Data::AssetId& dependentAssetId)
|
||||
{
|
||||
return m_registeredProductAssetDependencies[assetId].emplace(dependentAssetId).second;
|
||||
return RegisterProductAssetDependency(assetId, dependentAssetId, AZ::Data::AssetLoadBehavior::NoLoad);
|
||||
}
|
||||
|
||||
bool PrefabProcessorContext::RegisterProductAssetDependency(
|
||||
const AZ::Data::AssetId& assetId, const AZ::Data::AssetId& dependentAssetId, AZ::Data::AssetLoadBehavior loadBehavior)
|
||||
{
|
||||
auto dependencies = m_registeredProductAssetDependencies.equal_range(assetId);
|
||||
if (dependencies.first != dependencies.second)
|
||||
{
|
||||
for (auto it = dependencies.first; it != dependencies.second; ++it)
|
||||
{
|
||||
if (it->second.m_assetId == dependentAssetId)
|
||||
{
|
||||
if (it->second.m_loadBehavior < loadBehavior)
|
||||
{
|
||||
it->second.m_loadBehavior = loadBehavior;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m_registeredProductAssetDependencies.emplace(assetId, AssetDependencyInfo{ dependentAssetId, loadBehavior }).second;
|
||||
}
|
||||
|
||||
void PrefabProcessorContext::RegisterSpawnableEntityAlias(EntityAliasStore link)
|
||||
{
|
||||
m_entityAliases.push_back(AZStd::move(link));
|
||||
}
|
||||
|
||||
void PrefabProcessorContext::ResolveSpawnableEntityAliases(
|
||||
AZStd::string_view prefabName, AzFramework::Spawnable& spawnable, const AzToolsFramework::Prefab::Instance& instance)
|
||||
{
|
||||
using namespace AzToolsFramework::Prefab;
|
||||
|
||||
for (EntityAliasStore& entityAlias : m_entityAliases)
|
||||
{
|
||||
auto sourcePrefab = AZStd::get_if<EntityAliasPrefabLink>(&entityAlias.m_source);
|
||||
if (sourcePrefab != nullptr && sourcePrefab->m_prefabName == prefabName)
|
||||
{
|
||||
AZ::EntityId id = instance.GetEntityIdFromAliasPath(sourcePrefab->m_alias);
|
||||
AZ_Assert(
|
||||
id.IsValid(),
|
||||
"Entity '%s' was not found in Prefab Instance created from '%s' even though it was previously found.",
|
||||
sourcePrefab->m_alias.c_str(), sourcePrefab->m_prefabName.c_str());
|
||||
entityAlias.m_source.emplace<EntityAliasSpawnableLink>(spawnable, id);
|
||||
}
|
||||
|
||||
auto targetPrefab = AZStd::get_if<EntityAliasPrefabLink>(&entityAlias.m_target);
|
||||
if (targetPrefab != nullptr && targetPrefab->m_prefabName == prefabName)
|
||||
{
|
||||
AZ::EntityId id = instance.GetEntityIdFromAliasPath(targetPrefab->m_alias);
|
||||
AZ_Assert(
|
||||
id.IsValid(), "Entity '%s' was not found in Prefab Instance created from '%s' even though it was previously found.",
|
||||
targetPrefab->m_alias.c_str(), targetPrefab->m_prefabName.c_str());
|
||||
entityAlias.m_target.emplace<EntityAliasSpawnableLink>(spawnable, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PrefabProcessorContext::ProcessedObjectStoreContainer& PrefabProcessorContext::GetProcessedObjects()
|
||||
@@ -118,6 +192,46 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
return m_sourceUuid;
|
||||
}
|
||||
|
||||
void PrefabProcessorContext::ResolveLinks()
|
||||
{
|
||||
// Store the aliases visitor here when first encountered to avoid the visitor sorting the aliases for every addition.
|
||||
// Once this map goes out of scope the visitors will be destroyed and in turn sort their aliases.
|
||||
AZStd::unordered_map<AZ::Data::AssetId, AzFramework::Spawnable::EntityAliasVisitor> aliasVisitors;
|
||||
|
||||
for (EntityAliasStore& alias : m_entityAliases)
|
||||
{
|
||||
auto source = AZStd::get_if<EntityAliasSpawnableLink>(&alias.m_source);
|
||||
AZ_Assert(source, "Entity alias found that has a source that's not yet resolved to a spawnable");
|
||||
auto target = AZStd::get_if<EntityAliasSpawnableLink>(&alias.m_target);
|
||||
AZ_Assert(target, "Entity alias found that has a target that's not yet resolved to a spawnable");
|
||||
|
||||
uint32_t sourceIndex = SpawnableUtils::FindEntityIndex(source->m_index, source->m_spawnable);
|
||||
AZ_Assert(
|
||||
sourceIndex != SpawnableUtils::InvalidEntityIndex, "Entity %zu not found in source spawnable while resolving to index.",
|
||||
aznumeric_cast<AZ::u64>(source->m_index));
|
||||
uint32_t targetIndex = SpawnableUtils::FindEntityIndex(target->m_index, target->m_spawnable);
|
||||
AZ_Assert(
|
||||
targetIndex != SpawnableUtils::InvalidEntityIndex, "Entity %zu not found in target spawnable while resolving to index.",
|
||||
aznumeric_cast<AZ::u64>(target->m_index));
|
||||
|
||||
AZ::Data::AssetLoadBehavior loadBehavior = ToAssetLoadBehavior(alias.m_loadBehavior);
|
||||
|
||||
auto it = aliasVisitors.find(source->m_spawnable.GetId());
|
||||
if (it == aliasVisitors.end())
|
||||
{
|
||||
AzFramework::Spawnable::EntityAliasVisitor visitor = source->m_spawnable.TryGetAliases();
|
||||
AZ_Assert(visitor.IsValid(), "Unable to obtain lock for a newly create spawnable.");
|
||||
it = aliasVisitors.emplace(source->m_spawnable.GetId(), AZStd::move(visitor)).first;
|
||||
}
|
||||
it->second.AddAlias(
|
||||
AZ::Data::Asset<AzFramework::Spawnable>(&target->m_spawnable, loadBehavior), alias.m_tag, sourceIndex, targetIndex,
|
||||
alias.m_aliasType, alias.m_loadBehavior == EntityAliasSpawnableLoadBehavior::QueueLoad);
|
||||
|
||||
// Register the dependency between the two spawnables.
|
||||
RegisterProductAssetDependency(source->m_spawnable.GetId(), target->m_spawnable.GetId(), loadBehavior);
|
||||
}
|
||||
}
|
||||
|
||||
bool PrefabProcessorContext::HasCompletedSuccessfully() const
|
||||
{
|
||||
return m_completedSuccessfully;
|
||||
@@ -127,4 +241,10 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
{
|
||||
m_completedSuccessfully = false;
|
||||
}
|
||||
|
||||
AZ::Data::AssetLoadBehavior PrefabProcessorContext::ToAssetLoadBehavior(EntityAliasSpawnableLoadBehavior loadBehavior) const
|
||||
{
|
||||
return loadBehavior == EntityAliasSpawnableLoadBehavior::DependentLoad ? AZ::Data::AssetLoadBehavior::PreLoad
|
||||
: AZ::Data::AssetLoadBehavior::NoLoad;
|
||||
}
|
||||
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
|
||||
+79
-5
@@ -9,24 +9,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Component/ComponentExport.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
#include <AzCore/std/containers/unordered_map.h>
|
||||
#include <AzCore/std/containers/variant.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/functional.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzCore/std/string/string_view.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/Instance.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
|
||||
#include <AzToolsFramework/Prefab/Spawnable/ProcesedObjectStore.h>
|
||||
|
||||
namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
{
|
||||
enum class EntityAliasType : uint8_t
|
||||
{
|
||||
Disable, //!< No alias is added.
|
||||
OptionalReplace, //!< At runtime the entity might be replaced. If the alias is disabled the original entity will be spawned.
|
||||
//!< The original entity will be left in the spawnable and a copy is returned.
|
||||
Replace, //!< At runtime the entity will be replaced. If the alias is disabled nothing will be spawned. The original
|
||||
//!< entity is returned and a blank entity is left.
|
||||
Additional, //!< At runtime the alias entity will be added as an additional but unrelated entity with a new entity id.
|
||||
//!< An empty entity will be returned.
|
||||
Merge //!< At runtime the components in both entities will be merged. An empty entity will be returned. The added
|
||||
//!< components may no conflict with the entities already in the root entity.
|
||||
};
|
||||
|
||||
enum class EntityAliasSpawnableLoadBehavior : uint8_t
|
||||
{
|
||||
NoLoad, //!< Don't load the spawnable referenced in the entity alias. Loading will be up to the caller.
|
||||
QueueLoad, //!< Queue the spawnable referenced in the entity alias for loading. This will be an async load because asset
|
||||
//!< handlers aren't allowed to start a blocking load as this can lead to deadlocks. This option will allow
|
||||
//!< to disable loading the referenced spawnable through the event fired from the spawnables asset handler.
|
||||
DependentLoad //!< The spawnable referenced in the entity alias is made a dependency of the spawnable that holds the entity
|
||||
//!< alias. This will cause the spawnable to be automatically loaded along with the owning spawnable.
|
||||
};
|
||||
|
||||
struct EntityAliasSpawnableLink
|
||||
{
|
||||
EntityAliasSpawnableLink(AzFramework::Spawnable& spawnable, AZ::EntityId index);
|
||||
|
||||
AzFramework::Spawnable& m_spawnable;
|
||||
AZ::EntityId m_index;
|
||||
};
|
||||
|
||||
struct EntityAliasPrefabLink
|
||||
{
|
||||
EntityAliasPrefabLink(AZStd::string prefabName, AzToolsFramework::Prefab::AliasPath alias);
|
||||
|
||||
AZStd::string m_prefabName;
|
||||
AzToolsFramework::Prefab::AliasPath m_alias;
|
||||
};
|
||||
|
||||
struct EntityAliasStore
|
||||
{
|
||||
using LinkStore = AZStd::variant<AZStd::monostate, EntityAliasSpawnableLink, EntityAliasPrefabLink>;
|
||||
|
||||
LinkStore m_source;
|
||||
LinkStore m_target;
|
||||
uint32_t m_tag;
|
||||
AzFramework::Spawnable::EntityAliasType m_aliasType;
|
||||
EntityAliasSpawnableLoadBehavior m_loadBehavior;
|
||||
};
|
||||
|
||||
struct AssetDependencyInfo
|
||||
{
|
||||
AZ::Data::AssetId m_assetId;
|
||||
AZ::Data::AssetLoadBehavior m_loadBehavior;
|
||||
};
|
||||
|
||||
class PrefabProcessorContext
|
||||
{
|
||||
public:
|
||||
using ProcessedObjectStoreContainer = AZStd::vector<ProcessedObjectStore>;
|
||||
using ProductAssetDependencyContainer =
|
||||
AZStd::unordered_map<AZ::Data::AssetId, AZStd::unordered_set<AZ::Data::AssetId>>;
|
||||
using ProductAssetDependencyContainer = AZStd::unordered_multimap<AZ::Data::AssetId, AssetDependencyInfo>;
|
||||
|
||||
AZ_CLASS_ALLOCATOR(PrefabProcessorContext, AZ::SystemAllocator, 0);
|
||||
AZ_RTTI(PrefabProcessorContext, "{C7D77E3A-C544-486B-B774-7C82C38FE22F}");
|
||||
@@ -39,11 +98,20 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
virtual void ListPrefabs(const AZStd::function<void(AZStd::string_view, const PrefabDom&)>& callback) const;
|
||||
virtual bool HasPrefabs() const;
|
||||
|
||||
virtual bool RegisterSpawnableProductAssetDependency(AZStd::string prefabName, AZStd::string dependentPrefabName);
|
||||
virtual bool RegisterSpawnableProductAssetDependency(AZStd::string prefabName, const AZ::Data::AssetId& dependentAssetId);
|
||||
virtual bool RegisterSpawnableProductAssetDependency(uint32_t spawnableAssetSubId, uint32_t dependentSpawnableAssetSubId);
|
||||
virtual bool RegisterSpawnableProductAssetDependency(
|
||||
AZStd::string prefabName, AZStd::string dependentPrefabName, EntityAliasSpawnableLoadBehavior loadBehavior);
|
||||
virtual bool RegisterSpawnableProductAssetDependency(
|
||||
AZStd::string prefabName, const AZ::Data::AssetId& dependentAssetId, EntityAliasSpawnableLoadBehavior loadBehavior);
|
||||
virtual bool RegisterSpawnableProductAssetDependency(
|
||||
uint32_t spawnableAssetSubId, uint32_t dependentSpawnableAssetSubId, EntityAliasSpawnableLoadBehavior loadBehavior);
|
||||
virtual bool RegisterProductAssetDependency(const AZ::Data::AssetId& assetId, const AZ::Data::AssetId& dependentAssetId);
|
||||
virtual bool RegisterProductAssetDependency(
|
||||
const AZ::Data::AssetId& assetId, const AZ::Data::AssetId& dependentAssetId, AZ::Data::AssetLoadBehavior loadBehavior);
|
||||
|
||||
virtual void RegisterSpawnableEntityAlias(EntityAliasStore link);
|
||||
virtual void ResolveSpawnableEntityAliases(
|
||||
AZStd::string_view prefabName, AzFramework::Spawnable& spawnable, const AzToolsFramework::Prefab::Instance& instance);
|
||||
|
||||
virtual ProcessedObjectStoreContainer& GetProcessedObjects();
|
||||
virtual const ProcessedObjectStoreContainer& GetProcessedObjects() const;
|
||||
|
||||
@@ -54,13 +122,19 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
virtual const AZ::PlatformTagSet& GetPlatformTags() const;
|
||||
virtual const AZ::Uuid& GetSourceUuid() const;
|
||||
|
||||
virtual void ResolveLinks();
|
||||
|
||||
virtual bool HasCompletedSuccessfully() const;
|
||||
virtual void ErrorEncountered();
|
||||
|
||||
protected:
|
||||
using NamedPrefabContainer = AZStd::unordered_map<AZStd::string, PrefabDom>;
|
||||
using SpawnableEntityAliasStore = AZStd::vector<EntityAliasStore>;
|
||||
|
||||
AZ::Data::AssetLoadBehavior ToAssetLoadBehavior(EntityAliasSpawnableLoadBehavior loadBehavior) const;
|
||||
|
||||
NamedPrefabContainer m_prefabs;
|
||||
SpawnableEntityAliasStore m_entityAliases;
|
||||
ProcessedObjectStoreContainer m_products;
|
||||
ProductAssetDependencyContainer m_registeredProductAssetDependencies;
|
||||
|
||||
|
||||
+16
-2
@@ -11,7 +11,16 @@
|
||||
|
||||
namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
{
|
||||
ProcessedObjectStore::ProcessedObjectStore(AZStd::string uniqueId, AZStd::unique_ptr<AZ::Data::AssetData> asset, SerializerFunction assetSerializer)
|
||||
void ProcessedObjectStore::AssetSmartPtrDeleter::operator()(AZ::Data::AssetData* asset)
|
||||
{
|
||||
if (asset->GetUseCount() == 0)
|
||||
{
|
||||
// Only delete the asset if it wasn't turned into a full asset
|
||||
delete asset;
|
||||
}
|
||||
}
|
||||
|
||||
ProcessedObjectStore::ProcessedObjectStore(AZStd::string uniqueId, AssetSmartPtr asset, SerializerFunction assetSerializer)
|
||||
: m_uniqueId(AZStd::move(uniqueId))
|
||||
, m_assetSerializer(AZStd::move(assetSerializer))
|
||||
, m_asset(AZStd::move(asset))
|
||||
@@ -62,7 +71,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
return m_referencedAssets;
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::Data::AssetData> ProcessedObjectStore::ReleaseAsset()
|
||||
auto ProcessedObjectStore::ReleaseAsset() -> AssetSmartPtr
|
||||
{
|
||||
return AZStd::move(m_asset);
|
||||
}
|
||||
@@ -72,6 +81,11 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
return AzFramework::SpawnableAssetHandler::BuildSubId(id);
|
||||
}
|
||||
|
||||
uint32_t ProcessedObjectStore::GetSubId() const
|
||||
{
|
||||
return m_asset->GetId().m_subId;
|
||||
}
|
||||
|
||||
const AZStd::string& ProcessedObjectStore::GetId() const
|
||||
{
|
||||
return m_uniqueId;
|
||||
|
||||
+11
-5
@@ -26,6 +26,12 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
public:
|
||||
using SerializerFunction = AZStd::function<bool(AZStd::vector<uint8_t>&, const ProcessedObjectStore&)>;
|
||||
|
||||
struct AssetSmartPtrDeleter
|
||||
{
|
||||
void operator()(AZ::Data::AssetData* asset);
|
||||
};
|
||||
using AssetSmartPtr = AZStd::unique_ptr<AZ::Data::AssetData, AssetSmartPtrDeleter>;
|
||||
|
||||
//! Constructs a new instance.
|
||||
//! @param uniqueId A name for the object that's unique within the scope of the Prefab. This name will be used to generate a sub id for the product
|
||||
//! which requires that the name to be stable between runs.
|
||||
@@ -37,24 +43,24 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
|
||||
bool Serialize(AZStd::vector<uint8_t>& output) const;
|
||||
static uint32_t BuildSubId(AZStd::string_view id);
|
||||
uint32_t GetSubId() const;
|
||||
|
||||
bool HasAsset() const;
|
||||
const AZ::Data::AssetType& GetAssetType() const;
|
||||
const AZ::Data::AssetData& GetAsset() const;
|
||||
AZ::Data::AssetData& GetAsset();
|
||||
AZStd::unique_ptr<AZ::Data::AssetData> ReleaseAsset();
|
||||
AssetSmartPtr ReleaseAsset();
|
||||
|
||||
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& GetReferencedAssets();
|
||||
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& GetReferencedAssets() const;
|
||||
|
||||
|
||||
const AZStd::string& GetId() const;
|
||||
|
||||
private:
|
||||
ProcessedObjectStore(AZStd::string uniqueId, AZStd::unique_ptr<AZ::Data::AssetData> asset, SerializerFunction assetSerializer);
|
||||
ProcessedObjectStore(AZStd::string uniqueId, AssetSmartPtr asset, SerializerFunction assetSerializer);
|
||||
|
||||
SerializerFunction m_assetSerializer;
|
||||
AZStd::unique_ptr<AZ::Data::AssetData> m_asset;
|
||||
AssetSmartPtr m_asset;
|
||||
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>> m_referencedAssets;
|
||||
AZStd::string m_uniqueId;
|
||||
};
|
||||
@@ -66,7 +72,7 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils
|
||||
static_assert(AZStd::is_base_of_v<AZ::Data::AssetData, T>,
|
||||
"ProcessedObjectStore can only be created from a class that derives from AZ::Data::AssetData.");
|
||||
AZ::Data::AssetId assetId(sourceId, BuildSubId(uniqueId));
|
||||
auto instance = AZStd::make_unique<T>(assetId, AZ::Data::AssetData::AssetStatus::Ready);
|
||||
auto instance = AssetSmartPtr(aznew T(assetId, AZ::Data::AssetData::AssetStatus::Ready));
|
||||
ProcessedObjectStore resultLeft(AZStd::move(uniqueId), AZStd::move(instance), AZStd::move(assetSerializer));
|
||||
T* resultRight = static_cast<T*>(&resultLeft.GetAsset());
|
||||
return AZStd::make_pair<ProcessedObjectStore, T*>(AZStd::move(resultLeft), resultRight);
|
||||
|
||||
+261
-4
@@ -8,9 +8,12 @@
|
||||
|
||||
#include <AzToolsFramework/Prefab/Spawnable/SpawnableUtils.h>
|
||||
|
||||
#include <AzCore/Casting/numeric_cast.h>
|
||||
#include <AzCore/Component/ComponentApplicationBus.h>
|
||||
#include <AzCore/Component/Entity.h>
|
||||
#include <AzCore/Component/EntityUtils.h>
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/std/algorithm.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
@@ -20,18 +23,134 @@
|
||||
|
||||
namespace AzToolsFramework::Prefab::SpawnableUtils
|
||||
{
|
||||
namespace Internal
|
||||
{
|
||||
AZ::SerializeContext* GetSerializeContext()
|
||||
{
|
||||
AZ::SerializeContext* result = nullptr;
|
||||
AZ::ComponentApplicationBus::BroadcastResult(result, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
|
||||
AZ_Assert(result, "SpawnbleUtils was unable to locate the Serialize Context.");
|
||||
return result;
|
||||
}
|
||||
|
||||
AZ::Entity* FindEntity(AZ::EntityId entityId, AzToolsFramework::Prefab::Instance& source)
|
||||
{
|
||||
AZ::Entity* result = nullptr;
|
||||
source.GetEntities(
|
||||
[&result, entityId](AZStd::unique_ptr<AZ::Entity>& entity)
|
||||
{
|
||||
if (entity->GetId() != entityId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = entity.get();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
AZ::Entity* FindEntity(AZ::EntityId entityId, AzFramework::Spawnable& source)
|
||||
{
|
||||
uint32_t index = AzToolsFramework::Prefab::SpawnableUtils::FindEntityIndex(entityId, source);
|
||||
return index != InvalidEntityIndex ? source.GetEntities()[index].get() : nullptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
AZStd::unique_ptr<AZ::Entity> CloneEntity(AZ::EntityId entityId, T& source)
|
||||
{
|
||||
AZ::Entity* target = Internal::FindEntity(entityId, source);
|
||||
AZ_Assert(
|
||||
target, "SpawnbleUtils were unable to locate entity with id %zu in Instance or Spawnable for cloning.",
|
||||
aznumeric_cast<AZ::u64>(entityId));
|
||||
auto clone = AZStd::make_unique<AZ::Entity>();
|
||||
|
||||
static AZ::SerializeContext* sc = GetSerializeContext();
|
||||
sc->CloneObjectInplace(*clone, target);
|
||||
clone->SetId(AZ::Entity::MakeId());
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::Entity> ReplaceEntityWithPlaceholder(AZ::EntityId entityId, AzToolsFramework::Prefab::Instance& source)
|
||||
{
|
||||
auto&& [instance, alias] = source.FindInstanceAndAlias(entityId);
|
||||
AZ_Assert(
|
||||
instance, "SpawnbleUtils were unable to locate entity alias with id %zu in Instance '%s' for replacing.",
|
||||
aznumeric_cast<AZ::u64>(entityId), source.GetTemplateSourcePath().c_str());
|
||||
|
||||
EntityOptionalReference entityData = instance->GetEntity(alias);
|
||||
AZ_Assert(
|
||||
entityData.has_value(), "SpawnbleUtils were unable to locate entity '%.*s' in Instance '%s' for replacing.",
|
||||
AZ_STRING_ARG(alias), source.GetTemplateSourcePath().c_str());
|
||||
auto placeholder = AZStd::make_unique<AZ::Entity>(entityData->get().GetId(), entityData->get().GetName());
|
||||
return instance->ReplaceEntity(AZStd::move(placeholder), alias);
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::Entity> ReplaceEntityWithPlaceholder(AZ::EntityId entityId, AzFramework::Spawnable& source)
|
||||
{
|
||||
uint32_t index = AzToolsFramework::Prefab::SpawnableUtils::FindEntityIndex(entityId, source);
|
||||
AZ_Assert(
|
||||
index != InvalidEntityIndex, "SpawnbleUtils were unable to locate entity alias with id %zu in Spawnable for replacing.",
|
||||
aznumeric_cast<AZ::u64>(entityId));
|
||||
|
||||
AZStd::unique_ptr<AZ::Entity> original = AZStd::move(source.GetEntities()[index]);
|
||||
AZ_Assert(
|
||||
original, "SpawnbleUtils were unable to locate entity with id %zu in Spawnable for replacing.",
|
||||
aznumeric_cast<AZ::u64>(entityId));
|
||||
|
||||
source.GetEntities()[index] = AZStd::make_unique<AZ::Entity>(original->GetId(), original->GetName());
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
template<typename Source>
|
||||
AZStd::pair<AZStd::unique_ptr<AZ::Entity>, AzFramework::Spawnable::EntityAliasType> ApplyAlias(
|
||||
Source& source, AZ::EntityId entityId, AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType)
|
||||
{
|
||||
namespace PCU = AzToolsFramework::Prefab::PrefabConversionUtils;
|
||||
using ResultPair = AZStd::pair<AZStd::unique_ptr<AZ::Entity>, AzFramework::Spawnable::EntityAliasType>;
|
||||
|
||||
switch (aliasType)
|
||||
{
|
||||
case PCU::EntityAliasType::Disable:
|
||||
// No need to do anything as the alias is disabled.
|
||||
return ResultPair(nullptr, AzFramework::Spawnable::EntityAliasType::Disable);
|
||||
case PCU::EntityAliasType::OptionalReplace:
|
||||
return ResultPair(CloneEntity(entityId, source), AzFramework::Spawnable::EntityAliasType::Replace);
|
||||
case PCU::EntityAliasType::Replace:
|
||||
return ResultPair(ReplaceEntityWithPlaceholder(entityId, source), AzFramework::Spawnable::EntityAliasType::Replace);
|
||||
case PCU::EntityAliasType::Additional:
|
||||
ResultPair(AZStd::make_unique<AZ::Entity>(AZ::Entity::MakeId()), AzFramework::Spawnable::EntityAliasType::Additional);
|
||||
case PCU::EntityAliasType::Merge:
|
||||
// Use the same entity id as the original entity so at runtime the entity ids can be verified to match.
|
||||
ResultPair(AZStd::make_unique<AZ::Entity>(entityId), AzFramework::Spawnable::EntityAliasType::Merge);
|
||||
default:
|
||||
AZ_Assert(
|
||||
false, "Invalid PrefabProcessorContext::EntityAliasType type (%i) provided.", aznumeric_cast<uint64_t>(aliasType));
|
||||
return ResultPair(nullptr, AzFramework::Spawnable::EntityAliasType::Disable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom)
|
||||
{
|
||||
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>> referencedAssets;
|
||||
return CreateSpawnable(spawnable, prefabDom, referencedAssets);
|
||||
}
|
||||
|
||||
bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom, AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets)
|
||||
bool CreateSpawnable(
|
||||
AzFramework::Spawnable& spawnable,
|
||||
const PrefabDom& prefabDom,
|
||||
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets)
|
||||
{
|
||||
Instance instance;
|
||||
if (Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(instance, prefabDom, referencedAssets,
|
||||
Prefab::PrefabDomUtils::LoadFlags::AssignRandomEntityId)) // Always assign random entity ids because the spawnable is
|
||||
// going to be used to create clones of the entities.
|
||||
if (Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(
|
||||
instance, prefabDom, referencedAssets,
|
||||
Prefab::PrefabDomUtils::LoadFlags::AssignRandomEntityId)) // Always assign random entity ids because the spawnable is
|
||||
// going to be used to create clones of the entities.
|
||||
{
|
||||
AzFramework::Spawnable::EntityList& entities = spawnable.GetEntities();
|
||||
instance.DetachAllEntitiesInHierarchy(
|
||||
@@ -47,6 +166,144 @@ namespace AzToolsFramework::Prefab::SpawnableUtils
|
||||
}
|
||||
}
|
||||
|
||||
AZ::Entity* CreateEntityAlias(
|
||||
AZStd::string sourcePrefabName,
|
||||
AzToolsFramework::Prefab::Instance& source,
|
||||
AZStd::string targetPrefabName,
|
||||
AzToolsFramework::Prefab::Instance& target,
|
||||
AZ::EntityId entityId,
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType,
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior,
|
||||
uint32_t tag,
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context)
|
||||
{
|
||||
using namespace AzToolsFramework::Prefab::PrefabConversionUtils;
|
||||
|
||||
AliasPath alias = source.GetAliasPathRelativeToInstance(entityId);
|
||||
if (!alias.empty())
|
||||
{
|
||||
auto&& [replacement, storedAliasType] = Internal::ApplyAlias(source, entityId, aliasType);
|
||||
if (replacement)
|
||||
{
|
||||
AZ::Entity* result = replacement.get();
|
||||
target.AddEntity(AZStd::move(replacement), alias.Filename().Native());
|
||||
|
||||
EntityAliasStore store;
|
||||
store.m_aliasType = storedAliasType;
|
||||
store.m_source.emplace<EntityAliasPrefabLink>(AZStd::move(sourcePrefabName), AZStd::move(alias));
|
||||
store.m_target.emplace<EntityAliasPrefabLink>(
|
||||
AZStd::move(targetPrefabName), target.GetAliasPathRelativeToInstance(result->GetId()));
|
||||
store.m_loadBehavior = loadBehavior;
|
||||
store.m_tag = tag;
|
||||
context.RegisterSpawnableEntityAlias(AZStd::move(store));
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(false, "A replacement for entity with id %zu could not be created.", static_cast<AZ::u64>(entityId));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(false, "Entity with id %zu was not found in the source prefab.", static_cast<AZ::u64>(entityId));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AZ::Entity* CreateEntityAlias(
|
||||
AZStd::string sourcePrefabName,
|
||||
AzToolsFramework::Prefab::Instance& source,
|
||||
AzFramework::Spawnable& target,
|
||||
AZ::EntityId entityId,
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType,
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior,
|
||||
uint32_t tag,
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context)
|
||||
{
|
||||
using namespace AzToolsFramework::Prefab::PrefabConversionUtils;
|
||||
|
||||
AliasPath alias = source.GetAliasPathRelativeToInstance(entityId);
|
||||
if (!alias.empty())
|
||||
{
|
||||
auto&& [replacement, storedAliasType] = Internal::ApplyAlias(source, entityId, aliasType);
|
||||
if (replacement)
|
||||
{
|
||||
AZ::Entity* result = replacement.get();
|
||||
target.GetEntities().push_back(AZStd::move(replacement));
|
||||
|
||||
EntityAliasStore store;
|
||||
store.m_aliasType = storedAliasType;
|
||||
store.m_source.emplace<EntityAliasPrefabLink>(AZStd::move(sourcePrefabName), AZStd::move(alias));
|
||||
store.m_target.emplace<EntityAliasSpawnableLink>(target, result->GetId());
|
||||
store.m_tag = tag;
|
||||
store.m_loadBehavior = loadBehavior;
|
||||
context.RegisterSpawnableEntityAlias(AZStd::move(store));
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(false, "A replacement for entity with id %zu could not be created.", static_cast<AZ::u64>(entityId));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(false, "Entity with id %llu was not found in the source prefab.", static_cast<AZ::u64>(entityId));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
AZ::Entity* CreateEntityAlias(
|
||||
AzFramework::Spawnable& source,
|
||||
AzFramework::Spawnable& target,
|
||||
AZ::EntityId entityId,
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType,
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior,
|
||||
uint32_t tag,
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context)
|
||||
{
|
||||
using namespace AzToolsFramework::Prefab::PrefabConversionUtils;
|
||||
|
||||
auto&& [replacement, storedAliasType] = Internal::ApplyAlias(source, entityId, aliasType);
|
||||
if (replacement)
|
||||
{
|
||||
AZ::Entity* result = replacement.get();
|
||||
target.GetEntities().push_back(AZStd::move(replacement));
|
||||
|
||||
EntityAliasStore store;
|
||||
store.m_aliasType = storedAliasType;
|
||||
store.m_source.emplace<EntityAliasSpawnableLink>(source, entityId);
|
||||
store.m_target.emplace<EntityAliasSpawnableLink>(target, result->GetId());
|
||||
store.m_tag = tag;
|
||||
store.m_loadBehavior = loadBehavior;
|
||||
context.RegisterSpawnableEntityAlias(AZStd::move(store));
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Assert(false, "A replacement for entity with id %zu could not be created.", static_cast<AZ::u64>(entityId));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t FindEntityIndex(AZ::EntityId entity, const AzFramework::Spawnable& spawnable)
|
||||
{
|
||||
auto begin = spawnable.GetEntities().begin();
|
||||
auto end = spawnable.GetEntities().end();
|
||||
for(auto it = begin; it != end; ++it)
|
||||
{
|
||||
if ((*it)->GetId() == entity)
|
||||
{
|
||||
return aznumeric_caster(AZStd::distance(begin, it));
|
||||
}
|
||||
}
|
||||
return InvalidEntityIndex;
|
||||
}
|
||||
|
||||
template<typename EntityPtr>
|
||||
void OrganizeEntitiesForSorting(
|
||||
AZStd::vector<EntityPtr>& entities,
|
||||
|
||||
@@ -8,14 +8,59 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Component/EntityId.h>
|
||||
#include <AzCore/std/limits.h>
|
||||
#include <AzFramework/Spawnable/Spawnable.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabDomTypes.h>
|
||||
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
class Entity;
|
||||
}
|
||||
|
||||
namespace AzToolsFramework::Prefab
|
||||
{
|
||||
class Instance;
|
||||
}
|
||||
|
||||
namespace AzToolsFramework::Prefab::SpawnableUtils
|
||||
{
|
||||
static constexpr uint32_t InvalidEntityIndex = AZStd::numeric_limits<uint32_t>::max();
|
||||
|
||||
bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom);
|
||||
bool CreateSpawnable(AzFramework::Spawnable& spawnable, const PrefabDom& prefabDom, AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets);
|
||||
|
||||
AZ::Entity* CreateEntityAlias(
|
||||
AZStd::string sourcePrefabName,
|
||||
AzToolsFramework::Prefab::Instance& source,
|
||||
AZStd::string targetPrefabName,
|
||||
AzToolsFramework::Prefab::Instance& target,
|
||||
AZ::EntityId entityId,
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType,
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior,
|
||||
uint32_t tag,
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context);
|
||||
AZ::Entity* CreateEntityAlias(
|
||||
AZStd::string sourcePrefabName,
|
||||
AzToolsFramework::Prefab::Instance& source,
|
||||
AzFramework::Spawnable& target,
|
||||
AZ::EntityId entityId,
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType,
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior,
|
||||
uint32_t tag,
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context);
|
||||
AZ::Entity* CreateEntityAlias(
|
||||
AzFramework::Spawnable& source,
|
||||
AzFramework::Spawnable& target,
|
||||
AZ::EntityId entityId,
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasType aliasType,
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::EntityAliasSpawnableLoadBehavior loadBehavior,
|
||||
uint32_t tag,
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext& context);
|
||||
|
||||
uint32_t FindEntityIndex(AZ::EntityId entity, const AzFramework::Spawnable& spawnable);
|
||||
|
||||
void SortEntitiesByTransformHierarchy(AzFramework::Spawnable& spawnable);
|
||||
|
||||
template <typename EntityPtr>
|
||||
|
||||
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
* 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 <AzTest/AzTest.h>
|
||||
|
||||
#include <AzToolsFramework/AssetBrowser/AssetBrowserComponent.h>
|
||||
#include <AzToolsFramework/AssetBrowser/AssetBrowserFilterModel.h>
|
||||
#include <AzToolsFramework/AssetBrowser/AssetBrowserModel.h>
|
||||
#include <AzToolsFramework/AssetBrowser/AssetBrowserTableModel.h>
|
||||
#include <AzToolsFramework/AssetBrowser/Entries/AssetBrowserEntry.h>
|
||||
#include <AzToolsFramework/AssetBrowser/Entries/FolderAssetBrowserEntry.h>
|
||||
#include <AzToolsFramework/AssetBrowser/Entries/ProductAssetBrowserEntry.h>
|
||||
#include <AzToolsFramework/AssetBrowser/Entries/RootAssetBrowserEntry.h>
|
||||
#include <AzToolsFramework/AssetBrowser/Entries/SourceAssetBrowserEntry.h>
|
||||
#include <AzToolsFramework/AssetBrowser/Search/SearchWidget.h>
|
||||
#include <AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
|
||||
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
|
||||
#include <QAbstractItemModelTester>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
// Test fixture for the AssetBrowser 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 AssetBrowserTest
|
||||
: public ToolsApplicationFixture
|
||||
, public testing::WithParamInterface<const char*>
|
||||
{
|
||||
protected:
|
||||
enum class AssetEntryType
|
||||
{
|
||||
Root,
|
||||
Folder,
|
||||
Source,
|
||||
Product
|
||||
};
|
||||
|
||||
enum class FolderType
|
||||
{
|
||||
Root,
|
||||
File
|
||||
};
|
||||
|
||||
void SetUpEditorFixtureImpl() override;
|
||||
void TearDownEditorFixtureImpl() override;
|
||||
|
||||
//! Creates a Mock Scan Folder
|
||||
void AddScanFolder(AZ::s64 folderID, AZStd::string folderPath, AZStd::string displayName, FolderType folderType = FolderType::File);
|
||||
|
||||
//! Creates a Source entry from a mock file
|
||||
AZ::Uuid CreateSourceEntry(
|
||||
AZ::s64 fileID, AZ::s64 parentFolderID, AZStd::string filename, AssetEntryType sourceType = AssetEntryType::Source);
|
||||
|
||||
//! Creates a product from a given sourceEntry
|
||||
void CreateProduct(AZ::s64 productID, AZ::Uuid sourceUuid, AZStd::string productName);
|
||||
|
||||
void SetupAssetBrowser();
|
||||
void PrintModel(const QAbstractItemModel* model, AZStd::function<void(const QString&)> printer);
|
||||
QModelIndex GetModelIndex(const QAbstractItemModel* model, int targetDepth, int row = 0);
|
||||
AZStd::shared_ptr<AzToolsFramework::AssetBrowser::RootAssetBrowserEntry> GetRootEntry();
|
||||
AZStd::vector<QString> GetVectorFromFormattedString(const QString& formattedString);
|
||||
|
||||
protected:
|
||||
QString m_assetBrowserHierarchy = QString();
|
||||
|
||||
AZStd::unique_ptr<AzToolsFramework::AssetBrowser::SearchWidget> m_searchWidget;
|
||||
AZStd::unique_ptr<AzToolsFramework::AssetBrowser::AssetBrowserComponent> m_assetBrowserComponent;
|
||||
|
||||
AZStd::unique_ptr<AzToolsFramework::AssetBrowser::AssetBrowserFilterModel> m_filterModel;
|
||||
AZStd::unique_ptr<AzToolsFramework::AssetBrowser::AssetBrowserTableModel> m_tableModel;
|
||||
|
||||
AZStd::unique_ptr<QAbstractItemModelTester> m_modelTesterAssetBrowser;
|
||||
AZStd::unique_ptr<QAbstractItemModelTester> m_modelTesterFilterModel;
|
||||
AZStd::unique_ptr<QAbstractItemModelTester> m_modelTesterTableModel;
|
||||
|
||||
QVector<int> m_folderIds = { 13, 14, 15 };
|
||||
QVector<int> m_sourceIDs = { 1, 2, 3, 4, 5 };
|
||||
QVector<int> m_productIDs = { 1, 2, 3, 4, 5 };
|
||||
};
|
||||
|
||||
void AssetBrowserTest::SetUpEditorFixtureImpl()
|
||||
{
|
||||
GetApplication()->RegisterComponentDescriptor(AzToolsFramework::EditorEntityContextComponent::CreateDescriptor());
|
||||
|
||||
m_assetBrowserComponent = AZStd::make_unique<AzToolsFramework::AssetBrowser::AssetBrowserComponent>();
|
||||
m_assetBrowserComponent->Activate();
|
||||
|
||||
m_filterModel = AZStd::make_unique<AzToolsFramework::AssetBrowser::AssetBrowserFilterModel>();
|
||||
m_tableModel = AZStd::make_unique<AzToolsFramework::AssetBrowser::AssetBrowserTableModel>();
|
||||
|
||||
m_filterModel->setSourceModel(m_assetBrowserComponent->GetAssetBrowserModel());
|
||||
m_tableModel->setSourceModel(m_filterModel.get());
|
||||
|
||||
m_modelTesterAssetBrowser = AZStd::make_unique<QAbstractItemModelTester>(m_assetBrowserComponent->GetAssetBrowserModel());
|
||||
m_modelTesterFilterModel = AZStd::make_unique<QAbstractItemModelTester>(m_filterModel.get());
|
||||
m_modelTesterTableModel = AZStd::make_unique<QAbstractItemModelTester>(m_tableModel.get());
|
||||
m_searchWidget = AZStd::make_unique<AzToolsFramework::AssetBrowser::SearchWidget>();
|
||||
|
||||
// Setup String filters
|
||||
m_searchWidget->Setup(true, true);
|
||||
m_filterModel->SetFilter(m_searchWidget->GetFilter());
|
||||
|
||||
SetupAssetBrowser();
|
||||
}
|
||||
|
||||
void AssetBrowserTest::TearDownEditorFixtureImpl()
|
||||
{
|
||||
m_modelTesterAssetBrowser.reset();
|
||||
m_modelTesterFilterModel.reset();
|
||||
m_modelTesterTableModel.reset();
|
||||
|
||||
m_tableModel.reset();
|
||||
m_filterModel.reset();
|
||||
m_assetBrowserComponent->Deactivate();
|
||||
|
||||
m_assetBrowserComponent.reset();
|
||||
m_searchWidget.reset();
|
||||
}
|
||||
|
||||
void AssetBrowserTest::AddScanFolder(
|
||||
AZ::s64 folderID, AZStd::string folderPath, AZStd::string displayName, FolderType folderType /*= FolderType::File*/)
|
||||
{
|
||||
AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry scanFolder = AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry();
|
||||
scanFolder.m_scanFolderID = folderID;
|
||||
scanFolder.m_scanFolder = folderPath;
|
||||
scanFolder.m_displayName = displayName;
|
||||
scanFolder.m_isRoot = folderType == FolderType::Root;
|
||||
GetRootEntry()->AddScanFolder(scanFolder);
|
||||
}
|
||||
|
||||
AZ::Uuid AssetBrowserTest::CreateSourceEntry(
|
||||
AZ::s64 fileID, AZ::s64 parentFolderID, AZStd::string filename, AssetEntryType sourceType /*= AssetEntryType::Source*/)
|
||||
{
|
||||
AzToolsFramework::AssetDatabase::FileDatabaseEntry entry = AzToolsFramework::AssetDatabase::FileDatabaseEntry();
|
||||
entry.m_scanFolderPK = parentFolderID;
|
||||
entry.m_fileID = fileID;
|
||||
entry.m_fileName = filename;
|
||||
entry.m_isFolder = sourceType == AssetEntryType::Folder;
|
||||
GetRootEntry()->AddFile(entry);
|
||||
|
||||
if (!entry.m_isFolder)
|
||||
{
|
||||
AzToolsFramework::AssetBrowser::SourceWithFileID entrySource = AzToolsFramework::AssetBrowser::SourceWithFileID();
|
||||
entrySource.first = entry.m_fileID;
|
||||
entrySource.second = AzToolsFramework::AssetDatabase::SourceDatabaseEntry();
|
||||
entrySource.second.m_scanFolderPK = parentFolderID;
|
||||
entrySource.second.m_sourceName = filename;
|
||||
entrySource.second.m_sourceID = fileID;
|
||||
entrySource.second.m_sourceGuid = AZ::Uuid::CreateRandom();
|
||||
|
||||
GetRootEntry()->AddSource(entrySource);
|
||||
|
||||
return entrySource.second.m_sourceGuid;
|
||||
}
|
||||
|
||||
return AZ::Uuid::CreateNull();
|
||||
}
|
||||
|
||||
void AssetBrowserTest::CreateProduct(AZ::s64 productID, AZ::Uuid sourceUuid, AZStd::string productName)
|
||||
{
|
||||
AzToolsFramework::AssetBrowser::ProductWithUuid product = AzToolsFramework::AssetBrowser::ProductWithUuid();
|
||||
product.first = sourceUuid;
|
||||
product.second = AzToolsFramework::AssetDatabase::ProductDatabaseEntry();
|
||||
product.second.m_productID = productID;
|
||||
|
||||
product.second.m_subID = aznumeric_cast<AZ::u32>(productID);
|
||||
product.second.m_productName = productName;
|
||||
|
||||
GetRootEntry()->AddProduct(product);
|
||||
}
|
||||
|
||||
void AssetBrowserTest::SetupAssetBrowser()
|
||||
{
|
||||
// RootEntries : 1 | Folders : 4 | SourceEntries : 5 | ProductEntries : 9
|
||||
m_assetBrowserHierarchy = R"(
|
||||
D:
|
||||
\
|
||||
dev
|
||||
o3de
|
||||
GameProject
|
||||
Assets
|
||||
Source_1
|
||||
Product_1_1
|
||||
Product_1_0
|
||||
Source_0
|
||||
Product_0_3
|
||||
Product_0_2
|
||||
Product_0_1
|
||||
Product_0_0
|
||||
Scripts
|
||||
Source_3
|
||||
Source_2
|
||||
Product_2_2
|
||||
Product_2_1
|
||||
Product_2_0
|
||||
Misc
|
||||
Source_4
|
||||
Product_4_2
|
||||
Product_4_1
|
||||
Product_4_0 )";
|
||||
|
||||
namespace AzAssetBrowser = AzToolsFramework::AssetBrowser;
|
||||
|
||||
AddScanFolder(m_folderIds.at(2), "D:/dev/o3de/GameProject/Misc", "Misc");
|
||||
AZ::Uuid sourceUuid_4 = CreateSourceEntry(m_sourceIDs.at(4), m_folderIds.at(2), "Source_4");
|
||||
CreateProduct(m_productIDs.at(0), sourceUuid_4, "Product_4_0");
|
||||
CreateProduct(m_productIDs.at(1), sourceUuid_4, "Product_4_1");
|
||||
CreateProduct(m_productIDs.at(2), sourceUuid_4, "Product_4_2");
|
||||
|
||||
AddScanFolder(m_folderIds.at(1), "D:/dev/o3de/GameProject/Scripts", "Scripts");
|
||||
|
||||
AZ::Uuid sourceUuid_2 = CreateSourceEntry(m_sourceIDs.at(2), m_folderIds.at(1), "Source_2");
|
||||
CreateProduct(m_productIDs.at(0), sourceUuid_2, "Product_2_0");
|
||||
CreateProduct(m_productIDs.at(1), sourceUuid_2, "Product_2_1");
|
||||
CreateProduct(m_productIDs.at(2), sourceUuid_2, "Product_2_2");
|
||||
|
||||
CreateSourceEntry(m_sourceIDs.at(3), m_folderIds.at(1), "Source_3");
|
||||
|
||||
AddScanFolder(m_folderIds.at(0), "D:/dev/o3de/GameProject/Assets", "Assets");
|
||||
|
||||
AZ::Uuid sourceUuid_0 = CreateSourceEntry(m_sourceIDs.at(0), m_folderIds.at(0), "Source_0");
|
||||
CreateProduct(m_productIDs.at(0), sourceUuid_0, "Product_0_0");
|
||||
CreateProduct(m_productIDs.at(1), sourceUuid_0, "Product_0_1");
|
||||
CreateProduct(m_productIDs.at(2), sourceUuid_0, "Product_0_2");
|
||||
CreateProduct(m_productIDs.at(3), sourceUuid_0, "Product_0_3");
|
||||
|
||||
AZ::Uuid sourceUuid_1 = CreateSourceEntry(m_sourceIDs.at(1), m_folderIds.at(0), "Source_1");
|
||||
CreateProduct(m_productIDs.at(0), sourceUuid_1, "Product_1_0");
|
||||
CreateProduct(m_productIDs.at(1), sourceUuid_1, "Product_1_1");
|
||||
}
|
||||
|
||||
void AssetBrowserTest::PrintModel(const QAbstractItemModel* model, AZStd::function<void(const QString&)> printer)
|
||||
{
|
||||
AZStd::deque<AZStd::pair<QModelIndex, int>> indices;
|
||||
indices.push_back({ 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 += " ";
|
||||
}
|
||||
const QString message = indentString + index.data(Qt::DisplayRole).toString();
|
||||
printer(message);
|
||||
|
||||
for (int i = 0; i < model->rowCount(index); ++i)
|
||||
{
|
||||
indices.emplace_front(model->index(i, 0, index), depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndex AssetBrowserTest::GetModelIndex(const QAbstractItemModel* model, int targetDepth, int row)
|
||||
{
|
||||
AZStd::deque<AZStd::pair<QModelIndex, int>> indices;
|
||||
indices.push_back({ model->index(0, 0), 0 });
|
||||
while (!indices.empty())
|
||||
{
|
||||
auto [index, depth] = indices.front();
|
||||
indices.pop_front();
|
||||
|
||||
for (int i = 0; i < model->rowCount(index); ++i)
|
||||
{
|
||||
if (depth + 1 == targetDepth && row == i)
|
||||
{
|
||||
return model->index(i, 0, index);
|
||||
}
|
||||
indices.emplace_front(model->index(i, 0, index), depth + 1);
|
||||
}
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
AZStd::shared_ptr<AzToolsFramework::AssetBrowser::RootAssetBrowserEntry> AssetBrowserTest::GetRootEntry()
|
||||
{
|
||||
return m_assetBrowserComponent->GetAssetBrowserModel()->GetRootEntry();
|
||||
}
|
||||
|
||||
AZStd::vector<QString> AssetBrowserTest::GetVectorFromFormattedString(const QString& formattedString)
|
||||
{
|
||||
AZStd::vector<QString> hierarchySections;
|
||||
QStringList splittedList = formattedString.split('\n', Qt::SkipEmptyParts);
|
||||
|
||||
for (auto& str : splittedList)
|
||||
{
|
||||
str.replace(" ", "");
|
||||
hierarchySections.push_back(str);
|
||||
}
|
||||
return hierarchySections;
|
||||
}
|
||||
|
||||
TEST_F(AssetBrowserTest, CheckCorrectNumberOfEntriesInTableView)
|
||||
{
|
||||
m_filterModel->FilterUpdatedSlotImmediate();
|
||||
const int tableViewRowcount = m_tableModel->rowCount();
|
||||
|
||||
// RowCount should be 17 -> 5 SourceEntries + 12 ProductEntries)
|
||||
EXPECT_EQ(tableViewRowcount, 17);
|
||||
}
|
||||
|
||||
TEST_F(AssetBrowserTest, CheckCorrectNumberOfEntriesInTableViewAfterStringFilter)
|
||||
{
|
||||
/*
|
||||
*-Source_1
|
||||
* |
|
||||
* |-product_1_0
|
||||
* |-product_1_1
|
||||
*
|
||||
*
|
||||
* Matching entries = 3
|
||||
*/
|
||||
|
||||
// Apply string filter
|
||||
m_searchWidget->SetTextFilter(QString("source_1"));
|
||||
m_filterModel->FilterUpdatedSlotImmediate();
|
||||
|
||||
const int tableViewRowcount = m_tableModel->rowCount();
|
||||
EXPECT_EQ(tableViewRowcount, 3);
|
||||
}
|
||||
|
||||
TEST_F(AssetBrowserTest, CheckScanFolderAddition)
|
||||
{
|
||||
EXPECT_EQ(m_assetBrowserComponent->GetAssetBrowserModel()->rowCount(), 1);
|
||||
const int newFolderId = 20;
|
||||
AddScanFolder(newFolderId, "E:/TestFolder/TestFolder2", "TestFolder");
|
||||
|
||||
// Since the folder is empty it shouldn't be added to the model.
|
||||
EXPECT_EQ(m_assetBrowserComponent->GetAssetBrowserModel()->rowCount(), 1);
|
||||
|
||||
CreateSourceEntry(123, newFolderId, "DummyFile");
|
||||
|
||||
// When we add a file to the folder it should be added to the model
|
||||
EXPECT_EQ(m_assetBrowserComponent->GetAssetBrowserModel()->rowCount(), 2);
|
||||
}
|
||||
|
||||
} // namespace UnitTest
|
||||
@@ -82,8 +82,8 @@ namespace UnitTest
|
||||
}
|
||||
|
||||
auto transform = aznew AzToolsFramework::Components::TransformComponent;
|
||||
transform->SetParent(parentId);
|
||||
entity->AddComponent(transform);
|
||||
transform->SetParent(parentId);
|
||||
|
||||
entity->Activate();
|
||||
|
||||
|
||||
@@ -126,6 +126,7 @@ set(FILES
|
||||
UI/EntityIdQLineEditTests.cpp
|
||||
UI/EntityOutlinerTests.cpp
|
||||
UI/EntityPropertyEditorTests.cpp
|
||||
UI/AssetBrowserTests.cpp
|
||||
UndoStack.cpp
|
||||
Viewport/ClusterTests.cpp
|
||||
Viewport/ViewportEditorModeTests.cpp
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
|
||||
#include <AzGameFramework/Application/GameApplication.h>
|
||||
|
||||
#include <CryLibrary.h>
|
||||
#include <ISystem.h>
|
||||
#include <ITimer.h>
|
||||
#include <LegacyAllocator.h>
|
||||
@@ -80,146 +79,6 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
#if AZ_TRAIT_LAUNCHER_USE_CRY_DYNAMIC_MODULE_HANDLE
|
||||
// mimics AZ::DynamicModuleHandle but uses CryLibrary under the hood,
|
||||
// which is necessary to properly load legacy Cry libraries on some platforms
|
||||
class DynamicModuleHandle
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(DynamicModuleHandle, AZ::OSAllocator, 0)
|
||||
|
||||
static AZStd::unique_ptr<DynamicModuleHandle> Create(const char* fullFileName)
|
||||
{
|
||||
return AZStd::unique_ptr<DynamicModuleHandle>(aznew DynamicModuleHandle(fullFileName));
|
||||
}
|
||||
|
||||
DynamicModuleHandle(const DynamicModuleHandle&) = delete;
|
||||
DynamicModuleHandle& operator=(const DynamicModuleHandle&) = delete;
|
||||
|
||||
~DynamicModuleHandle()
|
||||
{
|
||||
Unload();
|
||||
}
|
||||
|
||||
// argument is strictly to match the API of AZ::DynamicModuleHandle
|
||||
bool Load(bool unused)
|
||||
{
|
||||
AZ_UNUSED(unused);
|
||||
|
||||
if (IsLoaded())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
m_moduleHandle = CryLoadLibrary(m_fileName.c_str());
|
||||
return IsLoaded();
|
||||
}
|
||||
|
||||
bool Unload()
|
||||
{
|
||||
if (!IsLoaded())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return CryFreeLibrary(m_moduleHandle);
|
||||
}
|
||||
|
||||
bool IsLoaded() const
|
||||
{
|
||||
return m_moduleHandle != nullptr;
|
||||
}
|
||||
|
||||
const AZ::OSString& GetFilename() const
|
||||
{
|
||||
return m_fileName;
|
||||
}
|
||||
|
||||
template<typename Function>
|
||||
Function GetFunction(const char* functionName) const
|
||||
{
|
||||
if (IsLoaded())
|
||||
{
|
||||
return reinterpret_cast<Function>(CryGetProcAddress(m_moduleHandle, functionName));
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
DynamicModuleHandle(const char* fileFullName)
|
||||
: m_fileName()
|
||||
, m_moduleHandle(nullptr)
|
||||
{
|
||||
m_fileName = AZ::OSString::format("%s%s%s",
|
||||
CrySharedLibraryPrefix, fileFullName, CrySharedLibraryExtension);
|
||||
}
|
||||
|
||||
AZ::OSString m_fileName;
|
||||
HMODULE m_moduleHandle;
|
||||
};
|
||||
#else
|
||||
// mimics AZ::DynamicModuleHandle but also calls InjectEnvironmentFunction on
|
||||
// the loaded module which is necessary to properly load legacy Cry libraries
|
||||
class DynamicModuleHandle
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(DynamicModuleHandle, AZ::OSAllocator, 0);
|
||||
|
||||
static AZStd::unique_ptr<DynamicModuleHandle> Create(const char* fullFileName)
|
||||
{
|
||||
return AZStd::unique_ptr<DynamicModuleHandle>(aznew DynamicModuleHandle(fullFileName));
|
||||
}
|
||||
|
||||
bool Load(bool isInitializeFunctionRequired)
|
||||
{
|
||||
const bool loaded = m_moduleHandle->Load(isInitializeFunctionRequired);
|
||||
if (loaded)
|
||||
{
|
||||
// We need to inject the environment first thing so that allocators are available immediately
|
||||
InjectEnvironmentFunction injectEnv = GetFunction<InjectEnvironmentFunction>(INJECT_ENVIRONMENT_FUNCTION);
|
||||
if (injectEnv)
|
||||
{
|
||||
auto env = AZ::Environment::GetInstance();
|
||||
injectEnv(env);
|
||||
}
|
||||
}
|
||||
return loaded;
|
||||
}
|
||||
|
||||
bool Unload()
|
||||
{
|
||||
bool unloaded = m_moduleHandle->Unload();
|
||||
if (unloaded)
|
||||
{
|
||||
DetachEnvironmentFunction detachEnv = GetFunction<DetachEnvironmentFunction>(DETACH_ENVIRONMENT_FUNCTION);
|
||||
if (detachEnv)
|
||||
{
|
||||
detachEnv();
|
||||
}
|
||||
}
|
||||
return unloaded;
|
||||
}
|
||||
|
||||
template<typename Function>
|
||||
Function GetFunction(const char* functionName) const
|
||||
{
|
||||
return m_moduleHandle->GetFunction<Function>(functionName);
|
||||
}
|
||||
|
||||
private:
|
||||
DynamicModuleHandle(const char* fileFullName)
|
||||
: m_moduleHandle(AZ::DynamicModuleHandle::Create(fileFullName))
|
||||
{
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<AZ::DynamicModuleHandle> m_moduleHandle;
|
||||
};
|
||||
#endif // AZ_TRAIT_LAUNCHER_USE_CRY_DYNAMIC_MODULE_HANDLE
|
||||
|
||||
void RunMainLoop(AzGameFramework::GameApplication& gameApplication)
|
||||
{
|
||||
// Ideally we'd just call GameApplication::RunMainLoop instead, but
|
||||
@@ -649,13 +508,13 @@ namespace O3DELauncher
|
||||
|
||||
// Create CrySystem.
|
||||
#if !defined(AZ_MONOLITHIC_BUILD)
|
||||
AZStd::unique_ptr<DynamicModuleHandle> crySystemLibrary;
|
||||
PFNCREATESYSTEMINTERFACE CreateSystemInterface = nullptr;
|
||||
|
||||
crySystemLibrary = DynamicModuleHandle::Create("CrySystem");
|
||||
if (crySystemLibrary->Load(false))
|
||||
constexpr const char* crySystemLibraryName = AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX "CrySystem" AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION;
|
||||
AZStd::unique_ptr<AZ::DynamicModuleHandle> crySystemLibrary = AZ::DynamicModuleHandle::Create(crySystemLibraryName);
|
||||
if (crySystemLibrary->Load(true))
|
||||
{
|
||||
CreateSystemInterface = crySystemLibrary->GetFunction<PFNCREATESYSTEMINTERFACE>("CreateSystemInterface");
|
||||
PFNCREATESYSTEMINTERFACE CreateSystemInterface =
|
||||
crySystemLibrary->GetFunction<PFNCREATESYSTEMINTERFACE>("CreateSystemInterface");
|
||||
|
||||
if (CreateSystemInterface)
|
||||
{
|
||||
systemInitParams.pSystem = CreateSystemInterface(systemInitParams);
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
#include <AzCore/IO/SystemFile.h> // for AZ_MAX_PATH_LEN
|
||||
#include <AzCore/Math/Vector2.h>
|
||||
|
||||
#include <CryLibrary.h>
|
||||
|
||||
#include <execinfo.h>
|
||||
#include <libgen.h>
|
||||
#include <netdb.h>
|
||||
@@ -86,17 +84,6 @@ int main(int argc, char** argv)
|
||||
|
||||
using namespace O3DELauncher;
|
||||
|
||||
#if !defined(AZ_MONOLITHIC_BUILD)
|
||||
char exePath[AZ_MAX_PATH_LEN] = { 0 };
|
||||
if (readlink("/proc/self/exe", exePath, AZ_MAX_PATH_LEN) == -1)
|
||||
{
|
||||
return static_cast<int>(ReturnCode::ErrExePath);
|
||||
}
|
||||
|
||||
char* runDir = dirname(exePath);
|
||||
SetModulePath(runDir);
|
||||
#endif // !defined(AZ_MONOLITHIC_BUILD)
|
||||
|
||||
PlatformMainInfo mainInfo;
|
||||
|
||||
mainInfo.m_updateResourceLimits = IncreaseResourceLimits;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user