Convert prefab to temp spawnable assets (#6179)

* Convert prefab to temp in-memory spawnable assets

Signed-off-by: chiyenteng <82238204+chiyenteng@users.noreply.github.com>
This commit is contained in:
chiyenteng
2021-12-10 10:55:01 -08:00
committed by GitHub
parent 3a4277395b
commit ca49eedc5f
55 changed files with 757 additions and 301 deletions
@@ -58,7 +58,10 @@ def launch_and_validate_results(request, test_directory, editor, editor_script,
if null_renderer:
editor.args.extend(["-rhi=Null"])
if enable_prefab_system:
editor.args.extend(["--regset=/Amazon/Preferences/EnablePrefabSystem=true"])
from os import path
editor.args.extend([
"--regset=/Amazon/Preferences/EnablePrefabSystem=true",
f"--regset-file={os.path.join(workspace.paths.engine_root(), 'Registry', 'prefab.test.setreg')}"])
else:
editor.args.extend(["--regset=/Amazon/Preferences/EnablePrefabSystem=false"])
@@ -39,7 +39,6 @@ def get_prefab_file_path(prefab_path):
prefab_path = name + ".prefab"
return prefab_path
def get_all_entity_ids():
return entity.SearchBus(bus.Broadcast, 'SearchEntities', entity.SearchFilter())
@@ -12,6 +12,8 @@ import sys
import pytest
import time
from os import path
import ly_test_tools.environment.file_system as file_system
import ly_test_tools.environment.process_utils as process_utils
import ly_test_tools.environment.waiter as waiter
@@ -98,7 +100,9 @@ class TestAutomationBase:
if autotest_mode:
pycmd += ["-autotest_mode"]
if enable_prefab_system:
pycmd += ["--regset=/Amazon/Preferences/EnablePrefabSystem=true"]
pycmd += [
"--regset=/Amazon/Preferences/EnablePrefabSystem=true",
f"--regset-file={path.join(workspace.paths.engine_root(), 'Registry', 'prefab.test.setreg')}"]
else:
pycmd += ["--regset=/Amazon/Preferences/EnablePrefabSystem=false"]
@@ -51,19 +51,22 @@ def AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude():
import os
import azlmbr.asset as asset
import azlmbr.editor as editor
import azlmbr.legacy.general as general
import azlmbr.bus as bus
import azlmbr.math as math
import azlmbr.prefab as prefab
import editor_python_test_tools.hydra_editor_utils as hydra
from editor_python_test_tools.prefab_utils import Prefab
from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import TestHelper as helper
# 1) Open an existing simple level
helper.init_idle()
helper.open_level("Physics", "Base")
helper.open_level("", "Base")
# Set view of planting area for visual debugging
general.set_current_view_position(512.0, 500.0, 38.0)
@@ -71,8 +74,11 @@ def AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude():
# 2) Create a new entity with required vegetation area components
center_point = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 32.0, 32.0, 32.0, asset_path)
flower_asset_path = os.path.join("assets", "objects", "foliage", "grass_flower_pink.azmodel")
flower_prefab = dynveg.create_temp_mesh_prefab(flower_asset_path, "PinkFlower")[0]
spawner_entity = dynveg.create_prefab_vegetation_area("Instance Spawner", center_point, 32.0, 32.0, 32.0, flower_prefab)
# Add a Vegetation Altitude Filter
spawner_entity.add_component("Vegetation Altitude Filter")
@@ -32,7 +32,9 @@ def AltitudeFilter_FilterStageToggle():
import os
import azlmbr.legacy.general as general
import azlmbr.bus as bus
import azlmbr.math as math
import azlmbr.prefab as prefab
import editor_python_test_tools.hydra_editor_utils as hydra
from largeworlds.large_worlds_utils import editor_dynveg_test_helper as dynveg
@@ -44,13 +46,16 @@ def AltitudeFilter_FilterStageToggle():
# Open an existing simple level
helper.init_idle()
helper.open_level("Physics", "Base")
helper.open_level("", "Base")
general.set_current_view_position(512.0, 480.0, 38.0)
# Create basic vegetation entity
position = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
vegetation = dynveg.create_vegetation_area("vegetation", position, 16.0, 16.0, 16.0, asset_path)
flower_asset_path = os.path.join("assets", "objects", "foliage", "grass_flower_pink.azmodel")
flower_prefab = dynveg.create_temp_mesh_prefab(flower_asset_path, "PinkFlower")[0]
vegetation = dynveg.create_prefab_vegetation_area("vegetation", position, 16.0, 16.0, 16.0, flower_prefab)
# Add a Vegetation Altitude Filter to the vegetation area entity
vegetation.add_component("Vegetation Altitude Filter")
@@ -57,7 +57,7 @@ def AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude():
# 1) Open an existing simple level
helper.init_idle()
helper.open_level("Physics", "Base")
helper.open_level("", "Base")
# Set view of planting area for visual debugging
general.set_current_view_position(512.0, 500.0, 38.0)
@@ -65,8 +65,11 @@ def AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude():
# 2) Create a new entity with required vegetation area components
center_point = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 16.0, asset_path)
flower_asset_path = os.path.join("assets", "objects", "foliage", "grass_flower_pink.azmodel")
flower_prefab = dynveg.create_temp_mesh_prefab(flower_asset_path, "PinkFlower")[0]
spawner_entity = dynveg.create_prefab_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 16.0, flower_prefab)
# Add a Vegetation Altitude Filter
spawner_entity.add_component("Vegetation Altitude Filter")
@@ -111,7 +111,7 @@ def AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea():
# 4) Create a spawner using a Vegetation Asset List Combiner component and a Weight Selector, and disallow
# spawning empty assets
spawner_entity = dynveg.create_vegetation_area("Spawner Entity", center_point, 16.0, 16.0, 16.0, None)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Spawner Entity", center_point, 16.0, 16.0, 16.0, None)
spawner_entity.remove_component("Vegetation Asset List")
spawner_entity.add_component("Vegetation Asset List Combiner")
spawner_entity.add_component("Vegetation Asset Weight Selector")
@@ -67,8 +67,8 @@ def AssetWeightSelector_InstancesExpressBasedOnWeight():
# valid slice entity, and one set to None
spawner_center_point = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
asset_path)
desc_asset = hydra.get_component_property_value(spawner_entity.components[2],
"Configuration|Embedded Assets")[0]
desc_list = [desc_asset, desc_asset]
@@ -72,8 +72,8 @@ def DistanceBetweenFilterOverrides_InstancesPlantAtSpecifiedRadius():
# 2) Create a new entity with required vegetation area components
spawner_center_point = math.Vector3(520.0, 520.0, 32.0)
asset_path = os.path.join("Slices", "1m_cube.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
asset_path)
# 3) Create a surface to plant on
surface_center_point = math.Vector3(512.0, 512.0, 32.0)
@@ -70,8 +70,8 @@ def DistanceBetweenFilter_InstancesPlantAtSpecifiedRadius():
# 2) Create a new entity with required vegetation area components
spawner_center_point = math.Vector3(520.0, 520.0, 32.0)
asset_path = os.path.join("Slices", "1m_cube.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
asset_path)
# 3) Create a surface to plant on
surface_center_point = math.Vector3(512.0, 512.0, 32.0)
@@ -80,7 +80,7 @@ def DynamicSliceInstanceSpawner_Embedded_E2E():
# 2) Create a new entity with required vegetation area components and Script Canvas component for launcher test
center_point = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 1.0, asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 1.0, asset_path)
spawner_entity.add_component("Script Canvas")
instance_counter_path = os.path.join("scriptcanvas", "instance_counter.scriptcanvas")
instance_counter_script = asset.AssetCatalogRequestBus(bus.Broadcast, "GetAssetIdByPath", instance_counter_path,
@@ -70,8 +70,8 @@ def InstanceSpawnerPriority_LayerAndSubPriority():
# 2) Create overlapping areas: 1 instance spawner area, and 1 blocker area
spawner_center_point = math.Vector3(508.0, 508.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 1.0,
asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 1.0,
asset_path)
blocker_center_point = math.Vector3(516.0, 516.0, 32.0)
blocker_entity = dynveg.create_blocker_area("Instance Blocker", blocker_center_point, 16.0, 16.0, 1.0)
@@ -86,17 +86,17 @@ def LayerBlender_E2E_Editor():
# 2) Create 2 vegetation areas with different meshes
purple_position = math.Vector3(504.0, 512.0, 32.0)
purple_asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
spawner_entity_1 = dynveg.create_vegetation_area("Purple Spawner",
purple_position,
16.0, 16.0, 1.0,
purple_asset_path)
spawner_entity_1 = dynveg.create_dynamic_slice_vegetation_area("Purple Spawner",
purple_position,
16.0, 16.0, 1.0,
purple_asset_path)
pink_position = math.Vector3(520.0, 512.0, 32.0)
pink_asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
spawner_entity_2 = dynveg.create_vegetation_area("Pink Spawner",
pink_position,
16.0, 16.0, 1.0,
pink_asset_path)
spawner_entity_2 = dynveg.create_dynamic_slice_vegetation_area("Pink Spawner",
pink_position,
16.0, 16.0, 1.0,
pink_asset_path)
base_position = math.Vector3(512.0, 512.0, 32.0)
dynveg.create_surface_entity("Surface Entity",
@@ -68,8 +68,8 @@ def LayerBlocker_InstancesBlockedInConfiguredArea():
# 2) Create a new instance spawner entity
spawner_center_point = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
asset_path)
# 3) Create surface for planting on
dynveg.create_surface_entity("Surface Entity", spawner_center_point, 32.0, 32.0, 1.0)
@@ -51,7 +51,7 @@ def LayerSpawner_FilterStageToggle():
# Create a vegetation area with all needed components
position = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
vegetation_entity = dynveg.create_vegetation_area("vegetation", position, 16.0, 16.0, 16.0, asset_path)
vegetation_entity = dynveg.create_dynamic_slice_vegetation_area("vegetation", position, 16.0, 16.0, 16.0, asset_path)
vegetation_entity.add_component("Vegetation Altitude Filter")
vegetation_entity.add_component("Vegetation Position Modifier")
@@ -62,10 +62,10 @@ def LayerSpawner_InstancesPlantInAllSupportedShapes():
# 2) Create basic vegetation area entity and set the properties
entity_position = math.Vector3(125.0, 136.0, 32.0)
asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
vegetation = dynveg.create_vegetation_area("Instance Spawner",
entity_position,
10.0, 10.0, 10.0,
asset_path)
vegetation = dynveg.create_dynamic_slice_vegetation_area("Instance Spawner",
entity_position,
10.0, 10.0, 10.0,
asset_path)
vegetation.remove_component("Box Shape")
vegetation.add_component("Shape Reference")
@@ -101,10 +101,10 @@ def LayerSpawner_InstancesRefreshUsingCorrectViewportCamera():
# Create the two vegetation areas
test_slice_asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
first_veg_entity = dynveg.create_vegetation_area("Veg Area 1", first_entity_center_point, box_size, box_size,
box_size, test_slice_asset_path)
second_veg_entity = dynveg.create_vegetation_area("Veg Area 2", second_entity_center_point, box_size, box_size,
box_size, test_slice_asset_path)
first_veg_entity = dynveg.create_dynamic_slice_vegetation_area("Veg Area 1", first_entity_center_point, box_size, box_size,
box_size, test_slice_asset_path)
second_veg_entity = dynveg.create_dynamic_slice_vegetation_area("Veg Area 2", second_entity_center_point, box_size, box_size,
box_size, test_slice_asset_path)
# When the first viewport is active, the first area should be full of instances, and the second should be empty
general.set_active_viewport(0)
@@ -59,10 +59,10 @@ def MeshBlocker_InstancesBlockedByMesh():
# Create entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
entity_position = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner",
entity_position,
10.0, 10.0, 10.0,
asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Instance Spawner",
entity_position,
10.0, 10.0, 10.0,
asset_path)
# Create surface entity to plant on
dynveg.create_surface_entity("Surface Entity", entity_position, 10.0, 10.0, 1.0)
@@ -62,10 +62,10 @@ def MeshBlocker_InstancesBlockedByMeshHeightTuning():
# 2) Create entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
entity_position = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner",
entity_position,
10.0, 10.0, 10.0,
asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Instance Spawner",
entity_position,
10.0, 10.0, 10.0,
asset_path)
# 3) Create surface entity to plant on
dynveg.create_surface_entity("Surface Entity", entity_position, 10.0, 10.0, 1.0)
@@ -91,7 +91,7 @@ def PhysXColliderSurfaceTagEmitter_E2E_Editor():
# Create a new entity with required vegetation area components
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Veg Area", entity_center_point, 32.0, 32.0, 32.0, asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Veg Area", entity_center_point, 32.0, 32.0, 32.0, asset_path)
# Add a Vegetation Surface Mask Filter component to the spawner entity and set it to include the "test" tag
spawner_entity.add_component("Vegetation Surface Mask Filter")
@@ -74,8 +74,8 @@ def PositionModifier_AutoSnapToSurfaceWorks():
# 2) Create a new entity with required vegetation area components and a Position Modifier
spawner_center_point = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
asset_path)
# Add a Vegetation Position Modifier and set offset values to 0
spawner_entity.add_component("Vegetation Position Modifier")
@@ -111,7 +111,7 @@ def PositionModifier_ComponentAndOverrides_InstancesPlantAtSpecifiedOffsets():
# 2) Create a new entity with required vegetation area components
spawner_center_point = math.Vector3(16.0, 16.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 1.0, 1.0, 1.0, asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Instance Spawner", spawner_center_point, 1.0, 1.0, 1.0, asset_path)
# Add a Vegetation Position Modifier and set offset values to 0
spawner_entity.add_component("Vegetation Position Modifier")
@@ -88,7 +88,7 @@ def RotationModifierOverrides_InstancesRotateWithinRange():
# 2) Create vegetation entity and add components
entity_position = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Spawner Entity", entity_position, 16.0, 16.0, 16.0, asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Spawner Entity", entity_position, 16.0, 16.0, 16.0, asset_path)
spawner_entity.add_component("Vegetation Rotation Modifier")
# Our default vegetation settings places 20 instances per 16 meters, so we expect 20 * 20 total instances.
num_expected = 20 * 20
@@ -126,7 +126,7 @@ def RotationModifier_InstancesRotateWithinRange():
# 2) Set up vegetation entities
asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Spawner Entity", LEVEL_CENTER, 2.0, 2.0, 2.0, asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Spawner Entity", LEVEL_CENTER, 2.0, 2.0, 2.0, asset_path)
additional_components = [
"Vegetation Rotation Modifier"
@@ -100,7 +100,7 @@ def ScaleModifierOverrides_InstancesProperlyScale():
# 2) Create a new entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
entity_position = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Spawner Entity", entity_position, 16.0, 16.0, 10.0, asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Spawner Entity", entity_position, 16.0, 16.0, 10.0, asset_path)
# Create a surface to plant on and add a Vegetation Debugger Level component to allow refreshes
dynveg.create_surface_entity("Surface Entity", entity_position, 20.0, 20.0, 1.0)
@@ -94,8 +94,8 @@ def ScaleModifier_InstancesProperlyScale():
# Vegetation Scale Modifier
entity_position = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Spawner Entity", entity_position, 16.0, 16.0, 16.0,
asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Spawner Entity", entity_position, 16.0, 16.0, 16.0,
asset_path)
spawner_entity.add_component("Vegetation Scale Modifier")
# Create a surface to plant on and add a Vegetation Debugger Level component to allow refreshes
@@ -57,7 +57,7 @@ def ShapeIntersectionFilter_FilterStageToggle():
# Create basic vegetation entity
position = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
vegetation = dynveg.create_vegetation_area("vegetation", position, 16.0, 16.0, 16.0, asset_path)
vegetation = dynveg.create_dynamic_slice_vegetation_area("vegetation", position, 16.0, 16.0, 16.0, asset_path)
# Create Surface for instances to plant on
dynveg.create_surface_entity("Surface_Entity_Parent", position, 16.0, 16.0, 1.0)
@@ -70,8 +70,8 @@ def ShapeIntersectionFilter_InstancesPlantInAssignedShape():
# 2) Create a new entity with required vegetation area components and Vegetation Shape Intersection Filter
center_point = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 1.0,
asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 1.0,
asset_path)
spawner_entity.add_component("Vegetation Shape Intersection Filter")
# Create a planting surface
@@ -66,7 +66,7 @@ def SlopeAlignmentModifierOverrides_InstanceSurfaceAlignment():
# Create a spawner entity setup with all needed components
center_point = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 32.0, asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 32.0, asset_path)
# Create a sloped mesh surface for the instances to plant on
center_point = math.Vector3(502.0, 512.0, 24.0)
@@ -67,7 +67,7 @@ def SlopeAlignmentModifier_InstanceSurfaceAlignment():
# Create a spawner entity setup with all needed components
center_point = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 32.0, asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Instance Spawner", center_point, 16.0, 16.0, 32.0, asset_path)
# Create a sloped mesh surface for the instances to plant on
center_point = math.Vector3(502.0, 512.0, 24.0)
@@ -72,7 +72,7 @@ def SlopeFilter_ComponentAndOverrides_InstancesPlantOnValidSlopes():
# 2) Create a new entity with required vegetation area components
center_point = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner", center_point, 32.0, 32.0, 32.0, asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Instance Spawner", center_point, 32.0, 32.0, 32.0, asset_path)
# Add a Vegetation Slope Filter
spawner_entity.add_component("Vegetation Slope Filter")
@@ -66,7 +66,7 @@ def SpawnerSlices_SliceCreationAndVisibilityToggleWorks():
# 2.1) Create basic vegetation entity
position = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
veg_1 = dynveg.create_vegetation_area("vegetation_1", position, 16.0, 16.0, 16.0, asset_path)
veg_1 = dynveg.create_dynamic_slice_vegetation_area("vegetation_1", position, 16.0, 16.0, 16.0, asset_path)
# 2.2) Create slice from the entity
slice_path = os.path.join("slices", "TestSlice_1.slice")
@@ -94,7 +94,7 @@ def SpawnerSlices_SliceCreationAndVisibilityToggleWorks():
# 4) C2627905 A slice containing the Vegetation Layer Blender component can be created.
# 4.1) Create another vegetation entity to add to blender component
veg_2 = dynveg.create_vegetation_area("vegetation_2", position, 1.0, 1.0, 1.0, "")
veg_2 = dynveg.create_dynamic_slice_vegetation_area("vegetation_2", position, 1.0, 1.0, 1.0, "")
# 4.2) Create entity with Vegetation Layer Blender
components_to_add = ["Box Shape", "Vegetation Layer Blender"]
@@ -77,8 +77,8 @@ def SurfaceMaskFilterOverrides_MultipleDescriptorOverridesPlantAsExpected():
# 2) Create a new instance spawner entity with multiple Dynamic Slice Instance Spawner descriptors
spawner_center_point = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Instance Spawner", spawner_center_point, 16.0, 16.0, 16.0,
asset_path)
asset_list_component = spawner_entity.components[2]
desc_asset = hydra.get_component_property_value(asset_list_component,
"Configuration|Embedded Assets")[0]
@@ -99,10 +99,10 @@ def SurfaceMaskFilter_ExclusionList():
# 2) Create entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
entity_position = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner",
entity_position,
10.0, 10.0, 10.0,
asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Instance Spawner",
entity_position,
10.0, 10.0, 10.0,
asset_path)
# 3) Add a Vegetation Surface Mask Filter component to the entity.
spawner_entity.add_component("Vegetation Surface Mask Filter")
@@ -100,10 +100,10 @@ def SurfaceMaskFilter_InclusionList():
# 2) Create entity with components "Vegetation Layer Spawner", "Vegetation Asset List", "Box Shape"
entity_position = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Instance Spawner",
entity_position,
10.0, 10.0, 10.0,
asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Instance Spawner",
entity_position,
10.0, 10.0, 10.0,
asset_path)
# 3) Add a Vegetation Surface Mask Filter component to the entity.
spawner_entity.add_component("Vegetation Surface Mask Filter")
@@ -55,7 +55,7 @@ def SystemSettings_SectorPointDensity():
# Create basic vegetation entity
position = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
dynveg.create_vegetation_area("vegetation", position, 16.0, 16.0, 1.0, asset_path)
dynveg.create_dynamic_slice_vegetation_area("vegetation", position, 16.0, 16.0, 1.0, asset_path)
dynveg.create_surface_entity("Surface_Entity", position, 16.0, 16.0, 1.0)
# Count the number of vegetation instances in the vegetation area
@@ -51,7 +51,7 @@ def SystemSettings_SectorSize():
# Create basic vegetation entity
position = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PinkFlower.dynamicslice")
vegetation = dynveg.create_vegetation_area("vegetation", position, 16.0, 16.0, 1.0, asset_path)
vegetation = dynveg.create_dynamic_slice_vegetation_area("vegetation", position, 16.0, 16.0, 1.0, asset_path)
dynveg.create_surface_entity("Surface_Entity", position, 16.0, 16.0, 1.0)
# Add the Vegetation Debugger component to the Level Inspector
@@ -54,7 +54,7 @@ def VegetationInstances_DespawnWhenOutOfRange():
# Create vegetation layer spawner
world_center = math.Vector3(512.0, 512.0, 32.0)
asset_path = os.path.join("Slices", "PurpleFlower.dynamicslice")
spawner_entity = dynveg.create_vegetation_area("Spawner Instance", world_center, 16.0, 16.0, 16.0, asset_path)
spawner_entity = dynveg.create_dynamic_slice_vegetation_area("Spawner Instance", world_center, 16.0, 16.0, 16.0, asset_path)
# Create a surface to spawn on
dynveg.create_surface_entity("Spawner Entity", world_center, 16.0, 16.0, 1.0)
@@ -16,6 +16,20 @@ from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, E
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
class TestAutomation(EditorTestSuite):
class test_AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude(EditorParallelTest):
from .EditorScripts import AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude as test_module
class test_AltitudeFilter_FilterStageToggle(EditorParallelTest):
from .EditorScripts import AltitudeFilter_FilterStageToggle as test_module
class test_AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude(EditorParallelTest):
from .EditorScripts import AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude as test_module
@pytest.mark.SUITE_main
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
class TestAutomation_PrefabNotEnabled(EditorTestSuite):
enable_prefab_system = False
@@ -36,19 +50,10 @@ class TestAutomation(EditorTestSuite):
class test_EmptyInstanceSpawner_EmptySpawnerWorks(EditorParallelTest):
from .EditorScripts import EmptyInstanceSpawner_EmptySpawnerWorks as test_module
class test_AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude(EditorParallelTest):
from .EditorScripts import AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude as test_module
class test_AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude(EditorParallelTest):
from .EditorScripts import AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude as test_module
class test_AltitudeFilter_FilterStageToggle(EditorParallelTest):
from .EditorScripts import AltitudeFilter_FilterStageToggle as test_module
class test_SpawnerSlices_SliceCreationAndVisibilityToggleWorks(EditorSingleTest):
# Custom teardown to remove slice asset created during test
def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
TestAutomation.cleanup_test_slices(self, workspace)
TestAutomation_PrefabNotEnabled.cleanup_test_slices(self, workspace)
from .EditorScripts import SpawnerSlices_SliceCreationAndVisibilityToggleWorks as test_module
class test_AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea(EditorParallelTest):
@@ -161,27 +166,27 @@ class TestAutomation(EditorTestSuite):
# Custom setup/teardown to remove test level created during test
def setup(self, request, workspace, editor, editor_test_results, launcher_platform):
TestAutomation.cleanup_test_level(self, workspace)
TestAutomation_PrefabNotEnabled.cleanup_test_level(self, workspace)
def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
TestAutomation.cleanup_test_level(self, workspace)
TestAutomation_PrefabNotEnabled.cleanup_test_level(self, workspace)
class test_DynamicSliceInstanceSpawner_External_E2E_Editor(EditorSingleTest):
from .EditorScripts import DynamicSliceInstanceSpawner_External_E2E as test_module
# Custom setup/teardown to remove test level created during test
def setup(self, request, workspace, editor, editor_test_results, launcher_platform):
TestAutomation.cleanup_test_level(self, workspace)
TestAutomation_PrefabNotEnabled.cleanup_test_level(self, workspace)
def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
TestAutomation.cleanup_test_level(self, workspace)
TestAutomation_PrefabNotEnabled.cleanup_test_level(self, workspace)
class test_LayerBlender_E2E_Editor(EditorSingleTest):
from .EditorScripts import LayerBlender_E2E_Editor as test_module
# Custom setup/teardown to remove test level created during test
def setup(self, request, workspace, editor, editor_test_results, launcher_platform):
TestAutomation.cleanup_test_level(self, workspace)
TestAutomation_PrefabNotEnabled.cleanup_test_level(self, workspace)
def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
TestAutomation.cleanup_test_level(self, workspace)
TestAutomation_PrefabNotEnabled.cleanup_test_level(self, workspace)
@@ -52,15 +52,15 @@ class TestAutomation(TestAutomationBase):
def test_AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude(self, request, workspace, editor, launcher_platform):
from .EditorScripts import AltitudeFilter_ComponentAndOverrides_InstancesPlantAtSpecifiedAltitude as test_module
self._run_test(request, workspace, editor, test_module, enable_prefab_system=False)
self._run_test(request, workspace, editor, test_module)
def test_AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude(self, request, workspace, editor, launcher_platform):
from .EditorScripts import AltitudeFilter_ShapeSample_InstancesPlantAtSpecifiedAltitude as test_module
self._run_test(request, workspace, editor, test_module, enable_prefab_system=False)
self._run_test(request, workspace, editor, test_module)
def test_AltitudeFilter_FilterStageToggle(self, request, workspace, editor, launcher_platform):
from .EditorScripts import AltitudeFilter_FilterStageToggle as test_module
self._run_test(request, workspace, editor, test_module, enable_prefab_system=False)
self._run_test(request, workspace, editor, test_module)
def test_SpawnerSlices_SliceCreationAndVisibilityToggleWorks(self, request, workspace, editor, remove_test_slice, launcher_platform):
from .EditorScripts import SpawnerSlices_SliceCreationAndVisibilityToggleWorks as test_module
@@ -8,14 +8,16 @@
import os
import sys
from pathlib import Path
import azlmbr.areasystem as areasystem
import azlmbr.asset as asset
import azlmbr.bus as bus
import azlmbr.components as components
import azlmbr.math as math
import azlmbr.vegetation as vegetation
import azlmbr.areasystem as areasystem
import azlmbr.paths
import azlmbr.prefab as prefab
import azlmbr.vegetation as vegetation
sys.path.append(os.path.join(azlmbr.paths.projectroot, 'Gem', 'PythonTests'))
import editor_python_test_tools.hydra_editor_utils as hydra
@@ -91,7 +93,7 @@ def create_mesh_surface_entity_with_slopes(name, center_point, uniform_scale):
return surface_entity
def create_vegetation_area(name, center_point, box_size_x, box_size_y, box_size_z, dynamic_slice_asset_path):
def create_dynamic_slice_vegetation_area(name, center_point, box_size_x, box_size_y, box_size_z, dynamic_slice_asset_path):
# Create a vegetation area entity to use as our test vegetation spawner
spawner_entity = hydra.Entity(name)
spawner_entity.create_entity(
@@ -104,14 +106,48 @@ def create_vegetation_area(name, center_point, box_size_x, box_size_y, box_size_
box_size_z))
# Set the vegetation area to a Dynamic Slice spawner with a specific slice asset selected
descriptor = hydra.get_component_property_value(spawner_entity.components[2], 'Configuration|Embedded Assets|[0]')
dynamic_slice_spawner = vegetation.DynamicSliceInstanceSpawner()
dynamic_slice_spawner.SetSliceAssetPath(dynamic_slice_asset_path)
descriptor = hydra.get_component_property_value(spawner_entity.components[2], 'Configuration|Embedded Assets|[0]')
descriptor.spawner = dynamic_slice_spawner
spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]", descriptor)
return spawner_entity
def create_prefab_vegetation_area(name, center_point, box_size_x, box_size_y, box_size_z, target_prefab):
# Create a vegetation area entity to use as our test vegetation spawner
spawner_entity = hydra.Entity(name)
spawner_entity.create_entity(
center_point,
["Vegetation Layer Spawner", "Box Shape", "Vegetation Asset List"]
)
if spawner_entity.id.IsValid():
print(f"'{spawner_entity.name}' created")
spawner_entity.get_set_test(1, "Box Shape|Box Configuration|Dimensions", math.Vector3(box_size_x, box_size_y,
box_size_z))
# Get the in-memory spawnable asset id if exists
spawnable_name = Path(target_prefab.file_path).stem
spawnable_asset_id = prefab.PrefabPublicRequestBus(bus.Broadcast, 'GetInMemorySpawnableAssetId',
spawnable_name)
# Create the in-memory spawnable asset from given prefab if the spawnable does not exist
if not spawnable_asset_id.is_valid():
create_spawnable_result = prefab.PrefabPublicRequestBus(bus.Broadcast, 'CreateInMemorySpawnableAsset',
target_prefab.file_path,
spawnable_name)
assert create_spawnable_result.IsSuccess(), \
f"Prefab operation 'CreateInMemorySpawnableAssets' failed. Error: {create_spawnable_result.GetError()}"
spawnable_asset_id = create_spawnable_result.GetValue()
# Set the vegetation area to a prefab instance spawner with a specific prefab asset selected
descriptor = hydra.get_component_property_value(spawner_entity.components[2], 'Configuration|Embedded Assets|[0]')
prefab_spawner = vegetation.PrefabInstanceSpawner()
prefab_spawner.SetPrefabAssetId(spawnable_asset_id)
descriptor.spawner = prefab_spawner
spawner_entity.get_set_test(2, "Configuration|Embedded Assets|[0]", descriptor)
return spawner_entity
def create_blocker_area(name, center_point, box_size_x, box_size_y, box_size_z):
# Create a Vegetation Layer Blocker area
blocker_entity = hydra.Entity(name)
@@ -15,6 +15,7 @@
#include <AzCore/std/string/string.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Spawnable/InMemorySpawnableAssetContainer.h>
namespace AzToolsFramework
{
@@ -47,7 +48,7 @@ namespace AzToolsFramework
//! Get all Assets generated by Prefab processing when entering Play-In Editor mode (Ctrl+G)
//! /return The vector of Assets generated by Prefab processing
virtual const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& GetPlayInEditorAssetData() = 0;
virtual const Prefab::PrefabConversionUtils::InMemorySpawnableAssetContainer::SpawnableAssets& GetPlayInEditorAssetData() const = 0;
virtual bool LoadFromStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) = 0;
virtual bool SaveToStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) = 0;
@@ -25,6 +25,7 @@
#include <AzToolsFramework/Prefab/PrefabLoader.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/PrefabUndoHelpers.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabConverterStackProfileNames.h>
namespace AzToolsFramework
{
@@ -379,9 +380,9 @@ namespace AzToolsFramework
return m_rootInstance ? m_rootInstance->GetTemplateId() : Prefab::InvalidTemplateId;
}
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& PrefabEditorEntityOwnershipService::GetPlayInEditorAssetData()
const Prefab::PrefabConversionUtils::InMemorySpawnableAssetContainer::SpawnableAssets& PrefabEditorEntityOwnershipService::GetPlayInEditorAssetData() const
{
return m_playInEditorData.m_assets;
return m_playInEditorData.m_assetsCache.GetAllInMemorySpawnableAssets();
}
void PrefabEditorEntityOwnershipService::OnEntityRemoved(AZ::EntityId entityId)
@@ -405,65 +406,6 @@ namespace AzToolsFramework
m_validateEntitiesCallback = AZStd::move(validateEntitiesCallback);
}
void PrefabEditorEntityOwnershipService::LoadReferencedAssets(AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets)
{
// Start our loads on all assets by calling GetAsset from the AssetManager
for (AZ::Data::Asset<AZ::Data::AssetData>& asset : referencedAssets)
{
if (!asset.GetId().IsValid())
{
AZ_Error("Prefab", false, "Invalid asset found referenced in scene while entering game mode");
continue;
}
const AZ::Data::AssetLoadBehavior loadBehavior = asset.GetAutoLoadBehavior();
if (loadBehavior == AZ::Data::AssetLoadBehavior::NoLoad)
{
continue;
}
AZ::Data::AssetId assetId = asset.GetId();
AZ::Data::AssetType assetType = asset.GetType();
asset = AZ::Data::AssetManager::Instance().GetAsset(assetId, assetType, loadBehavior);
if (!asset.GetId().IsValid())
{
AZ_Error("Prefab", false, "Invalid asset found referenced in scene while entering game mode");
continue;
}
}
// For all Preload assets we block until they're ready
// We do this as a seperate pass so that we don't interrupt queuing up all other asset loads
for (AZ::Data::Asset<AZ::Data::AssetData>& asset : referencedAssets)
{
if (!asset.GetId().IsValid())
{
AZ_Error("Prefab", false, "Invalid asset found referenced in scene while entering game mode");
continue;
}
const AZ::Data::AssetLoadBehavior loadBehavior = asset.GetAutoLoadBehavior();
if (loadBehavior != AZ::Data::AssetLoadBehavior::PreLoad)
{
continue;
}
asset.BlockUntilLoadComplete();
if (asset.IsError())
{
AZ_Error("Prefab", false, "Asset with id %s failed to preload while entering game mode",
asset.GetId().ToString<AZStd::string>().c_str());
continue;
}
}
}
void PrefabEditorEntityOwnershipService::StartPlayInEditor()
{
// This is a workaround until the replacement for GameEntityContext is done
@@ -471,110 +413,48 @@ namespace AzToolsFramework
if (m_rootInstance && !m_playInEditorData.m_isEnabled)
{
// Construct the runtime entities and products
Prefab::TemplateReference templateReference = m_prefabSystemComponent->FindTemplate(m_rootInstance->GetTemplateId());
if (templateReference.has_value())
// Construct the runtime entities and products
bool readyToCreateRootSpawnable = m_playInEditorData.m_assetsCache.IsActivated();
if (!readyToCreateRootSpawnable &&
!m_playInEditorData.m_assetsCache.Activate(Prefab::PrefabConversionUtils::PlayInEditor))
{
bool converterLoaded = m_playInEditorData.m_converter.IsLoaded();
if (!converterLoaded)
{
converterLoaded = m_playInEditorData.m_converter.LoadStackProfile("PlayInEditor");
}
if (converterLoaded)
{
// Use a random uuid as this is only a temporary source.
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabProcessorContext context(AZ::Uuid::CreateRandom());
Prefab::PrefabDom copy;
copy.CopyFrom(templateReference->get().GetPrefabDom(), copy.GetAllocator(), false);
context.AddPrefab(DefaultMainSpawnableName, AZStd::move(copy));
m_playInEditorData.m_converter.ProcessPrefab(context);
if (context.HasCompletedSuccessfully() && !context.GetProcessedObjects().empty())
{
static constexpr size_t NoRootSpawnable = AZStd::numeric_limits<size_t>::max();
size_t rootSpawnableIndex = NoRootSpawnable;
// Create temporary assets from the processed data.
for (auto& product : context.GetProcessedObjects())
{
if (product.GetAssetType() == AZ::AzTypeInfo<AzFramework::Spawnable>::Uuid() &&
product.GetId() ==
AZStd::string::format("%s.%s", DefaultMainSpawnableName, AzFramework::Spawnable::FileExtension))
{
rootSpawnableIndex = m_playInEditorData.m_assets.size();
}
AZ::Data::AssetInfo info;
info.m_assetId = product.GetAsset().GetId();
info.m_assetType = product.GetAssetType();
info.m_relativePath = product.GetId();
AZ::Data::AssetCatalogRequestBus::Broadcast(
&AZ::Data::AssetCatalogRequestBus::Events::RegisterAsset, info.m_assetId, info);
m_playInEditorData.m_assets.emplace_back(product.ReleaseAsset().release(), AZ::Data::AssetLoadBehavior::Default);
// Ensure the product asset is registered with the AssetManager
// Hold on to the returned asset to keep ref count alive until we assign it the latest data
AZ::Data::Asset<AZ::Data::AssetData> asset =
AZ::Data::AssetManager::Instance().FindOrCreateAsset(info.m_assetId, info.m_assetType, AZ::Data::AssetLoadBehavior::Default);
// Update the asset registered in the AssetManager with the data of our product from the Prefab Processor
AZ::Data::AssetManager::Instance().AssignAssetData(m_playInEditorData.m_assets.back());
}
for (auto& product : context.GetProcessedObjects())
{
LoadReferencedAssets(product.GetReferencedAssets());
}
// make sure that PRE_NOTIFY assets get their notify before we activate, so that we can preserve the order of
// (load asset) -> (notify) -> (init) -> (activate)
AZ::Data::AssetManager::Instance().DispatchEvents();
if (rootSpawnableIndex != NoRootSpawnable)
{
m_playInEditorData.m_entities.Reset(m_playInEditorData.m_assets[rootSpawnableIndex]);
m_playInEditorData.m_entities.SpawnAllEntities();
}
else
{
AZ_Error("Prefab", false,
"Processing of the level prefab failed to produce a root spawnable while entering game mode. "
"Unable to fully enter game mode.");
return;
}
// This is a workaround until the replacement for GameEntityContext is done
AzFramework::GameEntityContextEventBus::Broadcast(
&AzFramework::GameEntityContextEventBus::Events::OnGameEntitiesStarted);
}
else
{
AZ_Error("Prefab", false,
"Failed to convert the prefab into assets. "
"Confirm that the 'PlayInEditor' prefab processor stack is capable of producing a useable product asset.");
return;
}
}
else
{
AZ_Error("Prefab", false, "Failed to create a prefab processing stack from key 'PlayInEditor'.");
return;
}
m_rootInstance->GetAllEntitiesInHierarchy([this](AZStd::unique_ptr<AZ::Entity>& entity)
{
AZ_Assert(entity, "Invalid entity found in root instance while starting play in editor.");
if (entity->GetState() == AZ::Entity::State::Active)
{
entity->Deactivate();
m_playInEditorData.m_deactivatedEntities.push_back(entity.get());
}
return true;
});
AZ_Error("Prefab", false, "Failed to create a prefab processing stack from key '%.*s'.", AZ_STRING_ARG(Prefab::PrefabConversionUtils::PlayInEditor));
return;
}
}
m_playInEditorData.m_isEnabled = true;
auto createRootSpawnableResult = m_playInEditorData.m_assetsCache.CreateInMemorySpawnableAsset(m_rootInstance->GetTemplateId(), DefaultMainSpawnableName, true);
if (createRootSpawnableResult.IsSuccess())
{
// make sure that PRE_NOTIFY assets get their notify before we activate, so that we can preserve the order of
// (load asset) -> (notify) -> (init) -> (activate)
AZ::Data::AssetManager::Instance().DispatchEvents();
m_playInEditorData.m_entities.Reset(createRootSpawnableResult.GetValue());
m_playInEditorData.m_entities.SpawnAllEntities();
// This is a workaround until the replacement for GameEntityContext is done
AzFramework::GameEntityContextEventBus::Broadcast(
&AzFramework::GameEntityContextEventBus::Events::OnGameEntitiesStarted);
}
else
{
AZ_Error("Prefab", false, "Failed to create the root spawnable due to the error: '%.*s'", AZ_STRING_ARG(createRootSpawnableResult.GetError()));
return;
}
m_rootInstance->GetAllEntitiesInHierarchy([this](AZStd::unique_ptr<AZ::Entity>& entity)
{
AZ_Assert(entity, "Invalid entity found in root instance while starting play in editor.");
if (entity->GetState() == AZ::Entity::State::Active)
{
entity->Deactivate();
m_playInEditorData.m_deactivatedEntities.push_back(entity.get());
}
return true;
});
m_playInEditorData.m_isEnabled = true;
}
}
void PrefabEditorEntityOwnershipService::StopPlayInEditor()
@@ -588,7 +468,7 @@ namespace AzToolsFramework
m_playInEditorData.m_entities.DespawnAllEntities();
m_playInEditorData.m_entities.Alert(
[assets = AZStd::move(m_playInEditorData.m_assets),
[allSpawnableAssetData = m_playInEditorData.m_assetsCache.MoveAllInMemorySpawnableAssets(),
deactivatedEntities = AZStd::move(m_playInEditorData.m_deactivatedEntities)]([[maybe_unused]] uint32_t generation) mutable
{
auto end = deactivatedEntities.rend();
@@ -598,11 +478,10 @@ namespace AzToolsFramework
(*it)->Activate();
}
for (auto& asset : assets)
for (auto& [spawnableName, spawnableAssetData] : allSpawnableAssetData)
{
if (asset)
for (auto& asset : spawnableAssetData.m_assets)
{
// Explicitly release because this needs to happen before the asset is unregistered.
asset.Release();
AZ::Data::AssetCatalogRequestBus::Broadcast(
&AZ::Data::AssetCatalogRequestBus::Events::UnregisterAsset, asset.GetId());
@@ -14,7 +14,7 @@
#include <AzFramework/Spawnable/SpawnableEntitiesContainer.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
#include <AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabConversionPipeline.h>
#include <AzToolsFramework/Prefab/Spawnable/InMemorySpawnableAssetContainer.h>
namespace AzToolsFramework
{
@@ -176,8 +176,7 @@ namespace AzToolsFramework
private:
struct PlayInEditorData
{
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabConversionPipeline m_converter;
AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>> m_assets;
AzToolsFramework::Prefab::PrefabConversionUtils::InMemorySpawnableAssetContainer m_assetsCache;
AZStd::vector<AZ::Entity*> m_deactivatedEntities;
AzFramework::SpawnableEntitiesContainer m_entities;
bool m_isEnabled{ false };
@@ -195,14 +194,12 @@ namespace AzToolsFramework
Prefab::InstanceOptionalReference GetRootPrefabInstance() override;
Prefab::TemplateId GetRootPrefabTemplateId() override;
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& GetPlayInEditorAssetData() override;
const Prefab::PrefabConversionUtils::InMemorySpawnableAssetContainer::SpawnableAssets& GetPlayInEditorAssetData() const override;
//////////////////////////////////////////////////////////////////////////
void OnEntityRemoved(AZ::EntityId entityId);
void LoadReferencedAssets(AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets);
OnEntitiesAddedCallback m_entitiesAddedCallback;
OnEntitiesRemovedCallback m_entitiesRemovedCallback;
ValidateEntitiesCallback m_validateEntitiesCallback;
@@ -8,6 +8,7 @@
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/IO/Path/Path.h>
@@ -27,6 +28,7 @@ namespace AzToolsFramework
using InstantiatePrefabResult = AZ::Outcome<AZ::EntityId, AZStd::string>;
using DuplicatePrefabResult = AZ::Outcome<EntityIdList, AZStd::string>;
using PrefabOperationResult = AZ::Outcome<void, AZStd::string>;
using CreateSpawnableResult = AZ::Outcome<AZ::Data::AssetId, AZStd::string>;
/**
* The primary purpose of this bus is to facilitate writing automated tests for prefabs.
@@ -93,6 +95,34 @@ namespace AzToolsFramework
* Returns the path to the prefab, or an empty path if the entity is owned by the level.
*/
virtual AZStd::string GetOwningInstancePrefabPath(AZ::EntityId entityId) const = 0;
/**
* Convert a prefab on given file path with given name to in-memory spawnable asset.
* Returns the asset id of the produced spawnable if creation succeeded;
* on failure, it comes with an error message detailing the cause of the error.
*/
virtual CreateSpawnableResult CreateInMemorySpawnableAsset(AZStd::string_view prefabFilePath, AZStd::string_view spawnableName) = 0;
/**
* Remove in-memory spawnable asset with given name.
* Return an outcome object; on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult RemoveInMemorySpawnableAsset(AZStd::string_view spawnableName) = 0;
/**
* Return whether an in-memory spawnable with given name exists or not.
*/
virtual bool HasInMemorySpawnableAsset(AZStd::string_view spawnableName) const = 0;
/**
* Return an asset id of a spawnalbe with given name. Invalid asset id will be returned if the spawnable doesn't exist.
*/
virtual AZ::Data::AssetId GetInMemorySpawnableAssetId(AZStd::string_view spawnableName) const = 0;
/**
* Remove all the in-memory spawnable assets.
*/
virtual void RemoveAllInMemorySpawnableAssets() = 0;
};
using PrefabPublicRequestBus = AZ::EBus<PrefabPublicRequests>;
@@ -8,10 +8,16 @@
#include <AzToolsFramework/Prefab/PrefabPublicRequestHandler.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzToolsFramework/Prefab/PrefabLoader.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabConversionPipeline.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabConverterStackProfileNames.h>
namespace AzToolsFramework
{
namespace Prefab
@@ -31,13 +37,18 @@ namespace AzToolsFramework
->Event("DetachPrefab", &PrefabPublicRequests::DetachPrefab)
->Event("DuplicateEntitiesInInstance", &PrefabPublicRequests::DuplicateEntitiesInInstance)
->Event("GetOwningInstancePrefabPath", &PrefabPublicRequests::GetOwningInstancePrefabPath)
->Event("CreateInMemorySpawnableAsset", &PrefabPublicRequests::CreateInMemorySpawnableAsset)
->Event("RemoveInMemorySpawnableAsset", &PrefabPublicRequests::RemoveInMemorySpawnableAsset)
->Event("HasInMemorySpawnableAsset", &PrefabPublicRequests::HasInMemorySpawnableAsset)
->Event("GetInMemorySpawnableAssetId", &PrefabPublicRequests::GetInMemorySpawnableAssetId)
->Event("RemoveAllInMemorySpawnableAssets", &PrefabPublicRequests::RemoveAllInMemorySpawnableAssets)
;
}
}
void PrefabPublicRequestHandler::Connect()
{
m_prefabPublicInterface = AZ::Interface<Prefab::PrefabPublicInterface>::Get();
m_prefabPublicInterface = AZ::Interface<PrefabPublicInterface>::Get();
AZ_Assert(m_prefabPublicInterface, "PrefabPublicRequestHandler - Could not retrieve instance of PrefabPublicInterface");
PrefabPublicRequestBus::Handler::BusConnect();
@@ -46,7 +57,7 @@ namespace AzToolsFramework
void PrefabPublicRequestHandler::Disconnect()
{
PrefabPublicRequestBus::Handler::BusDisconnect();
m_spawnableAssetContainer.Deactivate();
m_prefabPublicInterface = nullptr;
}
@@ -79,5 +90,63 @@ namespace AzToolsFramework
{
return m_prefabPublicInterface->GetOwningInstancePrefabPath(entityId).Native();
}
bool PrefabPublicRequestHandler::TryActivateSpawnableAssetContainer()
{
bool activated = m_spawnableAssetContainer.IsActivated();
if (!activated)
{
activated = m_spawnableAssetContainer.Activate(PrefabConversionUtils::IntegrationTests);
}
return activated;
}
CreateSpawnableResult PrefabPublicRequestHandler::CreateInMemorySpawnableAsset(AZStd::string_view prefabFilePath, AZStd::string_view spawnableName)
{
if (!TryActivateSpawnableAssetContainer())
{
return AZ::Failure(AZStd::string("Failed to activate Spawnable Asset Container"));
}
auto result = m_spawnableAssetContainer.CreateInMemorySpawnableAsset(prefabFilePath, spawnableName);
if (result.IsSuccess())
{
return AZ::Success(result.GetValue().GetId());
}
else
{
return AZ::Failure(result.TakeError());
}
}
PrefabOperationResult PrefabPublicRequestHandler::RemoveInMemorySpawnableAsset(AZStd::string_view spawnableName)
{
auto result = m_spawnableAssetContainer.RemoveInMemorySpawnableAsset(spawnableName);
if (result.IsSuccess())
{
return AZ::Success();
}
else
{
return AZ::Failure(result.TakeError());
}
}
bool PrefabPublicRequestHandler::HasInMemorySpawnableAsset(AZStd::string_view spawnableName) const
{
return m_spawnableAssetContainer.HasInMemorySpawnableAsset(spawnableName);
}
AZ::Data::AssetId PrefabPublicRequestHandler::GetInMemorySpawnableAssetId(AZStd::string_view spawnableName) const
{
return m_spawnableAssetContainer.GetInMemorySpawnableAssetId(spawnableName);
}
void PrefabPublicRequestHandler::RemoveAllInMemorySpawnableAssets()
{
m_spawnableAssetContainer.ClearAllInMemorySpawnableAssets();
}
} // namespace Prefab
} // namespace AzToolsFramework
@@ -12,6 +12,7 @@
#include <AzCore/RTTI/RTTI.h>
#include <AzToolsFramework/Prefab/PrefabPublicRequestBus.h>
#include <AzToolsFramework/Prefab/Spawnable/InMemorySpawnableAssetContainer.h>
namespace AzToolsFramework
{
@@ -37,9 +38,18 @@ namespace AzToolsFramework
PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override;
DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
AZStd::string GetOwningInstancePrefabPath(AZ::EntityId entityId) const override;
CreateSpawnableResult CreateInMemorySpawnableAsset(AZStd::string_view prefabFilePath, AZStd::string_view spawnableName) override;
PrefabOperationResult RemoveInMemorySpawnableAsset(AZStd::string_view spawnableName) override;
bool HasInMemorySpawnableAsset(AZStd::string_view spawnableName) const override;
AZ::Data::AssetId GetInMemorySpawnableAssetId(AZStd::string_view spawnableName) const override;
void RemoveAllInMemorySpawnableAssets() override;
private:
bool TryActivateSpawnableAssetContainer();
PrefabConversionUtils::InMemorySpawnableAssetContainer m_spawnableAssetContainer;
PrefabPublicInterface* m_prefabPublicInterface = nullptr;
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -0,0 +1,275 @@
/*
* 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 <AzToolsFramework/Prefab/Spawnable/InMemorySpawnableAssetContainer.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzToolsFramework/Prefab/PrefabLoader.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabConverterStackProfileNames.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
InMemorySpawnableAssetContainer::~InMemorySpawnableAssetContainer()
{
Deactivate();
}
bool InMemorySpawnableAssetContainer::Activate(AZStd::string_view stackProfile)
{
AZ_Assert(!IsActivated(),
"InMemorySpawnableAssetContainer - Unable to activate an instance of InMemorySpawnableAssetContainer as the instance is already active.");
m_prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
AZ_Assert(m_prefabSystemComponentInterface, "InMemorySpawnableAssetContainer - Could not retrieve instance of PrefabSystemComponentInterface");
m_loaderInterface = AZ::Interface<Prefab::PrefabLoaderInterface>::Get();
AZ_Assert(m_loaderInterface, "InMemorySpawnableAssetContainer - Could not retrieve instance of PrefabLoaderInterface");
m_stockProfile = stackProfile;
return m_converter.LoadStackProfile(m_stockProfile);
}
void InMemorySpawnableAssetContainer::Deactivate()
{
ClearAllInMemorySpawnableAssets();
m_stockProfile = "";
m_loaderInterface = nullptr;
m_prefabSystemComponentInterface = nullptr;
}
bool InMemorySpawnableAssetContainer::IsActivated() const
{
return m_converter.IsLoaded();
}
AZStd::string_view InMemorySpawnableAssetContainer::GetStockProfile() const
{
return m_stockProfile;
}
bool InMemorySpawnableAssetContainer::HasInMemorySpawnableAsset(AZStd::string_view spawnableName) const
{
return m_spawnableAssets.find(spawnableName) != m_spawnableAssets.end();
}
AZ::Data::AssetId InMemorySpawnableAssetContainer::GetInMemorySpawnableAssetId(AZStd::string_view spawnableName) const
{
auto found = m_spawnableAssets.find(spawnableName);
if (found != m_spawnableAssets.end())
{
return found->second.m_spawnableAssetId;
}
else
{
return AZ::Data::AssetId();
}
}
auto InMemorySpawnableAssetContainer::RemoveInMemorySpawnableAsset(AZStd::string_view spawnableName) -> RemoveSpawnableResult
{
auto found = m_spawnableAssets.find(spawnableName);
if (found == m_spawnableAssets.end())
{
return AZ::Failure(AZStd::string::format("In-memory Spawnable '%.*s' doesn't exists.", AZ_STRING_ARG(spawnableName)));
}
for (auto& asset : found->second.m_assets)
{
asset.Release();
AZ::Data::AssetCatalogRequestBus::Broadcast(
&AZ::Data::AssetCatalogRequestBus::Events::UnregisterAsset, asset.GetId());
}
m_spawnableAssets.erase(found);
return AZ::Success();
}
InMemorySpawnableAssetContainer::CreateSpawnableResult InMemorySpawnableAssetContainer::CreateInMemorySpawnableAsset(
AzToolsFramework::Prefab::TemplateId templateId, AZStd::string_view spawnableName, bool loadReferencedAssets)
{
if (!IsActivated())
{
return AZ::Failure(AZStd::string::format("Failed to create a prefab processing stack from key '%.*s'.", AZ_STRING_ARG(m_stockProfile)));
}
if (HasInMemorySpawnableAsset(spawnableName))
{
return AZ::Failure(AZStd::string::format("In-memory Spawnable '%.*s' already exists.", AZ_STRING_ARG(spawnableName)));
}
TemplateReference templateReference = m_prefabSystemComponentInterface->FindTemplate(templateId);
if (!templateReference.has_value())
{
return AZ::Failure(AZStd::string::format("Could not get Template DOM for given Template's id %llu .", templateId));
}
// Use a random uuid as this is only a temporary source.
PrefabConversionUtils::PrefabProcessorContext context(AZ::Uuid::CreateRandom());
PrefabDom copy;
copy.CopyFrom(templateReference->get().GetPrefabDom(), copy.GetAllocator(), false);
context.AddPrefab(spawnableName, AZStd::move(copy));
m_converter.ProcessPrefab(context);
if (!context.HasCompletedSuccessfully() || context.GetProcessedObjects().empty())
{
return AZ::Failure(AZStd::string::format(
"Failed to convert the prefab into assets. Please confirm that the '%.*s' prefab processor stack is capable of producing a usable product asset.",
AZ_STRING_ARG(PrefabConversionUtils::IntegrationTests)));
}
static constexpr size_t NoTargetSpawnable = AZStd::numeric_limits<size_t>::max();
size_t targetSpawnableIndex = NoTargetSpawnable;
AZStd::vector<AZ::Data::AssetId> assetIds;
SpawnableAssetData spawnableAssetData;
AZStd::string rootProductId(spawnableName);
rootProductId += AzFramework::Spawnable::DotFileExtension;
// Create temporary assets from the processed data.
for (auto& product : context.GetProcessedObjects())
{
if (product.GetAssetType() == azrtti_typeid<AzFramework::Spawnable>() && product.GetId() == rootProductId)
{
targetSpawnableIndex = spawnableAssetData.m_assets.size();
}
AZ::Data::AssetInfo info;
info.m_assetId = product.GetAsset().GetId();
info.m_assetType = product.GetAssetType();
info.m_relativePath = product.GetId();
AZ::Data::AssetCatalogRequestBus::Broadcast(
&AZ::Data::AssetCatalogRequestBus::Events::RegisterAsset, info.m_assetId, info);
spawnableAssetData.m_assets.emplace_back(product.ReleaseAsset().release(), AZ::Data::AssetLoadBehavior::Default);
// Ensure the product asset is registered with the AssetManager
// Hold on to the returned asset to keep ref count alive until we assign it the latest data
AZ::Data::Asset<AZ::Data::AssetData> asset =
AZ::Data::AssetManager::Instance().FindOrCreateAsset(info.m_assetId, info.m_assetType, AZ::Data::AssetLoadBehavior::Default);
// Update the asset registered in the AssetManager with the data of our product from the Prefab Processor
AZ::Data::AssetManager::Instance().AssignAssetData(spawnableAssetData.m_assets.back());
}
if (targetSpawnableIndex == NoTargetSpawnable)
{
return AZ::Failure(AZStd::string::format("Failed to produce the target spawnable '%.*s'.", AZ_STRING_ARG(spawnableName)));
}
if (loadReferencedAssets)
{
for (auto& product : context.GetProcessedObjects())
{
LoadReferencedAssets(product.GetReferencedAssets());
}
}
auto& spawnableAssetDataAdded = m_spawnableAssets.emplace(spawnableName, spawnableAssetData).first->second;
spawnableAssetDataAdded.m_spawnableAssetId = spawnableAssetDataAdded.m_assets[targetSpawnableIndex].GetId();
return AZ::Success(spawnableAssetDataAdded.m_assets[targetSpawnableIndex]);
}
InMemorySpawnableAssetContainer::CreateSpawnableResult InMemorySpawnableAssetContainer::CreateInMemorySpawnableAsset(
AZStd::string_view prefabFilePath, AZStd::string_view spawnableName, bool loadReferencedAssets)
{
AZ::IO::Path relativePath = m_loaderInterface->GenerateRelativePath(prefabFilePath);
auto templateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(relativePath);
if (templateId == InvalidTemplateId)
{
return AZ::Failure(AZStd::string::format("Template with source path '%.*s' is not found.", AZ_STRING_ARG(prefabFilePath)));
}
return CreateInMemorySpawnableAsset(templateId, spawnableName, loadReferencedAssets);
}
void InMemorySpawnableAssetContainer::ClearAllInMemorySpawnableAssets()
{
for (auto& [spawnableName, spawnableAssetData] : m_spawnableAssets)
{
for (auto& asset : spawnableAssetData.m_assets)
{
asset.Release();
AZ::Data::AssetCatalogRequestBus::Broadcast(
&AZ::Data::AssetCatalogRequestBus::Events::UnregisterAsset, asset.GetId());
}
}
m_spawnableAssets.clear();
}
InMemorySpawnableAssetContainer::SpawnableAssets&& InMemorySpawnableAssetContainer::MoveAllInMemorySpawnableAssets()
{
return AZStd::move(m_spawnableAssets);
}
const InMemorySpawnableAssetContainer::SpawnableAssets& InMemorySpawnableAssetContainer::GetAllInMemorySpawnableAssets() const
{
return m_spawnableAssets;
}
void InMemorySpawnableAssetContainer::LoadReferencedAssets(AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets)
{
// Start our loads on all assets by calling GetAsset from the AssetManager
for (AZ::Data::Asset<AZ::Data::AssetData>& asset : referencedAssets)
{
if (!asset.GetId().IsValid())
{
AZ_Error("Prefab", false, "Invalid asset found referenced in scene while entering game mode");
continue;
}
const AZ::Data::AssetLoadBehavior loadBehavior = asset.GetAutoLoadBehavior();
if (loadBehavior == AZ::Data::AssetLoadBehavior::NoLoad)
{
continue;
}
AZ::Data::AssetId assetId = asset.GetId();
AZ::Data::AssetType assetType = asset.GetType();
asset = AZ::Data::AssetManager::Instance().GetAsset(assetId, assetType, loadBehavior);
if (!asset.GetId().IsValid())
{
AZ_Error("Prefab", false, "Invalid asset found referenced in scene while entering game mode");
continue;
}
}
// For all Preload assets we block until they're ready
// We do this as a separate pass so that we don't interrupt queuing up all other asset loads
for (AZ::Data::Asset<AZ::Data::AssetData>& asset : referencedAssets)
{
if (!asset.GetId().IsValid())
{
AZ_Error("Prefab", false, "Invalid asset found referenced in scene while entering game mode");
continue;
}
const AZ::Data::AssetLoadBehavior loadBehavior = asset.GetAutoLoadBehavior();
if (loadBehavior != AZ::Data::AssetLoadBehavior::PreLoad)
{
continue;
}
asset.BlockUntilLoadComplete();
if (asset.IsError())
{
AZ_Error("Prefab", false, "Asset with id %s failed to preload while entering game mode",
asset.GetId().ToString<AZStd::string>().c_str());
continue;
}
}
}
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,69 @@
/*
* 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/Memory/SystemAllocator.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string_view.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabConversionPipeline.h>
namespace AzToolsFramework::Prefab
{
class PrefabSystemComponentInterface;
class PrefabLoaderInterface;
}
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
class InMemorySpawnableAssetContainer
{
public:
AZ_CLASS_ALLOCATOR(InMemorySpawnableAssetContainer, AZ::SystemAllocator, 0);
using Assets = AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>;
using CreateSpawnableResult = AZ::Outcome<AZ::Data::Asset<AZ::Data::AssetData>&, AZStd::string>;
using RemoveSpawnableResult = AZ::Outcome<void, AZStd::string>;
struct SpawnableAssetData
{
Assets m_assets;
AZ::Data::AssetId m_spawnableAssetId;
};
using SpawnableAssets = AZStd::unordered_map<AZStd::string, SpawnableAssetData>;
~InMemorySpawnableAssetContainer();
bool Activate(AZStd::string_view stackProfile);
void Deactivate();
bool IsActivated() const;
AZStd::string_view GetStockProfile() const;
CreateSpawnableResult CreateInMemorySpawnableAsset(
AZStd::string_view prefabFilePath, AZStd::string_view spawnableName, bool loadReferencedAssets = false);
CreateSpawnableResult CreateInMemorySpawnableAsset(
AzToolsFramework::Prefab::TemplateId templateId, AZStd::string_view spawnableName, bool loadReferencedAssets = false);
RemoveSpawnableResult RemoveInMemorySpawnableAsset(AZStd::string_view spawnableName);
AZ::Data::AssetId GetInMemorySpawnableAssetId(AZStd::string_view spawnableName) const;
bool HasInMemorySpawnableAsset(AZStd::string_view spawnableName) const;
void ClearAllInMemorySpawnableAssets();
SpawnableAssets&& MoveAllInMemorySpawnableAssets();
const SpawnableAssets& GetAllInMemorySpawnableAssets() const;
private:
void LoadReferencedAssets(AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& referencedAssets);
SpawnableAssets m_spawnableAssets;
PrefabConversionUtils::PrefabConversionPipeline m_converter;
AZStd::string_view m_stockProfile;
PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr;
PrefabLoaderInterface* m_loaderInterface = nullptr;
};
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -0,0 +1,20 @@
/*
* 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/Memory/SystemAllocator.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzToolsFramework/Prefab/Spawnable/PrefabProcessorContext.h>
namespace AzToolsFramework::Prefab::PrefabConversionUtils
{
inline static constexpr AZStd::string_view PlayInEditor = "PlayInEditor";
inline static constexpr AZStd::string_view IntegrationTests = "IntegrationTests";
} // namespace AzToolsFramework::Prefab::PrefabConversionUtils
@@ -720,10 +720,13 @@ set(FILES
Prefab/Spawnable/EditorOnlyEntityHandler/UiEditorOnlyEntityHandler.cpp
Prefab/Spawnable/EditorOnlyEntityHandler/WorldEditorOnlyEntityHandler.h
Prefab/Spawnable/EditorOnlyEntityHandler/WorldEditorOnlyEntityHandler.cpp
Prefab/Spawnable/InMemorySpawnableAssetContainer.h
Prefab/Spawnable/InMemorySpawnableAssetContainer.cpp
Prefab/Spawnable/PrefabCatchmentProcessor.h
Prefab/Spawnable/PrefabCatchmentProcessor.cpp
Prefab/Spawnable/PrefabConversionPipeline.h
Prefab/Spawnable/PrefabConversionPipeline.cpp
Prefab/Spawnable/PrefabConverterStackProfileNames.h
Prefab/Spawnable/ProcesedObjectStore.h
Prefab/Spawnable/ProcesedObjectStore.cpp
Prefab/Spawnable/PrefabProcessor.h
@@ -277,22 +277,25 @@ namespace Multiplayer
}
// BeginGameMode and Prefab Processing have completed at this point
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData();
const auto& allAssetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData();
AZStd::vector<uint8_t> buffer;
AZ::IO::ByteContainerStream byteStream(&buffer);
// Serialize Asset information and AssetData into a potentially large buffer
for (const auto& asset : assetData)
for (auto& [spawnableName, spawnableAssetData] : allAssetData)
{
AZ::Data::AssetId assetId = asset.GetId();
AZStd::string assetHint = asset.GetHint();
uint32_t hintSize = aznumeric_cast<uint32_t>(assetHint.size());
for (auto& asset : spawnableAssetData.m_assets)
{
AZ::Data::AssetId assetId = asset.GetId();
AZStd::string assetHint = asset.GetHint();
uint32_t hintSize = aznumeric_cast<uint32_t>(assetHint.size());
byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast<void*>(&assetId));
byteStream.Write(sizeof(uint32_t), reinterpret_cast<void*>(&hintSize));
byteStream.Write(assetHint.size(), assetHint.data());
AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType());
byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast<void*>(&assetId));
byteStream.Write(sizeof(uint32_t), reinterpret_cast<void*>(&hintSize));
byteStream.Write(assetHint.size(), assetHint.data());
AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType());
}
}
const AZ::CVarFixedString remoteAddress = editorsv_serveraddr;
@@ -364,24 +367,27 @@ namespace Multiplayer
AZ_Printf("MultiplayerEditor", "Editor is sending the editor-server the level data packet.")
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData();
const auto& allAssetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData();
AZStd::vector<uint8_t> buffer;
AZ::IO::ByteContainerStream byteStream(&buffer);
// Serialize Asset information and AssetData into a potentially large buffer
for (const auto& asset : assetData)
for (auto& [spawnableName, spawnableAssetData] : allAssetData)
{
AZ::Data::AssetId assetId = asset.GetId();
AZStd::string assetHint = asset.GetHint();
auto hintSize = aznumeric_cast<uint32_t>(assetHint.size());
for (auto& asset : spawnableAssetData.m_assets)
{
AZ::Data::AssetId assetId = asset.GetId();
AZStd::string assetHint = asset.GetHint();
auto hintSize = aznumeric_cast<uint32_t>(assetHint.size());
byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast<void*>(&assetId));
byteStream.Write(sizeof(uint32_t), reinterpret_cast<void*>(&hintSize));
byteStream.Write(assetHint.size(), assetHint.data());
AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType());
byteStream.Write(sizeof(AZ::Data::AssetId), reinterpret_cast<void*>(&assetId));
byteStream.Write(sizeof(uint32_t), reinterpret_cast<void*>(&hintSize));
byteStream.Write(assetHint.size(), assetHint.data());
AZ::Utils::SaveObjectToStream(byteStream, AZ::DataStream::ST_BINARY, asset.GetData(), asset.GetData()->GetType());
}
}
// Spawnable library needs to be rebuilt since now we have newly registered in-memory spawnable assets
AZ::Interface<INetworkSpawnableLibrary>::Get()->BuildSpawnablesList();
+21
View File
@@ -0,0 +1,21 @@
{
"Amazon":
{
"Tools":
{
"Prefab":
{
"Processing":
{
"Stack": {
"IntegrationTests":
[
{ "$type": "AzToolsFramework::Prefab::PrefabConversionUtils::EditorInfoRemover" },
{ "$type": "AzToolsFramework::Prefab::PrefabConversionUtils::PrefabCatchmentProcessor" }
]
}
}
}
}
}
}
@@ -43,6 +43,8 @@ import types
import functools
import re
from os import path
import ly_test_tools.environment.file_system as file_system
import ly_test_tools.environment.waiter as waiter
import ly_test_tools.environment.process_utils as process_utils
@@ -745,7 +747,9 @@ class EditorTestSuite():
if test_spec.wait_for_debugger:
test_cmdline_args += ["--wait-for-debugger"]
if self.enable_prefab_system:
test_cmdline_args += ["--regset=/Amazon/Preferences/EnablePrefabSystem=true"]
test_cmdline_args += [
"--regset=/Amazon/Preferences/EnablePrefabSystem=true",
f"--regset-file={path.join(workspace.paths.engine_root(), 'Registry', 'prefab.test.setreg')}"]
else:
test_cmdline_args += ["--regset=/Amazon/Preferences/EnablePrefabSystem=false"]
@@ -812,7 +816,9 @@ class EditorTestSuite():
if any([t.wait_for_debugger for t in test_spec_list]):
test_cmdline_args += ["--wait-for-debugger"]
if self.enable_prefab_system:
test_cmdline_args += ["--regset=/Amazon/Preferences/EnablePrefabSystem=true"]
test_cmdline_args += [
"--regset=/Amazon/Preferences/EnablePrefabSystem=true",
f"--regset-file={path.join(workspace.paths.engine_root(), 'Registry', 'prefab.test.setreg')}"]
else:
test_cmdline_args += ["--regset=/Amazon/Preferences/EnablePrefabSystem=false"]
@@ -594,6 +594,7 @@ class TestRunningTests(unittest.TestCase):
mock_get_output_results, mock_create):
mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite()
mock_workspace = mock.MagicMock()
mock_workspace.paths.engine_root.return_value = ""
mock_editor = mock.MagicMock()
mock_test_spec = mock.MagicMock()
mock_test_spec.__name__ = 'mock_test_name'
@@ -620,6 +621,7 @@ class TestRunningTests(unittest.TestCase):
mock_get_output_results, mock_create):
mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite()
mock_workspace = mock.MagicMock()
mock_workspace.paths.engine_root.return_value = ""
mock_editor = mock.MagicMock()
mock_test_spec = mock.MagicMock()
mock_test_spec.__name__ = 'mock_test_name'
@@ -647,6 +649,7 @@ class TestRunningTests(unittest.TestCase):
mock_get_output_results, mock_retrieve_crash, mock_create):
mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite()
mock_workspace = mock.MagicMock()
mock_workspace.paths.engine_root.return_value = ""
mock_editor = mock.MagicMock()
mock_test_spec = mock.MagicMock()
mock_test_spec.__name__ = 'mock_test_name'
@@ -674,6 +677,7 @@ class TestRunningTests(unittest.TestCase):
mock_get_output_results, mock_create):
mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite()
mock_workspace = mock.MagicMock()
mock_workspace.paths.engine_root.return_value = ""
mock_editor = mock.MagicMock()
mock_test_spec = mock.MagicMock()
mock_test_spec.__name__ = 'mock_test_name'
@@ -699,6 +703,7 @@ class TestRunningTests(unittest.TestCase):
mock_retrieve_log, mock_retrieve_editor_log, mock_create):
mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite()
mock_workspace = mock.MagicMock()
mock_workspace.paths.engine_root.return_value = ""
mock_editor = mock.MagicMock()
mock_editor.get_returncode.return_value = 0
mock_test_spec = mock.MagicMock()
@@ -726,6 +731,7 @@ class TestRunningTests(unittest.TestCase):
mock_retrieve_log, mock_retrieve_editor_log, mock_get_results):
mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite()
mock_workspace = mock.MagicMock()
mock_workspace.paths.engine_root.return_value = ""
mock_editor = mock.MagicMock()
mock_editor.get_returncode.return_value = 15
mock_test_spec = mock.MagicMock()
@@ -751,6 +757,7 @@ class TestRunningTests(unittest.TestCase):
mock_get_results, mock_retrieve_crash, mock_create):
mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite()
mock_workspace = mock.MagicMock()
mock_workspace.paths.engine_root.return_value = ""
mock_editor = mock.MagicMock()
mock_editor.get_returncode.return_value = 1
mock_test_spec = mock.MagicMock()
@@ -785,6 +792,7 @@ class TestRunningTests(unittest.TestCase):
mock_get_results, mock_retrieve_crash, mock_create):
mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite()
mock_workspace = mock.MagicMock()
mock_workspace.paths.engine_root.return_value = ""
mock_editor = mock.MagicMock()
mock_editor.get_returncode.return_value = 1
mock_test_spec = mock.MagicMock()
@@ -821,6 +829,7 @@ class TestRunningTests(unittest.TestCase):
mock_get_results, mock_create):
mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite()
mock_workspace = mock.MagicMock()
mock_workspace.paths.engine_root.return_value = ""
mock_editor = mock.MagicMock()
mock_editor.wait.side_effect = ly_test_tools.launchers.exceptions.WaitTimeoutError()
mock_test_spec = mock.MagicMock()