merging latest development
Signed-off-by: kberg-amzn <karlberg@amazon.com>
This commit is contained in:
@@ -253,73 +253,3 @@ class TestAtomEditorComponentsMain(object):
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_generic'])
|
||||
@pytest.mark.system
|
||||
class TestMaterialEditorBasicTests(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_teardown(self, request, workspace, project):
|
||||
def delete_files():
|
||||
file_system.delete(
|
||||
[
|
||||
os.path.join(workspace.paths.project(), "Materials", "test_material.material"),
|
||||
os.path.join(workspace.paths.project(), "Materials", "test_material_1.material"),
|
||||
os.path.join(workspace.paths.project(), "Materials", "test_material_2.material"),
|
||||
],
|
||||
True,
|
||||
True,
|
||||
)
|
||||
# Cleanup our newly created materials
|
||||
delete_files()
|
||||
|
||||
def teardown():
|
||||
# Cleanup our newly created materials
|
||||
delete_files()
|
||||
|
||||
request.addfinalizer(teardown)
|
||||
|
||||
@pytest.mark.parametrize("exe_file_name", ["MaterialEditor"])
|
||||
@pytest.mark.test_case_id("C34448113") # Creating a New Asset.
|
||||
@pytest.mark.test_case_id("C34448114") # Opening an Existing Asset.
|
||||
@pytest.mark.test_case_id("C34448115") # Closing Selected Material.
|
||||
@pytest.mark.test_case_id("C34448116") # Closing All Materials.
|
||||
@pytest.mark.test_case_id("C34448117") # Closing all but Selected Material.
|
||||
@pytest.mark.test_case_id("C34448118") # Saving Material.
|
||||
@pytest.mark.test_case_id("C34448119") # Saving as a New Material.
|
||||
@pytest.mark.test_case_id("C34448120") # Saving as a Child Material.
|
||||
@pytest.mark.test_case_id("C34448121") # Saving all Open Materials.
|
||||
def test_MaterialEditorBasicTests(
|
||||
self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name):
|
||||
|
||||
expected_lines = [
|
||||
"Material opened: True",
|
||||
"Test asset doesn't exist initially: True",
|
||||
"New asset created: True",
|
||||
"New Material opened: True",
|
||||
"Material closed: True",
|
||||
"All documents closed: True",
|
||||
"Close All Except Selected worked as expected: True",
|
||||
"Actual Document saved with changes: True",
|
||||
"Document saved as copy is saved with changes: True",
|
||||
"Document saved as child is saved with changes: True",
|
||||
"Save All worked as expected: True",
|
||||
]
|
||||
unexpected_lines = [
|
||||
# "Trace::Assert",
|
||||
# "Trace::Error",
|
||||
"Traceback (most recent call last):"
|
||||
]
|
||||
|
||||
hydra.launch_and_validate_results(
|
||||
request,
|
||||
TEST_DIRECTORY,
|
||||
generic_launcher,
|
||||
"hydra_AtomMaterialEditor_BasicTests.py",
|
||||
run_python="--runpython",
|
||||
timeout=120,
|
||||
expected_lines=expected_lines,
|
||||
unexpected_lines=unexpected_lines,
|
||||
halt_on_unexpected=True,
|
||||
null_renderer=True,
|
||||
log_file_name="MaterialEditor.log",
|
||||
)
|
||||
|
||||
@@ -70,3 +70,75 @@ class TestAtomEditorComponentsSandbox(object):
|
||||
null_renderer=True,
|
||||
cfg_args=cfg_args,
|
||||
)
|
||||
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_generic'])
|
||||
@pytest.mark.system
|
||||
class TestMaterialEditorBasicTests(object):
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_teardown(self, request, workspace, project):
|
||||
def delete_files():
|
||||
file_system.delete(
|
||||
[
|
||||
os.path.join(workspace.paths.project(), "Materials", "test_material.material"),
|
||||
os.path.join(workspace.paths.project(), "Materials", "test_material_1.material"),
|
||||
os.path.join(workspace.paths.project(), "Materials", "test_material_2.material"),
|
||||
],
|
||||
True,
|
||||
True,
|
||||
)
|
||||
# Cleanup our newly created materials
|
||||
delete_files()
|
||||
|
||||
def teardown():
|
||||
# Cleanup our newly created materials
|
||||
delete_files()
|
||||
|
||||
request.addfinalizer(teardown)
|
||||
|
||||
@pytest.mark.parametrize("exe_file_name", ["MaterialEditor"])
|
||||
@pytest.mark.test_case_id("C34448113") # Creating a New Asset.
|
||||
@pytest.mark.test_case_id("C34448114") # Opening an Existing Asset.
|
||||
@pytest.mark.test_case_id("C34448115") # Closing Selected Material.
|
||||
@pytest.mark.test_case_id("C34448116") # Closing All Materials.
|
||||
@pytest.mark.test_case_id("C34448117") # Closing all but Selected Material.
|
||||
@pytest.mark.test_case_id("C34448118") # Saving Material.
|
||||
@pytest.mark.test_case_id("C34448119") # Saving as a New Material.
|
||||
@pytest.mark.test_case_id("C34448120") # Saving as a Child Material.
|
||||
@pytest.mark.test_case_id("C34448121") # Saving all Open Materials.
|
||||
def test_MaterialEditorBasicTests(
|
||||
self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name):
|
||||
|
||||
expected_lines = [
|
||||
"Material opened: True",
|
||||
"Test asset doesn't exist initially: True",
|
||||
"New asset created: True",
|
||||
"New Material opened: True",
|
||||
"Material closed: True",
|
||||
"All documents closed: True",
|
||||
"Close All Except Selected worked as expected: True",
|
||||
"Actual Document saved with changes: True",
|
||||
"Document saved as copy is saved with changes: True",
|
||||
"Document saved as child is saved with changes: True",
|
||||
"Save All worked as expected: True",
|
||||
]
|
||||
unexpected_lines = [
|
||||
# "Trace::Assert",
|
||||
# "Trace::Error",
|
||||
"Traceback (most recent call last):"
|
||||
]
|
||||
|
||||
hydra.launch_and_validate_results(
|
||||
request,
|
||||
TEST_DIRECTORY,
|
||||
generic_launcher,
|
||||
"hydra_AtomMaterialEditor_BasicTests.py",
|
||||
run_python="--runpython",
|
||||
timeout=120,
|
||||
expected_lines=expected_lines,
|
||||
unexpected_lines=unexpected_lines,
|
||||
halt_on_unexpected=True,
|
||||
null_renderer=True,
|
||||
log_file_name="MaterialEditor.log",
|
||||
)
|
||||
|
||||
|
||||
+158
-48
@@ -9,6 +9,7 @@ from __future__ import annotations
|
||||
from collections import Counter
|
||||
from collections import deque
|
||||
from os import path
|
||||
from pathlib import Path
|
||||
|
||||
from PySide2 import QtWidgets
|
||||
|
||||
@@ -20,6 +21,10 @@ from editor_python_test_tools.utils import Report
|
||||
|
||||
import azlmbr.entity as entity
|
||||
import azlmbr.bus as bus
|
||||
import azlmbr.components as components
|
||||
import azlmbr.editor as editor
|
||||
import azlmbr.globals
|
||||
import azlmbr.math as math
|
||||
import azlmbr.prefab as prefab
|
||||
import editor_python_test_tools.pyside_utils as pyside_utils
|
||||
|
||||
@@ -57,26 +62,46 @@ class PrefabInstance:
|
||||
def __hash__(self):
|
||||
return hash(self.container_entity.id)
|
||||
|
||||
"""
|
||||
See if this instance is valid to be used with other prefab operations.
|
||||
:return: Whether the target instance is valid or not.
|
||||
"""
|
||||
def is_valid(self) -> bool:
|
||||
"""
|
||||
See if this instance is valid to be used with other prefab operations.
|
||||
:return: Whether the target instance is valid or not.
|
||||
"""
|
||||
return self.container_entity.id.IsValid() and self.prefab_file_name in Prefab.existing_prefabs
|
||||
|
||||
"""
|
||||
Reparent this instance to target parent entity.
|
||||
The function will also check pop up dialog ui in editor to see if there's prefab cyclical dependency error while reparenting prefabs.
|
||||
:param parent_entity_id: The id of the entity this instance should be a child of in the transform hierarchy next.
|
||||
"""
|
||||
def has_editor_prefab_component(self) -> bool:
|
||||
"""
|
||||
Check if the instance's container entity contains EditorPrefabComponent.
|
||||
:return: Whether the container entity of target instance has EditorPrefabComponent in it or not.
|
||||
"""
|
||||
return editor.EditorComponentAPIBus(bus.Broadcast, "HasComponentOfType", self.container_entity.id, azlmbr.globals.property.EditorPrefabComponentTypeId)
|
||||
|
||||
def is_at_position(self, expected_position):
|
||||
"""
|
||||
Check if the instance's container entity is at expected position given.
|
||||
:return: Whether the container entity of target instance is at expected position or not.
|
||||
"""
|
||||
actual_position = components.TransformBus(bus.Event, "GetWorldTranslation", self.container_entity.id)
|
||||
is_at_position = actual_position.IsClose(expected_position)
|
||||
|
||||
if not is_at_position:
|
||||
Report.info(f"Prefab Instance Container Entity '{self.container_entity.id.ToString()}'\'s expected position: {expected_position.ToString()}, actual position: {actual_position.ToString()}")
|
||||
|
||||
return is_at_position
|
||||
|
||||
async def ui_reparent_prefab_instance(self, parent_entity_id: EntityId):
|
||||
"""
|
||||
Reparent this instance to target parent entity.
|
||||
The function will also check pop up dialog ui in editor to see if there's prefab cyclical dependency error while reparenting prefabs.
|
||||
:param parent_entity_id: The id of the entity this instance should be a child of in the transform hierarchy next.
|
||||
"""
|
||||
container_entity_id_before_reparent = self.container_entity.id
|
||||
|
||||
original_parent = EditorEntity(self.container_entity.get_parent_id())
|
||||
original_parent_before_reparent_children_ids = set(original_parent.get_children_ids())
|
||||
original_parent_before_reparent_children_ids = {child_id.ToString(): child_id for child_id in original_parent.get_children_ids()}
|
||||
|
||||
new_parent = EditorEntity(parent_entity_id)
|
||||
new_parent_before_reparent_children_ids = set(new_parent.get_children_ids())
|
||||
new_parent_before_reparent_children_ids = {child_id.ToString(): child_id for child_id in new_parent.get_children_ids()}
|
||||
|
||||
pyside_utils.run_soon(lambda: self.container_entity.set_parent_entity(parent_entity_id))
|
||||
pyside_utils.run_soon(lambda: wait_for_propagation())
|
||||
@@ -90,23 +115,28 @@ class PrefabInstance:
|
||||
except pyside_utils.EventLoopTimeoutException:
|
||||
pass
|
||||
|
||||
original_parent_after_reparent_children_ids = set(original_parent.get_children_ids())
|
||||
original_parent_after_reparent_children_ids = {child_id.ToString(): child_id for child_id in original_parent.get_children_ids()}
|
||||
assert len(original_parent_after_reparent_children_ids) == len(original_parent_before_reparent_children_ids) - 1, \
|
||||
"The children count of the Prefab Instance's original parent should be decreased by 1."
|
||||
assert not container_entity_id_before_reparent in original_parent_after_reparent_children_ids, \
|
||||
"This Prefab Instance is still a child entity of its original parent entity."
|
||||
|
||||
new_parent_after_reparent_children_ids = set(new_parent.get_children_ids())
|
||||
new_parent_after_reparent_children_ids = {child_id.ToString(): child_id for child_id in new_parent.get_children_ids()}
|
||||
assert len(new_parent_after_reparent_children_ids) == len(new_parent_before_reparent_children_ids) + 1, \
|
||||
"The children count of the Prefab Instance's new parent should be increased by 1."
|
||||
|
||||
container_entity_id_after_reparent = set(new_parent_after_reparent_children_ids).difference(new_parent_before_reparent_children_ids).pop()
|
||||
after_before_diff = set(new_parent_after_reparent_children_ids.keys()).difference(set(new_parent_before_reparent_children_ids.keys()))
|
||||
container_entity_id_after_reparent = new_parent_after_reparent_children_ids[after_before_diff.pop()]
|
||||
reparented_container_entity = EditorEntity(container_entity_id_after_reparent)
|
||||
reparented_container_entity_parent_id = reparented_container_entity.get_parent_id()
|
||||
has_correct_parent = reparented_container_entity_parent_id.ToString() == parent_entity_id.ToString()
|
||||
assert has_correct_parent, "Prefab Instance reparented is *not* under the expected parent entity"
|
||||
|
||||
current_instance_prefab = Prefab.get_prefab(self.prefab_file_name)
|
||||
current_instance_prefab.instances.remove(self)
|
||||
|
||||
self.container_entity = reparented_container_entity
|
||||
current_instance_prefab.instances.add(self)
|
||||
|
||||
# This is a helper class which contains some of the useful information about a prefab template.
|
||||
class Prefab:
|
||||
@@ -117,31 +147,32 @@ class Prefab:
|
||||
self.file_path: str = get_prefab_file_path(file_path)
|
||||
self.instances: set[PrefabInstance] = set()
|
||||
|
||||
"""
|
||||
Check if a prefab is ready to be used to generate its instances.
|
||||
:param file_path: A unique file path of the target prefab.
|
||||
:return: Whether the target prefab is loaded or not.
|
||||
"""
|
||||
@classmethod
|
||||
def is_prefab_loaded(cls, file_path: str) -> bool:
|
||||
"""
|
||||
Check if a prefab is ready to be used to generate its instances.
|
||||
:param file_path: A unique file path of the target prefab.
|
||||
:return: Whether the target prefab is loaded or not.
|
||||
"""
|
||||
return file_path in Prefab.existing_prefabs
|
||||
|
||||
"""
|
||||
Check if a prefab exists in the directory for files of prefab tests.
|
||||
:param file_name: A unique file name of the target prefab.
|
||||
:return: Whether the target prefab exists or not.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def prefab_exists(cls, file_path: str) -> bool:
|
||||
"""
|
||||
Check if a prefab exists in the directory for files of prefab tests.
|
||||
:param file_name: A unique file name of the target prefab.
|
||||
:return: Whether the target prefab exists or not.
|
||||
"""
|
||||
return path.exists(get_prefab_file_path(file_path))
|
||||
|
||||
"""
|
||||
Return a prefab which can be used immediately.
|
||||
:param file_name: A unique file name of the target prefab.
|
||||
:return: The prefab with given file name.
|
||||
"""
|
||||
@classmethod
|
||||
def get_prefab(cls, file_name: str) -> Prefab:
|
||||
"""
|
||||
Return a prefab which can be used immediately.
|
||||
:param file_name: A unique file name of the target prefab.
|
||||
:return: The prefab with given file name.
|
||||
"""
|
||||
assert file_name, "Received an empty file_name"
|
||||
if Prefab.is_prefab_loaded(file_name):
|
||||
return Prefab.existing_prefabs[file_name]
|
||||
@@ -151,15 +182,15 @@ class Prefab:
|
||||
Prefab.existing_prefabs[file_name] = Prefab(file_name)
|
||||
return new_prefab
|
||||
|
||||
"""
|
||||
Create a prefab in memory and return it. The very first instance of this prefab will also be created.
|
||||
:param entities: The entities that should form the new prefab (along with their descendants).
|
||||
:param file_name: A unique file name of new prefab.
|
||||
:param prefab_instance_name: A name for the very first instance generated while prefab creation. The default instance name is the same as file_name.
|
||||
:return: Created Prefab object and the very first PrefabInstance object owned by the prefab.
|
||||
"""
|
||||
@classmethod
|
||||
def create_prefab(cls, entities: list[EditorEntity], file_name: str, prefab_instance_name: str=None) -> tuple(Prefab, PrefabInstance):
|
||||
"""
|
||||
Create a prefab in memory and return it. The very first instance of this prefab will also be created.
|
||||
:param entities: The entities that should form the new prefab (along with their descendants).
|
||||
:param file_name: A unique file name of new prefab.
|
||||
:param prefab_instance_name: A name for the very first instance generated while prefab creation. The default instance name is the same as file_name.
|
||||
:return: Created Prefab object and the very first PrefabInstance object owned by the prefab.
|
||||
"""
|
||||
assert not Prefab.is_prefab_loaded(file_name), f"Can't create Prefab '{file_name}' since the prefab already exists"
|
||||
|
||||
new_prefab = Prefab(file_name)
|
||||
@@ -169,6 +200,9 @@ class Prefab:
|
||||
|
||||
container_entity_id = create_prefab_result.GetValue()
|
||||
container_entity = EditorEntity(container_entity_id)
|
||||
children_entity_ids = container_entity.get_children_ids()
|
||||
|
||||
assert len(children_entity_ids) == len(entities), f"Entity count of created prefab instance does *not* match the count of given entities."
|
||||
|
||||
if prefab_instance_name:
|
||||
container_entity.set_name(prefab_instance_name)
|
||||
@@ -180,12 +214,12 @@ class Prefab:
|
||||
Prefab.existing_prefabs[file_name] = new_prefab
|
||||
return new_prefab, new_prefab_instance
|
||||
|
||||
"""
|
||||
Remove target prefab instances.
|
||||
:param prefab_instances: Instances to be removed.
|
||||
"""
|
||||
@classmethod
|
||||
def remove_prefabs(cls, prefab_instances: list[PrefabInstance]):
|
||||
"""
|
||||
Remove target prefab instances.
|
||||
:param prefab_instances: Instances to be removed.
|
||||
"""
|
||||
entity_ids_to_remove = []
|
||||
entity_id_queue = [prefab_instance.container_entity for prefab_instance in prefab_instances]
|
||||
while entity_id_queue:
|
||||
@@ -212,15 +246,89 @@ class Prefab:
|
||||
instance_deleted_prefab = Prefab.get_prefab(instance.prefab_file_name)
|
||||
instance_deleted_prefab.instances.remove(instance)
|
||||
instance = PrefabInstance()
|
||||
|
||||
"""
|
||||
Instantiate an instance of this prefab.
|
||||
:param parent_entity: The entity the prefab should be a child of in the transform hierarchy.
|
||||
:param name: A name for newly instantiated prefab instance. The default instance name is the same as this prefab's file name.
|
||||
:param prefab_position: The position in world space the prefab should be instantiated in.
|
||||
:return: Instantiated PrefabInstance object owned by this prefab.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def duplicate_prefabs(cls, prefab_instances: list[PrefabInstance]):
|
||||
"""
|
||||
Duplicate target prefab instances.
|
||||
:param prefab_instances: Instances to be duplicated.
|
||||
:return: PrefabInstance objects of given prefab instances' duplicates.
|
||||
"""
|
||||
assert prefab_instances, "Input list of prefab instances should *not* be empty."
|
||||
|
||||
common_parent = EditorEntity(prefab_instances[0].container_entity.get_parent_id())
|
||||
common_parent_children_ids_before_duplicate = set([child_id.ToString() for child_id in common_parent.get_children_ids()])
|
||||
|
||||
container_entity_ids = [prefab_instance.container_entity.id for prefab_instance in prefab_instances]
|
||||
|
||||
duplicate_prefab_result = prefab.PrefabPublicRequestBus(bus.Broadcast, 'DuplicateEntitiesInInstance', container_entity_ids)
|
||||
assert duplicate_prefab_result.IsSuccess(), f"Prefab operation 'DuplicateEntitiesInInstance' failed. Error: {duplicate_prefab_result.GetError()}"
|
||||
|
||||
wait_for_propagation()
|
||||
|
||||
duplicate_container_entity_ids = duplicate_prefab_result.GetValue()
|
||||
common_parent_children_ids_after_duplicate = set([child_id.ToString() for child_id in common_parent.get_children_ids()])
|
||||
|
||||
assert set([container_entity_id.ToString() for container_entity_id in container_entity_ids]).issubset(common_parent_children_ids_after_duplicate), \
|
||||
"Provided prefab instances are *not* the children of their common parent anymore after duplication."
|
||||
assert common_parent_children_ids_before_duplicate.issubset(common_parent_children_ids_after_duplicate), \
|
||||
"Some children of provided entities' common parent before duplication are *not* the children of the common parent anymore after duplication."
|
||||
assert len(common_parent_children_ids_after_duplicate) == len(common_parent_children_ids_before_duplicate) + len(prefab_instances), \
|
||||
"The children count of the given prefab instances' common parent entity is *not* increased to the expected number."
|
||||
assert EditorEntity(duplicate_container_entity_ids[0]).get_parent_id().ToString() == common_parent.id.ToString(), \
|
||||
"Provided prefab instances' parent should be the same as duplicates' parent."
|
||||
|
||||
duplicate_instances = []
|
||||
for duplicate_container_entity_id in duplicate_container_entity_ids:
|
||||
prefab_file_path = prefab.PrefabPublicRequestBus(bus.Broadcast, 'GetOwningInstancePrefabPath', duplicate_container_entity_id)
|
||||
assert prefab_file_path, "Returned file path should *not* be empty."
|
||||
|
||||
prefab_file_name = Path(prefab_file_path).stem
|
||||
duplicate_instance_prefab = Prefab.get_prefab(prefab_file_name)
|
||||
duplicate_instance = PrefabInstance(prefab_file_path, EditorEntity(duplicate_container_entity_id))
|
||||
duplicate_instance_prefab.instances.add(duplicate_instance)
|
||||
duplicate_instances.append(duplicate_instance)
|
||||
|
||||
return duplicate_instances
|
||||
|
||||
@classmethod
|
||||
def detach_prefab(cls, prefab_instance: PrefabInstance):
|
||||
"""
|
||||
Detach target prefab instance.
|
||||
:param prefab_instances: Instance to be detached.
|
||||
"""
|
||||
parent = EditorEntity(prefab_instance.container_entity.get_parent_id())
|
||||
parent_children_ids_before_detach = set([child_id.ToString() for child_id in parent.get_children_ids()])
|
||||
|
||||
assert prefab_instance.has_editor_prefab_component(), f"Container entity should have EditorPrefabComponent before detachment."
|
||||
|
||||
detach_prefab_result = prefab.PrefabPublicRequestBus(bus.Broadcast, 'DetachPrefab', prefab_instance.container_entity.id)
|
||||
assert detach_prefab_result.IsSuccess(), f"Prefab operation 'DetachPrefab' failed. Error: {detach_prefab_result.GetError()}"
|
||||
|
||||
assert not prefab_instance.has_editor_prefab_component(), f"Container entity should *not* have EditorPrefabComponent after detachment."
|
||||
|
||||
parent_children_ids_after_detach = set([child_id.ToString() for child_id in parent.get_children_ids()])
|
||||
|
||||
assert prefab_instance.container_entity.id.ToString() in parent_children_ids_after_detach, \
|
||||
"Target prefab instance's container entity id should still exists after the detachment and before the propagation."
|
||||
|
||||
assert len(parent_children_ids_after_detach) == len(parent_children_ids_before_detach), \
|
||||
"Parent entity should still keep the same amount of children entities."
|
||||
|
||||
wait_for_propagation()
|
||||
|
||||
instance_owner_prefab = Prefab.get_prefab(prefab_instance.prefab_file_name)
|
||||
instance_owner_prefab.instances.remove(prefab_instance)
|
||||
prefab_instance = PrefabInstance()
|
||||
|
||||
def instantiate(self, parent_entity: EditorEntity=None, name: str=None, prefab_position: Vector3=Vector3()) -> PrefabInstance:
|
||||
"""
|
||||
Instantiate an instance of this prefab.
|
||||
:param parent_entity: The entity the prefab should be a child of in the transform hierarchy.
|
||||
:param name: A name for newly instantiated prefab instance. The default instance name is the same as this prefab's file name.
|
||||
:param prefab_position: The position in world space the prefab should be instantiated in.
|
||||
:return: Instantiated PrefabInstance object owned by this prefab.
|
||||
"""
|
||||
parent_entity_id = parent_entity.id if parent_entity is not None else EntityId()
|
||||
|
||||
instantiate_prefab_result = prefab.PrefabPublicRequestBus(
|
||||
@@ -240,4 +348,6 @@ class Prefab:
|
||||
assert not new_prefab_instance in self.instances, "This prefab instance is already existed before this instantiation."
|
||||
self.instances.add(new_prefab_instance)
|
||||
|
||||
assert new_prefab_instance.is_at_position(prefab_position), "This prefab instance is *not* at expected position."
|
||||
|
||||
return new_prefab_instance
|
||||
|
||||
@@ -47,3 +47,11 @@ class TestAutomation(TestAutomationBase):
|
||||
def test_PrefabBasicWorkflow_CreateAndReparentPrefab(self, request, workspace, editor, launcher_platform):
|
||||
from .tests import PrefabBasicWorkflow_CreateAndReparentPrefab as test_module
|
||||
self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False)
|
||||
|
||||
def test_PrefabBasicWorkflow_CreateReparentAndDetachPrefab(self, request, workspace, editor, launcher_platform):
|
||||
from .tests import PrefabBasicWorkflow_CreateReparentAndDetachPrefab as test_module
|
||||
self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False)
|
||||
|
||||
def test_PrefabBasicWorkflow_CreateAndDuplicatePrefab(self, request, workspace, editor, launcher_platform):
|
||||
from .tests import PrefabBasicWorkflow_CreateAndDuplicatePrefab as test_module
|
||||
self._run_prefab_test(request, workspace, editor, test_module)
|
||||
|
||||
+3
-4
@@ -16,16 +16,15 @@ def PrefabBasicWorkflow_CreateAndDeletePrefab():
|
||||
|
||||
prefab_test_utils.open_base_tests_level()
|
||||
|
||||
# Creates a new Entity at the root level
|
||||
# Asserts if creation didn't succeed
|
||||
# Creates a new entity at the root level
|
||||
car_entity = EditorEntity.create_editor_entity()
|
||||
car_prefab_entities = [car_entity]
|
||||
|
||||
# Asserts if prefab creation doesn't succeeds
|
||||
# Creates a prefab from the new entity
|
||||
_, car = Prefab.create_prefab(
|
||||
car_prefab_entities, CAR_PREFAB_FILE_NAME)
|
||||
|
||||
# Asserts if prefab deletion fails
|
||||
# Deletes the prefab instance
|
||||
Prefab.remove_prefabs([car])
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
def PrefabBasicWorkflow_CreateAndDuplicatePrefab():
|
||||
|
||||
CAR_PREFAB_FILE_NAME = 'car_prefab'
|
||||
|
||||
from editor_python_test_tools.editor_entity_utils import EditorEntity
|
||||
from editor_python_test_tools.prefab_utils import Prefab
|
||||
|
||||
import PrefabTestUtils as prefab_test_utils
|
||||
|
||||
prefab_test_utils.open_base_tests_level()
|
||||
|
||||
# Creates a new entity at the root level
|
||||
car_entity = EditorEntity.create_editor_entity()
|
||||
car_prefab_entities = [car_entity]
|
||||
|
||||
# Creates a prefab from the new entity
|
||||
_, car = Prefab.create_prefab(
|
||||
car_prefab_entities, CAR_PREFAB_FILE_NAME)
|
||||
|
||||
# Duplicates the prefab instance
|
||||
Prefab.duplicate_prefabs([car])
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(PrefabBasicWorkflow_CreateAndDuplicatePrefab)
|
||||
+5
-6
@@ -22,24 +22,23 @@ def PrefabBasicWorkflow_CreateAndReparentPrefab():
|
||||
|
||||
prefab_test_utils.open_base_tests_level()
|
||||
|
||||
# Creates a new Entity at the root level
|
||||
# Asserts if creation didn't succeed
|
||||
# Creates a new car entity at the root level
|
||||
car_entity = EditorEntity.create_editor_entity()
|
||||
car_prefab_entities = [car_entity]
|
||||
|
||||
# Checks for prefab creation passed or not
|
||||
# Creates a prefab from the car entity
|
||||
_, car = Prefab.create_prefab(
|
||||
car_prefab_entities, CAR_PREFAB_FILE_NAME)
|
||||
|
||||
# Creates another new Entity at the root level
|
||||
# Creates another new wheel entity at the root level
|
||||
wheel_entity = EditorEntity.create_editor_entity()
|
||||
wheel_prefab_entities = [wheel_entity]
|
||||
|
||||
# Checks for wheel prefab creation passed or not
|
||||
# Creates another prefab from the wheel entity
|
||||
_, wheel = Prefab.create_prefab(
|
||||
wheel_prefab_entities, WHEEL_PREFAB_FILE_NAME)
|
||||
|
||||
# Checks for prefab reparenting passed or not
|
||||
# Reparents the wheel prefab instance to the container entity of the car prefab instance
|
||||
await wheel.ui_reparent_prefab_instance(car.container_entity.id)
|
||||
|
||||
run_test()
|
||||
|
||||
@@ -17,12 +17,11 @@ def PrefabBasicWorkflow_CreatePrefab():
|
||||
|
||||
prefab_test_utils.open_base_tests_level()
|
||||
|
||||
# Creates a new Entity at the root level
|
||||
# Asserts if creation didn't succeed
|
||||
# Creates a new entity at the root level
|
||||
car_entity = EditorEntity.create_editor_entity()
|
||||
car_prefab_entities = [car_entity]
|
||||
|
||||
# Checks for prefab creation passed or not
|
||||
# Creates a prefab from the new entity
|
||||
Prefab.create_prefab(car_prefab_entities, CAR_PREFAB_FILE_NAME)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
def PrefabBasicWorkflow_CreateReparentAndDetachPrefab():
|
||||
|
||||
CAR_PREFAB_FILE_NAME = 'car_prefab'
|
||||
WHEEL_PREFAB_FILE_NAME = 'wheel_prefab'
|
||||
|
||||
import editor_python_test_tools.pyside_utils as pyside_utils
|
||||
|
||||
@pyside_utils.wrap_async
|
||||
async def run_test():
|
||||
|
||||
from editor_python_test_tools.editor_entity_utils import EditorEntity
|
||||
from editor_python_test_tools.prefab_utils import Prefab
|
||||
|
||||
import PrefabTestUtils as prefab_test_utils
|
||||
|
||||
prefab_test_utils.open_base_tests_level()
|
||||
|
||||
# Creates a new car entity at the root level
|
||||
car_entity = EditorEntity.create_editor_entity()
|
||||
car_prefab_entities = [car_entity]
|
||||
|
||||
# Creates a prefab from the car entity
|
||||
_, car = Prefab.create_prefab(
|
||||
car_prefab_entities, CAR_PREFAB_FILE_NAME)
|
||||
|
||||
# Creates another new wheel entity at the root level
|
||||
wheel_entity = EditorEntity.create_editor_entity()
|
||||
wheel_prefab_entities = [wheel_entity]
|
||||
|
||||
# Creates another prefab from the wheel entity
|
||||
_, wheel = Prefab.create_prefab(
|
||||
wheel_prefab_entities, WHEEL_PREFAB_FILE_NAME)
|
||||
|
||||
# Reparents the wheel prefab instance to the container entity of the car prefab instance
|
||||
await wheel.ui_reparent_prefab_instance(car.container_entity.id)
|
||||
|
||||
# Detaches the wheel prefab instance
|
||||
Prefab.detach_prefab(wheel)
|
||||
|
||||
run_test()
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(PrefabBasicWorkflow_CreateReparentAndDetachPrefab)
|
||||
+1
-2
@@ -19,9 +19,8 @@ def PrefabBasicWorkflow_InstantiatePrefab():
|
||||
|
||||
prefab_test_utils.open_base_tests_level()
|
||||
|
||||
# Checks for prefab instantiation passed or not
|
||||
# Instantiates a new car prefab instance
|
||||
test_prefab = Prefab.get_prefab(EXISTING_TEST_PREFAB_FILE_NAME)
|
||||
|
||||
test_instance = test_prefab.instantiate(
|
||||
prefab_position=INSTANTIATED_TEST_PREFAB_POSITION)
|
||||
|
||||
|
||||
@@ -18,20 +18,6 @@ import azlmbr.components as components
|
||||
import azlmbr.entity as entity
|
||||
import azlmbr.legacy.general as general
|
||||
|
||||
def check_entity_at_position(entity_id, expected_entity_position):
|
||||
entity_at_expected_position_result = (
|
||||
"entity is at expected position",
|
||||
"entity is *not* at expected position")
|
||||
|
||||
actual_entity_position = components.TransformBus(bus.Event, "GetWorldTranslation", entity_id)
|
||||
is_at_position = actual_entity_position.IsClose(expected_entity_position)
|
||||
Report.result(entity_at_expected_position_result, is_at_position)
|
||||
|
||||
if not is_at_position:
|
||||
Report.info(f"Entity '{entity_id.ToString()}'\'s expected position: {expected_entity_position.ToString()}, actual position: {actual_entity_position.ToString()}")
|
||||
|
||||
return is_at_position
|
||||
|
||||
def check_entity_children_count(entity_id, expected_children_count):
|
||||
entity_children_count_matched_result = (
|
||||
"Entity with a unique name found",
|
||||
@@ -47,19 +33,6 @@ def check_entity_children_count(entity_id, expected_children_count):
|
||||
|
||||
return entity_children_count_matched
|
||||
|
||||
def get_children_ids_by_name(entity_id, entity_name):
|
||||
entity = EditorEntity(entity_id)
|
||||
children_entity_ids = entity.get_children_ids()
|
||||
|
||||
result = []
|
||||
for child_entity_id in children_entity_ids:
|
||||
child_entity = EditorEntity(child_entity_id)
|
||||
child_entity_name = child_entity.get_name()
|
||||
if child_entity_name == entity_name:
|
||||
result.append(child_entity_id)
|
||||
|
||||
return result
|
||||
|
||||
def open_base_tests_level():
|
||||
helper.init_idle()
|
||||
helper.open_level("Prefab", "Base")
|
||||
|
||||
+3
@@ -9,6 +9,7 @@ import os
|
||||
import pytest
|
||||
|
||||
import ly_test_tools.environment.file_system as file_system
|
||||
import ly_test_tools._internal.pytest_plugin as internal_plugin
|
||||
from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite
|
||||
|
||||
|
||||
@@ -79,6 +80,8 @@ class TestAutomation(EditorTestSuite):
|
||||
class test_LandscapeCanvas_GradientNodes_EntityRemovedOnNodeDelete(EditorSharedTest):
|
||||
from .EditorScripts import GradientNodes_EntityRemovedOnNodeDelete as test_module
|
||||
|
||||
@pytest.mark.skipif("debug" == os.path.basename(internal_plugin.build_directory),
|
||||
reason="https://github.com/o3de/o3de/issues/4872")
|
||||
class test_LandscapeCanvas_GraphUpdates_UpdateComponents(EditorSharedTest):
|
||||
from .EditorScripts import GraphUpdates_UpdateComponents as test_module
|
||||
|
||||
|
||||
@@ -1,923 +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
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "EditorDefs.h"
|
||||
|
||||
#include "ColorGradientCtrl.h"
|
||||
|
||||
// Qt
|
||||
#include <QPainter>
|
||||
#include <QToolTip>
|
||||
|
||||
// AzQtComponents
|
||||
#include <AzQtComponents/Components/Widgets/ColorPicker.h>
|
||||
|
||||
|
||||
#define MIN_TIME_EPSILON 0.01f
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
CColorGradientCtrl::CColorGradientCtrl(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
m_nActiveKey = -1;
|
||||
m_nHitKeyIndex = -1;
|
||||
m_nKeyDrawRadius = 3;
|
||||
m_bTracking = false;
|
||||
m_pSpline = nullptr;
|
||||
m_fMinTime = -1;
|
||||
m_fMaxTime = 1;
|
||||
m_fMinValue = -1;
|
||||
m_fMaxValue = 1;
|
||||
m_fTooltipScaleX = 1;
|
||||
m_fTooltipScaleY = 1;
|
||||
m_bNoTimeMarker = true;
|
||||
m_bLockFirstLastKey = false;
|
||||
m_bNoZoom = true;
|
||||
|
||||
ClearSelection();
|
||||
|
||||
m_bSelectedKeys.reserve(0);
|
||||
|
||||
m_fTimeMarker = -10;
|
||||
|
||||
m_grid.zoom.x = 100;
|
||||
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
CColorGradientCtrl::~CColorGradientCtrl()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// QColorGradientCtrl message handlers
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
|
||||
QRect rc(QPoint(0, 0), event->size());
|
||||
m_rcGradient = rc;
|
||||
m_rcGradient.setHeight(m_rcGradient.height() - 11);
|
||||
//m_rcGradient.DeflateRect(4,4);
|
||||
|
||||
m_grid.rect = m_rcGradient;
|
||||
if (m_bNoZoom)
|
||||
{
|
||||
m_grid.zoom.x = static_cast<f32>(m_grid.rect.width());
|
||||
}
|
||||
|
||||
m_rcKeys = rc;
|
||||
m_rcKeys.setTop(m_rcKeys.bottom() - 10);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::SetZoom(float fZoom)
|
||||
{
|
||||
m_grid.zoom.x = fZoom;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::SetOrigin(float fOffset)
|
||||
{
|
||||
m_grid.origin.x = fOffset;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
QPoint CColorGradientCtrl::KeyToPoint(int nKey)
|
||||
{
|
||||
if (nKey >= 0)
|
||||
{
|
||||
return TimeToPoint(m_pSpline->GetKeyTime(nKey));
|
||||
}
|
||||
return QPoint(0, 0);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
QPoint CColorGradientCtrl::TimeToPoint(float time)
|
||||
{
|
||||
return QPoint(m_grid.WorldToClient(Vec2(time, 0)).x(), m_rcGradient.height() / 2);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
AZ::Color CColorGradientCtrl::TimeToColor(float time)
|
||||
{
|
||||
ISplineInterpolator::ValueType val;
|
||||
m_pSpline->Interpolate(time, val);
|
||||
const AZ::Color col = ValueToColor(val);
|
||||
return col;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::PointToTimeValue(QPoint point, float& time, ISplineInterpolator::ValueType& val)
|
||||
{
|
||||
time = XOfsToTime(point.x());
|
||||
ColorToValue(TimeToColor(time), val);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
float CColorGradientCtrl::XOfsToTime(int x)
|
||||
{
|
||||
return m_grid.ClientToWorld(QPoint(x, 0)).x;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
QPoint CColorGradientCtrl::XOfsToPoint(int x)
|
||||
{
|
||||
return TimeToPoint(XOfsToTime(x));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
AZ::Color CColorGradientCtrl::XOfsToColor(int x)
|
||||
{
|
||||
return TimeToColor(XOfsToTime(x));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::paintEvent(QPaintEvent* e)
|
||||
{
|
||||
QPainter painter(this);
|
||||
|
||||
QRect rcClient = rect();
|
||||
|
||||
if (m_pSpline)
|
||||
{
|
||||
m_bSelectedKeys.resize(m_pSpline->GetKeyCount());
|
||||
}
|
||||
{
|
||||
if (!isEnabled())
|
||||
{
|
||||
painter.setBrush(palette().button());
|
||||
painter.drawRect(rcClient);
|
||||
return;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Fill keys backgound.
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
QRect rcKeys = m_rcKeys.intersected(e->rect());
|
||||
painter.setBrush(palette().button());
|
||||
painter.drawRect(rcKeys);
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//Draw Keys and Curve
|
||||
if (m_pSpline)
|
||||
{
|
||||
DrawGradient(e, &painter);
|
||||
DrawKeys(e, &painter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::DrawGradient(QPaintEvent* e, QPainter* painter)
|
||||
{
|
||||
//Draw Curve
|
||||
// create and select a thick, white pen
|
||||
painter->setPen(QPen(QColor(128, 255, 128), 1, Qt::SolidLine));
|
||||
|
||||
const QRect rcClip = e->rect().intersected(m_rcGradient);
|
||||
const int right = rcClip.left() + rcClip.width();
|
||||
for (int x = rcClip.left(); x < right; x++)
|
||||
{
|
||||
const AZ::Color col = XOfsToColor(x);
|
||||
QPen pen(QColor(col.GetR8(), col.GetG8(), col.GetR8(), col.GetA8()), 1, Qt::SolidLine);
|
||||
painter->setPen(pen);
|
||||
painter->drawLine(x, m_rcGradient.top(), x, m_rcGradient.top() + m_rcGradient.height());
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::DrawKeys(QPaintEvent* e, QPainter* painter)
|
||||
{
|
||||
if (!m_pSpline)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// create and select a white pen
|
||||
painter->setPen(QPen(QColor(0, 0, 0), 1, Qt::SolidLine));
|
||||
|
||||
QRect rcClip = e->rect();
|
||||
|
||||
m_bSelectedKeys.resize(m_pSpline->GetKeyCount());
|
||||
|
||||
for (int i = 0; i < m_pSpline->GetKeyCount(); i++)
|
||||
{
|
||||
float time = m_pSpline->GetKeyTime(i);
|
||||
QPoint pt = TimeToPoint(time);
|
||||
|
||||
if (pt.x() < rcClip.left() - 8 || pt.x() > rcClip.left() + rcClip.width() + 8)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const AZ::Color clr = TimeToColor(time);
|
||||
QBrush brush(QColor(clr.GetR8(), clr.GetG8(), clr.GetB8(), clr.GetA8()));
|
||||
painter->setBrush(brush);
|
||||
|
||||
// Find the midpoints of the top, right, left, and bottom
|
||||
// of the client area. They will be the vertices of our polygon.
|
||||
QPoint pts[3];
|
||||
pts[0].rx() = pt.x();
|
||||
pts[0].ry() = m_rcKeys.top() + 1;
|
||||
pts[1].rx() = pt.x() - 5;
|
||||
pts[1].ry() = m_rcKeys.top() + 8;
|
||||
pts[2].rx() = pt.x() + 5;
|
||||
pts[2].ry() = m_rcKeys.top() + 8;
|
||||
painter->drawPolygon(pts, 3);
|
||||
|
||||
if (m_bSelectedKeys[i])
|
||||
{
|
||||
QPen pen(QColor(200, 0, 0), 1, Qt::SolidLine);
|
||||
QPen oldPen = painter->pen();
|
||||
painter->setPen(pen);
|
||||
painter->drawPolygon(pts, 3);
|
||||
painter->setPen(oldPen);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_bNoTimeMarker)
|
||||
{
|
||||
QPen timePen(QColor(255, 0, 255), 1, Qt::SolidLine);
|
||||
painter->setPen(timePen);
|
||||
QPoint pt = TimeToPoint(m_fTimeMarker);
|
||||
painter->drawLine(pt.x(), m_rcGradient.top() + 1, pt.x(), m_rcGradient.bottom() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void CColorGradientCtrl::UpdateTooltip(QPoint pos)
|
||||
{
|
||||
if (m_nHitKeyIndex >= 0)
|
||||
{
|
||||
float time = m_pSpline->GetKeyTime(m_nHitKeyIndex);
|
||||
ISplineInterpolator::ValueType val;
|
||||
m_pSpline->GetKeyValue(m_nHitKeyIndex, val);
|
||||
|
||||
AZ::Color col = TimeToColor(time);
|
||||
int cont_s = (m_pSpline->GetKeyFlags(m_nHitKeyIndex) >> SPLINE_KEY_TANGENT_IN_SHIFT) & SPLINE_KEY_TANGENT_LINEAR ? 1 : 2;
|
||||
int cont_d = (m_pSpline->GetKeyFlags(m_nHitKeyIndex) >> SPLINE_KEY_TANGENT_OUT_SHIFT) & SPLINE_KEY_TANGENT_LINEAR ? 1 : 2;
|
||||
|
||||
QString tipText(tr("%1 : %2,%3,%4 [%5,%6]").arg(time * m_fTooltipScaleX, 0, 'f', 2).arg(col.GetR8()).arg(col.GetG8()).arg(col.GetB8()).arg(cont_s).arg(cont_d));
|
||||
const QPoint globalPos = mapToGlobal(pos);
|
||||
QToolTip::showText(mapToGlobal(pos), tipText, this, QRect(globalPos, QSize(1, 1)));
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//Mouse Message Handlers
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton)
|
||||
{
|
||||
OnLButtonDown(event);
|
||||
}
|
||||
else if (event->button() == Qt::RightButton)
|
||||
{
|
||||
OnRButtonDown(event);
|
||||
}
|
||||
}
|
||||
|
||||
void CColorGradientCtrl::OnLButtonDown([[maybe_unused]] QMouseEvent* event)
|
||||
{
|
||||
if (m_bTracking)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!m_pSpline)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
setFocus();
|
||||
|
||||
switch (m_hitCode)
|
||||
{
|
||||
case HIT_KEY:
|
||||
StartTracking();
|
||||
SetActiveKey(m_nHitKeyIndex);
|
||||
break;
|
||||
|
||||
/*
|
||||
case HIT_SPLINE:
|
||||
{
|
||||
// Cycle the spline slope of the nearest key.
|
||||
int flags = m_pSpline->GetKeyFlags(m_nHitKeyIndex);
|
||||
if (m_nHitKeyDist < 0)
|
||||
// Toggle left side.
|
||||
flags ^= SPLINE_KEY_TANGENT_LINEAR << SPLINE_KEY_TANGENT_IN_SHIFT;
|
||||
if (m_nHitKeyDist > 0)
|
||||
// Toggle right side.
|
||||
flags ^= SPLINE_KEY_TANGENT_LINEAR << SPLINE_KEY_TANGENT_OUT_SHIFT;
|
||||
m_pSpline->SetKeyFlags(m_nHitKeyIndex, flags);
|
||||
m_pSpline->Update();
|
||||
|
||||
SetActiveKey(-1);
|
||||
SendNotifyEvent( CLRGRDN_CHANGE );
|
||||
if (m_updateCallback)
|
||||
m_updateCallback(this);
|
||||
break;
|
||||
}
|
||||
*/
|
||||
|
||||
case HIT_NOTHING:
|
||||
SetActiveKey(-1);
|
||||
break;
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::OnRButtonDown([[maybe_unused]] QMouseEvent* event)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::mouseDoubleClickEvent(QMouseEvent* event)
|
||||
{
|
||||
if (!m_pSpline)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->button() != Qt::LeftButton)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (m_hitCode)
|
||||
{
|
||||
case HIT_SPLINE:
|
||||
{
|
||||
int iIndex = InsertKey(event->pos());
|
||||
SetActiveKey(iIndex);
|
||||
EditKey(iIndex);
|
||||
|
||||
update();
|
||||
}
|
||||
break;
|
||||
case HIT_KEY:
|
||||
{
|
||||
EditKey(m_nHitKeyIndex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::mouseMoveEvent(QMouseEvent* event)
|
||||
{
|
||||
if (!m_pSpline)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_bTracking)
|
||||
{
|
||||
switch (HitTest(event->pos()))
|
||||
{
|
||||
case HIT_SPLINE:
|
||||
{
|
||||
setCursor(CMFCUtils::LoadCursor(IDC_ARRWHITE));
|
||||
} break;
|
||||
case HIT_KEY:
|
||||
{
|
||||
setCursor(CMFCUtils::LoadCursor(IDC_ARRBLCK));
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_bTracking)
|
||||
{
|
||||
TrackKey(event->pos());
|
||||
}
|
||||
|
||||
if (m_bTracking || m_nHitKeyIndex >= 0)
|
||||
{
|
||||
UpdateTooltip(event->pos());
|
||||
}
|
||||
else
|
||||
{
|
||||
QToolTip::hideText();
|
||||
}
|
||||
}
|
||||
|
||||
void CColorGradientCtrl::mouseReleaseEvent(QMouseEvent* event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton)
|
||||
{
|
||||
OnLButtonUp(event);
|
||||
}
|
||||
else if (event->button() == Qt::RightButton)
|
||||
{
|
||||
OnRButtonUp(event);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::OnLButtonUp(QMouseEvent* event)
|
||||
{
|
||||
if (!m_pSpline)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_bTracking)
|
||||
{
|
||||
StopTracking(event->pos());
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::OnRButtonUp([[maybe_unused]] QMouseEvent* event)
|
||||
{
|
||||
if (!m_pSpline)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::SetActiveKey(int nIndex)
|
||||
{
|
||||
ClearSelection();
|
||||
|
||||
//Activate New Key
|
||||
if (nIndex >= 0)
|
||||
{
|
||||
m_bSelectedKeys[nIndex] = true;
|
||||
}
|
||||
m_nActiveKey = nIndex;
|
||||
update();
|
||||
|
||||
SendNotifyEvent(CLRGRDN_ACTIVE_KEY_CHANGE);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::SetSpline(ISplineInterpolator* pSpline, bool bRedraw)
|
||||
{
|
||||
if (pSpline != m_pSpline)
|
||||
{
|
||||
//if (pSpline && pSpline->GetNumDimensions() != 3)
|
||||
//return;
|
||||
m_pSpline = pSpline;
|
||||
m_nActiveKey = -1;
|
||||
}
|
||||
|
||||
ClearSelection();
|
||||
|
||||
if (bRedraw)
|
||||
{
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
ISplineInterpolator* CColorGradientCtrl::GetSpline()
|
||||
{
|
||||
return m_pSpline;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
bool bProcessed = false;
|
||||
|
||||
if (m_nActiveKey != -1 && m_pSpline)
|
||||
{
|
||||
switch (event->key())
|
||||
{
|
||||
case Qt::Key_Delete:
|
||||
{
|
||||
RemoveKey(m_nActiveKey);
|
||||
bProcessed = true;
|
||||
} break;
|
||||
case Qt::Key_Up:
|
||||
{
|
||||
CUndo undo("Move Spline Key");
|
||||
QPoint point = KeyToPoint(m_nActiveKey);
|
||||
point.rx() -= 1;
|
||||
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
|
||||
TrackKey(point);
|
||||
bProcessed = true;
|
||||
} break;
|
||||
case Qt::Key_Down:
|
||||
{
|
||||
CUndo undo("Move Spline Key");
|
||||
QPoint point = KeyToPoint(m_nActiveKey);
|
||||
point.rx() += 1;
|
||||
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
|
||||
TrackKey(point);
|
||||
bProcessed = true;
|
||||
} break;
|
||||
case Qt::Key_Left:
|
||||
{
|
||||
CUndo undo("Move Spline Key");
|
||||
QPoint point = KeyToPoint(m_nActiveKey);
|
||||
point.rx() -= 1;
|
||||
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
|
||||
TrackKey(point);
|
||||
bProcessed = true;
|
||||
} break;
|
||||
case Qt::Key_Right:
|
||||
{
|
||||
CUndo undo("Move Spline Key");
|
||||
QPoint point = KeyToPoint(m_nActiveKey);
|
||||
point.rx() += 1;
|
||||
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
|
||||
TrackKey(point);
|
||||
bProcessed = true;
|
||||
} break;
|
||||
|
||||
default:
|
||||
break; //do nothing
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
event->setAccepted(bProcessed);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
CColorGradientCtrl::EHitCode CColorGradientCtrl::HitTest(QPoint point)
|
||||
{
|
||||
if (!m_pSpline)
|
||||
{
|
||||
return HIT_NOTHING;
|
||||
}
|
||||
|
||||
ISplineInterpolator::ValueType val;
|
||||
float time;
|
||||
PointToTimeValue(point, time, val);
|
||||
|
||||
QRect rc = rect();
|
||||
|
||||
m_nHitKeyIndex = -1;
|
||||
|
||||
if (rc.contains(point))
|
||||
{
|
||||
m_nHitKeyDist = 0xFFFF;
|
||||
m_hitCode = HIT_SPLINE;
|
||||
|
||||
for (int i = 0; i < m_pSpline->GetKeyCount(); i++)
|
||||
{
|
||||
QPoint splinePt = TimeToPoint(m_pSpline->GetKeyTime(i));
|
||||
if (abs(point.x() - splinePt.x()) < abs(m_nHitKeyDist))
|
||||
{
|
||||
m_nHitKeyIndex = i;
|
||||
m_nHitKeyDist = point.x() - splinePt.x();
|
||||
}
|
||||
}
|
||||
if (abs(m_nHitKeyDist) < 4)
|
||||
{
|
||||
m_hitCode = HIT_KEY;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_hitCode = HIT_NOTHING;
|
||||
}
|
||||
|
||||
return m_hitCode;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::StartTracking()
|
||||
{
|
||||
m_bTracking = true;
|
||||
|
||||
GetIEditor()->BeginUndo();
|
||||
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
|
||||
|
||||
setCursor(CMFCUtils::LoadCursor(IDC_ARRBLCKCROSS));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::TrackKey(QPoint point)
|
||||
{
|
||||
if (point.x() < m_rcGradient.left() || point.y() > m_rcGradient.right())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int nKey = m_nHitKeyIndex;
|
||||
|
||||
if (nKey >= 0)
|
||||
{
|
||||
ISplineInterpolator::ValueType val;
|
||||
float time;
|
||||
PointToTimeValue(point, time, val);
|
||||
|
||||
// Clamp to min/max time.
|
||||
if (time < m_fMinTime || time > m_fMaxTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < m_pSpline->GetKeyCount(); i++)
|
||||
{
|
||||
// Switch to next key.
|
||||
if ((m_pSpline->GetKeyTime(i) < time && i > nKey) ||
|
||||
(m_pSpline->GetKeyTime(i) > time && i < nKey))
|
||||
{
|
||||
m_pSpline->SetKeyTime(nKey, time);
|
||||
m_pSpline->Update();
|
||||
SetActiveKey(i);
|
||||
m_nHitKeyIndex = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_bLockFirstLastKey || (nKey != 0 && nKey != m_pSpline->GetKeyCount() - 1))
|
||||
{
|
||||
m_pSpline->SetKeyTime(nKey, time);
|
||||
m_pSpline->Update();
|
||||
}
|
||||
|
||||
SendNotifyEvent(CLRGRDN_CHANGE);
|
||||
if (m_updateCallback)
|
||||
{
|
||||
m_updateCallback(this);
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::StopTracking(QPoint point)
|
||||
{
|
||||
if (!m_bTracking)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetIEditor()->AcceptUndo("Spline Move");
|
||||
|
||||
if (m_nHitKeyIndex >= 0)
|
||||
{
|
||||
QRect rc = rect();
|
||||
rc = rc.marginsAdded(QMargins(100, 100, 100, 100));
|
||||
if (!rc.contains(point))
|
||||
{
|
||||
RemoveKey(m_nHitKeyIndex);
|
||||
}
|
||||
}
|
||||
|
||||
m_bTracking = false;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::EditKey(int nKey)
|
||||
{
|
||||
if (!m_pSpline)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (nKey < 0 || nKey >= m_pSpline->GetKeyCount())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetActiveKey(nKey);
|
||||
|
||||
ISplineInterpolator::ValueType val;
|
||||
m_pSpline->GetKeyValue(nKey, val);
|
||||
|
||||
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
|
||||
|
||||
AzQtComponents::ColorPicker dlg(AzQtComponents::ColorPicker::Configuration::RGB);
|
||||
dlg.setCurrentColor(ValueToColor(val));
|
||||
dlg.setSelectedColor(ValueToColor(val));
|
||||
connect(&dlg, &AzQtComponents::ColorPicker::currentColorChanged, this, &CColorGradientCtrl::OnKeyColorChanged);
|
||||
if (dlg.exec() == QDialog::Accepted)
|
||||
{
|
||||
CUndo undo("Modify Gradient Color");
|
||||
OnKeyColorChanged(dlg.selectedColor());
|
||||
}
|
||||
else
|
||||
{
|
||||
OnKeyColorChanged(ValueToColor(val));
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::OnKeyColorChanged(const AZ::Color& color)
|
||||
{
|
||||
int nKey = m_nActiveKey;
|
||||
if (!m_pSpline)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (nKey < 0 || nKey >= m_pSpline->GetKeyCount())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ISplineInterpolator::ValueType val;
|
||||
ColorToValue(color, val);
|
||||
m_pSpline->SetKeyValue(nKey, val);
|
||||
update();
|
||||
|
||||
if (m_bLockFirstLastKey)
|
||||
{
|
||||
if (nKey == 0)
|
||||
{
|
||||
m_pSpline->SetKeyValue(m_pSpline->GetKeyCount() - 1, val);
|
||||
}
|
||||
else if (nKey == m_pSpline->GetKeyCount() - 1)
|
||||
{
|
||||
m_pSpline->SetKeyValue(0, val);
|
||||
}
|
||||
}
|
||||
m_pSpline->Update();
|
||||
SendNotifyEvent(CLRGRDN_CHANGE);
|
||||
if (m_updateCallback)
|
||||
{
|
||||
m_updateCallback(this);
|
||||
}
|
||||
|
||||
GetIEditor()->UpdateViews(eRedrawViewports);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::RemoveKey(int nKey)
|
||||
{
|
||||
if (!m_pSpline)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (m_bLockFirstLastKey)
|
||||
{
|
||||
if (nKey == 0 || nKey == m_pSpline->GetKeyCount() - 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CUndo undo("Remove Spline Key");
|
||||
|
||||
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
|
||||
m_nActiveKey = -1;
|
||||
m_nHitKeyIndex = -1;
|
||||
if (m_pSpline)
|
||||
{
|
||||
m_pSpline->RemoveKey(nKey);
|
||||
m_pSpline->Update();
|
||||
}
|
||||
SendNotifyEvent(CLRGRDN_CHANGE);
|
||||
if (m_updateCallback)
|
||||
{
|
||||
m_updateCallback(this);
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
int CColorGradientCtrl::InsertKey(QPoint point)
|
||||
{
|
||||
CUndo undo("Spline Insert Key");
|
||||
|
||||
ISplineInterpolator::ValueType val;
|
||||
|
||||
float time;
|
||||
PointToTimeValue(point, time, val);
|
||||
|
||||
if (time < m_fMinTime || time > m_fMaxTime)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < m_pSpline->GetKeyCount(); i++)
|
||||
{
|
||||
// Skip if any key already have time that is very close.
|
||||
if (fabs(m_pSpline->GetKeyTime(i) - time) < MIN_TIME_EPSILON)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
|
||||
|
||||
m_pSpline->InsertKey(time, val);
|
||||
m_pSpline->Interpolate(time, val);
|
||||
ClearSelection();
|
||||
update();
|
||||
|
||||
SendNotifyEvent(CLRGRDN_CHANGE);
|
||||
if (m_updateCallback)
|
||||
{
|
||||
m_updateCallback(this);
|
||||
}
|
||||
|
||||
for (i = 0; i < m_pSpline->GetKeyCount(); i++)
|
||||
{
|
||||
// Find key with added time.
|
||||
if (m_pSpline->GetKeyTime(i) == time)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::ClearSelection()
|
||||
{
|
||||
m_nActiveKey = -1;
|
||||
if (m_pSpline)
|
||||
{
|
||||
m_bSelectedKeys.resize(m_pSpline->GetKeyCount());
|
||||
}
|
||||
for (int i = 0; i < (int)m_bSelectedKeys.size(); i++)
|
||||
{
|
||||
m_bSelectedKeys[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::SetTimeMarker(float fTime)
|
||||
{
|
||||
if (!m_pSpline)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
QPoint pt = TimeToPoint(m_fTimeMarker);
|
||||
QRect rc = QRect(pt.x(), m_rcGradient.top(), 0, m_rcGradient.bottom() - m_rcGradient.top()).normalized();
|
||||
rc += QMargins(1, 0, 1, 0);
|
||||
update(rc);
|
||||
}
|
||||
{
|
||||
QPoint pt = TimeToPoint(fTime);
|
||||
QRect rc = QRect(pt.x(), m_rcGradient.top(), 0, m_rcGradient.bottom() - m_rcGradient.top()).normalized();
|
||||
rc += QMargins(1, 0, 1, 0);
|
||||
update(rc);
|
||||
}
|
||||
m_fTimeMarker = fTime;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::SendNotifyEvent(int nEvent)
|
||||
{
|
||||
switch (nEvent)
|
||||
{
|
||||
case CLRGRDN_BEFORE_CHANGE:
|
||||
emit beforeChange();
|
||||
break;
|
||||
case CLRGRDN_CHANGE:
|
||||
emit change();
|
||||
break;
|
||||
case CLRGRDN_ACTIVE_KEY_CHANGE:
|
||||
emit activeKeyChange();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
AZ::Color CColorGradientCtrl::ValueToColor(ISplineInterpolator::ValueType val)
|
||||
{
|
||||
const AZ::Color color(val[0], val[1], val[2], 1.0);
|
||||
return color.LinearToGamma();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
void CColorGradientCtrl::ColorToValue(const AZ::Color& col, ISplineInterpolator::ValueType& val)
|
||||
{
|
||||
const AZ::Color colLin = col.GammaToLinear();
|
||||
val[0] = colLin.GetR();
|
||||
val[1] = colLin.GetG();
|
||||
val[2] = colLin.GetB();
|
||||
val[3] = 0;
|
||||
}
|
||||
|
||||
void CColorGradientCtrl::SetNoTimeMarker(bool noTimeMarker)
|
||||
{
|
||||
m_bNoTimeMarker = noTimeMarker;
|
||||
update();
|
||||
}
|
||||
|
||||
|
||||
#include <Controls/moc_ColorGradientCtrl.cpp>
|
||||
@@ -1,167 +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
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef CRYINCLUDE_EDITOR_CONTROLS_COLORGRADIENTCTRL_H
|
||||
#define CRYINCLUDE_EDITOR_CONTROLS_COLORGRADIENTCTRL_H
|
||||
#pragma once
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <QWidget>
|
||||
#include <ISplines.h>
|
||||
#include "Controls/WndGridHelper.h"
|
||||
#endif
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
class Color;
|
||||
}
|
||||
|
||||
// Notify event sent when spline is being modified.
|
||||
#define CLRGRDN_CHANGE (0x0001)
|
||||
// Notify event sent just before when spline is modified.
|
||||
#define CLRGRDN_BEFORE_CHANGE (0x0002)
|
||||
// Notify event sent when the active key changes
|
||||
#define CLRGRDN_ACTIVE_KEY_CHANGE (0x0003)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Spline control.
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
class CColorGradientCtrl
|
||||
: public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
CColorGradientCtrl(QWidget* parent = nullptr);
|
||||
virtual ~CColorGradientCtrl();
|
||||
|
||||
//Key functions
|
||||
int GetActiveKey() { return m_nActiveKey; };
|
||||
void SetActiveKey(int nIndex);
|
||||
int InsertKey(QPoint point);
|
||||
|
||||
// Turns on/off zooming and scroll support.
|
||||
void SetNoZoom([[maybe_unused]] bool bNoZoom) { m_bNoZoom = false; };
|
||||
|
||||
void SetTimeRange(float tmin, float tmax) { m_fMinTime = tmin; m_fMaxTime = tmax; }
|
||||
void SetValueRange(float tmin, float tmax) { m_fMinValue = tmin; m_fMaxValue = tmax; }
|
||||
void SetTooltipValueScale(float x, float y) { m_fTooltipScaleX = x; m_fTooltipScaleY = y; };
|
||||
// Lock value of first and last key to be the same.
|
||||
void LockFirstAndLastKeys(bool bLock) { m_bLockFirstLastKey = bLock; }
|
||||
|
||||
void SetSpline(ISplineInterpolator* pSpline, bool bRedraw = false);
|
||||
ISplineInterpolator* GetSpline();
|
||||
|
||||
void SetTimeMarker(float fTime);
|
||||
|
||||
// Zoom in pixels per time unit.
|
||||
void SetZoom(float fZoom);
|
||||
void SetOrigin(float fOffset);
|
||||
|
||||
typedef AZStd::function<void(CColorGradientCtrl*)> UpdateCallback;
|
||||
void SetUpdateCallback(const UpdateCallback& cb) { m_updateCallback = cb; };
|
||||
|
||||
void SetNoTimeMarker(bool noTimeMarker);
|
||||
|
||||
signals:
|
||||
void change();
|
||||
void beforeChange();
|
||||
void activeKeyChange();
|
||||
|
||||
protected:
|
||||
enum EHitCode
|
||||
{
|
||||
HIT_NOTHING,
|
||||
HIT_KEY,
|
||||
HIT_SPLINE,
|
||||
};
|
||||
|
||||
void paintEvent(QPaintEvent* e) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
void OnLButtonDown(QMouseEvent* event);
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void OnLButtonUp(QMouseEvent* event);
|
||||
void OnRButtonUp(QMouseEvent* event);
|
||||
void mouseDoubleClickEvent(QMouseEvent* event) override;
|
||||
void OnRButtonDown(QMouseEvent* event);
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
|
||||
// Drawing functions
|
||||
void DrawGradient(QPaintEvent* e, QPainter* painter);
|
||||
void DrawKeys(QPaintEvent* e, QPainter* painter);
|
||||
void UpdateTooltip(QPoint pos);
|
||||
|
||||
EHitCode HitTest(QPoint point);
|
||||
|
||||
//Tracking support helper functions
|
||||
void StartTracking();
|
||||
void TrackKey(QPoint point);
|
||||
void StopTracking(QPoint point);
|
||||
void RemoveKey(int nKey);
|
||||
void EditKey(int nKey);
|
||||
|
||||
QPoint KeyToPoint(int nKey);
|
||||
QPoint TimeToPoint(float time);
|
||||
void PointToTimeValue(QPoint point, float& time, ISplineInterpolator::ValueType& val);
|
||||
float XOfsToTime(int x);
|
||||
QPoint XOfsToPoint(int x);
|
||||
|
||||
AZ::Color XOfsToColor(int x);
|
||||
AZ::Color TimeToColor(float time);
|
||||
|
||||
void ClearSelection();
|
||||
|
||||
void SendNotifyEvent(int nEvent);
|
||||
|
||||
AZ::Color ValueToColor(ISplineInterpolator::ValueType val);
|
||||
void ColorToValue(const AZ::Color& col, ISplineInterpolator::ValueType& val);
|
||||
|
||||
|
||||
private:
|
||||
void OnKeyColorChanged(const AZ::Color& color);
|
||||
|
||||
private:
|
||||
ISplineInterpolator* m_pSpline;
|
||||
|
||||
bool m_bNoZoom;
|
||||
|
||||
QRect m_rcClipRect;
|
||||
QRect m_rcGradient;
|
||||
QRect m_rcKeys;
|
||||
|
||||
QPoint m_hitPoint;
|
||||
EHitCode m_hitCode;
|
||||
int m_nHitKeyIndex;
|
||||
int m_nHitKeyDist;
|
||||
QPoint m_curvePoint;
|
||||
|
||||
float m_fTimeMarker;
|
||||
|
||||
int m_nActiveKey;
|
||||
int m_nKeyDrawRadius;
|
||||
|
||||
bool m_bTracking;
|
||||
|
||||
float m_fMinTime, m_fMaxTime;
|
||||
float m_fMinValue, m_fMaxValue;
|
||||
float m_fTooltipScaleX, m_fTooltipScaleY;
|
||||
|
||||
bool m_bLockFirstLastKey;
|
||||
|
||||
bool m_bNoTimeMarker;
|
||||
|
||||
std::vector<int> m_bSelectedKeys;
|
||||
|
||||
UpdateCallback m_updateCallback;
|
||||
|
||||
CWndGridHelper m_grid;
|
||||
};
|
||||
|
||||
#endif // CRYINCLUDE_EDITOR_CONTROLS_COLORGRADIENTCTRL_H
|
||||
@@ -27,7 +27,6 @@ void RegisterReflectedVarHandlers()
|
||||
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew LocalStringPropertyHandler());
|
||||
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew LightAnimationPropertyHandler());
|
||||
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew UserPopupWidgetHandler());
|
||||
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew ColorCurveHandler());
|
||||
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew FloatCurveHandler());
|
||||
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew MotionPropertyWidgetHandler());
|
||||
}
|
||||
|
||||
@@ -170,30 +170,3 @@ bool FloatCurveHandler::ReadValuesIntoGUI([[maybe_unused]] size_t index, CSpline
|
||||
GUI->SetSpline(reinterpret_cast<ISplineInterpolator*>(instance.m_spline));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
QWidget* ColorCurveHandler::CreateGUI(QWidget *pParent)
|
||||
{
|
||||
CColorGradientCtrl* gradientCtrl = new CColorGradientCtrl(pParent);
|
||||
//connect(gradientCtrl, &CColorGradientCtrl::change, [gradientCtrl]()
|
||||
//{
|
||||
// EBUS_EVENT(AzToolsFramework::PropertyEditorGUIMessages::Bus, RequestWrite, gradientCtrl);
|
||||
//});
|
||||
gradientCtrl->SetTimeRange(0, 1);
|
||||
gradientCtrl->setFixedHeight(36);
|
||||
return gradientCtrl;
|
||||
|
||||
}
|
||||
|
||||
void ColorCurveHandler::ConsumeAttribute(CColorGradientCtrl*, AZ::u32, AzToolsFramework::PropertyAttributeReader*, const char*)
|
||||
{}
|
||||
|
||||
void ColorCurveHandler::WriteGUIValuesIntoProperty([[maybe_unused]] size_t index, [[maybe_unused]] CColorGradientCtrl* GUI, [[maybe_unused]] property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node)
|
||||
{}
|
||||
|
||||
bool ColorCurveHandler::ReadValuesIntoGUI([[maybe_unused]] size_t index, CColorGradientCtrl* GUI, const property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node)
|
||||
{
|
||||
GUI->SetSpline(reinterpret_cast<ISplineInterpolator*>(instance.m_spline));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
|
||||
#include "ReflectedVar.h"
|
||||
#include "Util/VariablePropertyType.h"
|
||||
#include "Controls/ColorGradientCtrl.h"
|
||||
#include "Controls/SplineCtrl.h"
|
||||
#include <QWidget>
|
||||
#endif
|
||||
@@ -82,17 +81,4 @@ public:
|
||||
void OnSplineChange(CSplineCtrl*);
|
||||
};
|
||||
|
||||
class ColorCurveHandler : public QObject, public AzToolsFramework::PropertyHandler < CReflectedVarSpline, CColorGradientCtrl>
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(ColorCurveHandler, AZ::SystemAllocator, 0);
|
||||
bool IsDefaultHandler() const override { return false; }
|
||||
QWidget* CreateGUI(QWidget *pParent) override;
|
||||
|
||||
AZ::u32 GetHandlerName(void) const override { return AZ_CRC("ePropertyColorCurve", 0xa30da4ec); }
|
||||
|
||||
void ConsumeAttribute(CColorGradientCtrl* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName) override;
|
||||
void WriteGUIValuesIntoProperty(size_t index, CColorGradientCtrl* GUI, property_t& instance, AzToolsFramework::InstanceDataNode* node) override;
|
||||
bool ReadValuesIntoGUI(size_t index, CColorGradientCtrl* GUI, const property_t& instance, AzToolsFramework::InstanceDataNode* node) override;
|
||||
};
|
||||
#endif // CRYINCLUDE_EDITOR_UTILS_PROPERTYMISCCTRL_H
|
||||
|
||||
@@ -76,6 +76,7 @@ namespace AZ
|
||||
else if (info.m_status == AzToolsFramework::SourceControlStatus::SCS_ProviderIsDown)
|
||||
{
|
||||
message = "Failed to put entries/dependencies into source control as the provider is not available.\n";
|
||||
reportAsWarning = true;
|
||||
}
|
||||
else if (info.m_status == AzToolsFramework::SourceControlStatus::SCS_CertificateInvalid)
|
||||
{
|
||||
|
||||
@@ -330,8 +330,6 @@ set(FILES
|
||||
Commands/CommandManager.h
|
||||
Controls/BitmapToolTip.cpp
|
||||
Controls/BitmapToolTip.h
|
||||
Controls/ColorGradientCtrl.cpp
|
||||
Controls/ColorGradientCtrl.h
|
||||
Controls/ConsoleSCB.cpp
|
||||
Controls/ConsoleSCB.h
|
||||
Controls/ConsoleSCB.ui
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Serialization/Json/JsonImporter.h>
|
||||
#include <AzCore/Serialization/Json/JsonUtils.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
JsonSerializationResult::ResultCode JsonImportResolver::ResolveNestedImports(rapidjson::Value& jsonDoc,
|
||||
rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack,
|
||||
JsonImportSettings& settings, const AZ::IO::FixedMaxPath& importPath, StackedString& element)
|
||||
{
|
||||
using namespace JsonSerializationResult;
|
||||
|
||||
for (auto& path : importPathStack)
|
||||
{
|
||||
if (importPath == path)
|
||||
{
|
||||
return settings.m_reporting(
|
||||
AZStd::string::format("'%s' was already imported in this chain. This indicates a cyclic dependency.", importPath.c_str()),
|
||||
ResultCode(Tasks::Import, Outcomes::Catastrophic), element);
|
||||
}
|
||||
}
|
||||
|
||||
importPathStack.push_back(importPath);
|
||||
AZ::StackedString importElement(AZ::StackedString::Format::JsonPointer);
|
||||
JsonImportSettings nestedImportSettings;
|
||||
nestedImportSettings.m_importer = settings.m_importer;
|
||||
nestedImportSettings.m_reporting = settings.m_reporting;
|
||||
nestedImportSettings.m_resolveFlags = ImportTracking::Dependencies;
|
||||
ResultCode result = ResolveImports(jsonDoc, allocator, importPathStack, nestedImportSettings, importElement);
|
||||
importPathStack.pop_back();
|
||||
|
||||
if (result.GetOutcome() == Outcomes::Catastrophic)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return ResultCode(Tasks::Import, Outcomes::Success);
|
||||
}
|
||||
|
||||
JsonSerializationResult::ResultCode JsonImportResolver::ResolveImports(rapidjson::Value& jsonDoc,
|
||||
rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack,
|
||||
JsonImportSettings& settings, StackedString& element)
|
||||
{
|
||||
using namespace JsonSerializationResult;
|
||||
|
||||
if (jsonDoc.IsObject())
|
||||
{
|
||||
for (auto& field : jsonDoc.GetObject())
|
||||
{
|
||||
if(strncmp(field.name.GetString(), JsonSerialization::ImportDirectiveIdentifier, field.name.GetStringLength()) == 0)
|
||||
{
|
||||
const rapidjson::Value& importDirective = field.value;
|
||||
AZ::IO::FixedMaxPath importAbsPath = importPathStack.back();
|
||||
importAbsPath.RemoveFilename();
|
||||
AZStd::string importName;
|
||||
if (importDirective.IsObject())
|
||||
{
|
||||
auto filenameField = importDirective.FindMember("filename");
|
||||
if (filenameField != importDirective.MemberEnd())
|
||||
{
|
||||
importName = AZStd::string(filenameField->value.GetString(), filenameField->value.GetStringLength());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
importName = AZStd::string(importDirective.GetString(), importDirective.GetStringLength());
|
||||
}
|
||||
importAbsPath.Append(importName);
|
||||
|
||||
rapidjson::Value patch;
|
||||
ResultCode resolveResult = settings.m_importer->ResolveImport(&jsonDoc, patch, importDirective, importAbsPath, allocator);
|
||||
if (resolveResult.GetOutcome() == Outcomes::Catastrophic)
|
||||
{
|
||||
return resolveResult;
|
||||
}
|
||||
|
||||
if ((settings.m_resolveFlags & ImportTracking::Imports) == ImportTracking::Imports)
|
||||
{
|
||||
rapidjson::Pointer path(element.Get().data(), element.Get().size());
|
||||
settings.m_importer->AddImportDirective(path, importName);
|
||||
}
|
||||
if ((settings.m_resolveFlags & ImportTracking::Dependencies) == ImportTracking::Dependencies)
|
||||
{
|
||||
settings.m_importer->AddImportedFile(importAbsPath.String());
|
||||
}
|
||||
|
||||
ResultCode result = ResolveNestedImports(jsonDoc, allocator, importPathStack, settings, importAbsPath, element);
|
||||
if (result.GetOutcome() == Outcomes::Catastrophic)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
settings.m_importer->ApplyPatch(jsonDoc, patch, allocator);
|
||||
}
|
||||
else if (field.value.IsObject() || field.value.IsArray())
|
||||
{
|
||||
ScopedStackedString entryName(element, AZStd::string_view(field.name.GetString(), field.name.GetStringLength()));
|
||||
ResultCode result = ResolveImports(field.value, allocator, importPathStack, settings, element);
|
||||
if (result.GetOutcome() == Outcomes::Catastrophic)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(jsonDoc.IsArray())
|
||||
{
|
||||
int index = 0;
|
||||
for (rapidjson::Value::ValueIterator elem = jsonDoc.Begin(); elem != jsonDoc.End(); ++elem, ++index)
|
||||
{
|
||||
if (!elem->IsObject() && !elem->IsArray())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
ScopedStackedString entryName(element, index);
|
||||
ResultCode result = ResolveImports(*elem, allocator, importPathStack, settings, element);
|
||||
if (result.GetOutcome() == Outcomes::Catastrophic)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode(Tasks::Import, Outcomes::Success);
|
||||
}
|
||||
|
||||
JsonSerializationResult::ResultCode JsonImportResolver::RestoreImports(rapidjson::Value& jsonDoc,
|
||||
rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings)
|
||||
{
|
||||
using namespace JsonSerializationResult;
|
||||
|
||||
if (jsonDoc.IsObject() || jsonDoc.IsArray())
|
||||
{
|
||||
const BaseJsonImporter::ImportDirectivesList& importDirectives = settings.m_importer->GetImportDirectives();
|
||||
for (auto& import : importDirectives)
|
||||
{
|
||||
rapidjson::Pointer importPtr = import.first;
|
||||
rapidjson::Value* currentValue = importPtr.Get(jsonDoc);
|
||||
|
||||
rapidjson::Value importedValue(rapidjson::kObjectType);
|
||||
importedValue.AddMember(rapidjson::StringRef(JsonSerialization::ImportDirectiveIdentifier), rapidjson::StringRef(import.second.c_str()), allocator);
|
||||
ResultCode resolveResult = JsonSerialization::ResolveImports(importedValue, allocator, settings);
|
||||
if (resolveResult.GetOutcome() == Outcomes::Catastrophic)
|
||||
{
|
||||
return resolveResult;
|
||||
}
|
||||
|
||||
rapidjson::Value patch;
|
||||
settings.m_importer->CreatePatch(patch, importedValue, *currentValue, allocator);
|
||||
settings.m_importer->RestoreImport(currentValue, patch, allocator, import.second);
|
||||
}
|
||||
}
|
||||
|
||||
return ResultCode(Tasks::Import, Outcomes::Success);
|
||||
}
|
||||
|
||||
JsonSerializationResult::ResultCode BaseJsonImporter::ResolveImport(rapidjson::Value* importPtr,
|
||||
rapidjson::Value& patch, const rapidjson::Value& importDirective,
|
||||
const AZ::IO::FixedMaxPath& importedFilePath, rapidjson::Document::AllocatorType& allocator)
|
||||
{
|
||||
using namespace JsonSerializationResult;
|
||||
|
||||
auto importedObject = JsonSerializationUtils::ReadJsonFile(importedFilePath.Native());
|
||||
if (importedObject.IsSuccess())
|
||||
{
|
||||
rapidjson::Value& importedDoc = importedObject.GetValue();
|
||||
|
||||
if (importDirective.IsObject())
|
||||
{
|
||||
auto patchField = importDirective.FindMember("patch");
|
||||
if (patchField != importDirective.MemberEnd())
|
||||
{
|
||||
patch.CopyFrom(patchField->value, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
importPtr->CopyFrom(importedDoc, allocator);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ResultCode(Tasks::Import, Outcomes::Catastrophic);
|
||||
}
|
||||
|
||||
return ResultCode(Tasks::Import, Outcomes::Success);
|
||||
}
|
||||
|
||||
JsonSerializationResult::ResultCode BaseJsonImporter::RestoreImport(rapidjson::Value* importPtr,
|
||||
rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator, const AZStd::string& importFilename)
|
||||
{
|
||||
using namespace JsonSerializationResult;
|
||||
|
||||
importPtr->SetObject();
|
||||
if ((patch.IsObject() && patch.MemberCount() > 0) || (patch.IsArray() && !patch.Empty()))
|
||||
{
|
||||
rapidjson::Value importDirective(rapidjson::kObjectType);
|
||||
importDirective.AddMember(rapidjson::StringRef("filename"), rapidjson::StringRef(importFilename.c_str()), allocator);
|
||||
importDirective.AddMember(rapidjson::StringRef("patch"), patch, allocator);
|
||||
importPtr->AddMember(rapidjson::StringRef(JsonSerialization::ImportDirectiveIdentifier), importDirective, allocator);
|
||||
}
|
||||
else
|
||||
{
|
||||
importPtr->AddMember(rapidjson::StringRef(JsonSerialization::ImportDirectiveIdentifier), rapidjson::StringRef(importFilename.c_str()), allocator);
|
||||
}
|
||||
|
||||
return ResultCode(Tasks::Import, Outcomes::Success);
|
||||
}
|
||||
|
||||
JsonSerializationResult::ResultCode BaseJsonImporter::ApplyPatch(rapidjson::Value& target,
|
||||
const rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator)
|
||||
{
|
||||
using namespace JsonSerializationResult;
|
||||
|
||||
if ((patch.IsObject() && patch.MemberCount() > 0) || (patch.IsArray() && !patch.Empty()))
|
||||
{
|
||||
return AZ::JsonSerialization::ApplyPatch(target, allocator, patch, JsonMergeApproach::JsonMergePatch);
|
||||
}
|
||||
|
||||
return ResultCode(Tasks::Import, Outcomes::Success);
|
||||
}
|
||||
|
||||
JsonSerializationResult::ResultCode BaseJsonImporter::CreatePatch(rapidjson::Value& patch,
|
||||
const rapidjson::Value& source, const rapidjson::Value& target,
|
||||
rapidjson::Document::AllocatorType& allocator)
|
||||
{
|
||||
return JsonSerialization::CreatePatch(patch, allocator, source, target, JsonMergeApproach::JsonMergePatch);
|
||||
}
|
||||
|
||||
void BaseJsonImporter::AddImportDirective(const rapidjson::Pointer& jsonPtr, AZStd::string importFile)
|
||||
{
|
||||
m_importDirectives.emplace_back(jsonPtr, AZStd::move(importFile));
|
||||
}
|
||||
|
||||
void BaseJsonImporter::AddImportedFile(AZStd::string importedFile)
|
||||
{
|
||||
m_importedFiles.insert(AZStd::move(importedFile));
|
||||
}
|
||||
|
||||
const BaseJsonImporter::ImportDirectivesList& BaseJsonImporter::GetImportDirectives()
|
||||
{
|
||||
return m_importDirectives;
|
||||
}
|
||||
|
||||
const BaseJsonImporter::ImportedFilesList& BaseJsonImporter::GetImportedFiles()
|
||||
{
|
||||
return m_importedFiles;
|
||||
}
|
||||
} // namespace AZ
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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/IO/Path/Path.h>
|
||||
#include <AzCore/JSON/document.h>
|
||||
#include <AzCore/JSON/pointer.h>
|
||||
#include <AzCore/RTTI/RTTI.h>
|
||||
#include <AzCore/std/containers/unordered_set.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerializationResult.h>
|
||||
#include <AzCore/Serialization/Json/StackedString.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
struct JsonImportSettings;
|
||||
|
||||
class BaseJsonImporter
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(BaseJsonImporter, "{7B225807-7B43-430F-8B11-C794DCF5ACA5}");
|
||||
|
||||
using ImportDirectivesList = AZStd::vector<AZStd::pair<rapidjson::Pointer, AZStd::string>>;
|
||||
using ImportedFilesList = AZStd::unordered_set<AZStd::string>;
|
||||
|
||||
virtual JsonSerializationResult::ResultCode ResolveImport(rapidjson::Value* importPtr,
|
||||
rapidjson::Value& patch, const rapidjson::Value& importDirective,
|
||||
const AZ::IO::FixedMaxPath& importedFilePath, rapidjson::Document::AllocatorType& allocator);
|
||||
|
||||
virtual JsonSerializationResult::ResultCode RestoreImport(rapidjson::Value* importPtr,
|
||||
rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator,
|
||||
const AZStd::string& importFilename);
|
||||
|
||||
virtual JsonSerializationResult::ResultCode ApplyPatch(rapidjson::Value& target,
|
||||
const rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator);
|
||||
|
||||
virtual JsonSerializationResult::ResultCode CreatePatch(rapidjson::Value& patch,
|
||||
const rapidjson::Value& source, const rapidjson::Value& target,
|
||||
rapidjson::Document::AllocatorType& allocator);
|
||||
|
||||
void AddImportDirective(const rapidjson::Pointer& jsonPtr, AZStd::string importFile);
|
||||
const ImportDirectivesList& GetImportDirectives();
|
||||
|
||||
void AddImportedFile(AZStd::string importedFile);
|
||||
const ImportedFilesList& GetImportedFiles();
|
||||
|
||||
virtual ~BaseJsonImporter() = default;
|
||||
|
||||
protected:
|
||||
|
||||
ImportDirectivesList m_importDirectives;
|
||||
ImportedFilesList m_importedFiles;
|
||||
};
|
||||
|
||||
enum class ImportTracking : AZ::u8
|
||||
{
|
||||
None = 0,
|
||||
Dependencies = (1<<0),
|
||||
Imports = (1<<1),
|
||||
All = (Dependencies | Imports)
|
||||
};
|
||||
AZ_DEFINE_ENUM_BITWISE_OPERATORS(ImportTracking);
|
||||
|
||||
class JsonImportResolver final
|
||||
{
|
||||
public:
|
||||
|
||||
using ImportPathStack = AZStd::vector<AZ::IO::FixedMaxPath>;
|
||||
|
||||
JsonImportResolver() = delete;
|
||||
JsonImportResolver& operator=(const JsonImportResolver& rhs) = delete;
|
||||
JsonImportResolver& operator=(JsonImportResolver&& rhs) = delete;
|
||||
JsonImportResolver(const JsonImportResolver& rhs) = delete;
|
||||
JsonImportResolver(JsonImportResolver&& rhs) = delete;
|
||||
~JsonImportResolver() = delete;
|
||||
|
||||
static JsonSerializationResult::ResultCode ResolveImports(rapidjson::Value& jsonDoc,
|
||||
rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack,
|
||||
JsonImportSettings& settings, StackedString& element);
|
||||
|
||||
static JsonSerializationResult::ResultCode RestoreImports(rapidjson::Value& jsonDoc,
|
||||
rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings);
|
||||
|
||||
private:
|
||||
|
||||
static JsonSerializationResult::ResultCode ResolveNestedImports(rapidjson::Value& jsonDoc,
|
||||
rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack,
|
||||
JsonImportSettings& settings, const AZ::IO::FixedMaxPath& importPath, StackedString& element);
|
||||
};
|
||||
|
||||
|
||||
struct JsonImportSettings final
|
||||
{
|
||||
JsonSerializationResult::JsonIssueCallback m_reporting;
|
||||
|
||||
BaseJsonImporter* m_importer = nullptr;
|
||||
|
||||
ImportTracking m_resolveFlags = ImportTracking::All;
|
||||
|
||||
AZ::IO::FixedMaxPath m_loadedJsonPath;
|
||||
};
|
||||
} // namespace AZ
|
||||
@@ -706,7 +706,7 @@ namespace AZ
|
||||
rapidjson::Value(rapidjson::kNullType), field.value, element, settings);
|
||||
}
|
||||
|
||||
if (result.GetOutcome() == Outcomes::Success)
|
||||
if (result.GetOutcome() == Outcomes::Success || result.GetOutcome() == Outcomes::PartialDefaults)
|
||||
{
|
||||
rapidjson::Value name;
|
||||
name.CopyFrom(field.name, allocator, true);
|
||||
@@ -717,6 +717,10 @@ namespace AZ
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
resultCode.Combine(result);
|
||||
}
|
||||
}
|
||||
|
||||
// Do an extra pass to find all the fields that are removed.
|
||||
@@ -751,7 +755,7 @@ namespace AZ
|
||||
rapidjson::Value value;
|
||||
ResultCode result = CreateMergePatchInternal(value, allocator,
|
||||
rapidjson::Value(rapidjson::kNullType), field.value, element, settings);
|
||||
if (result.GetOutcome() == Outcomes::Success)
|
||||
if (result.GetOutcome() == Outcomes::Success || result.GetOutcome() == Outcomes::PartialDefaults)
|
||||
{
|
||||
rapidjson::Value name;
|
||||
name.CopyFrom(field.name, allocator, true);
|
||||
@@ -762,11 +766,20 @@ namespace AZ
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
resultCode.Combine(result);
|
||||
}
|
||||
}
|
||||
|
||||
if (target.MemberCount() == 0)
|
||||
{
|
||||
resultCode.Combine(settings.m_reporting("Added empty object to JSON Merge Patch.",
|
||||
ResultCode(Tasks::CreatePatch, Outcomes::Success), element));
|
||||
}
|
||||
}
|
||||
|
||||
patch = AZStd::move(resultValue);
|
||||
resultCode.Combine(ResultCode(Tasks::CreatePatch, Outcomes::Success));
|
||||
return resultCode;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <AzCore/Component/ComponentApplicationBus.h>
|
||||
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
|
||||
#include <AzCore/Serialization/Json/JsonDeserializer.h>
|
||||
#include <AzCore/Serialization/Json/JsonImporter.h>
|
||||
#include <AzCore/Serialization/Json/JsonMerger.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerialization.h>
|
||||
#include <AzCore/Serialization/Json/JsonSerializer.h>
|
||||
@@ -19,11 +20,6 @@
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
const char* JsonSerialization::TypeIdFieldIdentifier = "$type";
|
||||
const char* JsonSerialization::DefaultStringIdentifier = "{}";
|
||||
const char* JsonSerialization::KeyFieldIdentifier = "Key";
|
||||
const char* JsonSerialization::ValueFieldIdentifier = "Value";
|
||||
|
||||
namespace JsonSerializationInternal
|
||||
{
|
||||
template<typename T>
|
||||
@@ -394,6 +390,60 @@ namespace AZ
|
||||
}
|
||||
}
|
||||
|
||||
JsonSerializationResult::ResultCode JsonSerialization::ResolveImports(
|
||||
rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings)
|
||||
{
|
||||
using namespace JsonSerializationResult;
|
||||
|
||||
if (settings.m_importer == nullptr)
|
||||
{
|
||||
AZ_Assert(false, "Importer object needs to be provided");
|
||||
return ResultCode(Tasks::Import, Outcomes::Catastrophic);
|
||||
}
|
||||
|
||||
AZStd::string scratchBuffer;
|
||||
auto issueReportingCallback = [&scratchBuffer](AZStd::string_view message, ResultCode result, AZStd::string_view target) -> ResultCode
|
||||
{
|
||||
return JsonSerialization::DefaultIssueReporter(scratchBuffer, message, result, target);
|
||||
};
|
||||
if (!settings.m_reporting)
|
||||
{
|
||||
settings.m_reporting = issueReportingCallback;
|
||||
}
|
||||
|
||||
JsonImportResolver::ImportPathStack importPathStack;
|
||||
importPathStack.push_back(settings.m_loadedJsonPath);
|
||||
StackedString element(StackedString::Format::JsonPointer);
|
||||
|
||||
return JsonImportResolver::ResolveImports(jsonDoc, allocator, importPathStack, settings, element);
|
||||
}
|
||||
|
||||
JsonSerializationResult::ResultCode JsonSerialization::RestoreImports(
|
||||
rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings)
|
||||
{
|
||||
using namespace JsonSerializationResult;
|
||||
|
||||
if (settings.m_importer == nullptr)
|
||||
{
|
||||
AZ_Assert(false, "Importer object needs to be provided");
|
||||
return ResultCode(Tasks::Import, Outcomes::Catastrophic);
|
||||
}
|
||||
|
||||
AZStd::string scratchBuffer;
|
||||
auto issueReportingCallback = [&scratchBuffer](AZStd::string_view message, ResultCode result, AZStd::string_view target) -> ResultCode
|
||||
{
|
||||
return JsonSerialization::DefaultIssueReporter(scratchBuffer, message, result, target);
|
||||
};
|
||||
if (!settings.m_reporting)
|
||||
{
|
||||
settings.m_reporting = issueReportingCallback;
|
||||
}
|
||||
|
||||
settings.m_resolveFlags = ImportTracking::None;
|
||||
|
||||
return JsonImportResolver::RestoreImports(jsonDoc, allocator, settings);
|
||||
}
|
||||
|
||||
JsonSerializationResult::ResultCode JsonSerialization::DefaultIssueReporter(AZStd::string& scratchBuffer,
|
||||
AZStd::string_view message, JsonSerializationResult::ResultCode result, AZStd::string_view path)
|
||||
{
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
namespace AZ
|
||||
{
|
||||
class BaseJsonSerializer;
|
||||
|
||||
struct JsonImportSettings;
|
||||
|
||||
enum class JsonMergeApproach
|
||||
{
|
||||
@@ -51,10 +53,11 @@ namespace AZ
|
||||
class JsonSerialization final
|
||||
{
|
||||
public:
|
||||
static const char* TypeIdFieldIdentifier;
|
||||
static const char* DefaultStringIdentifier;
|
||||
static const char* KeyFieldIdentifier;
|
||||
static const char* ValueFieldIdentifier;
|
||||
static constexpr const char* TypeIdFieldIdentifier = "$type";
|
||||
static constexpr const char* DefaultStringIdentifier = "{}";
|
||||
static constexpr const char* KeyFieldIdentifier = "Key";
|
||||
static constexpr const char* ValueFieldIdentifier = "Value";
|
||||
static constexpr const char* ImportDirectiveIdentifier = "$import";
|
||||
|
||||
//! Merges two json values together by applying "patch" to "target" using the selected merge algorithm.
|
||||
//! This version of ApplyPatch is destructive to "target". If the patch can't be correctly applied it will
|
||||
@@ -284,6 +287,22 @@ namespace AZ
|
||||
//! @return An enum containing less, equal or greater. In case of an error, the value for the enum will "error".
|
||||
static JsonSerializerCompareResult Compare(const rapidjson::Value& lhs, const rapidjson::Value& rhs);
|
||||
|
||||
//! Resolves all import directives, including nested imports, in the given document. An importer object needs to be passed
|
||||
//! in through the settings.
|
||||
//! @param jsonDoc The json document in which to resolve imports.
|
||||
//! @param allocator The allocator associated with the json document.
|
||||
//! @param settings Additional settings that control the way the imports are resolved.
|
||||
static JsonSerializationResult::ResultCode ResolveImports(
|
||||
rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings);
|
||||
|
||||
//! Restores all import directives that were present in the json document. The same importer object that was
|
||||
//! passed into ResolveImports through the settings needs to be passed here through settings as well.
|
||||
//! @param jsonDoc The json document in which to restore imports.
|
||||
//! @param allocator The allocator associated with the json document.
|
||||
//! @param settings Additional settings that control the way the imports are restored.
|
||||
static JsonSerializationResult::ResultCode RestoreImports(
|
||||
rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings);
|
||||
|
||||
private:
|
||||
JsonSerialization() = delete;
|
||||
~JsonSerialization() = delete;
|
||||
|
||||
@@ -69,6 +69,9 @@ namespace AZ
|
||||
case Tasks::CreatePatch:
|
||||
target.append("a create patch operation ");
|
||||
break;
|
||||
case Tasks::Import:
|
||||
target.append("an import operation");
|
||||
break;
|
||||
default:
|
||||
target.append("an unknown operation ");
|
||||
break;
|
||||
|
||||
@@ -32,7 +32,8 @@ namespace AZ
|
||||
ReadField, //!< Task to read a field from JSON to a value.
|
||||
WriteValue, //!< Task to write a value to a JSON field.
|
||||
Merge, //!< Task to merge two JSON values/documents together.
|
||||
CreatePatch //!< Task to create a patch to transform one value/document to another.
|
||||
CreatePatch, //!< Task to create a patch to transform one value/document to another.
|
||||
Import //!< Task to import a JSON document.
|
||||
};
|
||||
|
||||
//! Describes how the task was processed.
|
||||
|
||||
@@ -522,6 +522,8 @@ set(FILES
|
||||
Serialization/Json/IntSerializer.cpp
|
||||
Serialization/Json/JsonDeserializer.h
|
||||
Serialization/Json/JsonDeserializer.cpp
|
||||
Serialization/Json/JsonImporter.cpp
|
||||
Serialization/Json/JsonImporter.h
|
||||
Serialization/Json/JsonMerger.h
|
||||
Serialization/Json/JsonMerger.cpp
|
||||
Serialization/Json/JsonSerialization.h
|
||||
|
||||
@@ -0,0 +1,413 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/Serialization/Json/JsonImporter.h>
|
||||
#include <AzCore/Serialization/Json/JsonUtils.h>
|
||||
#include <Tests/Serialization/Json/JsonSerializationTests.h>
|
||||
|
||||
namespace JsonSerializationTests
|
||||
{
|
||||
class JsonImportingTests;
|
||||
|
||||
class JsonImporterCustom
|
||||
: public AZ::BaseJsonImporter
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(JsonImporterCustom, "{003F5896-71E0-4A50-A14F-08C319B06AD0}");
|
||||
|
||||
|
||||
AZ::JsonSerializationResult::ResultCode ResolveImport(rapidjson::Value* importPtr,
|
||||
rapidjson::Value& patch, const rapidjson::Value& importDirective,
|
||||
const AZ::IO::FixedMaxPath& importedFilePath, rapidjson::Document::AllocatorType& allocator) override;
|
||||
|
||||
JsonImporterCustom(JsonImportingTests* tests)
|
||||
{
|
||||
testClass = tests;
|
||||
}
|
||||
|
||||
private:
|
||||
JsonImportingTests* testClass;
|
||||
};
|
||||
|
||||
class JsonImportingTests
|
||||
: public BaseJsonSerializerFixture
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
BaseJsonSerializerFixture::SetUp();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
BaseJsonSerializerFixture::TearDown();
|
||||
}
|
||||
|
||||
void GetTestDocument(const AZStd::string& docName, rapidjson::Document& out)
|
||||
{
|
||||
const char *objectJson = R"({
|
||||
"field_1" : "value_1",
|
||||
"field_2" : "value_2",
|
||||
"field_3" : "value_3"
|
||||
})";
|
||||
|
||||
const char *arrayJson = R"([
|
||||
{ "element_1" : "value_1" },
|
||||
{ "element_2" : "value_2" },
|
||||
{ "element_3" : "value_3" }
|
||||
])";
|
||||
|
||||
const char *nestedImportJson = R"({
|
||||
"desc" : "Nested Import",
|
||||
"obj" : {"$import" : "object.json"}
|
||||
})";
|
||||
|
||||
const char *nestedImportCycle1Json = R"({
|
||||
"desc" : "Nested Import Cycle 1",
|
||||
"obj" : {"$import" : "nested_import_c2.json"}
|
||||
})";
|
||||
|
||||
const char *nestedImportCycle2Json = R"({
|
||||
"desc" : "Nested Import Cycle 2",
|
||||
"obj" : {"$import" : "nested_import_c1.json"}
|
||||
})";
|
||||
|
||||
if (docName.compare("object.json") == 0)
|
||||
{
|
||||
out.Parse(objectJson);
|
||||
ASSERT_FALSE(out.HasParseError());
|
||||
}
|
||||
else if (docName.compare("array.json") == 0)
|
||||
{
|
||||
out.Parse(arrayJson);
|
||||
ASSERT_FALSE(out.HasParseError());
|
||||
}
|
||||
else if (docName.compare("nested_import.json") == 0)
|
||||
{
|
||||
out.Parse(nestedImportJson);
|
||||
ASSERT_FALSE(out.HasParseError());
|
||||
}
|
||||
else if (docName.compare("nested_import_c1.json") == 0)
|
||||
{
|
||||
out.Parse(nestedImportCycle1Json);
|
||||
ASSERT_FALSE(out.HasParseError());
|
||||
}
|
||||
else if (docName.compare("nested_import_c2.json") == 0)
|
||||
{
|
||||
out.Parse(nestedImportCycle2Json);
|
||||
ASSERT_FALSE(out.HasParseError());
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
void TestImportLoadStore(const char* input, const char* expectedImportedValue)
|
||||
{
|
||||
m_jsonDocument->Parse(input);
|
||||
ASSERT_FALSE(m_jsonDocument->HasParseError());
|
||||
|
||||
JsonImporterCustom* importerObj = new JsonImporterCustom(this);
|
||||
|
||||
rapidjson::Document expectedOutcome;
|
||||
expectedOutcome.Parse(expectedImportedValue);
|
||||
ASSERT_FALSE(expectedOutcome.HasParseError());
|
||||
|
||||
TestResolveImports(importerObj);
|
||||
|
||||
Expect_DocStrEq(m_jsonDocument->GetObject(), expectedOutcome.GetObject());
|
||||
|
||||
rapidjson::Document originalInput;
|
||||
originalInput.Parse(input);
|
||||
ASSERT_FALSE(originalInput.HasParseError());
|
||||
|
||||
TestRestoreImports(importerObj);
|
||||
|
||||
Expect_DocStrEq(m_jsonDocument->GetObject(), originalInput.GetObject());
|
||||
|
||||
m_jsonDocument->SetObject();
|
||||
delete importerObj;
|
||||
}
|
||||
|
||||
void TestImportCycle(const char* input)
|
||||
{
|
||||
m_jsonDocument->Parse(input);
|
||||
ASSERT_FALSE(m_jsonDocument->HasParseError());
|
||||
|
||||
JsonImporterCustom* importerObj = new JsonImporterCustom(this);
|
||||
|
||||
AZ::JsonSerializationResult::ResultCode result = TestResolveImports(importerObj);
|
||||
|
||||
EXPECT_EQ(result.GetOutcome(), AZ::JsonSerializationResult::Outcomes::Catastrophic);
|
||||
|
||||
m_jsonDocument->SetObject();
|
||||
delete importerObj;
|
||||
}
|
||||
|
||||
void TestInsertNewImport(const char* input, const char* expectedRestoredValue)
|
||||
{
|
||||
m_jsonDocument->Parse(input);
|
||||
ASSERT_FALSE(m_jsonDocument->HasParseError());
|
||||
|
||||
JsonImporterCustom* importerObj = new JsonImporterCustom(this);
|
||||
|
||||
TestResolveImports(importerObj);
|
||||
|
||||
importerObj->AddImportDirective(rapidjson::Pointer("/object_2"), "object.json");
|
||||
|
||||
rapidjson::Document expectedOutput;
|
||||
expectedOutput.Parse(expectedRestoredValue);
|
||||
ASSERT_FALSE(expectedOutput.HasParseError());
|
||||
|
||||
TestRestoreImports(importerObj);
|
||||
|
||||
Expect_DocStrEq(m_jsonDocument->GetObject(), expectedOutput.GetObject());
|
||||
|
||||
m_jsonDocument->SetObject();
|
||||
delete importerObj;
|
||||
}
|
||||
|
||||
AZ::JsonSerializationResult::ResultCode TestResolveImports(JsonImporterCustom* importerObj)
|
||||
{
|
||||
AZ::JsonImportSettings settings;
|
||||
settings.m_importer = importerObj;
|
||||
|
||||
return AZ::JsonSerialization::ResolveImports(m_jsonDocument->GetObject(), m_jsonDocument->GetAllocator(), settings);
|
||||
}
|
||||
|
||||
AZ::JsonSerializationResult::ResultCode TestRestoreImports(JsonImporterCustom* importerObj)
|
||||
{
|
||||
AZ::JsonImportSettings settings;
|
||||
settings.m_importer = importerObj;
|
||||
|
||||
return AZ::JsonSerialization::RestoreImports(m_jsonDocument->GetObject(), m_jsonDocument->GetAllocator(), settings);
|
||||
}
|
||||
};
|
||||
|
||||
AZ::JsonSerializationResult::ResultCode JsonImporterCustom::ResolveImport(rapidjson::Value* importPtr,
|
||||
rapidjson::Value& patch, const rapidjson::Value& importDirective, const AZ::IO::FixedMaxPath& importedFilePath,
|
||||
rapidjson::Document::AllocatorType& allocator)
|
||||
{
|
||||
AZ::JsonSerializationResult::ResultCode resultCode(AZ::JsonSerializationResult::Tasks::Import);
|
||||
|
||||
rapidjson::Document importedDoc;
|
||||
testClass->GetTestDocument(importedFilePath.String(), importedDoc);
|
||||
|
||||
if (importDirective.IsObject())
|
||||
{
|
||||
auto patchField = importDirective.FindMember("patch");
|
||||
if (patchField != importDirective.MemberEnd())
|
||||
{
|
||||
patch.CopyFrom(patchField->value, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
importPtr->CopyFrom(importedDoc, allocator);
|
||||
|
||||
return resultCode;
|
||||
}
|
||||
|
||||
// Test Cases
|
||||
|
||||
TEST_F(JsonImportingTests, ImportSimpleObjectTest)
|
||||
{
|
||||
const char* inputFile = R"(
|
||||
{
|
||||
"name" : "simple_object_import",
|
||||
"object": {"$import" : "object.json"}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* expectedOutput = R"(
|
||||
{
|
||||
"name" : "simple_object_import",
|
||||
"object": {
|
||||
"field_1" : "value_1",
|
||||
"field_2" : "value_2",
|
||||
"field_3" : "value_3"
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
TestImportLoadStore(inputFile, expectedOutput);
|
||||
}
|
||||
|
||||
TEST_F(JsonImportingTests, ImportSimpleObjectPatchTest)
|
||||
{
|
||||
const char* inputFile = R"(
|
||||
{
|
||||
"name" : "simple_object_import",
|
||||
"object": {
|
||||
"$import" : {
|
||||
"filename" : "object.json",
|
||||
"patch" : { "field_2" : "patched_value" }
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* expectedOutput = R"(
|
||||
{
|
||||
"name" : "simple_object_import",
|
||||
"object": {
|
||||
"field_1" : "value_1",
|
||||
"field_2" : "patched_value",
|
||||
"field_3" : "value_3"
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
TestImportLoadStore(inputFile, expectedOutput);
|
||||
}
|
||||
|
||||
TEST_F(JsonImportingTests, ImportSimpleArrayTest)
|
||||
{
|
||||
const char* inputFile = R"(
|
||||
{
|
||||
"name" : "simple_array_import",
|
||||
"object": {"$import" : "array.json"}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* expectedOutput = R"(
|
||||
{
|
||||
"name" : "simple_array_import",
|
||||
"object": [
|
||||
{ "element_1" : "value_1" },
|
||||
{ "element_2" : "value_2" },
|
||||
{ "element_3" : "value_3" }
|
||||
]
|
||||
}
|
||||
)";
|
||||
|
||||
TestImportLoadStore(inputFile, expectedOutput);
|
||||
}
|
||||
|
||||
TEST_F(JsonImportingTests, ImportSimpleArrayPatchTest)
|
||||
{
|
||||
const char* inputFile = R"(
|
||||
{
|
||||
"name" : "simple_array_import",
|
||||
"object": {
|
||||
"$import" : {
|
||||
"filename" : "array.json",
|
||||
"patch" : [ { "element_1" : "patched_value" } ]
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* expectedOutput = R"(
|
||||
{
|
||||
"name" : "simple_array_import",
|
||||
"object": [
|
||||
{ "element_1" : "patched_value" }
|
||||
]
|
||||
}
|
||||
)";
|
||||
|
||||
TestImportLoadStore(inputFile, expectedOutput);
|
||||
}
|
||||
|
||||
TEST_F(JsonImportingTests, NestedImportTest)
|
||||
{
|
||||
const char* inputFile = R"(
|
||||
{
|
||||
"name" : "nested_import",
|
||||
"object": {"$import" : "nested_import.json"}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* expectedOutput = R"(
|
||||
{
|
||||
"name" : "nested_import",
|
||||
"object": {
|
||||
"desc" : "Nested Import",
|
||||
"obj" : {
|
||||
"field_1" : "value_1",
|
||||
"field_2" : "value_2",
|
||||
"field_3" : "value_3"
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
TestImportLoadStore(inputFile, expectedOutput);
|
||||
}
|
||||
|
||||
TEST_F(JsonImportingTests, NestedImportPatchTest)
|
||||
{
|
||||
const char* inputFile = R"(
|
||||
{
|
||||
"name" : "nested_import",
|
||||
"object": {
|
||||
"$import" : {
|
||||
"filename" : "nested_import.json",
|
||||
"patch" : { "obj" : { "field_3" : "patched_value" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* expectedOutput = R"(
|
||||
{
|
||||
"name" : "nested_import",
|
||||
"object": {
|
||||
"desc" : "Nested Import",
|
||||
"obj" : {
|
||||
"field_1" : "value_1",
|
||||
"field_2" : "value_2",
|
||||
"field_3" : "patched_value"
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
TestImportLoadStore(inputFile, expectedOutput);
|
||||
}
|
||||
|
||||
TEST_F(JsonImportingTests, NestedImportCycleTest)
|
||||
{
|
||||
const char* inputFile = R"(
|
||||
{
|
||||
"name" : "nested_import_cycle",
|
||||
"object": {"$import" : "nested_import_c1.json"}
|
||||
}
|
||||
)";
|
||||
|
||||
TestImportCycle(inputFile);
|
||||
}
|
||||
|
||||
TEST_F(JsonImportingTests, InsertNewImportTest)
|
||||
{
|
||||
const char* inputFile = R"(
|
||||
{
|
||||
"name" : "simple_object_import",
|
||||
"object_1": {"$import" : "object.json"},
|
||||
"object_2": {
|
||||
"field_1" : "other_value",
|
||||
"field_2" : "value_2",
|
||||
"field_3" : "value_3"
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char* expectedOutput = R"(
|
||||
{
|
||||
"name" : "simple_object_import",
|
||||
"object_1": {"$import" : "object.json"},
|
||||
"object_2": {
|
||||
"$import" : {
|
||||
"filename" : "object.json",
|
||||
"patch" : { "field_1" : "other_value" }
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
TestInsertNewImport(inputFile, expectedOutput);
|
||||
}
|
||||
}
|
||||
@@ -121,6 +121,7 @@ set(FILES
|
||||
Serialization/Json/TestCases_Classes.cpp
|
||||
Serialization/Json/TestCases_Compare.cpp
|
||||
Serialization/Json/TestCases_Enum.cpp
|
||||
Serialization/Json/TestCases_Importing.cpp
|
||||
Serialization/Json/TestCases_Patching.cpp
|
||||
Serialization/Json/TestCases_Pointers.h
|
||||
Serialization/Json/TestCases_Pointers.cpp
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace AzFramework
|
||||
class IMatchmakingAsyncRequests
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(ISessionAsyncRequests, "{53513480-2D02-493C-B44E-96AA27F42429}");
|
||||
AZ_RTTI(IMatchmakingAsyncRequests, "{53513480-2D02-493C-B44E-96AA27F42429}");
|
||||
|
||||
IMatchmakingAsyncRequests() = default;
|
||||
virtual ~IMatchmakingAsyncRequests() = default;
|
||||
@@ -60,4 +60,31 @@ namespace AzFramework
|
||||
// @param stopMatchmakingRequest The request of StopMatchmaking operation
|
||||
virtual void StopMatchmakingAsync(const StopMatchmakingRequest& stopMatchmakingRequest) = 0;
|
||||
};
|
||||
|
||||
//! MatchmakingAsyncRequestNotifications
|
||||
//! The notifications correspond to matchmaking async requests
|
||||
class MatchmakingAsyncRequestNotifications
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
// Safeguard handler for multi-threaded use case
|
||||
using MutexType = AZStd::recursive_mutex;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// EBusTraits overrides
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes
|
||||
virtual void OnAcceptMatchAsyncComplete() = 0;
|
||||
|
||||
// OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes
|
||||
// @param matchmakingTicketId The unique identifier for the matchmaking ticket
|
||||
virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0;
|
||||
|
||||
// OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes
|
||||
virtual void OnStopMatchmakingAsyncComplete() = 0;
|
||||
};
|
||||
using MatchmakingAsyncRequestNotificationBus = AZ::EBus<MatchmakingAsyncRequestNotifications>;
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -13,36 +13,10 @@
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
//! MatchmakingAsyncRequestNotifications
|
||||
//! The notifications correspond to matchmaking async requests
|
||||
class MatchmakingAsyncRequestNotifications
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
// Safeguard handler for multi-threaded use case
|
||||
using MutexType = AZStd::recursive_mutex;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// EBusTraits overrides
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes
|
||||
virtual void OnAcceptMatchAsyncComplete() = 0;
|
||||
|
||||
// OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes
|
||||
// @param matchmakingTicketId The unique identifier for the matchmaking ticket
|
||||
virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0;
|
||||
|
||||
// OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes
|
||||
virtual void OnStopMatchmakingAsyncComplete() = 0;
|
||||
};
|
||||
using MatchmakingAsyncRequestNotificationBus = AZ::EBus<MatchmakingAsyncRequestNotifications>;
|
||||
|
||||
//! MatchmakingNotifications
|
||||
//! The matchmaking notifications to listen for performing required operations
|
||||
class MatchAcceptanceNotifications
|
||||
//! based on matchmaking ticket event
|
||||
class MatchmakingNotifications
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
@@ -55,8 +29,18 @@ namespace AzFramework
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// OnMatchAcceptance is fired when DescribeMatchmaking ticket status is REQUIRES_ACCEPTANCE
|
||||
// OnMatchAcceptance is fired when match is found and pending on acceptance
|
||||
// Use this notification to accept found match
|
||||
virtual void OnMatchAcceptance() = 0;
|
||||
|
||||
// OnMatchComplete is fired when match is complete
|
||||
virtual void OnMatchComplete() = 0;
|
||||
|
||||
// OnMatchError is fired when match is processed with error
|
||||
virtual void OnMatchError() = 0;
|
||||
|
||||
// OnMatchFailure is fired when match is failed to complete
|
||||
virtual void OnMatchFailure() = 0;
|
||||
};
|
||||
using MatchAcceptanceNotificationBus = AZ::EBus<MatchAcceptanceNotifications>;
|
||||
using MatchmakingNotificationBus = AZ::EBus<MatchmakingNotifications>;
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace AzFramework
|
||||
AZStd::scoped_ptr<ProcessWatcher> pWatcher(LaunchProcess(processLaunchInfo, communicationType));
|
||||
if (!pWatcher)
|
||||
{
|
||||
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str());
|
||||
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str());
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@@ -31,7 +31,7 @@ namespace AzFramework
|
||||
ProcessCommunicator* pCommunicator = pWatcher->GetCommunicator();
|
||||
if (!pCommunicator || !pCommunicator->IsValid())
|
||||
{
|
||||
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str());
|
||||
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str());
|
||||
return false;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -308,7 +308,7 @@ namespace AzFramework
|
||||
event.data.data32[2] = 0;
|
||||
event.data.data32[3] = 1;
|
||||
event.data.data32[4] = 0;
|
||||
xcb_void_cookie_t xcbCheckResult = xcb_send_event(
|
||||
[[maybe_unused]] xcb_void_cookie_t xcbCheckResult = xcb_send_event(
|
||||
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
|
||||
(const char*)&event);
|
||||
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set _NET_WM_STATE_FULLSCREEN");
|
||||
@@ -333,7 +333,7 @@ namespace AzFramework
|
||||
event.data.data32[2] = 0;
|
||||
event.data.data32[3] = 0;
|
||||
event.data.data32[4] = 0;
|
||||
xcb_void_cookie_t xcbCheckResult = xcb_send_event(
|
||||
[[maybe_unused]] xcb_void_cookie_t xcbCheckResult = xcb_send_event(
|
||||
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
|
||||
(const char*)&event);
|
||||
AZ_Assert(
|
||||
|
||||
+18
@@ -7,17 +7,35 @@
|
||||
*/
|
||||
|
||||
#include <AzFramework/Application/Application.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
#include <AzFramework/XcbApplication.h>
|
||||
#endif
|
||||
|
||||
constexpr rlim_t g_minimumOpenFileHandles = 65536L;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Application::Implementation* Application::Implementation::Create()
|
||||
{
|
||||
// The default open file limit for processes may not be enough for O3DE applications.
|
||||
// We will need to increase to the recommended value if the current open file limit
|
||||
// is not sufficient.
|
||||
rlimit currentLimit;
|
||||
int get_limit_result = getrlimit(RLIMIT_NOFILE, ¤tLimit);
|
||||
AZ_Warning("Application", get_limit_result == 0, "Unable to read current ulimit open file limits");
|
||||
if ((get_limit_result == 0) && (currentLimit.rlim_cur < g_minimumOpenFileHandles || currentLimit.rlim_max < g_minimumOpenFileHandles))
|
||||
{
|
||||
rlimit newLimit;
|
||||
newLimit.rlim_cur = g_minimumOpenFileHandles; // Soft Limit
|
||||
newLimit.rlim_max = g_minimumOpenFileHandles; // Hard Limit
|
||||
[[maybe_unused]] int set_limit_result = setrlimit(RLIMIT_NOFILE, &newLimit);
|
||||
AZ_Assert(set_limit_result == 0, "Unable to update open file limits");
|
||||
}
|
||||
|
||||
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
return aznew XcbApplication();
|
||||
#elif PAL_TRAIT_LINUX_WINDOW_MANAGER_WAYLAND
|
||||
|
||||
+71
-24
@@ -91,31 +91,42 @@ namespace AzFramework
|
||||
return processId == 0;
|
||||
}
|
||||
|
||||
/*! Executes a command in the child process after the fork operation has been executed.
|
||||
* This function will never return. If the execvp command fails this will call _exit with
|
||||
* the errno value as the return value since continuing execution after a execvp command
|
||||
* is invalid (it will be running the parent's code and in its address space and will
|
||||
* cause many issues).
|
||||
/*! Executes a command in the child process after the fork operation
|
||||
* has been executed. This function will never return. If the execvpe
|
||||
* command fails this will call _exit since continuing execution after
|
||||
* a execvpe command is invalid (it will be running the parent's code
|
||||
* and in its address space and will cause many issues).
|
||||
*
|
||||
* This function runs after a `fork()` call. `fork()` creates a copy of
|
||||
* the current process, including the current state of the process's
|
||||
* memory, at the time the call is made. However, it only creates a
|
||||
* copy of the one thread that called `fork()`. This means that if any
|
||||
* mutexes are locked by other threads at the time of `fork()`, those
|
||||
* mutexes will remain locked in the child process, with no way to
|
||||
* unlock them. So this function needs to ensure that it does as little
|
||||
* work as possible.
|
||||
*
|
||||
* \param commandAndArgs - Array of strings that has the command to execute in index 0 with any args for the command following. Last element must be a null pointer.
|
||||
* \param envionrmentVariables - Array of strings that contains environment variables that command should use. Last element must be a null pointer.
|
||||
* \param environmentVariables - Array of strings that contains environment variables that command should use. Last element must be a null pointer.
|
||||
* \param processLaunchInfo - struct containing information about luanching the command
|
||||
* \param startupInfo - struct containing information needed to startup the command
|
||||
* \param errorPipe - a pipe file descriptor used to communicate a failed execvpe call's error code to the parent process
|
||||
*/
|
||||
void ExecuteCommandAsChild(char** commandAndArgs, char** environmentVariables, const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, StartupInfo& startupInfo)
|
||||
[[noreturn]] static void ExecuteCommandAsChild(char** commandAndArgs, char** environmentVariables, const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, StartupInfo& startupInfo, const AZStd::array<int, 2>& errorPipe)
|
||||
{
|
||||
close(errorPipe[0]);
|
||||
|
||||
if (!processLaunchInfo.m_workingDirectory.empty())
|
||||
{
|
||||
int res = chdir(processLaunchInfo.m_workingDirectory.c_str());
|
||||
if (res != 0)
|
||||
{
|
||||
std::cerr << strerror(errno) << std::endl;
|
||||
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to change the launched process' directory to '%s'.", processLaunchInfo.m_workingDirectory.c_str());
|
||||
write(errorPipe[1], &errno, sizeof(int));
|
||||
// We *have* to _exit as we are the child process and simply
|
||||
// returning at this point would mean we would start running
|
||||
// the code from our parent process and that will just wreck
|
||||
// havoc.
|
||||
_exit(errno);
|
||||
_exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,15 +146,17 @@ namespace AzFramework
|
||||
|
||||
startupInfo.SetupHandlesForChildProcess();
|
||||
|
||||
execve(commandAndArgs[0], commandAndArgs, environmentVariables);
|
||||
execvpe(commandAndArgs[0], commandAndArgs, environmentVariables);
|
||||
const int errval = errno;
|
||||
|
||||
// If we get here then execve failed to run the requested program and
|
||||
// If we get here then execvpe failed to run the requested program and
|
||||
// we have an error. In this case we need to exit the child process
|
||||
// to stop it from continuing to run as a clone of the parent
|
||||
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process %s : errno = %s ", commandAndArgs[0], strerror(errno));
|
||||
std::cerr << strerror(errno) << std::endl;
|
||||
// to stop it from continuing to run as a clone of the parent.
|
||||
// Communicate the error code back to the parent via a pipe for the
|
||||
// parent to read.
|
||||
write(errorPipe[1], &errval, sizeof(errval));
|
||||
|
||||
_exit(errno);
|
||||
_exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,9 +225,8 @@ namespace AzFramework
|
||||
|
||||
AZStd::string outputString;
|
||||
bool inQuotes = false;
|
||||
for (size_t pos = 0; pos < processLaunchInfo.m_commandlineParameters.size(); ++pos)
|
||||
for (const char currentChar : processLaunchInfo.m_commandlineParameters)
|
||||
{
|
||||
char currentChar = processLaunchInfo.m_commandlineParameters[pos];
|
||||
if (currentChar == '"')
|
||||
{
|
||||
inQuotes = !inQuotes;
|
||||
@@ -231,7 +243,7 @@ namespace AzFramework
|
||||
outputString.push_back(currentChar);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!outputString.empty())
|
||||
{
|
||||
commandTokens.push_back(outputString);
|
||||
@@ -249,10 +261,10 @@ namespace AzFramework
|
||||
return false;
|
||||
}
|
||||
|
||||
// Because of the way execve is defined we need to copy the strings from
|
||||
// Because of the way execvpe is defined we need to copy the strings from
|
||||
// AZ::string (using c_str() returns a const char*) into a non-const char*
|
||||
|
||||
// Need to add one more as exec requires the array's last element to be a null pointer
|
||||
// Need to add one more as execvpe requires the array's last element to be a null pointer
|
||||
char** commandAndArgs = new char*[commandTokens.size() + 1];
|
||||
for (int i = 0; i < commandTokens.size(); ++i)
|
||||
{
|
||||
@@ -275,7 +287,7 @@ namespace AzFramework
|
||||
azstrcat(environmentVariable.get(), envVarString.size() + 1, envVarString.c_str());
|
||||
environmentVariablesVector.emplace_back(environmentVariable.get());
|
||||
}
|
||||
// Adding one more as exec expects the array to have a nullptr as the last element
|
||||
// Adding one more as execvpe expects the array to have a nullptr as the last element
|
||||
environmentVariablesVector.emplace_back(nullptr);
|
||||
environmentVariables = environmentVariablesVector.data();
|
||||
}
|
||||
@@ -288,15 +300,50 @@ namespace AzFramework
|
||||
AZ_Assert(environmentVariables, "Environment variables for current process not available\n");
|
||||
}
|
||||
|
||||
// Set up a pipe to communicate the error code from the subprocess's execvpe call
|
||||
AZStd::array<int, 2> childErrorPipeFds{};
|
||||
pipe(childErrorPipeFds.data());
|
||||
|
||||
// This configures the write end of the pipe to close on calls to `exec`
|
||||
fcntl(childErrorPipeFds[1], F_SETFD, fcntl(childErrorPipeFds[1], F_GETFD) | FD_CLOEXEC);
|
||||
|
||||
pid_t child_pid = fork();
|
||||
if (IsIdChildProcess(child_pid))
|
||||
{
|
||||
ExecuteCommandAsChild(commandAndArgs, environmentVariables, processLaunchInfo, processData.m_startupInfo);
|
||||
ExecuteCommandAsChild(commandAndArgs, environmentVariables, processLaunchInfo, processData.m_startupInfo, childErrorPipeFds);
|
||||
}
|
||||
processData.m_childProcessId = child_pid;
|
||||
|
||||
// Close these handles as they are only to be used by the child process
|
||||
processData.m_startupInfo.CloseAllHandles();
|
||||
close(childErrorPipeFds[1]);
|
||||
|
||||
{
|
||||
int errorCodeFromChild = 0;
|
||||
int count = 0;
|
||||
// Read from the error pipe.
|
||||
// * If the child's call to execvpe succeeded, then the pipe will
|
||||
// be closed due to setting FD_CLOEXEC on the write end of the
|
||||
// pipe. `read()` will return 0.
|
||||
// * If the child's call to execvpe failed, the child will have
|
||||
// written the error code to the pipe. `read()` will return >0, and
|
||||
// the data to be read is the error code from execvpe.
|
||||
while ((count = read(childErrorPipeFds[0], &errorCodeFromChild, sizeof(errorCodeFromChild))) == -1)
|
||||
{
|
||||
if (errno != EAGAIN && errno != EINTR)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (count)
|
||||
{
|
||||
AZ_TracePrintf("Process Watcher", "ProcessLauncher::LaunchProcess: Unable to launch process %s : errno = %s\n", commandAndArgs[0], strerror(errorCodeFromChild));
|
||||
processData.m_childProcessIsDone = true;
|
||||
child_pid = -1;
|
||||
}
|
||||
}
|
||||
close(childErrorPipeFds[0]);
|
||||
|
||||
processData.m_childProcessId = child_pid;
|
||||
|
||||
for (int i = 0; i < commandTokens.size(); i++)
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
#include <dlfcn.h>
|
||||
#include <iostream>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzTest/Platform.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
@@ -20,11 +21,16 @@ public:
|
||||
explicit ModuleHandle(const std::string& lib)
|
||||
: m_libHandle(nullptr)
|
||||
{
|
||||
std::string libext = lib;
|
||||
if (!AZ::Test::EndsWith(libext, ".dylib"))
|
||||
AZ::IO::FixedMaxPath libext = AZStd::string_view{ lib.c_str(), lib.size() };
|
||||
if (!libext.Stem().Native().starts_with(AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX))
|
||||
{
|
||||
libext += ".dylib";
|
||||
libext = AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX + libext.Native();
|
||||
}
|
||||
if (libext.Extension() != AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION)
|
||||
{
|
||||
libext.Native() += AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION;
|
||||
}
|
||||
|
||||
m_libHandle = dlopen(libext.c_str(), RTLD_NOW);
|
||||
const char* error = dlerror();
|
||||
if (error)
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ namespace AzToolsFramework
|
||||
AZ_Assert(m_loaderInterface != nullptr,
|
||||
"Couldn't get prefab loader interface, it's a requirement for PrefabEntityOwnership system to work");
|
||||
|
||||
m_rootInstance = AZStd::unique_ptr<Prefab::Instance>(m_prefabSystemComponent->CreatePrefab({}, {}, "NewLevel.prefab"));
|
||||
m_rootInstance = AZStd::unique_ptr<Prefab::Instance>(m_prefabSystemComponent->CreatePrefab({}, {}, "newLevel.prefab"));
|
||||
m_sliceOwnershipService.BusConnect(m_entityContextId);
|
||||
m_sliceOwnershipService.m_shouldAssertForLegacySlicesUsage = m_shouldAssertForLegacySlicesUsage;
|
||||
m_editorSliceOwnershipService.BusConnect();
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
|
||||
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/Math/Uuid.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
|
||||
@@ -38,6 +40,13 @@ namespace AzToolsFramework
|
||||
AZ::Edit::SliceFlags::DontGatherReference);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->ConstantProperty(
|
||||
"EditorPrefabComponentTypeId", BehaviorConstant(AZ::Uuid(EditorPrefabComponent::EditorPrefabComponentTypeId)))
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPrefabComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
|
||||
|
||||
@@ -16,7 +16,9 @@ namespace AzToolsFramework
|
||||
class EditorPrefabComponent : public AzToolsFramework::Components::EditorComponentBase
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(EditorPrefabComponent, "{756E5F9C-3E08-4F8D-855C-A5AEEFB6FCDD}", EditorComponentBase);
|
||||
static constexpr const char* const EditorPrefabComponentTypeId = "{756E5F9C-3E08-4F8D-855C-A5AEEFB6FCDD}";
|
||||
|
||||
AZ_COMPONENT(EditorPrefabComponent, EditorPrefabComponentTypeId, EditorComponentBase);
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services);
|
||||
|
||||
@@ -28,7 +28,9 @@ namespace AzToolsFramework::Prefab
|
||||
"Instance Entity Mapper Interface could not be found. "
|
||||
"Check that it is being correctly initialized.");
|
||||
|
||||
EditorEntityInfoNotificationBus::Handler::BusConnect();
|
||||
EditorEntityContextNotificationBus::Handler::BusConnect();
|
||||
PrefabPublicNotificationBus::Handler::BusConnect();
|
||||
AZ::Interface<PrefabFocusInterface>::Register(this);
|
||||
AZ::Interface<PrefabFocusPublicInterface>::Register(this);
|
||||
}
|
||||
@@ -37,10 +39,12 @@ namespace AzToolsFramework::Prefab
|
||||
{
|
||||
AZ::Interface<PrefabFocusPublicInterface>::Unregister(this);
|
||||
AZ::Interface<PrefabFocusInterface>::Unregister(this);
|
||||
PrefabPublicNotificationBus::Handler::BusDisconnect();
|
||||
EditorEntityContextNotificationBus::Handler::BusDisconnect();
|
||||
EditorEntityInfoNotificationBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
void PrefabFocusHandler::Initialize()
|
||||
void PrefabFocusHandler::InitializeEditorInterfaces()
|
||||
{
|
||||
m_containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get();
|
||||
AZ_Assert(
|
||||
@@ -55,13 +59,6 @@ namespace AzToolsFramework::Prefab
|
||||
"Prefab - PrefabFocusHandler - "
|
||||
"Focus Mode Interface could not be found. "
|
||||
"Check that it is being correctly initialized.");
|
||||
|
||||
m_instanceEntityMapperInterface = AZ::Interface<InstanceEntityMapperInterface>::Get();
|
||||
AZ_Assert(
|
||||
m_instanceEntityMapperInterface,
|
||||
"Prefab - PrefabFocusHandler - "
|
||||
"Instance Entity Mapper Interface could not be found. "
|
||||
"Check that it is being correctly initialized.");
|
||||
}
|
||||
|
||||
PrefabFocusOperationResult PrefabFocusHandler::FocusOnOwningPrefab(AZ::EntityId entityId)
|
||||
@@ -90,12 +87,12 @@ namespace AzToolsFramework::Prefab
|
||||
|
||||
PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index)
|
||||
{
|
||||
if (index < 0 || index >= m_instanceFocusVector.size())
|
||||
if (index < 0 || index >= m_instanceFocusHierarchy.size())
|
||||
{
|
||||
return AZ::Failure(AZStd::string("Prefab Focus Handler: Invalid index on FocusOnPathIndex."));
|
||||
}
|
||||
|
||||
InstanceOptionalReference focusedInstance = m_instanceFocusVector[index];
|
||||
InstanceOptionalReference focusedInstance = m_instanceFocusHierarchy[index];
|
||||
|
||||
FocusOnOwningPrefab(focusedInstance->get().GetContainerEntityId());
|
||||
|
||||
@@ -134,42 +131,38 @@ namespace AzToolsFramework::Prefab
|
||||
return AZ::Failure(AZStd::string("Prefab Focus Handler: invalid instance to focus on."));
|
||||
}
|
||||
|
||||
if (!m_isInitialized)
|
||||
// Close all container entities in the old path.
|
||||
CloseInstanceContainers(m_instanceFocusHierarchy);
|
||||
|
||||
m_focusedInstance = focusedInstance;
|
||||
m_focusedTemplateId = focusedInstance->get().GetTemplateId();
|
||||
|
||||
AZ::EntityId containerEntityId;
|
||||
|
||||
if (focusedInstance->get().GetParentInstance() != AZStd::nullopt)
|
||||
{
|
||||
Initialize();
|
||||
containerEntityId = focusedInstance->get().GetContainerEntityId();
|
||||
}
|
||||
|
||||
if (!m_focusedInstance.has_value() || &m_focusedInstance->get() != &focusedInstance->get())
|
||||
else
|
||||
{
|
||||
// Close all container entities in the old path
|
||||
CloseInstanceContainers(m_instanceFocusVector);
|
||||
|
||||
m_focusedInstance = focusedInstance;
|
||||
m_focusedTemplateId = focusedInstance->get().GetTemplateId();
|
||||
|
||||
AZ::EntityId containerEntityId;
|
||||
|
||||
if (focusedInstance->get().GetParentInstance() != AZStd::nullopt)
|
||||
{
|
||||
containerEntityId = focusedInstance->get().GetContainerEntityId();
|
||||
}
|
||||
else
|
||||
{
|
||||
containerEntityId = AZ::EntityId();
|
||||
}
|
||||
containerEntityId = AZ::EntityId();
|
||||
}
|
||||
|
||||
// Focus on the descendants of the container entity
|
||||
// Focus on the descendants of the container entity in the Editor, if the interface is initialized.
|
||||
if (m_focusModeInterface)
|
||||
{
|
||||
m_focusModeInterface->SetFocusRoot(containerEntityId);
|
||||
|
||||
// Refresh path variables
|
||||
RefreshInstanceFocusList();
|
||||
|
||||
// Open all container entities in the new path
|
||||
OpenInstanceContainers(m_instanceFocusVector);
|
||||
|
||||
PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged);
|
||||
}
|
||||
|
||||
// Refresh path variables.
|
||||
RefreshInstanceFocusList();
|
||||
RefreshInstanceFocusPath();
|
||||
|
||||
// Open all container entities in the new path.
|
||||
OpenInstanceContainers(m_instanceFocusHierarchy);
|
||||
|
||||
PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged);
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
@@ -220,49 +213,80 @@ namespace AzToolsFramework::Prefab
|
||||
|
||||
const int PrefabFocusHandler::GetPrefabFocusPathLength([[maybe_unused]] AzFramework::EntityContextId entityContextId) const
|
||||
{
|
||||
return aznumeric_cast<int>(m_instanceFocusVector.size());
|
||||
return aznumeric_cast<int>(m_instanceFocusHierarchy.size());
|
||||
}
|
||||
|
||||
void PrefabFocusHandler::OnEntityStreamLoadSuccess()
|
||||
void PrefabFocusHandler::OnContextReset()
|
||||
{
|
||||
if (!m_isInitialized)
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
// Clear the old focus vector
|
||||
m_instanceFocusVector.clear();
|
||||
m_instanceFocusHierarchy.clear();
|
||||
|
||||
// Focus on the root prefab (AZ::EntityId() will default to it)
|
||||
FocusOnPrefabInstanceOwningEntityId(AZ::EntityId());
|
||||
}
|
||||
|
||||
void PrefabFocusHandler::OnEntityInfoUpdatedName(AZ::EntityId entityId, [[maybe_unused]]const AZStd::string& name)
|
||||
{
|
||||
// Determine if the entityId is the container for any of the instances in the vector
|
||||
auto result = AZStd::find_if(
|
||||
m_instanceFocusHierarchy.begin(), m_instanceFocusHierarchy.end(),
|
||||
[entityId](const InstanceOptionalReference& instance)
|
||||
{
|
||||
return (instance->get().GetContainerEntityId() == entityId);
|
||||
}
|
||||
);
|
||||
|
||||
if (result != m_instanceFocusHierarchy.end())
|
||||
{
|
||||
// Refresh the path and notify changes.
|
||||
RefreshInstanceFocusPath();
|
||||
PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged);
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabFocusHandler::OnPrefabInstancePropagationEnd()
|
||||
{
|
||||
// Refresh the path and notify changes in case propagation updated any container names.
|
||||
RefreshInstanceFocusPath();
|
||||
PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged);
|
||||
}
|
||||
|
||||
void PrefabFocusHandler::RefreshInstanceFocusList()
|
||||
{
|
||||
m_instanceFocusVector.clear();
|
||||
m_instanceFocusPath.clear();
|
||||
m_instanceFocusHierarchy.clear();
|
||||
|
||||
AZStd::list<InstanceOptionalReference> instanceFocusList;
|
||||
|
||||
// Use a support list to easily push front while traversing the prefab hierarchy
|
||||
InstanceOptionalReference currentInstance = m_focusedInstance;
|
||||
while (currentInstance.has_value())
|
||||
{
|
||||
instanceFocusList.push_front(currentInstance);
|
||||
m_instanceFocusHierarchy.emplace_back(currentInstance);
|
||||
|
||||
currentInstance = currentInstance->get().GetParentInstance();
|
||||
}
|
||||
|
||||
// Populate internals using the support list
|
||||
for (auto& instance : instanceFocusList)
|
||||
// Invert the vector, since we need the top instance to be at index 0
|
||||
AZStd::reverse(m_instanceFocusHierarchy.begin(), m_instanceFocusHierarchy.end());
|
||||
}
|
||||
|
||||
void PrefabFocusHandler::RefreshInstanceFocusPath()
|
||||
{
|
||||
m_instanceFocusPath.clear();
|
||||
|
||||
for (const InstanceOptionalReference& instance : m_instanceFocusHierarchy)
|
||||
{
|
||||
m_instanceFocusPath.Append(instance->get().GetContainerEntity()->get().GetName());
|
||||
m_instanceFocusVector.emplace_back(instance);
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabFocusHandler::OpenInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const
|
||||
{
|
||||
// If this is called outside the Editor, this interface won't be initialized.
|
||||
if (!m_containerEntityInterface)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (const InstanceOptionalReference& instance : instances)
|
||||
{
|
||||
if (instance.has_value())
|
||||
@@ -274,6 +298,12 @@ namespace AzToolsFramework::Prefab
|
||||
|
||||
void PrefabFocusHandler::CloseInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const
|
||||
{
|
||||
// If this is called outside the Editor, this interface won't be initialized.
|
||||
if (!m_containerEntityInterface)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (const InstanceOptionalReference& instance : instances)
|
||||
{
|
||||
if (instance.has_value())
|
||||
|
||||
@@ -11,9 +11,11 @@
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
|
||||
#include <AzToolsFramework/FocusMode/FocusModeInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabPublicNotificationBus.h>
|
||||
#include <AzToolsFramework/Prefab/Template/Template.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
@@ -30,7 +32,9 @@ namespace AzToolsFramework::Prefab
|
||||
class PrefabFocusHandler final
|
||||
: private PrefabFocusInterface
|
||||
, private PrefabFocusPublicInterface
|
||||
, private PrefabPublicNotificationBus::Handler
|
||||
, private EditorEntityContextNotificationBus::Handler
|
||||
, private EditorEntityInfoNotificationBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(PrefabFocusHandler, AZ::SystemAllocator, 0);
|
||||
@@ -38,9 +42,8 @@ namespace AzToolsFramework::Prefab
|
||||
PrefabFocusHandler();
|
||||
~PrefabFocusHandler();
|
||||
|
||||
void Initialize();
|
||||
|
||||
// PrefabFocusInterface overrides ...
|
||||
void InitializeEditorInterfaces() override;
|
||||
PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) override;
|
||||
TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const override;
|
||||
InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const override;
|
||||
@@ -54,25 +57,34 @@ namespace AzToolsFramework::Prefab
|
||||
const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const override;
|
||||
|
||||
// EditorEntityContextNotificationBus overrides ...
|
||||
void OnEntityStreamLoadSuccess() override;
|
||||
void OnContextReset() override;
|
||||
|
||||
// EditorEntityInfoNotificationBus overrides ...
|
||||
void OnEntityInfoUpdatedName(AZ::EntityId entityId, const AZStd::string& name) override;
|
||||
|
||||
// PrefabPublicNotifications overrides ...
|
||||
void OnPrefabInstancePropagationEnd();
|
||||
|
||||
private:
|
||||
PrefabFocusOperationResult FocusOnPrefabInstance(InstanceOptionalReference focusedInstance);
|
||||
void RefreshInstanceFocusList();
|
||||
void RefreshInstanceFocusPath();
|
||||
|
||||
void OpenInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const;
|
||||
void CloseInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const;
|
||||
|
||||
//! The instance the editor is currently focusing on.
|
||||
InstanceOptionalReference m_focusedInstance;
|
||||
//! The templateId of the focused instance.
|
||||
TemplateId m_focusedTemplateId;
|
||||
AZStd::vector<InstanceOptionalReference> m_instanceFocusVector;
|
||||
//! The list of instances going from the root (index 0) to the focused instance.
|
||||
AZStd::vector<InstanceOptionalReference> m_instanceFocusHierarchy;
|
||||
//! A path containing the names of the containers in the instance focus hierarchy, separated with a /.
|
||||
AZ::IO::Path m_instanceFocusPath;
|
||||
|
||||
ContainerEntityInterface* m_containerEntityInterface = nullptr;
|
||||
FocusModeInterface* m_focusModeInterface = nullptr;
|
||||
InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr;
|
||||
|
||||
bool m_isInitialized = false;
|
||||
};
|
||||
|
||||
} // namespace AzToolsFramework::Prefab
|
||||
|
||||
@@ -26,6 +26,11 @@ namespace AzToolsFramework::Prefab
|
||||
public:
|
||||
AZ_RTTI(PrefabFocusInterface, "{F3CFA37B-5FD8-436A-9C30-60EB54E350E1}");
|
||||
|
||||
//! Initializes the editor interfaces for Prefab Focus mode.
|
||||
//! If this is not called on initialization, the Prefab Focus Mode functions will still work
|
||||
//! but won't trigger the Editor APIs to visualize focus mode on the UI.
|
||||
virtual void InitializeEditorInterfaces() = 0;
|
||||
|
||||
//! Set the focused prefab instance to the owning instance of the entityId provided.
|
||||
//! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on.
|
||||
virtual PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) = 0;
|
||||
|
||||
@@ -974,7 +974,7 @@ namespace AzToolsFramework
|
||||
return DeleteFromInstance(entityIds, true);
|
||||
}
|
||||
|
||||
PrefabOperationResult PrefabPublicHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds)
|
||||
DuplicatePrefabResult PrefabPublicHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds)
|
||||
{
|
||||
if (entityIds.empty())
|
||||
{
|
||||
@@ -1021,6 +1021,7 @@ namespace AzToolsFramework
|
||||
|
||||
ScopedUndoBatch undoBatch("Duplicate Entities");
|
||||
|
||||
EntityIdList duplicatedEntityAndInstanceIds;
|
||||
{
|
||||
AZ_PROFILE_SCOPE(AzToolsFramework, "DuplicateEntitiesInInstance::UndoCaptureAndDuplicateEntities");
|
||||
|
||||
@@ -1033,7 +1034,7 @@ namespace AzToolsFramework
|
||||
|
||||
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
|
||||
{
|
||||
return AZStd::move(retrieveEntitiesAndInstancesOutcome);
|
||||
return AZ::Failure(retrieveEntitiesAndInstancesOutcome.TakeError());
|
||||
}
|
||||
|
||||
// Take a snapshot of the instance DOM before we manipulate it
|
||||
@@ -1044,8 +1045,6 @@ namespace AzToolsFramework
|
||||
PrefabDom instanceDomAfter;
|
||||
instanceDomAfter.CopyFrom(instanceDomBefore, instanceDomAfter.GetAllocator());
|
||||
|
||||
EntityIdList duplicatedEntityAndInstanceIds;
|
||||
|
||||
// Duplicate any nested entities and instances as requested
|
||||
AZStd::unordered_map<InstanceAlias, Instance*> newInstanceAliasToOldInstanceMap;
|
||||
AZStd::unordered_map<EntityAlias, EntityAlias> duplicateEntityAliasMap;
|
||||
@@ -1114,7 +1113,7 @@ namespace AzToolsFramework
|
||||
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, duplicatedEntityAndInstanceIds);
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
return AZ::Success(AZStd::move(duplicatedEntityAndInstanceIds));
|
||||
}
|
||||
|
||||
PrefabOperationResult PrefabPublicHandler::DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants)
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace AzToolsFramework
|
||||
|
||||
PrefabOperationResult DeleteEntitiesInInstance(const EntityIdList& entityIds) override;
|
||||
PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override;
|
||||
PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
|
||||
DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
|
||||
|
||||
PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace AzToolsFramework
|
||||
{
|
||||
typedef AZ::Outcome<AZ::EntityId, AZStd::string> CreatePrefabResult;
|
||||
typedef AZ::Outcome<AZ::EntityId, AZStd::string> InstantiatePrefabResult;
|
||||
typedef AZ::Outcome<EntityIdList, AZStd::string> DuplicatePrefabResult;
|
||||
typedef AZ::Outcome<void, AZStd::string> PrefabOperationResult;
|
||||
typedef AZ::Outcome<bool, AZStd::string> PrefabRequestResult;
|
||||
typedef AZ::Outcome<AZ::EntityId, AZStd::string> PrefabEntityResult;
|
||||
@@ -160,14 +161,15 @@ namespace AzToolsFramework
|
||||
/**
|
||||
* Duplicates all entities in the owning instance. Bails if the entities don't all belong to the same instance.
|
||||
* @param entities The entities to duplicate.
|
||||
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
|
||||
* @return An outcome object with a list of ids of target entities' duplicates if duplication succeeded;
|
||||
* on failure, it comes with an error message detailing the cause of the error.
|
||||
*/
|
||||
virtual PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0;
|
||||
virtual DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0;
|
||||
|
||||
/**
|
||||
* If the entity id is a container entity id, detaches the prefab instance corresponding to it. This includes converting
|
||||
* the container entity into a regular entity and putting it under the parent prefab, removing the link between this
|
||||
* instance and the parent, removing links between this instance and it's nested instances, adding entities directly
|
||||
* instance and the parent, removing links between this instance and its nested instances, and adding entities directly
|
||||
* owned by this instance under the parent instance.
|
||||
* Bails if the entity is not a container entity or belongs to the level prefab instance.
|
||||
* @param containerEntityId The container entity id of the instance to detach.
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace AzToolsFramework
|
||||
{
|
||||
using CreatePrefabResult = AZ::Outcome<AZ::EntityId, AZStd::string>;
|
||||
using InstantiatePrefabResult = AZ::Outcome<AZ::EntityId, AZStd::string>;
|
||||
using DuplicatePrefabResult = AZ::Outcome<EntityIdList, AZStd::string>;
|
||||
using PrefabOperationResult = AZ::Outcome<void, AZStd::string>;
|
||||
|
||||
/**
|
||||
@@ -69,6 +70,29 @@ namespace AzToolsFramework
|
||||
* Return an outcome object; on failure, it comes with an error message detailing the cause of the error.
|
||||
*/
|
||||
virtual PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) = 0;
|
||||
|
||||
/**
|
||||
* If the entity id is a container entity id, detaches the prefab instance corresponding to it. This includes converting
|
||||
* the container entity into a regular entity and putting it under the parent prefab, removing the link between this
|
||||
* instance and the parent, removing links between this instance and its nested instances, and adding entities directly
|
||||
* owned by this instance under the parent instance.
|
||||
* Bails if the entity is not a container entity or belongs to the level prefab instance.
|
||||
* Return an outcome object; on failure, it comes with an error message detailing the cause of the error.
|
||||
*/
|
||||
virtual PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) = 0;
|
||||
|
||||
/**
|
||||
* Duplicates all entities in the owning instance. Bails if the entities don't all belong to the same instance.
|
||||
* Return an outcome object with a list of ids of given entities' duplicates if duplication succeeded;
|
||||
* on failure, it comes with an error message detailing the cause of the error.
|
||||
*/
|
||||
virtual DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0;
|
||||
|
||||
/**
|
||||
* Get the file path to the prefab file for the prefab instance owning the entity provided.
|
||||
* 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;
|
||||
};
|
||||
|
||||
using PrefabPublicRequestBus = AZ::EBus<PrefabPublicRequests>;
|
||||
|
||||
+17
@@ -28,6 +28,9 @@ namespace AzToolsFramework
|
||||
->Event("CreatePrefabInMemory", &PrefabPublicRequests::CreatePrefabInMemory)
|
||||
->Event("InstantiatePrefab", &PrefabPublicRequests::InstantiatePrefab)
|
||||
->Event("DeleteEntitiesAndAllDescendantsInInstance", &PrefabPublicRequests::DeleteEntitiesAndAllDescendantsInInstance)
|
||||
->Event("DetachPrefab", &PrefabPublicRequests::DetachPrefab)
|
||||
->Event("DuplicateEntitiesInInstance", &PrefabPublicRequests::DuplicateEntitiesInInstance)
|
||||
->Event("GetOwningInstancePrefabPath", &PrefabPublicRequests::GetOwningInstancePrefabPath)
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -62,5 +65,19 @@ namespace AzToolsFramework
|
||||
return m_prefabPublicInterface->DeleteEntitiesAndAllDescendantsInInstance(entityIds);
|
||||
}
|
||||
|
||||
PrefabOperationResult PrefabPublicRequestHandler::DetachPrefab(const AZ::EntityId& containerEntityId)
|
||||
{
|
||||
return m_prefabPublicInterface->DetachPrefab(containerEntityId);
|
||||
}
|
||||
|
||||
DuplicatePrefabResult PrefabPublicRequestHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds)
|
||||
{
|
||||
return m_prefabPublicInterface->DuplicateEntitiesInInstance(entityIds);
|
||||
}
|
||||
|
||||
AZStd::string PrefabPublicRequestHandler::GetOwningInstancePrefabPath(AZ::EntityId entityId) const
|
||||
{
|
||||
return m_prefabPublicInterface->GetOwningInstancePrefabPath(entityId).Native();
|
||||
}
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
@@ -34,6 +34,9 @@ namespace AzToolsFramework
|
||||
CreatePrefabResult CreatePrefabInMemory(const EntityIdList& entityIds, AZStd::string_view filePath) override;
|
||||
InstantiatePrefabResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) override;
|
||||
PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override;
|
||||
PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override;
|
||||
DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
|
||||
AZStd::string GetOwningInstancePrefabPath(AZ::EntityId entityId) const override;
|
||||
|
||||
private:
|
||||
PrefabPublicInterface* m_prefabPublicInterface = nullptr;
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace AzToolsFramework
|
||||
}
|
||||
|
||||
//PrefabInstanceUndo
|
||||
PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName, const bool useImmediatePropagation)
|
||||
PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName, bool useImmediatePropagation)
|
||||
: PrefabUndoBase(undoOperationName)
|
||||
{
|
||||
m_useImmediatePropagation = useImmediatePropagation;
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace AzToolsFramework
|
||||
: public PrefabUndoBase
|
||||
{
|
||||
public:
|
||||
explicit PrefabUndoInstance(const AZStd::string& undoOperationName, const bool useImmediatePropagation = true);
|
||||
explicit PrefabUndoInstance(const AZStd::string& undoOperationName, bool useImmediatePropagation = true);
|
||||
|
||||
void Capture(
|
||||
const PrefabDom& initialState,
|
||||
|
||||
+18
-5
@@ -20,6 +20,8 @@
|
||||
#include <AzFramework/Process/ProcessWatcher.h>
|
||||
#include <AzToolsFramework/SourceControl/PerforceConnection.h>
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
namespace
|
||||
@@ -75,9 +77,17 @@ namespace AzToolsFramework
|
||||
m_resolveKey = true;
|
||||
m_testTrust = false;
|
||||
|
||||
|
||||
// set up signals before we start thread.
|
||||
m_shutdownThreadSignal = false;
|
||||
m_WorkerThread = AZStd::thread(AZStd::bind(&PerforceComponent::ThreadWorker, this));
|
||||
|
||||
// Check to see if the 'p4' command is available at the command line
|
||||
int p4VersionExitCode = QProcess::execute("p4", QStringList{ "-V" });
|
||||
m_p4ApplicationDetected = (p4VersionExitCode == 0);
|
||||
if (m_p4ApplicationDetected)
|
||||
{
|
||||
m_WorkerThread = AZStd::thread(AZStd::bind(&PerforceComponent::ThreadWorker, this));
|
||||
}
|
||||
|
||||
SourceControlConnectionRequestBus::Handler::BusConnect();
|
||||
SourceControlCommandBus::Handler::BusConnect();
|
||||
@@ -88,10 +98,13 @@ namespace AzToolsFramework
|
||||
SourceControlCommandBus::Handler::BusDisconnect();
|
||||
SourceControlConnectionRequestBus::Handler::BusDisconnect();
|
||||
|
||||
m_shutdownThreadSignal = true; // tell the thread to die.
|
||||
m_WorkerSemaphore.release(1); // wake up the thread so that it sees the signal
|
||||
m_WorkerThread.join(); // wait for the thread to finish.
|
||||
m_WorkerThread = AZStd::thread();
|
||||
if (m_p4ApplicationDetected)
|
||||
{
|
||||
m_shutdownThreadSignal = true; // tell the thread to die.
|
||||
m_WorkerSemaphore.release(1); // wake up the thread so that it sees the signal
|
||||
m_WorkerThread.join(); // wait for the thread to finish.
|
||||
m_WorkerThread = AZStd::thread();
|
||||
}
|
||||
|
||||
SetConnection(nullptr);
|
||||
}
|
||||
|
||||
@@ -260,5 +260,7 @@ namespace AzToolsFramework
|
||||
AZStd::atomic_bool m_validConnection;
|
||||
|
||||
SourceControlState m_connectionState;
|
||||
|
||||
bool m_p4ApplicationDetected { false };
|
||||
};
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+9
-15
@@ -27,6 +27,7 @@
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
|
||||
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
|
||||
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
|
||||
#include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
|
||||
@@ -135,6 +136,10 @@ namespace AzToolsFramework
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize Editor functionality for the Prefab Focus Handler
|
||||
auto prefabFocusInterface = AZ::Interface<PrefabFocusInterface>::Get();
|
||||
prefabFocusInterface->InitializeEditorInterfaces();
|
||||
|
||||
EditorContextMenuBus::Handler::BusConnect();
|
||||
EditorEventsBus::Handler::BusConnect();
|
||||
PrefabInstanceContainerNotificationBus::Handler::BusConnect();
|
||||
@@ -250,7 +255,7 @@ namespace AzToolsFramework
|
||||
if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity))
|
||||
{
|
||||
// Edit Prefab
|
||||
if (prefabWipFeaturesEnabled && !s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity))
|
||||
if (!s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity))
|
||||
{
|
||||
QAction* editAction = menu->addAction(QObject::tr("Edit Prefab"));
|
||||
editAction->setToolTip(QObject::tr("Edit the prefab in focus mode."));
|
||||
@@ -1154,25 +1159,14 @@ namespace AzToolsFramework
|
||||
{
|
||||
s_editorEntityUiInterface->RegisterEntity(entityId, m_prefabUiHandler.GetHandlerId());
|
||||
|
||||
bool prefabWipFeaturesEnabled = false;
|
||||
AzFramework::ApplicationRequests::Bus::BroadcastResult(
|
||||
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
|
||||
|
||||
if (prefabWipFeaturesEnabled)
|
||||
{
|
||||
// Register entity as a container
|
||||
s_containerEntityInterface->RegisterEntityAsContainer(entityId);
|
||||
}
|
||||
// Register entity as a container
|
||||
s_containerEntityInterface->RegisterEntityAsContainer(entityId);
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::OnPrefabComponentDeactivate(AZ::EntityId entityId)
|
||||
{
|
||||
bool prefabWipFeaturesEnabled = false;
|
||||
AzFramework::ApplicationRequests::Bus::BroadcastResult(
|
||||
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
|
||||
|
||||
if (prefabWipFeaturesEnabled && !s_prefabPublicInterface->IsLevelInstanceContainerEntity(entityId))
|
||||
if (!s_prefabPublicInterface->IsLevelInstanceContainerEntity(entityId))
|
||||
{
|
||||
// Unregister entity as a container
|
||||
s_containerEntityInterface->UnregisterEntityAsContainer(entityId);
|
||||
|
||||
@@ -316,14 +316,7 @@ namespace AzToolsFramework
|
||||
|
||||
void PrefabUiHandler::OnDoubleClick(AZ::EntityId entityId) const
|
||||
{
|
||||
bool prefabWipFeaturesEnabled = false;
|
||||
AzFramework::ApplicationRequests::Bus::BroadcastResult(
|
||||
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
|
||||
|
||||
if (prefabWipFeaturesEnabled)
|
||||
{
|
||||
// Focus on this prefab
|
||||
m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId);
|
||||
}
|
||||
// Focus on this prefab
|
||||
m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ struct FolderRootWatch::PlatformImplementation
|
||||
// Add the folder to watch and track it
|
||||
int watchHandle = inotify_add_watch(m_iNotifyHandle,
|
||||
cleanPath.toUtf8().constData(),
|
||||
IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY);
|
||||
IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY | IN_MOVE);
|
||||
|
||||
if (!m_handleToFolderMapLock.tryLock(s_handleToFolderMapLockTimeout))
|
||||
{
|
||||
@@ -95,7 +95,7 @@ struct FolderRootWatch::PlatformImplementation
|
||||
|
||||
int watchHandle = inotify_add_watch(m_iNotifyHandle,
|
||||
dirName.toUtf8().constData(),
|
||||
IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY);
|
||||
IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY | IN_MOVE);
|
||||
|
||||
if (!m_handleToFolderMapLock.tryLock(s_handleToFolderMapLockTimeout))
|
||||
{
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
#include <utilities/BatchApplicationManager.h>
|
||||
#include <utilities/ApplicationServer.h>
|
||||
#include <AzFramework/Asset/AssetSystemComponent.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
#include <connection/connectionManager.h>
|
||||
#include <QCoreApplication>
|
||||
#include <QTemporaryDir>
|
||||
@@ -98,10 +100,26 @@ namespace AssetProcessorMessagesTests
|
||||
|
||||
int argC = 0;
|
||||
m_batchApplicationManager = AZStd::make_unique<UnitTestBatchApplicationManager>(&argC, nullptr, nullptr);
|
||||
m_batchApplicationManager->BeforeRun();
|
||||
|
||||
// Override Game Name to be "AutomatedTesting"
|
||||
AssetUtilities::ComputeProjectName("AutomatedTesting", true);
|
||||
auto registry = AZ::SettingsRegistry::Get();
|
||||
EXPECT_NE(registry, nullptr);
|
||||
constexpr AZ::SettingsRegistryInterface::FixedValueString bootstrapKey{
|
||||
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey
|
||||
};
|
||||
constexpr AZ::SettingsRegistryInterface::FixedValueString projectPathKey{ bootstrapKey + "/project_path" };
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
// Force the branch token into settings registry before starting the application manager.
|
||||
// This avoids writing the asset_processor.setreg file which can cause fileIO errors.
|
||||
const AZ::IO::FixedMaxPathString enginePath = AZ::Utils::GetEnginePath();
|
||||
constexpr AZ::SettingsRegistryInterface::FixedValueString branchTokenKey{ bootstrapKey + "/assetProcessor_branch_token" };
|
||||
AZStd::string token;
|
||||
AZ::StringFunc::AssetPath::CalculateBranchToken(enginePath.c_str(), token);
|
||||
registry->Set(branchTokenKey, token.c_str());
|
||||
|
||||
auto status = m_batchApplicationManager->BeforeRun();
|
||||
ASSERT_EQ(status, ApplicationManager::BeforeRunStatus::Status_Success);
|
||||
|
||||
m_batchApplicationManager->m_platformConfiguration = new PlatformConfiguration();
|
||||
m_batchApplicationManager->InitAssetProcessorManager();
|
||||
@@ -159,21 +177,25 @@ namespace AssetProcessorMessagesTests
|
||||
|
||||
ASSERT_TRUE(result);
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
QEventLoop eventLoop;
|
||||
if (m_batchApplicationManager->m_connectionManager)
|
||||
{
|
||||
QEventLoop eventLoop;
|
||||
|
||||
QObject::connect(m_batchApplicationManager->m_connectionManager, &ConnectionManager::ReadyToQuit, &eventLoop, &QEventLoop::quit);
|
||||
QObject::connect(m_batchApplicationManager->m_connectionManager, &ConnectionManager::ReadyToQuit, &eventLoop, &QEventLoop::quit);
|
||||
|
||||
m_batchApplicationManager->m_connectionManager->QuitRequested();
|
||||
m_batchApplicationManager->m_connectionManager->QuitRequested();
|
||||
|
||||
eventLoop.exec();
|
||||
eventLoop.exec();
|
||||
}
|
||||
|
||||
m_assetSystemComponent->Deactivate();
|
||||
if (m_assetSystemComponent)
|
||||
{
|
||||
m_assetSystemComponent->Deactivate();
|
||||
}
|
||||
m_batchApplicationManager->Destroy();
|
||||
}
|
||||
|
||||
|
||||
@@ -505,6 +505,14 @@ bool ApplicationManager::StartAZFramework()
|
||||
AzFramework::Application::Descriptor appDescriptor;
|
||||
AZ::ComponentApplication::StartupParameters params;
|
||||
|
||||
QDir projectPath{ AssetUtilities::ComputeProjectPath() };
|
||||
if (!projectPath.exists("project.json"))
|
||||
{
|
||||
AZStd::string errorMsg = AZStd::string::format("Path '%s' is not a valid project path.", projectPath.path().toUtf8().constData());
|
||||
AssetProcessor::MessageInfoBus::Broadcast(&AssetProcessor::MessageInfoBus::Events::OnErrorMessage, errorMsg.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
QString projectName = AssetUtilities::ComputeProjectName();
|
||||
|
||||
// Prevent loading of gems in the Create method of the ComponentApplication
|
||||
@@ -520,7 +528,6 @@ bool ApplicationManager::StartAZFramework()
|
||||
//Registering all the Components
|
||||
m_frameworkApp.RegisterComponentDescriptor(AzFramework::LogComponent::CreateDescriptor());
|
||||
|
||||
|
||||
Reflect();
|
||||
|
||||
const AzFramework::CommandLine* commandLine = nullptr;
|
||||
|
||||
@@ -95,6 +95,8 @@ GUIApplicationManager::~GUIApplicationManager()
|
||||
|
||||
ApplicationManager::BeforeRunStatus GUIApplicationManager::BeforeRun()
|
||||
{
|
||||
AssetProcessor::MessageInfoBus::Handler::BusConnect();
|
||||
|
||||
ApplicationManager::BeforeRunStatus status = ApplicationManagerBase::BeforeRun();
|
||||
if (status != ApplicationManager::BeforeRunStatus::Status_Success)
|
||||
{
|
||||
@@ -109,7 +111,6 @@ ApplicationManager::BeforeRunStatus GUIApplicationManager::BeforeRun()
|
||||
#if defined(EXTERNAL_CRASH_REPORTING)
|
||||
CrashHandler::ToolsCrashHandler::InitCrashHandler("AssetProcessor", projectAssetRoot.absolutePath().toStdString());
|
||||
#endif
|
||||
AssetProcessor::MessageInfoBus::Handler::BusConnect();
|
||||
|
||||
// we have to monitor both the cache folder and the database file and restart AP if either of them gets deleted
|
||||
// It is important to note that we are monitoring the parent folder and not the actual cache folder itself since
|
||||
@@ -436,98 +437,7 @@ bool GUIApplicationManager::OnError(const char* /*window*/, const char* message)
|
||||
connection = Qt::QueuedConnection;
|
||||
}
|
||||
|
||||
if (m_isCurrentlyLoadingGems)
|
||||
{
|
||||
// if something goes wrong during gem initialization, this is a special case and we need to be extra helpful.
|
||||
const char* userSettingsFile = "_WAF_/user_settings.options";
|
||||
const char* defaultSettingsFile = "_WAF_/default_settings.json";
|
||||
|
||||
QDir engineRoot;
|
||||
AssetUtilities::ComputeEngineRoot(engineRoot);
|
||||
|
||||
QString settingsPath = engineRoot.absoluteFilePath(userSettingsFile);
|
||||
QString friendlyErrorMessage;
|
||||
bool usingDefaults = false;
|
||||
|
||||
if (QFile::exists(settingsPath))
|
||||
{
|
||||
QSettings loader(settingsPath, QSettings::IniFormat);
|
||||
QVariant settingValue = loader.value("Game Projects/enabled_game_projects");
|
||||
QStringList compiledProjects = settingValue.toStringList();
|
||||
|
||||
if (compiledProjects.isEmpty())
|
||||
{
|
||||
QByteArray byteArray;
|
||||
QFile jsonFile;
|
||||
jsonFile.setFileName(engineRoot.absoluteFilePath(defaultSettingsFile));
|
||||
jsonFile.open(QIODevice::ReadOnly | QIODevice::Text);
|
||||
byteArray = jsonFile.readAll();
|
||||
jsonFile.close();
|
||||
|
||||
QJsonObject settingsObject = QJsonDocument::fromJson(byteArray).object();
|
||||
QJsonArray projectsArray = settingsObject["Game Projects"].toArray();
|
||||
|
||||
if (!projectsArray.isEmpty())
|
||||
{
|
||||
auto projectObject = projectsArray[0].toObject();
|
||||
QString projects = projectObject["default_value"].toString();
|
||||
|
||||
if (!projects.isEmpty())
|
||||
{
|
||||
compiledProjects = projects.split(',');
|
||||
usingDefaults = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < compiledProjects.size(); ++i)
|
||||
{
|
||||
compiledProjects[i] = compiledProjects[i].trimmed();
|
||||
}
|
||||
|
||||
QString enabledProject = AssetUtilities::ComputeProjectName();
|
||||
|
||||
if (!compiledProjects.contains(enabledProject))
|
||||
{
|
||||
QString projectSourceLine;
|
||||
|
||||
if (usingDefaults)
|
||||
{
|
||||
projectSourceLine = QString("The currently compiled projects according to the defaults in %1 are '%2'").arg(defaultSettingsFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
projectSourceLine = QString("The currently compiled projects according to %1 are '%2'").arg(userSettingsFile);
|
||||
}
|
||||
|
||||
projectSourceLine = projectSourceLine.arg(compiledProjects.join(", "));
|
||||
friendlyErrorMessage = QString("An error occurred while loading gems.\n"
|
||||
"The enabled game project is not in the list of compiled projects.\n"
|
||||
"Please configure the enabled project to be compiled and rebuild or change the enabled project.\n"
|
||||
"The currently enabled game project (from bootstrap.cfg or /%4 command-line parameter) is '%1'.\n"
|
||||
"%2\n"
|
||||
"Full error text:\n"
|
||||
"%3"
|
||||
).arg(enabledProject).arg(projectSourceLine).arg(message).arg(AssetUtilities::ProjectPathOverrideParameter);
|
||||
}
|
||||
}
|
||||
|
||||
if (friendlyErrorMessage.isEmpty())
|
||||
{
|
||||
friendlyErrorMessage = QString("An error occurred while loading gems.\n"
|
||||
"This can happen when new gems are added to a project, but those gems need to be built in order to function.\n"
|
||||
"This can also happen when switching to a different project, one which uses gems which are not yet built.\n"
|
||||
"To continue, please build the current project before attempting to run Asset Processor again.\n\n"
|
||||
"Full error text:\n"
|
||||
"%1").arg(message);
|
||||
}
|
||||
QMetaObject::invokeMethod(this, "ShowMessageBox", connection, Q_ARG(QString, QString("Error")), Q_ARG(QString, friendlyErrorMessage), Q_ARG(bool, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
QMetaObject::invokeMethod(this, "ShowMessageBox", connection, Q_ARG(QString, QString("Error")), Q_ARG(QString, QString(message)), Q_ARG(bool, true));
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(this, "ShowMessageBox", connection, Q_ARG(QString, QString("Error")), Q_ARG(QString, QString(message)), Q_ARG(bool, true));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
+4
-63
@@ -5,75 +5,16 @@
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
#include <AzFramework/Matchmaking/IMatchmakingRequests.h>
|
||||
#include <AzFramework/Session/ISessionRequests.h>
|
||||
|
||||
namespace AWSGameLift
|
||||
{
|
||||
//! IAWSGameLiftRequests
|
||||
//! GameLift Gem interfaces to configure client manager
|
||||
class IAWSGameLiftRequests
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(IAWSGameLiftRequests, "{494167AD-1185-4AF3-8BF9-C8C37FC9C199}");
|
||||
|
||||
IAWSGameLiftRequests() = default;
|
||||
virtual ~IAWSGameLiftRequests() = default;
|
||||
|
||||
//! ConfigureGameLiftClient
|
||||
//! Configure GameLift client to interact with Amazon GameLift service
|
||||
//! @param region Specifies the AWS region to use
|
||||
//! @return True if client configuration succeeds, false otherwise
|
||||
virtual bool ConfigureGameLiftClient(const AZStd::string& region) = 0;
|
||||
|
||||
//! CreatePlayerId
|
||||
//! Create a new, random ID number for every player in every new game session.
|
||||
//! @param includeBrackets Whether includes brackets in player id
|
||||
//! @param includeDashes Whether includes dashes in player id
|
||||
//! @return The player id to use in game session
|
||||
virtual AZStd::string CreatePlayerId(bool includeBrackets, bool includeDashes) = 0;
|
||||
};
|
||||
|
||||
// IAWSGameLiftRequests EBus wrapper for scripting
|
||||
class AWSGameLiftRequests
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
using MutexType = AZStd::recursive_mutex;
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
};
|
||||
using AWSGameLiftRequestBus = AZ::EBus<IAWSGameLiftRequests, AWSGameLiftRequests>;
|
||||
|
||||
// ISessionAsyncRequests EBus wrapper for scripting
|
||||
class AWSGameLiftSessionAsyncRequests
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
using MutexType = AZStd::recursive_mutex;
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
};
|
||||
using AWSGameLiftSessionAsyncRequestBus = AZ::EBus<AzFramework::ISessionAsyncRequests, AWSGameLiftSessionAsyncRequests>;
|
||||
|
||||
// ISessionRequests EBus wrapper for scripting
|
||||
class AWSGameLiftSessionRequests
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
using MutexType = AZStd::recursive_mutex;
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
};
|
||||
using AWSGameLiftSessionRequestBus = AZ::EBus<AzFramework::ISessionRequests, AWSGameLiftSessionRequests>;
|
||||
|
||||
// IMatchmakingAsyncRequests EBus wrapper for scripting
|
||||
// IMatchmakingAsyncRequests EBus wrapper
|
||||
class AWSGameLiftMatchmakingAsyncRequests
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
@@ -84,7 +25,7 @@ namespace AWSGameLift
|
||||
};
|
||||
using AWSGameLiftMatchmakingAsyncRequestBus = AZ::EBus<AzFramework::IMatchmakingAsyncRequests, AWSGameLiftMatchmakingAsyncRequests>;
|
||||
|
||||
// IMatchmakingRequests EBus wrapper for scripting
|
||||
// IMatchmakingRequests EBus wrapper
|
||||
class AWSGameLiftMatchmakingRequests
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
@@ -121,7 +62,7 @@ namespace AWSGameLift
|
||||
virtual void StopPolling() = 0;
|
||||
};
|
||||
|
||||
// IAWSGameLiftMatchmakingEventRequests EBus wrapper for scripting
|
||||
// IAWSGameLiftMatchmakingEventRequests EBus wrapper
|
||||
class AWSGameLiftMatchmakingEventRequests
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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/string/string.h>
|
||||
|
||||
namespace AWSGameLift
|
||||
{
|
||||
//! IAWSGameLiftRequests
|
||||
//! GameLift Gem interfaces to configure GameLift client and other help functions,
|
||||
//! like creating random GameLift player id
|
||||
class IAWSGameLiftRequests
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(IAWSGameLiftRequests, "{494167AD-1185-4AF3-8BF9-C8C37FC9C199}");
|
||||
|
||||
IAWSGameLiftRequests() = default;
|
||||
virtual ~IAWSGameLiftRequests() = default;
|
||||
|
||||
//! ConfigureGameLiftClient
|
||||
//! Configure GameLift client to interact with Amazon GameLift service
|
||||
//! @param region Specifies the AWS region to use
|
||||
//! @return True if client configuration succeeds, false otherwise
|
||||
virtual bool ConfigureGameLiftClient(const AZStd::string& region) = 0;
|
||||
|
||||
//! CreatePlayerId
|
||||
//! Create a new, random ID number for every player in every new game session.
|
||||
//! @param includeBrackets Whether includes brackets in player id
|
||||
//! @param includeDashes Whether includes dashes in player id
|
||||
//! @return The player id to use in game session
|
||||
virtual AZStd::string CreatePlayerId(bool includeBrackets, bool includeDashes) = 0;
|
||||
};
|
||||
|
||||
// IAWSGameLiftRequests EBus wrapper
|
||||
class AWSGameLiftRequests
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
using MutexType = AZStd::recursive_mutex;
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
};
|
||||
using AWSGameLiftRequestBus = AZ::EBus<IAWSGameLiftRequests, AWSGameLiftRequests>;
|
||||
} // namespace AWSGameLift
|
||||
+38
@@ -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/string/string.h>
|
||||
#include <AzFramework/Session/ISessionRequests.h>
|
||||
|
||||
namespace AWSGameLift
|
||||
{
|
||||
// ISessionAsyncRequests EBus wrapper
|
||||
class AWSGameLiftSessionAsyncRequests
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
using MutexType = AZStd::recursive_mutex;
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
};
|
||||
using AWSGameLiftSessionAsyncRequestBus = AZ::EBus<AzFramework::ISessionAsyncRequests, AWSGameLiftSessionAsyncRequests>;
|
||||
|
||||
// ISessionRequests EBus wrapper
|
||||
class AWSGameLiftSessionRequests
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
using MutexType = AZStd::recursive_mutex;
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
};
|
||||
using AWSGameLiftSessionRequestBus = AZ::EBus<AzFramework::ISessionRequests, AWSGameLiftSessionRequests>;
|
||||
} // namespace AWSGameLift
|
||||
+11
-5
@@ -97,6 +97,7 @@ namespace AWSGameLift
|
||||
AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName,
|
||||
"Matchmaking ticket %s is complete.", ticket.GetTicketId().c_str());
|
||||
RequestPlayerJoinMatch(ticket, playerId);
|
||||
AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchComplete);
|
||||
m_status = TicketTrackerStatus::Idle;
|
||||
return;
|
||||
}
|
||||
@@ -104,25 +105,28 @@ namespace AWSGameLift
|
||||
ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::FAILED ||
|
||||
ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::CANCELLED)
|
||||
{
|
||||
AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, "Matchmaking ticket %s is not complete, %s",
|
||||
ticket.GetTicketId().c_str(), ticket.GetStatusReason().c_str());
|
||||
AZ_Warning(AWSGameLiftClientLocalTicketTrackerName, false, "Matchmaking ticket %s is not complete, %s",
|
||||
ticket.GetTicketId().c_str(), ticket.GetStatusMessage().c_str());
|
||||
AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchFailure);
|
||||
m_status = TicketTrackerStatus::Idle;
|
||||
return;
|
||||
}
|
||||
else if (ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::REQUIRES_ACCEPTANCE)
|
||||
{
|
||||
// broadcast acceptance requires to player
|
||||
AzFramework::MatchAcceptanceNotificationBus::Broadcast(&AzFramework::MatchAcceptanceNotifications::OnMatchAcceptance);
|
||||
AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, "Matchmaking ticket %s is pending on acceptance, %s.",
|
||||
ticket.GetTicketId().c_str(), ticket.GetStatusMessage().c_str());
|
||||
AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchAcceptance);
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_TracePrintf(AWSGameLiftClientLocalTicketTrackerName, "Matchmaking ticket %s is processing, %s.",
|
||||
ticket.GetTicketId().c_str(), ticket.GetStatusReason().c_str());
|
||||
ticket.GetTicketId().c_str(), ticket.GetStatusMessage().c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, "Unable to find expected ticket with id %s", ticketId.c_str());
|
||||
AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchError);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -130,11 +134,13 @@ namespace AWSGameLift
|
||||
AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, AWSGameLiftErrorMessageTemplate,
|
||||
describeMatchmakingOutcome.GetError().GetExceptionName().c_str(),
|
||||
describeMatchmakingOutcome.GetError().GetMessage().c_str());
|
||||
AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchError);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_Error(AWSGameLiftClientLocalTicketTrackerName, false, AWSGameLiftClientMissingErrorMessage);
|
||||
AzFramework::MatchmakingNotificationBus::Broadcast(&AzFramework::MatchmakingNotifications::OnMatchError);
|
||||
}
|
||||
m_waitEvent.try_acquire_for(AZStd::chrono::milliseconds(m_pollingPeriodInMS));
|
||||
}
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
#include <AzCore/std/parallel/mutex.h>
|
||||
#include <AzCore/std/parallel/thread.h>
|
||||
|
||||
#include <Request/IAWSGameLiftRequests.h>
|
||||
#include <Request/AWSGameLiftMatchmakingRequestBus.h>
|
||||
|
||||
#include <aws/gamelift/model/MatchmakingTicket.h>
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include <AWSCoreBus.h>
|
||||
#include <Credential/AWSCredentialBus.h>
|
||||
#include <Framework/AWSApiJobConfig.h>
|
||||
#include <ResourceMapping/AWSResourceMappingBus.h>
|
||||
|
||||
#include <AWSGameLiftClientManager.h>
|
||||
@@ -75,7 +76,15 @@ namespace AWSGameLift
|
||||
bool AWSGameLiftClientManager::ConfigureGameLiftClient(const AZStd::string& region)
|
||||
{
|
||||
AZ::Interface<IAWSGameLiftInternalRequests>::Get()->SetGameLiftClient(nullptr);
|
||||
|
||||
Aws::Client::ClientConfiguration clientConfig;
|
||||
AWSCore::AwsApiJobConfig* defaultConfig = nullptr;
|
||||
AWSCore::AWSCoreRequestBus::BroadcastResult(defaultConfig, &AWSCore::AWSCoreRequests::GetDefaultConfig);
|
||||
if (defaultConfig)
|
||||
{
|
||||
clientConfig = defaultConfig->GetClientConfiguration();
|
||||
}
|
||||
|
||||
// Set up client endpoint or region
|
||||
AZStd::string localEndpoint = "";
|
||||
#if defined(AWSGAMELIFT_DEV)
|
||||
|
||||
@@ -8,10 +8,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/std/smart_ptr/shared_ptr.h>
|
||||
#include <AzFramework/Matchmaking/MatchmakingNotifications.h>
|
||||
|
||||
#include <Request/IAWSGameLiftRequests.h>
|
||||
#include <Request/AWSGameLiftRequestBus.h>
|
||||
#include <Request/AWSGameLiftSessionRequestBus.h>
|
||||
#include <Request/AWSGameLiftMatchmakingRequestBus.h>
|
||||
|
||||
namespace AWSGameLift
|
||||
{
|
||||
@@ -23,22 +26,37 @@ namespace AWSGameLift
|
||||
struct AWSGameLiftStartMatchmakingRequest;
|
||||
struct AWSGameLiftStopMatchmakingRequest;
|
||||
|
||||
// MatchAcceptanceNotificationBus EBus handler for scripting
|
||||
class AWSGameLiftMatchAcceptanceNotificationBusHandler
|
||||
: public AzFramework::MatchAcceptanceNotificationBus::Handler
|
||||
// MatchmakingNotificationBus EBus handler for scripting
|
||||
class AWSGameLiftMatchmakingNotificationBusHandler
|
||||
: public AzFramework::MatchmakingNotificationBus::Handler
|
||||
, public AZ::BehaviorEBusHandler
|
||||
{
|
||||
public:
|
||||
AZ_EBUS_BEHAVIOR_BINDER(
|
||||
AWSGameLiftMatchAcceptanceNotificationBusHandler,
|
||||
AWSGameLiftMatchmakingNotificationBusHandler,
|
||||
"{CBE057D3-F5CE-46D3-B02D-8A6A1446B169}",
|
||||
AZ::SystemAllocator,
|
||||
OnMatchAcceptance);
|
||||
OnMatchAcceptance, OnMatchComplete, OnMatchError, OnMatchFailure);
|
||||
|
||||
void OnMatchAcceptance() override
|
||||
{
|
||||
Call(FN_OnMatchAcceptance);
|
||||
}
|
||||
|
||||
void OnMatchComplete() override
|
||||
{
|
||||
Call(FN_OnMatchComplete);
|
||||
}
|
||||
|
||||
void OnMatchError() override
|
||||
{
|
||||
Call(FN_OnMatchError);
|
||||
}
|
||||
|
||||
void OnMatchFailure() override
|
||||
{
|
||||
Call(FN_OnMatchFailure);
|
||||
}
|
||||
};
|
||||
|
||||
// MatchmakingAsyncRequestNotificationBus EBus handler for scripting
|
||||
|
||||
+18
-17
@@ -65,13 +65,6 @@ namespace AWSGameLift
|
||||
->Event("CreatePlayerId", &AWSGameLiftRequestBus::Events::CreatePlayerId,
|
||||
{ { { "IncludeBrackets", "" },
|
||||
{ "IncludeDashes", "" } } });
|
||||
|
||||
behaviorContext->EBus<AWSGameLiftMatchmakingEventRequestBus>("AWSGameLiftMatchmakingEventRequestBus")
|
||||
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift")
|
||||
->Event("StartPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StartPolling,
|
||||
{ { { "TicketId", "" },
|
||||
{ "PlayerId", "" } } })
|
||||
->Event("StopPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StopPolling);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +121,7 @@ namespace AWSGameLift
|
||||
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->EBus<AWSGameLiftMatchmakingAsyncRequestBus>("AWSGameLiftMatchmakingAsyncRequestBus")
|
||||
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift")
|
||||
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking")
|
||||
->Event("AcceptMatchAsync", &AWSGameLiftMatchmakingAsyncRequestBus::Events::AcceptMatchAsync,
|
||||
{ { { "AcceptMatchRequest", "" } } })
|
||||
->Event("StartMatchmakingAsync", &AWSGameLiftMatchmakingAsyncRequestBus::Events::StartMatchmakingAsync,
|
||||
@@ -137,20 +130,28 @@ namespace AWSGameLift
|
||||
{ { { "StopMatchmakingRequest", "" } } });
|
||||
|
||||
behaviorContext->EBus<AzFramework::MatchmakingAsyncRequestNotificationBus>("AWSGameLiftMatchmakingAsyncRequestNotificationBus")
|
||||
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift")
|
||||
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking")
|
||||
->Handler<AWSGameLiftMatchmakingAsyncRequestNotificationBusHandler>();
|
||||
|
||||
behaviorContext->EBus<AWSGameLiftMatchmakingRequestBus>("AWSGameLiftMatchmakingRequestBus")
|
||||
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift")
|
||||
->Event("AcceptMatch", &AWSGameLiftMatchmakingRequestBus::Events::AcceptMatch, { { { "AcceptMatchRequest", "" } } })
|
||||
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking")
|
||||
->Event("AcceptMatch", &AWSGameLiftMatchmakingRequestBus::Events::AcceptMatch,
|
||||
{ { { "AcceptMatchRequest", "" } } })
|
||||
->Event("StartMatchmaking", &AWSGameLiftMatchmakingRequestBus::Events::StartMatchmaking,
|
||||
{ { { "StartMatchmakingRequest", "" } } })
|
||||
->Event("StopMatchmaking", &AWSGameLiftMatchmakingRequestBus::Events::StopMatchmaking,
|
||||
{ { { "StopMatchmakingRequest", "" } } });
|
||||
|
||||
behaviorContext->EBus<AzFramework::MatchAcceptanceNotificationBus>("AWSGameLiftMatchAcceptanceNotificationBus")
|
||||
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift")
|
||||
->Handler<AWSGameLiftMatchAcceptanceNotificationBusHandler>();
|
||||
behaviorContext->EBus<AWSGameLiftMatchmakingEventRequestBus>("AWSGameLiftMatchmakingEventRequestBus")
|
||||
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking")
|
||||
->Event("StartPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StartPolling,
|
||||
{ { { "TicketId", "" },
|
||||
{ "PlayerId", "" } } })
|
||||
->Event("StopPolling", &AWSGameLiftMatchmakingEventRequestBus::Events::StopPolling);
|
||||
|
||||
behaviorContext->EBus<AzFramework::MatchmakingNotificationBus>("AWSGameLiftMatchmakingNotificationBus")
|
||||
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Matchmaking")
|
||||
->Handler<AWSGameLiftMatchmakingNotificationBusHandler>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +167,7 @@ namespace AWSGameLift
|
||||
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->EBus<AWSGameLiftSessionAsyncRequestBus>("AWSGameLiftSessionAsyncRequestBus")
|
||||
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift")
|
||||
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Session")
|
||||
->Event("CreateSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::CreateSessionAsync,
|
||||
{ { { "CreateSessionRequest", "" } } })
|
||||
->Event("JoinSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::JoinSessionAsync, { { { "JoinSessionRequest", "" } } })
|
||||
@@ -175,11 +176,11 @@ namespace AWSGameLift
|
||||
->Event("LeaveSessionAsync", &AWSGameLiftSessionAsyncRequestBus::Events::LeaveSessionAsync);
|
||||
|
||||
behaviorContext->EBus<AzFramework::SessionAsyncRequestNotificationBus>("AWSGameLiftSessionAsyncRequestNotificationBus")
|
||||
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift")
|
||||
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Session")
|
||||
->Handler<AWSGameLiftSessionAsyncRequestNotificationBusHandler>();
|
||||
|
||||
behaviorContext->EBus<AWSGameLiftSessionRequestBus>("AWSGameLiftSessionRequestBus")
|
||||
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift")
|
||||
->Attribute(AZ::Script::Attributes::Category, "AWSGameLift/Session")
|
||||
->Event("CreateSession", &AWSGameLiftSessionRequestBus::Events::CreateSession, { { { "CreateSessionRequest", "" } } })
|
||||
->Event("JoinSession", &AWSGameLiftSessionRequestBus::Events::JoinSession, { { { "JoinSessionRequest", "" } } })
|
||||
->Event("SearchSessions", &AWSGameLiftSessionRequestBus::Events::SearchSessions, { { { "SearchSessionsRequest", "" } } })
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ namespace Aws
|
||||
|
||||
namespace AWSGameLift
|
||||
{
|
||||
//! IAWSGameLiftRequests
|
||||
//! IAWSGameLiftInternalRequests
|
||||
//! GameLift Gem internal interface which is used to fetch gem global GameLift client
|
||||
class IAWSGameLiftInternalRequests
|
||||
{
|
||||
|
||||
+60
-44
@@ -82,27 +82,12 @@ protected:
|
||||
m_gameliftClientMockPtr.reset();
|
||||
}
|
||||
|
||||
void WaitForProcessFinish(uint64_t expectedNum)
|
||||
void WaitForProcessFinish(AZStd::function<bool()> processFinishCondition)
|
||||
{
|
||||
int processingTime = 0;
|
||||
while (processingTime < TEST_WAIT_MAXIMUM_TIME_MS)
|
||||
{
|
||||
if (::UnitTest::TestRunner::Instance().m_numAssertsFailed == expectedNum)
|
||||
{
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(expectedNum);
|
||||
return;
|
||||
}
|
||||
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(TEST_WAIT_BUFFER_TIME_MS));
|
||||
processingTime += TEST_WAIT_BUFFER_TIME_MS;
|
||||
}
|
||||
}
|
||||
|
||||
void WaitForProcessFinish()
|
||||
{
|
||||
int processingTime = 0;
|
||||
while (processingTime < TEST_WAIT_MAXIMUM_TIME_MS)
|
||||
{
|
||||
if (m_gameliftClientTicketTracker->IsTrackerIdle())
|
||||
if (processFinishCondition())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -119,19 +104,27 @@ public:
|
||||
TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithoutClientSetup_GetExpectedErrors)
|
||||
{
|
||||
AZ::Interface<IAWSGameLiftInternalRequests>::Get()->SetGameLiftClient(nullptr);
|
||||
|
||||
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
|
||||
WaitForProcessFinish(1);
|
||||
WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; });
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1);
|
||||
ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle());
|
||||
}
|
||||
|
||||
TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_MultipleCallsWithoutClientSetup_GetExpectedErrors)
|
||||
{
|
||||
AZ::Interface<IAWSGameLiftInternalRequests>::Get()->SetGameLiftClient(nullptr);
|
||||
|
||||
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
|
||||
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
|
||||
WaitForProcessFinish(1);
|
||||
WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; });
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1);
|
||||
ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle());
|
||||
}
|
||||
|
||||
@@ -144,9 +137,12 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButWithFailedOu
|
||||
.Times(1)
|
||||
.WillOnce(::testing::Return(outcome));
|
||||
|
||||
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
|
||||
WaitForProcessFinish(1);
|
||||
WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; });
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1);
|
||||
ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle());
|
||||
}
|
||||
|
||||
@@ -161,9 +157,12 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithMoreThanOne
|
||||
.Times(1)
|
||||
.WillOnce(::testing::Return(outcome));
|
||||
|
||||
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
|
||||
WaitForProcessFinish(1);
|
||||
WaitForProcessFinish([](){ return ::UnitTest::TestRunner::Instance().m_numAssertsFailed == 1; });
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchError == 1);
|
||||
ASSERT_FALSE(m_gameliftClientTicketTracker->IsTrackerIdle());
|
||||
}
|
||||
|
||||
@@ -189,13 +188,15 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallWithCompleteSta
|
||||
.Times(1)
|
||||
.WillOnce(::testing::Return(outcome));
|
||||
|
||||
SessionHandlingClientRequestsMock handlerMock;
|
||||
EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_))
|
||||
SessionHandlingClientRequestsMock sessionHandlerMock;
|
||||
EXPECT_CALL(sessionHandlerMock, RequestPlayerJoinSession(::testing::_))
|
||||
.Times(1)
|
||||
.WillOnce(::testing::Return(true));
|
||||
|
||||
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
|
||||
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
|
||||
WaitForProcessFinish();
|
||||
WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); });
|
||||
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1);
|
||||
ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle());
|
||||
}
|
||||
|
||||
@@ -217,9 +218,12 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButNoPlayerSess
|
||||
.Times(1)
|
||||
.WillOnce(::testing::Return(outcome));
|
||||
|
||||
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
|
||||
WaitForProcessFinish(1);
|
||||
WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); });
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1);
|
||||
ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle());
|
||||
}
|
||||
|
||||
@@ -245,14 +249,17 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButFailedToJoin
|
||||
.Times(1)
|
||||
.WillOnce(::testing::Return(outcome));
|
||||
|
||||
SessionHandlingClientRequestsMock handlerMock;
|
||||
EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_))
|
||||
SessionHandlingClientRequestsMock sessionHandlerMock;
|
||||
EXPECT_CALL(sessionHandlerMock, RequestPlayerJoinSession(::testing::_))
|
||||
.Times(1)
|
||||
.WillOnce(::testing::Return(false));
|
||||
|
||||
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
|
||||
WaitForProcessFinish(1);
|
||||
WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); });
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
|
||||
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1);
|
||||
ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle());
|
||||
}
|
||||
|
||||
@@ -269,9 +276,10 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButTicketTimeOu
|
||||
.Times(1)
|
||||
.WillOnce(::testing::Return(outcome));
|
||||
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
|
||||
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
|
||||
WaitForProcessFinish(1);
|
||||
WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); });
|
||||
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchFailure == 1);
|
||||
ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle());
|
||||
}
|
||||
|
||||
@@ -288,9 +296,10 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButTicketFailed
|
||||
.Times(1)
|
||||
.WillOnce(::testing::Return(outcome));
|
||||
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
|
||||
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
|
||||
WaitForProcessFinish(1);
|
||||
WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); });
|
||||
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchFailure == 1);
|
||||
ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle());
|
||||
}
|
||||
|
||||
@@ -307,9 +316,10 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallButTicketCancel
|
||||
.Times(1)
|
||||
.WillOnce(::testing::Return(outcome));
|
||||
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
|
||||
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
|
||||
WaitForProcessFinish(1);
|
||||
WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); });
|
||||
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchFailure == 1);
|
||||
ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle());
|
||||
}
|
||||
|
||||
@@ -342,13 +352,15 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallAndTicketComple
|
||||
.WillOnce(::testing::Return(outcome1))
|
||||
.WillOnce(::testing::Return(outcome2));
|
||||
|
||||
SessionHandlingClientRequestsMock handlerMock;
|
||||
EXPECT_CALL(handlerMock, RequestPlayerJoinSession(::testing::_))
|
||||
SessionHandlingClientRequestsMock sessionHandlerMock;
|
||||
EXPECT_CALL(sessionHandlerMock, RequestPlayerJoinSession(::testing::_))
|
||||
.Times(1)
|
||||
.WillOnce(::testing::Return(true));
|
||||
|
||||
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
|
||||
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
|
||||
WaitForProcessFinish();
|
||||
WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); });
|
||||
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1);
|
||||
ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle());
|
||||
}
|
||||
|
||||
@@ -365,7 +377,9 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_RequiresAcceptanceA
|
||||
connectionInfo.SetIpAddress("DummyIpAddress");
|
||||
connectionInfo.SetPort(123);
|
||||
connectionInfo.AddMatchedPlayerSessions(
|
||||
Aws::GameLift::Model::MatchedPlayerSession().WithPlayerId("player1").WithPlayerSessionId("playersession1"));
|
||||
Aws::GameLift::Model::MatchedPlayerSession()
|
||||
.WithPlayerId("player1")
|
||||
.WithPlayerSessionId("playersession1"));
|
||||
|
||||
Aws::GameLift::Model::MatchmakingTicket ticket2;
|
||||
ticket2.SetStatus(Aws::GameLift::Model::MatchmakingConfigurationStatus::COMPLETED);
|
||||
@@ -379,13 +393,15 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_RequiresAcceptanceA
|
||||
.WillOnce(::testing::Return(outcome1))
|
||||
.WillOnce(::testing::Return(outcome2));
|
||||
|
||||
MatchAcceptanceNotificationsHandlerMock handlerMock1;
|
||||
EXPECT_CALL(handlerMock1, OnMatchAcceptance()).Times(1);
|
||||
|
||||
SessionHandlingClientRequestsMock handlerMock2;
|
||||
EXPECT_CALL(handlerMock2, RequestPlayerJoinSession(::testing::_)).Times(1).WillOnce(::testing::Return(true));
|
||||
SessionHandlingClientRequestsMock sessionHandlerMock;
|
||||
EXPECT_CALL(sessionHandlerMock, RequestPlayerJoinSession(::testing::_))
|
||||
.Times(1)
|
||||
.WillOnce(::testing::Return(true));
|
||||
|
||||
MatchmakingNotificationsHandlerMock matchmakingHandlerMock;
|
||||
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
|
||||
WaitForProcessFinish();
|
||||
WaitForProcessFinish([this](){ return m_gameliftClientTicketTracker->IsTrackerIdle(); });
|
||||
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchAcceptance == 1);
|
||||
ASSERT_TRUE(matchmakingHandlerMock.m_numMatchComplete == 1);
|
||||
ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle());
|
||||
}
|
||||
|
||||
@@ -266,6 +266,8 @@ const char* const AWSGameLiftClientManagerTest::DummyPlayerId = "dummyPlayerId";
|
||||
|
||||
TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutRegion_GetFalseAsResult)
|
||||
{
|
||||
AWSCoreRequestsHandlerMock coreHandlerMock;
|
||||
EXPECT_CALL(coreHandlerMock, GetDefaultConfig()).Times(1).WillOnce(nullptr);
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
auto result = m_gameliftClientManager->ConfigureGameLiftClient("");
|
||||
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message
|
||||
@@ -274,6 +276,8 @@ TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutRegion_G
|
||||
|
||||
TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutCredential_GetFalseAsResult)
|
||||
{
|
||||
AWSCoreRequestsHandlerMock coreHandlerMock;
|
||||
EXPECT_CALL(coreHandlerMock, GetDefaultConfig()).Times(1).WillOnce(nullptr);
|
||||
AWSResourceMappingRequestsHandlerMock handlerMock;
|
||||
EXPECT_CALL(handlerMock, GetDefaultRegion()).Times(1).WillOnce(::testing::Return("us-west-2"));
|
||||
AZ_TEST_START_TRACE_SUPPRESSION;
|
||||
@@ -284,6 +288,8 @@ TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithoutCredenti
|
||||
|
||||
TEST_F(AWSGameLiftClientManagerTest, ConfigureGameLiftClient_CallWithRegionAndCredential_GetTrueAsResult)
|
||||
{
|
||||
AWSCoreRequestsHandlerMock coreHandlerMock;
|
||||
EXPECT_CALL(coreHandlerMock, GetDefaultConfig()).Times(1).WillOnce(nullptr);
|
||||
AWSCredentialRequestsHandlerMock handlerMock;
|
||||
EXPECT_CALL(handlerMock, GetCredentialsProvider())
|
||||
.Times(1)
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzFramework/Matchmaking/MatchmakingNotifications.h>
|
||||
#include <AzFramework/Session/ISessionRequests.h>
|
||||
#include <AzFramework/Session/ISessionHandlingRequests.h>
|
||||
#include <AzFramework/Matchmaking/IMatchmakingRequests.h>
|
||||
#include <AzFramework/Matchmaking/MatchmakingNotifications.h>
|
||||
#include <AzTest/AzTest.h>
|
||||
|
||||
@@ -76,21 +76,44 @@ public:
|
||||
MOCK_METHOD0(OnStopMatchmakingAsyncComplete, void());
|
||||
};
|
||||
|
||||
class MatchAcceptanceNotificationsHandlerMock
|
||||
: public AzFramework::MatchAcceptanceNotificationBus::Handler
|
||||
class MatchmakingNotificationsHandlerMock
|
||||
: public AzFramework::MatchmakingNotificationBus::Handler
|
||||
{
|
||||
public:
|
||||
MatchAcceptanceNotificationsHandlerMock()
|
||||
MatchmakingNotificationsHandlerMock()
|
||||
{
|
||||
AzFramework::MatchAcceptanceNotificationBus::Handler::BusConnect();
|
||||
AzFramework::MatchmakingNotificationBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
~MatchAcceptanceNotificationsHandlerMock()
|
||||
~MatchmakingNotificationsHandlerMock()
|
||||
{
|
||||
AzFramework::MatchAcceptanceNotificationBus::Handler::BusDisconnect();
|
||||
AzFramework::MatchmakingNotificationBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
MOCK_METHOD0(OnMatchAcceptance, void());
|
||||
void OnMatchAcceptance() override
|
||||
{
|
||||
++m_numMatchAcceptance;
|
||||
}
|
||||
|
||||
void OnMatchComplete() override
|
||||
{
|
||||
++m_numMatchComplete;
|
||||
}
|
||||
|
||||
void OnMatchError() override
|
||||
{
|
||||
++m_numMatchError;
|
||||
}
|
||||
|
||||
void OnMatchFailure() override
|
||||
{
|
||||
++m_numMatchFailure;
|
||||
}
|
||||
|
||||
int m_numMatchAcceptance = 0;
|
||||
int m_numMatchComplete = 0;
|
||||
int m_numMatchError = 0;
|
||||
int m_numMatchFailure = 0;
|
||||
};
|
||||
|
||||
class SessionAsyncRequestNotificationsHandlerMock
|
||||
|
||||
@@ -17,7 +17,9 @@ set(FILES
|
||||
Include/Request/AWSGameLiftSearchSessionsRequest.h
|
||||
Include/Request/AWSGameLiftStartMatchmakingRequest.h
|
||||
Include/Request/AWSGameLiftStopMatchmakingRequest.h
|
||||
Include/Request/IAWSGameLiftRequests.h
|
||||
Include/Request/AWSGameLiftRequestBus.h
|
||||
Include/Request/AWSGameLiftSessionRequestBus.h
|
||||
Include/Request/AWSGameLiftMatchmakingRequestBus.h
|
||||
Source/Activity/AWSGameLiftActivityUtils.cpp
|
||||
Source/Activity/AWSGameLiftActivityUtils.h
|
||||
Source/Activity/AWSGameLiftAcceptMatchActivity.cpp
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
@@ -45,7 +45,7 @@ namespace AWSGameLift
|
||||
virtual bool StopMatchBackfill(const AZStd::string& ticketId) = 0;
|
||||
};
|
||||
|
||||
// IAWSGameLiftServerRequests EBus wrapper for scripting
|
||||
// IAWSGameLiftServerRequests EBus wrapper
|
||||
class AWSGameLiftServerRequests
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
@@ -20,7 +20,7 @@
|
||||
#include <AzFramework/Session/SessionConfig.h>
|
||||
|
||||
#include <AWSGameLiftPlayer.h>
|
||||
#include <Request/IAWSGameLiftServerRequests.h>
|
||||
#include <Request/AWSGameLiftServerRequestBus.h>
|
||||
|
||||
namespace AWSGameLift
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ set(FILES
|
||||
../AWSGameLiftCommon/Include/AWSGameLiftPlayer.h
|
||||
../AWSGameLiftCommon/Source/AWSGameLiftPlayer.cpp
|
||||
../AWSGameLiftCommon/Source/AWSGameLiftSessionConstants.h
|
||||
Include/Request/IAWSGameLiftServerRequests.h
|
||||
Include/Request/AWSGameLiftServerRequestBus.h
|
||||
Source/AWSGameLiftServerManager.cpp
|
||||
Source/AWSGameLiftServerManager.h
|
||||
Source/AWSGameLiftServerSystemComponent.cpp
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace AWSMetrics
|
||||
static const unsigned int DesiredMaxWorkers = 2;
|
||||
|
||||
MetricsManager();
|
||||
~MetricsManager();
|
||||
virtual ~MetricsManager();
|
||||
|
||||
//! Initializing the metrics manager
|
||||
//! @return Whether the operation is successful.
|
||||
@@ -93,6 +93,12 @@ namespace AWSMetrics
|
||||
//! @return Total number of requests for sending metrics events.
|
||||
int GetNumTotalRequests() const;
|
||||
|
||||
protected:
|
||||
//! Send metrics to a local file.
|
||||
//! @param metricsQueue metricsQueue Metrics queue that stores the metrics.
|
||||
//! @return Outcome of the operation.
|
||||
virtual AZ::Outcome<void, AZStd::string> SendMetricsToFile(AZStd::shared_ptr<MetricsQueue> metricsQueue);
|
||||
|
||||
private:
|
||||
//! Job management
|
||||
void SetupJobContext();
|
||||
@@ -112,11 +118,6 @@ namespace AWSMetrics
|
||||
//! @param metricsQueue Metrics events to send.
|
||||
void SendMetricsToServiceApiAsync(const MetricsQueue& metricsQueue);
|
||||
|
||||
//! Send metrics to a local file.
|
||||
//! @param metricsQueue metricsQueue Metrics queue that stores the metrics.
|
||||
//! @return Outcome of the operation.
|
||||
AZ::Outcome<void, AZStd::string> SendMetricsToFile(AZStd::shared_ptr<MetricsQueue> metricsQueue);
|
||||
|
||||
//! Push metrics events to the front of the queue for retry.
|
||||
//! @param metricsEventsForRetry Metrics events for retry.
|
||||
void PushMetricsForRetry(MetricsQueue& metricsEventsForRetry);
|
||||
|
||||
@@ -139,11 +139,6 @@ namespace AWSMetrics
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RemoveDirectory(const AZStd::string& directory)
|
||||
{
|
||||
return AZ::IO::SystemFile::DeleteDir(directory.c_str());
|
||||
}
|
||||
|
||||
AZ::IO::FileIOBase* m_priorFileIO = nullptr;
|
||||
AZ::IO::FileIOBase* m_localFileIO = nullptr;
|
||||
|
||||
|
||||
@@ -75,6 +75,23 @@ namespace AZ
|
||||
|
||||
namespace AWSMetrics
|
||||
{
|
||||
class MetricsManagerMock
|
||||
: public MetricsManager
|
||||
{
|
||||
private:
|
||||
AZ::Outcome<void, AZStd::string> SendMetricsToFile(AZStd::shared_ptr<MetricsQueue> metricsQueue) override
|
||||
{
|
||||
if (AZ::IO::FileIOBase::GetInstance())
|
||||
{
|
||||
return AZ::Success();
|
||||
}
|
||||
else
|
||||
{
|
||||
return AZ::Failure(AZStd::string{ "Invalid File IO" });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class AWSMetricsNotificationBusMock
|
||||
: protected AWSMetricsNotificationBus::Handler
|
||||
{
|
||||
@@ -134,13 +151,11 @@ namespace AWSMetrics
|
||||
AWSMetricsGemAllocatorFixture::SetUp();
|
||||
AWSMetricsRequestBus::Handler::BusConnect();
|
||||
|
||||
m_metricsManager = AZStd::make_unique<MetricsManager>();
|
||||
m_metricsManager = AZStd::make_unique<MetricsManagerMock>();
|
||||
AZStd::string configFilePath = CreateClientConfigFile(true, (double) TestMetricsEventSizeInBytes / MbToBytes * 2, DefaultFlushPeriodInSeconds, 0);
|
||||
m_settingsRegistry->MergeSettingsFile(configFilePath, AZ::SettingsRegistryInterface::Format::JsonMergePatch, {});
|
||||
m_metricsManager->Init();
|
||||
|
||||
RemoveFile(m_metricsManager->GetMetricsFilePath());
|
||||
|
||||
ReplaceLocalFileIOWithMockIO();
|
||||
}
|
||||
|
||||
@@ -149,8 +164,6 @@ namespace AWSMetrics
|
||||
RevertMockIOToLocalFileIO();
|
||||
|
||||
RemoveFile(GetDefaultTestFilePath());
|
||||
RemoveFile(m_metricsManager->GetMetricsFilePath());
|
||||
RemoveDirectory(m_metricsManager->GetMetricsFileDirectory());
|
||||
|
||||
m_metricsManager.reset();
|
||||
|
||||
@@ -233,7 +246,7 @@ namespace AWSMetrics
|
||||
}
|
||||
}
|
||||
|
||||
AZStd::unique_ptr<MetricsManager> m_metricsManager;
|
||||
AZStd::unique_ptr<MetricsManagerMock> m_metricsManager;
|
||||
AWSMetricsNotificationBusMock m_notifications;
|
||||
|
||||
AZ::IO::FileIOBase* m_fileIOMock;
|
||||
|
||||
+2
-3
@@ -138,7 +138,7 @@ float3 ColorGrade(float3 frameColor)
|
||||
PassSrg::m_colorFilterMultiply, PassSrg::m_colorFilterIntensity), PassSrg::m_colorAdjustmentWeight);
|
||||
frameColor = max(frameColor, 0.0);
|
||||
frameColor = lerp(frameColor, ColorGradeSaturation(frameColor, PassSrg::m_colorGradingPreSaturation), PassSrg::m_colorAdjustmentWeight);
|
||||
|
||||
frameColor = max(frameColor, 0.0);
|
||||
frameColor = ColorGradeSplitTone(frameColor, PassSrg::m_splitToneBalance, PassSrg::m_splitToneWeight,
|
||||
PassSrg::m_splitToneShadowsColor, PassSrg::m_splitToneHighlightsColor);
|
||||
frameColor = ColorGradeChannelMixer(frameColor, PassSrg::m_channelMixingRed, PassSrg::m_channelMixingGreen, PassSrg::m_channelMixingBlue);
|
||||
@@ -147,8 +147,7 @@ float3 ColorGrade(float3 frameColor)
|
||||
PassSrg::m_smhHighlightsStart, PassSrg::m_smhHighlightsEnd, PassSrg::m_smhWeight,
|
||||
PassSrg::m_smhShadowsColor, PassSrg::m_smhMidtonesColor, PassSrg::m_smhHighlightsColor);
|
||||
|
||||
|
||||
frameColor = lerp(frameColor, ColorGradeSaturation(frameColor, PassSrg::m_colorGradingPostSaturation), PassSrg::m_finalAdjustmentWeight);
|
||||
frameColor = lerp(frameColor, ColorGradeHueShift(frameColor, PassSrg::m_colorGradingHueShift), PassSrg::m_finalAdjustmentWeight);
|
||||
frameColor = lerp(frameColor, ColorGradeSaturation(frameColor, PassSrg::m_colorGradingPostSaturation), PassSrg::m_finalAdjustmentWeight);
|
||||
return max(frameColor.rgb, 0.0);
|
||||
}
|
||||
|
||||
+9
-7
@@ -111,16 +111,18 @@ void DirectionalLightShadow::GetShadowCoords(
|
||||
float3 worldPosition,
|
||||
out float3 shadowCoords[ViewSrg::MaxCascadeCount])
|
||||
{
|
||||
const float4x4 depthBiasMatrices[ViewSrg::MaxCascadeCount] =
|
||||
ViewSrg::m_directionalLightShadows[lightIndex].m_depthBiasMatrices;
|
||||
const uint cascadeCount = ViewSrg::m_directionalLightShadows[lightIndex].m_cascadeCount;
|
||||
|
||||
const float shadowBias = ViewSrg::m_directionalLightShadows[lightIndex].m_shadowBias;
|
||||
const float4x4 lightViewToShadowmapMatrices[ViewSrg::MaxCascadeCount] = ViewSrg::m_directionalLightShadows[lightIndex].m_lightViewToShadowmapMatrices;
|
||||
const float4x4 worldToLightViewMatrices[ViewSrg::MaxCascadeCount] = ViewSrg::m_directionalLightShadows[lightIndex].m_worldToLightViewMatrices;
|
||||
|
||||
for (uint index = 0; index < cascadeCount; ++index)
|
||||
{
|
||||
const float4x4 depthBiasMatrix = depthBiasMatrices[index];
|
||||
const float4 shadowCoordHomogeneous = mul(depthBiasMatrix,
|
||||
float4(worldPosition, 1.));
|
||||
shadowCoords[index] = shadowCoordHomogeneous.xyz / shadowCoordHomogeneous.w;
|
||||
float4 lightSpacePos = mul(worldToLightViewMatrices[index], float4(worldPosition, 1.));
|
||||
lightSpacePos.z += shadowBias;
|
||||
|
||||
const float4 clipSpacePos = mul(lightViewToShadowmapMatrices[index], lightSpacePos);
|
||||
shadowCoords[index] = clipSpacePos.xyz / clipSpacePos.w;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,18 +102,19 @@ partial ShaderResourceGroup ViewSrg
|
||||
|
||||
struct DirectionalLightShadow
|
||||
{
|
||||
float4x4 m_depthBiasMatrices[MaxCascadeCount];
|
||||
float4x4 m_lightViewToShadowmapMatrices[MaxCascadeCount];
|
||||
float4x4 m_worldToLightViewMatrices[MaxCascadeCount];
|
||||
float m_slopeBiasBase[MaxCascadeCount];
|
||||
float m_boundaryScale;
|
||||
uint m_shadowmapSize; // width and height of shadowmap
|
||||
uint m_cascadeCount;
|
||||
float m_shadowBias;
|
||||
uint m_predictionSampleCount;
|
||||
uint m_filteringSampleCount;
|
||||
uint m_debugFlags;
|
||||
uint m_shadowFilterMethod;
|
||||
float m_far_minus_near;
|
||||
float3 m_padding;
|
||||
};
|
||||
|
||||
enum ShadowFilterMethod
|
||||
|
||||
+3
@@ -157,6 +157,9 @@ namespace AZ
|
||||
//! Sets whether the directional shadowmap should use receiver plane bias.
|
||||
//! This attempts to reduce shadow acne when using large pcf filters.
|
||||
virtual void SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable) = 0;
|
||||
|
||||
//! Reduces acne by applying a small amount of bias along shadow-space z.
|
||||
virtual void SetShadowBias(LightHandle handle, float bias) = 0;
|
||||
};
|
||||
} // namespace Render
|
||||
} // namespace AZ
|
||||
|
||||
+9
-4
@@ -589,6 +589,15 @@ namespace AZ
|
||||
m_shadowProperties.GetData(handle.GetIndex()).m_isReceiverPlaneBiasEnabled = enable;
|
||||
}
|
||||
|
||||
void DirectionalLightFeatureProcessor::SetShadowBias(LightHandle handle, float bias)
|
||||
{
|
||||
for (auto& it : m_shadowData)
|
||||
{
|
||||
it.second.GetData(handle.GetIndex()).m_shadowBias = bias;
|
||||
}
|
||||
m_shadowBufferNeedsUpdate = true;
|
||||
}
|
||||
|
||||
void DirectionalLightFeatureProcessor::OnRenderPipelineAdded(RPI::RenderPipelinePtr pipeline)
|
||||
{
|
||||
PrepareForChangingRenderPipelineAndCameraView();
|
||||
@@ -1522,10 +1531,6 @@ namespace AZ
|
||||
|
||||
for (uint16_t cascadeIndex = 0; cascadeIndex < GetCascadeCount(handle); ++cascadeIndex)
|
||||
{
|
||||
const Matrix4x4& worldToLightClipMatrix = property.m_segments.at(cameraView)[cascadeIndex].m_view->GetWorldToClipMatrix();
|
||||
const Matrix4x4 depthBiasMatrix = Shadow::GetClipToShadowmapTextureMatrix() * worldToLightClipMatrix;
|
||||
shadowData.m_depthBiasMatrices[cascadeIndex] = depthBiasMatrix;
|
||||
|
||||
const Matrix4x4& lightViewToLightClipMatrix = property.m_segments.at(cameraView)[cascadeIndex].m_view->GetViewToClipMatrix();
|
||||
const Matrix4x4 lightViewToShadowmapMatrix = Shadow::GetClipToShadowmapTextureMatrix() * lightViewToLightClipMatrix;
|
||||
shadowData.m_lightViewToShadowmapMatrices[cascadeIndex] = lightViewToShadowmapMatrix;
|
||||
|
||||
@@ -72,12 +72,6 @@ namespace AZ
|
||||
// [GFX TODO][ATOM-15172] Look into compacting struct DirectionalLightShadowData
|
||||
struct DirectionalLightShadowData
|
||||
{
|
||||
AZStd::array<Matrix4x4, Shadow::MaxNumberOfCascades> m_depthBiasMatrices =
|
||||
{ {
|
||||
Matrix4x4::CreateIdentity(),
|
||||
Matrix4x4::CreateIdentity(),
|
||||
Matrix4x4::CreateIdentity(),
|
||||
Matrix4x4::CreateIdentity() } };
|
||||
AZStd::array<Matrix4x4, Shadow::MaxNumberOfCascades> m_lightViewToShadowmapMatrices =
|
||||
{ {
|
||||
Matrix4x4::CreateIdentity(),
|
||||
@@ -97,11 +91,14 @@ namespace AZ
|
||||
float m_boundaryScale = 0.f;
|
||||
uint32_t m_shadowmapSize = 1; // width and height of shadowmap
|
||||
uint32_t m_cascadeCount = 1;
|
||||
// Reduce acne by applying a small amount of bias to apply along shadow-space z.
|
||||
float m_shadowBias = 0.0f;
|
||||
uint32_t m_predictionSampleCount = 0;
|
||||
uint32_t m_filteringSampleCount = 0;
|
||||
uint32_t m_debugFlags = 0;
|
||||
uint32_t m_shadowFilterMethod = 0;
|
||||
float m_far_minus_near = 0;
|
||||
float m_padding[3];
|
||||
};
|
||||
|
||||
class DirectionalLightFeatureProcessor final
|
||||
@@ -218,6 +215,7 @@ namespace AZ
|
||||
void SetShadowFilterMethod(LightHandle handle, ShadowFilterMethod method) override;
|
||||
void SetFilteringSampleCount(LightHandle handle, uint16_t count) override;
|
||||
void SetShadowReceiverPlaneBiasEnabled(LightHandle handle, bool enable) override;
|
||||
void SetShadowBias(LightHandle handle, float bias) override;
|
||||
|
||||
const Data::Instance<RPI::Buffer> GetLightBuffer() const;
|
||||
uint32_t GetLightCount() const;
|
||||
|
||||
@@ -10,6 +10,8 @@ if(NOT PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
return()
|
||||
endif()
|
||||
|
||||
ly_get_list_relative_pal_filename(pal_source_dir ${CMAKE_CURRENT_LIST_DIR}/Source/Platform/${PAL_PLATFORM_NAME})
|
||||
|
||||
ly_add_target(
|
||||
NAME AtomToolsFramework.Static STATIC
|
||||
NAMESPACE Gem
|
||||
@@ -18,9 +20,11 @@ ly_add_target(
|
||||
AUTORCC
|
||||
FILES_CMAKE
|
||||
atomtoolsframework_files.cmake
|
||||
${pal_source_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PRIVATE
|
||||
Source
|
||||
${pal_source_dir}
|
||||
PUBLIC
|
||||
Include
|
||||
BUILD_DEPENDENCIES
|
||||
|
||||
@@ -216,7 +216,11 @@ namespace AtomToolsFramework
|
||||
AtomToolsMainWindowNotificationBus::Handler::BusDisconnect();
|
||||
AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::StartDisconnectingAssetProcessor);
|
||||
|
||||
#if AZ_TRAIT_ATOMTOOLSFRAMEWORK_SKIP_APP_DESTROY
|
||||
_exit(0);
|
||||
#else
|
||||
Base::Destroy();
|
||||
#endif
|
||||
}
|
||||
|
||||
AZStd::vector<AZStd::string> AtomToolsApplication::GetCriticalAssetFilters() const
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// On some platforms, there is an issue with environment variables that are removed before some objects are deallocated (during the process of
|
||||
// ComponentApplication::Destroy). Until all of the shutdown issues are solved, the following trait will skip the parent ::Destroy() and exit
|
||||
// the application as soon as possible if set to true.
|
||||
// (Tracked by GHI - 4806)
|
||||
#define AZ_TRAIT_ATOMTOOLSFRAMEWORK_SKIP_APP_DESTROY true
|
||||
|
||||
+2
-3
@@ -5,7 +5,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Module/Module.h>
|
||||
|
||||
AZ_DECLARE_MODULE_CLASS(Gem_AudioEngineWwise, AZ::Module)
|
||||
#include <AtomToolsFramework_Traits_Linux.h>
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
#
|
||||
# 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
|
||||
AtomToolsFramework_Traits_Platform.h
|
||||
AtomToolsFramework_Traits_Linux.h
|
||||
)
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// On some platforms, there is an issue with environment variables that are removed before some objects are deallocated (during the process of
|
||||
// ComponentApplication::Destroy). Until all of the shutdown issues are solved, the following trait will skip the parent ::Destroy() and exit
|
||||
// the application as soon as possible if set to true.
|
||||
// (Tracked by GHI - 4806)
|
||||
#define AZ_TRAIT_ATOMTOOLSFRAMEWORK_SKIP_APP_DESTROY false
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* 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 <AtomToolsFramework_Traits_Mac.h>
|
||||
+2
-1
@@ -7,5 +7,6 @@
|
||||
#
|
||||
|
||||
set(FILES
|
||||
Source/AudioEngineWwiseModule_Stub.cpp
|
||||
AtomToolsFramework_Traits_Platform.h
|
||||
AtomToolsFramework_Traits_Mac.h
|
||||
)
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* 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 <AtomToolsFramework_Traits_Windows.h>
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
// On some platforms, there is an issue with environment variables that are removed before some objects are deallocated (during the process of
|
||||
// ComponentApplication::Destroy). Until all of the shutdown issues are solved, the following trait will skip the parent ::Destroy() and exit
|
||||
// the application as soon as possible if set to true.
|
||||
// (Tracked by GHI - 4806)
|
||||
#define AZ_TRAIT_ATOMTOOLSFRAMEWORK_SKIP_APP_DESTROY false
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
#
|
||||
# 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
|
||||
AtomToolsFramework_Traits_Platform.h
|
||||
AtomToolsFramework_Traits_Windows.h
|
||||
)
|
||||
+8
@@ -168,6 +168,14 @@ namespace AZ
|
||||
//! Sets whether the directional shadowmap should use receiver plane bias.
|
||||
//! @param enable flag specifying whether to enable the receiver plane bias feature
|
||||
virtual void SetShadowReceiverPlaneBiasEnabled(bool enable) = 0;
|
||||
|
||||
//! Shadow bias reduces acne by applying a small amount of offset along shadow-space z.
|
||||
//! @return Returns the amount of bias to apply.
|
||||
virtual float GetShadowBias() const = 0;
|
||||
|
||||
//! Shadow bias reduces acne by applying a small amount of offset along shadow-space z.
|
||||
//! @param Sets the amount of bias to apply.
|
||||
virtual void SetShadowBias(float bias) = 0;
|
||||
};
|
||||
using DirectionalLightRequestBus = EBus<DirectionalLightRequests>;
|
||||
|
||||
|
||||
+3
@@ -109,6 +109,9 @@ namespace AZ
|
||||
//! This uses partial derivatives to reduce shadow acne when using large pcf kernels.
|
||||
bool m_receiverPlaneBiasEnabled = true;
|
||||
|
||||
//! Reduces shadow acne by applying a small amount of offset along shadow-space z.
|
||||
float m_shadowBias = 0.0f;
|
||||
|
||||
bool IsSplitManual() const;
|
||||
bool IsSplitAutomatic() const;
|
||||
bool IsCascadeCorrectionDisabled() const;
|
||||
|
||||
+2
-1
@@ -38,7 +38,8 @@ namespace AZ
|
||||
->Field("IsDebugColoringEnabled", &DirectionalLightComponentConfig::m_isDebugColoringEnabled)
|
||||
->Field("ShadowFilterMethod", &DirectionalLightComponentConfig::m_shadowFilterMethod)
|
||||
->Field("PcfFilteringSampleCount", &DirectionalLightComponentConfig::m_filteringSampleCount)
|
||||
->Field("ShadowReceiverPlaneBiasEnabled", &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled);
|
||||
->Field("ShadowReceiverPlaneBiasEnabled", &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled)
|
||||
->Field("Shadow Bias", &DirectionalLightComponentConfig::m_shadowBias);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-1
@@ -84,6 +84,8 @@ namespace AZ
|
||||
->Event("SetFilteringSampleCount", &DirectionalLightRequestBus::Events::SetFilteringSampleCount)
|
||||
->Event("GetShadowReceiverPlaneBiasEnabled", &DirectionalLightRequestBus::Events::GetShadowReceiverPlaneBiasEnabled)
|
||||
->Event("SetShadowReceiverPlaneBiasEnabled", &DirectionalLightRequestBus::Events::SetShadowReceiverPlaneBiasEnabled)
|
||||
->Event("GetShadowBias", &DirectionalLightRequestBus::Events::GetShadowBias)
|
||||
->Event("SetShadowBias", &DirectionalLightRequestBus::Events::SetShadowBias)
|
||||
->VirtualProperty("Color", "GetColor", "SetColor")
|
||||
->VirtualProperty("Intensity", "GetIntensity", "SetIntensity")
|
||||
->VirtualProperty("AngularDiameter", "GetAngularDiameter", "SetAngularDiameter")
|
||||
@@ -98,7 +100,8 @@ namespace AZ
|
||||
->VirtualProperty("DebugColoringEnabled", "GetDebugColoringEnabled", "SetDebugColoringEnabled")
|
||||
->VirtualProperty("ShadowFilterMethod", "GetShadowFilterMethod", "SetShadowFilterMethod")
|
||||
->VirtualProperty("FilteringSampleCount", "GetFilteringSampleCount", "SetFilteringSampleCount")
|
||||
->VirtualProperty("ShadowReceiverPlaneBiasEnabled", "GetShadowReceiverPlaneBiasEnabled", "SetShadowReceiverPlaneBiasEnabled");
|
||||
->VirtualProperty("ShadowReceiverPlaneBiasEnabled", "GetShadowReceiverPlaneBiasEnabled", "SetShadowReceiverPlaneBiasEnabled")
|
||||
->VirtualProperty("ShadowBias", "GetShadowBias", "SetShadowBias");
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -406,6 +409,20 @@ namespace AZ
|
||||
return aznumeric_cast<uint32_t>(m_configuration.m_filteringSampleCount);
|
||||
}
|
||||
|
||||
void DirectionalLightComponentController::SetShadowBias(float bias)
|
||||
{
|
||||
m_configuration.m_shadowBias = bias;
|
||||
if (m_featureProcessor)
|
||||
{
|
||||
m_featureProcessor->SetShadowBias(m_lightHandle, bias);
|
||||
}
|
||||
}
|
||||
|
||||
float DirectionalLightComponentController::GetShadowBias() const
|
||||
{
|
||||
return m_configuration.m_shadowBias;
|
||||
}
|
||||
|
||||
void DirectionalLightComponentController::SetFilteringSampleCount(uint32_t count)
|
||||
{
|
||||
const uint16_t count16 = GetMin(Shadow::MaxPcfSamplingCount, aznumeric_cast<uint16_t>(count));
|
||||
@@ -499,6 +516,7 @@ namespace AZ
|
||||
SetViewFrustumCorrectionEnabled(m_configuration.m_isCascadeCorrectionEnabled);
|
||||
SetDebugColoringEnabled(m_configuration.m_isDebugColoringEnabled);
|
||||
SetShadowFilterMethod(m_configuration.m_shadowFilterMethod);
|
||||
SetShadowBias(m_configuration.m_shadowBias);
|
||||
SetFilteringSampleCount(m_configuration.m_filteringSampleCount);
|
||||
SetShadowReceiverPlaneBiasEnabled(m_configuration.m_receiverPlaneBiasEnabled);
|
||||
|
||||
|
||||
+2
@@ -80,6 +80,8 @@ namespace AZ
|
||||
void SetFilteringSampleCount(uint32_t count) override;
|
||||
bool GetShadowReceiverPlaneBiasEnabled() const override;
|
||||
void SetShadowReceiverPlaneBiasEnabled(bool enable) override;
|
||||
float GetShadowBias() const override;
|
||||
void SetShadowBias(float width) override;
|
||||
|
||||
private:
|
||||
friend class EditorDirectionalLightComponent;
|
||||
|
||||
+13
-4
@@ -133,8 +133,8 @@ namespace AZ
|
||||
->EnumAttribute(ShadowFilterMethod::Esm, "ESM")
|
||||
->EnumAttribute(ShadowFilterMethod::EsmPcf, "ESM+PCF")
|
||||
->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
|
||||
->DataElement(Edit::UIHandlers::Slider, &DirectionalLightComponentConfig::m_filteringSampleCount, "Filtering sample count",
|
||||
"This is used only when the pixel is predicted as on the boundary. "
|
||||
->DataElement(Edit::UIHandlers::Slider, &DirectionalLightComponentConfig::m_filteringSampleCount, "Filtering sample count\n",
|
||||
"This is used only when the pixel is predicted as on the boundary.\n"
|
||||
"Specific to PCF and ESM+PCF.")
|
||||
->Attribute(Edit::Attributes::Min, 4)
|
||||
->Attribute(Edit::Attributes::Max, 64)
|
||||
@@ -142,10 +142,19 @@ namespace AZ
|
||||
->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled)
|
||||
->DataElement(
|
||||
Edit::UIHandlers::CheckBox, &DirectionalLightComponentConfig::m_receiverPlaneBiasEnabled,
|
||||
"Shadow Receiver Plane Bias Enable",
|
||||
"Shadow Receiver Plane Bias Enable\n",
|
||||
"This reduces shadow acne when using large pcf kernels.")
|
||||
->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
|
||||
->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled);
|
||||
->Attribute(Edit::Attributes::ReadOnly, &DirectionalLightComponentConfig::IsShadowPcfDisabled)
|
||||
->DataElement(
|
||||
Edit::UIHandlers::Slider, &DirectionalLightComponentConfig::m_shadowBias,
|
||||
"Shadow Bias\n",
|
||||
"Reduces acne by applying a fixed bias along z in shadow-space.\n"
|
||||
"If this is 0, no biasing is applied.")
|
||||
->Attribute(Edit::Attributes::Min, 0.f)
|
||||
->Attribute(Edit::Attributes::Max, 0.2)
|
||||
->Attribute(Edit::Attributes::ChangeNotify, Edit::PropertyRefreshLevels::ValuesOnly)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user