Skip to content

Commit 373f177

Browse files
fix: Allow to convert .slice to .prefabs (o3de#17478)
* new: Allow to convert slice to prefab without asset processor * clean: keep same code path when using asset processor * clean: Add comments to new methods * clean: handle review comments --------- Signed-off-by: guillaume-haerinck <[email protected]>
1 parent eb9f4f2 commit 373f177

File tree

11 files changed

+296
-115
lines changed

11 files changed

+296
-115
lines changed

Code/Framework/AzCore/AzCore/Serialization/SerializationUtils.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,32 @@ namespace AZ::Utils
527527
return ptr;
528528
}
529529

530+
bool InspectSerializedFile(
531+
const char* filePath,
532+
SerializeContext* sc,
533+
const ObjectStream::ClassReadyCB& classCallback,
534+
Data::AssetFilterCB assetFilterCallback)
535+
{
536+
AZ::IO::FileIOStream stream(filePath, AZ::IO::OpenMode::ModeRead);
537+
if (!stream.IsOpen())
538+
{
539+
AZ_Error("Verify", false, "File '%s' could not be opened.", filePath);
540+
return false;
541+
}
542+
543+
ObjectStream::FilterDescriptor filter;
544+
// By default, never load dependencies. That's another file that would need to be processed
545+
// separately from this one.
546+
filter.m_assetCB = assetFilterCallback;
547+
if (!ObjectStream::LoadBlocking(&stream, *sc, classCallback, filter))
548+
{
549+
AZ_Printf("Verify", "Failed to deserialize '%s'\n", filePath);
550+
return false;
551+
}
552+
553+
return true;
554+
}
555+
530556
AZ::Attribute* FindEditOrSerializeContextAttribute(AZ::AttributeId attributeId, const AZ::SerializeContext::ClassData* classData,
531557
const AZ::SerializeContext::ClassElement* classElement, const AZ::Edit::ClassData* editClassData, const AZ::Edit::ElementData* elementEditData)
532558
{
@@ -598,4 +624,5 @@ namespace AZ::Utils
598624
const AZ::Edit::ElementData* elementEditData = classElement != nullptr ? classElement->m_editData : nullptr;
599625
return FindEditOrSerializeContextAttribute(attributeId, classData, classElement, editClassData, elementEditData);
600626
}
627+
601628
} // namespace AZ::Utils

Code/Framework/AzCore/AzCore/Serialization/Utils.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,15 @@ namespace AZ
139139
/// Resolve the instance pointer for a given ClassElement by casting it to the actual type
140140
/// expected by the ClassData for this element
141141
void* ResolvePointer(void* ptr, const SerializeContext::ClassElement& classElement, const SerializeContext& context);
142+
143+
//! Open the given file and load it into an ObjectStream that you can inspect
144+
//! @param classCallback Called for each root object loaded via the ObjectStream. Use it to read values from the file.
145+
//! @param assetFilterCallback Limit the processing/loading to specific asset type(s)
146+
bool InspectSerializedFile(
147+
const char* filePath,
148+
SerializeContext* sc,
149+
const ObjectStream::ClassReadyCB& classCallback,
150+
Data::AssetFilterCB assetFilterCallback = AZ::Data::AssetFilterNoAssetLoading);
142151
} // namespace Utils
143152
} // namespace Az
144153

Code/Framework/AzCore/AzCore/Slice/SliceComponent.cpp

Lines changed: 94 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,7 +1052,10 @@ namespace AZ
10521052
//=========================================================================
10531053
// SliceComponent::SliceReference::Instantiate
10541054
//=========================================================================
1055-
bool SliceComponent::SliceReference::Instantiate(const AZ::ObjectStream::FilterDescriptor& filterDesc)
1055+
bool SliceComponent::SliceReference::Instantiate(
1056+
const AZ::ObjectStream::FilterDescriptor& filterDesc,
1057+
AZ::SerializeContext* serializeContext,
1058+
AZStd::unordered_map<AZStd::string, AZStd::string>* relativeToAbsoluteSlicePaths)
10561059
{
10571060
AZ_PROFILE_FUNCTION(AzCore);
10581061

@@ -1061,41 +1064,64 @@ namespace AZ
10611064
return true;
10621065
}
10631066

1064-
AZ::Data::AssetInfo assetInfo;
1065-
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetInfoById, m_asset.GetId());
1066-
bool isAssetStillOnDisk = assetInfo.m_assetId.IsValid();
1067-
1068-
bool isCachedAssetReady = m_asset.IsReady();
1069-
1070-
if (!(isCachedAssetReady && isAssetStillOnDisk))
1067+
SliceComponent* dependentSlice = nullptr;
1068+
const bool useAssetCatalog = !serializeContext || !relativeToAbsoluteSlicePaths;
1069+
if (useAssetCatalog)
10711070
{
1072-
// If the asset has been queued for async loading but hasn't completed, we've reached the point where it is required
1073-
// to be complete, so block until it finishes loading.
1074-
if (m_asset.IsLoading())
1075-
{
1076-
m_asset.BlockUntilLoadComplete();
1077-
}
1071+
AZ::Data::AssetInfo assetInfo;
1072+
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetInfoById, m_asset.GetId());
10781073

1079-
// If the asset still isn't ready, an unexpected error has occurred.
1080-
if (!m_asset.IsReady())
1074+
const bool isAssetStillOnDisk = assetInfo.m_assetId.IsValid();
1075+
const bool isCachedAssetReady = m_asset.IsReady();
1076+
if (!(isCachedAssetReady && isAssetStillOnDisk))
10811077
{
1078+
// If the asset has been queued for async loading but hasn't completed, we've reached the point where it is required
1079+
// to be complete, so block until it finishes loading.
1080+
if (m_asset.IsLoading())
1081+
{
1082+
m_asset.BlockUntilLoadComplete();
1083+
}
1084+
1085+
// If the asset still isn't ready, an unexpected error has occurred.
1086+
if (!m_asset.IsReady())
1087+
{
10821088
#if defined(AZ_ENABLE_TRACING)
1083-
const Data::Asset<SliceAsset> owningAsset = m_component->m_myAsset ?
1084-
Data::Asset<SliceAsset>(Data::AssetManager::Instance().FindAsset(m_component->m_myAsset->GetId(), AZ::Data::AssetLoadBehavior::Default)) :
1085-
Data::Asset<SliceAsset>();
1086-
AZ_Error("Slice", false,
1087-
"Instantiation of %d slice instance(s) of asset %s failed - asset not ready or not found during instantiation of owning slice %s!"
1088-
"Saving owning slice will lose data for these instances.",
1089-
m_instances.size(),
1090-
m_asset.ToString<AZStd::string>().c_str(),
1091-
m_component->m_myAsset ? owningAsset.ToString<AZStd::string>().c_str() : "[Could not find owning slice]");
1089+
const Data::Asset<SliceAsset> owningAsset = m_component->m_myAsset ?
1090+
Data::Asset<SliceAsset>(Data::AssetManager::Instance().FindAsset(m_component->m_myAsset->GetId(), AZ::Data::AssetLoadBehavior::Default)) :
1091+
Data::Asset<SliceAsset>();
1092+
AZ_Error("Slice", false,
1093+
"Instantiation of %d slice instance(s) of asset %s failed - asset not ready or not found during instantiation of owning slice %s!"
1094+
"Saving owning slice will lose data for these instances.",
1095+
m_instances.size(),
1096+
m_asset.ToString<AZStd::string>().c_str(),
1097+
m_component->m_myAsset ? owningAsset.ToString<AZStd::string>().c_str() : "[Could not find owning slice]");
10921098
#endif // AZ_ENABLE_TRACING
1099+
return false;
1100+
}
1101+
}
1102+
dependentSlice = m_asset.Get()->GetComponent();
1103+
}
1104+
else
1105+
{
1106+
const AZStd::string& path = (*relativeToAbsoluteSlicePaths)[m_asset.GetHint()];
1107+
if (path.empty())
1108+
{
1109+
AZ_Error("SliceReference::Instantiate", false, "Failed to find Slice relative path from %s", m_asset.GetHint().c_str());
10931110
return false;
10941111
}
1112+
1113+
AZ::Entity* sliceRootEntity = AZ::EntityUtils::LoadRootEntityFromSlicePath(path.c_str(), serializeContext);
1114+
dependentSlice = AZ::EntityUtils::FindFirstDerivedComponent<SliceComponent>(sliceRootEntity);
1115+
m_asset.Get()->SetData(sliceRootEntity, dependentSlice, false);
10951116
}
10961117

1097-
SliceComponent* dependentSlice = m_asset.Get()->GetComponent();
1098-
InstantiateResult instantiationResult = dependentSlice->Instantiate();
1118+
if (!dependentSlice)
1119+
{
1120+
AZ_Error("SliceReference::Instantiate", false, "Failed to get SliceComponent from %s", m_asset.GetHint().c_str());
1121+
return false;
1122+
}
1123+
1124+
InstantiateResult instantiationResult = dependentSlice->Instantiate(serializeContext, relativeToAbsoluteSlicePaths);
10991125
if (instantiationResult != InstantiateResult::Success)
11001126
{
11011127
#if defined(AZ_ENABLE_TRACING)
@@ -1653,7 +1679,9 @@ namespace AZ
16531679
//=========================================================================
16541680
// SliceComponent::Instantiate
16551681
//=========================================================================
1656-
SliceComponent::InstantiateResult SliceComponent::Instantiate()
1682+
SliceComponent::InstantiateResult SliceComponent::Instantiate(
1683+
AZ::SerializeContext* serializeContext,
1684+
AZStd::unordered_map<AZStd::string, AZStd::string>* relativeToAbsoluteSlicePaths)
16571685
{
16581686
AZ_PROFILE_FUNCTION(AzCore);
16591687
AZStd::unique_lock<AZStd::recursive_mutex> lock(m_instantiateMutex);
@@ -1684,7 +1712,8 @@ namespace AZ
16841712
}
16851713
else
16861714
{
1687-
bool instantiateSuccess = slice.Instantiate(AZ::ObjectStream::FilterDescriptor(m_assetLoadFilterCB, m_filterFlags));
1715+
bool instantiateSuccess = slice.Instantiate(
1716+
AZ::ObjectStream::FilterDescriptor(m_assetLoadFilterCB, m_filterFlags), serializeContext, relativeToAbsoluteSlicePaths);
16881717
if (instantiateSuccess)
16891718
{
16901719
// Prune empty slice instances.
@@ -3956,4 +3985,40 @@ namespace AZ
39563985
;
39573986
}
39583987
}
3988+
3989+
namespace EntityUtils
3990+
{
3991+
AZ::Entity* LoadRootEntityFromSlicePath(const char* filePath, SerializeContext* context)
3992+
{
3993+
AZ::Entity* sliceRootEntity = nullptr;
3994+
auto callback = [&sliceRootEntity](void* classPtr, const Uuid& classId, SerializeContext* serializeContext)
3995+
{
3996+
sliceRootEntity = serializeContext->Cast<AZ::Entity*>(classPtr, classId);
3997+
if (!sliceRootEntity)
3998+
{
3999+
AZ_Warning("LoadRootEntityFromSlicePath", false, " File not opened: Slice root is not an entity.\n");
4000+
return false;
4001+
}
4002+
return true;
4003+
};
4004+
4005+
if (!AZ::Utils::InspectSerializedFile(
4006+
filePath,
4007+
context,
4008+
callback,
4009+
[](const AZ::Data::AssetFilterInfo& filterInfo)
4010+
{
4011+
return (filterInfo.m_assetType == azrtti_typeid<AZ::SliceAsset>());
4012+
}))
4013+
{
4014+
AZ_Warning("LoadRootEntityFromSlicePath", false, "Failed to load '%s'. File may not contain an object stream.", filePath);
4015+
return nullptr;
4016+
}
4017+
4018+
return sliceRootEntity;
4019+
}
4020+
}
4021+
39594022
} // namespace AZ
4023+
4024+

Code/Framework/AzCore/AzCore/Slice/SliceComponent.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,11 @@ namespace AZ
524524
const AZ::IdUtils::Remapper<AZ::EntityId>::IdMapper& customMapper = nullptr);
525525

526526
/// Instantiate all instances (by default we just hold the deltas - data patch), the Slice component controls the instantiate state
527-
bool Instantiate(const AZ::ObjectStream::FilterDescriptor& filterDesc);
527+
/// serializeContext and relativeToAbsoluteSlicePaths arguments are used for a specific case when asset processor is not available
528+
bool Instantiate(
529+
const AZ::ObjectStream::FilterDescriptor& filterDesc,
530+
AZ::SerializeContext* serializeContext = nullptr,
531+
AZStd::unordered_map<AZStd::string, AZStd::string>* relativeToAbsoluteSlicePaths = nullptr);
528532

529533
void UnInstantiate();
530534

@@ -936,8 +940,11 @@ namespace AZ
936940

937941
/**
938942
* Instantiate entities for this slice, otherwise only the data are stored.
943+
* \param serializeContext and relativeToAbsoluteSlicePaths are used for a specific case when asset processor is not available
939944
*/
940-
InstantiateResult Instantiate();
945+
InstantiateResult Instantiate(
946+
AZ::SerializeContext* serializeContext = nullptr,
947+
AZStd::unordered_map<AZStd::string, AZStd::string>* relativeToAbsoluteSlicePaths = nullptr);
941948
bool IsInstantiated() const;
942949
/**
943950
* Generate new entity Ids and remap references
@@ -1122,6 +1129,9 @@ namespace AZ
11221129

11231130
return replaced;
11241131
}
1132+
1133+
// Deserialize a slice without using asset processor and read root entity from it
1134+
AZ::Entity* LoadRootEntityFromSlicePath(const char* filePath, SerializeContext* context);
11251135
} // namespace EntityUtils
11261136

11271137
namespace IdUtils

Code/Tools/SerializeContextTools/Converter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ namespace AZ
114114
}
115115
return true;
116116
};
117-
if (!Utilities::InspectSerializedFile(filePath.c_str(), convertSettings.m_serializeContext, callback))
117+
if (!AZ::Utils::InspectSerializedFile(filePath.c_str(), convertSettings.m_serializeContext, callback))
118118
{
119119
AZ_Warning("Convert", false, "Failed to load '%s'. File may not contain an object stream.", filePath.c_str());
120120
result = false;

Code/Tools/SerializeContextTools/Dumper.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <AzCore/Serialization/EditContext.h>
2121
#include <AzCore/Serialization/Json/JsonSerialization.h>
2222
#include <AzCore/Serialization/Json/JsonSerializationSettings.h>
23+
#include <AzCore/Serialization/Utils.h>
2324
#include <AzCore/Settings/TextParser.h>
2425
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
2526
#include <AzCore/std/algorithm.h>
@@ -207,7 +208,7 @@ namespace AZ::SerializeContextTools
207208
result = false;
208209
}
209210
};
210-
if (!Utilities::InspectSerializedFile(filePath.c_str(), sc, callback))
211+
if (!AZ::Utils::InspectSerializedFile(filePath.c_str(), sc, callback))
211212
{
212213
result = false;
213214
continue;

Code/Tools/SerializeContextTools/Runner.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,12 @@ namespace SerializeContextTools
7272
AZ_Printf("Help", R"( example: 'convert-ini --files=AssetProcessorPlatformConfig.ini;bootstrap.cfg --ext=setreg)" "\n");
7373
AZ_Printf("Help", " 'convert-slice': Converts ObjectStream-based slice files or legacy levels to a JSON-based prefab.\n");
7474
AZ_Printf("Help", " [arg] -files=<path>: <comma or semicolon>-separated list of files to convert. Supports wildcards.\n");
75+
AZ_Printf("Help", " [opt] -slices=<path>: <comma or semicolon>-separated list of .slice files that you converted files depends on. Supports wildcards. Use this if you cannot use the asset processor on the target project (like o3de converting lumberyard)\n");
7576
AZ_Printf("Help", " [opt] -dryrun: Processes as normal, but doesn't write files.\n");
7677
AZ_Printf("Help", " [opt] -keepdefaults: Fields are written if a default value was found.\n");
7778
AZ_Printf("Help", " [opt] -verbose: Report additional details during the conversion process.\n");
7879
AZ_Printf("Help", " example: 'convert-slice -files=*.slice -specializations=editor\n");
79-
AZ_Printf("Help", " example: 'convert-slice -files=Levels/TestLevel/TestLevel.ly -specializations=editor\n");
80+
AZ_Printf("Help", " example: 'convert-slice -files=Levels/TestLevel/TestLevel.ly -project-path=F:/lmbr-fork/dev/StarterGame -slices=Gems/*.slice -specializations=editor\n");
8081
AZ_Printf("Help", "\n");
8182
AZ_Printf("Help", " 'createtype': Create a default constructed object using Json Serialization and output the contents.\n");
8283
AZ_Printf("Help", " [arg] --type-name=<string>: Name of type to construct and output.\n");

0 commit comments

Comments
 (0)