Merge branch 'development' of https://github.com/o3de/o3de into carlitosan/development

monroegm-disable-blank-issue-2
chcurran 4 years ago
commit 580cc7811e

File diff suppressed because it is too large Load Diff

@ -1,5 +1,4 @@
<EngineDependencies versionnumber="1.0.0"> <EngineDependencies versionnumber="1.0.0">
<Dependency path="libs/particles/preloadlibs.txt" optional="true" /> <Dependency path="libs/particles/preloadlibs.txt" optional="true" />
<Dependency path="libs/gameaudio/wwise/*.xml" optional="false" /> <Dependency path="libs/gameaudio/wwise/*.xml" optional="false" />
<Dependency path=":libs/gameaudio/wwise/levels/default_controls.xml" optional="false" />
</EngineDependencies> </EngineDependencies>

@ -226,9 +226,9 @@ class TestMaterialEditor(object):
self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name, cfg_args): self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name, cfg_args):
""" """
Tests each valid RHI option (Null RHI excluded) can be launched with the MaterialEditor. Tests each valid RHI option (Null RHI excluded) can be launched with the MaterialEditor.
Checks for the "Finished loading viewport configurtions." success message post lounch. Checks for the "Finished loading viewport configurations." success message post launch.
""" """
expected_lines = ["Finished loading viewport configurtions."] expected_lines = ["Finished loading viewport configurations."]
unexpected_lines = [ unexpected_lines = [
# "Trace::Assert", # "Trace::Assert",
# "Trace::Error", # "Trace::Error",
@ -241,7 +241,7 @@ class TestMaterialEditor(object):
generic_launcher, generic_launcher,
editor_script="", editor_script="",
run_python="--runpython", run_python="--runpython",
timeout=30, timeout=60,
expected_lines=expected_lines, expected_lines=expected_lines,
unexpected_lines=unexpected_lines, unexpected_lines=unexpected_lines,
halt_on_unexpected=False, halt_on_unexpected=False,

@ -96,19 +96,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_
## GradientSignal ## ## GradientSignal ##
ly_add_pytest(
NAME AutomatedTesting::GradientSignalTests_Periodic
TEST_SERIAL
TEST_SUITE periodic
PATH ${CMAKE_CURRENT_LIST_DIR}/gradient_signal/TestSuite_Periodic.py
RUNTIME_DEPENDENCIES
AZ::AssetProcessor
Legacy::Editor
AutomatedTesting.Assets
COMPONENT
LargeWorlds
)
ly_add_pytest( ly_add_pytest(
NAME AutomatedTesting::GradientSignalTests_Periodic_Optimized NAME AutomatedTesting::GradientSignalTests_Periodic_Optimized
TEST_SERIAL TEST_SERIAL

@ -10,7 +10,6 @@ import pytest
from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite
@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.")
@pytest.mark.SUITE_periodic @pytest.mark.SUITE_periodic
@pytest.mark.parametrize("launcher_platform", ['windows_editor']) @pytest.mark.parametrize("launcher_platform", ['windows_editor'])
@pytest.mark.parametrize("project", ["AutomatedTesting"]) @pytest.mark.parametrize("project", ["AutomatedTesting"])

@ -25,28 +25,39 @@ import prefab.Prefab_Test_Utils as prefab_test_utils
# This is a helper class which contains some of the useful information about a prefab instance. # This is a helper class which contains some of the useful information about a prefab instance.
class PrefabInstance: class PrefabInstance:
def __init__(self, name: str=None, prefab_file_name: str=None, container_entity: EditorEntity=EntityId()): def __init__(self, prefab_file_name: str=None, container_entity: EditorEntity=EntityId()):
self.name = name
self.prefab_file_name: str = prefab_file_name self.prefab_file_name: str = prefab_file_name
self.container_entity: EditorEntity = container_entity self.container_entity: EditorEntity = container_entity
def __eq__(self, other):
return other and self.container_entity.id == other.container_entity.id
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash(self.container_entity.id)
""" """
See if this instance is valid to be used with other prefab operations. See if this instance is valid to be used with other prefab operations.
:return: Whether the target instance is valid or not. :return: Whether the target instance is valid or not.
""" """
def is_valid() -> bool: def is_valid() -> bool:
return self.container_entity.id.IsValid() and self.name is not None and self.prefab_file_name in Prefab.existing_prefabs return self.container_entity.id.IsValid() and self.prefab_file_name in Prefab.existing_prefabs
""" """
Reparent this instance to target parent entity. 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. 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. :param parent_entity_id: The id of the entity this instance should be a child of in the transform hierarchy next.
""" """
async def ui_reparent_prefab_instance(self, parent_entity_id: EntityId): async def ui_reparent_prefab_instance(self, parent_entity_id: EntityId):
container_entity_name = self.container_entity.get_name() container_entity_id_before_reparent = self.container_entity.id
current_children_entity_ids_having_prefab_name = prefab_test_utils.get_children_ids_by_name(parent_entity_id, container_entity_name)
Report.info(f'current_children_entity_ids_having_prefab_name: {current_children_entity_ids_having_prefab_name}') original_parent = EditorEntity(self.container_entity.get_parent_id())
original_parent_before_reparent_children_ids = set(original_parent.get_children_ids())
new_parent = EditorEntity(parent_entity_id)
new_parent_before_reparent_children_ids = set(new_parent.get_children_ids())
pyside_utils.run_soon(lambda: self.container_entity.set_parent_entity(parent_entity_id)) pyside_utils.run_soon(lambda: self.container_entity.set_parent_entity(parent_entity_id))
pyside_utils.run_soon(lambda: prefab_test_utils.wait_for_propagation()) pyside_utils.run_soon(lambda: prefab_test_utils.wait_for_propagation())
@ -60,18 +71,23 @@ class PrefabInstance:
except pyside_utils.EventLoopTimeoutException: except pyside_utils.EventLoopTimeoutException:
pass pass
updated_children_entity_ids_having_prefab_name = prefab_test_utils.get_children_ids_by_name(parent_entity_id, container_entity_name) original_parent_after_reparent_children_ids = set(original_parent.get_children_ids())
Report.info(f'updated_children_entity_ids_having_prefab_name: {updated_children_entity_ids_having_prefab_name}') assert len(original_parent_after_reparent_children_ids) == len(original_parent_before_reparent_children_ids) - 1, \
new_child_with_reparented_prefab_name_added = len(updated_children_entity_ids_having_prefab_name) == len(current_children_entity_ids_having_prefab_name) + 1 "The children count of the Prefab Instance's original parent should be decreased by 1."
assert new_child_with_reparented_prefab_name_added, "No entity with reparented prefab name become a child of target parent entity" 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())
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."
updated_container_entity_id = set(updated_children_entity_ids_having_prefab_name).difference(current_children_entity_ids_having_prefab_name).pop() container_entity_id_after_reparent = set(new_parent_after_reparent_children_ids).difference(new_parent_before_reparent_children_ids).pop()
updated_container_entity = EditorEntity(updated_container_entity_id) reparented_container_entity = EditorEntity(container_entity_id_after_reparent)
updated_container_entity_parent_id = updated_container_entity.get_parent_id() reparented_container_entity_parent_id = reparented_container_entity.get_parent_id()
has_correct_parent = updated_container_entity_parent_id.ToString() == parent_entity_id.ToString() has_correct_parent = reparented_container_entity_parent_id.ToString() == parent_entity_id.ToString()
assert has_correct_parent, "Prefab reparented is *not* under the expected parent entity" assert has_correct_parent, "Prefab Instance reparented is *not* under the expected parent entity"
self.container_entity = EditorEntity(updated_container_entity_id) self.container_entity = reparented_container_entity
# This is a helper class which contains some of the useful information about a prefab template. # This is a helper class which contains some of the useful information about a prefab template.
class Prefab: class Prefab:
@ -81,7 +97,7 @@ class Prefab:
def __init__(self, file_name: str): def __init__(self, file_name: str):
self.file_name:str = file_name self.file_name:str = file_name
self.file_path: str = prefab_test_utils.get_prefab_file_path(file_name) self.file_path: str = prefab_test_utils.get_prefab_file_path(file_name)
self.instances: dict = {} self.instances: set[PrefabInstance] = set()
""" """
Check if a prefab is ready to be used to generate its instances. Check if a prefab is ready to be used to generate its instances.
@ -122,10 +138,10 @@ class Prefab:
:param entities: The entities that should form the new prefab (along with their descendants). :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 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. :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: An outcome object with an entityId of the new prefab's container entity; on failure, it comes with an error message detailing the cause of the error. :return: Created Prefab object and the very first PrefabInstance object owned by the prefab.
""" """
@classmethod @classmethod
def create_prefab(cls, entities: list[EditorEntity], file_name: str, prefab_instance_name: str=None) -> Prefab: def create_prefab(cls, entities: list[EditorEntity], file_name: str, prefab_instance_name: str=None) -> (Prefab, PrefabInstance):
assert not Prefab.is_prefab_loaded(file_name), f"Can't create Prefab '{file_name}' since the prefab already exists" 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) new_prefab = Prefab(file_name)
@ -133,18 +149,18 @@ class Prefab:
create_prefab_result = prefab.PrefabPublicRequestBus(bus.Broadcast, 'CreatePrefabInMemory', entity_ids, new_prefab.file_path) create_prefab_result = prefab.PrefabPublicRequestBus(bus.Broadcast, 'CreatePrefabInMemory', entity_ids, new_prefab.file_path)
assert create_prefab_result.IsSuccess(), f"Prefab operation 'CreatePrefab' failed. Error: {create_prefab_result.GetError()}" assert create_prefab_result.IsSuccess(), f"Prefab operation 'CreatePrefab' failed. Error: {create_prefab_result.GetError()}"
container_entity = EditorEntity(create_prefab_result.GetValue()) container_entity_id = create_prefab_result.GetValue()
container_entity = EditorEntity(container_entity_id)
if prefab_instance_name: if prefab_instance_name:
container_entity.set_name(prefab_instance_name) container_entity.set_name(prefab_instance_name)
else:
prefab_instance_name = file_name
prefab_test_utils.wait_for_propagation() prefab_test_utils.wait_for_propagation()
container_entity_id = prefab_test_utils.find_entity_by_unique_name(prefab_instance_name)
new_prefab.instances[prefab_instance_name] = PrefabInstance(prefab_instance_name, file_name, EditorEntity(container_entity_id)) new_prefab_instance = PrefabInstance(file_name, EditorEntity(container_entity_id))
new_prefab.instances.add(new_prefab_instance)
Prefab.existing_prefabs[file_name] = new_prefab Prefab.existing_prefabs[file_name] = new_prefab
return new_prefab return new_prefab, new_prefab_instance
""" """
Remove target prefab instances. Remove target prefab instances.
@ -152,22 +168,15 @@ class Prefab:
""" """
@classmethod @classmethod
def remove_prefabs(cls, prefab_instances: list[PrefabInstance]): def remove_prefabs(cls, prefab_instances: list[PrefabInstance]):
instances_to_remove_name_counts = Counter() entity_ids_to_remove = []
instances_removed_expected_name_counts = Counter() entity_id_queue = [prefab_instance.container_entity for prefab_instance in prefab_instances]
while entity_id_queue:
entities_to_remove = [prefab_instance.container_entity for prefab_instance in prefab_instances] entity = entity_id_queue.pop(0)
while entities_to_remove:
entity = entities_to_remove.pop(-1)
entity_name = entity.get_name()
instances_to_remove_name_counts[entity_name] += 1
children_entity_ids = entity.get_children_ids() children_entity_ids = entity.get_children_ids()
for child_entity_id in children_entity_ids: for child_entity_id in children_entity_ids:
entities_to_remove.append(EditorEntity(child_entity_id)) entity_id_queue.append(EditorEntity(child_entity_id))
for entity_name, entity_count in instances_to_remove_name_counts.items(): entity_ids_to_remove.append(entity.id)
entities = prefab_test_utils.find_entities_by_name(entity_name)
instances_removed_expected_name_counts[entity_name] = len(entities) - entity_count
container_entity_ids = [prefab_instance.container_entity.id for prefab_instance in prefab_instances] container_entity_ids = [prefab_instance.container_entity.id for prefab_instance in prefab_instances]
delete_prefab_result = prefab.PrefabPublicRequestBus(bus.Broadcast, 'DeleteEntitiesAndAllDescendantsInInstance', container_entity_ids) delete_prefab_result = prefab.PrefabPublicRequestBus(bus.Broadcast, 'DeleteEntitiesAndAllDescendantsInInstance', container_entity_ids)
@ -175,28 +184,24 @@ class Prefab:
prefab_test_utils.wait_for_propagation() prefab_test_utils.wait_for_propagation()
prefab_entities_deleted = True entity_ids_after_delete = set(prefab_test_utils.get_all_entities())
for entity_name, expected_entity_count in instances_removed_expected_name_counts.items(): for entity_id_removed in entity_ids_to_remove:
actual_entity_count = len(prefab_test_utils.find_entities_by_name(entity_name)) if entity_id_removed in entity_ids_after_delete:
if actual_entity_count is not expected_entity_count: assert prefab_entities_deleted, "Not all entities and descendants in target prefabs are deleted."
prefab_entities_deleted = False
break
assert prefab_entities_deleted, "Not all entities and descendants in target prefabs are deleted."
for instance in prefab_instances: for instance in prefab_instances:
instance_deleted_prefab = Prefab.get_prefab(instance.prefab_file_name) instance_deleted_prefab = Prefab.get_prefab(instance.prefab_file_name)
instance_deleted_prefab.instances.pop(instance.name) instance_deleted_prefab.instances.remove(instance)
instance = PrefabInstance() instance = PrefabInstance()
""" """
Instantiate an instance of this prefab. Instantiate an instance of this prefab.
:param name: A name for newly instantiated prefab instance. The default instance name is the same as this prefab's file name.
:param parent_entity: The entity the prefab should be a child of in the transform hierarchy. :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. :param prefab_position: The position in world space the prefab should be instantiated in.
:return: An outcome object with an entityId of the new prefab's container entity; on failure, it comes with an error message detailing the cause of the error. :return: Instantiated PrefabInstance object owned by this prefab.
""" """
def instantiate(self, name: str=None, parent_entity: EditorEntity=None, prefab_position: Vector3=Vector3()) -> PrefabInstance: def instantiate(self, parent_entity: EditorEntity=None, name: str=None, prefab_position: Vector3=Vector3()) -> PrefabInstance:
parent_entity_id = parent_entity.id if parent_entity is not None else EntityId() parent_entity_id = parent_entity.id if parent_entity is not None else EntityId()
instantiate_prefab_result = prefab.PrefabPublicRequestBus( instantiate_prefab_result = prefab.PrefabPublicRequestBus(
@ -209,13 +214,13 @@ class Prefab:
if name: if name:
container_entity.set_name(name) container_entity.set_name(name)
else:
name = self.file_name
prefab_test_utils.wait_for_propagation() prefab_test_utils.wait_for_propagation()
container_entity_id = prefab_test_utils.find_entity_by_unique_name(name)
self.instances[name] = PrefabInstance(name, self.file_name, EditorEntity(container_entity_id)) new_prefab_instance = PrefabInstance(self.file_name, EditorEntity(container_entity_id))
assert not new_prefab_instance in self.instances, "This prefab instance is already existed before this instantiation."
self.instances.add(new_prefab_instance)
prefab_test_utils.check_entity_at_position(container_entity_id, prefab_position) prefab_test_utils.check_entity_at_position(container_entity_id, prefab_position)
return container_entity_id return new_prefab_instance

@ -22,11 +22,10 @@ def Prefab_BasicWorkflow_CreateAndDeletePrefab():
car_prefab_entities = [car_entity] car_prefab_entities = [car_entity]
# Checks for prefab creation passed or not # Checks for prefab creation passed or not
car_prefab = Prefab.create_prefab( _, car = Prefab.create_prefab(
car_prefab_entities, CAR_PREFAB_FILE_NAME) car_prefab_entities, CAR_PREFAB_FILE_NAME)
# Checks for prefab deletion passed or not # Checks for prefab deletion passed or not
car = car_prefab.instances[CAR_PREFAB_FILE_NAME]
Prefab.remove_prefabs([car]) Prefab.remove_prefabs([car])
if __name__ == "__main__": if __name__ == "__main__":

@ -28,7 +28,7 @@ def Prefab_BasicWorkflow_CreateAndReparentPrefab():
car_prefab_entities = [car_entity] car_prefab_entities = [car_entity]
# Checks for prefab creation passed or not # Checks for prefab creation passed or not
car_prefab = Prefab.create_prefab( _, car = Prefab.create_prefab(
car_prefab_entities, CAR_PREFAB_FILE_NAME) car_prefab_entities, CAR_PREFAB_FILE_NAME)
# Creates another new Entity at the root level # Creates another new Entity at the root level
@ -36,12 +36,10 @@ def Prefab_BasicWorkflow_CreateAndReparentPrefab():
wheel_prefab_entities = [wheel_entity] wheel_prefab_entities = [wheel_entity]
# Checks for wheel prefab creation passed or not # Checks for wheel prefab creation passed or not
wheel_prefab = Prefab.create_prefab( _, wheel = Prefab.create_prefab(
wheel_prefab_entities, WHEEL_PREFAB_FILE_NAME) wheel_prefab_entities, WHEEL_PREFAB_FILE_NAME)
# Checks for prefab reparenting passed or not # Checks for prefab reparenting passed or not
car = car_prefab.instances[CAR_PREFAB_FILE_NAME]
wheel = wheel_prefab.instances[WHEEL_PREFAB_FILE_NAME]
await wheel.ui_reparent_prefab_instance(car.container_entity.id) await wheel.ui_reparent_prefab_instance(car.container_entity.id)
run_test() run_test()

@ -22,11 +22,11 @@ def Prefab_BasicWorkflow_InstantiatePrefab():
# Checks for prefab instantiation passed or not # Checks for prefab instantiation passed or not
test_prefab = Prefab.get_prefab(EXISTING_TEST_PREFAB_FILE_NAME) test_prefab = Prefab.get_prefab(EXISTING_TEST_PREFAB_FILE_NAME)
instantiated_test_container_entity_id = test_prefab.instantiate( test_instance = test_prefab.instantiate(
prefab_position=INSTANTIATED_TEST_PREFAB_POSITION) prefab_position=INSTANTIATED_TEST_PREFAB_POSITION)
prefab_test_utils.check_entity_children_count( prefab_test_utils.check_entity_children_count(
instantiated_test_container_entity_id, test_instance.container_entity.id,
EXPECTED_TEST_PREFAB_CHILDREN_COUNT) EXPECTED_TEST_PREFAB_CHILDREN_COUNT)
if __name__ == "__main__": if __name__ == "__main__":

@ -29,20 +29,8 @@ def find_entities_by_name(entity_name):
searchFilter.names = [entity_name] searchFilter.names = [entity_name]
return entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter) return entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter)
def find_entity_by_unique_name(entity_name): def get_all_entities():
unique_name_entity_found_result = ( return entity.SearchBus(bus.Broadcast, 'SearchEntities', entity.SearchFilter())
"Entity with a unique name found",
"Entity with a unique name *not* found")
entities = find_entities_by_name(entity_name)
unique_name_entity_found = len(entities) == 1
Report.result(unique_name_entity_found_result, unique_name_entity_found)
if unique_name_entity_found:
return entities[0]
else:
Report.info(f"{len(entities)} entities with name '{entity_name}' found")
return EntityId()
def check_entity_at_position(entity_id, expected_entity_position): def check_entity_at_position(entity_id, expected_entity_position):
entity_at_expected_position_result = ( entity_at_expected_position_result = (

@ -122,6 +122,7 @@ class TestAutomation(TestAutomationBase):
from . import Debugger_HappyPath_TargetMultipleEntities as test_module from . import Debugger_HappyPath_TargetMultipleEntities as test_module
self._run_test(request, workspace, editor, test_module) self._run_test(request, workspace, editor, test_module)
@pytest.mark.xfail(reason="Test fails to find expected lines, it needs to be fixed.")
def test_EditMenu_Default_UndoRedo(self, request, workspace, editor, launcher_platform, project): def test_EditMenu_Default_UndoRedo(self, request, workspace, editor, launcher_platform, project):
from . import EditMenu_Default_UndoRedo as test_module from . import EditMenu_Default_UndoRedo as test_module
self._run_test(request, workspace, editor, test_module) self._run_test(request, workspace, editor, test_module)
@ -181,6 +182,7 @@ class TestAutomation(TestAutomationBase):
from . import NodePalette_SearchText_Deletion as test_module from . import NodePalette_SearchText_Deletion as test_module
self._run_test(request, workspace, editor, test_module) self._run_test(request, workspace, editor, test_module)
@pytest.mark.xfail(reason="Test fails to find expected lines, it needs to be fixed.")
def test_VariableManager_UnpinVariableType_Works(self, request, workspace, editor, launcher_platform): def test_VariableManager_UnpinVariableType_Works(self, request, workspace, editor, launcher_platform):
from . import VariableManager_UnpinVariableType_Works as test_module from . import VariableManager_UnpinVariableType_Works as test_module
self._run_test(request, workspace, editor, test_module) self._run_test(request, workspace, editor, test_module)

@ -35,8 +35,6 @@ struct IDisplayViewport
*/ */
virtual float GetDistanceToLine(const Vec3& lineP1, const Vec3& lineP2, const QPoint& point) const = 0; virtual float GetDistanceToLine(const Vec3& lineP1, const Vec3& lineP2, const QPoint& point) const = 0;
virtual CBaseObjectsCache* GetVisibleObjectsCache() = 0;
enum EAxis enum EAxis
{ {
AXIS_NONE, AXIS_NONE,

@ -33,10 +33,6 @@
AZ_CVAR_EXTERNED(bool, ed_visibility_logTiming); AZ_CVAR_EXTERNED(bool, ed_visibility_logTiming);
AZ_CVAR(
bool, ed_visibility_use, true, nullptr, AZ::ConsoleFunctorFlags::Null,
"Enable/disable using the new IVisibilitySystem for Entity visibility determination");
/*! /*!
* Class Description used for object templates. * Class Description used for object templates.
* This description filled from Xml template files. * This description filled from Xml template files.
@ -76,17 +72,6 @@ public:
int GameCreationOrder() override { return superType->GameCreationOrder(); }; int GameCreationOrder() override { return superType->GameCreationOrder(); };
}; };
void CBaseObjectsCache::AddObject(CBaseObject* object)
{
m_objects.push_back(object);
if (object->GetType() == OBJTYPE_AZENTITY)
{
auto componentEntityObject = static_cast<CComponentEntityObject*>(object);
m_entityIds.push_back(componentEntityObject->GetAssociatedEntityId());
}
}
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// CObjectManager implementation. // CObjectManager implementation.
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@ -1267,25 +1252,8 @@ void CObjectManager::Display(DisplayContext& dc)
UpdateVisibilityList(); UpdateVisibilityList();
} }
bool viewIsDirty = dc.settings->IsDisplayHelpers(); // displaying helpers require computing all the bound boxes and things anyway. if (dc.settings->IsDisplayHelpers())
if (!viewIsDirty)
{ {
if (CBaseObjectsCache* cache = dc.view->GetVisibleObjectsCache())
{
// if the current rendering viewport has an out-of-date cache serial number, it needs to be refreshed too.
// views set their cache empty when they indicate they need to force a refresh.
if ((cache->GetObjectCount() == 0) || (cache->GetSerialNumber() != m_visibilitySerialNumber))
{
viewIsDirty = true;
}
}
}
if (viewIsDirty)
{
FindDisplayableObjects(dc, true); // this also actually draws the helpers.
// Also broadcast for anyone else that needs to draw global debug to do so now // Also broadcast for anyone else that needs to draw global debug to do so now
AzFramework::DebugDisplayEventBus::Broadcast(&AzFramework::DebugDisplayEvents::DrawGlobalDebugInfo); AzFramework::DebugDisplayEventBus::Broadcast(&AzFramework::DebugDisplayEvents::DrawGlobalDebugInfo);
} }
@ -1296,94 +1264,14 @@ void CObjectManager::Display(DisplayContext& dc)
} }
} }
void CObjectManager::ForceUpdateVisibleObjectCache(DisplayContext& dc) void CObjectManager::ForceUpdateVisibleObjectCache([[maybe_unused]] DisplayContext& dc)
{ {
FindDisplayableObjects(dc, false); AZ_Assert(false, "CObjectManager::ForceUpdateVisibleObjectCache is legacy/deprecated and should not be used.");
} }
void CObjectManager::FindDisplayableObjects(DisplayContext& dc, [[maybe_unused]] bool bDisplay) void CObjectManager::FindDisplayableObjects([[maybe_unused]] DisplayContext& dc, [[maybe_unused]] bool bDisplay)
{ {
// if the new IVisibilitySystem is being used, do not run this logic AZ_Assert(false, "CObjectManager::FindDisplayableObjects is legacy/deprecated and should not be used.");
if (ed_visibility_use)
{
return;
}
AZ_PROFILE_FUNCTION(Editor);
auto start = std::chrono::steady_clock::now();
CBaseObjectsCache* pDispayedViewObjects = dc.view->GetVisibleObjectsCache();
if (!pDispayedViewObjects)
{
return;
}
pDispayedViewObjects->SetSerialNumber(m_visibilitySerialNumber); // update viewport to be latest serial number
AABB bbox;
bbox.min.zero();
bbox.max.zero();
pDispayedViewObjects->ClearObjects();
pDispayedViewObjects->Reserve(static_cast<int>(m_visibleObjects.size()));
if (dc.flags & DISPLAY_2D)
{
int numVis = static_cast<int>(m_visibleObjects.size());
for (int i = 0; i < numVis; i++)
{
CBaseObject* obj = m_visibleObjects[i];
obj->GetBoundBox(bbox);
if (dc.box.IsIntersectBox(bbox))
{
pDispayedViewObjects->AddObject(obj);
}
}
}
else
{
CSelectionGroup* pSelection = GetSelection();
if (pSelection && pSelection->GetCount() > 1)
{
AABB mergedAABB;
mergedAABB.Reset();
for (int i = 0, iCount(pSelection->GetCount()); i < iCount; ++i)
{
CBaseObject* pObj(pSelection->GetObject(i));
if (pObj == nullptr)
{
continue;
}
AABB aabb;
pObj->GetBoundBox(aabb);
mergedAABB.Add(aabb);
}
pSelection->GetObject(0)->CBaseObject::DrawDimensions(dc, &mergedAABB);
}
int numVis = static_cast<int>(m_visibleObjects.size());
for (int i = 0; i < numVis; i++)
{
CBaseObject* obj = m_visibleObjects[i];
if (obj)
{
if ((dc.flags & DISPLAY_SELECTION_HELPERS) || obj->IsSelected())
{
pDispayedViewObjects->AddObject(obj);
}
}
}
}
if (ed_visibility_logTiming && !ed_visibility_use)
{
auto stop = std::chrono::steady_clock::now();
std::chrono::duration<double> diff = stop - start;
AZ_Printf("Visibility", "FindDisplayableObjects (old) - Duration: %f", diff);
}
} }
void CObjectManager::BeginEditParams(CBaseObject* obj, int flags) void CObjectManager::BeginEditParams(CBaseObject* obj, int flags)
@ -1630,214 +1518,24 @@ bool CObjectManager::HitTestObject(CBaseObject* obj, HitContext& hc)
return (bSelectionHelperHit || obj->HitTest(hc)); return (bSelectionHelperHit || obj->HitTest(hc));
} }
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
bool CObjectManager::HitTest(HitContext& hitInfo) bool CObjectManager::HitTest([[maybe_unused]] HitContext& hitInfo)
{ {
AZ_PROFILE_FUNCTION(Editor); AZ_Assert(false, "CObjectManager::HitTest is legacy/deprecated and should not be used.");
hitInfo.object = nullptr;
hitInfo.dist = FLT_MAX;
hitInfo.axis = 0;
hitInfo.manipulatorMode = 0;
HitContext hcOrg = hitInfo;
if (hcOrg.view)
{
hcOrg.view->GetPerpendicularAxis(nullptr, &hcOrg.b2DViewport);
}
hcOrg.rayDir = hcOrg.rayDir.GetNormalized();
HitContext hc = hcOrg;
float mindist = FLT_MAX;
if (!hitInfo.bIgnoreAxis && !hc.bUseSelectionHelpers)
{
// Test gizmos.
if (m_gizmoManager->HitTest(hc))
{
if (hc.axis != 0)
{
hitInfo.object = hc.object;
hitInfo.gizmo = hc.gizmo;
hitInfo.axis = hc.axis;
hitInfo.manipulatorMode = hc.manipulatorMode;
hitInfo.dist = hc.dist;
return true;
}
}
}
if (hitInfo.bOnlyGizmo)
{
return false;
}
// Only HitTest objects, that where previously Displayed.
CBaseObjectsCache* pDispayedViewObjects = hitInfo.view->GetVisibleObjectsCache();
const bool iconsPrioritized = true; // Force icons to always be prioritized over other things you hit. Can change to be a configurable option in the future.
CBaseObject* selected = nullptr;
const char* name = nullptr;
bool iconHit = false;
int numVis = pDispayedViewObjects->GetObjectCount();
for (int i = 0; i < numVis; i++)
{
CBaseObject* obj = pDispayedViewObjects->GetObject(i);
if (obj == hitInfo.pExcludedObject)
{
continue;
}
if (HitTestObject(obj, hc))
{
if (m_selectCallback && !m_selectCallback->CanSelectObject(obj))
{
continue;
}
// Check if this object is nearest.
if (hc.axis != 0)
{
hitInfo.object = obj;
hitInfo.axis = hc.axis;
hitInfo.dist = hc.dist;
return true;
}
// When prioritizing icons, we don't allow non-icon hits to beat icon hits
if (iconsPrioritized && iconHit && !hc.iconHit)
{
continue;
}
if (hc.dist < mindist || (!iconHit && hc.iconHit))
{
if (hc.iconHit)
{
iconHit = true;
}
mindist = hc.dist;
name = hc.name;
selected = obj;
}
// Clear the object pointer if an object was hit, not just if the collision
// was closer than any previous. Not all paths from HitTestObject set the object pointer and so you could get
// an object from a previous (rejected) result but with collision information about a closer hit.
hc.object = nullptr;
hc.iconHit = false;
// If use deep selection
if (hitInfo.pDeepSelection)
{
hitInfo.pDeepSelection->AddObject(hc.dist, obj);
}
}
}
if (selected)
{
hitInfo.object = selected;
hitInfo.dist = mindist;
hitInfo.name = name;
hitInfo.iconHit = iconHit;
return true;
}
return false; return false;
} }
void CObjectManager::FindObjectsInRect(CViewport* view, const QRect& rect, std::vector<GUID>& guids)
{
AZ_PROFILE_FUNCTION(Editor);
if (rect.width() < 1 || rect.height() < 1)
{
return;
}
HitContext hc;
hc.view = view;
hc.b2DViewport = view->GetType() != ET_ViewportCamera;
hc.rect = rect;
hc.bUseSelectionHelpers = view->GetAdvancedSelectModeFlag();
guids.clear();
CBaseObjectsCache* pDispayedViewObjects = view->GetVisibleObjectsCache(); void CObjectManager::FindObjectsInRect(
[[maybe_unused]] CViewport* view, [[maybe_unused]] const QRect& rect, [[maybe_unused]] std::vector<GUID>& guids)
int numVis = pDispayedViewObjects->GetObjectCount(); {
for (int i = 0; i < numVis; ++i) AZ_Assert(false, "CObjectManager::FindObjectsInRect is legacy/deprecated and should not be used.");
{
CBaseObject* pObj = pDispayedViewObjects->GetObject(i);
HitTestObjectAgainstRect(pObj, view, hc, guids);
}
} }
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
void CObjectManager::SelectObjectsInRect(CViewport* view, const QRect& rect, bool bSelect) void CObjectManager::SelectObjectsInRect(
[[maybe_unused]] CViewport* view, [[maybe_unused]] const QRect& rect, [[maybe_unused]] bool bSelect)
{ {
AZ_PROFILE_FUNCTION(Editor); AZ_Assert(false, "CObjectManager::SelectObjectsInRect is legacy/deprecated and should not be used.");
// Ignore too small rectangles.
if (rect.width() < 1 || rect.height() < 1)
{
return;
}
CUndo undo("Select Object(s)");
HitContext hc;
hc.view = view;
hc.b2DViewport = view->GetType() != ET_ViewportCamera;
hc.rect = rect;
hc.bUseSelectionHelpers = view->GetAdvancedSelectModeFlag();
bool isUndoRecording = GetIEditor()->IsUndoRecording();
if (isUndoRecording)
{
m_processingBulkSelect = true;
}
CBaseObjectsCache* displayedViewObjects = view->GetVisibleObjectsCache();
int numVis = displayedViewObjects->GetObjectCount();
// Tracking the previous selection allows proper undo/redo functionality of additional
// selections (CTRL + drag select)
AZStd::unordered_set<const CBaseObject*> previousSelection;
for (int i = 0; i < numVis; ++i)
{
CBaseObject* object = displayedViewObjects->GetObject(i);
if (object->IsSelected())
{
previousSelection.insert(object);
}
else
{
// This will update m_currSelection
SelectObjectInRect(object, view, hc, bSelect);
// Legacy undo/redo does not go through the Ebus system and must be done individually
if (isUndoRecording && object->GetType() != OBJTYPE_AZENTITY)
{
GetIEditor()->RecordUndo(new CUndoBaseObjectSelect(object, true));
}
}
}
if (isUndoRecording && m_currSelection)
{
// Component Entities can handle undo/redo in bulk due to Ebuses
GetIEditor()->RecordUndo(new CUndoBaseObjectBulkSelect(previousSelection, *m_currSelection));
}
m_processingBulkSelect = false;
} }
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@ -3011,6 +2709,4 @@ namespace AzToolsFramework
} }
} }
} } // namespace AzToolsFramework

@ -52,40 +52,6 @@ public:
} }
}; };
//////////////////////////////////////////////////////////////////////////
// Array of editor objects.
//////////////////////////////////////////////////////////////////////////
class CBaseObjectsCache
{
public:
int GetObjectCount() const { return static_cast<int>(m_objects.size()); }
CBaseObject* GetObject(int nIndex) const { return m_objects[nIndex]; }
void AddObject(CBaseObject* object);
void ClearObjects()
{
m_objects.clear();
m_entityIds.clear();
}
void Reserve(int nCount)
{
m_objects.reserve(nCount);
m_entityIds.reserve(nCount);
}
const AZStd::vector<AZ::EntityId>& GetEntityIdCache() const { return m_entityIds; }
/// Checksum is used as a dirty flag.
unsigned int GetSerialNumber() { return m_serialNumber; }
void SetSerialNumber(unsigned int serialNumber) { m_serialNumber = serialNumber; }
private:
//! List of objects that was displayed at last frame.
std::vector<_smart_ptr<CBaseObject> > m_objects;
AZStd::vector<AZ::EntityId> m_entityIds;
unsigned int m_serialNumber = 0;
};
/*! /*!
* CObjectManager is a singleton object that * CObjectManager is a singleton object that
* manages global set of objects in level. * manages global set of objects in level.

@ -142,8 +142,7 @@ void GetSelectedEntitiesSetWithFlattenedHierarchy(AzToolsFramework::EntityIdSet&
} }
SandboxIntegrationManager::SandboxIntegrationManager() SandboxIntegrationManager::SandboxIntegrationManager()
: m_inObjectPickMode(false) : m_startedUndoRecordingNestingLevel(0)
, m_startedUndoRecordingNestingLevel(0)
, m_dc(nullptr) , m_dc(nullptr)
, m_notificationWindowManager(new AzToolsFramework::SliceOverridesNotificationWindowManager()) , m_notificationWindowManager(new AzToolsFramework::SliceOverridesNotificationWindowManager())
{ {
@ -1000,62 +999,6 @@ void SandboxIntegrationManager::SetupSliceContextMenu_Modify(QMenu* menu, const
revertAction->setEnabled(canRevert); revertAction->setEnabled(canRevert);
} }
void SandboxIntegrationManager::HandleObjectModeSelection(const AZ::Vector2& point, [[maybe_unused]] int flags, bool& handled)
{
// Todo - Use a custom "edit tool". This will eliminate the need for this bus message entirely, which technically
// makes this feature less intrusive on Sandbox.
// UPDATE: This is now provided by EditorPickEntitySelection when the new Viewport Interaction Model changes are enabled.
if (m_inObjectPickMode)
{
CViewport* view = GetIEditor()->GetViewManager()->GetGameViewport();
const QPoint viewPoint(static_cast<int>(point.GetX()), static_cast<int>(point.GetY()));
HitContext hitInfo;
hitInfo.view = view;
if (view->HitTest(viewPoint, hitInfo))
{
if (hitInfo.object && (hitInfo.object->GetType() == OBJTYPE_AZENTITY))
{
CComponentEntityObject* entityObject = static_cast<CComponentEntityObject*>(hitInfo.object);
AzToolsFramework::EditorPickModeRequestBus::Broadcast(
&AzToolsFramework::EditorPickModeRequests::PickModeSelectEntity, entityObject->GetAssociatedEntityId());
}
}
AzToolsFramework::EditorPickModeRequestBus::Broadcast(
&AzToolsFramework::EditorPickModeRequests::StopEntityPickMode);
handled = true;
}
}
void SandboxIntegrationManager::UpdateObjectModeCursor(AZ::u32& cursorId, AZStd::string& cursorStr)
{
if (m_inObjectPickMode)
{
cursorId = static_cast<AZ::u64>(STD_CURSOR_HAND);
cursorStr = "Pick an entity...";
}
}
void SandboxIntegrationManager::OnEntityPickModeStarted()
{
m_inObjectPickMode = true;
// Currently this object pick mode is activated only via PropertyEntityIdCtrl picker.
// When the picker button is clicked, we transfer focus to the viewport so the
// spacebar can still be used to activate selection helpers.
if (CViewport* view = GetIEditor()->GetViewManager()->GetGameViewport())
{
view->SetFocus();
}
}
void SandboxIntegrationManager::OnEntityPickModeStopped()
{
m_inObjectPickMode = false;
}
void SandboxIntegrationManager::CreateEditorRepresentation(AZ::Entity* entity) void SandboxIntegrationManager::CreateEditorRepresentation(AZ::Entity* entity)
{ {
IEditor* editor = GetIEditor(); IEditor* editor = GetIEditor();

@ -93,7 +93,6 @@ namespace AzToolsFramework
class SandboxIntegrationManager class SandboxIntegrationManager
: private AzToolsFramework::ToolsApplicationEvents::Bus::Handler : private AzToolsFramework::ToolsApplicationEvents::Bus::Handler
, private AzToolsFramework::EditorRequests::Bus::Handler , private AzToolsFramework::EditorRequests::Bus::Handler
, private AzToolsFramework::EditorPickModeNotificationBus::Handler
, private AzToolsFramework::EditorContextMenuBus::Handler , private AzToolsFramework::EditorContextMenuBus::Handler
, private AzToolsFramework::EditorWindowRequests::Bus::Handler , private AzToolsFramework::EditorWindowRequests::Bus::Handler
, private AzFramework::AssetCatalogEventBus::Handler , private AzFramework::AssetCatalogEventBus::Handler
@ -140,8 +139,6 @@ private:
QDockWidget* InstanceViewPane(const char* paneName) override; QDockWidget* InstanceViewPane(const char* paneName) override;
void CloseViewPane(const char* paneName) override; void CloseViewPane(const char* paneName) override;
void BrowseForAssets(AzToolsFramework::AssetBrowser::AssetSelectionModel& selection) override; void BrowseForAssets(AzToolsFramework::AssetBrowser::AssetSelectionModel& selection) override;
void HandleObjectModeSelection(const AZ::Vector2& point, int flags, bool& handled) override;
void UpdateObjectModeCursor(AZ::u32& cursorId, AZStd::string& cursorStr) override;
void CreateEditorRepresentation(AZ::Entity* entity) override; void CreateEditorRepresentation(AZ::Entity* entity) override;
bool DestroyEditorRepresentation(AZ::EntityId entityId, bool deleteAZEntity) override; bool DestroyEditorRepresentation(AZ::EntityId entityId, bool deleteAZEntity) override;
void CloneSelection(bool& handled) override; void CloneSelection(bool& handled) override;
@ -175,10 +172,6 @@ private:
QWidget* GetAppMainWindow() override; QWidget* GetAppMainWindow() override;
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// EditorPickModeNotificationBus
void OnEntityPickModeStarted() override;
void OnEntityPickModeStopped() override;
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// AzToolsFramework::EditorContextMenu::Bus::Handler overrides // AzToolsFramework::EditorContextMenu::Bus::Handler overrides
void PopulateEditorGlobalContextMenu(QMenu* menu, const AZ::Vector2& point, int flags) override; void PopulateEditorGlobalContextMenu(QMenu* menu, const AZ::Vector2& point, int flags) override;
@ -281,7 +274,6 @@ private:
private: private:
AZ::Vector2 m_contextMenuViewPoint; AZ::Vector2 m_contextMenuViewPoint;
int m_inObjectPickMode;
short m_startedUndoRecordingNestingLevel; // used in OnBegin/EndUndo to ensure we only accept undo's we started recording short m_startedUndoRecordingNestingLevel; // used in OnBegin/EndUndo to ensure we only accept undo's we started recording
AzToolsFramework::SliceOverridesNotificationWindowManager* m_notificationWindowManager; AzToolsFramework::SliceOverridesNotificationWindowManager* m_notificationWindowManager;

@ -101,13 +101,13 @@ public:
if (fresh.size() < m_stackNames.size()) if (fresh.size() < m_stackNames.size())
{ {
beginRemoveRows(createIndex(-1, -1), static_cast<int>(fresh.size()), static_cast<int>(m_stackNames.size() - 1)); beginRemoveRows(QModelIndex(), static_cast<int>(fresh.size()), static_cast<int>(m_stackNames.size() - 1));
m_stackNames = fresh; m_stackNames = fresh;
endRemoveRows(); endRemoveRows();
} }
else else
{ {
beginInsertRows(createIndex(-1, -1), static_cast<int>(m_stackNames.size()), static_cast<int>(fresh.size() - 1)); beginInsertRows(QModelIndex(), static_cast<int>(m_stackNames.size()), static_cast<int>(fresh.size() - 1));
m_stackNames = fresh; m_stackNames = fresh;
endInsertRows(); endInsertRows();
} }

@ -173,8 +173,6 @@ QtViewport::QtViewport(QWidget* parent)
m_bAdvancedSelectMode = false; m_bAdvancedSelectMode = false;
m_pVisibleObjectsCache = new CBaseObjectsCache;
m_constructionPlane.SetPlane(Vec3_OneZ, Vec3_Zero); m_constructionPlane.SetPlane(Vec3_OneZ, Vec3_Zero);
m_constructionPlaneAxisX = Vec3_Zero; m_constructionPlaneAxisX = Vec3_Zero;
m_constructionPlaneAxisY = Vec3_Zero; m_constructionPlaneAxisY = Vec3_Zero;
@ -204,8 +202,6 @@ QtViewport::QtViewport(QWidget* parent)
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
QtViewport::~QtViewport() QtViewport::~QtViewport()
{ {
delete m_pVisibleObjectsCache;
GetIEditor()->GetViewManager()->UnregisterViewport(this); GetIEditor()->GetViewManager()->UnregisterViewport(this);
} }
@ -376,11 +372,6 @@ void QtViewport::OnDeactivate()
void QtViewport::ResetContent() void QtViewport::ResetContent()
{ {
m_pMouseOverObject = nullptr; m_pMouseOverObject = nullptr;
// Need to clear visual object cache.
// Right after loading new level, some code(e.g. OnMouseMove) access invalid
// previous level object before cache updated.
GetVisibleObjectsCache()->ClearObjects();
} }
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@ -398,11 +389,8 @@ void QtViewport::Update()
m_viewportUi.Update(); m_viewportUi.Update();
m_bAdvancedSelectMode = false; m_bAdvancedSelectMode = false;
bool bSpaceClick = false;
{ if (CheckVirtualKey(Qt::Key_Space) && !CheckVirtualKey(Qt::Key_Shift) && hasFocus())
bSpaceClick = CheckVirtualKey(Qt::Key_Space) & !CheckVirtualKey(Qt::Key_Shift) /*& !CheckVirtualKey(Qt::Key_Control)*/;
}
if (bSpaceClick && hasFocus())
{ {
m_bAdvancedSelectMode = true; m_bAdvancedSelectMode = true;
} }

@ -157,7 +157,7 @@ public:
virtual Vec3 SnapToGrid(const Vec3& vec) = 0; virtual Vec3 SnapToGrid(const Vec3& vec) = 0;
//! Get selection procision tolerance. //! Get selection precision tolerance.
virtual float GetSelectionTolerance() const = 0; virtual float GetSelectionTolerance() const = 0;
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@ -491,10 +491,6 @@ public:
void ResetCursor() override; void ResetCursor() override;
void SetSupplementaryCursorStr(const QString& str) override; void SetSupplementaryCursorStr(const QString& str) override;
//////////////////////////////////////////////////////////////////////////
// Return visble objects cache.
CBaseObjectsCache* GetVisibleObjectsCache() override { return m_pVisibleObjectsCache; };
void RegisterRenderListener(IRenderListener* piListener) override; void RegisterRenderListener(IRenderListener* piListener) override;
bool UnregisterRenderListener(IRenderListener* piListener) override; bool UnregisterRenderListener(IRenderListener* piListener) override;
bool IsRenderListenerRegistered(IRenderListener* piListener) override; bool IsRenderListenerRegistered(IRenderListener* piListener) override;
@ -612,8 +608,6 @@ protected:
int m_nLastUpdateFrame; int m_nLastUpdateFrame;
int m_nLastMouseMoveFrame; int m_nLastMouseMoveFrame;
CBaseObjectsCache* m_pVisibleObjectsCache;
QRect m_rcClient; QRect m_rcClient;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING

@ -551,8 +551,6 @@ namespace AZ
{ {
PrepareShutDown(); PrepareShutDown();
DispatchEvents();
// Acquire the asset lock to make sure nobody else is trying to do anything fancy with assets // Acquire the asset lock to make sure nobody else is trying to do anything fancy with assets
AZStd::scoped_lock<AZStd::recursive_mutex> assetLock(m_assetMutex); AZStd::scoped_lock<AZStd::recursive_mutex> assetLock(m_assetMutex);
@ -575,7 +573,10 @@ namespace AZ
{ {
AZ_PROFILE_FUNCTION(AzCore); AZ_PROFILE_FUNCTION(AzCore);
AssetManagerNotificationBus::Broadcast(&AssetManagerNotificationBus::Events::OnAssetEventsDispatchBegin); AssetManagerNotificationBus::Broadcast(&AssetManagerNotificationBus::Events::OnAssetEventsDispatchBegin);
AssetBus::ExecuteQueuedEvents(); while (AssetBus::QueuedEventCount())
{
AssetBus::ExecuteQueuedEvents();
}
AssetManagerNotificationBus::Broadcast(&AssetManagerNotificationBus::Events::OnAssetEventsDispatchEnd); AssetManagerNotificationBus::Broadcast(&AssetManagerNotificationBus::Events::OnAssetEventsDispatchEnd);
} }

@ -22,6 +22,8 @@
#include <AzCore/Time/TimeSystemComponent.h> #include <AzCore/Time/TimeSystemComponent.h>
#include <AzCore/Console/LoggerSystemComponent.h> #include <AzCore/Console/LoggerSystemComponent.h>
#include <AzCore/EBus/EventSchedulerSystemComponent.h> #include <AzCore/EBus/EventSchedulerSystemComponent.h>
#include <AzCore/Task/TaskGraphSystemComponent.h>
#include <AzCore/Statistics/StatisticalProfilerProxySystemComponent.h>
namespace AZ namespace AZ
{ {
@ -41,6 +43,11 @@ namespace AZ
TimeSystemComponent::CreateDescriptor(), TimeSystemComponent::CreateDescriptor(),
LoggerSystemComponent::CreateDescriptor(), LoggerSystemComponent::CreateDescriptor(),
EventSchedulerSystemComponent::CreateDescriptor(), EventSchedulerSystemComponent::CreateDescriptor(),
TaskGraphSystemComponent::CreateDescriptor(),
#if !defined(_RELEASE)
Statistics::StatisticalProfilerProxySystemComponent::CreateDescriptor(),
#endif
#if !defined(AZCORE_EXCLUDE_LUA) #if !defined(AZCORE_EXCLUDE_LUA)
ScriptSystemComponent::CreateDescriptor(), ScriptSystemComponent::CreateDescriptor(),
@ -55,6 +62,11 @@ namespace AZ
azrtti_typeid<TimeSystemComponent>(), azrtti_typeid<TimeSystemComponent>(),
azrtti_typeid<LoggerSystemComponent>(), azrtti_typeid<LoggerSystemComponent>(),
azrtti_typeid<EventSchedulerSystemComponent>(), azrtti_typeid<EventSchedulerSystemComponent>(),
azrtti_typeid<TaskGraphSystemComponent>(),
#if !defined(_RELEASE)
azrtti_typeid<Statistics::StatisticalProfilerProxySystemComponent>(),
#endif
}; };
} }
} }

@ -1367,9 +1367,6 @@ namespace AZ
#endif #endif
} }
//=========================================================================
// Tick
//=========================================================================
void ComponentApplication::Tick(float deltaOverride /*= -1.f*/) void ComponentApplication::Tick(float deltaOverride /*= -1.f*/)
{ {
{ {
@ -1397,9 +1394,6 @@ namespace AZ
} }
} }
//=========================================================================
// Tick
//=========================================================================
void ComponentApplication::TickSystem() void ComponentApplication::TickSystem()
{ {
AZ_PROFILE_SCOPE(System, "Component application tick"); AZ_PROFILE_SCOPE(System, "Component application tick");
@ -1547,5 +1541,4 @@ namespace AZ
AZ::SettingsRegistryScriptUtils::ReflectSettingsRegistryToBehaviorContext(*behaviorContext); AZ::SettingsRegistryScriptUtils::ReflectSettingsRegistryToBehaviorContext(*behaviorContext);
} }
} }
} // namespace AZ } // namespace AZ

@ -5,6 +5,7 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT * SPDX-License-Identifier: Apache-2.0 OR MIT
* *
*/ */
#pragma once #pragma once
#include <AzCore/Component/ComponentApplicationBus.h> #include <AzCore/Component/ComponentApplicationBus.h>

@ -11,6 +11,7 @@
#include <AzCore/Module/Environment.h> #include <AzCore/Module/Environment.h>
#include <AzCore/Math/Crc.h> #include <AzCore/Math/Crc.h>
#include <AzCore/Memory/SystemAllocator.h> #include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Statistics/StatisticalProfilerProxy.h>
AZ_DEFINE_BUDGET(Animation); AZ_DEFINE_BUDGET(Animation);
AZ_DEFINE_BUDGET(Audio); AZ_DEFINE_BUDGET(Audio);
@ -30,8 +31,7 @@ namespace AZ::Debug
}; };
Budget::Budget(const char* name) Budget::Budget(const char* name)
: m_name{ name } : Budget( name, Crc32(name) )
, m_crc{ Crc32(name) }
{ {
} }
@ -40,6 +40,10 @@ namespace AZ::Debug
, m_crc{ crc } , m_crc{ crc }
{ {
m_impl = aznew BudgetImpl; m_impl = aznew BudgetImpl;
if (auto statsProfiler = Interface<Statistics::StatisticalProfilerProxy>::Get(); statsProfiler)
{
statsProfiler->RegisterProfilerId(m_crc);
}
} }
Budget::~Budget() Budget::~Budget()

@ -8,6 +8,7 @@
#pragma once #pragma once
#include <AzCore/Debug/Budget.h> #include <AzCore/Debug/Budget.h>
#include <AzCore/Statistics/StatisticalProfilerProxy.h>
#ifdef USE_PIX #ifdef USE_PIX
#include <AzCore/PlatformIncl.h> #include <AzCore/PlatformIncl.h>
@ -44,7 +45,10 @@
#define AZ_PROFILE_INTERVAL_START(...) #define AZ_PROFILE_INTERVAL_START(...)
#define AZ_PROFILE_INTERVAL_START_COLORED(...) #define AZ_PROFILE_INTERVAL_START_COLORED(...)
#define AZ_PROFILE_INTERVAL_END(...) #define AZ_PROFILE_INTERVAL_END(...)
#define AZ_PROFILE_INTERVAL_SCOPED(...) #define AZ_PROFILE_INTERVAL_SCOPED(budget, scopeNameId, ...) \
static constexpr AZ::Crc32 AZ_JOIN(blockId, __LINE__)(scopeNameId); \
AZ::Statistics::StatisticalProfilerProxy::TimedScope AZ_JOIN(scope, __LINE__)(AZ_CRC_CE(#budget), AZ_JOIN(blockId, __LINE__));
#endif #endif
#ifndef AZ_PROFILE_DATAPOINT #ifndef AZ_PROFILE_DATAPOINT

@ -262,17 +262,17 @@ namespace AZ
#else // !AZ_ENABLE_TRACING #else // !AZ_ENABLE_TRACING
#define AZ_Assert(...) AZ_UNUSED(__VA_ARGS__); #define AZ_Assert(...)
#define AZ_Error(...) AZ_UNUSED(__VA_ARGS__); #define AZ_Error(...)
#define AZ_ErrorOnce(...) AZ_UNUSED(__VA_ARGS__); #define AZ_ErrorOnce(...)
#define AZ_Warning(...) AZ_UNUSED(__VA_ARGS__); #define AZ_Warning(...)
#define AZ_WarningOnce(...) AZ_UNUSED(__VA_ARGS__); #define AZ_WarningOnce(...)
#define AZ_TracePrintf(...) AZ_UNUSED(__VA_ARGS__); #define AZ_TracePrintf(...)
#define AZ_TracePrintfOnce(...) AZ_UNUSED(__VA_ARGS__); #define AZ_TracePrintfOnce(...)
#define AZ_Verify(...) AZ_UNUSED(__VA_ARGS__); #define AZ_Verify(expression, ...) AZ_UNUSED(expression)
#define AZ_VerifyError(...) AZ_UNUSED(__VA_ARGS__); #define AZ_VerifyError(window, expression, ...) AZ_UNUSED(expression)
#define AZ_VerifyWarning(...) AZ_UNUSED(__VA_ARGS__); #define AZ_VerifyWarning(window, expression, ...) AZ_UNUSED(expression)
#endif // AZ_ENABLE_TRACING #endif // AZ_ENABLE_TRACING

@ -12,7 +12,7 @@
* that Open 3D Engine uses to dispatch notifications and receive requests. * that Open 3D Engine uses to dispatch notifications and receive requests.
* EBuses are configurable and support many different use cases. * EBuses are configurable and support many different use cases.
* For more information about %EBuses, see AZ::EBus in this guide and * For more information about %EBuses, see AZ::EBus in this guide and
* [Event Bus](http://docs.aws.amazon.com/lumberyard/latest/developerguide/asset-pipeline-ebus.html) * [Event Bus](https://o3de.org/docs/user-guide/engine/ebus/)
* in the *Open 3D Engine Developer Guide*. * in the *Open 3D Engine Developer Guide*.
*/ */
@ -62,7 +62,7 @@ namespace AZ
* @endcode * @endcode
* *
* For more information about %EBuses, see EBus in this guide and * For more information about %EBuses, see EBus in this guide and
* [Event Bus](http://docs.aws.amazon.com/lumberyard/latest/developerguide/asset-pipeline-ebus.html) * [Event Bus](https://o3de.org/docs/user-guide/engine/ebus/)
* in the *Open 3D Engine Developer Guide*. * in the *Open 3D Engine Developer Guide*.
*/ */
struct EBusTraits struct EBusTraits
@ -259,8 +259,8 @@ namespace AZ
* *
* EBuses are configurable and support many different use cases. * EBuses are configurable and support many different use cases.
* For more information about EBuses, see * For more information about EBuses, see
* [Event Bus](http://docs.aws.amazon.com/lumberyard/latest/developerguide/asset-pipeline-ebus.html) * [Event Bus](https://o3de.org/docs/user-guide/engine/ebus/)
* and [Components and EBuses: Best Practices ](http://docs.aws.amazon.com/lumberyard/latest/developerguide/component-entity-system-pg-components-ebuses-best-practices.html) * and [Components and EBuses: Best Practices ](https://o3de.org/docs/user-guide/components/development/entity-system-pg-components-ebuses-best-practices/)
* in the *Open 3D Engine Developer Guide*. * in the *Open 3D Engine Developer Guide*.
* *
* ## How Components Use EBuses * ## How Components Use EBuses

@ -43,10 +43,12 @@ namespace AZ::IO
m_mainLoopDesc = threadDesc; m_mainLoopDesc = threadDesc;
m_mainLoopDesc.m_name = "IO Scheduler"; m_mainLoopDesc.m_name = "IO Scheduler";
m_mainLoop = AZStd::thread([this]() m_mainLoop = AZStd::thread(
{ m_mainLoopDesc,
Thread_MainLoop(); [this]()
}, &m_mainLoopDesc); {
Thread_MainLoop();
});
} }
} }

@ -35,6 +35,7 @@ namespace Platform
SystemFile::SizeType Length(FileHandleType handle, const SystemFile* systemFile); SystemFile::SizeType Length(FileHandleType handle, const SystemFile* systemFile);
bool Exists(const char* fileName); bool Exists(const char* fileName);
bool IsDirectory(const char* filePath);
void FindFiles(const char* filter, SystemFile::FindFileCB cb); void FindFiles(const char* filter, SystemFile::FindFileCB cb);
AZ::u64 ModificationTime(const char* fileName); AZ::u64 ModificationTime(const char* fileName);
SystemFile::SizeType Length(const char* fileName); SystemFile::SizeType Length(const char* fileName);
@ -235,6 +236,11 @@ bool SystemFile::Exists(const char* fileName)
return Platform::Exists(fileName); return Platform::Exists(fileName);
} }
bool SystemFile::IsDirectory(const char* filePath)
{
return Platform::IsDirectory(filePath);
}
void SystemFile::FindFiles(const char* filter, FindFileCB cb) void SystemFile::FindFiles(const char* filter, FindFileCB cb)
{ {
Platform::FindFiles(filter, cb); Platform::FindFiles(filter, cb);

@ -99,6 +99,8 @@ namespace AZ
// Utility functions // Utility functions
/// Check if a file or directory exists. /// Check if a file or directory exists.
static bool Exists(const char* path); static bool Exists(const char* path);
/// Check if path is a directory
static bool IsDirectory(const char* path);
/// FindFiles /// FindFiles
typedef AZStd::function<bool /* true to continue to enumerate otherwise false */ (const char* /* fileName*/, bool /* true if file, false if folder*/)> FindFileCB; typedef AZStd::function<bool /* true to continue to enumerate otherwise false */ (const char* /* fileName*/, bool /* true if file, false if folder*/)> FindFileCB;
static void FindFiles(const char* filter, FindFileCB cb); static void FindFiles(const char* filter, FindFileCB cb);

@ -644,11 +644,11 @@ JobManagerWorkStealing::ThreadList JobManagerWorkStealing::CreateWorkerThreads(c
} }
info->m_thread = AZStd::thread( info->m_thread = AZStd::thread(
threadDesc,
[this, info]() [this, info]()
{ {
this->ProcessJobsWorker(info); this->ProcessJobsWorker(info);
}, }
&threadDesc
); );
info->m_threadId = info->m_thread.get_id(); info->m_threadId = info->m_thread.get_id();

@ -5,7 +5,6 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT * SPDX-License-Identifier: Apache-2.0 OR MIT
* *
*/ */
#pragma once #pragma once
#include <AzCore/base.h> #include <AzCore/base.h>

@ -180,6 +180,13 @@ namespace AZ
bool IsGreaterEqualThan(const Vector2& v) const; bool IsGreaterEqualThan(const Vector2& v) const;
//! @} //! @}
//! Floor/Ceil/Round functions, operate on each component individually, result will be a new Vector2.
//! @{
Vector2 GetFloor() const;
Vector2 GetCeil() const;
Vector2 GetRound() const; // Ties to even (banker's rounding)
//! @}
//! Min/Max functions, operate on each component individually, result will be a new Vector2. //! Min/Max functions, operate on each component individually, result will be a new Vector2.
//! @{ //! @{
Vector2 GetMin(const Vector2& v) const; Vector2 GetMin(const Vector2& v) const;

@ -398,6 +398,24 @@ namespace AZ
} }
AZ_MATH_INLINE Vector2 Vector2::GetFloor() const
{
return Vector2(Simd::Vec2::Floor(m_value));
}
AZ_MATH_INLINE Vector2 Vector2::GetCeil() const
{
return Vector2(Simd::Vec2::Ceil(m_value));
}
AZ_MATH_INLINE Vector2 Vector2::GetRound() const
{
return Vector2(Simd::Vec2::Round(m_value));
}
AZ_MATH_INLINE Vector2 Vector2::GetMin(const Vector2& v) const AZ_MATH_INLINE Vector2 Vector2::GetMin(const Vector2& v) const
{ {
#if AZ_TRAIT_USE_PLATFORM_SIMD_SCALAR #if AZ_TRAIT_USE_PLATFORM_SIMD_SCALAR

@ -211,6 +211,13 @@ namespace AZ
bool IsGreaterEqualThan(const Vector3& rhs) const; bool IsGreaterEqualThan(const Vector3& rhs) const;
//! @} //! @}
//! Floor/Ceil/Round functions, operate on each component individually, result will be a new Vector3.
//! @{
Vector3 GetFloor() const;
Vector3 GetCeil() const;
Vector3 GetRound() const; // Ties to even (banker's rounding)
//! @}
//! Min/Max functions, operate on each component individually, result will be a new Vector3. //! Min/Max functions, operate on each component individually, result will be a new Vector3.
//! @{ //! @{
Vector3 GetMin(const Vector3& v) const; Vector3 GetMin(const Vector3& v) const;

@ -481,6 +481,24 @@ namespace AZ
} }
AZ_MATH_INLINE Vector3 Vector3::GetFloor() const
{
return Vector3(Simd::Vec3::Floor(m_value));
}
AZ_MATH_INLINE Vector3 Vector3::GetCeil() const
{
return Vector3(Simd::Vec3::Ceil(m_value));
}
AZ_MATH_INLINE Vector3 Vector3::GetRound() const
{
return Vector3(Simd::Vec3::Round(m_value));
}
AZ_MATH_INLINE Vector3 Vector3::GetMin(const Vector3& v) const AZ_MATH_INLINE Vector3 Vector3::GetMin(const Vector3& v) const
{ {
#if AZ_TRAIT_USE_PLATFORM_SIMD_SCALAR #if AZ_TRAIT_USE_PLATFORM_SIMD_SCALAR

@ -189,6 +189,13 @@ namespace AZ
bool IsGreaterEqualThan(const Vector4& rhs) const; bool IsGreaterEqualThan(const Vector4& rhs) const;
//! @} //! @}
//! Floor/Ceil/Round functions, operate on each component individually, result will be a new Vector4.
//! @{
Vector4 GetFloor() const;
Vector4 GetCeil() const;
Vector4 GetRound() const; // Ties to even (banker's rounding)
//! @}
//! Min/Max functions, operate on each component individually, result will be a new Vector4. //! Min/Max functions, operate on each component individually, result will be a new Vector4.
//! @{ //! @{
Vector4 GetMin(const Vector4& v) const; Vector4 GetMin(const Vector4& v) const;

@ -464,6 +464,24 @@ namespace AZ
} }
AZ_MATH_INLINE Vector4 Vector4::GetFloor() const
{
return Vector4(Simd::Vec4::Floor(m_value));
}
AZ_MATH_INLINE Vector4 Vector4::GetCeil() const
{
return Vector4(Simd::Vec4::Ceil(m_value));
}
AZ_MATH_INLINE Vector4 Vector4::GetRound() const
{
return Vector4(Simd::Vec4::Round(m_value));
}
AZ_MATH_INLINE Vector4 Vector4::GetMin(const Vector4& v) const AZ_MATH_INLINE Vector4 Vector4::GetMin(const Vector4& v) const
{ {
#if AZ_TRAIT_USE_PLATFORM_SIMD_SCALAR #if AZ_TRAIT_USE_PLATFORM_SIMD_SCALAR

@ -276,7 +276,9 @@ namespace AZ::SettingsRegistryMergeUtils
return engineRoot; return engineRoot;
} }
return {}; // Fall back to using the project root as the engine root if the engine path could not be reconciled
// by checking the project.json "engine" string within o3de_manifest.json "engine_paths" object
return projectRoot;
} }
AZ::IO::FixedMaxPath FindProjectRoot(SettingsRegistryInterface& settingsRegistry) AZ::IO::FixedMaxPath FindProjectRoot(SettingsRegistryInterface& settingsRegistry)
@ -309,7 +311,13 @@ namespace AZ::SettingsRegistryMergeUtils
return projectRoot; return projectRoot;
} }
return {}; // Step 3 Check for a "Cache" directory by scanning upwards from the executable directory
if (auto candidateRoot = Internal::ScanUpRootLocator("Cache");
!candidateRoot.empty() && AZ::IO::SystemFile::IsDirectory(candidateRoot.c_str()))
{
projectRoot = AZStd::move(candidateRoot);
}
return projectRoot;
} }
AZStd::string_view ConfigParserSettings::DefaultCommentPrefixFilter(AZStd::string_view line) AZStd::string_view ConfigParserSettings::DefaultCommentPrefixFilter(AZStd::string_view line)
@ -717,7 +725,9 @@ namespace AZ::SettingsRegistryMergeUtils
if (registry.Get(cacheRootPath, FilePathKey_CacheRootFolder)) if (registry.Get(cacheRootPath, FilePathKey_CacheRootFolder))
{ {
mergePath = AZStd::move(cacheRootPath); mergePath = AZStd::move(cacheRootPath);
mergePath /= SettingsRegistryInterface::RegistryFolder; AZStd::fixed_string<32> registryFolderLower(SettingsRegistryInterface::RegistryFolder);
AZStd::to_lower(registryFolderLower.begin(), registryFolderLower.end());
mergePath /= registryFolderLower;
registry.MergeSettingsFolder(mergePath.Native(), specializations, platform, "", scratchBuffer); registry.MergeSettingsFolder(mergePath.Native(), specializations, platform, "", scratchBuffer);
} }

@ -1,106 +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 "RunningStatisticsManager.h"
namespace AzFramework
{
namespace Statistics
{
bool RunningStatisticsManager::ContainsStatistic(const AZStd::string& name)
{
auto iterator = m_statisticsNamesToIndexMap.find(name);
return iterator != m_statisticsNamesToIndexMap.end();
}
bool RunningStatisticsManager::AddStatistic(const AZStd::string& name, const AZStd::string& units)
{
if (ContainsStatistic(name))
{
return false;
}
AddStatisticValidated(name, units);
return true;
}
void RunningStatisticsManager::RemoveStatistic(const AZStd::string& name)
{
auto iterator = m_statisticsNamesToIndexMap.find(name);
if (iterator == m_statisticsNamesToIndexMap.end())
{
return;
}
AZ::u32 itemIndex = iterator->second;
m_statistics.erase(m_statistics.begin() + itemIndex);
m_statisticsNamesToIndexMap.erase(iterator);
//Update the indices in m_statisticsNamesToIndexMap.
while (itemIndex < m_statistics.size())
{
const AZStd::string& statName = m_statistics[itemIndex].GetName();
m_statisticsNamesToIndexMap[statName] = itemIndex;
++itemIndex;
}
}
void RunningStatisticsManager::ResetStatistic(const AZStd::string& name)
{
NamedRunningStatistic* stat = GetStatistic(name);
if (!stat)
{
return;
}
stat->Reset();
}
void RunningStatisticsManager::ResetAllStatistics()
{
for (NamedRunningStatistic& stat : m_statistics)
{
stat.Reset();
}
}
void RunningStatisticsManager::PushSampleForStatistic(const AZStd::string& name, double value)
{
NamedRunningStatistic* stat = GetStatistic(name);
if (!stat)
{
return;
}
stat->PushSample(value);
}
NamedRunningStatistic* RunningStatisticsManager::GetStatistic(const AZStd::string& name, AZ::u32* indexOut)
{
auto iterator = m_statisticsNamesToIndexMap.find(name);
if (iterator == m_statisticsNamesToIndexMap.end())
{
return nullptr;
}
const AZ::u32 index = iterator->second;
if (indexOut)
{
*indexOut = index;
}
return &m_statistics[index];
}
const AZStd::vector<NamedRunningStatistic>& RunningStatisticsManager::GetAllStatistics() const
{
return m_statistics;
}
void RunningStatisticsManager::AddStatisticValidated(const AZStd::string& name, const AZStd::string& units)
{
m_statistics.emplace_back(NamedRunningStatistic(name, units));
const AZ::u32 itemIndex = static_cast<AZ::u32>(m_statistics.size() - 1);
m_statisticsNamesToIndexMap[name] = itemIndex;
}
}//namespace Statistics
}//namespace AzFramework

@ -8,7 +8,6 @@
#pragma once #pragma once
#include <AzCore/EBus/BusImpl.h> //Just to get AZ::NullMutex #include <AzCore/EBus/BusImpl.h> //Just to get AZ::NullMutex
#include <AzCore/std/chrono/types.h>
#include <AzCore/Statistics/StatisticsManager.h> #include <AzCore/Statistics/StatisticsManager.h>
#include <AzCore/std/chrono/chrono.h> #include <AzCore/std/chrono/chrono.h>
#include <AzCore/std/parallel/scoped_lock.h> #include <AzCore/std/parallel/scoped_lock.h>
@ -37,8 +36,7 @@ namespace AZ
//! are some things to consider when working with the StatisticalProfilerProxy: //! are some things to consider when working with the StatisticalProfilerProxy:
//! The StatisticalProfilerProxy OWNS an array of StatisticalProfiler<AZStd::string, AZStd::shared_spin_mutex>. //! The StatisticalProfilerProxy OWNS an array of StatisticalProfiler<AZStd::string, AZStd::shared_spin_mutex>.
//! You can "manage" one of those StatisticalProfiler by getting a reference to it and //! You can "manage" one of those StatisticalProfiler by getting a reference to it and
//! add Running statistics etc. See The TerrainProfilers mentioned above to see concrete use //! add Running statistics etc.
//! cases on how to work with the StatisticalProfilerProxy.
template <class StatIdType = AZStd::string, class MutexType = AZ::NullMutex> template <class StatIdType = AZStd::string, class MutexType = AZ::NullMutex>
class StatisticalProfiler class StatisticalProfiler
{ {

@ -7,27 +7,11 @@
*/ */
#pragma once #pragma once
#include <AzCore/std/chrono/types.h>
#include <AzCore/std/parallel/shared_spin_mutex.h>
#include <AzCore/std/parallel/scoped_lock.h>
#include <AzCore/std/containers/bitset.h>
#include <AzCore/std/string/string.h>
#include <AzCore/Interface/Interface.h> #include <AzCore/Interface/Interface.h>
#include <AzCore/Statistics/StatisticalProfiler.h> #include <AzCore/Statistics/StatisticalProfiler.h>
#include <AzCore/Debug/Profiler.h> #include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/parallel/shared_spin_mutex.h>
#if defined(AZ_STATISTICAL_PROFILING_ENABLED)
#if defined(AZ_PROFILE_SCOPE)
#undef AZ_PROFILE_SCOPE
#endif // #if defined(AZ_PROFILE_SCOPE)
#define AZ_PROFILE_SCOPE(profiler, scopeNameId) \
static const AZStd::string AZ_JOIN(blockName, __LINE__)(scopeNameId); \
AZ::Statistics::StatisticalProfilerProxy::TimedScope AZ_JOIN(scope, __LINE__)(profiler, AZ_JOIN(blockName, __LINE__));
#endif //#if defined(AZ_STATISTICAL_PROFILING_ENABLED)
namespace AZ::Statistics namespace AZ::Statistics
{ {
@ -65,7 +49,7 @@ namespace AZ::Statistics
public: public:
AZ_TYPE_INFO(StatisticalProfilerProxy, "{1103D0EB-1C32-4854-B9D9-40A2D65BDBD2}"); AZ_TYPE_INFO(StatisticalProfilerProxy, "{1103D0EB-1C32-4854-B9D9-40A2D65BDBD2}");
using StatIdType = AZStd::string; using StatIdType = AZ::Crc32;
using StatisticalProfilerType = StatisticalProfiler<StatIdType, AZStd::shared_spin_mutex>; using StatisticalProfilerType = StatisticalProfiler<StatIdType, AZStd::shared_spin_mutex>;
//! A Convenience class used to measure time performance of scopes of code //! A Convenience class used to measure time performance of scopes of code
@ -94,6 +78,7 @@ namespace AZ::Statistics
} }
m_startTime = AZStd::chrono::high_resolution_clock::now(); m_startTime = AZStd::chrono::high_resolution_clock::now();
} }
~TimedScope() ~TimedScope()
{ {
if (!m_profilerProxy) if (!m_profilerProxy)
@ -122,7 +107,6 @@ namespace AZ::Statistics
StatisticalProfilerProxy() StatisticalProfilerProxy()
{ {
// TODO:BUDGETS Query available budgets at registration time and create an associated profiler per type
AZ::Interface<StatisticalProfilerProxy>::Register(this); AZ::Interface<StatisticalProfilerProxy>::Register(this);
} }
@ -135,30 +119,54 @@ namespace AZ::Statistics
StatisticalProfilerProxy(StatisticalProfilerProxy&&) = delete; StatisticalProfilerProxy(StatisticalProfilerProxy&&) = delete;
StatisticalProfilerProxy& operator=(StatisticalProfilerProxy&&) = delete; StatisticalProfilerProxy& operator=(StatisticalProfilerProxy&&) = delete;
void RegisterProfilerId(StatisticalProfilerId id)
{
m_profilers.try_emplace(id, ProfilerInfo());
}
bool IsProfilerActive(StatisticalProfilerId id) const bool IsProfilerActive(StatisticalProfilerId id) const
{ {
return m_activeProfilersFlag[static_cast<AZStd::size_t>(id)]; auto iter = m_profilers.find(id);
return (iter != m_profilers.end()) ? iter->second.m_enabled : false;
} }
StatisticalProfilerType& GetProfiler(StatisticalProfilerId id) StatisticalProfilerType& GetProfiler(StatisticalProfilerId id)
{ {
return m_profilers[static_cast<AZStd::size_t>(id)]; auto iter = m_profilers.try_emplace(id, ProfilerInfo()).first;
return iter->second.m_profiler;
} }
void ActivateProfiler(StatisticalProfilerId id, bool activate) void ActivateProfiler(StatisticalProfilerId id, bool activate, bool autoCreate = true)
{ {
m_activeProfilersFlag[static_cast<AZStd::size_t>(id)] = activate; if (autoCreate)
{
auto iter = m_profilers.try_emplace(id, ProfilerInfo()).first;
iter->second.m_enabled = activate;
}
else if (auto iter = m_profilers.find(id); iter != m_profilers.end())
{
iter->second.m_enabled = activate;
}
} }
void PushSample(StatisticalProfilerId id, const StatIdType& statId, double value) void PushSample(StatisticalProfilerId id, const StatIdType& statId, double value)
{ {
m_profilers[static_cast<AZStd::size_t>(id)].PushSample(statId, value); if (auto iter = m_profilers.find(id); iter != m_profilers.end())
{
iter->second.m_profiler.PushSample(statId, value);
}
} }
private: private:
// TODO:BUDGETS the number of bits allocated here must be based on the number of budgets available at profiler registration time struct ProfilerInfo
AZStd::bitset<128> m_activeProfilersFlag; {
AZStd::vector<StatisticalProfilerType> m_profilers; StatisticalProfilerType m_profiler;
bool m_enabled{ false };
};
using ProfilerMap = AZStd::unordered_map<StatisticalProfilerId, ProfilerInfo>;
ProfilerMap m_profilers;
}; // class StatisticalProfilerProxy }; // class StatisticalProfilerProxy
}; // namespace AZ::Statistics }; // namespace AZ::Statistics

@ -190,11 +190,13 @@ namespace AZ
class TaskWorker class TaskWorker
{ {
public: public:
void Spawn(::AZ::TaskExecutor& executor, size_t id, AZStd::semaphore& initSemaphore, bool affinitize) static thread_local TaskWorker* t_worker;
void Spawn(::AZ::TaskExecutor& executor, uint32_t id, AZStd::semaphore& initSemaphore, bool affinitize)
{ {
m_executor = &executor; m_executor = &executor;
AZStd::string threadName = AZStd::string::format("TaskWorker %zu", id); AZStd::string threadName = AZStd::string::format("TaskWorker %u", id);
AZStd::thread_desc desc = {}; AZStd::thread_desc desc = {};
desc.m_name = threadName.c_str(); desc.m_name = threadName.c_str();
if (affinitize) if (affinitize)
@ -203,12 +205,29 @@ namespace AZ
} }
m_active.store(true, AZStd::memory_order_release); m_active.store(true, AZStd::memory_order_release);
m_thread = AZStd::thread{ [this, &initSemaphore] m_thread = AZStd::thread{ desc,
[this, &initSemaphore]
{ {
t_worker = this;
initSemaphore.release(); initSemaphore.release();
Run(); Run();
}, } };
&desc }; }
// Threads that wait on a graph to complete are disqualified from receiving tasks until the wait finishes
void Disable()
{
m_enabled = false;
}
void Enable()
{
m_enabled = true;
}
bool Enabled() const
{
return m_enabled;
} }
void Join() void Join()
@ -222,11 +241,7 @@ namespace AZ
{ {
m_queue.Enqueue(task); m_queue.Enqueue(task);
if (!m_busy.exchange(true)) m_semaphore.release();
{
// The worker was idle prior to enqueueing the task, release the semaphore
m_semaphore.release();
}
} }
private: private:
@ -234,7 +249,6 @@ namespace AZ
{ {
while (m_active) while (m_active)
{ {
m_busy = false;
m_semaphore.acquire(); m_semaphore.acquire();
if (!m_active) if (!m_active)
@ -242,8 +256,6 @@ namespace AZ
return; return;
} }
m_busy = true;
Task* task = m_queue.TryDequeue(); Task* task = m_queue.TryDequeue();
while (task) while (task)
{ {
@ -271,12 +283,15 @@ namespace AZ
AZStd::thread m_thread; AZStd::thread m_thread;
AZStd::atomic<bool> m_active; AZStd::atomic<bool> m_active;
AZStd::atomic<bool> m_busy; AZStd::atomic<bool> m_enabled = true;
AZStd::binary_semaphore m_semaphore; AZStd::binary_semaphore m_semaphore;
::AZ::TaskExecutor* m_executor; ::AZ::TaskExecutor* m_executor;
TaskQueue m_queue; TaskQueue m_queue;
friend class ::AZ::TaskExecutor;
}; };
thread_local TaskWorker* TaskWorker::t_worker = nullptr;
} // namespace Internal } // namespace Internal
static EnvironmentVariable<TaskExecutor*> s_executor; static EnvironmentVariable<TaskExecutor*> s_executor;
@ -291,13 +306,16 @@ namespace AZ
return **s_executor; return **s_executor;
} }
// TODO: Create the default executor as part of a component (as in TaskManagerComponent)
void TaskExecutor::SetInstance(TaskExecutor* executor) void TaskExecutor::SetInstance(TaskExecutor* executor)
{ {
AZ_Assert(!s_executor, "Attempting to set the global task executor more than once"); if (!executor)
{
s_executor = AZ::Environment::CreateVariable<TaskExecutor*>("GlobalTaskExecutor"); s_executor.Reset();
s_executor.Set(executor); }
else if (!s_executor) // ignore any calls to set after the first (this happens in unit tests that create new system entities)
{
s_executor = AZ::Environment::CreateVariable<TaskExecutor*>(s_executorName, executor);
}
} }
TaskExecutor::TaskExecutor(uint32_t threadCount) TaskExecutor::TaskExecutor(uint32_t threadCount)
@ -307,14 +325,12 @@ namespace AZ
m_workers = reinterpret_cast<Internal::TaskWorker*>(azmalloc(m_threadCount * sizeof(Internal::TaskWorker))); m_workers = reinterpret_cast<Internal::TaskWorker*>(azmalloc(m_threadCount * sizeof(Internal::TaskWorker)));
bool affinitize = m_threadCount == AZStd::thread::hardware_concurrency();
AZStd::semaphore initSemaphore; AZStd::semaphore initSemaphore;
for (size_t i = 0; i != m_threadCount; ++i) for (uint32_t i = 0; i != m_threadCount; ++i)
{ {
new (m_workers + i) Internal::TaskWorker{}; new (m_workers + i) Internal::TaskWorker{};
m_workers[i].Spawn(*this, i, initSemaphore, affinitize); m_workers[i].Spawn(*this, i, initSemaphore, false);
} }
for (size_t i = 0; i != m_threadCount; ++i) for (size_t i = 0; i != m_threadCount; ++i)
@ -334,9 +350,21 @@ namespace AZ
azfree(m_workers); azfree(m_workers);
} }
void TaskExecutor::Submit(Internal::CompiledTaskGraph& graph) Internal::TaskWorker* TaskExecutor::GetTaskWorker()
{
if (Internal::TaskWorker::t_worker && Internal::TaskWorker::t_worker->m_executor == this)
{
return Internal::TaskWorker::t_worker;
}
return nullptr;
}
void TaskExecutor::Submit(Internal::CompiledTaskGraph& graph, TaskGraphEvent* event)
{ {
++m_graphsRemaining; ++m_graphsRemaining;
event->m_executor = this; // Used to validate event is not waited for inside a job
// Submit all tasks that have no inbound edges // Submit all tasks that have no inbound edges
for (Internal::Task& task : graph.Tasks()) for (Internal::Task& task : graph.Tasks())
{ {
@ -352,11 +380,24 @@ namespace AZ
// TODO: Something more sophisticated is likely needed here. // TODO: Something more sophisticated is likely needed here.
// First, we are completely ignoring affinity. // First, we are completely ignoring affinity.
// Second, some heuristics on core availability will help distribute work more effectively // Second, some heuristics on core availability will help distribute work more effectively
m_workers[++m_lastSubmission % m_threadCount].Enqueue(&task); uint32_t nextWorker = ++m_lastSubmission % m_threadCount;
while (!m_workers[nextWorker].Enabled())
{
// Graphs that are waiting for the completion of a task graph cannot enqueue tasks onto
// the thread issuing the wait.
nextWorker = ++m_lastSubmission % m_threadCount;
}
m_workers[nextWorker].Enqueue(&task);
} }
void TaskExecutor::ReleaseGraph() void TaskExecutor::ReleaseGraph()
{ {
--m_graphsRemaining; --m_graphsRemaining;
} }
void TaskExecutor::ReactivateTaskWorker()
{
GetTaskWorker()->Enable();
}
} // namespace AZ } // namespace AZ

@ -72,14 +72,19 @@ namespace AZ
explicit TaskExecutor(uint32_t threadCount = 0); explicit TaskExecutor(uint32_t threadCount = 0);
~TaskExecutor(); ~TaskExecutor();
void Submit(Internal::CompiledTaskGraph& graph); // Submit a task graph for execution. Waitable task graphs cannot enqueue work on the task thread
// that is currently active
void Submit(Internal::CompiledTaskGraph& graph, TaskGraphEvent* event);
void Submit(Internal::Task& task); void Submit(Internal::Task& task);
private: private:
friend class Internal::TaskWorker; friend class Internal::TaskWorker;
friend class TaskGraphEvent;
Internal::TaskWorker* GetTaskWorker();
void ReleaseGraph(); void ReleaseGraph();
void ReactivateTaskWorker();
Internal::TaskWorker* m_workers; Internal::TaskWorker* m_workers;
uint32_t m_threadCount = 0; uint32_t m_threadCount = 0;

@ -14,6 +14,12 @@ namespace AZ
{ {
using Internal::CompiledTaskGraph; using Internal::CompiledTaskGraph;
void TaskGraphEvent::Wait()
{
AZ_Assert(m_executor->GetTaskWorker() == nullptr, "Waiting in a task is unsupported");
m_semaphore.acquire();
}
void TaskToken::PrecedesInternal(TaskToken& comesAfter) void TaskToken::PrecedesInternal(TaskToken& comesAfter)
{ {
AZ_Assert(!m_parent.m_submitted, "Cannot mutate a TaskGraph that was previously submitted."); AZ_Assert(!m_parent.m_submitted, "Cannot mutate a TaskGraph that was previously submitted.");
@ -71,7 +77,7 @@ namespace AZ
m_compiledTaskGraph->m_tasks[i].Init(); m_compiledTaskGraph->m_tasks[i].Init();
} }
executor.Submit(*m_compiledTaskGraph); executor.Submit(*m_compiledTaskGraph, waitEvent);
if (m_retained) if (m_retained)
{ {

@ -22,10 +22,19 @@ namespace AZ
namespace Internal namespace Internal
{ {
class CompiledTaskGraph; class CompiledTaskGraph;
class TaskWorker;
} }
class TaskExecutor; class TaskExecutor;
class TaskGraph; class TaskGraph;
class TaskGraphActiveInterface
{
public:
AZ_RTTI(TaskGraphActiveInterface, "{08118074-B139-4EF9-B8FD-29F1D6DC9233}");
virtual bool IsTaskGraphActive() const = 0;
};
// A TaskToken is returned each time a Task is added to the TaskGraph. TaskTokens are used to // A TaskToken is returned each time a Task is added to the TaskGraph. TaskTokens are used to
// express dependencies between tasks within the graph, and have no purpose after the graph // express dependencies between tasks within the graph, and have no purpose after the graph
// is submitted (simply let them go out of scope) // is submitted (simply let them go out of scope)
@ -70,9 +79,12 @@ namespace AZ
private: private:
friend class ::AZ::Internal::CompiledTaskGraph; friend class ::AZ::Internal::CompiledTaskGraph;
friend class TaskGraph; friend class TaskGraph;
friend class TaskExecutor;
void Signal(); void Signal();
AZStd::binary_semaphore m_semaphore; AZStd::binary_semaphore m_semaphore;
TaskExecutor* m_executor = nullptr;
}; };
// The TaskGraph encapsulates a set of tasks and their interdependencies. After adding // The TaskGraph encapsulates a set of tasks and their interdependencies. After adding
@ -89,6 +101,9 @@ namespace AZ
// Reset the state of the task graph to begin recording tasks and edges again // Reset the state of the task graph to begin recording tasks and edges again
// NOTE: Graph must be in a "settled" state (cannot be in-flight) // NOTE: Graph must be in a "settled" state (cannot be in-flight)
void Reset(); void Reset();
// Returns false if 1 or more tasks have been added to the graph
bool IsEmpty();
// Add a task to the graph, retrieiving a token that can be used to express dependencies // Add a task to the graph, retrieiving a token that can be used to express dependencies
// between tasks. The first argument specifies the TaskKind, used for tracking the task. // between tasks. The first argument specifies the TaskKind, used for tracking the task.

@ -33,11 +33,6 @@ namespace AZ
return m_semaphore.try_acquire_for(AZStd::chrono::milliseconds{ 0 }); return m_semaphore.try_acquire_for(AZStd::chrono::milliseconds{ 0 });
} }
inline void TaskGraphEvent::Wait()
{
m_semaphore.acquire();
}
inline void TaskGraphEvent::Signal() inline void TaskGraphEvent::Signal()
{ {
m_semaphore.release(); m_semaphore.release();
@ -59,6 +54,11 @@ namespace AZ
return { AddTask(descriptor, AZStd::forward<Lambdas>(lambdas))... }; return { AddTask(descriptor, AZStd::forward<Lambdas>(lambdas))... };
} }
inline bool TaskGraph::IsEmpty()
{
return m_tasks.empty();
}
inline void TaskGraph::Detach() inline void TaskGraph::Detach()
{ {
m_retained = false; m_retained = false;

@ -0,0 +1,88 @@
/*
* 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/Console/IConsole.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Task/TaskGraphSystemComponent.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
// Create a cvar as a central location for experimentation with switching from the Job system to TaskGraph system.
AZ_CVAR(bool, cl_activateTaskGraph, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Flag clients of TaskGraph to switch between jobs/taskgraph (Note does not disable task graph system)");
static constexpr uint32_t TaskExecutorServiceCrc = AZ_CRC_CE("TaskExecutorService");
namespace AZ
{
void TaskGraphSystemComponent::Activate()
{
AZ_Assert(m_taskExecutor == nullptr, "Error multiple activation of the TaskGraphSystemComponent");
if (Interface<TaskGraphActiveInterface>::Get() == nullptr)
{
Interface<TaskGraphActiveInterface>::Register(this);
m_taskExecutor = aznew TaskExecutor();
TaskExecutor::SetInstance(m_taskExecutor);
}
}
void TaskGraphSystemComponent::Deactivate()
{
if (&TaskExecutor::Instance() == m_taskExecutor) // check that our instance is the global instance (not always true in unit tests)
{
m_taskExecutor->SetInstance(nullptr);
}
if (m_taskExecutor)
{
azdestroy(m_taskExecutor);
m_taskExecutor = nullptr;
}
if (Interface<TaskGraphActiveInterface>::Get() == this)
{
Interface<TaskGraphActiveInterface>::Unregister(this);
}
}
void TaskGraphSystemComponent::GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(TaskExecutorServiceCrc);
}
void TaskGraphSystemComponent::GetIncompatibleServices(ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(TaskExecutorServiceCrc);
}
void TaskGraphSystemComponent::GetDependentServices([[maybe_unused]] ComponentDescriptor::DependencyArrayType& dependent)
{
}
void TaskGraphSystemComponent::Reflect(ReflectContext* context)
{
if (SerializeContext* serializeContext = azrtti_cast<SerializeContext*>(context))
{
serializeContext->Class<TaskGraphSystemComponent, AZ::Component>()
->Version(1)
;
if (AZ::EditContext* ec = serializeContext->GetEditContext())
{
ec->Class<TaskGraphSystemComponent>
("TaskGraph", "System component to create the default executor")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Engine")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System"))
;
}
}
}
bool TaskGraphSystemComponent::IsTaskGraphActive() const
{
return cl_activateTaskGraph;
}
} // namespace AZ

@ -0,0 +1,47 @@
/*
* 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/Component/Component.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/Task/TaskExecutor.h>
#include <AzCore/Task/TaskGraph.h>
namespace AZ
{
class TaskGraphSystemComponent
: public Component
, public TaskGraphActiveInterface
{
public:
AZ_COMPONENT(AZ::TaskGraphSystemComponent, "{5D56B829-1FEB-43D5-A0BD-E33C0497EFE2}")
TaskGraphSystemComponent() = default;
// Implement TaskGraphActiveInterface
bool IsTaskGraphActive() const override;
private:
//////////////////////////////////////////////////////////////////////////
// Component base
void Activate() override;
void Deactivate() override;
//////////////////////////////////////////////////////////////////////////
/// \ref ComponentDescriptor::GetProvidedServices
static void GetProvidedServices(ComponentDescriptor::DependencyArrayType& provided);
/// \ref ComponentDescriptor::GetIncompatibleServices
static void GetIncompatibleServices(ComponentDescriptor::DependencyArrayType& incompatible);
/// \ref ComponentDescriptor::GetDependentServices
static void GetDependentServices(ComponentDescriptor::DependencyArrayType& dependent);
/// \red ComponentDescriptor::Reflect
static void Reflect(ReflectContext* reflection);
AZ::TaskExecutor* m_taskExecutor = nullptr;
};
}

@ -13,14 +13,26 @@
#include <AzCore/RTTI/RTTI.h> #include <AzCore/RTTI/RTTI.h>
#include <AzCore/RTTI/TypeSafeIntegral.h> #include <AzCore/RTTI/TypeSafeIntegral.h>
#include <AzCore/std/time.h> #include <AzCore/std/time.h>
#include <AzCore/std/chrono/chrono.h>
namespace AZ namespace AZ
{ {
//! This is a strong typedef for representing a millisecond value since application start. //! This is a strong typedef for representing a millisecond value since application start.
AZ_TYPE_SAFE_INTEGRAL(TimeMs, int64_t); AZ_TYPE_SAFE_INTEGRAL(TimeMs, int64_t);
//! This is a strong typedef for representing a microsecond value since application start.
//! Using int64_t as the underlying type, this is good to represent approximately 292,471 years
AZ_TYPE_SAFE_INTEGRAL(TimeUs, int64_t);
//! @class ITime //! @class ITime
//! @brief This is an AZ::Interface<> for managing time related operations. //! @brief This is an AZ::Interface<> for managing time related operations.
//! AZ::ITime and associated types may not operate in realtime. These abstractions are to allow our application
//! simulation to operate both slower and faster than realtime in a well defined and user controllable manner
//! The rate at which time passes for AZ::ITime is controlled by the cvar t_scale
//! t_scale == 0 means simulation time should halt
//! 0 < t_scale < 1 will cause time to pass slower than realtime, with t_scale 0.1 being roughly 1/10th realtime
//! t_scale == 1 will cause time to pass at roughly realtime
//! t_scale > 1 will cause time to pass faster than normal, with t_scale 10 being roughly 10x realtime
class ITime class ITime
{ {
public: public:
@ -33,6 +45,10 @@ namespace AZ
//! @return the number of milliseconds that have elapsed since application start //! @return the number of milliseconds that have elapsed since application start
virtual TimeMs GetElapsedTimeMs() const = 0; virtual TimeMs GetElapsedTimeMs() const = 0;
//! Returns the number of microseconds since application start.
//! @return the number of microseconds that have elapsed since application start
virtual TimeUs GetElapsedTimeUs() const = 0;
AZ_DISABLE_COPY_MOVE(ITime); AZ_DISABLE_COPY_MOVE(ITime);
}; };
@ -51,6 +67,53 @@ namespace AZ
{ {
return AZ::Interface<ITime>::Get()->GetElapsedTimeMs(); return AZ::Interface<ITime>::Get()->GetElapsedTimeMs();
} }
}
//! This is a simple convenience wrapper
inline TimeUs GetElapsedTimeUs()
{
return AZ::Interface<ITime>::Get()->GetElapsedTimeUs();
}
//! Converts from milliseconds to microseconds
inline TimeUs TimeMsToUs(TimeMs value)
{
return static_cast<TimeUs>(value * static_cast<TimeMs>(1000));
}
//! Converts from microseconds to milliseconds
inline TimeMs TimeUsToMs(TimeUs value)
{
return static_cast<TimeMs>(value / static_cast<TimeUs>(1000));
}
//! Converts from milliseconds to seconds
inline float TimeMsToSeconds(TimeMs value)
{
return static_cast<float>(value) / 1000.0f;
}
//! Converts from microseconds to seconds
inline float TimeUsToSeconds(TimeUs value)
{
return static_cast<float>(value) / 1000000.0f;
}
//! Converts from milliseconds to AZStd::chrono::time_point
inline auto TimeMsToChrono(TimeMs value)
{
auto epoch = AZStd::chrono::time_point<AZStd::chrono::high_resolution_clock>();
auto chronoValue = AZStd::chrono::milliseconds(aznumeric_cast<int64_t>(value));
return epoch + chronoValue;
}
//! Converts from microseconds to AZStd::chrono::time_point
inline auto TimeUsToChrono(TimeUs value)
{
auto epoch = AZStd::chrono::time_point<AZStd::chrono::high_resolution_clock>();
auto chronoValue = AZStd::chrono::microseconds(aznumeric_cast<int64_t>(value));
return epoch + chronoValue;
}
} // namespace AZ
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(AZ::TimeMs); AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(AZ::TimeMs);
AZ_TYPE_SAFE_INTEGRAL_SERIALIZEBINDING(AZ::TimeUs);

@ -35,7 +35,7 @@ namespace AZ
TimeSystemComponent::TimeSystemComponent() TimeSystemComponent::TimeSystemComponent()
{ {
m_lastInvokedTimeMs = static_cast<TimeMs>(AZStd::GetTimeNowMicroSecond() / 1000); m_lastInvokedTimeUs = static_cast<TimeUs>(AZStd::GetTimeNowMicroSecond());
AZ::Interface<ITime>::Register(this); AZ::Interface<ITime>::Register(this);
ITimeRequestBus::Handler::BusConnect(); ITimeRequestBus::Handler::BusConnect();
} }
@ -58,18 +58,23 @@ namespace AZ
TimeMs TimeSystemComponent::GetElapsedTimeMs() const TimeMs TimeSystemComponent::GetElapsedTimeMs() const
{ {
TimeMs currentTime = static_cast<TimeMs>(AZStd::GetTimeNowMicroSecond() / 1000); return TimeUsToMs(GetElapsedTimeUs());
TimeMs deltaTime = currentTime - m_lastInvokedTimeMs; }
TimeUs TimeSystemComponent::GetElapsedTimeUs() const
{
TimeUs currentTime = static_cast<TimeUs>(AZStd::GetTimeNowMicroSecond());
TimeUs deltaTime = currentTime - m_lastInvokedTimeUs;
if (t_scale != 1.0f) if (t_scale != 1.0f)
{ {
float floatDelta = static_cast<float>(deltaTime) * t_scale; float floatDelta = static_cast<float>(deltaTime) * t_scale;
deltaTime = static_cast<TimeMs>(static_cast<int64_t>(floatDelta)); deltaTime = static_cast<TimeUs>(static_cast<int64_t>(floatDelta));
} }
m_accumulatedTimeMs += deltaTime; m_accumulatedTimeUs += deltaTime;
m_lastInvokedTimeMs = currentTime; m_lastInvokedTimeUs = currentTime;
return m_accumulatedTimeMs; return m_accumulatedTimeUs;
} }
} }

@ -39,11 +39,12 @@ namespace AZ
//! ITime overrides. //! ITime overrides.
//! @{ //! @{
TimeMs GetElapsedTimeMs() const override; TimeMs GetElapsedTimeMs() const override;
TimeUs GetElapsedTimeUs() const override;
//! @} //! @}
private: private:
mutable TimeMs m_lastInvokedTimeMs = TimeMs{0}; mutable TimeUs m_lastInvokedTimeUs = TimeUs{0};
mutable TimeMs m_accumulatedTimeMs = TimeMs{0}; mutable TimeUs m_accumulatedTimeUs = TimeUs{0};
}; };
} }

@ -633,6 +633,8 @@ set(FILES
Task/TaskGraph.cpp Task/TaskGraph.cpp
Task/TaskGraph.h Task/TaskGraph.h
Task/TaskGraph.inl Task/TaskGraph.inl
Task/TaskGraphSystemComponent.h
Task/TaskGraphSystemComponent.cpp
Threading/ThreadSafeDeque.h Threading/ThreadSafeDeque.h
Threading/ThreadSafeDeque.inl Threading/ThreadSafeDeque.inl
Threading/ThreadSafeObject.h Threading/ThreadSafeObject.h

@ -87,12 +87,6 @@ namespace AZStd
// construct/copy/destroy: // construct/copy/destroy:
thread(); thread();
/**
* \note thread_desc is AZStd extension.
*/
template <class F>
explicit thread(F&& f, const thread_desc* desc = 0);
~thread(); ~thread();
thread(thread&& rhs) thread(thread&& rhs)
@ -108,6 +102,15 @@ namespace AZStd
return *this; return *this;
} }
template<class F, class... Args, typename = AZStd::enable_if_t<!AZStd::is_convertible_v<AZStd::decay_t<F>, thread_desc>>>
explicit thread(F&& f, Args&&... args);
/**
* \note thread_desc is AZStd extension.
*/
template<class F, class... Args>
thread(const thread_desc& desc, F&& f, Args&&... args);
// Till we fully have RVALUES // Till we fully have RVALUES
template <class F> template <class F>
explicit thread(Internal::thread_move_t<F> f); explicit thread(Internal::thread_move_t<F> f);
@ -138,8 +141,8 @@ namespace AZStd
//thread(AZStd::delegate<void ()> d,const thread_desc* desc = 0); //thread(AZStd::delegate<void ()> d,const thread_desc* desc = 0);
private: private:
thread(thread&); thread(const thread&) = delete;
thread& operator=(thread&); thread& operator=(const thread&) = delete;
native_thread_data_type m_thread; native_thread_data_type m_thread;
}; };

@ -368,6 +368,21 @@ namespace Platform
return access(fileName, F_OK) == 0; return access(fileName, F_OK) == 0;
} }
} }
bool IsDirectory(const char* filePath)
{
if (AZ::Android::Utils::IsApkPath(filePath))
{
return AZ::Android::APKFileHandler::IsDirectory(AZ::Android::Utils::StripApkPrefix(filePath).c_str());
}
struct stat result;
if (stat(filePath, &result) == 0)
{
return S_ISDIR(result.st_mode);
}
return false;
}
} // namespace AZ::IO::Platform } // namespace AZ::IO::Platform
} // namespace AZ::IO } // namespace AZ::IO

@ -38,7 +38,7 @@ namespace AZ
{ {
return false; return false;
} }
for (size_t i = tracerPidOffset; i < numRead; ++i) for (size_t i = tracerPidOffset + tracerPidString.length(); i < numRead; ++i)
{ {
if (!::isspace(processStatusView[i])) if (!::isspace(processStatusView[i]))
{ {

@ -10,6 +10,8 @@
#include <unistd.h> #include <unistd.h>
#include <sched.h> #include <sched.h>
#include <AzCore/std/tuple.h>
namespace AZStd namespace AZStd
{ {
namespace Internal namespace Internal
@ -22,12 +24,20 @@ namespace AZStd
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// thread // thread
template <class F> template<class F, class... Args, typename>
inline thread::thread(F&& f, const thread_desc* desc) thread::thread(F&& f, Args&&... args)
: thread(thread_desc{}, AZStd::forward<F>(f), AZStd::forward<Args>(args)...)
{}
template<class F, class... Args>
thread::thread(const thread_desc& desc, F&& f, Args&&... args)
{ {
Internal::thread_info* ti = Internal::create_thread_info(AZStd::forward<F>(f)); auto threadfunc = [fn = AZStd::forward<F>(f), argsTuple = AZStd::make_tuple(AZStd::forward<Args>(args)...)]() mutable -> void
ti->m_name = desc ? desc->m_name : nullptr; {
m_thread = Internal::create_thread(desc, ti); AZStd::apply(AZStd::move(fn), AZStd::move(argsTuple));
};
Internal::thread_info* ti = Internal::create_thread_info(AZStd::move(threadfunc));
m_thread = Internal::create_thread(&desc, ti);
} }
inline bool thread::joinable() const inline bool thread::joinable() const

@ -249,6 +249,16 @@ namespace Platform
{ {
return access(fileName, F_OK) == 0; return access(fileName, F_OK) == 0;
} }
bool IsDirectory(const char* filePath)
{
struct stat result;
if (stat(filePath, &result) == 0)
{
return S_ISDIR(result.st_mode);
}
return false;
}
} }
} // namespace AZ::IO } // namespace AZ::IO

@ -10,6 +10,7 @@
#include <AzCore/IO/FileIO.h> #include <AzCore/IO/FileIO.h>
#include <AzCore/IO/FileIOEventBus.h> #include <AzCore/IO/FileIOEventBus.h>
#include <AzCore/Casting/numeric_cast.h> #include <AzCore/Casting/numeric_cast.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/PlatformIncl.h> #include <AzCore/PlatformIncl.h>
#include <AzCore/Utils/Utils.h> #include <AzCore/Utils/Utils.h>
@ -18,7 +19,7 @@
namespace AZ::IO namespace AZ::IO
{ {
using FixedMaxPathWString = AZStd::fixed_wstring<MaxPathLength>;
namespace namespace
{ {
//========================================================================= //=========================================================================
@ -28,16 +29,9 @@ namespace
//========================================================================= //=========================================================================
DWORD GetAttributes(const char* fileName) DWORD GetAttributes(const char* fileName)
{ {
wchar_t fileNameW[AZ_MAX_PATH_LEN]; FixedMaxPathWString fileNameW;
size_t numCharsConverted; AZStd::to_wstring(fileNameW, fileName);
if (mbstowcs_s(&numCharsConverted, fileNameW, fileName, AZ_ARRAY_SIZE(fileNameW) - 1) == 0) return GetFileAttributesW(fileNameW.c_str());
{
return GetFileAttributesW(fileNameW);
}
else
{
return INVALID_FILE_ATTRIBUTES;
}
} }
//========================================================================= //=========================================================================
@ -47,16 +41,9 @@ namespace
//========================================================================= //=========================================================================
BOOL SetAttributes(const char* fileName, DWORD fileAttributes) BOOL SetAttributes(const char* fileName, DWORD fileAttributes)
{ {
wchar_t fileNameW[AZ_MAX_PATH_LEN]; FixedMaxPathWString fileNameW;
size_t numCharsConverted; AZStd::to_wstring(fileNameW, fileName);
if (mbstowcs_s(&numCharsConverted, fileNameW, fileName, AZ_ARRAY_SIZE(fileNameW) - 1) == 0) return SetFileAttributesW(fileNameW.c_str(), fileAttributes);
{
return SetFileAttributesW(fileNameW, fileAttributes);
}
else
{
return FALSE;
}
} }
//========================================================================= //=========================================================================
@ -68,9 +55,9 @@ namespace
// * GetLastError() on Windows-like platforms // * GetLastError() on Windows-like platforms
// * errno on Unix platforms // * errno on Unix platforms
//========================================================================= //=========================================================================
bool CreateDirRecursive(wchar_t* dirPath) bool CreateDirRecursive(AZ::IO::FixedMaxPathWString& dirPath)
{ {
if (CreateDirectoryW(dirPath, nullptr)) if (CreateDirectoryW(dirPath.c_str(), nullptr))
{ {
return true; // Created without error return true; // Created without error
} }
@ -78,28 +65,24 @@ namespace
if (error == ERROR_PATH_NOT_FOUND) if (error == ERROR_PATH_NOT_FOUND)
{ {
// try to create our parent hierarchy // try to create our parent hierarchy
for (size_t i = wcslen(dirPath); i > 0; --i) if (size_t i = dirPath.find_last_of(LR"(/\)"); i != FixedMaxPathWString::npos)
{ {
if (dirPath[i] == L'/' || dirPath[i] == L'\\') wchar_t delimiter = dirPath[i];
dirPath[i] = 0; // null-terminate at the previous slash
const bool ret = CreateDirRecursive(dirPath);
dirPath[i] = delimiter; // restore slash
if (ret)
{ {
wchar_t delimiter = dirPath[i]; // now that our parent is created, try to create again
dirPath[i] = 0; // null-terminate at the previous slash return CreateDirectoryW(dirPath.c_str(), nullptr) != 0;
bool ret = CreateDirRecursive(dirPath);
dirPath[i] = delimiter; // restore slash
if (ret)
{
// now that our parent is created, try to create again
return CreateDirectoryW(dirPath, nullptr) != 0;
}
return false;
} }
} }
// if we reach here then there was no parent folder to create, so we failed for other reasons // if we reach here then there was no parent folder to create, so we failed for other reasons
} }
else if (error == ERROR_ALREADY_EXISTS) else if (error == ERROR_ALREADY_EXISTS)
{ {
DWORD attributes = GetFileAttributesW(dirPath); DWORD attributes = GetFileAttributesW(dirPath.c_str());
return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
} }
return false; return false;
} }
@ -152,13 +135,10 @@ bool SystemFile::PlatformOpen(int mode, int platformFlags)
CreatePath(m_fileName.c_str()); CreatePath(m_fileName.c_str());
} }
wchar_t fileNameW[AZ_MAX_PATH_LEN]; AZ::IO::FixedMaxPathWString fileNameW;
size_t numCharsConverted; AZStd::to_wstring(fileNameW, m_fileName);
m_handle = INVALID_HANDLE_VALUE; m_handle = INVALID_HANDLE_VALUE;
if (mbstowcs_s(&numCharsConverted, fileNameW, m_fileName.c_str(), AZ_ARRAY_SIZE(fileNameW) - 1) == 0) m_handle = CreateFileW(fileNameW.c_str(), dwDesiredAccess, dwShareMode, 0, dwCreationDisposition, dwFlagsAndAttributes, 0);
{
m_handle = CreateFileW(fileNameW, dwDesiredAccess, dwShareMode, 0, dwCreationDisposition, dwFlagsAndAttributes, 0);
}
if (m_handle == INVALID_HANDLE_VALUE) if (m_handle == INVALID_HANDLE_VALUE)
{ {
@ -350,6 +330,12 @@ namespace Platform
return GetAttributes(fileName) != INVALID_FILE_ATTRIBUTES; return GetAttributes(fileName) != INVALID_FILE_ATTRIBUTES;
} }
bool IsDirectory(const char* filePath)
{
DWORD attributes = GetAttributes(filePath);
return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
}
void FindFiles(const char* filter, SystemFile::FindFileCB cb) void FindFiles(const char* filter, SystemFile::FindFileCB cb)
{ {
@ -357,35 +343,26 @@ namespace Platform
HANDLE hFile; HANDLE hFile;
int lastError; int lastError;
wchar_t filterW[AZ_MAX_PATH_LEN]; AZ::IO::FixedMaxPathWString filterW;
size_t numCharsConverted; AZStd::to_wstring(filterW, filter);
hFile = INVALID_HANDLE_VALUE; hFile = INVALID_HANDLE_VALUE;
if (mbstowcs_s(&numCharsConverted, filterW, filter, AZ_ARRAY_SIZE(filterW) - 1) == 0) hFile = FindFirstFileW(filterW.c_str(), &fd);
{
hFile = FindFirstFile(filterW, &fd);
}
if (hFile != INVALID_HANDLE_VALUE) if (hFile != INVALID_HANDLE_VALUE)
{ {
const char* fileName; const char* fileName;
char fileNameA[AZ_MAX_PATH_LEN]; AZ::IO::FixedMaxPathString fileNameUtf8;
fileName = NULL; AZStd::to_string(fileNameUtf8, fd.cFileName);
if (wcstombs_s(&numCharsConverted, fileNameA, fd.cFileName, AZ_ARRAY_SIZE(fileNameA) - 1) == 0) fileName = fileNameUtf8.c_str();
{
fileName = fileNameA;
}
cb(fileName, (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0); cb(fileName, (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0);
// List all the other files in the directory. // List all the other files in the directory.
while (FindNextFileW(hFile, &fd) != 0) while (FindNextFileW(hFile, &fd) != 0)
{ {
fileName = NULL; AZStd::to_string(fileNameUtf8, fd.cFileName);
if (wcstombs_s(&numCharsConverted, fileNameA, fd.cFileName, AZ_ARRAY_SIZE(fileNameA) - 1) == 0) fileName = fileNameUtf8.c_str();
{
fileName = fileNameA;
}
cb(fileName, (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0); cb(fileName, (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0);
} }
@ -411,12 +388,9 @@ namespace Platform
{ {
HANDLE handle = nullptr; HANDLE handle = nullptr;
wchar_t fileNameW[AZ_MAX_PATH_LEN]; AZ::IO::FixedMaxPathWString fileNameW;
size_t numCharsConverted; AZStd::to_wstring(fileNameW, fileName);
if (mbstowcs_s(&numCharsConverted, fileNameW, fileName, AZ_ARRAY_SIZE(fileNameW) - 1) == 0) handle = CreateFileW(fileNameW.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
{
handle = CreateFileW(fileNameW, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
}
if (handle == INVALID_HANDLE_VALUE) if (handle == INVALID_HANDLE_VALUE)
{ {
@ -448,12 +422,9 @@ namespace Platform
WIN32_FILE_ATTRIBUTE_DATA data = { 0 }; WIN32_FILE_ATTRIBUTE_DATA data = { 0 };
BOOL result = FALSE; BOOL result = FALSE;
wchar_t fileNameW[AZ_MAX_PATH_LEN]; AZ::IO::FixedMaxPathWString fileNameW;
size_t numCharsConverted; AZStd::to_wstring(fileNameW, fileName);
if (mbstowcs_s(&numCharsConverted, fileNameW, fileName, AZ_ARRAY_SIZE(fileNameW) - 1) == 0) result = GetFileAttributesExW(fileNameW.c_str(), GetFileExInfoStandard, &data);
{
result = GetFileAttributesExW(fileNameW, GetFileExInfoStandard, &data);
}
if (result) if (result)
{ {
@ -473,18 +444,11 @@ namespace Platform
bool Delete(const char* fileName) bool Delete(const char* fileName)
{ {
wchar_t fileNameW[AZ_MAX_PATH_LEN]; AZ::IO::FixedMaxPathWString fileNameW;
size_t numCharsConverted; AZStd::to_wstring(fileNameW, fileName);
if (mbstowcs_s(&numCharsConverted, fileNameW, fileName, AZ_ARRAY_SIZE(fileNameW) - 1) == 0) if (DeleteFileW(fileNameW.c_str()) == 0)
{
if (DeleteFileW(fileNameW) == 0)
{
EBUS_EVENT(FileIOEventBus, OnError, nullptr, fileName, (int)GetLastError());
return false;
}
}
else
{ {
EBUS_EVENT(FileIOEventBus, OnError, nullptr, fileName, (int)GetLastError());
return false; return false;
} }
@ -493,20 +457,13 @@ namespace Platform
bool Rename(const char* sourceFileName, const char* targetFileName, bool overwrite) bool Rename(const char* sourceFileName, const char* targetFileName, bool overwrite)
{ {
wchar_t sourceFileNameW[AZ_MAX_PATH_LEN]; AZ::IO::FixedMaxPathWString sourceFileNameW;
wchar_t targetFileNameW[AZ_MAX_PATH_LEN]; AZStd::to_wstring(sourceFileNameW, sourceFileName);
size_t numCharsConverted; AZ::IO::FixedMaxPathWString targetFileNameW;
if (mbstowcs_s(&numCharsConverted, sourceFileNameW, sourceFileName, AZ_ARRAY_SIZE(sourceFileNameW) - 1) == 0 && AZStd::to_wstring(targetFileNameW, targetFileName);
mbstowcs_s(&numCharsConverted, targetFileNameW, targetFileName, AZ_ARRAY_SIZE(targetFileNameW) - 1) == 0) if (MoveFileExW(sourceFileNameW.c_str(), targetFileNameW.c_str(), overwrite ? MOVEFILE_REPLACE_EXISTING : 0) == 0)
{
if (MoveFileExW(sourceFileNameW, targetFileNameW, overwrite ? MOVEFILE_REPLACE_EXISTING : 0) == 0)
{
EBUS_EVENT(FileIOEventBus, OnError, nullptr, sourceFileName, (int)GetLastError());
return false;
}
}
else
{ {
EBUS_EVENT(FileIOEventBus, OnError, nullptr, sourceFileName, (int)GetLastError());
return false; return false;
} }
@ -543,17 +500,14 @@ namespace Platform
{ {
if (dirName) if (dirName)
{ {
wchar_t dirPath[AZ_MAX_PATH_LEN]; AZ::IO::FixedMaxPathWString dirNameW;
size_t numCharsConverted; AZStd::to_wstring(dirNameW, dirName);
if (mbstowcs_s(&numCharsConverted, dirPath, dirName, AZ_ARRAY_SIZE(dirPath) - 1) == 0) bool success = CreateDirRecursive(dirNameW);
if (!success)
{ {
bool success = CreateDirRecursive(dirPath); EBUS_EVENT(FileIOEventBus, OnError, nullptr, dirName, (int)GetLastError());
if (!success)
{
EBUS_EVENT(FileIOEventBus, OnError, nullptr, dirName, (int)GetLastError());
}
return success;
} }
return success;
} }
return false; return false;
} }
@ -562,12 +516,9 @@ namespace Platform
{ {
if (dirName) if (dirName)
{ {
wchar_t dirNameW[AZ_MAX_PATH_LEN]; AZ::IO::FixedMaxPathWString dirNameW;
size_t numCharsConverted; AZStd::to_wstring(dirNameW, dirName);
if (mbstowcs_s(&numCharsConverted, dirNameW, dirName, AZ_ARRAY_SIZE(dirNameW) - 1) == 0) return RemoveDirectory(dirNameW.c_str()) != 0;
{
return RemoveDirectory(dirNameW) != 0;
}
} }
return false; return false;

@ -18,6 +18,8 @@ extern "C"
AZ_DLL_IMPORT unsigned long __stdcall GetCurrentThreadId(void); AZ_DLL_IMPORT unsigned long __stdcall GetCurrentThreadId(void);
} }
#include <AzCore/std/tuple.h>
namespace AZStd namespace AZStd
{ {
namespace Internal namespace Internal
@ -30,11 +32,20 @@ namespace AZStd
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// thread // thread
template <class F> template<class F, class... Args, typename>
inline thread::thread(F&& f, const thread_desc* desc) thread::thread(F&& f, Args&&... args)
: thread(thread_desc{}, AZStd::forward<F>(f), AZStd::forward<Args>(args)...)
{}
template<class F, class... Args>
thread::thread(const thread_desc& desc, F&& f, Args&&... args)
{ {
Internal::thread_info* ti = Internal::create_thread_info(AZStd::forward<F>(f)); auto threadfunc = [fn = AZStd::forward<F>(f), argsTuple = AZStd::make_tuple(AZStd::forward<Args>(args)...)]() mutable -> void
m_thread.m_handle = Internal::create_thread(desc, ti, &m_thread.m_id); {
AZStd::apply(AZStd::move(fn), AZStd::move(argsTuple));
};
Internal::thread_info* ti = Internal::create_thread_info(AZStd::move(threadfunc));
m_thread.m_handle = Internal::create_thread(&desc, ti, &m_thread.m_id);
} }
inline bool thread::joinable() const inline bool thread::joinable() const

@ -195,18 +195,18 @@ namespace UnitTest
void test_thread_id_for_running_thread_is_not_default_constructed_id() void test_thread_id_for_running_thread_is_not_default_constructed_id()
{ {
const thread_desc* desc = m_numThreadDesc ? &m_desc[0] : nullptr; const thread_desc desc = m_numThreadDesc ? m_desc[0] : thread_desc{};
AZStd::thread t(AZStd::bind(&Parallel_Thread::do_nothing, this), desc); AZStd::thread t(desc, AZStd::bind(&Parallel_Thread::do_nothing, this));
AZ_TEST_ASSERT(t.get_id() != AZStd::thread::id()); AZ_TEST_ASSERT(t.get_id() != AZStd::thread::id());
t.join(); t.join();
} }
void test_different_threads_have_different_ids() void test_different_threads_have_different_ids()
{ {
const thread_desc* desc1 = m_numThreadDesc ? &m_desc[0] : nullptr; const thread_desc desc1 = m_numThreadDesc ? m_desc[0] : thread_desc{};
const thread_desc* desc2 = m_numThreadDesc ? &m_desc[1] : nullptr; const thread_desc desc2 = m_numThreadDesc ? m_desc[1] : thread_desc{};
AZStd::thread t(AZStd::bind(&Parallel_Thread::do_nothing, this), desc1); AZStd::thread t(desc1, AZStd::bind(&Parallel_Thread::do_nothing, this));
AZStd::thread t2(AZStd::bind(&Parallel_Thread::do_nothing, this), desc2); AZStd::thread t2(desc2, AZStd::bind(&Parallel_Thread::do_nothing, this));
AZ_TEST_ASSERT(t.get_id() != t2.get_id()); AZ_TEST_ASSERT(t.get_id() != t2.get_id());
t.join(); t.join();
t2.join(); t2.join();
@ -214,13 +214,13 @@ namespace UnitTest
void test_thread_ids_have_a_total_order() void test_thread_ids_have_a_total_order()
{ {
const thread_desc* desc1 = m_numThreadDesc ? &m_desc[0] : nullptr; const thread_desc desc1 = m_numThreadDesc ? m_desc[0] : thread_desc{};
const thread_desc* desc2 = m_numThreadDesc ? &m_desc[1] : nullptr; const thread_desc desc2 = m_numThreadDesc ? m_desc[1] : thread_desc{};
const thread_desc* desc3 = m_numThreadDesc ? &m_desc[2] : nullptr; const thread_desc desc3 = m_numThreadDesc ? m_desc[2] : thread_desc{};
AZStd::thread t(AZStd::bind(&Parallel_Thread::do_nothing, this), desc1); AZStd::thread t(desc1, AZStd::bind(&Parallel_Thread::do_nothing, this));
AZStd::thread t2(AZStd::bind(&Parallel_Thread::do_nothing, this), desc2); AZStd::thread t2(desc2, AZStd::bind(&Parallel_Thread::do_nothing, this));
AZStd::thread t3(AZStd::bind(&Parallel_Thread::do_nothing, this), desc3); AZStd::thread t3(desc3, AZStd::bind(&Parallel_Thread::do_nothing, this));
AZ_TEST_ASSERT(t.get_id() != t2.get_id()); AZ_TEST_ASSERT(t.get_id() != t2.get_id());
AZ_TEST_ASSERT(t.get_id() != t3.get_id()); AZ_TEST_ASSERT(t.get_id() != t3.get_id());
AZ_TEST_ASSERT(t2.get_id() != t3.get_id()); AZ_TEST_ASSERT(t2.get_id() != t3.get_id());
@ -313,10 +313,10 @@ namespace UnitTest
void test_thread_id_of_running_thread_returned_by_this_thread_get_id() void test_thread_id_of_running_thread_returned_by_this_thread_get_id()
{ {
const thread_desc* desc1 = m_numThreadDesc ? &m_desc[0] : nullptr; const thread_desc desc1 = m_numThreadDesc ? m_desc[0] : thread_desc{};
AZStd::thread::id id; AZStd::thread::id id;
AZStd::thread t(AZStd::bind(&Parallel_Thread::get_thread_id, this, &id), desc1); AZStd::thread t(desc1, AZStd::bind(&Parallel_Thread::get_thread_id, this, &id));
AZStd::thread::id t_id = t.get_id(); AZStd::thread::id t_id = t.get_id();
t.join(); t.join();
AZ_TEST_ASSERT(id == t_id); AZ_TEST_ASSERT(id == t_id);
@ -366,10 +366,10 @@ namespace UnitTest
void test_move_on_construction() void test_move_on_construction()
{ {
const thread_desc* desc1 = m_numThreadDesc ? &m_desc[0] : nullptr; const thread_desc desc1 = m_numThreadDesc ? m_desc[0] : thread_desc{};
AZStd::thread::id the_id; AZStd::thread::id the_id;
AZStd::thread x; AZStd::thread x;
x = AZStd::thread(AZStd::bind(&Parallel_Thread::do_nothing_id, this, &the_id), desc1); x = AZStd::thread(desc1, AZStd::bind(&Parallel_Thread::do_nothing_id, this, &the_id));
AZStd::thread::id x_id = x.get_id(); AZStd::thread::id x_id = x.get_id();
x.join(); x.join();
AZ_TEST_ASSERT(the_id == x_id); AZ_TEST_ASSERT(the_id == x_id);
@ -377,8 +377,8 @@ namespace UnitTest
AZStd::thread make_thread(AZStd::thread::id* the_id) AZStd::thread make_thread(AZStd::thread::id* the_id)
{ {
const thread_desc* desc1 = m_numThreadDesc ? &m_desc[0] : nullptr; const thread_desc desc1 = m_numThreadDesc ? m_desc[0] : thread_desc{};
return AZStd::thread(AZStd::bind(&Parallel_Thread::do_nothing_id, this, the_id), desc1); return AZStd::thread(desc1, AZStd::bind(&Parallel_Thread::do_nothing_id, this, the_id));
} }
void test_move_from_function_return() void test_move_from_function_return()
@ -430,9 +430,9 @@ namespace UnitTest
void do_test_creation() void do_test_creation()
{ {
const thread_desc* desc1 = m_numThreadDesc ? &m_desc[0] : nullptr; const thread_desc desc1 = m_numThreadDesc ? m_desc[0] : thread_desc{};
m_data = 0; m_data = 0;
AZStd::thread t(AZStd::bind(&Parallel_Thread::simple_thread, this), desc1); AZStd::thread t(desc1, AZStd::bind(&Parallel_Thread::simple_thread, this));
t.join(); t.join();
AZ_TEST_ASSERT(m_data == 999); AZ_TEST_ASSERT(m_data == 999);
} }
@ -445,9 +445,9 @@ namespace UnitTest
void do_test_id_comparison() void do_test_id_comparison()
{ {
const thread_desc* desc1 = m_numThreadDesc ? &m_desc[0] : nullptr; const thread_desc desc1 = m_numThreadDesc ? m_desc[0] : thread_desc{};
AZStd::thread::id self = this_thread::get_id(); AZStd::thread::id self = this_thread::get_id();
AZStd::thread thrd(AZStd::bind(&Parallel_Thread::comparison_thread, this, self), desc1); AZStd::thread thrd(desc1, AZStd::bind(&Parallel_Thread::comparison_thread, this, self));
thrd.join(); thrd.join();
} }
@ -476,10 +476,10 @@ namespace UnitTest
void do_test_creation_through_reference_wrapper() void do_test_creation_through_reference_wrapper()
{ {
const thread_desc* desc1 = m_numThreadDesc ? &m_desc[0] : nullptr; const thread_desc desc1 = m_numThreadDesc ? m_desc[0] : thread_desc{};
non_copyable_functor f; non_copyable_functor f;
AZStd::thread thrd(AZStd::ref(f), desc1); AZStd::thread thrd(desc1, AZStd::ref(f));
thrd.join(); thrd.join();
AZ_TEST_ASSERT(f.value == 999); AZ_TEST_ASSERT(f.value == 999);
} }
@ -491,10 +491,10 @@ namespace UnitTest
void test_swap() void test_swap()
{ {
const thread_desc* desc1 = m_numThreadDesc ? &m_desc[0] : nullptr; const thread_desc desc1 = m_numThreadDesc ? m_desc[0] : thread_desc{};
const thread_desc* desc2 = m_numThreadDesc ? &m_desc[1] : nullptr; const thread_desc desc2 = m_numThreadDesc ? m_desc[1] : thread_desc{};
AZStd::thread t(AZStd::bind(&Parallel_Thread::simple_thread, this), desc1); AZStd::thread t(desc1, AZStd::bind(&Parallel_Thread::simple_thread, this));
AZStd::thread t2(AZStd::bind(&Parallel_Thread::simple_thread, this), desc2); AZStd::thread t2(desc2, AZStd::bind(&Parallel_Thread::simple_thread, this));
AZStd::thread::id id1 = t.get_id(); AZStd::thread::id id1 = t.get_id();
AZStd::thread::id id2 = t2.get_id(); AZStd::thread::id id2 = t2.get_id();
@ -512,7 +512,7 @@ namespace UnitTest
void run() void run()
{ {
const thread_desc* desc1 = m_numThreadDesc ? &m_desc[0] : nullptr; const thread_desc desc1 = m_numThreadDesc ? m_desc[0] : thread_desc{};
// We need to have at least one processor // We need to have at least one processor
AZ_TEST_ASSERT(AZStd::thread::hardware_concurrency() >= 1); AZ_TEST_ASSERT(AZStd::thread::hardware_concurrency() >= 1);
@ -520,18 +520,18 @@ namespace UnitTest
// Create thread to increment data till we need to // Create thread to increment data till we need to
m_data = 0; m_data = 0;
m_dataMax = 10; m_dataMax = 10;
AZStd::thread tr(AZStd::bind(&Parallel_Thread::increment_data, this), desc1); AZStd::thread tr(desc1, AZStd::bind(&Parallel_Thread::increment_data, this));
tr.join(); tr.join();
AZ_TEST_ASSERT(m_data == m_dataMax); AZ_TEST_ASSERT(m_data == m_dataMax);
m_data = 0; m_data = 0;
AZStd::thread trDel(make_delegate(this, &Parallel_Thread::increment_data), desc1); AZStd::thread trDel(desc1, make_delegate(this, &Parallel_Thread::increment_data));
trDel.join(); trDel.join();
AZ_TEST_ASSERT(m_data == m_dataMax); AZ_TEST_ASSERT(m_data == m_dataMax);
chrono::system_clock::time_point startTime = chrono::system_clock::now(); chrono::system_clock::time_point startTime = chrono::system_clock::now();
{ {
AZStd::thread tr1(AZStd::bind(&Parallel_Thread::sleep_thread, this, chrono::milliseconds(100)), desc1); AZStd::thread tr1(desc1, AZStd::bind(&Parallel_Thread::sleep_thread, this, chrono::milliseconds(100)));
tr1.join(); tr1.join();
} }
auto sleepTime = chrono::system_clock::now() - startTime; auto sleepTime = chrono::system_clock::now() - startTime;
@ -563,71 +563,71 @@ namespace UnitTest
{ {
MfTest x; MfTest x;
AZStd::function<void ()> func = AZStd::bind(&MfTest::f0, &x); AZStd::function<void ()> func = AZStd::bind(&MfTest::f0, &x);
AZStd::thread(func, desc1).join(); AZStd::thread(desc1, func).join();
func = AZStd::bind(&MfTest::f0, AZStd::ref(x)); func = AZStd::bind(&MfTest::f0, AZStd::ref(x));
AZStd::thread(func, desc1).join(); AZStd::thread(desc1, func).join();
func = AZStd::bind(&MfTest::g0, &x); func = AZStd::bind(&MfTest::g0, &x);
AZStd::thread(func, desc1).join(); AZStd::thread(desc1, func).join();
func = AZStd::bind(&MfTest::g0, x); func = AZStd::bind(&MfTest::g0, x);
AZStd::thread(func, desc1).join(); AZStd::thread(desc1, func).join();
func = AZStd::bind(&MfTest::g0, AZStd::ref(x)); func = AZStd::bind(&MfTest::g0, AZStd::ref(x));
AZStd::thread(func, desc1).join(); AZStd::thread(desc1, func).join();
//// 1 //// 1
//thread( AZStd::bind(&MfTest::f1, &x, 1) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::f1, &x, 1)).join();
//thread( AZStd::bind(&MfTest::f1, AZStd::ref(x), 1) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::f1, AZStd::ref(x), 1)).join();
//thread( AZStd::bind(&MfTest::g1, &x, 1) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g1, &x, 1)).join();
//thread( AZStd::bind(&MfTest::g1, x, 1) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g1, x, 1)).join();
//thread( AZStd::bind(&MfTest::g1, AZStd::ref(x), 1) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g1, AZStd::ref(x), 1)).join();
//// 2 //// 2
//thread( AZStd::bind(&MfTest::f2, &x, 1, 2) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::f2, &x, 1, 2)).join();
//thread( AZStd::bind(&MfTest::f2, AZStd::ref(x), 1, 2) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::f2, AZStd::ref(x), 1, 2)).join();
//thread( AZStd::bind(&MfTest::g2, &x, 1, 2) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g2, &x, 1, 2)).join();
//thread( AZStd::bind(&MfTest::g2, x, 1, 2) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g2, x, 1, 2)).join();
//thread( AZStd::bind(&MfTest::g2, AZStd::ref(x), 1, 2) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g2, AZStd::ref(x), 1, 2)).join();
//// 3 //// 3
//thread( AZStd::bind(&MfTest::f3, &x, 1, 2, 3) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::f3, &x, 1, 2, 3)).join();
//thread( AZStd::bind(&MfTest::f3, AZStd::ref(x), 1, 2, 3) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::f3, AZStd::ref(x), 1, 2, 3)).join();
//thread( AZStd::bind(&MfTest::g3, &x, 1, 2, 3) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g3, &x, 1, 2, 3)).join();
//thread( AZStd::bind(&MfTest::g3, x, 1, 2, 3) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g3, x, 1, 2, 3)).join();
//thread( AZStd::bind(&MfTest::g3, AZStd::ref(x), 1, 2, 3) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g3, AZStd::ref(x), 1, 2, 3)).join();
//// 4 //// 4
//thread( AZStd::bind(&MfTest::f4, &x, 1, 2, 3, 4) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::f4, &x, 1, 2, 3, 4)).join();
//thread( AZStd::bind(&MfTest::f4, AZStd::ref(x), 1, 2, 3, 4) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::f4, AZStd::ref(x), 1, 2, 3, 4)).join();
//thread( AZStd::bind(&MfTest::g4, &x, 1, 2, 3, 4) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g4, &x, 1, 2, 3, 4)).join();
//thread( AZStd::bind(&MfTest::g4, x, 1, 2, 3, 4) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g4, x, 1, 2, 3, 4)).join();
//thread( AZStd::bind(&MfTest::g4, AZStd::ref(x), 1, 2, 3, 4) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g4, AZStd::ref(x), 1, 2, 3, 4)).join();
//// 5 //// 5
//thread( AZStd::bind(&MfTest::f5, &x, 1, 2, 3, 4, 5) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::f5, &x, 1, 2, 3, 4, 5)).join();
//thread( AZStd::bind(&MfTest::f5, AZStd::ref(x), 1, 2, 3, 4, 5) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::f5, AZStd::ref(x), 1, 2, 3, 4, 5)).join();
//thread( AZStd::bind(&MfTest::g5, &x, 1, 2, 3, 4, 5) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g5, &x, 1, 2, 3, 4, 5)).join();
//thread( AZStd::bind(&MfTest::g5, x, 1, 2, 3, 4, 5) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g5, x, 1, 2, 3, 4, 5)).join();
//thread( AZStd::bind(&MfTest::g5, AZStd::ref(x), 1, 2, 3, 4, 5) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g5, AZStd::ref(x), 1, 2, 3, 4, 5)).join();
//// 6 //// 6
//thread( AZStd::bind(&MfTest::f6, &x, 1, 2, 3, 4, 5, 6) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::f6, &x, 1, 2, 3, 4, 5, 6)).join();
//thread( AZStd::bind(&MfTest::f6, AZStd::ref(x), 1, 2, 3, 4, 5, 6) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::f6, AZStd::ref(x), 1, 2, 3, 4, 5, 6)).join();
//thread( AZStd::bind(&MfTest::g6, &x, 1, 2, 3, 4, 5, 6) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g6, &x, 1, 2, 3, 4, 5, 6)).join();
//thread( AZStd::bind(&MfTest::g6, x, 1, 2, 3, 4, 5, 6) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g6, x, 1, 2, 3, 4, 5, 6)).join();
//thread( AZStd::bind(&MfTest::g6, AZStd::ref(x), 1, 2, 3, 4, 5, 6) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g6, AZStd::ref(x), 1, 2, 3, 4, 5, 6)).join();
//// 7 //// 7
//thread( AZStd::bind(&MfTest::f7, &x, 1, 2, 3, 4, 5, 6, 7), desc1).join(); //thread( AZStd::bind(desc1, &MfTest::f7, &x, 1, 2, 3, 4, 5, 6, 7)).join();
//thread( AZStd::bind(&MfTest::f7, AZStd::ref(x), 1, 2, 3, 4, 5, 6, 7), desc1).join(); //thread( AZStd::bind(desc1, &MfTest::f7, AZStd::ref(x), 1, 2, 3, 4, 5, 6, 7)).join();
//thread( AZStd::bind(&MfTest::g7, &x, 1, 2, 3, 4, 5, 6, 7), desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g7, &x, 1, 2, 3, 4, 5, 6, 7)).join();
//thread( AZStd::bind(&MfTest::g7, x, 1, 2, 3, 4, 5, 6, 7), desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g7, x, 1, 2, 3, 4, 5, 6, 7)).join();
//thread( AZStd::bind(&MfTest::g7, AZStd::ref(x), 1, 2, 3, 4, 5, 6, 7), desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g7, AZStd::ref(x), 1, 2, 3, 4, 5, 6, 7)).join();
//// 8 //// 8
//thread( AZStd::bind(&MfTest::f8, &x, 1, 2, 3, 4, 5, 6, 7, 8) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::f8, &x, 1, 2, 3, 4, 5, 6, 7, 8)).join();
//thread( AZStd::bind(&MfTest::f8, AZStd::ref(x), 1, 2, 3, 4, 5, 6, 7, 8) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::f8, AZStd::ref(x), 1, 2, 3, 4, 5, 6, 7, 8)).join();
//thread( AZStd::bind(&MfTest::g8, &x, 1, 2, 3, 4, 5, 6, 7, 8) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g8, &x, 1, 2, 3, 4, 5, 6, 7, 8)).join();
//thread( AZStd::bind(&MfTest::g8, x, 1, 2, 3, 4, 5, 6, 7, 8) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g8, x, 1, 2, 3, 4, 5, 6, 7, 8)).join();
//thread( AZStd::bind(&MfTest::g8, AZStd::ref(x), 1, 2, 3, 4, 5, 6, 7, 8) , desc1).join(); //thread( AZStd::bind(desc1, &MfTest::g8, AZStd::ref(x), 1, 2, 3, 4, 5, 6, 7, 8)).join();
AZ_TEST_ASSERT(x.m_hash == 1366); AZ_TEST_ASSERT(x.m_hash == 1366);
} }

@ -151,7 +151,7 @@ namespace UnitTest
AZStd::thread m_threads[m_maxNumThreads]; AZStd::thread m_threads[m_maxNumThreads];
for (unsigned int i = 0; i < m_maxNumThreads; ++i) for (unsigned int i = 0; i < m_maxNumThreads; ++i)
{ {
m_threads[i] = AZStd::thread(AZStd::bind(&SystemAllocatorTest::ThreadFunc, this), &m_desc[i]); m_threads[i] = AZStd::thread(m_desc[i], AZStd::bind(&SystemAllocatorTest::ThreadFunc, this));
// give some time offset to the threads so we can test alloc and dealloc at the same time. // give some time offset to the threads so we can test alloc and dealloc at the same time.
//AZStd::this_thread::sleep_for(AZStd::chrono::microseconds(500)); //AZStd::this_thread::sleep_for(AZStd::chrono::microseconds(500));
} }
@ -286,7 +286,7 @@ namespace UnitTest
AZStd::thread m_threads[m_maxNumThreads]; AZStd::thread m_threads[m_maxNumThreads];
for (unsigned int i = 0; i < m_maxNumThreads; ++i) for (unsigned int i = 0; i < m_maxNumThreads; ++i)
{ {
m_threads[i] = AZStd::thread(AZStd::bind(&SystemAllocatorTest::ThreadFunc, this), &m_desc[i]); m_threads[i] = AZStd::thread(m_desc[i], AZStd::bind(&SystemAllocatorTest::ThreadFunc, this));
// give some time offset to the threads so we can test alloc and dealloc at the same time. // give some time offset to the threads so we can test alloc and dealloc at the same time.
AZStd::this_thread::sleep_for(AZStd::chrono::microseconds(500)); AZStd::this_thread::sleep_for(AZStd::chrono::microseconds(500));
} }
@ -724,7 +724,7 @@ namespace UnitTest
AZStd::thread m_threads[m_maxNumThreads]; AZStd::thread m_threads[m_maxNumThreads];
for (unsigned int i = 0; i < m_maxNumThreads; ++i) for (unsigned int i = 0; i < m_maxNumThreads; ++i)
{ {
m_threads[i] = AZStd::thread(AZStd::bind(&ThreadPoolAllocatorTest::AllocDeallocFunc, this), &m_desc[i]); m_threads[i] = AZStd::thread(m_desc[i], AZStd::bind(&ThreadPoolAllocatorTest::AllocDeallocFunc, this));
} }
for (unsigned int i = 0; i < m_maxNumThreads; ++i) for (unsigned int i = 0; i < m_maxNumThreads; ++i)
@ -743,12 +743,12 @@ namespace UnitTest
for (unsigned int i = m_maxNumThreads/2; i <m_maxNumThreads; ++i) for (unsigned int i = m_maxNumThreads/2; i <m_maxNumThreads; ++i)
{ {
m_threads[i] = AZStd::thread(AZStd::bind(&ThreadPoolAllocatorTest::SharedDeAlloc, this), &m_desc[i]); m_threads[i] = AZStd::thread(m_desc[i], AZStd::bind(&ThreadPoolAllocatorTest::SharedDeAlloc, this));
} }
for (unsigned int i = 0; i < m_maxNumThreads/2; ++i) for (unsigned int i = 0; i < m_maxNumThreads/2; ++i)
{ {
m_threads[i] = AZStd::thread(AZStd::bind(&ThreadPoolAllocatorTest::SharedAlloc, this), &m_desc[i]); m_threads[i] = AZStd::thread(m_desc[i], AZStd::bind(&ThreadPoolAllocatorTest::SharedAlloc, this));
} }
for (unsigned int i = 0; i < m_maxNumThreads/2; ++i) for (unsigned int i = 0; i < m_maxNumThreads/2; ++i)

@ -30,6 +30,8 @@
namespace UnitTest namespace UnitTest
{ {
constexpr AZ::u32 ProfilerProxyGroup = AZ_CRC_CE("StatisticalProfilerProxyTests");
class StatisticalProfilerTest class StatisticalProfilerTest
: public AllocatorsFixture : public AllocatorsFixture
{ {
@ -98,10 +100,10 @@ namespace UnitTest
AZ::Statistics::StatisticalProfiler<AZ::Crc32> profiler; AZ::Statistics::StatisticalProfiler<AZ::Crc32> profiler;
const AZ::Crc32 statIdPerformance = AZ_CRC("PerformanceResult", 0xc1f29a10); constexpr AZ::Crc32 statIdPerformance = AZ_CRC_CE("PerformanceResult");
const AZStd::string statNamePerformance("PerformanceResult"); const AZStd::string statNamePerformance("PerformanceResult");
const AZ::Crc32 statIdBlock = AZ_CRC("Block", 0x831b9722); constexpr AZ::Crc32 statIdBlock = AZ_CRC_CE("Block");
const AZStd::string statNameBlock("Block"); const AZStd::string statNameBlock("Block");
ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdPerformance, statNamePerformance, "us") != nullptr); ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdPerformance, statNamePerformance, "us") != nullptr);
@ -175,10 +177,10 @@ namespace UnitTest
AZ::Statistics::StatisticalProfiler<AZ::Crc32, AZStd::shared_spin_mutex> profiler; AZ::Statistics::StatisticalProfiler<AZ::Crc32, AZStd::shared_spin_mutex> profiler;
const AZ::Crc32 statIdPerformance = AZ_CRC("PerformanceResult", 0xc1f29a10); constexpr AZ::Crc32 statIdPerformance = AZ_CRC_CE("PerformanceResult");
const AZStd::string statNamePerformance("PerformanceResult"); const AZStd::string statNamePerformance("PerformanceResult");
const AZ::Crc32 statIdBlock = AZ_CRC("Block", 0x831b9722); constexpr AZ::Crc32 statIdBlock = AZ_CRC_CE("Block");
const AZStd::string statNameBlock("Block"); const AZStd::string statNameBlock("Block");
ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdPerformance, statNamePerformance, "us") != nullptr); ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdPerformance, statNamePerformance, "us") != nullptr);
@ -317,26 +319,26 @@ namespace UnitTest
AZ::Statistics::StatisticalProfilerProxy::TimedScope::ClearCachedProxy(); AZ::Statistics::StatisticalProfilerProxy::TimedScope::ClearCachedProxy();
AZ::Statistics::StatisticalProfilerProxy profilerProxy; AZ::Statistics::StatisticalProfilerProxy profilerProxy;
AZ::Statistics::StatisticalProfilerProxy* proxy = AZ::Interface<AZ::Statistics::StatisticalProfilerProxy>::Get(); AZ::Statistics::StatisticalProfilerProxy* proxy = AZ::Interface<AZ::Statistics::StatisticalProfilerProxy>::Get();
AZ::Statistics::StatisticalProfilerProxy::StatisticalProfilerType& profiler = proxy->GetProfiler(AZ::Debug::ProfileCategory::Terrain); AZ::Statistics::StatisticalProfilerProxy::StatisticalProfilerType& profiler = proxy->GetProfiler(ProfilerProxyGroup);
const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdPerformance = "PerformanceResult"; const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdPerformance("PerformanceResult");
const AZStd::string statNamePerformance("PerformanceResult"); const AZStd::string statNamePerformance("PerformanceResult");
const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdBlock = "Block"; const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdBlock("Block");
const AZStd::string statNameBlock("Block"); const AZStd::string statNameBlock("Block");
ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdPerformance, statNamePerformance, "us") != nullptr); ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdPerformance, statNamePerformance, "us") != nullptr);
ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdBlock, statNameBlock, "us") != nullptr); ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdBlock, statNameBlock, "us") != nullptr);
proxy->ActivateProfiler(AZ::Debug::ProfileCategory::Terrain, true); proxy->ActivateProfiler(ProfilerProxyGroup, true);
const int iter_count = 10; const int iter_count = 10;
{ {
CODE_PROFILER_PROXY_PUSH_TIME(AZ::Debug::ProfileCategory::Terrain, statIdPerformance) CODE_PROFILER_PROXY_PUSH_TIME(ProfilerProxyGroup, statIdPerformance)
int counter = 0; int counter = 0;
for (int i = 0; i < iter_count; i++) for (int i = 0; i < iter_count; i++)
{ {
CODE_PROFILER_PROXY_PUSH_TIME(AZ::Debug::ProfileCategory::Terrain, statIdBlock) CODE_PROFILER_PROXY_PUSH_TIME(ProfilerProxyGroup, statIdBlock)
counter++; counter++;
} }
} }
@ -348,7 +350,7 @@ namespace UnitTest
EXPECT_EQ(profiler.GetStatistic(statIdBlock)->GetNumSamples(), iter_count); EXPECT_EQ(profiler.GetStatistic(statIdBlock)->GetNumSamples(), iter_count);
//Clean Up //Clean Up
proxy->ActivateProfiler(AZ::Debug::ProfileCategory::Terrain, false); proxy->ActivateProfiler(ProfilerProxyGroup, false);
#undef CODE_PROFILER_PROXY_PUSH_TIME #undef CODE_PROFILER_PROXY_PUSH_TIME
@ -362,12 +364,12 @@ namespace UnitTest
const AZ::Statistics::StatisticalProfilerProxy::StatIdType simple_thread1("simple_thread1"); const AZ::Statistics::StatisticalProfilerProxy::StatIdType simple_thread1("simple_thread1");
const AZ::Statistics::StatisticalProfilerProxy::StatIdType simple_thread1_loop("simple_thread1_loop"); const AZ::Statistics::StatisticalProfilerProxy::StatIdType simple_thread1_loop("simple_thread1_loop");
CODE_PROFILER_PROXY_PUSH_TIME(AZ::Debug::ProfileCategory::Terrain, simple_thread1); CODE_PROFILER_PROXY_PUSH_TIME(ProfilerProxyGroup, simple_thread1);
static int counter = 0; static int counter = 0;
for (int i = 0; i < loop_cnt; i++) for (int i = 0; i < loop_cnt; i++)
{ {
CODE_PROFILER_PROXY_PUSH_TIME(AZ::Debug::ProfileCategory::Terrain, simple_thread1_loop); CODE_PROFILER_PROXY_PUSH_TIME(ProfilerProxyGroup, simple_thread1_loop);
counter++; counter++;
} }
} }
@ -377,12 +379,12 @@ namespace UnitTest
const AZ::Statistics::StatisticalProfilerProxy::StatIdType simple_thread2("simple_thread2"); const AZ::Statistics::StatisticalProfilerProxy::StatIdType simple_thread2("simple_thread2");
const AZ::Statistics::StatisticalProfilerProxy::StatIdType simple_thread2_loop("simple_thread2_loop"); const AZ::Statistics::StatisticalProfilerProxy::StatIdType simple_thread2_loop("simple_thread2_loop");
CODE_PROFILER_PROXY_PUSH_TIME(AZ::Debug::ProfileCategory::Terrain, simple_thread2); CODE_PROFILER_PROXY_PUSH_TIME(ProfilerProxyGroup, simple_thread2);
static int counter = 0; static int counter = 0;
for (int i = 0; i < loop_cnt; i++) for (int i = 0; i < loop_cnt; i++)
{ {
CODE_PROFILER_PROXY_PUSH_TIME(AZ::Debug::ProfileCategory::Terrain, simple_thread2_loop); CODE_PROFILER_PROXY_PUSH_TIME(ProfilerProxyGroup, simple_thread2_loop);
counter++; counter++;
} }
} }
@ -392,12 +394,13 @@ namespace UnitTest
const AZ::Statistics::StatisticalProfilerProxy::StatIdType simple_thread3("simple_thread3"); const AZ::Statistics::StatisticalProfilerProxy::StatIdType simple_thread3("simple_thread3");
const AZ::Statistics::StatisticalProfilerProxy::StatIdType simple_thread3_loop("simple_thread3_loop"); const AZ::Statistics::StatisticalProfilerProxy::StatIdType simple_thread3_loop("simple_thread3_loop");
CODE_PROFILER_PROXY_PUSH_TIME(AZ::Debug::ProfileCategory::Terrain, simple_thread3); CODE_PROFILER_PROXY_PUSH_TIME(ProfilerProxyGroup, simple_thread3);
static int counter = 0; static int counter = 0;
for (int i = 0; i < loop_cnt; i++) for (int i = 0; i < loop_cnt; i++)
{ {
CODE_PROFILER_PROXY_PUSH_TIME(AZ::Debug::ProfileCategory::Terrain, simple_thread3_loop); CODE_PROFILER_PROXY_PUSH_TIME(ProfilerProxyGroup, simple_thread3_loop);
counter++;
} }
} }
@ -408,21 +411,21 @@ namespace UnitTest
AZ::Statistics::StatisticalProfilerProxy::TimedScope::ClearCachedProxy(); AZ::Statistics::StatisticalProfilerProxy::TimedScope::ClearCachedProxy();
AZ::Statistics::StatisticalProfilerProxy profilerProxy; AZ::Statistics::StatisticalProfilerProxy profilerProxy;
AZ::Statistics::StatisticalProfilerProxy* proxy = AZ::Interface<AZ::Statistics::StatisticalProfilerProxy>::Get(); AZ::Statistics::StatisticalProfilerProxy* proxy = AZ::Interface<AZ::Statistics::StatisticalProfilerProxy>::Get();
AZ::Statistics::StatisticalProfilerProxy::StatisticalProfilerType& profiler = proxy->GetProfiler(AZ::Debug::ProfileCategory::Terrain); AZ::Statistics::StatisticalProfilerProxy::StatisticalProfilerType& profiler = proxy->GetProfiler(ProfilerProxyGroup);
const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread1 = "simple_thread1"; const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread1("simple_thread1");
const AZStd::string statNameThread1("simple_thread1"); const AZStd::string statNameThread1("simple_thread1");
const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread1Loop = "simple_thread1_loop"; const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread1Loop("simple_thread1_loop");
const AZStd::string statNameThread1Loop("simple_thread1_loop"); const AZStd::string statNameThread1Loop("simple_thread1_loop");
const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread2 = "simple_thread2"; const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread2("simple_thread2");
const AZStd::string statNameThread2("simple_thread2"); const AZStd::string statNameThread2("simple_thread2");
const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread2Loop = "simple_thread2_loop"; const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread2Loop("simple_thread2_loop");
const AZStd::string statNameThread2Loop("simple_thread2_loop"); const AZStd::string statNameThread2Loop("simple_thread2_loop");
const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread3 = "simple_thread3"; const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread3("simple_thread3");
const AZStd::string statNameThread3("simple_thread3"); const AZStd::string statNameThread3("simple_thread3");
const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread3Loop = "simple_thread3_loop"; const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread3Loop("simple_thread3_loop");
const AZStd::string statNameThread3Loop("simple_thread3_loop"); const AZStd::string statNameThread3Loop("simple_thread3_loop");
ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdThread1, statNameThread1, "us")); ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdThread1, statNameThread1, "us"));
@ -432,7 +435,7 @@ namespace UnitTest
ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdThread3, statNameThread3, "us")); ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdThread3, statNameThread3, "us"));
ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdThread3Loop, statNameThread3Loop, "us")); ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdThread3Loop, statNameThread3Loop, "us"));
proxy->ActivateProfiler(AZ::Debug::ProfileCategory::Terrain, true); proxy->ActivateProfiler(ProfilerProxyGroup, true);
//Let's kickoff the threads to see how much contention affects the profiler's performance. //Let's kickoff the threads to see how much contention affects the profiler's performance.
const int iter_count = 10; const int iter_count = 10;
@ -459,7 +462,7 @@ namespace UnitTest
EXPECT_EQ(profiler.GetStatistic(statIdThread3Loop)->GetNumSamples(), iter_count); EXPECT_EQ(profiler.GetStatistic(statIdThread3Loop)->GetNumSamples(), iter_count);
//Clean Up //Clean Up
proxy->ActivateProfiler(AZ::Debug::ProfileCategory::Terrain, false); proxy->ActivateProfiler(ProfilerProxyGroup, false);
} }
/** Trace message handler to track messages during tests /** Trace message handler to track messages during tests
@ -566,10 +569,10 @@ namespace UnitTest
AZ::Statistics::StatisticalProfiler<AZ::Crc32> profiler; AZ::Statistics::StatisticalProfiler<AZ::Crc32> profiler;
const AZ::Crc32 statIdPerformance = AZ_CRC("PerformanceResult", 0xc1f29a10); constexpr AZ::Crc32 statIdPerformance = AZ_CRC_CE("PerformanceResult");
const AZStd::string statNamePerformance("PerformanceResult"); const AZStd::string statNamePerformance("PerformanceResult");
const AZ::Crc32 statIdBlock = AZ_CRC("Block", 0x831b9722); constexpr AZ::Crc32 statIdBlock = AZ_CRC_CE("Block");
const AZStd::string statNameBlock("Block"); const AZStd::string statNameBlock("Block");
ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdPerformance, statNamePerformance, "us") != nullptr); ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdPerformance, statNamePerformance, "us") != nullptr);
@ -647,10 +650,10 @@ namespace UnitTest
AZ::Statistics::StatisticalProfiler<AZ::Crc32, AZStd::shared_spin_mutex> profiler; AZ::Statistics::StatisticalProfiler<AZ::Crc32, AZStd::shared_spin_mutex> profiler;
const AZ::Crc32 statIdPerformance = AZ_CRC("PerformanceResult", 0xc1f29a10); constexpr AZ::Crc32 statIdPerformance = AZ_CRC_CE("PerformanceResult");
const AZStd::string statNamePerformance("PerformanceResult"); const AZStd::string statNamePerformance("PerformanceResult");
const AZ::Crc32 statIdBlock = AZ_CRC("Block", 0x831b9722); constexpr AZ::Crc32 statIdBlock = AZ_CRC_CE("Block");
const AZStd::string statNameBlock("Block"); const AZStd::string statNameBlock("Block");
ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdPerformance, statNamePerformance, "us") != nullptr); ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdPerformance, statNamePerformance, "us") != nullptr);
@ -745,26 +748,26 @@ namespace UnitTest
AZ::Statistics::StatisticalProfilerProxy::TimedScope::ClearCachedProxy(); AZ::Statistics::StatisticalProfilerProxy::TimedScope::ClearCachedProxy();
AZ::Statistics::StatisticalProfilerProxy profilerProxy; AZ::Statistics::StatisticalProfilerProxy profilerProxy;
AZ::Statistics::StatisticalProfilerProxy* proxy = AZ::Interface<AZ::Statistics::StatisticalProfilerProxy>::Get(); AZ::Statistics::StatisticalProfilerProxy* proxy = AZ::Interface<AZ::Statistics::StatisticalProfilerProxy>::Get();
AZ::Statistics::StatisticalProfilerProxy::StatisticalProfilerType& profiler = proxy->GetProfiler(AZ::Debug::ProfileCategory::Terrain); AZ::Statistics::StatisticalProfilerProxy::StatisticalProfilerType& profiler = proxy->GetProfiler(ProfilerProxyGroup);
const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdPerformance = "PerformanceResult"; const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdPerformance("PerformanceResult");
const AZStd::string statNamePerformance("PerformanceResult"); const AZStd::string statNamePerformance("PerformanceResult");
const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdBlock = "Block"; const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdBlock("Block");
const AZStd::string statNameBlock("Block"); const AZStd::string statNameBlock("Block");
ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdPerformance, statNamePerformance, "us") != nullptr); ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdPerformance, statNamePerformance, "us") != nullptr);
ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdBlock, statNameBlock, "us") != nullptr); ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdBlock, statNameBlock, "us") != nullptr);
proxy->ActivateProfiler(AZ::Debug::ProfileCategory::Terrain, true); proxy->ActivateProfiler(ProfilerProxyGroup, true);
const int iter_count = 1000000; const int iter_count = 1000000;
{ {
CODE_PROFILER_PROXY_PUSH_TIME(AZ::Debug::ProfileCategory::Terrain, statIdPerformance) CODE_PROFILER_PROXY_PUSH_TIME(ProfilerProxyGroup, statIdPerformance)
int counter = 0; int counter = 0;
for (int i = 0; i < iter_count; i++) for (int i = 0; i < iter_count; i++)
{ {
CODE_PROFILER_PROXY_PUSH_TIME(AZ::Debug::ProfileCategory::Terrain, statIdBlock) CODE_PROFILER_PROXY_PUSH_TIME(ProfilerProxyGroup, statIdBlock)
counter++; counter++;
} }
} }
@ -778,7 +781,7 @@ namespace UnitTest
profiler.LogAndResetStats("StatisticalProfilerProxy"); profiler.LogAndResetStats("StatisticalProfilerProxy");
//Clean Up //Clean Up
proxy->ActivateProfiler(AZ::Debug::ProfileCategory::Terrain, false); proxy->ActivateProfiler(ProfilerProxyGroup, false);
} }
#undef CODE_PROFILER_PROXY_PUSH_TIME #undef CODE_PROFILER_PROXY_PUSH_TIME
@ -788,21 +791,21 @@ namespace UnitTest
AZ::Statistics::StatisticalProfilerProxy::TimedScope::ClearCachedProxy(); AZ::Statistics::StatisticalProfilerProxy::TimedScope::ClearCachedProxy();
AZ::Statistics::StatisticalProfilerProxy profilerProxy; AZ::Statistics::StatisticalProfilerProxy profilerProxy;
AZ::Statistics::StatisticalProfilerProxy* proxy = AZ::Interface<AZ::Statistics::StatisticalProfilerProxy>::Get(); AZ::Statistics::StatisticalProfilerProxy* proxy = AZ::Interface<AZ::Statistics::StatisticalProfilerProxy>::Get();
AZ::Statistics::StatisticalProfilerProxy::StatisticalProfilerType& profiler = proxy->GetProfiler(AZ::Debug::ProfileCategory::Terrain); AZ::Statistics::StatisticalProfilerProxy::StatisticalProfilerType& profiler = proxy->GetProfiler(ProfilerProxyGroup);
const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread1 = "simple_thread1"; const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread1("simple_thread1");
const AZStd::string statNameThread1("simple_thread1"); const AZStd::string statNameThread1("simple_thread1");
const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread1Loop = "simple_thread1_loop"; const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread1Loop("simple_thread1_loop");
const AZStd::string statNameThread1Loop("simple_thread1_loop"); const AZStd::string statNameThread1Loop("simple_thread1_loop");
const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread2 = "simple_thread2"; const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread2("simple_thread2");
const AZStd::string statNameThread2("simple_thread2"); const AZStd::string statNameThread2("simple_thread2");
const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread2Loop = "simple_thread2_loop"; const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread2Loop("simple_thread2_loop");
const AZStd::string statNameThread2Loop("simple_thread2_loop"); const AZStd::string statNameThread2Loop("simple_thread2_loop");
const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread3 = "simple_thread3"; const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread3("simple_thread3");
const AZStd::string statNameThread3("simple_thread3"); const AZStd::string statNameThread3("simple_thread3");
const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread3Loop = "simple_thread3_loop"; const AZ::Statistics::StatisticalProfilerProxy::StatIdType statIdThread3Loop("simple_thread3_loop");
const AZStd::string statNameThread3Loop("simple_thread3_loop"); const AZStd::string statNameThread3Loop("simple_thread3_loop");
ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdThread1, statNameThread1, "us")); ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdThread1, statNameThread1, "us"));
@ -812,7 +815,7 @@ namespace UnitTest
ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdThread3, statNameThread3, "us")); ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdThread3, statNameThread3, "us"));
ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdThread3Loop, statNameThread3Loop, "us")); ASSERT_TRUE(profiler.GetStatsManager().AddStatistic(statIdThread3Loop, statNameThread3Loop, "us"));
proxy->ActivateProfiler(AZ::Debug::ProfileCategory::Terrain, true); proxy->ActivateProfiler(ProfilerProxyGroup, true);
//Let's kickoff the threads to see how much contention affects the profiler's performance. //Let's kickoff the threads to see how much contention affects the profiler's performance.
const int iter_count = 1000000; const int iter_count = 1000000;
@ -841,7 +844,7 @@ namespace UnitTest
profiler.LogAndResetStats("3_Threads_StatisticalProfilerProxy"); profiler.LogAndResetStats("3_Threads_StatisticalProfilerProxy");
//Clean Up //Clean Up
proxy->ActivateProfiler(AZ::Debug::ProfileCategory::Terrain, false); proxy->ActivateProfiler(ProfilerProxyGroup, false);
} }
}//namespace UnitTest }//namespace UnitTest

@ -34,7 +34,7 @@ namespace UnitTest
AZ::AllocatorInstance<AZ::PoolAllocator>::Create(); AZ::AllocatorInstance<AZ::PoolAllocator>::Create();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Create(); AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Create();
m_executor = aznew TaskExecutor(4); m_executor = aznew TaskExecutor();
} }
void TearDown() override void TearDown() override
@ -236,6 +236,82 @@ namespace UnitTest
EXPECT_EQ(x, 1); EXPECT_EQ(x, 1);
} }
TEST_F(TaskGraphTestFixture, SingleTask)
{
AZStd::atomic_int32_t x = 0;
TaskGraph graph;
graph.AddTask(
defaultTD,
[&x]
{
x = 1;
});
TaskGraphEvent ev;
graph.SubmitOnExecutor(*m_executor, &ev);
ev.Wait();
EXPECT_EQ(1, x);
}
TEST_F(TaskGraphTestFixture, SingleTaskChain)
{
AZStd::atomic_int32_t x = 0;
TaskGraph graph;
auto a = graph.AddTask(
defaultTD,
[&x]
{
x += 1;
});
auto b = graph.AddTask(
defaultTD,
[&x]
{
x += 1;
});
b.Precedes(a);
TaskGraphEvent ev;
graph.SubmitOnExecutor(*m_executor, &ev);
ev.Wait();
EXPECT_EQ(2, x);
}
TEST_F(TaskGraphTestFixture, MultipleIndependentTaskChains)
{
AZStd::atomic_int32_t x = 0;
constexpr int numChains = 5;
TaskGraph graph;
for( int i = 0; i < numChains; ++i)
{
auto a = graph.AddTask(
defaultTD,
[&x]
{
x += 1;
});
auto b = graph.AddTask(
defaultTD,
[&x]
{
x += 1;
});
b.Precedes(a);
}
TaskGraphEvent ev;
graph.SubmitOnExecutor(*m_executor, &ev);
ev.Wait();
EXPECT_EQ(2*numChains, x);
}
TEST_F(TaskGraphTestFixture, VariadicInterface) TEST_F(TaskGraphTestFixture, VariadicInterface)
{ {
int x = 0; int x = 0;
@ -388,6 +464,7 @@ namespace UnitTest
EXPECT_EQ(3, x); EXPECT_EQ(3, x);
} }
// Waiting inside a task is disallowed , test that it fails correctly
TEST_F(TaskGraphTestFixture, SpawnSubgraph) TEST_F(TaskGraphTestFixture, SpawnSubgraph)
{ {
AZStd::atomic<int> x = 0; AZStd::atomic<int> x = 0;
@ -434,7 +511,10 @@ namespace UnitTest
f.Precedes(g); f.Precedes(g);
TaskGraphEvent ev; TaskGraphEvent ev;
subgraph.SubmitOnExecutor(*m_executor, &ev); subgraph.SubmitOnExecutor(*m_executor, &ev);
// TaskGraphEvent::Wait asserts if called on a worker thread, suppress & validate assert
AZ_TEST_START_TRACE_SUPPRESSION;
ev.Wait(); ev.Wait();
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
}); });
auto d = graph.AddTask( auto d = graph.AddTask(
defaultTD, defaultTD,
@ -464,8 +544,6 @@ namespace UnitTest
TaskGraphEvent ev; TaskGraphEvent ev;
graph.SubmitOnExecutor(*m_executor, &ev); graph.SubmitOnExecutor(*m_executor, &ev);
ev.Wait(); ev.Wait();
EXPECT_EQ(3 | 0b100000, x);
} }
TEST_F(TaskGraphTestFixture, RetainedGraph) TEST_F(TaskGraphTestFixture, RetainedGraph)

@ -0,0 +1,56 @@
/*
* 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/Time/TimeSystemComponent.h>
#include <AzCore/UnitTest/TestTypes.h>
namespace UnitTest
{
class TimeTests
: public AllocatorsFixture
{
public:
void SetUp() override
{
SetupAllocator();
m_timeComponent = new AZ::TimeSystemComponent;
}
void TearDown() override
{
delete m_timeComponent;
TeardownAllocator();
}
AZ::TimeSystemComponent* m_timeComponent = nullptr;
};
TEST_F(TimeTests, TestConversionUsToMs)
{
AZ::TimeUs timeUs = AZ::TimeUs{ 1000 };
AZ::TimeMs timeMs = AZ::TimeUsToMs(timeUs);
EXPECT_EQ(timeMs, AZ::TimeMs{ 1 });
}
TEST_F(TimeTests, TestConversionMsToUs)
{
AZ::TimeMs timeMs = AZ::TimeMs{ 1000 };
AZ::TimeUs timeUs = AZ::TimeMsToUs(timeMs);
EXPECT_EQ(timeUs, AZ::TimeUs{ 1000000 });
}
TEST_F(TimeTests, TestClocks)
{
AZ::TimeUs timeUs = AZ::GetElapsedTimeUs();
AZ::TimeMs timeMs = AZ::GetElapsedTimeMs();
AZ::TimeMs timeUsToMs = AZ::TimeUsToMs(timeUs);
int64_t delta = static_cast<int64_t>(timeMs) - static_cast<int64_t>(timeUsToMs);
EXPECT_LT(abs(delta), 1);
}
}

@ -61,6 +61,7 @@ set(FILES
Slice.cpp Slice.cpp
State.cpp State.cpp
Statistics.cpp Statistics.cpp
StatisticalProfiler.cpp
StreamerTests.cpp StreamerTests.cpp
StringFunc.cpp StringFunc.cpp
SystemFile.cpp SystemFile.cpp
@ -127,6 +128,7 @@ set(FILES
Serialization/Json/UnorderedSetSerializerTests.cpp Serialization/Json/UnorderedSetSerializerTests.cpp
Serialization/Json/UnsupportedTypesSerializerTests.cpp Serialization/Json/UnsupportedTypesSerializerTests.cpp
Serialization/Json/UuidSerializerTests.cpp Serialization/Json/UuidSerializerTests.cpp
Time/TimeTests.cpp
Math/AabbTests.cpp Math/AabbTests.cpp
Math/ColorTests.cpp Math/ColorTests.cpp
Math/CrcTests.cpp Math/CrcTests.cpp

@ -28,6 +28,7 @@
#include <AzCore/NativeUI/NativeUISystemComponent.h> #include <AzCore/NativeUI/NativeUISystemComponent.h>
#include <AzCore/Module/ModuleManagerBus.h> #include <AzCore/Module/ModuleManagerBus.h>
#include <AzCore/Interface/Interface.h> #include <AzCore/Interface/Interface.h>
#include <AzCore/Task/TaskGraphSystemComponent.h>
#include <AzFramework/Asset/SimpleAsset.h> #include <AzFramework/Asset/SimpleAsset.h>
#include <AzFramework/Asset/AssetBundleManifest.h> #include <AzFramework/Asset/AssetBundleManifest.h>
@ -295,6 +296,7 @@ namespace AzFramework
azrtti_typeid<AZ::ScriptSystemComponent>(), azrtti_typeid<AZ::ScriptSystemComponent>(),
azrtti_typeid<AZ::JobManagerComponent>(), azrtti_typeid<AZ::JobManagerComponent>(),
azrtti_typeid<AZ::SliceSystemComponent>(), azrtti_typeid<AZ::SliceSystemComponent>(),
azrtti_typeid<AZ::TaskGraphSystemComponent>(),
azrtti_typeid<AzFramework::AssetCatalogComponent>(), azrtti_typeid<AzFramework::AssetCatalogComponent>(),
azrtti_typeid<AzFramework::CustomAssetTypeComponent>(), azrtti_typeid<AzFramework::CustomAssetTypeComponent>(),
@ -477,14 +479,16 @@ namespace AzFramework
newThreadDesc.m_cpuId = AFFINITY_MASK_USERTHREADS; newThreadDesc.m_cpuId = AFFINITY_MASK_USERTHREADS;
newThreadDesc.m_name = newThreadName; newThreadDesc.m_name = newThreadName;
AZStd::binary_semaphore binarySemaphore; AZStd::binary_semaphore binarySemaphore;
AZStd::thread newThread([&workForNewThread, &binarySemaphore, &newThreadName] AZStd::thread newThread(
{ newThreadDesc,
AZ_PROFILE_SCOPE(AzFramework, [&workForNewThread, &binarySemaphore, &newThreadName]
"Application::PumpSystemEventLoopWhileDoingWorkInNewThread:ThreadWorker %s", newThreadName); {
AZ_PROFILE_SCOPE(AzFramework,
"Application::PumpSystemEventLoopWhileDoingWorkInNewThread:ThreadWorker %s", newThreadName);
workForNewThread(); workForNewThread();
binarySemaphore.release(); binarySemaphore.release();
}, &newThreadDesc); });
while (!binarySemaphore.try_acquire_for(eventPumpFrequency)) while (!binarySemaphore.try_acquire_for(eventPumpFrequency))
{ {
PumpSystemEventLoopUntilEmpty(); PumpSystemEventLoopUntilEmpty();

@ -1631,7 +1631,20 @@ namespace AZ::IO
return nullptr; return nullptr;
} }
ZipDir::CacheFactory factory(ZipDir::ZD_INIT_FAST, nFactoryFlags); ZipDir::InitMethod initType = ZipDir::InitMethod::Default;
if (!ZipDir::IsReleaseConfig)
{
if ((nFlags & INestedArchive::FLAGS_FULL_VALIDATE) != 0)
{
initType = ZipDir::InitMethod::FullValidation;
}
else if ((nFlags & INestedArchive::FLAGS_VALIDATE_HEADERS) != 0)
{
initType = ZipDir::InitMethod::ValidateHeaders;
}
}
ZipDir::CacheFactory factory(initType, nFactoryFlags);
ZipDir::CachePtr cache = factory.New(szFullPath->c_str()); ZipDir::CachePtr cache = factory.New(szFullPath->c_str());
if (cache) if (cache)

@ -11,7 +11,9 @@
#include <AzCore/IO/Path/Path_fwd.h> #include <AzCore/IO/Path/Path_fwd.h>
#include <AzCore/Math/Crc.h> #include <AzCore/Math/Crc.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/intrusive_base.h> #include <AzCore/std/smart_ptr/intrusive_base.h>
#include <AzCore/std/string/string.h>
#include <AzFramework/Archive/Codec.h> #include <AzFramework/Archive/Codec.h>
namespace AZ::IO namespace AZ::IO
@ -71,6 +73,13 @@ namespace AZ::IO
// multiple times // multiple times
FLAGS_DONT_COMPACT = 1 << 5, FLAGS_DONT_COMPACT = 1 << 5,
// if this is set, validate header data when opening the archive
FLAGS_VALIDATE_HEADERS = 1 << 9,
// if this is set, validate header data when opening the archive and validate CRCs when decompressing
// & reading files.
FLAGS_FULL_VALIDATE = 1 << 10,
// Disable a pak file without unloading it, this flag is used in combination with patches and multiplayer // Disable a pak file without unloading it, this flag is used in combination with patches and multiplayer
// to ensure that specific paks stay in the position(to keep the same priority) but being disabled // to ensure that specific paks stay in the position(to keep the same priority) but being disabled
// when running multiplayer // when running multiplayer
@ -128,6 +137,10 @@ namespace AZ::IO
// Deletes all files and directories in the archive. // Deletes all files and directories in the archive.
virtual int RemoveAll() = 0; virtual int RemoveAll() = 0;
// Summary:
// Lists all the files in the archive.
virtual int ListAllFiles(AZStd::vector<AZ::IO::Path>& outFileEntries) = 0;
// Summary: // Summary:
// Finds the file; you don't have to close the returned handle. // Finds the file; you don't have to close the returned handle.
// Returns: // Returns:

@ -89,11 +89,51 @@ namespace AZ::IO
return m_pCache->RemoveDir(fullPath); return m_pCache->RemoveDir(fullPath);
} }
//////////////////////////////////////////////////////////////////////////
int NestedArchive::RemoveAll() int NestedArchive::RemoveAll()
{ {
return m_pCache->RemoveAll(); return m_pCache->RemoveAll();
} }
//////////////////////////////////////////////////////////////////////////
// Helper for 'ListAllFiles' to recursively traverse the FileEntryTree and gather all the files
void EnumerateFilesRecursive(AZ::IO::Path currentPath, ZipDir::FileEntryTree* currentTree, AZStd::vector<AZ::IO::Path>& fileList)
{
// Drill down directories first...
for (auto dirIter = currentTree->GetDirBegin(); dirIter != currentTree->GetDirEnd(); ++dirIter)
{
if (ZipDir::FileEntryTree* subTree = currentTree->GetDirEntry(dirIter);
subTree != nullptr)
{
EnumerateFilesRecursive(currentPath / currentTree->GetDirName(dirIter), subTree, fileList);
}
}
// Then enumerate the files in current directory...
for (auto fileIter = currentTree->GetFileBegin(); fileIter != currentTree->GetFileEnd(); ++fileIter)
{
fileList.emplace_back(currentPath / currentTree->GetFileName(fileIter));
}
}
//////////////////////////////////////////////////////////////////////////
// lists all files in the archive
int NestedArchive::ListAllFiles(AZStd::vector<AZ::IO::Path>& outFileEntries)
{
AZStd::vector<AZ::IO::Path> filesInArchive;
ZipDir::FileEntryTree* tree = m_pCache->GetRoot();
if (!tree)
{
return ZipDir::ZD_ERROR_UNEXPECTED;
}
EnumerateFilesRecursive(AZ::IO::Path{ AZ::IO::PosixPathSeparator }, tree, filesInArchive);
AZStd::swap(outFileEntries, filesInArchive);
return ZipDir::ZD_ERROR_SUCCESS;
}
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Adds a new file to the zip or update an existing one // Adds a new file to the zip or update an existing one
// adds a directory (creates several nested directories if needed) // adds a directory (creates several nested directories if needed)

@ -39,7 +39,7 @@ namespace AZ::IO
NestedArchive(IArchive* pArchive, AZStd::string_view strBindRoot, ZipDir::CachePtr pCache, uint32_t nFlags = 0); NestedArchive(IArchive* pArchive, AZStd::string_view strBindRoot, ZipDir::CachePtr pCache, uint32_t nFlags = 0);
~NestedArchive() override; ~NestedArchive() override;
auto GetRootFolderHandle() -> Handle override; auto GetRootFolderHandle() -> Handle override;
// Adds a new file to the zip or update an existing one // Adds a new file to the zip or update an existing one
@ -68,6 +68,9 @@ namespace AZ::IO
// deletes all files from the archive // deletes all files from the archive
int RemoveAll() override; int RemoveAll() override;
// lists all the files in the archive
int ListAllFiles(AZStd::vector<AZ::IO::Path>& outFileEntries) override;
// finds the file; you don't have to close the returned handle // finds the file; you don't have to close the returned handle
Handle FindFile(AZStd::string_view szRelativePath) override; Handle FindFile(AZStd::string_view szRelativePath) override;
@ -79,7 +82,6 @@ namespace AZ::IO
// returns the full path to the archive file // returns the full path to the archive file
AZ::IO::PathView GetFullPath() const override; AZ::IO::PathView GetFullPath() const override;
ZipDir::Cache* GetCache();
uint32_t GetFlags() const override; uint32_t GetFlags() const override;
bool SetFlags(uint32_t nFlagsToSet) override; bool SetFlags(uint32_t nFlagsToSet) override;
@ -87,12 +89,15 @@ namespace AZ::IO
bool SetPackAccessible(bool bAccessible) override; bool SetPackAccessible(bool bAccessible) override;
ZipDir::Cache* GetCache();
protected: protected:
// returns the pointer to the relative file path to be passed // returns the pointer to the relative file path to be passed
// to the underlying Cache pointer. Uses the given buffer to construct the path. // to the underlying Cache pointer. Uses the given buffer to construct the path.
// returns nullptr if the file path is invalid // returns nullptr if the file path is invalid
AZ::IO::FixedMaxPathString AdjustPath(AZStd::string_view szRelativePath); AZ::IO::FixedMaxPathString AdjustPath(AZStd::string_view szRelativePath);
ZipDir::CachePtr m_pCache; ZipDir::CachePtr m_pCache;
// the binding root may be empty string - in this case, the absolute path binding won't work // the binding root may be empty string - in this case, the absolute path binding won't work
AZ::IO::Path m_strBindRoot; AZ::IO::Path m_strBindRoot;

@ -101,10 +101,11 @@ namespace AZ::IO::ZipDir
FileEntry* operator -> () { return m_pFileEntry; } FileEntry* operator -> () { return m_pFileEntry; }
FileEntryTransactionAdd(Cache* pCache, AZStd::string_view szRelativePath) FileEntryTransactionAdd(Cache* pCache, AZStd::string_view szRelativePath)
: m_pCache(pCache) : m_pCache(pCache)
, m_szRelativePath(AZ::IO::PosixPathSeparator)
, m_bCommitted(false) , m_bCommitted(false)
{ {
// Update the cache string pool with the relative path to the file // Update the cache string pool with the relative path to the file
auto pathIt = m_pCache->m_relativePathPool.emplace(AZ::IO::PathView(szRelativePath).LexicallyNormal()); auto pathIt = m_pCache->m_relativePathPool.emplace(AZ::IO::PathView(szRelativePath, AZ::IO::PosixPathSeparator).LexicallyNormal());
m_szRelativePath = *pathIt.first; m_szRelativePath = *pathIt.first;
// this is the name of the directory - create it or find it // this is the name of the directory - create it or find it
m_pFileEntry = m_pCache->GetRoot()->Add(m_szRelativePath.Native()); m_pFileEntry = m_pCache->GetRoot()->Add(m_szRelativePath.Native());
@ -740,6 +741,16 @@ namespace AZ::IO::ZipDir
{ {
return ZD_ERROR_CORRUPTED_DATA; return ZD_ERROR_CORRUPTED_DATA;
} }
if (pFileEntry->bCheckCRCNextRead)
{
pFileEntry->bCheckCRCNextRead = false;
uLong uCRC32 = AZ::Crc32((Bytef*)pUncompressed, nSizeUncompressed);
if (uCRC32 != pFileEntry->desc.lCRC32)
{
AZ_Warning("Archive", false, "ZD_ERROR_CRC32_CHECK: Uncompressed stream CRC32 check failed");
return ZD_ERROR_CRC32_CHECK;
}
}
} }
} }

@ -29,7 +29,7 @@ namespace AZ::IO::ZipDir
// this sets the window size of the blocks of data read from the end of the file to find the Central Directory Record // this sets the window size of the blocks of data read from the end of the file to find the Central Directory Record
// since normally there are no // since normally there are no
static constexpr size_t CDRSearchWindowSize = 0x100; static constexpr size_t CDRSearchWindowSize = 0x100;
CacheFactory::CacheFactory(InitMethodEnum nInitMethod, uint32_t nFlags) CacheFactory::CacheFactory(InitMethod nInitMethod, uint32_t nFlags)
{ {
m_nCDREndPos = 0; m_nCDREndPos = 0;
m_bBuildFileEntryMap = false; // we only need it for validation/debugging m_bBuildFileEntryMap = false; // we only need it for validation/debugging
@ -448,7 +448,6 @@ namespace AZ::IO::ZipDir
// builds up the m_mapFileEntries // builds up the m_mapFileEntries
bool CacheFactory::BuildFileEntryMap() bool CacheFactory::BuildFileEntryMap()
{ {
Seek(m_CDREnd.lCDROffset); Seek(m_CDREnd.lCDROffset);
if (m_CDREnd.lCDRSize == 0) if (m_CDREnd.lCDRSize == 0)
@ -530,14 +529,6 @@ namespace AZ::IO::ZipDir
{ {
// Add this file entry. // Add this file entry.
char* str = reinterpret_cast<char*>(pFileName); char* str = reinterpret_cast<char*>(pFileName);
for (int i = 0; i < pFile->nFileNameLength; i++)
{
str[i] = std::tolower(str[i], std::locale());
if (str[i] == AZ_WRONG_FILESYSTEM_SEPARATOR)
{
str[i] = AZ_CORRECT_FILESYSTEM_SEPARATOR;
}
}
str[pFile->nFileNameLength] = 0; // Not standard!, may overwrite signature of the next memory record data in zip. str[pFile->nFileNameLength] = 0; // Not standard!, may overwrite signature of the next memory record data in zip.
AddFileEntry(str, pFile, extra); AddFileEntry(str, pFile, extra);
} }
@ -574,11 +565,7 @@ namespace AZ::IO::ZipDir
FileEntryBase fileEntry(*pFileHeader, extra); FileEntryBase fileEntry(*pFileHeader, extra);
// when using encrypted headers we should always initialize data offsets from CDR InitDataOffset(fileEntry, pFileHeader);
if ((m_encryptedHeaders != ZipFile::HEADERS_NOT_ENCRYPTED || m_nInitMethod >= ZD_INIT_FULL) && pFileHeader->desc.lSizeCompressed)
{
InitDataOffset(fileEntry, pFileHeader);
}
if (m_bBuildFileEntryMap) if (m_bBuildFileEntryMap)
{ {
@ -606,142 +593,81 @@ namespace AZ::IO::ZipDir
{ {
Seek(pFileHeader->lLocalHeaderOffset); Seek(pFileHeader->lLocalHeaderOffset);
// read the local file header and the name (for validation) into the buffer // Read only the LocalFileHeader w/ no additional bytes ('name' or 'extra' fields)
AZStd::vector<char>pBuffer; AZStd::vector<char> buffer;
uint32_t nBufferLength = sizeof(ZipFile::LocalFileHeader) + pFileHeader->nFileNameLength; uint32_t bufferLen = sizeof(ZipFile::LocalFileHeader);
pBuffer.resize(nBufferLength); buffer.resize_no_construct(bufferLen);
Read(&pBuffer[0], nBufferLength); Read(buffer.data(), bufferLen);
// validate the local file header (compare with the CDR file header - they should contain basically the same information)
const auto* pLocalFileHeader = reinterpret_cast<const ZipFile::LocalFileHeader*>(&pBuffer[0]);
if (pFileHeader->desc != pLocalFileHeader->desc
|| pFileHeader->nMethod != pLocalFileHeader->nMethod
|| pFileHeader->nFileNameLength != pLocalFileHeader->nFileNameLength
// for a tough validation, we can compare the timestamps of the local and central directory entries
// but we won't do that for backward compatibility with ZipDir
//|| pFileHeader->nLastModDate != pLocalFileHeader->nLastModDate
//|| pFileHeader->nLastModTime != pLocalFileHeader->nLastModTime
)
{
AZ_Warning("Archive", false, "ZD_ERROR_VALIDATION_FAILED:"
" The local file header descriptor doesn't match the basic parameters declared in the global file header in the file."
" The archive content is misconsistent and may be damaged. Please try to repair the archive");
return;
}
// now compare the local file name with the one recorded in CDR: they must match. const auto* localFileHeader = reinterpret_cast<const ZipFile::LocalFileHeader*>(buffer.data());
auto CompareNoCase = [](const char lhs, const char rhs) { return std::tolower(lhs, std::locale()) == std::tolower(rhs, std::locale()); };
auto zipFileDataBegin = pBuffer.begin() + sizeof(ZipFile::LocalFileHeader);
auto zipFileDataEnd = zipFileDataBegin + pFileHeader->nFileNameLength;
if (!AZStd::equal(zipFileDataBegin, zipFileDataEnd, reinterpret_cast<const char*>(pFileHeader + 1), CompareNoCase))
{
// either file name, or the extra field do not match
AZ_Warning("Archive", false, "ZD_ERROR_VALIDATION_FAILED:"
" The local file header contains file name which does not match the file name of the global file header."
" The archive content is misconsistent with its directory. Please repair the archive");
return;
}
fileEntry.nFileDataOffset = pFileHeader->lLocalHeaderOffset + sizeof(ZipFile::LocalFileHeader) + pLocalFileHeader->nFileNameLength + pLocalFileHeader->nExtraFieldLength; // set the correct file data offset...
} fileEntry.nFileDataOffset = pFileHeader->lLocalHeaderOffset + sizeof(ZipFile::LocalFileHeader) +
localFileHeader->nFileNameLength + localFileHeader->nExtraFieldLength;
// make sure it's the same file and the fileEntry structure is properly initialized fileEntry.nEOFOffset = fileEntry.nFileDataOffset + fileEntry.desc.lSizeCompressed;
AZ_Assert(fileEntry.nFileHeaderOffset == pFileHeader->lLocalHeaderOffset, "The file entry header offset doesn't match the file header local offst");
fileEntry.nEOFOffset = fileEntry.nFileDataOffset + fileEntry.desc.lSizeCompressed;
if (fileEntry.nFileDataOffset >= m_nCDREndPos)
{
AZ_Warning("Archive", false, "ZD_ERROR_VALIDATION_FAILED:"
" The global file header declares the file which crosses the boundaries of the archive."
" The archive is either corrupted or truncated, please try to repair it");
return;
}
if (m_nInitMethod >= ZD_INIT_VALIDATE) if (m_nInitMethod != ZipDir::InitMethod::Default)
{ {
Validate(fileEntry); if (m_nInitMethod == ZipDir::InitMethod::FullValidation)
} {
} // Mark the FileEntry to check CRC when the next read occurs
fileEntry.bCheckCRCNextRead = true;
}
////////////////////////////////////////////////////////////////////////// // Timestamps
// reads the file pointed by the given header and entry (they must be coherent) if (pFileHeader->nLastModDate != localFileHeader->nLastModDate
// and decompresses it; then calculates and validates its CRC32 || pFileHeader->nLastModTime != localFileHeader->nLastModTime)
void CacheFactory::Validate(const FileEntryBase& fileEntry) {
{ AZ_Warning("Archive", false, "ZD_ERROR_VALIDATION_FAILED: (%s)\n"
AZStd::vector<char> pBuffer; " The local file header's modification timestamps don't match that of the global file header in the archive."
// validate the file contents " The archive timestamps are inconsistent and may be damaged. Check the archive file.", m_szFilename.c_str());
// allocate memory for both the compressed data and uncompressed data // don't return here, it may be ok.
pBuffer.resize(fileEntry.desc.lSizeCompressed + fileEntry.desc.lSizeUncompressed); }
char* pUncompressed = &pBuffer[fileEntry.desc.lSizeCompressed];
char* pCompressed = &pBuffer[0];
AZ_Assert(fileEntry.nFileDataOffset != FileEntry::INVALID_DATA_OFFSET, "File entry has invalid data offset of %" PRIx32, FileEntry::INVALID_DATA_OFFSET); // Validate data
Seek(fileEntry.nFileDataOffset); if (pFileHeader->desc != localFileHeader->desc // this checks CRCs and compressed/uncompressed sizes
|| pFileHeader->nMethod != localFileHeader->nMethod
|| pFileHeader->nFileNameLength != localFileHeader->nFileNameLength)
{
AZ_Warning("Archive", false, "ZD_ERROR_VALIDATION_FAILED: (%s)\n"
" The local file header descriptor doesn't match basic parameters declared in the global file header in the file."
" The archive content is inconsistent and may be damaged. Please try to repair the archive.", m_szFilename.c_str());
// return here because further checks aren't worse than this.
return;
}
Read(pCompressed, fileEntry.desc.lSizeCompressed); // Read extra data
uint32_t extraDataLen = localFileHeader->nFileNameLength + localFileHeader->nExtraFieldLength;
buffer.resize_no_construct(buffer.size() + extraDataLen);
Read(buffer.data() + buffer.size(), extraDataLen);
size_t nDestSize = fileEntry.desc.lSizeUncompressed; // Compare local file name with the CDR file name, they should match
int nError = Z_OK; AZStd::string_view zipFileName{ buffer.data() + sizeof(ZipFile::LocalFileHeader), localFileHeader->nFileNameLength };
if (fileEntry.nMethod) AZStd::string_view cdrFileName{ reinterpret_cast<const char*>(pFileHeader + 1), pFileHeader->nFileNameLength };
{ if (zipFileName != cdrFileName)
nError = ZipRawUncompress(pUncompressed, &nDestSize, pCompressed, fileEntry.desc.lSizeCompressed); {
} AZ_Warning("Archive", false, "ZD_ERROR_VALIDATION_FAILED: (%s)\n"
else " The file name in the local file header doesn't match the name in the global file header."
{ " The archive content is inconsisten with the directory. Please check the archive.", m_szFilename.c_str());
AZ_Assert(fileEntry.desc.lSizeCompressed == fileEntry.desc.lSizeUncompressed, "Uncompressed file does not have the same commpressed %u and uncompressed file sizes %u", }
fileEntry.desc.lSizeCompressed, fileEntry.desc.lSizeUncompressed);
memcpy(pUncompressed, pCompressed, fileEntry.desc.lSizeUncompressed);
}
switch (nError)
{
case Z_OK:
break;
case Z_MEM_ERROR:
AZ_Warning("Archive", false, "ZD_ERROR_ZLIB_NO_MEMORY: ZLib reported out-of-memory error");
return;
case Z_BUF_ERROR:
AZ_Warning("Archive", false, "ZD_ERROR_ZLIB_CORRUPTED_DATA: ZLib reported compressed stream buffer error");
return;
case Z_DATA_ERROR:
AZ_Warning("Archive", false, "ZD_ERROR_ZLIB_CORRUPTED_DATA: ZLib reported compressed stream data error");
return;
default:
AZ_Warning("Archive", false, "ZD_ERROR_ZLIB_FAILED: ZLib reported an unexpected unknown error");
return;
}
if (nDestSize != fileEntry.desc.lSizeUncompressed) // CDR and local "extra field" lengths may be different, should we compare them if they are equal?
{
AZ_Warning("Archive", false, "ZD_ERROR_CORRUPTED_DATA: Uncompressed stream doesn't match the size of uncompressed file stored in the archive file headers");
return;
}
uLong uCRC32 = AZ::Crc32((Bytef*)pUncompressed, nDestSize); // make sure it's the same file and the fileEntry structure is properly initialized
if (uCRC32 != fileEntry.desc.lCRC32) AZ_Assert(fileEntry.nFileHeaderOffset == pFileHeader->lLocalHeaderOffset,
{ "The file entry header offset doesn't match the file header local offst (%s)", m_szFilename.c_str());
AZ_Warning("Archive", false, "ZD_ERROR_CRC32_CHECK: Uncompressed stream CRC32 check failed");
return;
}
}
if (fileEntry.nFileDataOffset >= m_nCDREndPos)
{
AZ_Warning("Archive", false, "ZD_ERROR_VALIDATION_FAILED: (%s)\n"
" The global file header declares the file which crosses the boundaries of the archive."
" The archive is either corrupted or truncated, please try to repair it", m_szFilename.c_str());
}
////////////////////////////////////////////////////////////////////////// // End Validation
// extracts the file path from the file header with subsequent information }
// may, or may not, put all letters to lower-case (depending on whether the system is to be case-sensitive or not)
// it's the responsibility of the caller to ensure that the file name is in readable valid memory
char* CacheFactory::GetFilePath(const char* pFileName, uint16_t nFileNameLength)
{
static char strResult[AZ_MAX_PATH_LEN];
AZ_Assert(nFileNameLength < AZ_MAX_PATH_LEN, "Only filenames shorter than %zu can be copied from filename parameter", AZ_MAX_PATH_LEN);
memcpy(strResult, pFileName, nFileNameLength);
strResult[nFileNameLength] = 0;
for (int i = 0; i < nFileNameLength; i++)
{
strResult[i] = std::tolower(strResult[i], std::locale{});
} }
return strResult;
} }
// seeks in the file relative to the starting position // seeks in the file relative to the starting position

@ -39,7 +39,7 @@ namespace AZ::IO::ZipDir
// initializes the internal structures // initializes the internal structures
// nFlags can have FLAGS_READ_ONLY flag, in this case the object will be opened only for reading // nFlags can have FLAGS_READ_ONLY flag, in this case the object will be opened only for reading
CacheFactory(InitMethodEnum nInitMethod, uint32_t nFlags = 0); CacheFactory(InitMethod nInitMethod, uint32_t nFlags = 0);
~CacheFactory(); ~CacheFactory();
// the new function creates a new cache // the new function creates a new cache
@ -66,28 +66,6 @@ namespace AZ::IO::ZipDir
// This function can actually modify strFilePath variable, make sure you use a copy of the real path. // This function can actually modify strFilePath variable, make sure you use a copy of the real path.
void AddFileEntry(char* strFilePath, const ZipFile::CDRFileHeader* pFileHeader, const SExtraZipFileData& extra);// throw (ErrorEnum); void AddFileEntry(char* strFilePath, const ZipFile::CDRFileHeader* pFileHeader, const SExtraZipFileData& extra);// throw (ErrorEnum);
// extracts the file path from the file header with subsequent information
// may, or may not, put all letters to lower-case (depending on whether the system is to be case-sensitive or not)
// it's the responsibility of the caller to ensure that the file name is in readable valid memory
char* GetFilePath(const ZipFile::CDRFileHeader* pFileHeader)
{
return GetFilePath((const char*)(pFileHeader + 1), pFileHeader->nFileNameLength);
}
// extracts the file path from the file header with subsequent information
// may, or may not, put all letters to lower-case (depending on whether the system is to be case-sensitive or not)
// it's the responsibility of the caller to ensure that the file name is in readable valid memory
char* GetFilePath(const ZipFile::LocalFileHeader* pFileHeader)
{
return GetFilePath((const char*)(pFileHeader + 1), pFileHeader->nFileNameLength);
}
// extracts the file path from the file header with subsequent information
// may, or may not, put all letters to lower-case (depending on whether the system is to be case-sensitive or not)
// it's the responsibility of the caller to ensure that the file name is in readable valid memory
char* GetFilePath(const char* pFileName, uint16_t nFileNameLength);
// validates (if the init method has the corresponding value) the given file/header
void Validate(const FileEntryBase& fileEntry);
// initializes the actual data offset in the file in the fileEntry structure // initializes the actual data offset in the file in the fileEntry structure
// searches to the local file header, reads it and calculates the actual offset in the file // searches to the local file header, reads it and calculates the actual offset in the file
void InitDataOffset(FileEntryBase& fileEntry, const ZipFile::CDRFileHeader* pFileHeader); void InitDataOffset(FileEntryBase& fileEntry, const ZipFile::CDRFileHeader* pFileHeader);
@ -104,7 +82,7 @@ namespace AZ::IO::ZipDir
AZStd::string m_szFilename; AZStd::string m_szFilename;
CZipFile m_fileExt; CZipFile m_fileExt;
InitMethodEnum m_nInitMethod; InitMethod m_nInitMethod;
uint32_t m_nFlags; uint32_t m_nFlags;
ZipFile::CDREnd m_CDREnd; ZipFile::CDREnd m_CDREnd;
@ -129,7 +107,7 @@ namespace AZ::IO::ZipDir
ZipFile::CryCustomEncryptionHeader m_headerEncryption; ZipFile::CryCustomEncryptionHeader m_headerEncryption;
ZipFile::CrySignedCDRHeader m_headerSignature; ZipFile::CrySignedCDRHeader m_headerSignature;
ZipFile::CryCustomExtendedHeader m_headerExtended; ZipFile::CryCustomExtendedHeader m_headerExtended;
}; };
} }

@ -68,14 +68,14 @@ namespace AZ::IO::ZipDir
{ {
for (FileEntryTree::SubdirMap::iterator it = pTree->GetDirBegin(); it != pTree->GetDirEnd(); ++it) for (FileEntryTree::SubdirMap::iterator it = pTree->GetDirBegin(); it != pTree->GetDirEnd(); ++it)
{ {
AddAllFiles(it->second.get(), (AZ::IO::Path(strRoot) / it->first).Native()); AddAllFiles(it->second.get(), (AZ::IO::Path(strRoot, AZ::IO::PosixPathSeparator) / it->first).Native());
} }
for (FileEntryTree::FileMap::iterator it = pTree->GetFileBegin(); it != pTree->GetFileEnd(); ++it) for (FileEntryTree::FileMap::iterator it = pTree->GetFileBegin(); it != pTree->GetFileEnd(); ++it)
{ {
FileRecord rec; FileRecord rec;
rec.pFileEntryBase = pTree->GetFileEntry(it); rec.pFileEntryBase = pTree->GetFileEntry(it);
rec.strPath = (AZ::IO::Path(strRoot) / it->first).Native(); rec.strPath = (AZ::IO::Path(strRoot, AZ::IO::PosixPathSeparator) / it->first).Native();
push_back(rec); push_back(rec);
} }
} }

@ -119,19 +119,28 @@ namespace AZ::IO::ZipDir
const char* m_szDescription; const char* m_szDescription;
}; };
#if defined(_RELEASE)
inline static constexpr bool IsReleaseConfig{ true };
#else
inline static constexpr bool IsReleaseConfig{};
#endif // _RELEASE
// possible initialization methods // possible initialization methods
enum InitMethodEnum enum class InitMethod
{ {
// initialize as fast as possible, with minimal validation // initializes without any sort of extra validation steps
ZD_INIT_FAST, Default,
// after initialization, scan through all file headers, precache the actual file data offset values and validate the headers
ZD_INIT_FULL, // initializes with extra validation steps
// scan all file headers and try to decompress the data, searching for corrupted files // not available in RELEASE
ZD_INIT_VALIDATE_IN_MEMORY, // will check CDR and local headers data match
// store archive in memory ValidateHeaders,
ZD_INIT_VALIDATE,
// maximum level of validation, checks for integrity of the archive // initializes with extra validation steps
ZD_INIT_VALIDATE_MAX = ZD_INIT_VALIDATE // not available in RELEASE
// will check CDR and local headers data match
// will check file data CRC matches (when file is read)
FullValidation,
}; };
// Uncompresses raw (without wrapping) data that is compressed with method 8 (deflated) in the Zip file // Uncompresses raw (without wrapping) data that is compressed with method 8 (deflated) in the Zip file
@ -184,7 +193,11 @@ namespace AZ::IO::ZipDir
// the offset to the start of the next file's header - this // the offset to the start of the next file's header - this
// can be used to calculate the available space in zip file // can be used to calculate the available space in zip file
uint32_t nEOFOffset{}; uint32_t nEOFOffset{};
// whether to check the CRC upon the next data read
bool bCheckCRCNextRead{};
}; };
// this is the record about the file in the Zip file. // this is the record about the file in the Zip file.
struct FileEntry struct FileEntry
: FileEntryBase : FileEntryBase

@ -281,6 +281,14 @@ namespace AZ
return SystemFile::Exists(resolvedPath); return SystemFile::Exists(resolvedPath);
} }
bool LocalFileIO::IsDirectory(const char* filePath)
{
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
return SystemFile::IsDirectory(resolvedPath);
}
void LocalFileIO::CheckInvalidWrite([[maybe_unused]] const char* path) void LocalFileIO::CheckInvalidWrite([[maybe_unused]] const char* path)
{ {
#if defined(AZ_ENABLE_TRACING) #if defined(AZ_ENABLE_TRACING)

@ -593,7 +593,7 @@ namespace AzFramework
DebugMessage("StartThread: Starting %s", thread.m_desc.m_name); DebugMessage("StartThread: Starting %s", thread.m_desc.m_name);
thread.m_join = false; thread.m_join = false;
thread.m_thread = AZStd::thread(thread.m_main, &thread.m_desc); thread.m_thread = AZStd::thread(thread.m_desc, thread.m_main);
} }
void AssetProcessorConnection::JoinThread(ThreadState& thread, AZStd::condition_variable* wakeUpCondition /* = nullptr */) void AssetProcessorConnection::JoinThread(ThreadState& thread, AZStd::condition_variable* wakeUpCondition /* = nullptr */)

@ -319,7 +319,7 @@ namespace AzFramework
AZStd::thread_desc td; AZStd::thread_desc td;
td.m_name = "TargetManager Thread"; td.m_name = "TargetManager Thread";
td.m_cpuId = AFFINITY_MASK_USERTHREADS; td.m_cpuId = AFFINITY_MASK_USERTHREADS;
m_threadHandle = AZStd::thread(AZStd::bind(&TargetManagementComponent::TickThread, this), &td); m_threadHandle = AZStd::thread(td, AZStd::bind(&TargetManagementComponent::TickThread, this));
} }
void TargetManagementComponent::Deactivate() void TargetManagementComponent::Deactivate()

@ -8,6 +8,7 @@
#pragma once #pragma once
#include <AzCore/EBus/EBus.h> #include <AzCore/EBus/EBus.h>
#include <AzCore/std/containers/set.h>
#include <AzCore/Math/Vector2.h> #include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h> #include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Aabb.h> #include <AzCore/Math/Aabb.h>
@ -17,16 +18,38 @@ namespace AzFramework
{ {
namespace SurfaceData namespace SurfaceData
{ {
namespace Constants
{
static const char* s_unassignedTagName = "(unassigned)";
}
struct SurfaceTagWeight struct SurfaceTagWeight
{ {
AZ_TYPE_INFO(SurfaceTagWeight, "{EA14018E-E853-4BF5-8E13-D83BB99A54CC}"); AZ_TYPE_INFO(SurfaceTagWeight, "{EA14018E-E853-4BF5-8E13-D83BB99A54CC}");
AZ::Crc32 m_surfaceType; AZ::Crc32 m_surfaceType = AZ::Crc32(Constants::s_unassignedTagName);
float m_weight; //! A Value in the range [0.0f .. 1.0f] float m_weight = 0.0f; //! A Value in the range [0.0f .. 1.0f]
//! Don't call this directly. TerrainDataRequests::Reflect is doing it already. //! Don't call this directly. TerrainDataRequests::Reflect is doing it already.
static void Reflect(AZ::ReflectContext* context); static void Reflect(AZ::ReflectContext* context);
}; };
struct SurfaceTagWeightComparator
{
bool operator()(const SurfaceTagWeight& tagWeight1, const SurfaceTagWeight& tagWeight2) const
{
if (!AZ::IsClose(tagWeight1.m_weight, tagWeight2.m_weight))
{
return tagWeight1.m_weight > tagWeight2.m_weight;
}
else
{
return tagWeight1.m_surfaceType > tagWeight2.m_surfaceType;
}
}
};
using OrderedSurfaceTagWeightSet = AZStd::set<SurfaceTagWeight, SurfaceTagWeightComparator>;
} //namespace SurfaceData } //namespace SurfaceData
namespace Terrain namespace Terrain
@ -75,8 +98,28 @@ namespace AzFramework
//! @terrainExists: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside a terrain HOLE then *terrainExistsPtr will be set to false, //! @terrainExists: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside a terrain HOLE then *terrainExistsPtr will be set to false,
//! otherwise *terrainExistsPtr will be set to true. //! otherwise *terrainExistsPtr will be set to true.
virtual SurfaceData::SurfaceTagWeight GetMaxSurfaceWeight(AZ::Vector3 position, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0; virtual SurfaceData::SurfaceTagWeight GetMaxSurfaceWeight(AZ::Vector3 position, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0;
virtual SurfaceData::SurfaceTagWeight GetMaxSurfaceWeightFromVector2(const AZ::Vector2& inPosition, Sampler sampleFilter = Sampler::DEFAULT, bool* terrainExistsPtr = nullptr) const = 0;
virtual SurfaceData::SurfaceTagWeight GetMaxSurfaceWeightFromFloats(float x, float y, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0; virtual SurfaceData::SurfaceTagWeight GetMaxSurfaceWeightFromFloats(float x, float y, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0;
//! Given an XY coordinate, return the set of surface types and weights. The Vector3 input position version is defined to ignore
//! the input Z value.
virtual void GetSurfaceWeights(
const AZ::Vector3& inPosition,
SurfaceData::OrderedSurfaceTagWeightSet& outSurfaceWeights,
Sampler sampleFilter = Sampler::DEFAULT,
bool* terrainExistsPtr = nullptr) const = 0;
virtual void GetSurfaceWeightsFromVector2(
const AZ::Vector2& inPosition,
SurfaceData::OrderedSurfaceTagWeightSet& outSurfaceWeights,
Sampler sampleFilter = Sampler::DEFAULT,
bool* terrainExistsPtr = nullptr) const = 0;
virtual void GetSurfaceWeightsFromFloats(
float x,
float y,
SurfaceData::OrderedSurfaceTagWeightSet& outSurfaceWeights,
Sampler sampleFilter = Sampler::DEFAULT,
bool* terrainExistsPtr = nullptr) const = 0;
//! Convenience function for low level systems that can't do a reverse lookup from Crc to string. Everyone else should use GetMaxSurfaceWeight or GetMaxSurfaceWeightFromFloats. //! Convenience function for low level systems that can't do a reverse lookup from Crc to string. Everyone else should use GetMaxSurfaceWeight or GetMaxSurfaceWeightFromFloats.
//! Not available in the behavior context. //! Not available in the behavior context.
//! Returns nullptr if the position is inside a hole or outside of the terrain boundaries. //! Returns nullptr if the position is inside a hole or outside of the terrain boundaries.

@ -28,9 +28,12 @@ namespace AzFramework
//! @note This is used to drive event driven updates to the visibility system. //! @note This is used to drive event driven updates to the visibility system.
virtual void RefreshEntityLocalBoundsUnion(AZ::EntityId entityId) = 0; virtual void RefreshEntityLocalBoundsUnion(AZ::EntityId entityId) = 0;
//! Returns the cached union of all component Aabbs. //! Returns the cached union of all component Aabbs in local entity space.
virtual AZ::Aabb GetEntityLocalBoundsUnion(AZ::EntityId entityId) const = 0; virtual AZ::Aabb GetEntityLocalBoundsUnion(AZ::EntityId entityId) const = 0;
//! Returns the cached union of all component Aabbs in world space.
virtual AZ::Aabb GetEntityWorldBoundsUnion(AZ::EntityId entityId) const = 0;
//! Writes the current changes made to all entities (transforms and bounds) to the visibility system. //! Writes the current changes made to all entities (transforms and bounds) to the visibility system.
//! @note During normal operation this is called every frame in OnTick but can //! @note During normal operation this is called every frame in OnTick but can
//! also be called explicitly (e.g. For testing purposes). //! also be called explicitly (e.g. For testing purposes).

@ -128,6 +128,24 @@ namespace AzFramework
return AZ::Aabb::CreateNull(); return AZ::Aabb::CreateNull();
} }
AZ::Aabb EntityVisibilityBoundsUnionSystem::GetEntityWorldBoundsUnion(const AZ::EntityId entityId) const
{
AZ::Entity* entity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(entityId);
if (entity != nullptr)
{
// if the entity is not found in the mapping then return a null Aabb, this is to mimic
// as closely as possible the behavior of an individual GetLocalBounds call to an Entity that
// had been deleted (there would be no response, leaving the default value assigned)
if (auto instance_it = m_entityVisibilityBoundsUnionInstanceMapping.find(entity);
instance_it != m_entityVisibilityBoundsUnionInstanceMapping.end())
{
return instance_it->second.m_localEntityBoundsUnion.GetTranslated(entity->GetTransform()->GetWorldTranslation());
}
}
return AZ::Aabb::CreateNull();
}
void EntityVisibilityBoundsUnionSystem::ProcessEntityBoundsUnionRequests() void EntityVisibilityBoundsUnionSystem::ProcessEntityBoundsUnionRequests()
{ {
AZ_PROFILE_FUNCTION(AzFramework); AZ_PROFILE_FUNCTION(AzFramework);

@ -31,6 +31,7 @@ namespace AzFramework
// EntityBoundsUnionRequestBus overrides ... // EntityBoundsUnionRequestBus overrides ...
void RefreshEntityLocalBoundsUnion(AZ::EntityId entityId) override; void RefreshEntityLocalBoundsUnion(AZ::EntityId entityId) override;
AZ::Aabb GetEntityLocalBoundsUnion(AZ::EntityId entityId) const override; AZ::Aabb GetEntityLocalBoundsUnion(AZ::EntityId entityId) const override;
AZ::Aabb GetEntityWorldBoundsUnion(AZ::EntityId entityId) const override;
void ProcessEntityBoundsUnionRequests() override; void ProcessEntityBoundsUnionRequests() override;
void OnTransformUpdated(AZ::Entity* entity) override; void OnTransformUpdated(AZ::Entity* entity) override;

@ -40,26 +40,6 @@ namespace AZ
{ {
namespace IO namespace IO
{ {
bool LocalFileIO::IsDirectory(const char* filePath)
{
ANDROID_IO_PROFILE_SECTION_ARGS("IsDir:%s", filePath);
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
if (AZ::Android::Utils::IsApkPath(resolvedPath))
{
return AZ::Android::APKFileHandler::IsDirectory(AZ::Android::Utils::StripApkPrefix(resolvedPath).c_str());
}
struct stat result;
if (stat(resolvedPath, &result) == 0)
{
return S_ISDIR(result.st_mode);
}
return false;
}
Result LocalFileIO::Copy(const char* sourceFilePath, const char* destinationFilePath) Result LocalFileIO::Copy(const char* sourceFilePath, const char* destinationFilePath)
{ {
char resolvedSourcePath[AZ_MAX_PATH_LEN]; char resolvedSourcePath[AZ_MAX_PATH_LEN];

@ -17,19 +17,6 @@ namespace AZ
{ {
namespace IO namespace IO
{ {
bool LocalFileIO::IsDirectory(const char* filePath)
{
char resolvedPath[AZ_MAX_PATH_LEN] = {0};
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
struct stat result;
if (stat(resolvedPath, &result) == 0)
{
return S_ISDIR(result.st_mode);
}
return false;
}
Result LocalFileIO::Copy(const char* sourceFilePath, const char* destinationFilePath) Result LocalFileIO::Copy(const char* sourceFilePath, const char* destinationFilePath)
{ {
char resolvedSourceFilePath[AZ_MAX_PATH_LEN] = {0}; char resolvedSourceFilePath[AZ_MAX_PATH_LEN] = {0};

@ -15,22 +15,6 @@ namespace AZ
{ {
namespace IO namespace IO
{ {
bool LocalFileIO::IsDirectory(const char* filePath)
{
char resolvedPath[AZ_MAX_PATH_LEN];
ResolvePath(filePath, resolvedPath, AZ_MAX_PATH_LEN);
wchar_t resolvedPathW[AZ_MAX_PATH_LEN];
AZStd::to_wstring(resolvedPathW, AZ_MAX_PATH_LEN, resolvedPath);
DWORD fileAttributes = GetFileAttributesW(resolvedPathW);
if (fileAttributes == INVALID_FILE_ATTRIBUTES)
{
return false;
}
return (fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
}
Result LocalFileIO::FindFiles(const char* filePath, const char* filter, FindFilesCallbackType callback) Result LocalFileIO::FindFiles(const char* filePath, const char* filter, FindFilesCallbackType callback)
{ {
char resolvedPath[AZ_MAX_PATH_LEN]; char resolvedPath[AZ_MAX_PATH_LEN];

@ -239,6 +239,8 @@ namespace AzFramework
RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes; RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes;
AzFramework::RawInputNotificationBusWindows::Broadcast( AzFramework::RawInputNotificationBusWindows::Broadcast(
&AzFramework::RawInputNotificationBusWindows::Events::OnRawInputEvent, *rawInput); &AzFramework::RawInputNotificationBusWindows::Events::OnRawInputEvent, *rawInput);
delete [] rawInputBytes;
break; break;
} }
case WM_CHAR: case WM_CHAR:

@ -14,7 +14,6 @@
#include <AzCore/UnitTest/TestTypes.h> #include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Utils/Utils.h> #include <AzCore/Utils/Utils.h>
#include <AzFramework/IO/LocalFileIO.h> #include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/IO/FileOperations.h> #include <AzFramework/IO/FileOperations.h>
#include <time.h> #include <time.h>
#include <AzTest/Utils.h> #include <AzTest/Utils.h>
@ -30,21 +29,6 @@ using namespace AZ;
using namespace AZ::IO; using namespace AZ::IO;
using namespace AZ::Debug; using namespace AZ::Debug;
namespace PathUtil
{
AZStd::string AddSlash(const AZStd::string& path)
{
if (path.empty() || path[path.length() - 1] == '/')
{
return path;
}
if (path[path.length() - 1] == '\\')
{
return path.substr(0, path.length() - 1) + "/";
}
return path + "/";
}
}
namespace UnitTest namespace UnitTest
{ {
@ -161,15 +145,16 @@ namespace UnitTest
: public ScopedAllocatorSetupFixture : public ScopedAllocatorSetupFixture
{ {
public: public:
AZStd::string m_root; AZ::Test::ScopedAutoTempDirectory m_tempDir;
AZStd::string folderName; AZ::IO::Path m_root;
AZStd::string deepFolder; AZ::IO::Path m_folderName;
AZStd::string extraFolder; AZ::IO::Path m_deepFolder;
AZ::IO::Path m_extraFolder;
AZStd::string fileRoot;
AZStd::string file01Name; AZ::IO::Path m_fileRoot;
AZStd::string file02Name; AZ::IO::Path m_file01Name;
AZStd::string file03Name; AZ::IO::Path m_file02Name;
AZ::IO::Path m_file03Name;
int m_randomFolderKey = 0; int m_randomFolderKey = 0;
FolderFixture() FolderFixture()
@ -179,43 +164,13 @@ namespace UnitTest
void ChooseRandomFolder() void ChooseRandomFolder()
{ {
char currentDir[AZ_MAX_PATH_LEN]; m_root = m_tempDir.GetDirectory();
AZ::Utils::GetExecutableDirectory(currentDir, AZ_MAX_PATH_LEN); m_folderName = m_root / AZStd::string::format("tmp%08x", m_randomFolderKey);
m_deepFolder = m_folderName / "test" / "subdir";
folderName = currentDir; m_extraFolder = m_deepFolder / "subdir2";
folderName.append("/temp");
m_root = folderName;
if (folderName.size() > 0)
{
folderName = PathUtil::AddSlash(folderName);
}
AZStd::string tempName = AZStd::string::format("tmp%08x", m_randomFolderKey);
folderName.append(tempName.c_str());
folderName = PathUtil::AddSlash(folderName);
AZStd::replace(folderName.begin(), folderName.end(), '\\', '/');
// Make sure the drive letter is capitalized
if (folderName.size() > 2)
{
if (folderName[1] == ':')
{
folderName[0] = static_cast<char>(toupper(folderName[0]));
}
}
deepFolder = folderName;
deepFolder.append("test");
deepFolder = PathUtil::AddSlash(deepFolder);
deepFolder.append("subdir");
extraFolder = deepFolder;
extraFolder = PathUtil::AddSlash(extraFolder);
extraFolder.append("subdir2");
// make a couple files there, and in the root: // make a couple files there, and in the root:
fileRoot = PathUtil::AddSlash(extraFolder); m_fileRoot = m_extraFolder;
} }
void SetUp() override void SetUp() override
@ -229,37 +184,33 @@ namespace UnitTest
{ {
ChooseRandomFolder(); ChooseRandomFolder();
++m_randomFolderKey; ++m_randomFolderKey;
} while (local.IsDirectory(fileRoot.c_str())); } while (local.IsDirectory(m_fileRoot.c_str()));
file01Name = fileRoot + "file01.txt"; m_file01Name = m_fileRoot / "file01.txt";
file02Name = fileRoot + "file02.asdf"; m_file02Name = m_fileRoot / "file02.asdf";
file03Name = fileRoot + "test123.wha"; m_file03Name = m_fileRoot / "test123.wha";
} }
void TearDown() override void TearDown() override
{ {
if ((!folderName.empty())&&(strstr(folderName.c_str(), "/temp") != nullptr))
{
// cleanup!
LocalFileIO local;
local.DestroyPath(folderName.c_str());
}
} }
void CreateTestFiles() void CreateTestFiles()
{ {
constexpr auto openMode = SystemFile::OpenMode::SF_OPEN_WRITE_ONLY
| SystemFile::OpenMode::SF_OPEN_CREATE
| SystemFile::OpenMode::SF_OPEN_CREATE_NEW;
constexpr AZStd::string_view testContent("this is just a test");
LocalFileIO local; LocalFileIO local;
AZ_TEST_ASSERT(local.CreatePath(fileRoot.c_str())); AZ_TEST_ASSERT(local.CreatePath(m_fileRoot.c_str()));
AZ_TEST_ASSERT(local.IsDirectory(fileRoot.c_str())); AZ_TEST_ASSERT(local.IsDirectory(m_fileRoot.c_str()));
for (const AZStd::string& filename : { file01Name, file02Name, file03Name }) for (const AZ::IO::Path& filename : { m_file01Name, m_file02Name, m_file03Name })
{ {
#ifdef AZ_COMPILER_MSVC SystemFile tempFile;
FILE* tempFile; tempFile.Open(filename.c_str(), openMode);
fopen_s(&tempFile, filename.c_str(), "wb");
#else tempFile.Write(testContent.data(), testContent.size());
FILE* tempFile = fopen(filename.c_str(), "wb"); tempFile.Close();
#endif
fwrite("this is just a test", 1, 19, tempFile);
fclose(tempFile);
} }
} }
}; };
@ -272,28 +223,23 @@ namespace UnitTest
{ {
LocalFileIO local; LocalFileIO local;
AZ_TEST_ASSERT(!local.Exists(folderName.c_str())); AZ_TEST_ASSERT(!local.Exists(m_folderName.c_str()));
AZStd::string longPathCreateTest = folderName; AZ::IO::Path longPathCreateTest = m_folderName / "one" / "two" / "three";
longPathCreateTest.append("one");
longPathCreateTest = PathUtil::AddSlash(longPathCreateTest);
longPathCreateTest.append("two");
longPathCreateTest = PathUtil::AddSlash(longPathCreateTest);
longPathCreateTest.append("three");
AZ_TEST_ASSERT(!local.Exists(longPathCreateTest.c_str())); AZ_TEST_ASSERT(!local.Exists(longPathCreateTest.c_str()));
AZ_TEST_ASSERT(!local.IsDirectory(longPathCreateTest.c_str())); AZ_TEST_ASSERT(!local.IsDirectory(longPathCreateTest.c_str()));
AZ_TEST_ASSERT(local.CreatePath(longPathCreateTest.c_str())); AZ_TEST_ASSERT(local.CreatePath(longPathCreateTest.c_str()));
AZ_TEST_ASSERT(local.IsDirectory(longPathCreateTest.c_str())); AZ_TEST_ASSERT(local.IsDirectory(longPathCreateTest.c_str()));
AZ_TEST_ASSERT(!local.Exists(deepFolder.c_str())); AZ_TEST_ASSERT(!local.Exists(m_deepFolder.c_str()));
AZ_TEST_ASSERT(!local.IsDirectory(deepFolder.c_str())); AZ_TEST_ASSERT(!local.IsDirectory(m_deepFolder.c_str()));
AZ_TEST_ASSERT(local.CreatePath(deepFolder.c_str())); AZ_TEST_ASSERT(local.CreatePath(m_deepFolder.c_str()));
AZ_TEST_ASSERT(local.IsDirectory(deepFolder.c_str())); AZ_TEST_ASSERT(local.IsDirectory(m_deepFolder.c_str()));
AZ_TEST_ASSERT(local.Exists(deepFolder.c_str())); AZ_TEST_ASSERT(local.Exists(m_deepFolder.c_str()));
AZ_TEST_ASSERT(local.CreatePath(deepFolder.c_str())); AZ_TEST_ASSERT(local.CreatePath(m_deepFolder.c_str()));
AZ_TEST_ASSERT(local.Exists(deepFolder.c_str())); AZ_TEST_ASSERT(local.Exists(m_deepFolder.c_str()));
} }
}; };
@ -310,16 +256,19 @@ namespace UnitTest
{ {
LocalFileIO local; LocalFileIO local;
AZ_TEST_ASSERT(!local.Exists(fileRoot.c_str())); AZ_TEST_ASSERT(!local.Exists(m_fileRoot.c_str()));
AZ_TEST_ASSERT(!local.IsDirectory(fileRoot.c_str())); AZ_TEST_ASSERT(!local.IsDirectory(m_fileRoot.c_str()));
AZ_TEST_ASSERT(local.CreatePath(fileRoot.c_str())); AZ_TEST_ASSERT(local.CreatePath(m_fileRoot.c_str()));
AZ_TEST_ASSERT(local.IsDirectory(fileRoot.c_str())); AZ_TEST_ASSERT(local.IsDirectory(m_fileRoot.c_str()));
FILE* tempFile = nullptr;
azfopen(&tempFile, file01Name.c_str(), "wb");
fwrite("this is just a test", 1, 19, tempFile); constexpr auto openMode = SystemFile::OpenMode::SF_OPEN_WRITE_ONLY
fclose(tempFile); | SystemFile::OpenMode::SF_OPEN_CREATE
| SystemFile::OpenMode::SF_OPEN_CREATE_NEW;
SystemFile tempFile;
tempFile.Open(m_file01Name.c_str(), openMode);
constexpr AZStd::string_view testContent("this is just a test");
tempFile.Write(testContent.data(), testContent.size());
tempFile.Close();
AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle; AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
AZ_TEST_ASSERT(!local.Open("", AZ::IO::OpenMode::ModeWrite, fileHandle)); AZ_TEST_ASSERT(!local.Open("", AZ::IO::OpenMode::ModeWrite, fileHandle));
@ -327,12 +276,12 @@ namespace UnitTest
// test size without opening: // test size without opening:
AZ::u64 fs = 0; AZ::u64 fs = 0;
AZ_TEST_ASSERT(local.Size(file01Name.c_str(), fs)); AZ_TEST_ASSERT(local.Size(m_file01Name.c_str(), fs));
AZ_TEST_ASSERT(fs == 19); AZ_TEST_ASSERT(fs == 19);
fileHandle = AZ::IO::InvalidHandle; fileHandle = AZ::IO::InvalidHandle;
AZ::u64 modTimeA = local.ModificationTime(file01Name.c_str()); AZ::u64 modTimeA = local.ModificationTime(m_file01Name.c_str());
AZ_TEST_ASSERT(modTimeA != 0); AZ_TEST_ASSERT(modTimeA != 0);
// test invalid handle ops: // test invalid handle ops:
@ -344,14 +293,14 @@ namespace UnitTest
AZ_TEST_ASSERT(!local.Read(fileHandle, nullptr, 0, false)); AZ_TEST_ASSERT(!local.Read(fileHandle, nullptr, 0, false));
AZ_TEST_ASSERT(!local.Tell(fileHandle, fs)); AZ_TEST_ASSERT(!local.Tell(fileHandle, fs));
AZ_TEST_ASSERT(!local.Exists((file01Name + "notexist").c_str())); AZ_TEST_ASSERT(!local.Exists((m_file01Name.Native() + "notexist").c_str()));
AZ_TEST_ASSERT(local.Exists(file01Name.c_str())); AZ_TEST_ASSERT(local.Exists(m_file01Name.c_str()));
AZ_TEST_ASSERT(!local.IsReadOnly(file01Name.c_str())); AZ_TEST_ASSERT(!local.IsReadOnly(m_file01Name.c_str()));
AZ_TEST_ASSERT(!local.IsDirectory(file01Name.c_str())); AZ_TEST_ASSERT(!local.IsDirectory(m_file01Name.c_str()));
// test reads and seeks. // test reads and seeks.
AZ_TEST_ASSERT(local.Open(file01Name.c_str(), AZ::IO::OpenMode::ModeRead, fileHandle)); AZ_TEST_ASSERT(local.Open(m_file01Name.c_str(), AZ::IO::OpenMode::ModeRead, fileHandle));
AZ_TEST_ASSERT(fileHandle != AZ::IO::InvalidHandle); AZ_TEST_ASSERT(fileHandle != AZ::IO::InvalidHandle);
// use this again later... // use this again later...
@ -368,7 +317,7 @@ namespace UnitTest
// test size without opening, after its already open: // test size without opening, after its already open:
fs = 0; fs = 0;
AZ_TEST_ASSERT(local.Size(file01Name.c_str(), fs)); AZ_TEST_ASSERT(local.Size(m_file01Name.c_str(), fs));
AZ_TEST_ASSERT(fs == 19); AZ_TEST_ASSERT(fs == 19);
AZ::u64 offs = 0; AZ::u64 offs = 0;
@ -442,22 +391,22 @@ namespace UnitTest
#if AZ_TRAIT_AZFRAMEWORKTEST_PERFORM_CHMOD_TEST #if AZ_TRAIT_AZFRAMEWORKTEST_PERFORM_CHMOD_TEST
#if AZ_TRAIT_USE_WINDOWS_FILE_API #if AZ_TRAIT_USE_WINDOWS_FILE_API
_chmod(file01Name.c_str(), _S_IREAD); _chmod(m_file01Name.c_str(), _S_IREAD);
#else #else
chmod(file01Name.c_str(), S_IRUSR | S_IRGRP | S_IROTH); chmod(m_file01Name.c_str(), S_IRUSR | S_IRGRP | S_IROTH);
#endif #endif
AZ_TEST_ASSERT(local.IsReadOnly(file01Name.c_str())); AZ_TEST_ASSERT(local.IsReadOnly(m_file01Name.c_str()));
#if AZ_TRAIT_USE_WINDOWS_FILE_API #if AZ_TRAIT_USE_WINDOWS_FILE_API
_chmod(file01Name.c_str(), _S_IREAD | _S_IWRITE); _chmod(m_file01Name.c_str(), _S_IREAD | _S_IWRITE);
#else #else
chmod(file01Name.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); chmod(m_file01Name.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
#endif #endif
#endif #endif
AZ_TEST_ASSERT(!local.IsReadOnly(file01Name.c_str())); AZ_TEST_ASSERT(!local.IsReadOnly(m_file01Name.c_str()));
} }
}; };
@ -474,14 +423,14 @@ namespace UnitTest
{ {
LocalFileIO local; LocalFileIO local;
AZ_TEST_ASSERT(local.CreatePath(fileRoot.c_str())); AZ_TEST_ASSERT(local.CreatePath(m_fileRoot.c_str()));
AZ_TEST_ASSERT(local.IsDirectory(fileRoot.c_str())); AZ_TEST_ASSERT(local.IsDirectory(m_fileRoot.c_str()));
{ {
#ifdef AZ_COMPILER_MSVC #ifdef AZ_COMPILER_MSVC
FILE* tempFile; FILE* tempFile;
fopen_s(&tempFile, file01Name.c_str(), "wb"); fopen_s(&tempFile, m_file01Name.c_str(), "wb");
#else #else
FILE* tempFile = fopen(file01Name.c_str(), "wb"); FILE* tempFile = fopen(m_file01Name.c_str(), "wb");
#endif #endif
fwrite("this is just a test", 1, 19, tempFile); fwrite("this is just a test", 1, 19, tempFile);
fclose(tempFile); fclose(tempFile);
@ -489,47 +438,47 @@ namespace UnitTest
// make sure attributes are copied (such as modtime) even if they're copied: // make sure attributes are copied (such as modtime) even if they're copied:
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(1500)); AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(1500));
AZ_TEST_ASSERT(local.Copy(file01Name.c_str(), file02Name.c_str())); AZ_TEST_ASSERT(local.Copy(m_file01Name.c_str(), m_file02Name.c_str()));
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(1500)); AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(1500));
AZ_TEST_ASSERT(local.Copy(file01Name.c_str(), file03Name.c_str())); AZ_TEST_ASSERT(local.Copy(m_file01Name.c_str(), m_file03Name.c_str()));
AZ_TEST_ASSERT(local.Exists(file01Name.c_str())); AZ_TEST_ASSERT(local.Exists(m_file01Name.c_str()));
AZ_TEST_ASSERT(local.Exists(file02Name.c_str())); AZ_TEST_ASSERT(local.Exists(m_file02Name.c_str()));
AZ_TEST_ASSERT(local.Exists(file03Name.c_str())); AZ_TEST_ASSERT(local.Exists(m_file03Name.c_str()));
AZ_TEST_ASSERT(!local.DestroyPath(file01Name.c_str())); // you may not destroy files. AZ_TEST_ASSERT(!local.DestroyPath(m_file01Name.c_str())); // you may not destroy files.
AZ_TEST_ASSERT(!local.DestroyPath(file02Name.c_str())); AZ_TEST_ASSERT(!local.DestroyPath(m_file02Name.c_str()));
AZ_TEST_ASSERT(!local.DestroyPath(file03Name.c_str())); AZ_TEST_ASSERT(!local.DestroyPath(m_file03Name.c_str()));
AZ_TEST_ASSERT(local.Exists(file01Name.c_str())); AZ_TEST_ASSERT(local.Exists(m_file01Name.c_str()));
AZ_TEST_ASSERT(local.Exists(file02Name.c_str())); AZ_TEST_ASSERT(local.Exists(m_file02Name.c_str()));
AZ_TEST_ASSERT(local.Exists(file03Name.c_str())); AZ_TEST_ASSERT(local.Exists(m_file03Name.c_str()));
AZ::u64 f1s = 0; AZ::u64 f1s = 0;
AZ::u64 f2s = 0; AZ::u64 f2s = 0;
AZ::u64 f3s = 0; AZ::u64 f3s = 0;
AZ_TEST_ASSERT(local.Size(file01Name.c_str(), f1s)); AZ_TEST_ASSERT(local.Size(m_file01Name.c_str(), f1s));
AZ_TEST_ASSERT(local.Size(file02Name.c_str(), f2s)); AZ_TEST_ASSERT(local.Size(m_file02Name.c_str(), f2s));
AZ_TEST_ASSERT(local.Size(file03Name.c_str(), f3s)); AZ_TEST_ASSERT(local.Size(m_file03Name.c_str(), f3s));
AZ_TEST_ASSERT(f1s == f2s); AZ_TEST_ASSERT(f1s == f2s);
AZ_TEST_ASSERT(f1s == f3s); AZ_TEST_ASSERT(f1s == f3s);
// Copying over top other files is allowed // Copying over top other files is allowed
SystemFile file; SystemFile file;
EXPECT_TRUE(file.Open(file01Name.c_str(), SystemFile::SF_OPEN_WRITE_ONLY)); EXPECT_TRUE(file.Open(m_file01Name.c_str(), SystemFile::SF_OPEN_WRITE_ONLY));
file.Write("this is just a test that is longer", 34); file.Write("this is just a test that is longer", 34);
file.Close(); file.Close();
// make sure attributes are copied (such as modtime) even if they're copied: // make sure attributes are copied (such as modtime) even if they're copied:
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(1500)); AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(1500));
EXPECT_TRUE(local.Copy(file01Name.c_str(), file02Name.c_str())); EXPECT_TRUE(local.Copy(m_file01Name.c_str(), m_file02Name.c_str()));
f1s = 0; f1s = 0;
f2s = 0; f2s = 0;
f3s = 0; f3s = 0;
EXPECT_TRUE(local.Size(file01Name.c_str(), f1s)); EXPECT_TRUE(local.Size(m_file01Name.c_str(), f1s));
EXPECT_TRUE(local.Size(file02Name.c_str(), f2s)); EXPECT_TRUE(local.Size(m_file02Name.c_str(), f2s));
EXPECT_TRUE(local.Size(file03Name.c_str(), f3s)); EXPECT_TRUE(local.Size(m_file03Name.c_str(), f3s));
EXPECT_EQ(f1s, f2s); EXPECT_EQ(f1s, f2s);
EXPECT_NE(f1s, f3s); EXPECT_NE(f1s, f3s);
} }
@ -552,37 +501,37 @@ namespace UnitTest
AZ::u64 modTimeC = 0; AZ::u64 modTimeC = 0;
AZ::u64 modTimeD = 0; AZ::u64 modTimeD = 0;
modTimeC = local.ModificationTime(file02Name.c_str()); modTimeC = local.ModificationTime(m_file02Name.c_str());
modTimeD = local.ModificationTime(file03Name.c_str()); modTimeD = local.ModificationTime(m_file03Name.c_str());
// make sure modtimes are in ascending order (at least) // make sure modtimes are in ascending order (at least)
AZ_TEST_ASSERT(modTimeD >= modTimeC); AZ_TEST_ASSERT(modTimeD >= modTimeC);
// now touch some of the files. This is also how we test append mode, and write mode. // now touch some of the files. This is also how we test append mode, and write mode.
AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle; AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
AZ_TEST_ASSERT(local.Open(file02Name.c_str(), AZ::IO::OpenMode::ModeAppend | AZ::IO::OpenMode::ModeBinary, fileHandle)); AZ_TEST_ASSERT(local.Open(m_file02Name.c_str(), AZ::IO::OpenMode::ModeAppend | AZ::IO::OpenMode::ModeBinary, fileHandle));
AZ_TEST_ASSERT(fileHandle != AZ::IO::InvalidHandle); AZ_TEST_ASSERT(fileHandle != AZ::IO::InvalidHandle);
AZ_TEST_ASSERT(local.Write(fileHandle, "more", 4)); AZ_TEST_ASSERT(local.Write(fileHandle, "more", 4));
AZ_TEST_ASSERT(local.Close(fileHandle)); AZ_TEST_ASSERT(local.Close(fileHandle));
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(1500)); AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(1500));
// No-append-mode // No-append-mode
AZ_TEST_ASSERT(local.Open(file03Name.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary, fileHandle)); AZ_TEST_ASSERT(local.Open(m_file03Name.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary, fileHandle));
AZ_TEST_ASSERT(fileHandle != AZ::IO::InvalidHandle); AZ_TEST_ASSERT(fileHandle != AZ::IO::InvalidHandle);
AZ_TEST_ASSERT(local.Write(fileHandle, "more", 4)); AZ_TEST_ASSERT(local.Write(fileHandle, "more", 4));
AZ_TEST_ASSERT(local.Close(fileHandle)); AZ_TEST_ASSERT(local.Close(fileHandle));
modTimeC = local.ModificationTime(file02Name.c_str()); modTimeC = local.ModificationTime(m_file02Name.c_str());
modTimeD = local.ModificationTime(file03Name.c_str()); modTimeD = local.ModificationTime(m_file03Name.c_str());
AZ_TEST_ASSERT(modTimeD > modTimeC); AZ_TEST_ASSERT(modTimeD > modTimeC);
AZ::u64 f1s = 0; AZ::u64 f1s = 0;
AZ::u64 f2s = 0; AZ::u64 f2s = 0;
AZ::u64 f3s = 0; AZ::u64 f3s = 0;
AZ_TEST_ASSERT(local.Size(file01Name.c_str(), f1s)); AZ_TEST_ASSERT(local.Size(m_file01Name.c_str(), f1s));
AZ_TEST_ASSERT(local.Size(file02Name.c_str(), f2s)); AZ_TEST_ASSERT(local.Size(m_file02Name.c_str(), f2s));
AZ_TEST_ASSERT(local.Size(file03Name.c_str(), f3s)); AZ_TEST_ASSERT(local.Size(m_file03Name.c_str(), f3s));
AZ_TEST_ASSERT(f2s == f1s + 4); AZ_TEST_ASSERT(f2s == f1s + 4);
AZ_TEST_ASSERT(f3s == 4); AZ_TEST_ASSERT(f3s == 4);
} }
@ -603,8 +552,8 @@ namespace UnitTest
CreateTestFiles(); CreateTestFiles();
AZStd::vector<AZStd::string> resultFiles; AZStd::vector<AZ::IO::Path> resultFiles;
bool foundOK = local.FindFiles(fileRoot.c_str(), "*", bool foundOK = local.FindFiles(m_fileRoot.c_str(), "*",
[&](const char* filePath) -> bool [&](const char* filePath) -> bool
{ {
resultFiles.push_back(filePath); resultFiles.push_back(filePath);
@ -616,7 +565,7 @@ namespace UnitTest
resultFiles.clear(); resultFiles.clear();
foundOK = local.FindFiles(fileRoot.c_str(), "*", foundOK = local.FindFiles(m_fileRoot.c_str(), "*",
[&](const char* filePath) -> bool [&](const char* filePath) -> bool
{ {
resultFiles.push_back(filePath); resultFiles.push_back(filePath);
@ -627,7 +576,7 @@ namespace UnitTest
AZ_TEST_ASSERT(resultFiles.size() == 3); AZ_TEST_ASSERT(resultFiles.size() == 3);
// note: following tests accumulate more files without clearing resultfiles. // note: following tests accumulate more files without clearing resultfiles.
foundOK = local.FindFiles(fileRoot.c_str(), "*.txt", foundOK = local.FindFiles(m_fileRoot.c_str(), "*.txt",
[&](const char* filePath) -> bool [&](const char* filePath) -> bool
{ {
resultFiles.push_back(filePath); resultFiles.push_back(filePath);
@ -637,7 +586,7 @@ namespace UnitTest
AZ_TEST_ASSERT(foundOK); AZ_TEST_ASSERT(foundOK);
AZ_TEST_ASSERT(resultFiles.size() == 4); AZ_TEST_ASSERT(resultFiles.size() == 4);
foundOK = local.FindFiles(fileRoot.c_str(), "file*.asdf", foundOK = local.FindFiles(m_fileRoot.c_str(), "file*.asdf",
[&](const char* filePath) -> bool [&](const char* filePath) -> bool
{ {
resultFiles.push_back(filePath); resultFiles.push_back(filePath);
@ -647,7 +596,7 @@ namespace UnitTest
AZ_TEST_ASSERT(foundOK); AZ_TEST_ASSERT(foundOK);
AZ_TEST_ASSERT(resultFiles.size() == 5); AZ_TEST_ASSERT(resultFiles.size() == 5);
foundOK = local.FindFiles(fileRoot.c_str(), "asaf.asdf", foundOK = local.FindFiles(m_fileRoot.c_str(), "asaf.asdf",
[&](const char* filePath) -> bool [&](const char* filePath) -> bool
{ {
resultFiles.push_back(filePath); resultFiles.push_back(filePath);
@ -660,7 +609,7 @@ namespace UnitTest
resultFiles.clear(); resultFiles.clear();
// test to make sure directories show up: // test to make sure directories show up:
foundOK = local.FindFiles(deepFolder.c_str(), "*", foundOK = local.FindFiles(m_deepFolder.c_str(), "*",
[&](const char* filePath) -> bool [&](const char* filePath) -> bool
{ {
resultFiles.push_back(filePath); resultFiles.push_back(filePath);
@ -668,11 +617,11 @@ namespace UnitTest
}); });
// canonicalize the name in the same way that find does. // canonicalize the name in the same way that find does.
//AZStd::replace() extraFolder.replace('\\', '/'); FIXME PPATEL //AZStd::replace() m_extraFolder.replace('\\', '/'); FIXME PPATEL
AZ_TEST_ASSERT(foundOK); AZ_TEST_ASSERT(foundOK);
AZ_TEST_ASSERT(resultFiles.size() == 1); AZ_TEST_ASSERT(resultFiles.size() == 1);
AZ_TEST_ASSERT(resultFiles[0] == extraFolder); AZ_TEST_ASSERT(resultFiles[0] == m_extraFolder);
resultFiles.clear(); resultFiles.clear();
foundOK = local.FindFiles("o:137787621!@#$%^&&**())_+[])_", "asaf.asdf", foundOK = local.FindFiles("o:137787621!@#$%^&&**())_+[])_", "asaf.asdf",
[&](const char* filePath) -> bool [&](const char* filePath) -> bool
@ -684,13 +633,13 @@ namespace UnitTest
AZ_TEST_ASSERT(!foundOK); AZ_TEST_ASSERT(!foundOK);
AZ_TEST_ASSERT(resultFiles.size() == 0); AZ_TEST_ASSERT(resultFiles.size() == 0);
AZStd::string file04Name = fileRoot + "test.wha"; AZ::IO::Path file04Name = m_fileRoot / "test.wha";
// test rename // test rename
AZ_TEST_ASSERT(local.Rename(file03Name.c_str(), file04Name.c_str())); AZ_TEST_ASSERT(local.Rename(m_file03Name.c_str(), file04Name.c_str()));
AZ_TEST_ASSERT(!local.Rename(file03Name.c_str(), file04Name.c_str())); AZ_TEST_ASSERT(!local.Rename(m_file03Name.c_str(), file04Name.c_str()));
AZ_TEST_ASSERT(local.Rename(file04Name.c_str(), file04Name.c_str())); // this is valid and ok AZ_TEST_ASSERT(local.Rename(file04Name.c_str(), file04Name.c_str())); // this is valid and ok
AZ_TEST_ASSERT(local.Exists(file04Name.c_str())); AZ_TEST_ASSERT(local.Exists(file04Name.c_str()));
AZ_TEST_ASSERT(!local.Exists(file03Name.c_str())); AZ_TEST_ASSERT(!local.Exists(m_file03Name.c_str()));
AZ_TEST_ASSERT(!local.IsDirectory(file04Name.c_str())); AZ_TEST_ASSERT(!local.IsDirectory(file04Name.c_str()));
AZ::u64 f3s = 0; AZ::u64 f3s = 0;
@ -698,8 +647,8 @@ namespace UnitTest
AZ_TEST_ASSERT(f3s == 19); AZ_TEST_ASSERT(f3s == 19);
// deep destroy directory: // deep destroy directory:
AZ_TEST_ASSERT(local.DestroyPath(folderName.c_str())); AZ_TEST_ASSERT(local.DestroyPath(m_folderName.c_str()));
AZ_TEST_ASSERT(!local.Exists(folderName.c_str())); AZ_TEST_ASSERT(!local.Exists(m_folderName.c_str()));
} }
}; };
@ -715,7 +664,7 @@ namespace UnitTest
AZ::IO::LocalFileIO local; AZ::IO::LocalFileIO local;
// test aliases // test aliases
local.SetAlias("@test@", folderName.c_str()); local.SetAlias("@test@", m_folderName.c_str());
const char* testDest1 = local.GetAlias("@test@"); const char* testDest1 = local.GetAlias("@test@");
AZ_TEST_ASSERT(testDest1 != nullptr); AZ_TEST_ASSERT(testDest1 != nullptr);
const char* testDest2 = local.GetAlias("@NOPE@"); const char* testDest2 = local.GetAlias("@NOPE@");
@ -725,18 +674,18 @@ namespace UnitTest
// test resolving // test resolving
const char* aliasTestPath = "@test@\\some\\path\\somefile.txt"; const char* aliasTestPath = "@test@\\some\\path\\somefile.txt";
char aliasResolvedPath[AZ_MAX_PATH_LEN]; char aliasResolvedPath[AZ::IO::MaxPathLength];
bool resolveDidWork = local.ResolvePath(aliasTestPath, aliasResolvedPath, AZ_MAX_PATH_LEN); bool resolveDidWork = local.ResolvePath(aliasTestPath, aliasResolvedPath, AZ::IO::MaxPathLength);
AZ_TEST_ASSERT(resolveDidWork); AZ_TEST_ASSERT(resolveDidWork);
AZStd::string expectedResolvedPath = folderName + "some/path/somefile.txt"; AZ::IO::Path expectedResolvedPath = m_folderName / "some/path/somefile.txt";
AZ_TEST_ASSERT(aliasResolvedPath == expectedResolvedPath); AZ_TEST_ASSERT(aliasResolvedPath == expectedResolvedPath);
// more resolve path tests with invalid inputs // more resolve path tests with invalid inputs
const char* testPath = nullptr; const char* testPath = nullptr;
char* testResolvedPath = nullptr; char* testResolvedPath = nullptr;
resolveDidWork = local.ResolvePath(testPath, aliasResolvedPath, AZ_MAX_PATH_LEN); resolveDidWork = local.ResolvePath(testPath, aliasResolvedPath, AZ::IO::MaxPathLength);
AZ_TEST_ASSERT(!resolveDidWork); AZ_TEST_ASSERT(!resolveDidWork);
resolveDidWork = local.ResolvePath(aliasTestPath, testResolvedPath, AZ_MAX_PATH_LEN); resolveDidWork = local.ResolvePath(aliasTestPath, testResolvedPath, AZ::IO::MaxPathLength);
AZ_TEST_ASSERT(!resolveDidWork); AZ_TEST_ASSERT(!resolveDidWork);
resolveDidWork = local.ResolvePath(aliasTestPath, aliasResolvedPath, 0); resolveDidWork = local.ResolvePath(aliasTestPath, aliasResolvedPath, 0);
AZ_TEST_ASSERT(!resolveDidWork); AZ_TEST_ASSERT(!resolveDidWork);
@ -751,7 +700,7 @@ namespace UnitTest
// Test that sending in a too small output path fails, // Test that sending in a too small output path fails,
// if the output buffer is too small to hold the resolved path // if the output buffer is too small to hold the resolved path
size_t SMALLER_THAN_FINAL_RESOLVED_PATH = expectedResolvedPath.length() - 1; size_t SMALLER_THAN_FINAL_RESOLVED_PATH = expectedResolvedPath.Native().length() - 1;
AZ_TEST_START_TRACE_SUPPRESSION; AZ_TEST_START_TRACE_SUPPRESSION;
resolveDidWork = local.ResolvePath(aliasTestPath, aliasResolvedPath, SMALLER_THAN_FINAL_RESOLVED_PATH); resolveDidWork = local.ResolvePath(aliasTestPath, aliasResolvedPath, SMALLER_THAN_FINAL_RESOLVED_PATH);
AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_STOP_TRACE_SUPPRESSION(1);
@ -766,22 +715,23 @@ namespace UnitTest
TEST_F(AliasTest, ResolvePath_PathViewOverload_Succeeds) TEST_F(AliasTest, ResolvePath_PathViewOverload_Succeeds)
{ {
AZ::IO::LocalFileIO local; AZ::IO::LocalFileIO local;
local.SetAlias("@test@", folderName.c_str()); local.SetAlias("@test@", m_folderName.c_str());
AZ::IO::PathView aliasTestPath = "@test@\\some\\path\\somefile.txt"; AZ::IO::PathView aliasTestPath = "@test@\\some\\path\\somefile.txt";
AZ::IO::FixedMaxPath aliasResolvedPath; AZ::IO::FixedMaxPath aliasResolvedPath;
ASSERT_TRUE(local.ResolvePath(aliasResolvedPath, aliasTestPath)); ASSERT_TRUE(local.ResolvePath(aliasResolvedPath, aliasTestPath));
const auto expectedResolvedPath = AZ::IO::FixedMaxPathString::format("%ssome/path/somefile.txt", folderName.c_str()); AZ::IO::Path expectedResolvedPath = m_folderName / "some" / "path" / "somefile.txt";
EXPECT_STREQ(expectedResolvedPath.c_str(), aliasResolvedPath.c_str());
EXPECT_EQ(expectedResolvedPath, aliasResolvedPath);
AZStd::optional<AZ::IO::FixedMaxPath> optionalResolvedPath = local.ResolvePath(aliasTestPath); AZStd::optional<AZ::IO::FixedMaxPath> optionalResolvedPath = local.ResolvePath(aliasTestPath);
ASSERT_TRUE(optionalResolvedPath); ASSERT_TRUE(optionalResolvedPath);
EXPECT_STREQ(expectedResolvedPath.c_str(), optionalResolvedPath->c_str()); EXPECT_EQ(expectedResolvedPath, optionalResolvedPath.value());
} }
TEST_F(AliasTest, ResolvePath_PathViewOverloadWithEmptyPath_Fails) TEST_F(AliasTest, ResolvePath_PathViewOverloadWithEmptyPath_Fails)
{ {
AZ::IO::LocalFileIO local; AZ::IO::LocalFileIO local;
local.SetAlias("@test@", folderName.c_str()); local.SetAlias("@test@", m_folderName.c_str());
AZ::IO::FixedMaxPath aliasResolvedPath; AZ::IO::FixedMaxPath aliasResolvedPath;
EXPECT_FALSE(local.ResolvePath(aliasResolvedPath, {})); EXPECT_FALSE(local.ResolvePath(aliasResolvedPath, {}));
} }
@ -860,24 +810,23 @@ namespace UnitTest
{ {
LocalFileIO localFileIO; LocalFileIO localFileIO;
AZ::IO::FileIOBase::SetInstance(&localFileIO); AZ::IO::FileIOBase::SetInstance(&localFileIO);
AZStd::string path; AZ::IO::Path path = m_file01Name.ParentPath();
AzFramework::StringFunc::Path::GetFullPath(file01Name.c_str(), path);
AZ_TEST_ASSERT(localFileIO.CreatePath(path.c_str())); AZ_TEST_ASSERT(localFileIO.CreatePath(path.c_str()));
AzFramework::StringFunc::Path::GetFullPath(file02Name.c_str(), path); path = m_file01Name.ParentPath();
AZ_TEST_ASSERT(localFileIO.CreatePath(path.c_str())); AZ_TEST_ASSERT(localFileIO.CreatePath(path.c_str()));
AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle; AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
localFileIO.Open(file01Name.c_str(), OpenMode::ModeWrite | OpenMode::ModeText, fileHandle); localFileIO.Open(m_file01Name.c_str(), OpenMode::ModeWrite | OpenMode::ModeText, fileHandle);
localFileIO.Write(fileHandle, "DummyFile", 9); localFileIO.Write(fileHandle, "DummyFile", 9);
localFileIO.Close(fileHandle); localFileIO.Close(fileHandle);
AZ::IO::HandleType fileHandle1 = AZ::IO::InvalidHandle; AZ::IO::HandleType fileHandle1 = AZ::IO::InvalidHandle;
localFileIO.Open(file02Name.c_str(), OpenMode::ModeWrite | OpenMode::ModeText, fileHandle1); localFileIO.Open(m_file02Name.c_str(), OpenMode::ModeWrite | OpenMode::ModeText, fileHandle1);
localFileIO.Write(fileHandle1, "TestFile", 8); localFileIO.Write(fileHandle1, "TestFile", 8);
localFileIO.Close(fileHandle1); localFileIO.Close(fileHandle1);
fileHandle1 = AZ::IO::InvalidHandle; fileHandle1 = AZ::IO::InvalidHandle;
localFileIO.Open(file02Name.c_str(), OpenMode::ModeRead | OpenMode::ModeText, fileHandle1); localFileIO.Open(m_file02Name.c_str(), OpenMode::ModeRead | OpenMode::ModeText, fileHandle1);
static const size_t testStringLen = 256; static const size_t testStringLen = 256;
char testString[testStringLen] = { 0 }; char testString[testStringLen] = { 0 };
localFileIO.Read(fileHandle1, testString, testStringLen); localFileIO.Read(fileHandle1, testString, testStringLen);
@ -885,50 +834,50 @@ namespace UnitTest
AZ_TEST_ASSERT(strncmp(testString, "TestFile", 8) == 0); AZ_TEST_ASSERT(strncmp(testString, "TestFile", 8) == 0);
// try swapping files when none of the files are in use // try swapping files when none of the files are in use
AZ_TEST_ASSERT(AZ::IO::SmartMove(file01Name.c_str(), file02Name.c_str())); AZ_TEST_ASSERT(AZ::IO::SmartMove(m_file01Name.c_str(), m_file02Name.c_str()));
fileHandle1 = AZ::IO::InvalidHandle; fileHandle1 = AZ::IO::InvalidHandle;
localFileIO.Open(file02Name.c_str(), OpenMode::ModeRead | OpenMode::ModeText, fileHandle1); localFileIO.Open(m_file02Name.c_str(), OpenMode::ModeRead | OpenMode::ModeText, fileHandle1);
testString[0] = '\0'; testString[0] = '\0';
localFileIO.Read(fileHandle1, testString, testStringLen); localFileIO.Read(fileHandle1, testString, testStringLen);
localFileIO.Close(fileHandle1); localFileIO.Close(fileHandle1);
AZ_TEST_ASSERT(strncmp(testString, "DummyFile", 9) == 0); AZ_TEST_ASSERT(strncmp(testString, "DummyFile", 9) == 0);
//try swapping files when source file is not present, this should fail //try swapping files when source file is not present, this should fail
AZ_TEST_ASSERT(!AZ::IO::SmartMove(file01Name.c_str(), file02Name.c_str())); AZ_TEST_ASSERT(!AZ::IO::SmartMove(m_file01Name.c_str(), m_file02Name.c_str()));
fileHandle = AZ::IO::InvalidHandle; fileHandle = AZ::IO::InvalidHandle;
localFileIO.Open(file01Name.c_str(), OpenMode::ModeWrite | OpenMode::ModeText, fileHandle); localFileIO.Open(m_file01Name.c_str(), OpenMode::ModeWrite | OpenMode::ModeText, fileHandle);
localFileIO.Write(fileHandle, "TestFile", 8); localFileIO.Write(fileHandle, "TestFile", 8);
localFileIO.Close(fileHandle); localFileIO.Close(fileHandle);
#if AZ_TRAIT_AZFRAMEWORKTEST_MOVE_WHILE_OPEN #if AZ_TRAIT_AZFRAMEWORKTEST_MOVE_WHILE_OPEN
fileHandle1 = AZ::IO::InvalidHandle; fileHandle1 = AZ::IO::InvalidHandle;
localFileIO.Open(file02Name.c_str(), OpenMode::ModeRead | OpenMode::ModeText, fileHandle1); localFileIO.Open(m_file02Name.c_str(), OpenMode::ModeRead | OpenMode::ModeText, fileHandle1);
testString[0] = '\0'; testString[0] = '\0';
localFileIO.Read(fileHandle1, testString, testStringLen); localFileIO.Read(fileHandle1, testString, testStringLen);
// try swapping files when the destination file is open for read only, // try swapping files when the destination file is open for read only,
// since window is unable to move files that are open for read, this will fail. // since window is unable to move files that are open for read, this will fail.
AZ_TEST_ASSERT(!AZ::IO::SmartMove(file01Name.c_str(), file02Name.c_str())); AZ_TEST_ASSERT(!AZ::IO::SmartMove(m_file01Name.c_str(), m_file02Name.c_str()));
localFileIO.Close(fileHandle1); localFileIO.Close(fileHandle1);
#endif #endif
fileHandle = AZ::IO::InvalidHandle; fileHandle = AZ::IO::InvalidHandle;
localFileIO.Open(file01Name.c_str(), OpenMode::ModeRead | OpenMode::ModeText, fileHandle); localFileIO.Open(m_file01Name.c_str(), OpenMode::ModeRead | OpenMode::ModeText, fileHandle);
// try swapping files when the source file is open for read only // try swapping files when the source file is open for read only
AZ_TEST_ASSERT(AZ::IO::SmartMove(file01Name.c_str(), file02Name.c_str())); AZ_TEST_ASSERT(AZ::IO::SmartMove(m_file01Name.c_str(), m_file02Name.c_str()));
localFileIO.Close(fileHandle); localFileIO.Close(fileHandle);
fileHandle1 = AZ::IO::InvalidHandle; fileHandle1 = AZ::IO::InvalidHandle;
localFileIO.Open(file02Name.c_str(), OpenMode::ModeRead | OpenMode::ModeText, fileHandle1); localFileIO.Open(m_file02Name.c_str(), OpenMode::ModeRead | OpenMode::ModeText, fileHandle1);
testString[0] = '\0'; testString[0] = '\0';
localFileIO.Read(fileHandle1, testString, testStringLen); localFileIO.Read(fileHandle1, testString, testStringLen);
AZ_TEST_ASSERT(strncmp(testString, "TestFile", 8) == 0); AZ_TEST_ASSERT(strncmp(testString, "TestFile", 8) == 0);
localFileIO.Close(fileHandle1); localFileIO.Close(fileHandle1);
localFileIO.Remove(file01Name.c_str()); localFileIO.Remove(m_file01Name.c_str());
localFileIO.Remove(file02Name.c_str()); localFileIO.Remove(m_file02Name.c_str());
localFileIO.DestroyPath(m_root.c_str()); localFileIO.DestroyPath(m_root.c_str());
AZ::IO::FileIOBase::SetInstance(nullptr); AZ::IO::FileIOBase::SetInstance(nullptr);

@ -28,20 +28,22 @@ namespace AzGameFramework
// can read from the FileIOBase instance if available // can read from the FileIOBase instance if available
m_settingsRegistry->SetUseFileIO(true); m_settingsRegistry->SetUseFileIO(true);
// Attempt to mount the engine pak from the Executable Directory // Attempt to mount the engine pak to the project product asset alias
// at the Assets alias, otherwise to attempting to mount the engine pak // Search Order:
// from the Cache folder // - Project Cache Root Directory
AZ::IO::FixedMaxPath enginePakPath = AZ::Utils::GetExecutableDirectory(); // - Executable Directory
enginePakPath /= "engine.pak"; bool enginePakOpened{};
if (!m_archive->OpenPack("@assets@", enginePakPath.Native())) AZ::IO::FixedMaxPath enginePakPath;
if (m_settingsRegistry->Get(enginePakPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder))
{ {
enginePakPath.clear(); // fall back to checking Project Cache Root.
if (m_settingsRegistry->Get(enginePakPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder)) enginePakPath /= "engine.pak";
{ enginePakOpened = m_archive->OpenPack("@projectproductassets@", enginePakPath.Native());
// fall back to checking Project Cache Root. }
enginePakPath /= "engine.pak"; if (!enginePakOpened)
m_archive->OpenPack("@assets@", enginePakPath.Native()); {
} enginePakPath = AZ::IO::FixedMaxPath(AZ::Utils::GetExecutableDirectory()) / "engine.pak";
m_archive->OpenPack("@projectproductassets@", enginePakPath.Native());
} }
} }

@ -6,16 +6,25 @@ namespace {{ xml.attrib['Name'] }}
{ {
switch (aznumeric_cast<int32_t>(packetHeader.GetPacketType())) switch (aznumeric_cast<int32_t>(packetHeader.GetPacketType()))
{ {
{% set packet_ns = namespace(handshake=false) %}
{% for Packet in xml.iter('Packet') %}
{% if ('HandshakePacket' in Packet.attrib) and (Packet.attrib['HandshakePacket']|booleanTrue == true) %}
{% set packet_ns.handshake = True %}
{% endif %}
{% endfor %}
{% for Packet in xml.iter('Packet') %} {% for Packet in xml.iter('Packet') %}
case aznumeric_cast<int32_t>({{ Packet.attrib['Name'] }}::Type): case aznumeric_cast<int32_t>({{ Packet.attrib['Name'] }}::Type):
{ {
AZLOG(Debug_DispatchPackets, "Received packet %s", "{{ Packet.attrib['Name'] }}"); AZLOG(Debug_DispatchPackets, "Received packet %s", "{{ Packet.attrib['Name'] }}");
{% if ('HandshakePacket' not in Packet.attrib) or (Packet.attrib['HandshakePacket'] == 'false') %} {% if packet_ns.handshake %}
if (!handler.IsHandshakeComplete()) {% if ('HandshakePacket' not in Packet.attrib) or (Packet.attrib['HandshakePacket'] == 'false') %}
if (!handler.IsHandshakeComplete(connection))
{ {
return AzNetworking::PacketDispatchResult::Skipped; return AzNetworking::PacketDispatchResult::Skipped;
} }
{% endif %} {% endif %}
{% endif %}
{{ Packet.attrib['Name'] }} packet; {{ Packet.attrib['Name'] }} packet;
if (!serializer.Serialize(packet, "Packet")) if (!serializer.Serialize(packet, "Packet"))

@ -94,6 +94,7 @@ namespace AzNetworking
class ITimeoutHandler class ITimeoutHandler
{ {
public: public:
virtual ~ITimeoutHandler() = default;
//! Handler callback for timed out items. //! Handler callback for timed out items.
//! @param item containing registered timeout details //! @param item containing registered timeout details

@ -103,13 +103,13 @@ namespace AzNetworking
//! @return boolean true on success //! @return boolean true on success
virtual bool Disconnect(ConnectionId connectionId, DisconnectReason reason) = 0; virtual bool Disconnect(ConnectionId connectionId, DisconnectReason reason) = 0;
//! Sets whether this connection interface can disconnect by virtue of a timeout //! Sets the timeout time in milliseconds, 0 ms means timeouts are disabled.
//! @param timeoutEnabled If this connection interface will automatically disconnect due to a timeout //! @param timeoutMs the number of milliseconds with no traffic before we timeout and close a connection
virtual void SetTimeoutEnabled(bool timeoutEnabled) = 0; virtual void SetTimeoutMs(AZ::TimeMs timeoutMs) = 0;
//! Whether this connection interface will disconnect by virtue of a time out (does not account for cvars affecting all connections) //! Retrieves the timeout time in milliseconds for this network interface, 0 ms means timeouts are disabled.
//! @return boolean true if this connection will not disconnect on timeout (does not account for cvars affecting all connections) //! @return the timeout time in milliseconds for this network interface, 0 ms means timeouts are disabled
virtual bool IsTimeoutEnabled() const = 0; virtual AZ::TimeMs GetTimeoutMs() const = 0;
//! Const access to the metrics tracked by this network interface. //! Const access to the metrics tracked by this network interface.
//! @return const reference to the metrics tracked by this network interface //! @return const reference to the metrics tracked by this network interface

@ -321,4 +321,19 @@ namespace AzNetworking
return serializer.IsValid(); return serializer.IsValid();
} }
}; };
template <>
struct SerializeObjectHelper<AZ::Aabb>
{
static bool SerializeObject(ISerializer& serializer, AZ::Aabb& value)
{
AZ::Vector3 minValue = value.GetMin();
AZ::Vector3 maxValue = value.GetMax();
serializer.Serialize(minValue, "minValue");
serializer.Serialize(maxValue, "maxValue");
value.SetMin(minValue);
value.SetMax(maxValue);
return serializer.IsValid();
}
};
} }

@ -22,14 +22,15 @@ namespace AzNetworking
#endif #endif
AZ_CVAR(bool, net_TcpTimeoutConnections, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Boolean value on whether we should timeout Tcp connections"); AZ_CVAR(bool, net_TcpTimeoutConnections, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Boolean value on whether we should timeout Tcp connections");
AZ_CVAR(AZ::TimeMs, net_TcpHearthbeatTimeMs, AZ::TimeMs{ 2 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Tcp connection heartbeat frequency"); AZ_CVAR(AZ::TimeMs, net_TcpHeartbeatTimeMs, AZ::TimeMs{ 2 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Tcp connection heartbeat frequency");
AZ_CVAR(AZ::TimeMs, net_TcpTimeoutTimeMs, AZ::TimeMs{ 10 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time in milliseconds before we timeout an idle Tcp connection"); AZ_CVAR(AZ::TimeMs, net_TcpDefaultTimeoutMs, AZ::TimeMs{ 10 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time in milliseconds before we timeout an idle Tcp connection");
TcpNetworkInterface::TcpNetworkInterface(AZ::Name name, IConnectionListener& connectionListener, TrustZone trustZone, TcpListenThread& listenThread) TcpNetworkInterface::TcpNetworkInterface(AZ::Name name, IConnectionListener& connectionListener, TrustZone trustZone, TcpListenThread& listenThread)
: m_name(name) : m_name(name)
, m_trustZone(trustZone) , m_trustZone(trustZone)
, m_connectionListener(connectionListener) , m_connectionListener(connectionListener)
, m_listenThread(listenThread) , m_listenThread(listenThread)
, m_timeoutMs(net_TcpDefaultTimeoutMs)
{ {
; ;
} }
@ -97,7 +98,7 @@ namespace AzNetworking
} }
AZLOG_INFO("Adding new socket %d", static_cast<int32_t>(tcpSocket->GetSocketFd())); AZLOG_INFO("Adding new socket %d", static_cast<int32_t>(tcpSocket->GetSocketFd()));
const TimeoutId newTimeoutId = m_connectionTimeoutQueue.RegisterItem(static_cast<uint64_t>(tcpSocket->GetSocketFd()), net_TcpHearthbeatTimeMs); const TimeoutId newTimeoutId = m_connectionTimeoutQueue.RegisterItem(static_cast<uint64_t>(tcpSocket->GetSocketFd()), net_TcpHeartbeatTimeMs);
connection->SetTimeoutId(newTimeoutId); connection->SetTimeoutId(newTimeoutId);
connection->SendReliablePacket(CorePackets::InitiateConnectionPacket()); connection->SendReliablePacket(CorePackets::InitiateConnectionPacket());
m_connectionListener.OnConnect(connection.get()); m_connectionListener.OnConnect(connection.get());
@ -174,14 +175,14 @@ namespace AzNetworking
return connection->Disconnect(reason, TerminationEndpoint::Local); return connection->Disconnect(reason, TerminationEndpoint::Local);
} }
void TcpNetworkInterface::SetTimeoutEnabled(bool timeoutEnabled) void TcpNetworkInterface::SetTimeoutMs(AZ::TimeMs timeoutMs)
{ {
m_timeoutEnabled = timeoutEnabled; m_timeoutMs = timeoutMs;
} }
bool TcpNetworkInterface::IsTimeoutEnabled() const AZ::TimeMs TcpNetworkInterface::GetTimeoutMs() const
{ {
return m_timeoutEnabled; return m_timeoutMs;
} }
void TcpNetworkInterface::QueueNewConnection(const PendingConnection& pendingConnection) void TcpNetworkInterface::QueueNewConnection(const PendingConnection& pendingConnection)
@ -257,7 +258,7 @@ namespace AzNetworking
return; return;
} }
AZLOG(NET_TcpTraffic, "Adding new socket %d", static_cast<int32_t>(tcpSocket.GetSocketFd())); AZLOG(NET_TcpTraffic, "Adding new socket %d", static_cast<int32_t>(tcpSocket.GetSocketFd()));
const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(static_cast<uint64_t>(tcpSocket.GetSocketFd()), net_TcpTimeoutTimeMs); const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(static_cast<uint64_t>(tcpSocket.GetSocketFd()), m_timeoutMs);
AZStd::unique_ptr<TcpConnection> connection = AZStd::make_unique<TcpConnection>(connectionId, remoteAddress, *this, tcpSocket, timeoutId); AZStd::unique_ptr<TcpConnection> connection = AZStd::make_unique<TcpConnection>(connectionId, remoteAddress, *this, tcpSocket, timeoutId);
AZ_Assert(connection->GetConnectionRole() == ConnectionRole::Acceptor, "Invalid role for connection"); AZ_Assert(connection->GetConnectionRole() == ConnectionRole::Acceptor, "Invalid role for connection");
GetConnectionListener().OnConnect(connection.get()); GetConnectionListener().OnConnect(connection.get());
@ -316,7 +317,7 @@ namespace AzNetworking
{ {
tcpConnection->SendReliablePacket(CorePackets::HeartbeatPacket()); tcpConnection->SendReliablePacket(CorePackets::HeartbeatPacket());
} }
else if (net_TcpTimeoutConnections && m_networkInterface.IsTimeoutEnabled()) else if (net_TcpTimeoutConnections && (m_networkInterface.GetTimeoutMs() > AZ::TimeMs{ 0 }))
{ {
tcpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local); tcpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local);
return TimeoutResult::Delete; return TimeoutResult::Delete;

@ -99,8 +99,8 @@ namespace AzNetworking
bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) override; bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) override;
bool StopListening() override; bool StopListening() override;
bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override; bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override;
void SetTimeoutEnabled(bool timeoutEnabled) override; void SetTimeoutMs(AZ::TimeMs timeoutMs) override;
bool IsTimeoutEnabled() const override; AZ::TimeMs GetTimeoutMs() const override;
//! @} //! @}
//! Queues a new incoming connection for this network interface. //! Queues a new incoming connection for this network interface.
@ -156,7 +156,7 @@ namespace AzNetworking
AZ::Name m_name; AZ::Name m_name;
TrustZone m_trustZone; TrustZone m_trustZone;
uint16_t m_port = 0; uint16_t m_port = 0;
bool m_timeoutEnabled = true; AZ::TimeMs m_timeoutMs = AZ::TimeMs{ 0 };
IConnectionListener& m_connectionListener; IConnectionListener& m_connectionListener;
TcpConnectionSet m_connectionSet; TcpConnectionSet m_connectionSet;
TcpSocketManager m_tcpSocketManager; TcpSocketManager m_tcpSocketManager;

@ -53,18 +53,11 @@ namespace AzNetworking
{ {
Close(); Close();
if (!SocketCreateInternal()) if (!SocketCreateInternal()
{ || !BindSocketForListenInternal(port)
return false; || !(SetSocketNonBlocking(m_socketFd) && SetSocketNoDelay(m_socketFd)))
}
if (!BindSocketForListenInternal(port))
{
return false;
}
if (!(SetSocketNonBlocking(m_socketFd) && SetSocketNoDelay(m_socketFd)))
{ {
Close();
return false; return false;
} }
@ -75,18 +68,11 @@ namespace AzNetworking
{ {
Close(); Close();
if (!SocketCreateInternal()) if (!SocketCreateInternal()
{ || !BindSocketForConnectInternal(address)
return false; || !(SetSocketNonBlocking(m_socketFd) && SetSocketNoDelay(m_socketFd)))
}
if (!BindSocketForConnectInternal(address))
{
return false;
}
if (!(SetSocketNonBlocking(m_socketFd) && SetSocketNoDelay(m_socketFd)))
{ {
Close();
return false; return false;
} }

@ -31,8 +31,8 @@ namespace AzNetworking
AZ_CVAR(bool, net_UdpTimeoutConnections, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Boolean value on whether we should timeout Udp connections"); AZ_CVAR(bool, net_UdpTimeoutConnections, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Boolean value on whether we should timeout Udp connections");
AZ_CVAR(AZ::TimeMs, net_UdpPacketTimeSliceMs, AZ::TimeMs{ 8 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The number of milliseconds to allow for packet processing"); AZ_CVAR(AZ::TimeMs, net_UdpPacketTimeSliceMs, AZ::TimeMs{ 8 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The number of milliseconds to allow for packet processing");
AZ_CVAR(AZ::TimeMs, net_UdpHearthbeatTimeMs, AZ::TimeMs{ 2 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Udp connection heartbeat frequency"); AZ_CVAR(AZ::TimeMs, net_UdpHeartbeatTimeMs, AZ::TimeMs{ 2 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Udp connection heartbeat frequency");
AZ_CVAR(AZ::TimeMs, net_UdpTimeoutTimeMs, AZ::TimeMs{ 10 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time in milliseconds before we timeout an idle Udp connection"); AZ_CVAR(AZ::TimeMs, net_UdpDefaultTimeoutMs, AZ::TimeMs{ 10 * 1000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Time in milliseconds before we timeout an idle Udp connection");
AZ_CVAR(AZ::TimeMs, net_MinPacketTimeoutMs, AZ::TimeMs{ 200 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Minimum time to wait before timing out an unacked packet"); AZ_CVAR(AZ::TimeMs, net_MinPacketTimeoutMs, AZ::TimeMs{ 200 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Minimum time to wait before timing out an unacked packet");
AZ_CVAR(int32_t, net_MaxTimeoutsPerFrame, 1000, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Maximum number of packet timeouts to allow to process in a single frame"); AZ_CVAR(int32_t, net_MaxTimeoutsPerFrame, 1000, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Maximum number of packet timeouts to allow to process in a single frame");
AZ_CVAR(float, net_RttFudgeScalar, 2.0f, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Scalar value to multiply computed Rtt by to determine an optimal packet timeout threshold"); AZ_CVAR(float, net_RttFudgeScalar, 2.0f, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Scalar value to multiply computed Rtt by to determine an optimal packet timeout threshold");
@ -61,6 +61,7 @@ namespace AzNetworking
, m_connectionListener(connectionListener) , m_connectionListener(connectionListener)
, m_socket(net_UdpUseEncryption ? new DtlsSocket() : new UdpSocket()) , m_socket(net_UdpUseEncryption ? new DtlsSocket() : new UdpSocket())
, m_readerThread(readerThread) , m_readerThread(readerThread)
, m_timeoutMs(net_UdpDefaultTimeoutMs)
{ {
const AZ::CVarFixedString compressor = static_cast<AZ::CVarFixedString>(net_UdpCompressor); const AZ::CVarFixedString compressor = static_cast<AZ::CVarFixedString>(net_UdpCompressor);
const AZ::Name compressorName = AZ::Name(compressor); const AZ::Name compressorName = AZ::Name(compressor);
@ -138,7 +139,7 @@ namespace AzNetworking
} }
const ConnectionId connectionId = m_connectionSet.GetNextConnectionId(); const ConnectionId connectionId = m_connectionSet.GetNextConnectionId();
const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(aznumeric_cast<uint64_t>(connectionId), net_UdpHearthbeatTimeMs); const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(aznumeric_cast<uint64_t>(connectionId), m_timeoutMs);
AZStd::unique_ptr<UdpConnection> connection = AZStd::make_unique<UdpConnection>(connectionId, remoteAddress, *this, ConnectionRole::Connector); AZStd::unique_ptr<UdpConnection> connection = AZStd::make_unique<UdpConnection>(connectionId, remoteAddress, *this, ConnectionRole::Connector);
UdpPacketEncodingBuffer dtlsData; UdpPacketEncodingBuffer dtlsData;
@ -403,14 +404,14 @@ namespace AzNetworking
return connection->Disconnect(reason, TerminationEndpoint::Local); return connection->Disconnect(reason, TerminationEndpoint::Local);
} }
void UdpNetworkInterface::SetTimeoutEnabled(bool timeoutEnabled) void UdpNetworkInterface::SetTimeoutMs(AZ::TimeMs timeoutMs)
{ {
m_timeoutEnabled = timeoutEnabled; m_timeoutMs = timeoutMs;
} }
bool UdpNetworkInterface::IsTimeoutEnabled() const AZ::TimeMs UdpNetworkInterface::GetTimeoutMs() const
{ {
return m_timeoutEnabled; return m_timeoutMs;
} }
bool UdpNetworkInterface::IsEncrypted() const bool UdpNetworkInterface::IsEncrypted() const
@ -681,7 +682,7 @@ namespace AzNetworking
// How long should we sit in the timeout queue before heartbeating or disconnecting // How long should we sit in the timeout queue before heartbeating or disconnecting
const ConnectionId connectionId = m_connectionSet.GetNextConnectionId(); const ConnectionId connectionId = m_connectionSet.GetNextConnectionId();
const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(aznumeric_cast<uint64_t>(connectionId), net_UdpTimeoutTimeMs); const TimeoutId timeoutId = m_connectionTimeoutQueue.RegisterItem(aznumeric_cast<uint64_t>(connectionId), m_timeoutMs);
AZLOG(Debug_UdpConnect, "Accepted new Udp Connection"); AZLOG(Debug_UdpConnect, "Accepted new Udp Connection");
AZStd::unique_ptr<UdpConnection> connection = AZStd::make_unique<UdpConnection>(connectionId, connectPacket.m_address, *this, ConnectionRole::Acceptor); AZStd::unique_ptr<UdpConnection> connection = AZStd::make_unique<UdpConnection>(connectionId, connectPacket.m_address, *this, ConnectionRole::Acceptor);
@ -745,7 +746,7 @@ namespace AzNetworking
{ {
udpConnection->SendUnreliablePacket(CorePackets::HeartbeatPacket()); udpConnection->SendUnreliablePacket(CorePackets::HeartbeatPacket());
} }
else if (net_UdpTimeoutConnections && m_networkInterface.IsTimeoutEnabled()) else if (net_UdpTimeoutConnections && (m_networkInterface.GetTimeoutMs() > AZ::TimeMs{ 0 }))
{ {
udpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local); udpConnection->Disconnect(DisconnectReason::Timeout, TerminationEndpoint::Local);
return TimeoutResult::Delete; return TimeoutResult::Delete;

@ -104,8 +104,8 @@ namespace AzNetworking
bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) override; bool WasPacketAcked(ConnectionId connectionId, PacketId packetId) override;
bool StopListening() override; bool StopListening() override;
bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override; bool Disconnect(ConnectionId connectionId, DisconnectReason reason) override;
void SetTimeoutEnabled(bool timeoutEnabled) override; void SetTimeoutMs(AZ::TimeMs timeoutMs) override;
bool IsTimeoutEnabled() const override; AZ::TimeMs GetTimeoutMs() const override;
//! @} //! @}
//! Returns true if this is an encrypted socket, false if not. //! Returns true if this is an encrypted socket, false if not.
@ -181,7 +181,7 @@ namespace AzNetworking
TrustZone m_trustZone; TrustZone m_trustZone;
uint16_t m_port = 0; uint16_t m_port = 0;
bool m_allowIncomingConnections = false; bool m_allowIncomingConnections = false;
bool m_timeoutEnabled = true; AZ::TimeMs m_timeoutMs = AZ::TimeMs{ 0 };
IConnectionListener& m_connectionListener; IConnectionListener& m_connectionListener;
UdpConnectionSet m_connectionSet; UdpConnectionSet m_connectionSet;
TimeoutQueue m_connectionTimeoutQueue; TimeoutQueue m_connectionTimeoutQueue;

@ -79,17 +79,15 @@ namespace AzNetworking
{ {
const int32_t error = GetLastNetworkError(); const int32_t error = GetLastNetworkError();
AZLOG_ERROR("Failed to bind UDP socket to port %u (%d:%s)", uint32_t(port), error, GetNetworkErrorDesc(error)); AZLOG_ERROR("Failed to bind UDP socket to port %u (%d:%s)", uint32_t(port), error, GetNetworkErrorDesc(error));
Close();
return false; return false;
} }
} }
if (!SetSocketBufferSizes(m_socketFd, net_UdpSendBufferSize, net_UdpRecvBufferSize)) if (!SetSocketBufferSizes(m_socketFd, net_UdpSendBufferSize, net_UdpRecvBufferSize)
{ || !SetSocketNonBlocking(m_socketFd))
return false;
}
if (!SetSocketNonBlocking(m_socketFd))
{ {
Close();
return false; return false;
} }

@ -26,27 +26,29 @@ namespace AzNetworking
{ {
m_running = true; m_running = true;
m_joinable = true; m_joinable = true;
m_thread = AZStd::thread([this]() m_thread = AZStd::thread(
{ m_threadDesc,
OnStart(); [this]()
while (m_running)
{ {
const AZ::TimeMs startTimeMs = AZ::GetElapsedTimeMs(); OnStart();
OnUpdate(m_updateRate); while (m_running)
const AZ::TimeMs updateTimeMs = AZ::GetElapsedTimeMs() - startTimeMs;
if (m_updateRate > updateTimeMs)
{ {
AZStd::chrono::milliseconds sleepTimeMs(static_cast<int64_t>(m_updateRate - updateTimeMs)); const AZ::TimeMs startTimeMs = AZ::GetElapsedTimeMs();
AZStd::this_thread::sleep_for(sleepTimeMs); OnUpdate(m_updateRate);
} const AZ::TimeMs updateTimeMs = AZ::GetElapsedTimeMs() - startTimeMs;
else if (m_updateRate < updateTimeMs)
{ if (m_updateRate > updateTimeMs)
AZLOG(NET_TimedThread, "TimedThread bled %d ms", aznumeric_cast<int32_t>(updateTimeMs - m_updateRate)); {
AZStd::chrono::milliseconds sleepTimeMs(static_cast<int64_t>(m_updateRate - updateTimeMs));
AZStd::this_thread::sleep_for(sleepTimeMs);
}
else if (m_updateRate < updateTimeMs)
{
AZLOG(NET_TimedThread, "TimedThread bled %d ms", aznumeric_cast<int32_t>(updateTimeMs - m_updateRate));
}
} }
} OnStop();
OnStop(); });
}, &m_threadDesc);
} }
void TimedThread::Stop() void TimedThread::Stop()

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save