1010using Nitrox . Server . Subnautica . Models . Helper ;
1111using Nitrox . Server . Subnautica . Models . Resources . AddressablesTools . Catalog ;
1212using Nitrox . Server . Subnautica . Models . Resources . Core ;
13+ using ClassIdByRuntimeKeyDictionary = System . Collections . Generic . Dictionary < string , string > ;
14+ using AddressableCatalogDictionary = System . Collections . Generic . Dictionary < string , string [ ] > ;
1315
1416namespace Nitrox . Server . Subnautica . Models . Resources . Parsers ;
1517
@@ -23,18 +25,17 @@ internal sealed class PrefabPlaceholderGroupsResource(SubnauticaAssetsManager as
2325 /// the cache is rebuilt
2426 /// </para>
2527 /// </summary>
26- private const int CACHE_VERSION = 3 ;
28+ private const int CACHE_VERSION = 4 ;
29+
2730 private const string CACHE_FILENAME = "PrefabPlaceholdersGroupAssetsCache.json" ;
2831
29- private readonly ConcurrentDictionary < string , string [ ] > addressableCatalog = new ( ) ;
3032 private readonly SubnauticaAssetsManager assetsManager = assetsManager ;
31- private readonly ConcurrentDictionary < string , string > classIdByRuntimeKey = new ( ) ;
32- private readonly ConcurrentDictionary < string , PrefabPlaceholdersGroupAsset > groupsByClassId = new ( ) ;
3333 private readonly ILogger < PrefabPlaceholderGroupsResource > logger = logger ;
3434 private readonly IOptions < ServerStartOptions > options = options ;
35- private readonly ConcurrentDictionary < string , PrefabPlaceholderAsset > placeholdersByClassId = [ ] ;
3635 private readonly TaskCompletionSource resourceLoadFinished = new ( ) ;
3736 private readonly JsonSerializer serializer = new ( ) { TypeNameHandling = TypeNameHandling . Auto } ;
37+ private ConcurrentDictionary < string , PrefabPlaceholdersGroupAsset > groupsByClassId = [ ] ;
38+ private ConcurrentDictionary < string , PrefabPlaceholderAsset > placeholdersByClassId = [ ] ;
3839 private ConcurrentDictionary < string , string [ ] > randomPossibilitiesByClassId = [ ] ;
3940
4041 public ConcurrentDictionary < string , PrefabPlaceholdersGroupAsset > GroupsByClassId
@@ -62,10 +63,6 @@ public ConcurrentDictionary<string, string[]> RandomPossibilitiesByClassId
6263 resourceLoadFinished . Task . GetAwaiter ( ) . GetResult ( ) ;
6364 return randomPossibilitiesByClassId ;
6465 }
65- private set
66- {
67- randomPossibilitiesByClassId = value ;
68- }
6966 }
7067
7168 public async Task LoadAsync ( CancellationToken cancellationToken )
@@ -89,7 +86,7 @@ public void PickRandomClassIdIfRequired(ref string classId)
8986 }
9087 }
9188
92- private static Dictionary < string , string > LoadPrefabDatabase ( string fullFilename )
89+ private static ClassIdByRuntimeKeyDictionary LoadPrefabDatabase ( string fullFilename )
9390 {
9491 Dictionary < string , string > prefabFiles = new ( ) ;
9592 if ( ! File . Exists ( fullFilename ) )
@@ -122,40 +119,29 @@ private static void GetPrefabGameObjectInfoFromBundle(SubnauticaAssetsManager am
122119 prefabGameObjectInfo = assetFileInst . file . Metadata . GetAssetInfo ( rootAssetPathId ) ;
123120 }
124121
125- private Task < Dictionary < string , PrefabPlaceholdersGroupAsset > > LoadPrefabsAndSpawnPossibilitiesAsync ( CancellationToken cancellationToken = default )
122+ private async Task LoadPrefabsAndSpawnPossibilitiesAsync ( CancellationToken cancellationToken = default )
126123 {
127- string prefabDatabasePath = Path . Combine ( options . Value . GetSubnauticaResourcesPath ( ) , "StreamingAssets" , "SNUnmanagedData" , "prefabs.db" ) ;
128-
129- // Get all prefab-classIds linked to the (partial) bundle path
130- Dictionary < string , string > prefabDatabase = LoadPrefabDatabase ( prefabDatabasePath ) ;
131- cancellationToken . ThrowIfCancellationRequested ( ) ;
132-
133124 // Loading all prefabs by their classId and file paths (first the path to the prefab then the dependencies)
134- LoadAddressableCatalog ( options . Value . GetSubnauticaAaResourcePath ( ) , prefabDatabase ) ;
135125 cancellationToken . ThrowIfCancellationRequested ( ) ;
136- Dictionary < string , PrefabPlaceholdersGroupAsset > result = CreateOrLoadPrefabCache ( options . Value . GetServerCachePath ( ) ) ;
126+ await CreateOrLoadPrefabCacheAsync ( options . Value . GetServerCachePath ( ) ) ;
137127 cancellationToken . ThrowIfCancellationRequested ( ) ;
138128
139129 // Select only prefabs with a PrefabPlaceholdersGroups component in the root and link them with their dependencyPaths
140130 // Do not remove: the internal cache list is slowing down the process more than loading a few assets again. There maybe is a better way in the new AssetToolsNetVersion but, we need a byte to texture library bc ATNs sub-package is only for netstandard.
141131 assetsManager . UnloadAll ( true ) ;
142- // Clear private collections that were used temporarily to parse the files.
143- addressableCatalog . Clear ( ) ;
144- classIdByRuntimeKey . Clear ( ) ;
145132
146133 // Get all needed data for the filtered PrefabPlaceholdersGroups to construct PrefabPlaceholdersGroupAssets and add them to the dictionary by classId
147134 Validate . IsFalse ( randomPossibilitiesByClassId . IsEmpty ) ;
148- return Task . FromResult ( result ) ;
149135 }
150136
151- private Dictionary < string , PrefabPlaceholdersGroupAsset > CreateOrLoadPrefabCache ( string nitroxCachePath )
137+ private async Task CreateOrLoadPrefabCacheAsync ( string nitroxCachePath )
152138 {
153- Dictionary < string , PrefabPlaceholdersGroupAsset > prefabPlaceholdersGroupPaths = null ;
139+ Dictionary < string , PrefabPlaceholdersGroupAsset > prefabPlaceholdersGroupPaths ;
154140 string cacheFilePath = Path . Combine ( nitroxCachePath , CACHE_FILENAME ) ;
155141 Cache ? cache = null ;
156142 try
157143 {
158- cache = Cache . Deserialize ( serializer , cacheFilePath ) ;
144+ cache = await Cache . DeserializeAsync ( serializer , cacheFilePath ) ;
159145 }
160146 catch ( Exception ex )
161147 {
@@ -169,24 +155,33 @@ private Dictionary<string, PrefabPlaceholdersGroupAsset> CreateOrLoadPrefabCache
169155 }
170156 prefabPlaceholdersGroupPaths = cache . Value . PrefabPlaceholdersGroupPaths ;
171157 randomPossibilitiesByClassId = cache . Value . RandomPossibilitiesByClassId ;
158+ groupsByClassId = cache . Value . GroupsByClassId ;
159+ placeholdersByClassId = cache . Value . PlaceholdersByClassId ;
172160 logger . ZLogDebug ( $ "Successfully loaded cache with { prefabPlaceholdersGroupPaths . Count : @PrefabPlaceholdersCount} prefab placeholder groups and { randomPossibilitiesByClassId . Count : @RandomPossibilitiesCount} random spawn behaviours.") ;
173161 }
174162 // Fallback solution
175- if ( prefabPlaceholdersGroupPaths is null )
163+ else
176164 {
177165 logger . ZLogInformation ( $ "Building cache, this may take a while...") ;
178- prefabPlaceholdersGroupPaths = new ( GetPrefabPlaceholderGroupAssetsByGroupClassId ( assetsManager , GetAllPrefabPlaceholdersGroupsFast ( assetsManager ) ) ) ;
179- Cache . Serialize ( serializer , new Cache ( CACHE_VERSION , prefabPlaceholdersGroupPaths , randomPossibilitiesByClassId ) , cacheFilePath ) ;
166+ // Get all prefab-classIds linked to the (partial) bundle path
167+ string prefabDatabasePath = Path . Combine ( options . Value . GetSubnauticaResourcesPath ( ) , "StreamingAssets" , "SNUnmanagedData" , "prefabs.db" ) ;
168+ Dictionary < string , string > prefabDatabase = LoadPrefabDatabase ( prefabDatabasePath ) ;
169+ ( AddressableCatalogDictionary addressableCatalog , ClassIdByRuntimeKeyDictionary classIdByRuntimeKey ) = LoadAddressableCatalog ( options . Value . GetSubnauticaAaResourcePath ( ) , prefabDatabase ) ;
170+ prefabPlaceholdersGroupPaths = new ( GetPrefabPlaceholderGroupAssetsByGroupClassId ( assetsManager , GetAllPrefabPlaceholdersGroupsFast ( assetsManager , addressableCatalog , classIdByRuntimeKey ) , addressableCatalog , classIdByRuntimeKey ) ) ;
171+ await Cache . SerializeAsync ( serializer , new Cache ( CACHE_VERSION , prefabPlaceholdersGroupPaths , randomPossibilitiesByClassId , groupsByClassId , placeholdersByClassId ) , cacheFilePath ) ;
180172 logger . ZLogDebug (
181173 $ "Successfully built cache with { prefabPlaceholdersGroupPaths . Count : @PrefabPlaceholdersCount} prefab placeholder groups and { randomPossibilitiesByClassId . Count : @RandomPossibilitiesCount} random spawn behaviours. Future server starts will take less time.") ;
182174 }
183175 Validate . IsTrue ( prefabPlaceholdersGroupPaths . Count > 0 ) ;
184176 Validate . IsTrue ( randomPossibilitiesByClassId . Count > 0 ) ;
185- return prefabPlaceholdersGroupPaths ;
177+ Validate . IsTrue ( groupsByClassId . Count > 0 ) ;
178+ Validate . IsTrue ( placeholdersByClassId . Count > 0 ) ;
186179 }
187180
188- private void LoadAddressableCatalog ( string aaRootPath , Dictionary < string , string > prefabDatabase )
181+ private ( AddressableCatalogDictionary , ClassIdByRuntimeKeyDictionary ) LoadAddressableCatalog ( string aaRootPath , Dictionary < string , string > prefabDatabase )
189182 {
183+ ClassIdByRuntimeKeyDictionary classIdByRuntimeKey = [ ] ;
184+ AddressableCatalogDictionary addressableCatalog = [ ] ;
190185 ContentCatalogData ccd = ContentCatalogData . FromJson ( File . ReadAllText ( Path . Combine ( aaRootPath , "catalog.json" ) ) ) ;
191186 Dictionary < string , string > classIdByPath = prefabDatabase . ToDictionary ( m => m . Value , m => m . Key ) ;
192187
@@ -216,13 +211,15 @@ private void LoadAddressableCatalog(string aaRootPath, Dictionary<string, string
216211 break ;
217212 }
218213 }
214+
215+ return ( addressableCatalog , classIdByRuntimeKey ) ;
219216 }
220217
221218 /// <summary>
222219 /// Gathers bundle paths by class id for prefab placeholder groups.
223220 /// Also fills <see cref="RandomPossibilitiesByClassId" />
224221 /// </summary>
225- private ConcurrentDictionary < string , string [ ] > GetAllPrefabPlaceholdersGroupsFast ( SubnauticaAssetsManager am )
222+ private ConcurrentDictionary < string , string [ ] > GetAllPrefabPlaceholdersGroupsFast ( SubnauticaAssetsManager am , AddressableCatalogDictionary addressableCatalog , ClassIdByRuntimeKeyDictionary classIdByRuntimeKey )
226223 {
227224 // First step is to find out about the hash of the types PrefabPlaceholdersGroup and SpawnRandom
228225 // to be able to recognize them easily later on
@@ -313,7 +310,8 @@ private ConcurrentDictionary<string, string[]> GetAllPrefabPlaceholdersGroupsFas
313310 return prefabPlaceholdersGroupPaths ;
314311 }
315312
316- private ConcurrentDictionary < string , PrefabPlaceholdersGroupAsset > GetPrefabPlaceholderGroupAssetsByGroupClassId ( SubnauticaAssetsManager am , ConcurrentDictionary < string , string [ ] > prefabPlaceholdersGroupPaths )
313+ private ConcurrentDictionary < string , PrefabPlaceholdersGroupAsset > GetPrefabPlaceholderGroupAssetsByGroupClassId ( SubnauticaAssetsManager am , ConcurrentDictionary < string , string [ ] > prefabPlaceholdersGroupPaths ,
314+ AddressableCatalogDictionary addressableCatalog , ClassIdByRuntimeKeyDictionary classIdByRuntimeKey )
317315 {
318316 ConcurrentDictionary < string , PrefabPlaceholdersGroupAsset > prefabPlaceholderGroupsByGroupClassId = new ( ) ;
319317
@@ -323,7 +321,7 @@ private ConcurrentDictionary<string, PrefabPlaceholdersGroupAsset> GetPrefabPlac
323321 SubnauticaAssetsManager amInnerClone = amClone . Clone ( ) ;
324322 AssetsFileInstance assetFileInst = amInnerClone . LoadBundleWithDependencies ( keyValuePair . Value ) ;
325323
326- PrefabPlaceholdersGroupAsset prefabPlaceholderGroup = GetAndCachePrefabPlaceholdersGroupOfBundle ( amInnerClone , assetFileInst , keyValuePair . Key ) ;
324+ PrefabPlaceholdersGroupAsset prefabPlaceholderGroup = GetAndCachePrefabPlaceholdersGroupOfBundle ( amInnerClone , assetFileInst , keyValuePair . Key , addressableCatalog , classIdByRuntimeKey ) ;
327325 amInnerClone . UnloadAll ( ) ;
328326
329327 if ( ! prefabPlaceholderGroupsByGroupClassId . TryAdd ( keyValuePair . Key , prefabPlaceholderGroup ) )
@@ -334,13 +332,15 @@ private ConcurrentDictionary<string, PrefabPlaceholdersGroupAsset> GetPrefabPlac
334332 return prefabPlaceholderGroupsByGroupClassId ;
335333 }
336334
337- private PrefabPlaceholdersGroupAsset GetAndCachePrefabPlaceholdersGroupOfBundle ( SubnauticaAssetsManager amInst , AssetsFileInstance assetFileInst , string classId )
335+ private PrefabPlaceholdersGroupAsset GetAndCachePrefabPlaceholdersGroupOfBundle ( SubnauticaAssetsManager amInst , AssetsFileInstance assetFileInst , string classId , AddressableCatalogDictionary addressableCatalog ,
336+ ClassIdByRuntimeKeyDictionary classIdByRuntimeKey )
338337 {
339338 GetPrefabGameObjectInfoFromBundle ( amInst , assetFileInst , out AssetFileInfo prefabGameObjectInfo ) ;
340- return GetAndCachePrefabPlaceholdersGroupGroup ( amInst , assetFileInst , prefabGameObjectInfo , classId ) ;
339+ return GetAndCachePrefabPlaceholdersGroupGroup ( amInst , assetFileInst , prefabGameObjectInfo , classId , addressableCatalog , classIdByRuntimeKey ) ;
341340 }
342341
343- private PrefabPlaceholdersGroupAsset GetAndCachePrefabPlaceholdersGroupGroup ( SubnauticaAssetsManager amInst , AssetsFileInstance assetFileInst , AssetFileInfo rootGameObjectInfo , string classId )
342+ private PrefabPlaceholdersGroupAsset GetAndCachePrefabPlaceholdersGroupGroup ( SubnauticaAssetsManager amInst , AssetsFileInstance assetFileInst , AssetFileInfo rootGameObjectInfo , string classId , AddressableCatalogDictionary addressableCatalog ,
343+ ClassIdByRuntimeKeyDictionary classIdByRuntimeKey )
344344 {
345345 if ( ! string . IsNullOrEmpty ( classId ) && groupsByClassId . TryGetValue ( classId , out PrefabPlaceholdersGroupAsset cachedGroup ) )
346346 {
@@ -368,7 +368,7 @@ private PrefabPlaceholdersGroupAsset GetAndCachePrefabPlaceholdersGroupGroup(Sub
368368
369369 AssetTypeValueField gameObjectPtr = prefabPlaceholder [ "m_GameObject" ] ;
370370 AssetTypeValueField gameObjectField = amInst . GetExtAsset ( assetFileInst , gameObjectPtr ) . baseField ;
371- IPrefabAsset asset = GetAndCacheAsset ( amInst , prefabPlaceholder [ "prefabClassId" ] . AsString ) ;
371+ IPrefabAsset asset = GetAndCacheAsset ( amInst , prefabPlaceholder [ "prefabClassId" ] . AsString , addressableCatalog , classIdByRuntimeKey ) ;
372372 bool isEntitySlotAsset = asset is PrefabPlaceholderAsset prefabPlaceholderAsset && prefabPlaceholderAsset . EntitySlot . HasValue ;
373373 NitroxTransform transform = amInst . GetTransformFromGameObject ( assetFileInst , gameObjectField , rootGameObjectName , isEntitySlotAsset ) ;
374374 string prefabAssetClassId = prefabPlaceholder [ "prefabClassId" ] . AsString ;
@@ -388,7 +388,7 @@ private PrefabPlaceholdersGroupAsset GetAndCachePrefabPlaceholdersGroupGroup(Sub
388388 return prefabPlaceholdersGroup ;
389389 }
390390
391- private IPrefabAsset ? GetAndCacheAsset ( SubnauticaAssetsManager am , string classId )
391+ private IPrefabAsset ? GetAndCacheAsset ( SubnauticaAssetsManager am , string classId , AddressableCatalogDictionary addressableCatalog , ClassIdByRuntimeKeyDictionary classIdByRuntimeKey )
392392 {
393393 if ( string . IsNullOrEmpty ( classId ) )
394394 {
@@ -415,7 +415,7 @@ private PrefabPlaceholdersGroupAsset GetAndCachePrefabPlaceholdersGroupGroup(Sub
415415 AssetFileInfo placeholdersGroupInfo = am . GetMonoBehaviourFromGameObject ( assetFileInst , prefabGameObjectInfo , "PrefabPlaceholdersGroup" ) ;
416416 if ( placeholdersGroupInfo != null )
417417 {
418- PrefabPlaceholdersGroupAsset groupAsset = GetAndCachePrefabPlaceholdersGroupOfBundle ( am , assetFileInst , classId ) ;
418+ PrefabPlaceholdersGroupAsset groupAsset = GetAndCachePrefabPlaceholdersGroupOfBundle ( am , assetFileInst , classId , addressableCatalog , classIdByRuntimeKey ) ;
419419 groupsByClassId [ classId ] = groupAsset ;
420420 return groupAsset ;
421421 }
@@ -469,24 +469,30 @@ private PrefabPlaceholdersGroupAsset GetAndCachePrefabPlaceholdersGroupGroup(Sub
469469 return prefabPlaceholderAsset ;
470470 }
471471
472- private record struct Cache ( int Version , Dictionary < string , PrefabPlaceholdersGroupAsset > PrefabPlaceholdersGroupPaths , ConcurrentDictionary < string , string [ ] > RandomPossibilitiesByClassId )
472+ private record struct Cache (
473+ int Version ,
474+ Dictionary < string , PrefabPlaceholdersGroupAsset > PrefabPlaceholdersGroupPaths ,
475+ ConcurrentDictionary < string , string [ ] > RandomPossibilitiesByClassId ,
476+ ConcurrentDictionary < string , PrefabPlaceholdersGroupAsset > GroupsByClassId ,
477+ ConcurrentDictionary < string , PrefabPlaceholderAsset > PlaceholdersByClassId
478+ )
473479 {
474- public static void Serialize ( JsonSerializer serializer , Cache cache , string filePath )
480+ public static async Task SerializeAsync ( JsonSerializer serializer , Cache cache , string filePath )
475481 {
476482 Directory . CreateDirectory ( Path . GetDirectoryName ( filePath ) ?? throw new Exception ( "Failed to get directory path from cache file path" ) ) ;
477- using StreamWriter stream = File . CreateText ( filePath ) ;
483+ await using StreamWriter stream = File . CreateText ( filePath ) ;
478484 serializer . Serialize ( stream , cache ) ;
479485 }
480486
481- public static Cache ? Deserialize ( JsonSerializer serializer , string filePath )
487+ public static Task < Cache ? > DeserializeAsync ( JsonSerializer serializer , string filePath )
482488 {
483489 if ( ! File . Exists ( filePath ) )
484490 {
485- return null ;
491+ return Task . FromResult < Cache ? > ( null ) ;
486492 }
487493 Directory . CreateDirectory ( Path . GetDirectoryName ( filePath ) ?? throw new Exception ( "Failed to get directory path from cache file path" ) ) ;
488494 using StreamReader reader = File . OpenText ( filePath ) ;
489- return ( Cache ? ) serializer . Deserialize ( reader , typeof ( Cache ) ) ;
495+ return Task . FromResult ( ( Cache ? ) serializer . Deserialize ( reader , typeof ( Cache ) ) ) ;
490496 }
491497 }
492498}
0 commit comments