Skip to content

Commit 3b6b18e

Browse files
committed
Merge branch 'development' of https://github.com/o3de/o3de into daimini/dragAndDropAdjustment
Signed-off-by: Danilo Aimini <[email protected]>
2 parents 92da67e + f45ceb5 commit 3b6b18e

File tree

23 files changed

+526
-147
lines changed

23 files changed

+526
-147
lines changed

Code/Framework/AzCore/AzCore/IO/Streamer/FileRequest.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,12 @@ namespace AZ::IO::Requests
270270

271271
namespace AZ::IO
272272
{
273+
class Streamer_SchedulerTest_RequestSorting_Test;
274+
273275
class FileRequest final
274276
{
277+
friend Streamer_SchedulerTest_RequestSorting_Test;
278+
275279
public:
276280
inline constexpr static AZStd::chrono::steady_clock::time_point s_noDeadlineTime = AZStd::chrono::steady_clock::time_point::max();
277281

@@ -407,6 +411,7 @@ namespace AZ::IO
407411
friend class StreamerContext;
408412
friend class Scheduler;
409413
friend class Device;
414+
friend class Streamer_SchedulerTest_RequestSorting_Test;
410415
friend bool operator==(const FileRequestHandle& lhs, const FileRequestPtr& rhs);
411416

412417
public:

Code/Framework/AzCore/AzCore/IO/Streamer/Scheduler.cpp

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,11 @@ namespace AZ::IO
501501

502502
auto Scheduler::Thread_PrioritizeRequests(const FileRequest* first, const FileRequest* second) const -> Order
503503
{
504+
if (first == second)
505+
{
506+
return Order::Equal;
507+
}
508+
504509
// Sort by order priority of the command in the request. This allows to for instance have cancel request
505510
// always happen before any other requests.
506511
auto order = [](auto&& args)
@@ -544,7 +549,12 @@ namespace AZ::IO
544549
}
545550

546551
// If neither has started and have the same priority, prefer to start the closest deadline.
547-
return firstRead->m_deadline <= secondRead->m_deadline ? Order::FirstRequest : Order::SecondRequest;
552+
if (firstRead->m_deadline == secondRead->m_deadline)
553+
{
554+
return Order::Equal;
555+
}
556+
557+
return firstRead->m_deadline < secondRead->m_deadline ? Order::FirstRequest : Order::SecondRequest;
548558
}
549559

550560
// Check if one of the requests is in panic and prefer to prioritize that request
@@ -593,7 +603,13 @@ namespace AZ::IO
593603
s64 secondReadOffset = AZStd::visit(offset, second->GetCommand());
594604
s64 firstSeekDistance = abs(aznumeric_cast<s64>(m_threadData.m_lastFileOffset) - firstReadOffset);
595605
s64 secondSeekDistance = abs(aznumeric_cast<s64>(m_threadData.m_lastFileOffset) - secondReadOffset);
596-
return firstSeekDistance <= secondSeekDistance ? Order::FirstRequest : Order::SecondRequest;
606+
607+
if (firstSeekDistance == secondSeekDistance)
608+
{
609+
return Order::Equal;
610+
}
611+
612+
return firstSeekDistance < secondSeekDistance ? Order::FirstRequest : Order::SecondRequest;
597613
}
598614

599615
// Prefer to continue in the same file so prioritize the request that's in the same file
@@ -625,6 +641,13 @@ namespace AZ::IO
625641
"Scheduler::Thread_ScheduleRequests - Sorting %i requests", m_context.GetNumPreparedRequests());
626642
auto sorter = [this](const FileRequest* lhs, const FileRequest* rhs) -> bool
627643
{
644+
if (lhs == rhs)
645+
{
646+
// AZStd::sort may compare an element to itself;
647+
// it's required this condition remain consistent and return false.
648+
return false;
649+
}
650+
628651
Order order = Thread_PrioritizeRequests(lhs, rhs);
629652
switch (order)
630653
{

Code/Framework/AzCore/AzCore/IO/Streamer/Scheduler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
namespace AZ::IO
2525
{
2626
class FileRequest;
27+
class Streamer_SchedulerTest_RequestSorting_Test;
2728

2829
namespace Requests
2930
{
@@ -65,6 +66,7 @@ namespace AZ::IO
6566
void GetRecommendations(IStreamerTypes::Recommendations& recommendations) const;
6667

6768
private:
69+
friend class Streamer_SchedulerTest_RequestSorting_Test;
6870
inline static constexpr u32 ProfilerColor = 0x0080ffff; //!< A lite shade of blue. (See https://www.color-hex.com/color/0080ff).
6971

7072
void Thread_MainLoop();

Code/Framework/AzCore/AzCore/IO/Streamer/Streamer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ namespace AZ::IO::Requests
2828
namespace AZ::IO
2929
{
3030
class StreamStackEntry;
31+
class Streamer_SchedulerTest_RequestSorting_Test;
3132

3233
/**
3334
* Data streamer.
3435
*/
3536
class Streamer final
3637
: public AZ::IO::IStreamer
3738
{
39+
friend Streamer_SchedulerTest_RequestSorting_Test;
3840
public:
3941
AZ_RTTI(Streamer, "{3D880982-6E3F-4913-9947-55E01030D4AA}", IStreamer);
4042
AZ_CLASS_ALLOCATOR(Streamer, SystemAllocator);

Code/Framework/AzCore/Tests/Streamer/SchedulerTests.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,4 +378,54 @@ namespace AZ::IO
378378

379379
EXPECT_EQ(Iterations + 1, counter);
380380
}
381+
382+
TEST_F(Streamer_SchedulerTest, RequestSorting)
383+
{
384+
//////////////////////////////////////////////////////////////
385+
// Test equal priority requests that are past their deadlines (aka panic)
386+
//////////////////////////////////////////////////////////////
387+
IStreamerTypes::Deadline panicDeadline(IStreamerTypes::Deadline::min());
388+
auto estimatedCompleteTime = AZStd::chrono::steady_clock::now();
389+
char fakeBuffer[8];
390+
FileRequestPtr panicRequest = m_streamer->Read("PanicRequest", fakeBuffer, sizeof(fakeBuffer), 8, panicDeadline);
391+
panicRequest->m_request.SetEstimatedCompletion(estimatedCompleteTime);
392+
393+
// Passed deadline, same object (same pointer)
394+
EXPECT_EQ(
395+
m_streamer->m_streamStack->Thread_PrioritizeRequests(&panicRequest->m_request, &panicRequest->m_request),
396+
Scheduler::Order::Equal);
397+
398+
// Passed deadline, different object
399+
FileRequestPtr panicRequest2 = m_streamer->Read("PanicRequest2", fakeBuffer, sizeof(fakeBuffer), 8, panicDeadline);
400+
panicRequest2->m_request.SetEstimatedCompletion(estimatedCompleteTime);
401+
EXPECT_EQ(
402+
m_streamer->m_streamStack->Thread_PrioritizeRequests(&panicRequest->m_request, &panicRequest2->m_request),
403+
Scheduler::Order::Equal);
404+
405+
406+
//////////////////////////////////////////////////////////////
407+
// Test equal priority requests that are both reading the same file
408+
//////////////////////////////////////////////////////////////
409+
FileRequestPtr readRequest = m_streamer->Read("SameFile", fakeBuffer, sizeof(fakeBuffer), 8, panicDeadline);
410+
FileRequestPtr sameFileRequest = m_streamer->CreateRequest();
411+
sameFileRequest->m_request.CreateRead(&sameFileRequest->m_request, fakeBuffer, 8, RequestPath(), 0, 8);
412+
sameFileRequest->m_request.m_parent = &readRequest->m_request;
413+
sameFileRequest->m_request.m_dependencies = 0;
414+
415+
// Same file read, same object (same pointer)
416+
EXPECT_EQ(
417+
m_streamer->m_streamStack->Thread_PrioritizeRequests(&sameFileRequest->m_request, &sameFileRequest->m_request),
418+
Scheduler::Order::Equal);
419+
420+
FileRequestPtr readRequest2 = m_streamer->Read("SameFile2", fakeBuffer, sizeof(fakeBuffer), 8, panicDeadline);
421+
FileRequestPtr sameFileRequest2 = m_streamer->CreateRequest();
422+
sameFileRequest2->m_request.CreateRead(&sameFileRequest2->m_request, fakeBuffer, 8, RequestPath(), 0, 8);
423+
sameFileRequest2->m_request.m_parent = &readRequest2->m_request;
424+
sameFileRequest2->m_request.m_dependencies = 0;
425+
426+
// Same file read, different objects
427+
EXPECT_EQ(
428+
m_streamer->m_streamStack->Thread_PrioritizeRequests(&sameFileRequest->m_request, &sameFileRequest2->m_request),
429+
Scheduler::Order::Equal);
430+
}
381431
} // namespace AZ::IO

Gems/AtomLyIntegration/CommonFeatures/Assets/Editor/Scripts/LegacyContentConversion/LegacyActorComponentConverter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def is_this_the_component_im_looking_for(self, xmlElement, parent):
3737

3838
def gather_info_for_conversion(self, xmlElement, parent):
3939
# We don't need to modify the actor component, but we do need to add a material component
40-
for possibleActorAssetComponent in xmlElement.getchildren():
40+
for possibleActorAssetComponent in xmlElement.iter():
4141
if "field" in possibleActorAssetComponent.keys() and possibleActorAssetComponent.get("field") == "ActorAsset" and "value" in possibleActorAssetComponent.keys():
4242
assetId = possibleActorAssetComponent.get("value")
4343

Gems/AtomLyIntegration/CommonFeatures/Assets/Editor/Scripts/LegacyContentConversion/LegacyComponentConverter.py

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@
1515
project and attempts to convert the following components into something
1616
reasonably similar that renders in Atom:
1717
Mesh
18+
Material
19+
PointLight
1820
Actor
1921
2022
2123
Do materials get carried over?
2224
================================================
2325
For the mesh component, this script will attempt to create an atom mesh component
2426
that uses the same material, pre-supposing that you have already run the
25-
LegacyMaterialConverter.py script sto generate Atom .material files out of legacy .mtl files
27+
LegacyAssetConverter script to generate Atom .material files out of legacy .mtl files.
28+
(Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\SDK\Maya\Scripts\Python\legacy_asset_converter\main.py)
2629
For meshes that only have one sub-mesh, this is straightforward as the mesh will only
2730
have one material to apply, and this script will look for a material with the same
2831
name but a .material extension.
@@ -35,14 +38,26 @@
3538
re-named the submaterials, it should find a match. This applies to both the
3639
ActorComponent and the MeshComponent
3740
41+
42+
What does this script needs?
43+
================================================
44+
This script will parse the assetcatalog.xml from the cache folder to get asset ids and path.
45+
This file is in binary by default, you need to rebuild the engine with a modification to make it useable by this script.
46+
- In lumberyard, modify dev\Code\Tools\AssetProcessor\native\AssetManager\AssetCatalog.cpp line 343 to use AZ::ObjectStream::ST_XML instead of ST_BINARY
47+
- In o3de (if you use assetCatalogOverridePath), modify Code\Framework\AzFramework\AzFramework\Asset\AssetCatalog.cpp
48+
and Code\Tools\AssetProcessor\native\AssetManager\AssetCatalog.cpp in the same fashion
49+
50+
Note that after the modification and rebuild, you will need to delete the cache folder and launch the engine for the catalog to be rebuilt
51+
3852
How do I run this script from a command line?
3953
================================================
4054
1) Check out any .slice, .layer, .ly, and .cry files you want to convert from source control
4155
- This script will remove the legacy components entirely, so make sure you have your files
4256
backed up before you run this script in case you want to run it again
43-
2) From the Lumberyard root folder, run LegacyComponentConverter.py project=<ProjectName> --include_gems
44-
- --include_gems is optional
45-
- if you include Gems, it will run all all Gems, not just the ones enabled by your project
57+
2) From the Lumberyard root folder, run LegacyComponentConverter.py -project=<ProjectName> -include_gems -assetCatalogOverridePath=<ExternalProjectPath\Cache\pc\assetcatalog.xml>
58+
- -include_gems is optional. If you include Gems, it will run all all Gems, not just the ones enabled by your project
59+
- -assetCatalogOverridePath is optional. It will replace mesh/material asset id from the source project to this target project
60+
based on relative asset path from the project root (in general, Objects/Models/yourasset.fbx)
4661
4762
4863
What are the artifacts of this script?
@@ -59,12 +74,10 @@
5974
CONVERTED_LOG_NAME = "ComponentConversion_ConvertedLegacyFiles.log"
6075
UNCONVERTED_LOG_NAME = "ComponentConversion_UnsupportedLegacyFiles.log"
6176
STATS_LOG_NAME = "ComponentConversion_LegacyComponentStats.log"
62-
BUILD_PATH = None
63-
GEMS_PATH = None
6477

6578
# Normal imports
6679
import sys
67-
import xml.etree.ElementTree
80+
import xml.etree.ElementTree as ET
6881
import time
6982
from zipfile import ZipFile
7083
import tempfile
@@ -76,8 +89,9 @@
7689
from LegacyMaterialComponentConverter import *
7790
from LegacyActorComponentConverter import *
7891
from LegacyPointLightComponentConverter import *
92+
from LegacyTransformComponentConverter import *
7993

80-
BUILD_PATH = "./"
94+
BUILD_PATH = "./" # Use current working directory, we expect to be in lumberyard dev folder
8195
GEMS_PATH = os.path.join(BUILD_PATH, "Gems")
8296

8397
class Component_File(object):
@@ -169,6 +183,7 @@ def gather_elements(self):
169183
componentConverters.append(Mesh_Component_Converter(self.assetCatalogHelper, self.statsCollector, self.normalizedProjectDir))
170184
componentConverters.append(Actor_Component_Converter(self.assetCatalogHelper, self.statsCollector, self.normalizedProjectDir))
171185
componentConverters.append(Point_Light_Component_Converter(self.assetCatalogHelper, self.statsCollector, self.normalizedProjectDir))
186+
componentConverters.append(Transform_Component_Converter(self.assetCatalogHelper, self.statsCollector, self.normalizedProjectDir))
172187

173188
if self.is_valid_xml():
174189
root = self.xml.getroot()
@@ -190,7 +205,10 @@ def gather_elements(self):
190205
# TODO - we're about to change the tree structure while iterating, which is apparently undefined but appears to work. Might be better to just build up a list of things that need to be modified, then do a second pass to replace the legacy component
191206
# Seems to be okay since we only change or add elements, never remove entirely
192207
componentConverter.convert(child, parent)
193-
self.xml._setroot(root)
208+
self.xml._setroot(root)
209+
# pretty print
210+
ET.indent(self.xml, space='\t')
211+
194212
print("finished parsing {0}".format(self.filename))
195213

196214
def can_be_converted(self):
@@ -265,12 +283,12 @@ def getUpdatedStatsCollector(self):
265283
def main():
266284
'''sys.__name__ wrapper function'''
267285

268-
msgStr = "This tool will scan all of your project's level/layer/slice files\n\
286+
msgStr = "This tool will scan all of your lumberyard project's level/layer/slice files\n\
269287
convert any compatible legacy components into the equivalent Atom components\n\
270288
This script will overwrite the original files, and will remove the legacy components\n\
271289
upon conversion, decimating the previous contents of those components.\n"
272290

273-
commandLineOptions = Common_Command_Line_Options(sys.argv[0], sys.argv[1])
291+
commandLineOptions = Common_Command_Line_Options(sys.argv)
274292
if commandLineOptions.isHelp:
275293
print (commandLineOptions.helpString)
276294
return
@@ -281,8 +299,13 @@ def main():
281299
extensionList = [".slice", ".layer", ".ly", ".cry"]
282300
fileList = get_file_list(commandLineOptions.projectName, commandLineOptions.includeGems, extensionList, BUILD_PATH, GEMS_PATH)
283301

284-
assetCatalogDictionaries = get_asset_catalog_dictionaries(BUILD_PATH, commandLineOptions.projectName)
285-
302+
if commandLineOptions.assetCatalogOverridePath:
303+
assetCatalogPath = commandLineOptions.assetCatalogOverridePath
304+
assetCatalogDictionaries = get_asset_catalog_dictionaries(assetCatalogPath)
305+
else:
306+
assetCatalogPath = os.path.join("Cache", commandLineOptions.projectName, get_default_asset_platform(), commandLineOptions.projectName, "assetcatalog.xml")
307+
assetCatalogDictionaries = get_asset_catalog_dictionaries(assetCatalogPath)
308+
286309
# Create a log file to store converted component file filenames
287310
# and to check to see if the component file has already been converted.
288311
convertedLogFile = Log_File(filename="{0}\\{1}".format(BUILD_PATH, CONVERTED_LOG_NAME))

Gems/AtomLyIntegration/CommonFeatures/Assets/Editor/Scripts/LegacyContentConversion/LegacyConversionHelpers.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"""
99

1010
import os
11+
import platform
1112
import xml.etree.ElementTree
1213

1314
class Stats_Collector(object):
@@ -101,30 +102,36 @@ class Common_Command_Line_Options(object):
101102
"""
102103
Some common options/parsing
103104
"""
104-
def __init__(self, argv0, argv1):
105-
arguments = argv1.split('-')
106-
105+
def __init__(self, args):
107106
self.projectName = ""
107+
self.assetCatalogOverridePath = ""
108108
self.includeGems = False
109109
self.useP4 = False
110110
self.endsWithStr = ""
111111
self.isHelp = False
112-
self.helpString = "usage: {0} -project=<project name> -include_gems -ends_with=<filter> -use_p4\n\
112+
self.helpString = f"usage: {args[0]} -project=<project name> -include_gems -ends_with=<filter> -use_p4 -assetCatalogOverridePath=<path>\n\
113113
E.g.:\n\
114-
{1} -project=StarterGame -include_gems\n\
115-
-project is required.\n\
114+
-project=StarterGame -include_gems\n\
115+
-project is required. Path is relative\n\
116116
-include_gems is optional, and by default gems will not be included.\n\
117117
-ends_with is optional. It could be used to filter for a specific file (--ends_with=default.mtl)\n\
118-
-use_p4 is optional. It will use the p4 command line to check out files that are edited in your default changelist".format(argv0, argv0)
119-
120-
for argument in arguments:
121-
argument = argument.rstrip(" ")
122-
if argument == "h" or argument == "help" or argv1 == "?":
118+
-use_p4 is optional. It will use the p4 command line to check out files that are edited in your default changelist\n\
119+
-assetCatalogOverridePath is optional. It will use asset ids from this file instead of using the current project asset catalog\n\
120+
(match via relative asset path to the project root folder)"
121+
122+
123+
for argument in args:
124+
argument = argument.rstrip(" ").lstrip("-")
125+
if argument == "h" or argument == "help" or argument == "?":
123126
self.isHelp = True
124127
elif argument.startswith("project"):
125128
projectArgs = argument.split("=")
126129
if len(projectArgs) > 1:
127130
self.projectName = projectArgs[1]
131+
elif argument.startswith("assetCatalogOverridePath"):
132+
projectArgs = argument.split("=")
133+
if len(projectArgs) > 1:
134+
self.assetCatalogOverridePath = projectArgs[1]
128135
elif argument == "include_gems":
129136
self.includeGems = True
130137
elif argument == "use_p4":
@@ -204,7 +211,7 @@ def get_asset_id_from_relative_path(self, relativePath):
204211
return self.relativePathToAssetIdDict[relativePath]
205212
return ""
206213

207-
def get_asset_catalog_dictionaries(buildPath, projectName):
214+
def get_asset_catalog_dictionaries(assetCatalogPath):
208215
"""
209216
This function pre-supposes that you have modified AssetCatalog::SaveRegistry_Impl
210217
in Code/Tools/AssetProcessor/native/AssetManager/AssetCatalog.cpp
@@ -218,7 +225,6 @@ def get_asset_catalog_dictionaries(buildPath, projectName):
218225
relativePathToAssetIdDict[""] = "{00000000-0000-0000-0000-000000000000}:0"
219226
assetIdToRelativePathDict = {}
220227
assetUuidToAssetIdsDict = {}
221-
assetCatalogPath = os.path.join(projectName, "Cache", "pc", "assetcatalog.xml")
222228
assetCatalogXml = xml.etree.ElementTree.parse(assetCatalogPath)
223229
for possibleAssetInfo in assetCatalogXml.getroot().iter('Class'):
224230
if "name" in possibleAssetInfo.keys() and possibleAssetInfo.get("name") == "AssetInfo":
@@ -278,6 +284,12 @@ def get_subid_from_assetId(assetId):
278284
separatorIndex = assetId.find(":") + 1
279285
return assetId[separatorIndex:]
280286

287+
def get_default_asset_platform():
288+
host_platform_to_asset_platform_map = { 'windows': 'pc',
289+
'linux': 'linux',
290+
'darwin': 'mac' }
291+
return host_platform_to_asset_platform_map.get(platform.system().lower(), "")
292+
281293
class Component_Converter(object):
282294
"""
283295
Converter base class

0 commit comments

Comments
 (0)