diff --git a/Explorer/Assets/DCL/AvatarRendering/Emotes/Tests/FinalizeEmoteLoadingSystemShould.cs b/Explorer/Assets/DCL/AvatarRendering/Emotes/Tests/FinalizeEmoteLoadingSystemShould.cs index d6af8ba8f00..e4e1db9c812 100644 --- a/Explorer/Assets/DCL/AvatarRendering/Emotes/Tests/FinalizeEmoteLoadingSystemShould.cs +++ b/Explorer/Assets/DCL/AvatarRendering/Emotes/Tests/FinalizeEmoteLoadingSystemShould.cs @@ -270,7 +270,7 @@ public void FinalizeAssetBundleEmoteLoadingCorrectly() Entity emoteEntity = CreateEmoteEntityWithPromise(mockEmote, intention, bodyShape, out AssetBundlePromise promise); // Mocking promise result - var assetBundleData = new AssetBundleData(null, null, new []{mockGameObject}, null, null); + var assetBundleData = new AssetBundleData(null, new []{mockGameObject}, null, null); var promiseResult = new StreamableLoadingResult(assetBundleData); world.Add(promise.Entity, promiseResult); @@ -303,7 +303,7 @@ public void FinalizeAssetBundleEmoteLoadingUnisexCorrectly() Entity emoteEntity = CreateEmoteEntityWithPromise(mockEmote, intention, loadingBodyShape, out AssetBundlePromise promise); Entity resultHolderEntity = promise.Entity; - var assetBundleData = new AssetBundleData(null, null, new []{mockGameObject}, null, null); + var assetBundleData = new AssetBundleData(null, new []{mockGameObject}, null, null); world.Add(resultHolderEntity, new StreamableLoadingResult(assetBundleData)); system.Update(0); diff --git a/Explorer/Assets/DCL/ECS/GlobalPartitioning/GlobalDeferredLoadingSystem.cs b/Explorer/Assets/DCL/ECS/GlobalPartitioning/GlobalDeferredLoadingSystem.cs index 8b82019810a..0cbf5a5e634 100644 --- a/Explorer/Assets/DCL/ECS/GlobalPartitioning/GlobalDeferredLoadingSystem.cs +++ b/Explorer/Assets/DCL/ECS/GlobalPartitioning/GlobalDeferredLoadingSystem.cs @@ -1,4 +1,5 @@ using Arch.Core; +using DCL.SceneRunner.Scene; using Arch.SystemGroups; using Arch.SystemGroups.DefaultSystemGroups; using DCL.AvatarRendering.Emotes; @@ -14,6 +15,7 @@ using ECS.SceneLifeCycle.SceneDefinition; using ECS.SceneLifeCycle.Systems; using ECS.StreamableLoading.AssetBundles; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; using ECS.StreamableLoading.AudioClips; using ECS.StreamableLoading.DeferredLoading; using ECS.StreamableLoading.GLTF; @@ -42,6 +44,7 @@ static GlobalDeferredLoadingSystem() { CreateQuery(), CreateQuery(), + CreateQuery(), CreateQuery(), CreateQuery(), CreateQuery(), @@ -59,7 +62,8 @@ static GlobalDeferredLoadingSystem() COMPONENT_HANDLERS_SCENES = new[] { CreateQuery(), - CreateQuery() + CreateQuery(), + CreateQuery(), }; } diff --git a/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/JsModulesImplementation/RestrictedActions/Tests/GlobalWorldActionsShould.cs b/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/JsModulesImplementation/RestrictedActions/Tests/GlobalWorldActionsShould.cs index 7fbdc764680..88b624c17bf 100644 --- a/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/JsModulesImplementation/RestrictedActions/Tests/GlobalWorldActionsShould.cs +++ b/Explorer/Assets/DCL/Infrastructure/CrdtEcsBridge/JsModulesImplementation/RestrictedActions/Tests/GlobalWorldActionsShould.cs @@ -12,7 +12,6 @@ using DCL.Ipfs; using DCL.Multiplayer.Emotes; using DCL.Multiplayer.Profiles.Bunches; -using DCL.SceneRunner.Scene; using NUnit.Framework; using SceneRunner.Scene; using System; @@ -330,7 +329,7 @@ private class MockSceneData : ISceneData { public bool SceneLoadingConcluded { get; set; } = true; - public IInitialSceneState InitialSceneStateInfo { get; } = new ISceneData.FakeInitialSceneState(); + public DCL.SceneRunner.Scene.ISSDescriptor? ISSDescriptor => DCL.SceneRunner.Scene.ISSDescriptor.NONE; public SceneShortInfo SceneShortInfo { get; set; } = new (Vector2Int.zero, "mockScene"); public IReadOnlyList Parcels { get; set; } = new List(); public ISceneContent SceneContent => new SceneNonHashedContent(URLDomain.FromString("file://mock/")); diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/IncreasingRadius/ResolveSceneStateByIncreasingRadiusSystem.cs b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/IncreasingRadius/ResolveSceneStateByIncreasingRadiusSystem.cs index 3e7a542587c..6c4d8754382 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/IncreasingRadius/ResolveSceneStateByIncreasingRadiusSystem.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/IncreasingRadius/ResolveSceneStateByIncreasingRadiusSystem.cs @@ -1,13 +1,12 @@ using Arch.Core; using Arch.System; using Arch.SystemGroups; -using CommunicationData.URLHelpers; using DCL.Character.Components; using DCL.Ipfs; using DCL.LOD; using DCL.LOD.Components; -using DCL.Multiplayer.Connections.DecentralandUrls; using DCL.Roads.Components; +using DCL.SceneRunner.Scene; using ECS.Abstract; using ECS.LifeCycle; using ECS.LifeCycle.Components; @@ -16,6 +15,7 @@ using ECS.SceneLifeCycle.Components; using ECS.SceneLifeCycle.SceneDefinition; using ECS.SceneLifeCycle.Systems; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; using ECS.StreamableLoading.Common; using SceneRunner.Scene; using System.Collections.Generic; @@ -108,20 +108,20 @@ protected override void Update(float t) [Query] [None(typeof(SceneLoadingState), typeof(DeleteEntityIntention), typeof(RoadInfo))] private void AddNewSceneDefinitionToList(in Entity entity, in PartitionComponent partitionComponent, - in SceneDefinitionComponent sceneDefinitionComponent) + in SceneDefinitionComponent sceneDefinitionComponent, ISSDescriptor issDescriptor) { if (sceneDefinitionComponent.IsPortableExperience) { //Portable experiences shouldnt be analyzed. Create straight away World.Add(entity, AssetPromise.Create(World, - new GetSceneFacadeIntention(sceneDefinitionComponent), partitionComponent), SceneLoadingState.CreatePortableExperience()); + new GetSceneFacadeIntention(sceneDefinitionComponent, issDescriptor), partitionComponent), SceneLoadingState.CreatePortableExperience()); } else { var sceneLoadingState = new SceneLoadingState(); //Sizes should always be the same - orderedDataManaged.Add(new OrderedDataManaged(entity, sceneDefinitionComponent, partitionComponent, sceneLoadingState)); + orderedDataManaged.Add(new OrderedDataManaged(entity, sceneDefinitionComponent, partitionComponent, sceneLoadingState, issDescriptor)); orderedDataNative.Add(new OrderedDataNative()); arraysInSync = false; World.Add(entity, sceneLoadingState); @@ -242,7 +242,7 @@ private void CreatePromisesFromOrderedData(IIpfsRealm ipfsRealm) { //The parcel we are teleporting to should be the first one OrderedDataManaged data = orderedDataManaged[dataPtr[0].ReferenceListIndex]; - UpdateLoadingState(ipfsRealm, data.Entity, data.SceneDefinitionComponent, data.PartitionComponent, data.SceneLoadingState); + UpdateLoadingState(ipfsRealm, data.Entity, data.SceneDefinitionComponent, data.PartitionComponent, data.SceneLoadingState, data.ISSDescriptor); return; } @@ -253,7 +253,7 @@ private void CreatePromisesFromOrderedData(IIpfsRealm ipfsRealm) if (dataPtr[i].RawSqrDistance < 0 || dataPtr[i].OutOfRange) continue; OrderedDataManaged data = orderedDataManaged[dataPtr[i].ReferenceListIndex]; - UpdateLoadingState(ipfsRealm, data.Entity, data.SceneDefinitionComponent, data.PartitionComponent, data.SceneLoadingState); + UpdateLoadingState(ipfsRealm, data.Entity, data.SceneDefinitionComponent, data.PartitionComponent, data.SceneLoadingState, data.ISSDescriptor); } } @@ -279,7 +279,7 @@ private void Unload(in Entity entity, ref SceneLoadingState sceneState) } private void UpdateLoadingState(IIpfsRealm ipfsRealm, in Entity entity, in SceneDefinitionComponent sceneDefinitionComponent, in PartitionComponent partitionComponent, - SceneLoadingState sceneState) + SceneLoadingState sceneState, ISSDescriptor issDescriptor) { VisualSceneState candidateBy = visualSceneStateResolver.ResolveVisualSceneState(partitionComponent, sceneDefinitionComponent, sceneState.VisualSceneState, ipfsRealm.SceneUrns.Count > 0); @@ -322,6 +322,21 @@ VisualSceneState candidateBy || sceneState.VisualSceneState == candidateBy) return; + // ISS descriptor gate: SHOWING_LOD / SHOWING_SCENE both need the descriptor to be resolved + // before consumers downstream (UpdateSceneLODInfoSystem reads it, GetSceneFacadeIntention + // captures it). If still in Uninitialized state, attach the resolver promise to the entity + // and bail; the next tick re-enters and re-checks. ResolveISSDescriptorSystem consumes the + // promise, mutates the same ISSDescriptor instance in place via MarkResolved, and removes + // the promise component. Because issDescriptor is a class reference cached in + // OrderedDataManaged, the gate sees the resolved state on the next tick without a refetch. + if (issDescriptor.CurrentState == ISSDescriptorState.Uninitialized) + { + if (!World.Has>(entity)) + World.Add(entity, AssetPromise.Create( + World, GetISSDescriptorIntention.For(sceneDefinitionComponent.Definition), partitionComponent)); + return; + } + promisesCreated++; sceneState.PromiseCreated = true; sceneState.VisualSceneState = candidateBy; @@ -339,7 +354,7 @@ VisualSceneState candidateBy //Therefore, we need to make this check because we dont want to break the entity mutual exclusive state if (!World.Has(entity)) World.Add(entity, AssetPromise.Create(World, - new GetSceneFacadeIntention(sceneDefinitionComponent), partitionComponent)); + new GetSceneFacadeIntention(sceneDefinitionComponent, issDescriptor), partitionComponent)); break; } } @@ -392,16 +407,20 @@ public class OrderedDataManaged public readonly SceneDefinitionComponent SceneDefinitionComponent; public readonly SceneLoadingState SceneLoadingState; public readonly PartitionComponent PartitionComponent; + // Class-typed component: same reference is mutated in place by ResolveISSDescriptorSystem, + // so the gate below sees live state without a World.Get refresh. + public readonly ISSDescriptor ISSDescriptor; public int XCoordinate; - public OrderedDataManaged(Entity entity, SceneDefinitionComponent sceneDefinitionComponent, PartitionComponent partitionComponent, SceneLoadingState sceneLoadingState) + public OrderedDataManaged(Entity entity, SceneDefinitionComponent sceneDefinitionComponent, PartitionComponent partitionComponent, SceneLoadingState sceneLoadingState, ISSDescriptor issDescriptor) { Entity = entity; SceneDefinitionComponent = sceneDefinitionComponent; SceneLoadingState = sceneLoadingState; PartitionComponent = partitionComponent; + ISSDescriptor = issDescriptor; XCoordinate = sceneDefinitionComponent.Definition.metadata.scene.DecodedBase.x; } diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneDefinition/Components/SceneDefinitionComponent.cs b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneDefinition/Components/SceneDefinitionComponent.cs index 4da3b49ec10..31c8e6755ec 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneDefinition/Components/SceneDefinitionComponent.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneDefinition/Components/SceneDefinitionComponent.cs @@ -2,6 +2,8 @@ using DCL.Ipfs; using ECS.SceneLifeCycle.IncreasingRadius; using ECS.StreamableLoading.AssetBundles; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; +using ECS.StreamableLoading.Common; using Org.BouncyCastle.Utilities.Collections; using System; using System.Collections.Generic; @@ -27,7 +29,6 @@ public struct SceneDefinitionComponent public int InternalJobIndex { get; set; } - public float EstimatedMemoryUsageInMB; public float EstimatedMemoryUsageForLODMB; public float EstimatedMemoryUsageForQualityReductedLODMB; diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneDefinition/Systems/LoadSceneDefinitionSystem.cs b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneDefinition/Systems/LoadSceneDefinitionSystem.cs index 262412aca02..a808b30a5d7 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneDefinition/Systems/LoadSceneDefinitionSystem.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneDefinition/Systems/LoadSceneDefinitionSystem.cs @@ -1,4 +1,4 @@ -using Arch.Core; +using Arch.Core; using Arch.SystemGroups; using Arch.SystemGroups.DefaultSystemGroups; using CommunicationData.URLHelpers; diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneDefinition/Systems/ResolveISSDescriptorSystem.cs b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneDefinition/Systems/ResolveISSDescriptorSystem.cs new file mode 100644 index 00000000000..9acefa11a47 --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneDefinition/Systems/ResolveISSDescriptorSystem.cs @@ -0,0 +1,54 @@ +using Arch.Core; +using Arch.System; +using Arch.SystemGroups; +using DCL.Diagnostics; +using DCL.SceneRunner.Scene; +using ECS.Abstract; +using ECS.LifeCycle.Components; +using ECS.SceneLifeCycle; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; +using ECS.StreamableLoading.Common; +using ECS.StreamableLoading.Common.Components; + +namespace ECS.SceneLifeCycle.SceneDefinition +{ + /// + /// Consumes the transient ISS descriptor promise that + /// ResolveSceneStateByIncreasingRadiusSystem attaches to a scene entity while its descriptor + /// is being resolved. On consume, mutates the entity's in place via + /// and removes the promise component. The same descriptor + /// instance persists for the scene's lifetime, so cached class references elsewhere (e.g. + /// OrderedDataManaged.ISSDescriptor) see the resolved state without a refetch. + /// + [UpdateInGroup(typeof(RealmGroup))] + [LogCategory(ReportCategory.SCENE_LOADING)] + public partial class ResolveISSDescriptorSystem : BaseUnityLoopSystem + { + internal ResolveISSDescriptorSystem(World world) : base(world) { } + + protected override void Update(float t) + { + ConsumePromiseQuery(World); + } + + [Query] + [None(typeof(DeleteEntityIntention))] + private void ConsumePromise(in Entity entity, SceneDefinitionComponent sceneDefinitionComponent, ISSDescriptor descriptor, ref AssetPromise promise) + { + if (!promise.TryConsume(World, out StreamableLoadingResult result)) + return; + + // The loader returns a failed result for both "no descriptor JSON" and "manifest predates ISS", + // so any failure here means this scene has no ISS and we fall back to the legacy LOD path. + if (result is { Succeeded: true, Asset: { } resolved }) + descriptor.MarkResolved(resolved.assets); + else + { + ReportHub.Log(GetReportCategory(), $"ISSDescriptor is unavailable for scene {sceneDefinitionComponent.Definition.id} ({result.Exception?.Message ?? "unknown reason"}), fallback LOD will be used"); + descriptor.MarkAsNone(); + } + + World.Remove>(entity); + } + } +} diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneDefinition/Systems/ResolveISSDescriptorSystem.cs.meta b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneDefinition/Systems/ResolveISSDescriptorSystem.cs.meta new file mode 100644 index 00000000000..6bc0dc2ed64 --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneDefinition/Systems/ResolveISSDescriptorSystem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0de279c9995504a12b09ab385273e6de \ No newline at end of file diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneFacade/Components/GetSceneFacadeIntention.cs b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneFacade/Components/GetSceneFacadeIntention.cs index a38675ab735..1883289f1b1 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneFacade/Components/GetSceneFacadeIntention.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneFacade/Components/GetSceneFacadeIntention.cs @@ -1,6 +1,8 @@ using CommunicationData.URLHelpers; using DCL.Ipfs; +using DCL.SceneRunner.Scene; using ECS.SceneLifeCycle.SceneDefinition; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; using ECS.StreamableLoading.Common.Components; using Ipfs; using System; @@ -19,9 +21,17 @@ public struct GetSceneFacadeIntention : ILoadingIntention, IEquatable + /// ISSDescriptor for this scene, captured by reference at intention-creation time. The radius + /// system gates SHOWING_SCENE transitions on descriptor resolution, so by the time this intention + /// is constructed the descriptor is guaranteed to be in a resolved state. + /// + public readonly ISSDescriptor ISSDescriptor; + + public GetSceneFacadeIntention(SceneDefinitionComponent definitionComponent, ISSDescriptor issDescriptor) { DefinitionComponent = definitionComponent; + ISSDescriptor = issDescriptor; // URL = EntityId just for identification, it is used by LoadSystemBase, it won't be used as a URL CommonArguments = new CommonLoadingArguments(definitionComponent.IpfsPath.EntityId); diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneFacade/CreateSceneFacadePromise.cs b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneFacade/CreateSceneFacadePromise.cs index 32252dfec68..030fb823cb1 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneFacade/CreateSceneFacadePromise.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneFacade/CreateSceneFacadePromise.cs @@ -1,7 +1,9 @@ using Arch.Core; +using DCL.SceneRunner.Scene; using ECS.Prioritization.Components; using ECS.SceneLifeCycle.Components; using ECS.SceneLifeCycle.SceneDefinition; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; using ECS.StreamableLoading.Common; using SceneRunner.Scene; @@ -9,11 +11,11 @@ namespace ECS.SceneLifeCycle.SceneFacade { public static class CreateSceneFacadePromise { - public static void Execute(World world, Entity entity, in SceneDefinitionComponent definitionComponent, IPartitionComponent partitionComponent) + public static void Execute(World world, Entity entity, in SceneDefinitionComponent definitionComponent, ISSDescriptor issDescriptor, IPartitionComponent partitionComponent) { world.Add(entity, AssetPromise.Create(world, - new GetSceneFacadeIntention(definitionComponent), partitionComponent)); + new GetSceneFacadeIntention(definitionComponent, issDescriptor), partitionComponent)); } } } diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneFacade/Systems/LoadSceneSystemLogicBase.cs b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneFacade/Systems/LoadSceneSystemLogicBase.cs index 2104dc5b112..c57158e4027 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneFacade/Systems/LoadSceneSystemLogicBase.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/SceneFacade/Systems/LoadSceneSystemLogicBase.cs @@ -1,24 +1,19 @@ -using Arch.Core; +using Arch.Core; using System; -using System.Collections.Generic; using System.Threading; using CommunicationData.URLHelpers; using Cysharp.Threading.Tasks; using DCL.Diagnostics; using DCL.Ipfs; -using DCL.SceneRunner.Scene; -using DCL.Utility; using DCL.Utility.Exceptions; using DCL.WebRequests; using ECS.Prioritization.Components; using ECS.SceneLifeCycle.Components; -using ECS.StreamableLoading.AssetBundles; -using ECS.StreamableLoading.Common; -using ECS.StreamableLoading.InitialSceneState; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; using SceneRunner; using SceneRunner.Scene; using SceneRuntime.ScenePermissions; -using AssetBundlePromise = ECS.StreamableLoading.Common.AssetPromise; +using DCL.SceneRunner.Scene; namespace ECS.SceneLifeCycle.Systems { @@ -46,16 +41,14 @@ public async UniTask FlowAsync(World world, ISceneFactory sceneFac // Fixes possible race conditions with the setup of the scene definition, especially on Hybrid mode (LSD+remote ABs) await OverrideSceneMetadataAsync(hashedContent, intention, reportCategory, ipfsPath.EntityId, ct); await UniTask.SwitchToMainThread(ct); - var loadMainCrdt = LoadMainCrdtAsync(hashedContent, reportCategory, ct); - var ISSContainedAssetsPromise = LoadISSAsync(world, definition, ct); - (ReadOnlyMemory mainCrdt, IInitialSceneState? ISSAssets) = await UniTask.WhenAll(loadMainCrdt, ISSContainedAssetsPromise); + ReadOnlyMemory mainCrdt = await LoadMainCrdtAsync(hashedContent, reportCategory, ct); // Create scene data var baseParcel = intention.DefinitionComponent.Definition.metadata.scene.DecodedBase; var sceneData = new SceneData(hashedContent, definitionComponent.Definition, baseParcel, definitionComponent.SceneGeometry, definitionComponent.Parcels, new StaticSceneMessages(mainCrdt), - ISSAssets); + intention.ISSDescriptor); // Launch at the end of the frame await UniTask.SwitchToMainThread(PlayerLoopTiming.LastPostLateUpdate, ct); @@ -69,32 +62,6 @@ public async UniTask FlowAsync(World world, ISceneFactory sceneFac return sceneFacade; } - private async UniTask LoadISSAsync(World world, SceneEntityDefinition sceneDefinitionComponent, CancellationToken ct) - { - if (sceneDefinitionComponent.SupportInitialSceneState()) - { - var promise = AssetBundlePromise.Create(world, - GetAssetBundleIntention.FromHash(GetAssetBundleIntention.BuildInitialSceneStateURL(sceneDefinitionComponent.id), - assetBundleManifestVersion: sceneDefinitionComponent.assetBundleManifestVersion, - parentEntityID: sceneDefinitionComponent.id), - PartitionComponent.TOP_PRIORITY); - - promise = await promise.ToUniTaskAsync(world, cancellationToken: ct); - - if (promise.Result.Value.Succeeded) - { - var result = new HashSet(); - - foreach (string s in promise.Result.Value.Asset.InitialSceneStateMetadata.Value.assetHash) - result.Add($"{s}{PlatformUtils.GetCurrentPlatform()}"); - - return InitialSceneStateInfo.CreateISS(promise.Result.Value.Asset, result); - } - } - - return InitialSceneStateInfo.CreateEmpty(); - } - protected abstract string GetAssetBundleSceneId(string ipfsPathEntityId); protected abstract UniTask GetSceneHashedContentAsync(SceneEntityDefinition definition, URLDomain contentBaseUrl, ReportData reportCategory, CancellationToken ct); diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/EarlyAsset.meta b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/EarlyAsset.meta deleted file mode 100644 index 8d0ebf51026..00000000000 --- a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/EarlyAsset.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 5161586cbefd4e758b2c03833119fc1a -timeCreated: 1759500499 \ No newline at end of file diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/EarlyAsset/EarlySceneRequestSystem.cs b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/EarlyAsset/EarlySceneRequestSystem.cs deleted file mode 100644 index 36ca0d23ce5..00000000000 --- a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/EarlyAsset/EarlySceneRequestSystem.cs +++ /dev/null @@ -1,88 +0,0 @@ -using Arch.Core; -using Arch.System; -using Arch.SystemGroups; -using Arch.SystemGroups.DefaultSystemGroups; -using DCL.Ipfs; -using DCL.Multiplayer.Connections.DecentralandUrls; -using DCL.RealmNavigation; -using ECS.Abstract; -using ECS.Prioritization.Components; -using ECS.SceneLifeCycle.SceneDefinition; -using ECS.StreamableLoading.AssetBundles.EarlyAsset; -using ECS.StreamableLoading.Common.Components; -using System.Collections.Generic; -using Unity.Mathematics; -using Utility; -using ScenePromise = ECS.StreamableLoading.Common.AssetPromise; - - -namespace ECS.SceneLifeCycle.Systems.EarlyAsset -{ - [UpdateInGroup(typeof(PresentationSystemGroup))] - [UpdateBefore(typeof(EarlyAssetBundleRequestSystem))] - public partial class EarlySceneRequestSystem : BaseUnityLoopSystem - { - private readonly StartParcel startParcel; - private readonly IRealmData realmData; - private readonly IDecentralandUrlsSource urlsSource; - - private bool sceneRequestInitialized; - - public EarlySceneRequestSystem(World world, StartParcel startParcel, IRealmData realmData, IDecentralandUrlsSource urlsSource) : base(world) - { - this.startParcel = startParcel; - this.realmData = realmData; - this.urlsSource = urlsSource; - } - - protected override void Update(float t) - { - if (!realmData.Configured) - return; - - //For now, this only works for Genesis - if (!realmData.IsGenesis()) - return; - - if (!sceneRequestInitialized) - RequestEarlyScene(); - - CompleteEarlySceneRequestQuery(World); - } - - private void RequestEarlyScene() - { - var entityDefinitionList = new List(); - var pointersList = new List { startParcel.Peek().ToInt2() }; - - var promise = ScenePromise.Create(World, - new GetSceneDefinitionList(entityDefinitionList, pointersList, new CommonLoadingArguments(urlsSource.Url(DecentralandUrl.EntitiesActive))), - PartitionComponent.TOP_PRIORITY); - - World.Create(promise, new EarlySceneFlag()); - sceneRequestInitialized = true; - } - - [Query] - [All(typeof(EarlySceneFlag))] - private void CompleteEarlySceneRequest(Entity entity, ref ScenePromise promise) - { - if (promise.TryConsume(World, out StreamableLoadingResult Result)) - { - if (Result.Succeeded && Result.Asset.Value.Count > 0) - { - if (Result.Asset.Value[0].SupportInitialSceneState()) - { - //Do nothing. We just needed loaded in memory, we dont care the result. - //Whoever needs it, will grab it later - //Test URL - World.Create(EarlyAssetBundleFlag.CreateAssetBundleRequest(Result.Asset.Value[0])); - } - } - - //Nothing to do with it after creation of the early asset bundle request - World.Destroy(entity); - } - } - } -} diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/EarlyAsset/EarlySceneRequestSystem.cs.meta b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/EarlyAsset/EarlySceneRequestSystem.cs.meta deleted file mode 100644 index fcad9c8a66c..00000000000 --- a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/EarlyAsset/EarlySceneRequestSystem.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: cd71247a45ab4ac599ea9cd4941c78c3 -timeCreated: 1759500538 \ No newline at end of file diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadFixedPointersSystem.cs b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadFixedPointersSystem.cs index abaa5ccd582..42fa6af6f91 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadFixedPointersSystem.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadFixedPointersSystem.cs @@ -4,9 +4,11 @@ using CommunicationData.URLHelpers; using DCL.Ipfs; using DCL.Multiplayer.Connections.DecentralandUrls; +using DCL.SceneRunner.Scene; using ECS.Prioritization.Components; using ECS.SceneLifeCycle.Components; using ECS.SceneLifeCycle.SceneDefinition; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; using ECS.StreamableLoading.Common; using ECS.StreamableLoading.Common.Components; using System.Collections.Generic; @@ -67,7 +69,7 @@ private void ResolveCatalystPromises(ref FixedScenePointers fixedScenePointers, { if (result.Succeeded) { - CreateSceneEntity(result.Asset, promise.LoadingIntention.IpfsPath); + CreateSceneEntity(result.Asset, promise.LoadingIntention.IpfsPath, ISSDescriptor.CreateUninitialized()); IReadOnlyList parcels = result.Asset.metadata.scene.DecodedParcels; //We are gonna load into the first loaded scene as startup @@ -107,7 +109,7 @@ private void ResolveRegistryPromises(ref FixedScenePointers fixedScenePointers, SceneEntityDefinition definition = definitions[i]; fixedScenePointers.SceneResults.Add(definition); var ipfsPath = new IpfsPath(definition.id, URLDomain.FromString(urlsSource.Url(DecentralandUrl.WorldContentServer))); - CreateSceneEntity(definition, ipfsPath); + CreateSceneEntity(definition, ipfsPath, ISSDescriptor.CreateUninitialized()); IReadOnlyList parcels = definition.metadata.scene.DecodedParcels; for (var j = 0; j < parcels.Count; j++) diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadPortableExperiencePointersSystem.cs b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadPortableExperiencePointersSystem.cs index 43c50b4ccb1..7e481bca04e 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadPortableExperiencePointersSystem.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadPortableExperiencePointersSystem.cs @@ -2,10 +2,12 @@ using Arch.System; using Arch.SystemGroups; using DCL.Ipfs; +using DCL.SceneRunner.Scene; using ECS.LifeCycle.Components; using ECS.Prioritization.Components; using ECS.SceneLifeCycle.Components; using ECS.SceneLifeCycle.SceneDefinition; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; using ECS.StreamableLoading.Common; using ECS.StreamableLoading.Common.Components; using System.Collections.Generic; @@ -63,7 +65,7 @@ private void ResolvePromises(ref PortableExperienceScenePointers portableExperie { if (result is {Asset: not null, Succeeded: true }) { - var entity = CreateSceneEntity(result.Asset, promise.LoadingIntention.IpfsPath, true); + var entity = CreateSceneEntity(result.Asset, promise.LoadingIntention.IpfsPath, ISSDescriptor.NONE, true); World.Add(entity, portableExperienceComponent); } } diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadScenePointerSystemBase.cs b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadScenePointerSystemBase.cs index 2483e9dc13e..147b3f0da18 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadScenePointerSystemBase.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadScenePointerSystemBase.cs @@ -1,9 +1,11 @@ using Arch.Core; using DCL.Ipfs; using DCL.Roads.Components; +using DCL.SceneRunner.Scene; using ECS.Abstract; using ECS.SceneLifeCycle.IncreasingRadius; using ECS.SceneLifeCycle.SceneDefinition; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; using Ipfs; using System.Collections.Generic; using Unity.Collections; @@ -24,12 +26,12 @@ protected LoadScenePointerSystemBase(World world, HashSet roadCoordi this.realmData = realmData; } - protected Entity CreateSceneEntity(SceneEntityDefinition definition, IpfsPath ipfsPath, bool isPortableExperience = false) + protected Entity CreateSceneEntity(SceneEntityDefinition definition, IpfsPath ipfsPath, ISSDescriptor issDescriptor, bool isPortableExperience = false) { if (IsRoad(definition)) - return World.Create(SceneDefinitionComponentFactory.CreateFromDefinition(definition, ipfsPath, isPortableExperience), RoadInfo.Create(), SceneLoadingState.CreateRoad()); + return World.Create(SceneDefinitionComponentFactory.CreateFromDefinition(definition, ipfsPath, isPortableExperience), issDescriptor, RoadInfo.Create(), SceneLoadingState.CreateRoad()); - return World.Create(SceneDefinitionComponentFactory.CreateFromDefinition(definition, ipfsPath, isPortableExperience)); + return World.Create(SceneDefinitionComponentFactory.CreateFromDefinition(definition, ipfsPath, isPortableExperience), issDescriptor); } private bool IsRoad(SceneEntityDefinition definition) => @@ -53,7 +55,7 @@ protected void TryCreateSceneEntity(SceneEntityDefinition definition, IpfsPath i if (shouldCreate) { // Note: Span.ToArray is not LINQ - CreateSceneEntity(definition, ipfsPath); + CreateSceneEntity(definition, ipfsPath, ISSDescriptor.CreateUninitialized()); } } } diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadSmartWearablePreviewSceneSystem.cs b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadSmartWearablePreviewSceneSystem.cs index b8239eba8f3..4fb5076a18e 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadSmartWearablePreviewSceneSystem.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadSmartWearablePreviewSceneSystem.cs @@ -6,11 +6,13 @@ using DCL.Diagnostics; using DCL.Ipfs; using DCL.Multiplayer.Connections.DecentralandUrls; +using DCL.SceneRunner.Scene; using DCL.WebRequests; using ECS.Abstract; using ECS.Prioritization.Components; using ECS.SceneLifeCycle.SceneDefinition; using ECS.SceneLifeCycle.SceneFacade; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; using ECS.StreamableLoading.Common.Components; using System; using System.Collections.Generic; @@ -99,13 +101,16 @@ private async UniTask TryLoadPreviewSceneAsync(Entity realm, IIpfsRealm ipfs, Ca }; var ipfsPath = new IpfsPath(definition.id!, URLDomain.FromString(urlsSource.Url(DecentralandUrl.Content))); - // NOTICE that when creating the scene we do NOT mark it as a PX because we are running it as a normal scene + // NOTICE that when creating the scene we do NOT mark it as a PX because we are running it as a normal scene. + // Smart-wearable preview content is creator-tooling, never deployed through the AB pipeline — no ISS + // descriptor exists for it. Attach a State.None descriptor so the resolver gate skips it entirely. SceneDefinitionComponent definitionComponent = SceneDefinitionComponentFactory.CreateFromDefinition(definition, ipfsPath); + var issDescriptor = ISSDescriptor.NONE; await UniTask.SwitchToMainThread(); - Entity scene = World.Create(definitionComponent, PartitionComponent.TOP_PRIORITY); - CreateSceneFacadePromise.Execute(World, scene, definitionComponent, PartitionComponent.TOP_PRIORITY); + Entity scene = World.Create(definitionComponent, issDescriptor, PartitionComponent.TOP_PRIORITY); + CreateSceneFacadePromise.Execute(World, scene, definitionComponent, issDescriptor, PartitionComponent.TOP_PRIORITY); World.Set(realm, new SmartWearablePreviewScene { Value = scene }); } diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadSmartWearableSceneSystem.cs b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadSmartWearableSceneSystem.cs index 50c82442cae..c0a662411c9 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadSmartWearableSceneSystem.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadSmartWearableSceneSystem.cs @@ -1,5 +1,6 @@ using Arch.SystemGroups; using CommunicationData.URLHelpers; +using DCL.SceneRunner.Scene; using Cysharp.Threading.Tasks; using DCL.AvatarRendering.Wearables.Components; using DCL.AvatarRendering.Wearables.Helpers; @@ -9,10 +10,10 @@ using DCL.WebRequests; using ECS.Prioritization.Components; using ECS.SceneLifeCycle.SceneDefinition; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; using ECS.StreamableLoading.Cache; using ECS.StreamableLoading.Common.Components; using ECS.StreamableLoading.Common.Systems; -using ECS.StreamableLoading.InitialSceneState; using Runtime.Wearables; using SceneRunner; using SceneRunner.Scene; @@ -82,7 +83,7 @@ public LoadSmartWearableSceneSystem( definitionComponent.SceneGeometry, definitionComponent.Parcels, new StaticSceneMessages(crdt), - InitialSceneStateInfo.CreateEmpty()); + ISSDescriptor.NONE); // Set the flag is we are using the special DTO for builder collection preview sceneData.IsWearableBuilderCollectionPreview = wearable.DTO is BuilderWearableDTO; diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadStaticPointersSystem.cs b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadStaticPointersSystem.cs index cc0fef14219..bf0995ad7f5 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadStaticPointersSystem.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/LoadStaticPointersSystem.cs @@ -6,7 +6,9 @@ using DCL.Multiplayer.Connections.DecentralandUrls; using ECS.Prioritization.Components; using ECS.SceneLifeCycle.Components; +using DCL.SceneRunner.Scene; using ECS.SceneLifeCycle.SceneDefinition; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; using ECS.StreamableLoading.Common; using ECS.StreamableLoading.Common.Components; using System.Collections.Generic; @@ -57,7 +59,10 @@ private void ResolvePromise(ref StaticScenePointers staticScenePointers) { SceneEntityDefinition definition = result.Asset.Value[i]; var path = new IpfsPath(definition.id, URLDomain.FromString(urlsSource.Url(DecentralandUrl.Content))); - CreateSceneEntity(definition, path); + // Static pointer scenes (LSD + hardcoded debug positions) aren't deployed through + // the AB pipeline, so no ISS descriptor exists for them. Initialize in State.None so + // the resolver gate skips them entirely. + CreateSceneEntity(definition, path, ISSDescriptor.NONE); } } diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/ResolveStaticPointersSystem.cs b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/ResolveStaticPointersSystem.cs index d01ec91b922..e1ad5092f2c 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/ResolveStaticPointersSystem.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Systems/ResolveStaticPointersSystem.cs @@ -1,5 +1,6 @@ using Arch.Core; using Arch.System; +using DCL.SceneRunner.Scene; using Arch.SystemGroups; using CommunicationData.URLHelpers; using DCL.CharacterCamera; @@ -9,6 +10,7 @@ using ECS.SceneLifeCycle.Components; using ECS.SceneLifeCycle.SceneDefinition; using ECS.SceneLifeCycle.SceneFacade; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; using ECS.StreamableLoading.Common; using SceneRunner.Scene; using System.Linq; @@ -53,7 +55,7 @@ private void ForEachRealm(ref RealmComponent realm, ref StaticScenePointers stat [Query] [None(typeof(ISceneFacade), typeof(AssetPromise), typeof(SceneLODInfo))] private void StartSceneLoading([Data] IIpfsRealm realm, [Data] in StaticScenePointers staticScenePointers, - in Entity entity, ref SceneDefinitionComponent definition, ref PartitionComponent partitionComponent) + in Entity entity, ref SceneDefinitionComponent definition, ISSDescriptor issDescriptor, ref PartitionComponent partitionComponent) { for (var i = 0; i < definition.Parcels.Count; i++) { @@ -61,7 +63,7 @@ private void StartSceneLoading([Data] IIpfsRealm realm, [Data] in StaticScenePoi if (staticScenePointers.Value.Contains(parcel.ToInt2())) { - CreateSceneFacadePromise.Execute(World, entity, in definition, partitionComponent); + CreateSceneFacadePromise.Execute(World, entity, in definition, issDescriptor, partitionComponent); return; } } diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Tests/ResolveSceneStateByIncreasingRadiusSystemShould.cs b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Tests/ResolveSceneStateByIncreasingRadiusSystemShould.cs index 99d428ccfcb..bfd2e6ab86b 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Tests/ResolveSceneStateByIncreasingRadiusSystemShould.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/SceneLifeCycle/Tests/ResolveSceneStateByIncreasingRadiusSystemShould.cs @@ -7,6 +7,8 @@ using DCL.LOD; using DCL.LOD.Components; using DCL.Multiplayer.Connections.DecentralandUrls; +using DCL.SceneRunner.Scene; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; using DCL.Optimization.PerformanceBudgeting; using DCL.Utilities.Extensions; using DCL.Utility; @@ -151,7 +153,7 @@ public async Task LimitScenesLoading() new IpfsPath()), new PartitionComponent { Bucket = (byte)i, RawSqrDistance = ParcelMathHelper.SQR_PARCEL_SIZE * i, - }); + }, ISSDescriptor.NONE); } system.Update(0f); @@ -194,7 +196,7 @@ public void StartUnloading() { Bucket = i, RawSqrDistance = (ParcelMathHelper.PARCEL_SIZE * i * ParcelMathHelper.PARCEL_SIZE * i) - 1f, OutOfRange = i < 4, }, Substitute.For(), - SceneLoadingState.CreateBuiltScene()); + SceneLoadingState.CreateBuiltScene(), ISSDescriptor.NONE); } system.Update(0f); @@ -247,7 +249,7 @@ private void CreateScene(int parcelAmount, string runtime, int distanceToPlayer) new IpfsPath()), new PartitionComponent { Bucket = 0, RawSqrDistance = ParcelMathHelper.SQR_PARCEL_SIZE * distanceToPlayer, - }); + }, ISSDescriptor.NONE); } private void CreateLOD(int parcelAmount, int distanceToPlayer) @@ -264,7 +266,7 @@ private void CreateLOD(int parcelAmount, int distanceToPlayer) new IpfsPath()), new PartitionComponent { Bucket = (byte)(SDK7LODThreshold + 1), RawSqrDistance = ParcelMathHelper.SQR_PARCEL_SIZE * distanceToPlayer, - }); + }, ISSDescriptor.NONE); } } } diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/AssetBundleData.cs b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/AssetBundleData.cs index 70d39f2a221..6297ebebf92 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/AssetBundleData.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/AssetBundleData.cs @@ -16,30 +16,25 @@ public class AssetBundleData : StreamableRefCountData { private readonly string AssetBundleName; - public readonly InitialSceneStateMetadata? InitialSceneStateMetadata; - private bool AssetBundleUnloaded; private Dictionary? Assets; private readonly AssetBundleData[] Dependencies; - public AssetBundleData(AssetBundle assetBundle, InitialSceneStateMetadata? initialSceneState, Object[] loadedAssets, Type? assetType, AssetBundleData[] dependencies, string version = "", string source = "") + public AssetBundleData(AssetBundle assetBundle , Object[] loadedAssets, Type? assetType, AssetBundleData[] dependencies, string version = "", string source = "") : base(assetBundle, ReportCategory.ASSET_BUNDLES) { - InitialSceneStateMetadata = initialSceneState; - Assets = new Dictionary(); for (var i = 0; i < loadedAssets.Length; i++) - Assets[loadedAssets[i].name] = new AssetInfo(loadedAssets[i], assetType ?? loadedAssets[i].GetType(), version, source, InitialSceneStateMetadata.HasValue); + Assets[loadedAssets[i].name] = new AssetInfo(loadedAssets[i], assetType ?? loadedAssets[i].GetType(), version, source); Dependencies = dependencies; //Debugging purposes. Test cases may bring a null AB, therefore we need this check AssetBundleName = Asset?.name; - //We cannot unload an AB if its an ISS (Initial Scene State AB). It may be a dependency for dynamically isntanced AB - if (!InitialSceneStateMetadata.HasValue) - UnloadAB(); + // are still in use by dynamically-instantiated assets and must NOT be unloaded eagerly. + UnloadAB(); } public AssetBundleData(AssetBundle assetBundle, AssetBundleData[] dependencies) : base(assetBundle, ReportCategory.ASSET_BUNDLES) @@ -138,11 +133,11 @@ public struct AssetInfo public Object Asset { get; } public Type AssetType { get; } - public AssetInfo(Object asset, Type assetType, string version, string source, bool isISS) + public AssetInfo(Object asset, Type assetType, string version, string source) { Asset = asset; AssetType = assetType; - Asset.name = isISS ? $"AB:{Asset?.name}_{version}_{source}_ISS" : $"AB:{Asset?.name}_{version}_{source}_NoISS"; + Asset.name = $"AB:{Asset?.name}_{version}_{source}"; } } @@ -157,10 +152,3 @@ public static TValue FirstValueOrDefaultNonAlloc(this Dictionary assetHash; - public List positions; - public List rotations; - public List scales; -} diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/EarlyAsset.meta b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/EarlyAsset.meta deleted file mode 100644 index b52b37a6b1b..00000000000 --- a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/EarlyAsset.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 8b2f93775138400aa4da283a81d29cf9 -timeCreated: 1759491076 \ No newline at end of file diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/EarlyAsset/EarlyAssetBundleRequestSystem.cs b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/EarlyAsset/EarlyAssetBundleRequestSystem.cs deleted file mode 100644 index fb1d6fd286f..00000000000 --- a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/EarlyAsset/EarlyAssetBundleRequestSystem.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Arch.Core; -using Arch.System; -using Arch.SystemGroups; -using Arch.SystemGroups.DefaultSystemGroups; -using DCL.Utility; -using ECS.Abstract; -using ECS.Prioritization.Components; -using ECS.StreamableLoading.Common.Components; -using AssetBundlePromise = ECS.StreamableLoading.Common.AssetPromise; - -namespace ECS.StreamableLoading.AssetBundles.EarlyAsset -{ - [UpdateInGroup(typeof(PresentationSystemGroup))] - [UpdateBefore(typeof(PrepareGlobalAssetBundleLoadingParametersSystem))] - public partial class EarlyAssetBundleRequestSystem : BaseUnityLoopSystem - { - private bool requestDone; - - public EarlyAssetBundleRequestSystem(World world) : base(world) - { - } - - protected override void Update(float t) - { - if (!requestDone) - StartEarlyDownloadQuery(World); - ResolveEarlyDownloadQuery(World); - } - - [Query] - [All(typeof(EarlyAssetBundleFlag))] - [None(typeof(AssetBundlePromise))] - private void StartEarlyDownload(Entity entity, ref EarlyAssetBundleFlag earlySceneFlag) - { - AssetBundlePromise promise = AssetBundlePromise.Create(World, - GetAssetBundleIntention.FromHash(GetAssetBundleIntention.BuildInitialSceneStateURL(earlySceneFlag.Scene.id), - assetBundleManifestVersion: earlySceneFlag.Scene.assetBundleManifestVersion, - parentEntityID: earlySceneFlag.Scene.id), - PartitionComponent.TOP_PRIORITY); - - requestDone = true; - World.Add(entity, promise); - } - - - [Query] - [All(typeof(EarlyAssetBundleFlag))] - private void ResolveEarlyDownload(Entity entity, ref AssetBundlePromise promise) - { - if (promise.TryConsume(World, out StreamableLoadingResult Result)) - { - //Do nothing. We just needed loaded in memory, we dont care the result. - //Whoever needs it, will grab it later - //TODO (JUANI) : Maybe we should instantiate it already? - World.Destroy(entity); - if (Result.Succeeded) - { - //Dereferencing, because no one is using it yet - Result.Asset.Dereference(); - } - } - } - - } -} diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/EarlyAsset/EarlyAssetBundleRequestSystem.cs.meta b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/EarlyAsset/EarlyAssetBundleRequestSystem.cs.meta deleted file mode 100644 index 9408f3ca293..00000000000 --- a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/EarlyAsset/EarlyAssetBundleRequestSystem.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: fc164f90917947f2bea1b47bf4751b91 -timeCreated: 1759490723 \ No newline at end of file diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/EarlyAsset/EarlySceneFlag.cs b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/EarlyAsset/EarlySceneFlag.cs deleted file mode 100644 index 36152ff5aee..00000000000 --- a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/EarlyAsset/EarlySceneFlag.cs +++ /dev/null @@ -1,19 +0,0 @@ -using DCL.Ipfs; - -namespace ECS.StreamableLoading.AssetBundles.EarlyAsset -{ - public struct EarlySceneFlag - { - } - - public struct EarlyAssetBundleFlag - { - public EntityDefinitionBase Scene; - - public static EarlyAssetBundleFlag CreateAssetBundleRequest(EntityDefinitionBase scene) => - new () - { - Scene = scene, - }; - } -} diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/EarlyAsset/EarlySceneFlag.cs.meta b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/EarlyAsset/EarlySceneFlag.cs.meta deleted file mode 100644 index 753b57043e4..00000000000 --- a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/EarlyAsset/EarlySceneFlag.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 232932c70fbb47d0adfa949fb58c2eab -timeCreated: 1759491223 \ No newline at end of file diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState.meta b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState.meta index 881621be5b9..f4c025c1df6 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState.meta +++ b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState.meta @@ -1,3 +1,8 @@ fileFormatVersion: 2 guid: 6062f2f08fb94ae5b2771a59f5f7bbba -timeCreated: 1762118591 \ No newline at end of file +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/GetISSDescriptorIntention.cs b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/GetISSDescriptorIntention.cs new file mode 100644 index 00000000000..60c73dcc003 --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/GetISSDescriptorIntention.cs @@ -0,0 +1,70 @@ +using DCL.Ipfs; +using DCL.SceneRunner.Scene; +using ECS.StreamableLoading.Cache.Disk.Cacheables; +using ECS.StreamableLoading.Common.Components; +using System; +using System.Threading; + +namespace ECS.StreamableLoading.AssetBundles.InitialSceneState +{ + /// + /// Intention for loading a scene's . Equality is by scene id so the + /// streamable cache dedupes concurrent requests across the LOD path and the SDK runtime path. + /// The v49 manifest gate lives inside — pre-v49 scenes get + /// a cached result without any HTTP work. + /// For more info regarding versioning, please take a look at the Asset bundle converter + /// project (https://github.com/decentraland/asset-bundle-converter) + /// + public struct GetISSDescriptorIntention : ILoadingIntention, IEquatable + { + public CancellationTokenSource CancellationTokenSource => CommonArguments.CancellationTokenSource; + public CommonLoadingArguments CommonArguments { get; set; } + + public readonly string SceneId; + public readonly AssetBundleManifestVersion ManifestVersion; + + public GetISSDescriptorIntention(string sceneId, AssetBundleManifestVersion manifestVersion) + { + SceneId = sceneId; + ManifestVersion = manifestVersion; + // URL is just an identifier here — the loader builds the real URL from sceneId + the configured streaming-assets domain. + CommonArguments = new CommonLoadingArguments(sceneId); + } + + // Manifest is required: callers reach this only after the scene definition (and therefore its manifest) + // has been loaded. A valid manifest should be available at this point + public static GetISSDescriptorIntention For(SceneEntityDefinition definition) => + new (definition.id, definition.assetBundleManifestVersion ?? AssetBundleManifestVersion.CreateFailed()); + + public bool Equals(GetISSDescriptorIntention other) => + SceneId == other.SceneId; + + public override bool Equals(object obj) => + obj is GetISSDescriptorIntention other && Equals(other); + + public override int GetHashCode() => + SceneId.GetHashCode(); + + public override string ToString() => + $"Get ISS Descriptor: {SceneId}"; + + public class DiskHashCompute : AbstractDiskHashCompute + { + // Bump if the on-disk format changes incompatibly. v2: switched from "state byte + raw JSON" + // to a single JSON document with state field, so files open in text editors. + private const int ITERATION_NUMBER = 2; + + public static readonly DiskHashCompute INSTANCE = new (); + + private DiskHashCompute() { } + + protected override void FillPayload(IHashKeyPayload keyPayload, in GetISSDescriptorIntention asset) + { + // Entity hash uniquely identifies the deploy — when the scene re-deploys, sceneId changes + // and the old cache entry becomes inert (LRU evicts it). No need to mix in the manifest version. + keyPayload.Put(asset.SceneId); + keyPayload.Put(ITERATION_NUMBER); + } + } + } +} diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/GetISSDescriptorIntention.cs.meta b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/GetISSDescriptorIntention.cs.meta new file mode 100644 index 00000000000..506be5b3721 --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/GetISSDescriptorIntention.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 49d5a0ae6a8d34fb7a98160879cb9abb \ No newline at end of file diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/ISSDescriptorDiskSerializer.cs b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/ISSDescriptorDiskSerializer.cs new file mode 100644 index 00000000000..c5c277f5b7f --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/ISSDescriptorDiskSerializer.cs @@ -0,0 +1,55 @@ +using Cysharp.Threading.Tasks; +using ECS.StreamableLoading.Cache.Disk; +using System.Text; +using System.Threading; +using UnityEngine; + +namespace ECS.StreamableLoading.AssetBundles.InitialSceneState +{ + /// + /// Thin JsonUtility wrapper: the on-disk shape IS , identical to + /// the server's descriptor JSON. The loader's resolution mode (Bundle vs Descriptor) is a live + /// decision (HEAD probe + manifest gate) and is intentionally not cached — a scene that resolved + /// to Descriptor today could legitimately resolve to Bundle tomorrow once the AB converter + /// publishes one. Caching only the JSON contents lets the mode be re-derived on every load. + /// + public class ISSDescriptorDiskSerializer : IDiskSerializer> + { + //TODO (opti): We could serialize as blobs and not jsons + public SerializeMemoryIterator Serialize(ISSDescriptorMetadata data) + { + string json = JsonUtility.ToJson(data); + byte[] payload = Encoding.UTF8.GetBytes(json); + + var state = new State(payload); + + return SerializeMemoryIterator.New( + state, + static (source, index, buffer) => SerializeMemoryIterator.ReadNextData(index, source.Bytes, buffer), + static (source, index, bufferLength) => SerializeMemoryIterator.CanReadNextData(index, source.Bytes.Length, bufferLength)); + } + + public UniTask DeserializeAsync(SlicedOwnedMemory data, CancellationToken token) + { + try + { + string json = Encoding.UTF8.GetString(data.Memory.Span); + return UniTask.FromResult(JsonUtility.FromJson(json)); + } + finally + { + data.Dispose(); + } + } + + public readonly struct State + { + public readonly byte[] Bytes; + + public State(byte[] bytes) + { + Bytes = bytes; + } + } + } +} diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/ISSDescriptorDiskSerializer.cs.meta b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/ISSDescriptorDiskSerializer.cs.meta new file mode 100644 index 00000000000..9a910b7fc8c --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/ISSDescriptorDiskSerializer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3799f3e8aea4a4ccba081657fc2eea7b \ No newline at end of file diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/ISSDescriptorMetadata.cs b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/ISSDescriptorMetadata.cs new file mode 100644 index 00000000000..0e72cb5d9ad --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/ISSDescriptorMetadata.cs @@ -0,0 +1,16 @@ +using DCL.SceneRunner.Scene; +using System; +using System.Collections.Generic; + +namespace ECS.StreamableLoading.AssetBundles.InitialSceneState +{ + /// + /// Server-side JSON DTO for the descriptor file at the LOD manifest bucket. Deserialized by + /// ; not used outside the loader. + /// + [Serializable] + public struct ISSDescriptorMetadata + { + public List assets; + } +} diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/ISSDescriptorMetadata.cs.meta b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/ISSDescriptorMetadata.cs.meta new file mode 100644 index 00000000000..2e2049777b5 --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/ISSDescriptorMetadata.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 22a5d198f98fc439088d9eeceef84c20 \ No newline at end of file diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/InitialSceneStateInfo.cs b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/InitialSceneStateInfo.cs deleted file mode 100644 index f306fa2707a..00000000000 --- a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/InitialSceneStateInfo.cs +++ /dev/null @@ -1,29 +0,0 @@ -using DCL.SceneRunner.Scene; -using ECS.StreamableLoading.AssetBundles; -using System.Collections.Generic; - -namespace ECS.StreamableLoading.InitialSceneState -{ - public struct InitialSceneStateInfo : IInitialSceneState - { - private AssetBundleData? assetBundleData; - public HashSet ISSAssets { get; private set; } - public static InitialSceneStateInfo CreateISS(AssetBundleData assetBundleData, HashSet ISSAssets) => - new () - { - assetBundleData = assetBundleData, - ISSAssets = ISSAssets - }; - - public static InitialSceneStateInfo CreateEmpty() => - new () - { - ISSAssets = new HashSet() - }; - - public void Dispose() - { - assetBundleData?.Dereference(); - } - } -} diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/InitialSceneStateInfo.cs.meta b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/InitialSceneStateInfo.cs.meta deleted file mode 100644 index 8a0a19dc810..00000000000 --- a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/InitialSceneStateInfo.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 76390afd4e6e4565b4964107b12c308b -timeCreated: 1762118627 \ No newline at end of file diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/LoadISSDescriptorSystem.cs b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/LoadISSDescriptorSystem.cs new file mode 100644 index 00000000000..6c384f4995b --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/LoadISSDescriptorSystem.cs @@ -0,0 +1,108 @@ +using Arch.Core; +using Arch.SystemGroups; +using CommunicationData.URLHelpers; +using Cysharp.Threading.Tasks; +using DCL.Diagnostics; +using DCL.Ipfs; +using DCL.Utility; +using DCL.WebRequests; +using ECS.Groups; +using ECS.Prioritization.Components; +using ECS.StreamableLoading.Cache; +using ECS.StreamableLoading.Common.Components; +using ECS.StreamableLoading.Common.Systems; +using ECS.StreamableLoading.Cache.Disk; +using System; +using System.Threading; + +namespace ECS.StreamableLoading.AssetBundles.InitialSceneState +{ + /// + /// Resolves the descriptor JSON for a scene: fetches it from the LOD manifest bucket and hands the + /// parsed back to the consumer. Scenes without ISS data + /// (pre-v49 manifest or missing descriptor JSON) yield a *failed* result — that way the framework + /// never persists the "no ISS" outcome to disk (PutAsync is gated on success), and the consumer + /// in ResolveISSDescriptorSystem calls ISSDescriptor.MarkAsNone. + /// + /// The HEAD probe that chooses between Bundle and Descriptor modes is temporarily disabled — see + /// . A later PR will re-enable Bundle mode. + /// + /// + [UpdateInGroup(typeof(LoadGlobalSystemGroup))] + [LogCategory(ReportCategory.SCENE_LOADING)] + public partial class LoadISSDescriptorSystem : LoadSystemBase + { + // TODO (Juani): Hardcoded for this iteration — wire to DI once the dev/prod bucket split lands. + private static readonly URLDomain DESCRIPTOR_BASE_URL = + URLDomain.FromString("https://lod-unity-bucket-dev-0871c25.s3.us-east-1.amazonaws.com/lods-unity/manifests/"); + + private readonly IWebRequestController webRequestController; + private readonly URLDomain assetBundleURL; + + internal LoadISSDescriptorSystem(World world, IWebRequestController webRequestController, URLDomain assetBundleURL, + IStreamableCache cache, DiskCacheOptions? diskCacheOptions = null) + : base(world, cache, diskCacheOptions) + { + this.webRequestController = webRequestController; + this.assetBundleURL = assetBundleURL; + } + + protected override async UniTask> FlowInternalAsync(GetISSDescriptorIntention intention, StreamableLoadingState state, IPartitionComponent partition, CancellationToken ct) + { + // Skip network roundtrips entirely for AB versions that pre-date ISS support. Returning a + // failed result (plain Exception, not StreamableLoadingException) keeps the disk cache clean + // — LoadSystemBase only persists on success — and doesn't spam logs from the result ctor. + if (!intention.ManifestVersion.SupportsISS()) + return new StreamableLoadingResult(GetReportData(), new Exception("ISS unsupported for this manifest version")); + + ISSDescriptorMetadata? metadata = await TryLoadDescriptorAsync(intention.SceneId, ct); + if (!metadata.HasValue) + return new StreamableLoadingResult(GetReportData(), new Exception("No ISS descriptor JSON for this scene")); + + // Bundle-mode HEAD probe is temporarily disabled — every ISS-capable scene goes through + // Descriptor mode for now. Kept the IsBundleReachableAsync helper below so re-enabling is + // a one-line restore in a later PR; see PR description for rationale. + // bool bundleReachable = await IsBundleReachableAsync(intention.SceneId, intention.ManifestVersion!, ct); + + return new StreamableLoadingResult(metadata.Value); + } + + private async UniTask TryLoadDescriptorAsync(string sceneId, CancellationToken ct) + { + URLAddress url = DESCRIPTOR_BASE_URL.Append(URLPath.FromString($"{sceneId}_InitialSceneState.json")); + + try + { + // Missing-descriptor is the expected case for non-ISS scenes — suppress the underlying + // 403/404 log so it doesn't spam every realm load. The catch below still treats the + // failure as "no ISS for this scene." + return await webRequestController + .GetAsync(new CommonArguments(url), ct, GetReportData(), suppressErrors: true) + .CreateFromJson(WRJsonParser.Unity); + } + catch (OperationCanceledException) { throw; } + catch (Exception) + { + // No descriptor at the expected path — treat as no ISS. + return null; + } + } + + // TODO (Juani): Currently unused: FlowInternalAsync forces Descriptor mode. Kept for the follow-up PR that + // re-enables Bundle-mode selection. + private async UniTask IsBundleReachableAsync(string sceneId, AssetBundleManifestVersion manifestVersion, CancellationToken ct) + { + if (manifestVersion == null || manifestVersion.assetBundleManifestRequestFailed) return false; + + string version = manifestVersion.GetAssetBundleManifestVersion(); + if (string.IsNullOrEmpty(version)) return false; + + string bundleHash = $"staticscene_{sceneId}{PlatformUtils.GetCurrentPlatform()}"; + URLAddress bundleUrl = manifestVersion.HasHashInPath() + ? assetBundleURL.Append(new URLPath($"{version}/{sceneId}/{bundleHash}")) + : assetBundleURL.Append(new URLPath($"{version}/{bundleHash}")); + + return await webRequestController.IsHeadReachableAsync(GetReportData(), bundleUrl, ct, suppressErrors: true); + } + } +} diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/LoadISSDescriptorSystem.cs.meta b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/LoadISSDescriptorSystem.cs.meta new file mode 100644 index 00000000000..6a5cf91f839 --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/InitialSceneState/LoadISSDescriptorSystem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 611bac1a7a22c42d9ae98e754f9b1363 \ No newline at end of file diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/LoadAssetBundleSystem.cs b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/LoadAssetBundleSystem.cs index 676ed3277da..5d4ff126cf8 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/LoadAssetBundleSystem.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/LoadAssetBundleSystem.cs @@ -29,7 +29,6 @@ namespace ECS.StreamableLoading.AssetBundles public partial class LoadAssetBundleSystem : LoadSystemBase { private const string METADATA_FILENAME = "metadata.json"; - private const string STATIC_SCENE_DESCRIPTOR_FILENAME = "StaticSceneDescriptor.json"; private static readonly ThreadSafeObjectPool METADATA_POOL = new (() => new AssetBundleMetadata(), actionOnRelease: metadata => metadata.Clear() @@ -93,13 +92,10 @@ protected override async UniTask> FlowI // get metrics string? metadataJSON; - string? sceneDescriptoJSON; - using (AssetBundleLoadingMutex.LoadingRegion _ = await loadingMutex.AcquireAsync(ct)) { metadataJSON = assetBundle.LoadAsset(METADATA_FILENAME)?.text; - sceneDescriptoJSON = assetBundle.LoadAsset(STATIC_SCENE_DESCRIPTOR_FILENAME)?.text; } // Switch to thread pool to parse JSONs @@ -109,10 +105,6 @@ protected override async UniTask> FlowI AssetBundleData[] dependencies; var mainAsset = ""; - InitialSceneStateMetadata? initialSceneState = null; - - if (!string.IsNullOrEmpty(sceneDescriptoJSON)) - initialSceneState = JsonUtility.FromJson(sceneDescriptoJSON); if (!string.IsNullOrEmpty(metadataJSON)) { @@ -131,7 +123,7 @@ protected override async UniTask> FlowI string source = intention.CommonArguments.CurrentSource.ToStringNonAlloc(); // if the type was not specified don't load any assets - StreamableLoadingResult result = await CreateAssetBundleDataAsync(assetBundle, initialSceneState, intention.ExpectedObjectType, mainAsset, loadingMutex, dependencies, GetReportData(), + StreamableLoadingResult result = await CreateAssetBundleDataAsync(assetBundle, intention.ExpectedObjectType, mainAsset, loadingMutex, dependencies, GetReportData(), intention.AssetBundleManifestVersion == null ? "" : intention.AssetBundleManifestVersion.GetAssetBundleManifestVersion(), source, intention.IsDependency, intention.LookForDependencies, ct); return result; @@ -151,7 +143,7 @@ protected override async UniTask> FlowI } public static async UniTask> CreateAssetBundleDataAsync( - AssetBundle assetBundle, InitialSceneStateMetadata? initialSceneState, Type? expectedObjType, string? mainAsset, + AssetBundle assetBundle, Type? expectedObjType, string? mainAsset, AssetBundleLoadingMutex loadingMutex, AssetBundleData[] dependencies, ReportData reportCategory, @@ -175,7 +167,7 @@ public static async UniTask> CreateAsse Object[]? asset = await LoadAllAssetsAsync(assetBundle, expectedObjType, mainAsset, loadingMutex, reportCategory, ct); - return new StreamableLoadingResult(new AssetBundleData(assetBundle, initialSceneState, asset, expectedObjType, dependencies, + return new StreamableLoadingResult(new AssetBundleData(assetBundle, asset, expectedObjType, dependencies, version: version, source: source)); } diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/PrepareAssetBundleLoadingParametersSystem.cs b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/PrepareAssetBundleLoadingParametersSystem.cs index 919bd3c8eec..16934586a5f 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/PrepareAssetBundleLoadingParametersSystem.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/PrepareAssetBundleLoadingParametersSystem.cs @@ -36,9 +36,6 @@ protected override void Update(float t) assetBundleIntention.AssetBundleManifestVersion = sceneData.SceneEntityDefinition.assetBundleManifestVersion; assetBundleIntention.ParentEntityID = sceneData.SceneEntityDefinition.id; - if (sceneData.InitialSceneStateInfo.ISSAssets.Contains(assetBundleIntention.Hash)) - assetBundleIntention.Hash = GetAssetBundleIntention.BuildInitialSceneStateURL(assetBundleIntention.ParentEntityID); - base.PrepareCommonArguments(in entity, ref assetBundleIntention, ref state); } diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/Tests/EditModeTests/AssetBundleDataShould.cs b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/Tests/EditModeTests/AssetBundleDataShould.cs index 9b900e1f21a..9e8f32b9e1d 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/Tests/EditModeTests/AssetBundleDataShould.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/Tests/EditModeTests/AssetBundleDataShould.cs @@ -11,7 +11,7 @@ public class AssetBundleDataShould public void ProperlyCountReferenceWhenAddReferenceCalled(int refCount) { // Arrange - var assetBundleData = new AssetBundleData(null, null, Array.Empty(), null, null); + var assetBundleData = new AssetBundleData(null, Array.Empty(), null, null); // Act for (var i = 0; i < refCount; i++) @@ -27,7 +27,7 @@ public void ProperlyCountReferenceWhenAddReferenceCalled(int refCount) public void ProperlyRemoveReferenceWhenDereferenced(int initialRefs, int derefs, int remainedRefs) { // Arrange - var assetBundleData = new AssetBundleData(null, null, Array.Empty(), null, null); + var assetBundleData = new AssetBundleData(null, Array.Empty(), null, null); for (var i = 0; i < initialRefs; i++) @@ -46,7 +46,7 @@ public void ProperlyRemoveReferenceWhenDereferenced(int initialRefs, int derefs, public void CannotBeDisposedWhenStillReferenced(int refCount, bool canBeDisposed) { // Arrange - var assetBundleData = new AssetBundleData(null, null, Array.Empty(), null, null); + var assetBundleData = new AssetBundleData(null, Array.Empty(), null, null); for (var i = 0; i < refCount; i++) diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/Tests/EditModeTests/DepsDigestCacheKeyShould.cs b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/Tests/EditModeTests/DepsDigestCacheKeyShould.cs index 00943212d72..fb67badf243 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/Tests/EditModeTests/DepsDigestCacheKeyShould.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/StreamableLoading/AssetBundles/Tests/EditModeTests/DepsDigestCacheKeyShould.cs @@ -276,13 +276,11 @@ public void VersionPredicates_DoNotThrowOnNonVNVersions() var lodManifest = AssetBundleManifestVersion.CreateForLOD("LOD/0", "dummyDate"); Assert.That(lodManifest.HasHashInPath(), Is.False); - Assert.That(lodManifest.SupportsInitialSceneState(), Is.False); Assert.That(lodManifest.SupportsDepsDigests(), Is.False); var manualManifest = AssetBundleManifestVersion.CreateManualManifest(); Assert.That(manualManifest.HasHashInPath(), Is.False); - Assert.That(manualManifest.SupportsInitialSceneState(), Is.False); Assert.That(manualManifest.SupportsDepsDigests(), Is.False); var wrongString = AssetBundleManifestVersion.CreateFromFallback("v", "dummyDate"); diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Asset/Components/GltfContainerAsset.cs b/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Asset/Components/GltfContainerAsset.cs index a9223ded73a..536319fd4e2 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Asset/Components/GltfContainerAsset.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Asset/Components/GltfContainerAsset.cs @@ -60,11 +60,10 @@ public struct VisibleMeshCollider public List? DecodedVisibleSDKColliders; public IStreamableRefCountData AssetData { get; private set; } - public bool IsISS; private GltfContainerAsset(GameObject root, IStreamableRefCountData assetData, List invisibleColliders, List visibleColliderMeshes, List renderers, List animations, - List animators, IReadOnlyList? hierarchyPaths = null, bool isISS = false) + List animators, IReadOnlyList? hierarchyPaths = null) { this.AssetData = assetData; @@ -75,7 +74,6 @@ private GltfContainerAsset(GameObject root, IStreamableRefCountData assetData, L Animations = animations; Animators = animators; HierarchyPaths = hierarchyPaths; - IsISS = isISS; ProfilingCounters.GltfContainerAssetsAmount.Value++; } @@ -117,7 +115,7 @@ public void Dispose() ProfilingCounters.GltfContainerAssetsAmount.Value--; } - public static GltfContainerAsset Create(GameObject root, IStreamableRefCountData assetData, bool isPartOfInitialSceneState = false, IReadOnlyList? hierarchyPaths = null) => - new (root, assetData, COLLIDERS_POOL.Get(), VISIBLE_MESH_COLLIDERS_POOL.Get(), RENDERERS_POOL.Get(), ANIMATIONS_POOL.Get(), ANIMATORS_POOL.Get(), hierarchyPaths, isISS: isPartOfInitialSceneState); + public static GltfContainerAsset Create(GameObject root, IStreamableRefCountData assetData, IReadOnlyList? hierarchyPaths = null) => + new (root, assetData, COLLIDERS_POOL.Get(), VISIBLE_MESH_COLLIDERS_POOL.Get(), RENDERERS_POOL.Get(), ANIMATIONS_POOL.Get(), ANIMATORS_POOL.Get(), hierarchyPaths); } } diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Asset/Systems/CreateGltfAssetFromAssetBundleSystem.cs b/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Asset/Systems/CreateGltfAssetFromAssetBundleSystem.cs index 0b7be08d12e..eb4d6b9dcda 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Asset/Systems/CreateGltfAssetFromAssetBundleSystem.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Asset/Systems/CreateGltfAssetFromAssetBundleSystem.cs @@ -57,7 +57,6 @@ private void ConvertFromAssetBundle(in Entity entity, ref GetGltfContainerAssetI AssetBundleData assetBundleData = assetBundleResult.Asset!; - // Create a new container root. It will be cached and pooled if (Utils.TryCreateGltfObject(assetBundleData, assetIntention.Hash, out GltfContainerAsset result)) World.Add(entity, new StreamableLoadingResult(result)); else diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Asset/Tests/GltfContainerTestResources.cs b/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Asset/Tests/GltfContainerTestResources.cs index 087dfd4c37a..eabb6ae07a4 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Asset/Tests/GltfContainerTestResources.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Asset/Tests/GltfContainerTestResources.cs @@ -47,12 +47,12 @@ public async UniTask> LoadAssetBundle(s try { - shaderAssetBundleData = await LoadAssetBundleSystem.CreateAssetBundleDataAsync(shaderAssetBundle, null, null, "", mutex, Array.Empty(), + shaderAssetBundleData = await LoadAssetBundleSystem.CreateAssetBundleDataAsync(shaderAssetBundle, null, "", mutex, Array.Empty(), ReportCategory.ASSET_BUNDLES, "", "", true, false, CancellationToken.None); shaderAssetBundleData.Asset.AddReference(); - assetBundleData = await LoadAssetBundleSystem.CreateAssetBundleDataAsync(assetBundle, null, typeof(GameObject), "", mutex, new[] { shaderAssetBundleData.Asset }, + assetBundleData = await LoadAssetBundleSystem.CreateAssetBundleDataAsync(assetBundle, typeof(GameObject), "", mutex, new[] { shaderAssetBundleData.Asset }, ReportCategory.ASSET_BUNDLES, "", "", false, false, CancellationToken.None); return assetBundleData; } diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Systems/CleanUpGltfContainerSystem.cs b/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Systems/CleanUpGltfContainerSystem.cs index 3fd2a2a81bb..644eaf37c96 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Systems/CleanUpGltfContainerSystem.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Systems/CleanUpGltfContainerSystem.cs @@ -1,9 +1,10 @@ -using Arch.Core; +using Arch.Core; using Arch.System; using Arch.SystemGroups; using DCL.Diagnostics; using DCL.Interaction.Utility; using DCL.LOD.Systems; +using DCL.SceneRunner.Scene; using ECS.Abstract; using ECS.Groups; using ECS.LifeCycle; @@ -26,12 +27,14 @@ public partial class CleanUpGltfContainerSystem : BaseUnityLoopSystem, IFinalize private IPartitionComponent scenePartition; private IGltfContainerAssetsCache cache; private IEntityCollidersSceneCache entityCollidersSceneCache; + private readonly ISSDescriptor issDescriptor; - internal CleanUpGltfContainerSystem(World world, IGltfContainerAssetsCache cache, IEntityCollidersSceneCache entityCollidersSceneCache, IPartitionComponent scenePartition) : base(world) + internal CleanUpGltfContainerSystem(World world, IGltfContainerAssetsCache cache, IEntityCollidersSceneCache entityCollidersSceneCache, IPartitionComponent scenePartition, ISSDescriptor issDescriptor) : base(world) { this.scenePartition = scenePartition; this.cache = cache; this.entityCollidersSceneCache = entityCollidersSceneCache; + this.issDescriptor = issDescriptor; } protected override void Update(float t) @@ -58,12 +61,18 @@ public void FinalizeComponents(in Query query) DestroyWithScenePartitionQuery(World); } - private void DestroyGLTFContainer(ref GltfContainerComponent component, bool putInBridge) + private void DestroyGLTFContainer(ref GltfContainerComponent component, bool partitionAllowsBridge) { if (component.Promise.TryGetResult(World, out StreamableLoadingResult result) && result.Succeeded) { entityCollidersSceneCache.Remove(result.Asset); - cache.Dereference(component.CacheKey, result.Asset, putInBridge && result.Asset.IsISS); + + // Bridge only if the scene's partition allows it AND the descriptor still has a slot for this hash + // (capped to the exact number of times it appears in the descriptor — no duplicates). + // Null descriptor means no ISS for this scene → no bridging. + bool putInBridge = partitionAllowsBridge + && issDescriptor.TryReserveBridgeSlot(component.Hash); + cache.Dereference(component.CacheKey, result.Asset, putInBridge); } component.RootGameObject = null; diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Tests/CleanUpGltfContainerSystemShould.cs b/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Tests/CleanUpGltfContainerSystemShould.cs index 725f63cd1aa..88dafc7a8ba 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Tests/CleanUpGltfContainerSystemShould.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Tests/CleanUpGltfContainerSystemShould.cs @@ -1,6 +1,7 @@ using Arch.Core; using DCL.ECSComponents; using DCL.Interaction.Utility; +using DCL.Ipfs; using ECS.LifeCycle.Components; using ECS.Prioritization.Components; using ECS.StreamableLoading.Common; @@ -29,7 +30,7 @@ public class CleanUpGltfContainerSystemShould : UnitySystemTestBase(); - system = new CleanUpGltfContainerSystem(world, cache, collidersSceneCache = Substitute.For(), PartitionComponent.TOP_PRIORITY); + system = new CleanUpGltfContainerSystem(world, cache, collidersSceneCache = Substitute.For(), PartitionComponent.TOP_PRIORITY, null); } [Test] diff --git a/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Utils.cs b/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Utils.cs index ff7261550e4..f1e54ac84e4 100644 --- a/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Utils.cs +++ b/Explorer/Assets/DCL/Infrastructure/ECS/Unity/GLTFContainer/Utils.cs @@ -25,7 +25,7 @@ public static bool TryCreateGltfObject(AssetBundleData assetBundleData, string a container.SetActive(false); Transform containerTransform = container.transform; - var result = GltfContainerAsset.Create(container, assetBundleData, assetBundleData.InitialSceneStateMetadata.HasValue); + var result = GltfContainerAsset.Create(container, assetBundleData); GameObject? instance = Object.Instantiate(asset, containerTransform); diff --git a/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/GlobalWorldFactory.cs b/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/GlobalWorldFactory.cs index b37413dafec..862eb451f25 100644 --- a/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/GlobalWorldFactory.cs +++ b/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/GlobalWorldFactory.cs @@ -1,4 +1,5 @@ using Arch.Core; +using DCL.SceneRunner.Scene; using Arch.SystemGroups; using CommunicationData.URLHelpers; using CrdtEcsBridge.RestrictedActions; @@ -32,7 +33,9 @@ using ECS.SceneLifeCycle.Reporting; using ECS.SceneLifeCycle.SceneDefinition; using ECS.SceneLifeCycle.Systems; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; using ECS.StreamableLoading.Cache; +using ECS.StreamableLoading.Common.Systems; using SceneRunner; using SceneRunner.Scene; using System.Collections.Generic; @@ -40,7 +43,6 @@ using DCL.Profiles; using DCL.RealmNavigation; using DCL.Roads.Systems; -using ECS.SceneLifeCycle.Systems.EarlyAsset; using SystemGroups.Visualiser; using UnityEngine; using Utility; @@ -165,6 +167,15 @@ public GlobalWorld Create(ISceneFactory sceneFactory, Entity playerEntity) var assetBundleCdnUrl = URLDomain.FromString(urlsSource.Url(DecentralandUrl.AssetBundlesCDN)); + LoadISSDescriptorSystem.InjectToWorld(ref builder, webRequestController, assetBundleCdnUrl, + new NoCache(false, false), + new DiskCacheOptions(staticContainer.ISSDescriptorDiskCache, GetISSDescriptorIntention.DiskHashCompute.INSTANCE, "iss.json")); + + // Mutates the entity's ISSDescriptor component (class, ref-shared) in place when the resolver + // promise spawned by ResolveSceneStateByIncreasingRadiusSystem completes. Cached references in + // OrderedDataManaged + downstream queries see the resolved state without a refetch. + ResolveISSDescriptorSystem.InjectToWorld(ref builder); + if (hybridSceneParams.EnableHybridScene) { URLDomain worldContentServerBaseUrl = URLDomain.FromString(urlsSource.Url(DecentralandUrl.WorldServer)); @@ -215,8 +226,6 @@ public GlobalWorld Create(ISceneFactory sceneFactory, Entity playerEntity) UpdateCurrentSceneSystem.InjectToWorld(ref builder, realmData, scenesCache, currentSceneInfo, playerEntity, debugContainerBuilder); - EarlySceneRequestSystem.InjectToWorld(ref builder, startParcel, realmData, urlsSource); - LoadSmartWearableSceneSystem.InjectToWorld(ref builder, NoCache.INSTANCE, webRequestController, sceneFactory, staticContainer.SmartWearableCache, urlsSource); LoadSmartWearablePreviewSceneSystem.InjectToWorld(ref builder, webRequestController, urlsSource); diff --git a/Explorer/Assets/DCL/Infrastructure/Global/StaticContainer.cs b/Explorer/Assets/DCL/Infrastructure/Global/StaticContainer.cs index ea93667f171..44ede7b02ac 100644 --- a/Explorer/Assets/DCL/Infrastructure/Global/StaticContainer.cs +++ b/Explorer/Assets/DCL/Infrastructure/Global/StaticContainer.cs @@ -1,4 +1,5 @@ using Arch.Core; +using DCL.SceneRunner.Scene; using CommunicationData.URLHelpers; using CrdtEcsBridge.Components; using Cysharp.Threading.Tasks; @@ -56,6 +57,7 @@ using DCL.SDKComponents.SkyboxTime; using DCL.Utility; using ECS.SceneLifeCycle.IncreasingRadius; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; using ECS.StreamableLoading.Cache.Disk; using ECS.StreamableLoading.Common.Components; using ECS.StreamableLoading.Textures; @@ -134,6 +136,7 @@ public class StaticContainer : IDCLPlugin public IGltfContainerAssetsCache GltfContainerAssetsCache { get; private set; } public AssetPreLoadCache AssetPreLoadCache { get; private set; } + public DiskCache> ISSDescriptorDiskCache { get; private set; } public void Dispose() { @@ -250,6 +253,8 @@ public UniTask InitializeAsync(StaticSettings settings, CancellationToken ct) var textureDiskCache = new DiskCache>(diskCache, new TextureDiskSerializer()); var textureResolvePlugin = new TexturesLoadingPlugin(container.WebRequestsContainer.WebRequestController, container.CacheCleaner, textureDiskCache, launchMode, container.ProfilesContainer.Repository); + container.ISSDescriptorDiskCache = new DiskCache>(diskCache, new ISSDescriptorDiskSerializer()); + diagnosticsContainer.AddSentryScopeConfigurator(scope => { if (container.ScenesCache.CurrentScene.Value != null) diff --git a/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/IInitialSceneState.cs b/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/IInitialSceneState.cs deleted file mode 100644 index ca085b00b0b..00000000000 --- a/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/IInitialSceneState.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; - -namespace DCL.SceneRunner.Scene -{ - public interface IInitialSceneState - { - void Dispose(); - - HashSet ISSAssets { get; } - } -} diff --git a/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/IInitialSceneState.cs.meta b/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/IInitialSceneState.cs.meta deleted file mode 100644 index d74caf4a2fd..00000000000 --- a/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/IInitialSceneState.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 79c2173d424a482e961afd5e78325be6 -timeCreated: 1762119443 \ No newline at end of file diff --git a/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/ISSDescriptor.cs b/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/ISSDescriptor.cs new file mode 100644 index 00000000000..f1feee55f77 --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/ISSDescriptor.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; + +namespace DCL.SceneRunner.Scene +{ + /// + /// Per-entity Initial Scene State component for a single scene. Class semantics — the same + /// reference lives on the entity for the scene's lifetime, with internal state mutating in place + /// when the resolver promise completes (see / ). + /// Held on . + /// + /// Lifecycle: constructed in for scenes that may have + /// ISS, or in for opt-outs (PX / static-pointer / smart-wearable + /// previews). On promise completion the resolver calls with the descriptor's + /// asset list (success) or (failure / no ISS). Mutating in place lets cached + /// references elsewhere (e.g. OrderedDataManaged in the radius system) see the update without + /// a refetch. + /// + /// + public class ISSDescriptor + { + /// + /// Shared sentinel used by opt-out paths (PX / static-pointer / smart-wearable preview) so they + /// don't have to construct a fresh descriptor just to express "no ISS." Safe to share because + /// descriptors have no per-scene mutable state. + /// + public static readonly ISSDescriptor NONE = new (ISSDescriptorState.None); + + public static ISSDescriptor CreateUninitialized() => + new (ISSDescriptorState.Uninitialized); + + public ISSDescriptorState CurrentState { get; private set; } + public IReadOnlyList Assets { get; private set; } + + // hash -> how many times that hash appears in the descriptor (the cap for bridge slots) + private readonly Dictionary hashCapacity = new (); + + // hash -> how many copies are currently parked in the bridge + private readonly Dictionary bridgedCount = new (); + + private ISSDescriptor(ISSDescriptorState state) + { + CurrentState = state; + Assets = Array.Empty(); + } + + /// + /// Transitions the descriptor to with the given + /// asset list. Called by ResolveISSDescriptorSystem when the loader promise succeeds. + /// + public void MarkResolved(IReadOnlyList? assets) + { + CurrentState = ISSDescriptorState.Descriptor; + Assets = assets ?? Array.Empty(); + for (var i = 0; i < Assets.Count; i++) + { + string hash = Assets[i].hash; + hashCapacity[hash] = hashCapacity.TryGetValue(hash, out int n) ? n + 1 : 1; + } + } + + /// + /// Transitions the descriptor to . Called by + /// ResolveISSDescriptorSystem when the loader promise fails (scene has no ISS). + /// + public void MarkAsNone() + { + CurrentState = ISSDescriptorState.None; + Assets = Array.Empty(); + } + + public bool TryReserveBridgeSlot(string hash) + { + if (CurrentState is ISSDescriptorState.None or ISSDescriptorState.Uninitialized) return false; + if (!hashCapacity.TryGetValue(hash, out int cap)) return false; + + int current = bridgedCount.TryGetValue(hash, out int n) ? n : 0; + if (current >= cap) return false; + + bridgedCount[hash] = current + 1; + return true; + } + + public void TryReleaseBridgeSlot(string hash) + { + if (bridgedCount.TryGetValue(hash, out int n) && n > 0) + bridgedCount[hash] = n - 1; + } + + public bool SupportsDescriptor() => + CurrentState == ISSDescriptorState.Descriptor; + } +} diff --git a/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/ISSDescriptor.cs.meta b/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/ISSDescriptor.cs.meta new file mode 100644 index 00000000000..37b0c0c7e06 --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/ISSDescriptor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3afbdaa314e6a4dcca9c10085220c35d \ No newline at end of file diff --git a/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/ISSDescriptorState.cs b/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/ISSDescriptorState.cs new file mode 100644 index 00000000000..90a31f7a959 --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/ISSDescriptorState.cs @@ -0,0 +1,29 @@ +using System; +using UnityEngine; + +namespace DCL.SceneRunner.Scene +{ + /// + /// Resolution outcome for a scene's Initial Scene State. Carried on + /// . + /// + public enum ISSDescriptorState + { + /// + /// The descriptor has not yet been resolved. The radius gate spawns the resolver promise when it sees this state. + /// + Uninitialized, + None, + Bundle, + Descriptor, + } + + [Serializable] + public struct ISSDescriptorAsset + { + public string hash; + public Vector3 position; + public Quaternion rotation; + public Vector3 scale; + } +} diff --git a/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/ISSDescriptorState.cs.meta b/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/ISSDescriptorState.cs.meta new file mode 100644 index 00000000000..c6cbacf7c11 --- /dev/null +++ b/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/ISSDescriptorState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0e4a98db1aef94a9997a02a58492862c \ No newline at end of file diff --git a/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/ISceneData.cs b/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/ISceneData.cs index 14de9b11052..779b5cc0c16 100644 --- a/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/ISceneData.cs +++ b/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/ISceneData.cs @@ -14,7 +14,13 @@ public interface ISceneData /// SceneLoadingConcluded is TRUE when the scene has been repositioned to its rightful place away from MORDOR /// bool SceneLoadingConcluded { get; set; } - IInitialSceneState InitialSceneStateInfo { get; } + + /// + /// The resolved Initial Scene State descriptor for this scene, or null when no ISS is in play + /// (pre-v49 manifest, descriptor JSON missing, or wearable / portable-experience scenes that + /// don't go through the ISS loader). + /// + ISSDescriptor? ISSDescriptor { get; } SceneShortInfo SceneShortInfo { get; } @@ -111,7 +117,7 @@ public bool SceneLoadingConcluded set { } } - public IInitialSceneState InitialSceneStateInfo { get; } = new FakeInitialSceneState(); + public ISSDescriptor? ISSDescriptor => null; public SceneShortInfo SceneShortInfo => new (Vector2Int.zero, "Fake"); public IReadOnlyList Parcels { get; } = new List(); @@ -171,15 +177,6 @@ public bool IsSDKVersionOrHigher(string minVersion) => public bool IsSDKVersion(string version) => false; } - - public class FakeInitialSceneState : IInitialSceneState - { - public void Dispose() - { - } - - public HashSet ISSAssets { get; } = new HashSet(); - } } } diff --git a/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/SceneData.cs b/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/SceneData.cs index bfd441167a4..731579f4088 100644 --- a/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/SceneData.cs +++ b/Explorer/Assets/DCL/Infrastructure/SceneRunner/Scene/SceneData.cs @@ -1,8 +1,8 @@ using CommunicationData.URLHelpers; using DCL.Diagnostics; using DCL.Ipfs; -using SceneRuntime.ScenePermissions; using DCL.SceneRunner.Scene; +using SceneRuntime.ScenePermissions; using System; using System.Collections.Generic; using System.Text.RegularExpressions; @@ -27,7 +27,7 @@ public class SceneData : ISceneData public SceneEntityDefinition SceneEntityDefinition { get; } public StaticSceneMessages StaticSceneMessages { get; } public bool SceneLoadingConcluded { get; set; } - public IInitialSceneState InitialSceneStateInfo { get; } + public ISSDescriptor ISSDescriptor { get; } public SceneShortInfo SceneShortInfo { get; } public ParcelMathHelper.SceneGeometry Geometry { get; } public IReadOnlyList Parcels { get; } @@ -40,7 +40,7 @@ public SceneData( ParcelMathHelper.SceneGeometry geometry, IReadOnlyList parcels, StaticSceneMessages staticSceneMessages, - IInitialSceneState initialSceneStateInfo) + ISSDescriptor issDescriptor) { SceneContent = sceneContent; SceneEntityDefinition = sceneDefinition; @@ -48,7 +48,7 @@ public SceneData( Parcels = parcels; SceneShortInfo = new SceneShortInfo(baseParcel, sceneDefinition.id, sceneDefinition.metadata.sdkVersion); Geometry = geometry; - InitialSceneStateInfo = initialSceneStateInfo; + ISSDescriptor = issDescriptor; } public bool HasRequiredPermission(string permission) diff --git a/Explorer/Assets/DCL/Infrastructure/SceneRunner/SceneFacade.cs b/Explorer/Assets/DCL/Infrastructure/SceneRunner/SceneFacade.cs index 26d455651ad..9163f64eb33 100644 --- a/Explorer/Assets/DCL/Infrastructure/SceneRunner/SceneFacade.cs +++ b/Explorer/Assets/DCL/Infrastructure/SceneRunner/SceneFacade.cs @@ -79,11 +79,9 @@ public void Dispose() SceneStateProvider.State.Set(SceneState.Disposing); runtimeInstance.SetIsDisposing(); - #if ALTTESTER AlttesterSceneReadinessProbe.ClearIfCurrent(this); #endif - DisposeInternal(); SceneStateProvider.State.Set(SceneState.Disposed); @@ -116,7 +114,6 @@ public async UniTask DisposeAsync() await UniTask.Yield(PlayerLoopTiming.Initialization); DisposeInternal(); - SceneData.InitialSceneStateInfo.Dispose(); SceneStateProvider.State.Set(SceneState.Disposed); } diff --git a/Explorer/Assets/DCL/Infrastructure/SceneRunner/SceneFactory.cs b/Explorer/Assets/DCL/Infrastructure/SceneRunner/SceneFactory.cs index a9215cabcca..57b80df8ba6 100644 --- a/Explorer/Assets/DCL/Infrastructure/SceneRunner/SceneFactory.cs +++ b/Explorer/Assets/DCL/Infrastructure/SceneRunner/SceneFactory.cs @@ -1,5 +1,6 @@ using CommunicationData.URLHelpers; using CRDT.Serializer; +using DCL.SceneRunner.Scene; using CrdtEcsBridge.Components; using CrdtEcsBridge.JsModulesImplementation; using CrdtEcsBridge.JsModulesImplementation.Communications; @@ -20,6 +21,7 @@ using DCL.WebRequests; using ECS; using ECS.Prioritization.Components; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; using Microsoft.ClearScript; using MVC; using PortableExperiences.Controller; @@ -135,7 +137,7 @@ public async UniTask CreateSceneFromFileAsync(string jsCodeUrl, IP ); var sceneData = new SceneData(new SceneNonHashedContent(baseUrl), sceneDefinition, Vector2Int.zero, - ParcelMathHelper.UNDEFINED_SCENE_GEOMETRY, Array.Empty(), StaticSceneMessages.EMPTY, new ISceneData.FakeInitialSceneState()); + ParcelMathHelper.UNDEFINED_SCENE_GEOMETRY, Array.Empty(), StaticSceneMessages.EMPTY, ISSDescriptor.NONE); return await CreateSceneAsync(sceneData, new AllowEverythingJsApiPermissionsProvider(), partitionProvider, ct); } @@ -156,7 +158,7 @@ public async UniTask CreateSceneFromStreamableDirectoryAsync(strin var sceneDefinition = new SceneEntityDefinition(directoryName, sceneMetadata); var sceneData = new SceneData(new SceneNonHashedContent(fullPath), sceneDefinition, - Vector2Int.zero, ParcelMathHelper.UNDEFINED_SCENE_GEOMETRY, Array.Empty(), StaticSceneMessages.EMPTY, new ISceneData.FakeInitialSceneState()); + Vector2Int.zero, ParcelMathHelper.UNDEFINED_SCENE_GEOMETRY, Array.Empty(), StaticSceneMessages.EMPTY, ISSDescriptor.NONE); return await CreateSceneAsync(sceneData, new AllowEverythingJsApiPermissionsProvider(), partitionProvider, ct); } diff --git a/Explorer/Assets/DCL/LOD/Components/InitialSceneStateLOD.cs b/Explorer/Assets/DCL/LOD/Components/InitialSceneStateLOD.cs index 35d4e1b5706..6b6f3afd512 100644 --- a/Explorer/Assets/DCL/LOD/Components/InitialSceneStateLOD.cs +++ b/Explorer/Assets/DCL/LOD/Components/InitialSceneStateLOD.cs @@ -13,6 +13,7 @@ namespace DCL.LOD.Components public class InitialSceneStateLOD { private readonly List Assets = new (); + public string SceneID { get; private set; } = string.Empty; public GameObject ParentContainer { get; private set; } public IGltfContainerAssetsCache gltfCache { get; private set; } public int TotalAssetsToInstantiate { get; private set; } @@ -45,6 +46,14 @@ public void ForgetLoading(World world) { Generation++; Clear(); + + // Destroy the container too, otherwise ResolveISSLODSystem's "promises already spawned" + // guard (ParentContainer != null) misreads a stale container from this aborted run as + // "current run is already in progress" and never re-spawns promises on re-entry — + // leaving AllAssetsInstantiated() permanently false (Assets.Count == 0 ≠ Total). + // Unity's overloaded == treats a destroyed GameObject as null, so EnsureParentContainer + // and the guard both behave correctly after the destroy. + UnityObjectUtils.SafeDestroy(ParentContainer); } CurrentState = State.UNINITIALIZED; @@ -80,7 +89,7 @@ public void AddResolvedAsset(string assetHash, GltfContainerAsset asset) => }); public bool AllAssetsInstantiated() => - AssetBundleData != null && Assets.Count == TotalAssetsToInstantiate; + ParentContainer != null && Assets.Count == TotalAssetsToInstantiate; public bool IsProcessing() => CurrentState is State.PROCESSING; @@ -88,14 +97,31 @@ public bool IsProcessing() => public void Initialize(string sceneID, Vector3 sceneGeometryBaseParcelPosition, AssetBundleData resultAsset, IGltfContainerAssetsCache gltfContainerAssetsCache, int assetHashCount) { - if (ParentContainer == null) - ParentContainer = new GameObject($"{sceneID}_ISS_LOD"); - ParentContainer.transform.position = sceneGeometryBaseParcelPosition; + EnsureParentContainer(sceneID, sceneGeometryBaseParcelPosition); AssetBundleData = resultAsset; gltfCache = gltfContainerAssetsCache; TotalAssetsToInstantiate = assetHashCount; } + /// + /// Descriptor-only initialization: no shared ISS bundle to hold; each asset will arrive via its own promise. + /// + public void InitializeFromDescriptor(string sceneID, Vector3 sceneGeometryBaseParcelPosition, IGltfContainerAssetsCache gltfContainerAssetsCache, int assetHashCount) + { + EnsureParentContainer(sceneID, sceneGeometryBaseParcelPosition); + AssetBundleData = null; + gltfCache = gltfContainerAssetsCache; + TotalAssetsToInstantiate = assetHashCount; + } + + private void EnsureParentContainer(string sceneID, Vector3 sceneGeometryBaseParcelPosition) + { + SceneID = sceneID; + if (ParentContainer == null) + ParentContainer = new GameObject($"{sceneID}_ISS_LOD"); + ParentContainer.transform.position = sceneGeometryBaseParcelPosition; + } + public void AddFailedAsset(string creationHelperAssetHash) { Assets.Add(new ISSStoredAsset diff --git a/Explorer/Assets/DCL/LOD/Components/SceneLODInfo.cs b/Explorer/Assets/DCL/LOD/Components/SceneLODInfo.cs index c9d18c6ebc0..3d15a4437b1 100644 --- a/Explorer/Assets/DCL/LOD/Components/SceneLODInfo.cs +++ b/Explorer/Assets/DCL/LOD/Components/SceneLODInfo.cs @@ -125,8 +125,8 @@ private void CalculateLODBounds(Renderer[] lodRenderers, int renderersCount) for (int i = 1; i < renderersCount; i++) mergedBounds.Encapsulate(lodRenderers[i].bounds); - //Object size required to be the largest of the 3 axis - metadata.LodGroup.size = Mathf.Max(Mathf.Max(mergedBounds.size.x, mergedBounds.size.y), mergedBounds.size.z); + //Object size based on the Y axis, as screen-relative height is what Unity evaluates for LODGroup transitions + metadata.LodGroup.size = mergedBounds.size.y; } private void CalculateCullRelativeHeight(float defaultFOV, float defaultLodBias, int loadingDistance) diff --git a/Explorer/Assets/DCL/LOD/Systems/ResolveISSLODSystem.cs b/Explorer/Assets/DCL/LOD/Systems/ResolveISSLODSystem.cs index 26b5a11ed12..970de1751c8 100644 --- a/Explorer/Assets/DCL/LOD/Systems/ResolveISSLODSystem.cs +++ b/Explorer/Assets/DCL/LOD/Systems/ResolveISSLODSystem.cs @@ -2,18 +2,21 @@ using Arch.System; using Arch.SystemGroups; using DCL.Diagnostics; +using DCL.Ipfs; using DCL.LOD.Components; using DCL.Optimization.PerformanceBudgeting; +using DCL.Utility; using ECS.Abstract; +using System.Collections.Generic; using ECS.Prioritization.Components; using ECS.SceneLifeCycle; using ECS.SceneLifeCycle.SceneDefinition; +using DCL.SceneRunner.Scene; using ECS.StreamableLoading.AssetBundles; using ECS.StreamableLoading.Common.Components; using ECS.Unity.GLTFContainer; using ECS.Unity.GLTFContainer.Asset.Cache; using ECS.Unity.GLTFContainer.Asset.Components; -using System; using UnityEngine; using AssetBundlePromise = ECS.StreamableLoading.Common.AssetPromise; @@ -24,11 +27,11 @@ namespace DCL.LOD.Systems [LogCategory(ReportCategory.LOD)] public partial class ResolveISSLODSystem : BaseUnityLoopSystem { - private IGltfContainerAssetsCache gltfCache; + private readonly IGltfContainerAssetsCache gltfCache; private readonly IPerformanceBudget instantiationFrameTimeBudget; private readonly IPerformanceBudget memoryBudget; - public ResolveISSLODSystem(World world, IGltfContainerAssetsCache gltfCache, IPerformanceBudget instantiationFrameTimeBudget, IPerformanceBudget memoryBudget) : base(world) + internal ResolveISSLODSystem(World world, IGltfContainerAssetsCache gltfCache, IPerformanceBudget instantiationFrameTimeBudget, IPerformanceBudget memoryBudget) : base(world) { this.gltfCache = gltfCache; this.instantiationFrameTimeBudget = instantiationFrameTimeBudget; @@ -37,138 +40,160 @@ public ResolveISSLODSystem(World world, IGltfContainerAssetsCache gltfCache, IPe protected override void Update(float t) { - ResolveInitialSceneStateLODQuery(World); + ResolveInitialSceneStateLODDescriptorQuery(World); ConvertFromAssetBundleQuery(World); } [Query] - private void ResolveInitialSceneStateLOD(ref SceneLODInfo sceneLODInfo, ref SceneDefinitionComponent sceneDefinition) + private void ResolveInitialSceneStateLODDescriptor(ref SceneLODInfo sceneLODInfo, ref SceneDefinitionComponent sceneDefinition, ISSDescriptor issDescriptor) { InitialSceneStateLOD initialSceneStateLOD = sceneLODInfo.InitialSceneStateLOD; - if (initialSceneStateLOD.CurrentState == InitialSceneStateLOD.State.PROCESSING) + if (initialSceneStateLOD.CurrentState != InitialSceneStateLOD.State.PROCESSING) return; + + // Only the descriptor-mode path is owned by this query. + if (issDescriptor.CurrentState != ISSDescriptorState.Descriptor) return; + + // First-time entry: nothing to do once promises have been spawned. + if (initialSceneStateLOD.ParentContainer != null) return; + + IReadOnlyList assets = issDescriptor.Assets; + + initialSceneStateLOD.InitializeFromDescriptor(sceneLODInfo.id, sceneDefinition.SceneGeometry.BaseParcelPosition, + gltfCache, assets.Count); + + SpawnAssetPromises(initialSceneStateLOD, assets, sceneDefinition, issDescriptor); + } + + /// + /// For each entry in the descriptor either positions a cached asset immediately or spawns + /// a new for it. + /// + private void SpawnAssetPromises(InitialSceneStateLOD initialSceneStateLOD, IReadOnlyList assets, SceneDefinitionComponent sceneDefinition, ISSDescriptor issDescriptor) + { + AssetBundleManifestVersion? manifest = sceneDefinition.Definition.assetBundleManifestVersion; + + for (var i = 0; i < assets.Count; i++) { - // Skip if promise hasn't been created yet or is already consumed - if (initialSceneStateLOD.AssetBundlePromise == AssetBundlePromise.NULL || initialSceneStateLOD.AssetBundlePromise.IsConsumed) return; + ISSDescriptorAsset entry = assets[i]; - if (initialSceneStateLOD.AssetBundlePromise.TryConsume(World, out StreamableLoadingResult Result)) + // The GLTF container cache is keyed by "hash@digest" (see AssetBundleManifestVersionExtensions.ComposeCacheKey). + // Looking up by bare hash misses any bridged entry the SDK runtime left behind, so the LOD spawns a + // second instance of an asset that's already resident — that's the visible "double" overlap. + string cacheKey = manifest.ComposeCacheKey(entry.hash); + + if (gltfCache.TryGet(cacheKey, out var asset)) { - if (Result.Succeeded) - { - if (Result.Asset!.InitialSceneStateMetadata.HasValue) - { - InitialSceneStateMetadata initialSceneStateMetadata = Result.Asset!.InitialSceneStateMetadata.Value; - initialSceneStateLOD.Initialize(sceneLODInfo.id, sceneDefinition.SceneGeometry.BaseParcelPosition, Result.Asset, - gltfCache, initialSceneStateMetadata.assetHash.Count); - - for (var i = 0; i < initialSceneStateMetadata.assetHash.Count; i++) - { - string assetHash = initialSceneStateMetadata.assetHash[i]; - - if (gltfCache.TryGet(assetHash, out var asset)) - PositionAsset(initialSceneStateLOD, assetHash, asset, initialSceneStateLOD.ParentContainer.transform, initialSceneStateMetadata, i); - else - { - //Little bit redundant, but needed for correct ref counting - AssetBundlePromise promise = AssetBundlePromise.Create(World, - GetAssetBundleIntention.FromHash(GetAssetBundleIntention.BuildInitialSceneStateURL(sceneDefinition.Definition.id), - assetBundleManifestVersion: sceneDefinition.Definition.assetBundleManifestVersion, - parentEntityID: sceneDefinition.Definition.id), - PartitionComponent.TOP_PRIORITY); - - ISSAssetCreationHelper assetCreationHelper - = new ISSAssetCreationHelper(initialSceneStateLOD, assetHash, i); - - World.Create(promise, assetCreationHelper); - } - } - } - else - { - MarkAssetBundleAsFailed(ref sceneLODInfo, - $"No initial scene state descriptor in the ISS for {sceneLODInfo.id}, will try to do the old LOD"); - initialSceneStateLOD.AssetBundleData!.Dispose(); - } - } - else - { - MarkAssetBundleAsFailed(ref sceneLODInfo, - $"Failed to get ISS LOD for {sceneLODInfo.id}, will try to do the old LOD"); - } + // Best-effort release: if this hit came from a prior SDK→LOD bridge handoff there's + // a slot to free; on the first-ever LOD load of a scene the cache may have the asset + // from an unrelated context with no reservation, in which case this is a no-op. + issDescriptor.TryReleaseBridgeSlot(entry.hash); + PositionAsset(initialSceneStateLOD, entry, cacheKey, asset, initialSceneStateLOD.ParentContainer.transform); + continue; } + + // Descriptor mode: fetch each asset's own bundle — must include the platform suffix to match the deployed AB filename. + string promiseHash = $"{entry.hash}{PlatformUtils.GetCurrentPlatform()}"; + + var intent = GetAssetBundleIntention.FromHash(promiseHash, + assetBundleManifestVersion: manifest, + parentEntityID: sceneDefinition.Definition.id); + + // Mirror the digest populated by PrepareGltfAssetLoadingSystem so this promise lands in the same + // AssetBundleCache slot as the SDK runtime would. Without it the (Hash, DepsDigest) key diverges + // and two parallel LoadAssetBundleSystem flows race for the same physical bundle, which Unity + // refuses with "asset bundle already loaded". The digest map is keyed by bare CID. + if (manifest != null && manifest.TryGetDepsDigest(entry.hash, out string digest)) + intent.DepsDigest = digest; + + AssetBundlePromise promise = AssetBundlePromise.Create(World, intent, PartitionComponent.TOP_PRIORITY); + + ISSAssetCreationHelper assetCreationHelper = new ISSAssetCreationHelper(initialSceneStateLOD, entry, cacheKey); + + World.Create(promise, assetCreationHelper); } } - [Query] private void ConvertFromAssetBundle(Entity entity, ISSAssetCreationHelper creationHelper, ref AssetBundlePromise assetBundleResult) { if (!instantiationFrameTimeBudget.TrySpendBudget() || !memoryBudget.TrySpendBudget()) return; - if (assetBundleResult.TryConsume(World, out StreamableLoadingResult Result)) + if (!assetBundleResult.TryConsume(World, out StreamableLoadingResult Result)) + return; + + bool stillRelevant = creationHelper.Generation == creationHelper.InitialSceneStateLOD.Generation + && creationHelper.InitialSceneStateLOD.ParentContainer != null; + + if (Result.Succeeded) { - if (Result.Succeeded) + if (stillRelevant) { - if (creationHelper.Generation == creationHelper.InitialSceneStateLOD.Generation - && creationHelper.InitialSceneStateLOD.ParentContainer != null) + if (Utils.TryCreateGltfObject(Result.Asset, creationHelper.AssetNameInBundle, out GltfContainerAsset asset)) { - if (Utils.TryCreateGltfObject(Result.Asset, creationHelper.AssetHash, out GltfContainerAsset asset)) - PositionAsset(creationHelper.InitialSceneStateLOD, creationHelper.AssetHash, asset, - creationHelper.InitialSceneStateLOD.ParentContainer.transform, Result.Asset.InitialSceneStateMetadata.Value, creationHelper.IndexToCreate); - else - { - ReportHub.LogWarning(GetReportData(), $"Failed to load {creationHelper.AssetHash} for LOD, the result may not look correct"); - creationHelper.InitialSceneStateLOD.AddFailedAsset(creationHelper.AssetHash); - } + PositionAsset(creationHelper.InitialSceneStateLOD, creationHelper.Entry, creationHelper.CacheKey, asset, + creationHelper.InitialSceneStateLOD.ParentContainer.transform); } else { - //Means that the ISS loading has been cancelled. We need to remove the reference to keep counting correctly - Result.Asset!.Dereference(); + ReportHub.LogWarning(GetReportData(), $"Failed to load {creationHelper.Entry.hash} for LOD, the result may not look correct"); + creationHelper.InitialSceneStateLOD.AddFailedAsset(creationHelper.Entry.hash); } } - World.Destroy(entity); + else + { + //Means that the ISS loading has been cancelled. We need to remove the reference to keep counting correctly + Result.Asset!.Dereference(); + } + } + else if (stillRelevant) + { + // AB promise failed (e.g. 404 / network). Count it so AllAssetsInstantiated can settle + // and UnloadLODForISS gets a chance to bridge the successful assets. + creationHelper.InitialSceneStateLOD.AddFailedAsset(creationHelper.Entry.hash); } + + World.Destroy(entity); } - private void PositionAsset(InitialSceneStateLOD initialSceneStateLOD, string assetHash, GltfContainerAsset asset, Transform parent, InitialSceneStateMetadata initialSceneStateMetadata, int indexToPosition) + private static void PositionAsset(InitialSceneStateLOD initialSceneStateLOD, ISSDescriptorAsset entry, string cacheKey, GltfContainerAsset asset, Transform parent) { asset.Root.SetActive(true); asset.Root.transform.SetParent(parent); - asset.Root.transform.localPosition = initialSceneStateMetadata.positions[indexToPosition]; - asset.Root.transform.localRotation = initialSceneStateMetadata.rotations[indexToPosition]; - asset.Root.transform.localScale = initialSceneStateMetadata.scales[indexToPosition]; + asset.Root.transform.localPosition = entry.position; + asset.Root.transform.localRotation = entry.rotation; + asset.Root.transform.localScale = entry.scale; asset.ToggleAnimationState(false); - initialSceneStateLOD.AddResolvedAsset(assetHash, asset); - } - - private static void MarkAssetBundleAsFailed(ref SceneLODInfo sceneLODInfo, string message) - { - ReportHub.Log(ReportCategory.LOD, message); - sceneLODInfo.InitialSceneStateLOD.CurrentState = InitialSceneStateLOD.State.FAILED; - //We need to re-evaluate the LOD to see if we can get the old method - sceneLODInfo.CurrentLODLevelPromise = byte.MaxValue; + // Store under the digest-aware cache key so the eventual Dereference in InitialSceneStateLOD.Clear + // matches what the SDK runtime would look up — that's what allows bridging round-trips between + // LOD and the real scene without spawning a second copy of the same asset. + initialSceneStateLOD.AddResolvedAsset(cacheKey, asset); } } public struct ISSAssetCreationHelper { - public ISSAssetCreationHelper(InitialSceneStateLOD initialSceneStateLOD, string assetHash, int indexToCreate) + public ISSAssetCreationHelper(InitialSceneStateLOD initialSceneStateLOD, ISSDescriptorAsset entry, string cacheKey) { InitialSceneStateLOD = initialSceneStateLOD; - AssetHash = assetHash; - IndexToCreate = indexToCreate; + Entry = entry; + // Bundle mode: the shared ISS bundle contains many assets keyed by hash. + // Descriptor mode: per-asset bundle has a single asset, so passing empty name to TryGetAsset returns it. + // Both shared ISS bundles and per-asset bundles are baked with assets named by their content hash. + AssetNameInBundle = entry.hash; + CacheKey = cacheKey; Generation = initialSceneStateLOD.Generation; } public InitialSceneStateLOD InitialSceneStateLOD { get; } - public string AssetHash { get; } - public int IndexToCreate { get; } + public ISSDescriptorAsset Entry { get; } + public string AssetNameInBundle { get; } + public string CacheKey { get; } public int Generation { get; } } } diff --git a/Explorer/Assets/DCL/LOD/Systems/UnloadSceneLODSystem.cs b/Explorer/Assets/DCL/LOD/Systems/UnloadSceneLODSystem.cs index 04ac823467a..244ed91a7f6 100644 --- a/Explorer/Assets/DCL/LOD/Systems/UnloadSceneLODSystem.cs +++ b/Explorer/Assets/DCL/LOD/Systems/UnloadSceneLODSystem.cs @@ -20,6 +20,8 @@ using ECS.StreamableLoading.Common.Components; using SceneRunner.Scene; using UnityEngine; +using AssetBundlePromise = ECS.StreamableLoading.Common.AssetPromise; + namespace ECS.SceneLifeCycle.Systems { @@ -50,13 +52,14 @@ public override void Initialize() protected override void Update(float t) { - UnloadSceneLODForISSQuery(World); + UnloadLODForISSQuery(World); UnloadLODQuery(World); UnloadLODWhenSceneReadyQuery(World); } public void FinalizeComponents(in Query query) { + AbortISSHelperEntitiesQuery(World); AbortSucceededLODPromisesQuery(World); DestroySceneLODQuery(World); } @@ -64,7 +67,7 @@ public void FinalizeComponents(in Query query) [Query] [All(typeof(AssetPromise))] - private void UnloadSceneLODForISS(in Entity entity, ref SceneLODInfo sceneLODInfo, ref PartitionComponent partitionComponent, + private void UnloadLODForISS(in Entity entity, ref SceneLODInfo sceneLODInfo, ref PartitionComponent partitionComponent, ref SceneDefinitionComponent sceneDefinitionComponent, ref SceneLoadingState sceneLoadingState) { if (sceneLoadingState.VisualSceneState == VisualSceneState.SHOWING_SCENE) @@ -120,5 +123,13 @@ private void DestroySceneLOD(ref SceneDefinitionComponent sceneDefinitionCompone sceneLODInfo.DisposeSceneLODAndReleaseToCache(scenesCache, sceneDefinitionComponent.Parcels, lodCache, World, defaultFOV, defaultLodBias, realmPartitionSettingsAsset.MaxLoadingDistanceInParcels, sceneDefinitionComponent.Parcels.Count); } + + [Query] + [All(typeof(ISSAssetCreationHelper))] + private void AbortISSHelperEntities(in Entity entity, ref AssetBundlePromise assetBundleResult) + { + assetBundleResult.ForgetLoading(World); + World.Destroy(entity); + } } } diff --git a/Explorer/Assets/DCL/LOD/Systems/UpdateSceneLODInfoSystem.cs b/Explorer/Assets/DCL/LOD/Systems/UpdateSceneLODInfoSystem.cs index 3836356bcc1..da6363c1ac9 100644 --- a/Explorer/Assets/DCL/LOD/Systems/UpdateSceneLODInfoSystem.cs +++ b/Explorer/Assets/DCL/LOD/Systems/UpdateSceneLODInfoSystem.cs @@ -1,4 +1,5 @@ -using Arch.Core; +using Arch.Core; +using DCL.SceneRunner.Scene; using Arch.System; using Arch.SystemGroups; using AssetManagement; @@ -14,6 +15,7 @@ using ECS.SceneLifeCycle.IncreasingRadius; using ECS.SceneLifeCycle.SceneDefinition; using ECS.StreamableLoading.AssetBundles; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; using ECS.StreamableLoading.Common; using SceneRunner.Scene; using System.Collections.Generic; @@ -45,7 +47,7 @@ protected override void Update(float t) [Query] [None(typeof(DeleteEntityIntention), typeof(PortableExperienceComponent), typeof(AssetPromise), typeof(ISceneFacade))] - private void UpdateLODLevel(ref SceneLODInfo sceneLODInfo, ref PartitionComponent partitionComponent, SceneDefinitionComponent sceneDefinitionComponent, ref SceneLoadingState sceneState) + private void UpdateLODLevel(ref SceneLODInfo sceneLODInfo, ref PartitionComponent partitionComponent, SceneDefinitionComponent sceneDefinitionComponent, ISSDescriptor issDescriptor, ref SceneLoadingState sceneState) { if (!partitionComponent.IsBehind) // Only want to load scene in our direction of travel && not quality reducted { @@ -62,28 +64,26 @@ private void UpdateLODLevel(ref SceneLODInfo sceneLODInfo, ref PartitionComponen lodForAcquisition = GetLODLevelForPartition(ref partitionComponent, ref sceneLODInfo); } if (!sceneLODInfo.HasLOD(lodForAcquisition)) - StartLODPromise(ref sceneLODInfo, ref partitionComponent, sceneDefinitionComponent, lodForAcquisition); + StartLODPromise(ref sceneLODInfo, ref partitionComponent, sceneDefinitionComponent, issDescriptor, lodForAcquisition); } } - private void StartLODPromise(ref SceneLODInfo sceneLODInfo, ref PartitionComponent partitionComponent, SceneDefinitionComponent sceneDefinitionComponent, byte level) + private void StartLODPromise(ref SceneLODInfo sceneLODInfo, ref PartitionComponent partitionComponent, SceneDefinitionComponent sceneDefinitionComponent, ISSDescriptor issDescriptor, byte level) { sceneLODInfo.ForgetAllLoadings(World); - if (level == 0 && sceneDefinitionComponent.Definition.SupportInitialSceneState() - && sceneLODInfo.InitialSceneStateLOD.CurrentState != InitialSceneStateLOD.State.FAILED) + if (level == 0 && sceneLODInfo.InitialSceneStateLOD.CurrentState != InitialSceneStateLOD.State.FAILED) { - var initialSceneState = GetAssetBundleIntention.FromHash( - GetAssetBundleIntention.BuildInitialSceneStateURL(sceneDefinitionComponent.Definition.id), - typeof(GameObject), - permittedSources: AssetSource.WEB, - assetBundleManifestVersion: sceneDefinitionComponent.Definition.assetBundleManifestVersion, - parentEntityID: sceneDefinitionComponent.Definition.id - ); - sceneLODInfo.InitialSceneStateLOD.AssetBundlePromise = Promise.Create(World, initialSceneState, partitionComponent); - sceneLODInfo.InitialSceneStateLOD.CurrentState = InitialSceneStateLOD.State.PROCESSING; - sceneLODInfo.CurrentLODLevelPromise = level; - return; + // ResolveSceneStateByIncreasingRadiusSystem gates SHOWING_LOD/SHOWING_SCENE transitions on + // descriptor resolution, so by the time we reach this point the descriptor is guaranteed to + // be either None (no ISS for this scene) or a resolved Bundle/Descriptor. + if (issDescriptor.SupportsDescriptor()) + { + sceneLODInfo.InitialSceneStateLOD.CurrentState = InitialSceneStateLOD.State.PROCESSING; + sceneLODInfo.CurrentLODLevelPromise = level; + return; + } + // descriptor in None state — no ISS for this scene; fall through to legacy LOD. } string platformLODKey = $"{sceneDefinitionComponent.Definition.id.ToLower()}_{level.ToString()}{PlatformUtils.GetCurrentPlatform()}"; diff --git a/Explorer/Assets/DCL/LOD/Tests/EditMode/UpdateSceneLODInfoSystemShould.cs b/Explorer/Assets/DCL/LOD/Tests/EditMode/UpdateSceneLODInfoSystemShould.cs index bece728839b..80f1d760d18 100644 --- a/Explorer/Assets/DCL/LOD/Tests/EditMode/UpdateSceneLODInfoSystemShould.cs +++ b/Explorer/Assets/DCL/LOD/Tests/EditMode/UpdateSceneLODInfoSystemShould.cs @@ -6,7 +6,9 @@ using ECS.SceneLifeCycle; using ECS.SceneLifeCycle.IncreasingRadius; using ECS.SceneLifeCycle.Reporting; +using DCL.SceneRunner.Scene; using ECS.SceneLifeCycle.SceneDefinition; +using ECS.StreamableLoading.AssetBundles.InitialSceneState; using ECS.TestSuite; using ECS.Unity.GLTFContainer.Asset.Cache; using NSubstitute; @@ -74,7 +76,7 @@ public void ResolveLODLevelWithUnsupportedISS(byte bucket, int expectedLODLevel) //Arrange partitionComponent.IsDirty = true; partitionComponent.Bucket = bucket; - Entity entity = world.Create(sceneLODInfo, partitionComponent, sceneDefinitionComponent, SceneLoadingState.CreateBuiltScene()); + Entity entity = world.Create(sceneLODInfo, partitionComponent, sceneDefinitionComponent, SceneLoadingState.CreateBuiltScene(), ISSDescriptor.NONE); //Act system.Update(0); diff --git a/Explorer/Assets/DCL/LOD/Tests/PlayMode/InstantiateSceneLODInfoSystemShould.cs b/Explorer/Assets/DCL/LOD/Tests/PlayMode/InstantiateSceneLODInfoSystemShould.cs index 2d06f49f69a..864c81bcc10 100644 --- a/Explorer/Assets/DCL/LOD/Tests/PlayMode/InstantiateSceneLODInfoSystemShould.cs +++ b/Explorer/Assets/DCL/LOD/Tests/PlayMode/InstantiateSceneLODInfoSystemShould.cs @@ -151,7 +151,7 @@ private Promise GenerateFailedPromise() GetAssetBundleIntention.FromHash("Cube", typeof(GameObject)), new PartitionComponent()); - var fakeAssetBundleData = new AssetBundleData(null,null, new []{GameObject.CreatePrimitive(PrimitiveType.Cube)}, + var fakeAssetBundleData = new AssetBundleData(null, new []{GameObject.CreatePrimitive(PrimitiveType.Cube)}, typeof(GameObject), new AssetBundleData[] { }); world.Add(promise.Entity, diff --git a/Explorer/Assets/DCL/NetworkDefinitions/AssetBundleManifestVersion.cs b/Explorer/Assets/DCL/NetworkDefinitions/AssetBundleManifestVersion.cs index 782a3aa76c3..c346f5ab89d 100644 --- a/Explorer/Assets/DCL/NetworkDefinitions/AssetBundleManifestVersion.cs +++ b/Explorer/Assets/DCL/NetworkDefinitions/AssetBundleManifestVersion.cs @@ -12,12 +12,13 @@ public class AssetBundleManifestVersion //This was done to solve cache issues private const int ASSET_BUNDLE_VERSION_REQUIRES_HASH = 25; - //v2000 marks that it has ISS enabled - private const int ASSET_BUNDLE_VERSION_SUPPORTS_ISS = 2000; - //From v49 the manifest exposes a per-file deps digest we can key the cache by private const int ASSET_BUNDLE_VERSION_SUPPORTS_DEPS_DIGEST = 49; + //ISS (Initial Scene State) descriptors are only baked starting from v49 — older + //manifests can't have an ISS, so the descriptor lookup is short-circuited. + private const int ASSET_BUNDLE_VERSION_SUPPORTS_ISS = 49; + public static readonly int AB_MIN_SUPPORTED_VERSION_WINDOWS = 15; public static readonly int AB_MIN_SUPPORTED_VERSION_MAC = 16; @@ -30,10 +31,9 @@ public class AssetBundleManifestVersion public static bool DepsDigestKeyingEnabled; private bool? HasHashInPathValue; - private bool? SupportsISS; - private bool? SupportsDepsDigestsValue; - + private bool? SupportsDepsDigestsValue; + private bool? SupportsISSValue; public bool assetBundleManifestRequestFailed; public bool IsLSDAsset; public AssetBundleManifestVersionPerPlatform? assets; @@ -47,12 +47,6 @@ public bool HasHashInPath() return HasHashInPathValue.Value; } - public bool SupportsInitialSceneState() - { - SupportsISS ??= TryParseVersionNumber(GetAssetBundleManifestVersion(), out int version) && version >= ASSET_BUNDLE_VERSION_SUPPORTS_ISS; - return SupportsISS.Value; - } - /// /// True when the manifest's version is v49 or newer — i.e. when the per-file deps-digest scheme is /// in use for cache keying. This is purely a version check; an individual asset may still have an @@ -65,6 +59,18 @@ public bool SupportsDepsDigests() return SupportsDepsDigestsValue.Value; } + /// + /// True when the manifest's version is new enough to potentially have an ISS (Initial Scene State) + /// descriptor baked for the scene. Older manifests pre-date the feature and can be short-circuited + /// without touching the network. + /// + public bool SupportsISS() + { + if (assetBundleManifestRequestFailed) return false; + SupportsISSValue ??= TryParseVersionNumber(GetAssetBundleManifestVersion(), out int version) && version >= ASSET_BUNDLE_VERSION_SUPPORTS_ISS; + return SupportsISSValue.Value; + } + //Try parse is required to avoid throwing exceptions when the version is not in the expected format, which can happen for LODs in example private static bool TryParseVersionNumber(string? version, out int parsed) { diff --git a/Explorer/Assets/DCL/NetworkDefinitions/DCL.Network.asmdef b/Explorer/Assets/DCL/NetworkDefinitions/DCL.Network.asmdef index c2763384a08..2efc84c57e0 100644 --- a/Explorer/Assets/DCL/NetworkDefinitions/DCL.Network.asmdef +++ b/Explorer/Assets/DCL/NetworkDefinitions/DCL.Network.asmdef @@ -13,7 +13,6 @@ "GUID:9c1932d9907bf426d845767a97c591ac", "GUID:1087662aaf1c5462baa91fb9484296fd", "GUID:e0cd26848372d4e5c891c569017e11f1", - "GUID:9887bf5401cdc9140916d3edbea10b69", "GUID:8baf705856414dad9a73b3f382f1bc8b", "GUID:479525a7f2ececd499dc130b607f895f", "GUID:d8b63aba1907145bea998dd612889d6b", diff --git a/Explorer/Assets/DCL/NetworkDefinitions/SceneEntityDefinition.cs b/Explorer/Assets/DCL/NetworkDefinitions/SceneEntityDefinition.cs index 3c2c2b4ef56..d610af261f9 100644 --- a/Explorer/Assets/DCL/NetworkDefinitions/SceneEntityDefinition.cs +++ b/Explorer/Assets/DCL/NetworkDefinitions/SceneEntityDefinition.cs @@ -19,14 +19,6 @@ public SceneEntityDefinition(string id, SceneMetadata metadata, AssetBundleManif public string GetLogSceneName() => logSceneName ??= $"{metadata.scene?.DecodedBase} - {id}"; - public bool SupportInitialSceneState() - { - if (assetBundleManifestVersion != null) - return assetBundleManifestVersion.SupportsInitialSceneState(); - - return false; - } - public bool Contains(int x, int y) { if(SceneLookup.HasValue) diff --git a/Explorer/Assets/DCL/PluginSystem/World/AssetBundlesPlugin.cs b/Explorer/Assets/DCL/PluginSystem/World/AssetBundlesPlugin.cs index e58d34d9005..a46b6bddb94 100644 --- a/Explorer/Assets/DCL/PluginSystem/World/AssetBundlesPlugin.cs +++ b/Explorer/Assets/DCL/PluginSystem/World/AssetBundlesPlugin.cs @@ -9,7 +9,6 @@ using ECS.LifeCycle; using ECS.StreamableLoading; using ECS.StreamableLoading.AssetBundles; -using ECS.StreamableLoading.AssetBundles.EarlyAsset; using ECS.StreamableLoading.Cache; using ECS.StreamableLoading.Cache.Disk; using ECS.StreamableLoading.Common.Components; @@ -77,8 +76,6 @@ public void InjectToWorld(ref ArchSystemsWorldBuilder builder, // TODO create a runtime ref-counting cache LoadGlobalAssetBundleSystem.InjectToWorld(ref builder, assetBundleCache, webRequestController, assetBundleLoadingMutex, buffersPool, partialsDiskCache); - - EarlyAssetBundleRequestSystem.InjectToWorld(ref builder); } UniTask IDCLPlugin.InitializeAsync(NoExposedPluginSettings settings, CancellationToken ct) => diff --git a/Explorer/Assets/DCL/PluginSystem/World/GltfContainerPlugin.cs b/Explorer/Assets/DCL/PluginSystem/World/GltfContainerPlugin.cs index b22947c7cb2..6f3b8f66cf1 100644 --- a/Explorer/Assets/DCL/PluginSystem/World/GltfContainerPlugin.cs +++ b/Explorer/Assets/DCL/PluginSystem/World/GltfContainerPlugin.cs @@ -1,4 +1,5 @@ using Arch.SystemGroups; +using DCL.SceneRunner.Scene; using Cysharp.Threading.Tasks; using DCL.Ipfs; using DCL.Optimization.Pools; @@ -129,7 +130,7 @@ public void InjectToWorld(ref ArchSystemsWorldBuilder builder, ResetGltfContainerSystem.InjectToWorld(ref builder, assetsCache, sharedDependencies.EntityCollidersSceneCache, buffer, sharedDependencies.EcsToCRDTWriter); WriteGltfContainerLoadingStateSystem.InjectToWorld(ref builder, sharedDependencies.EcsToCRDTWriter, buffer); GltfContainerVisibilitySystem.InjectToWorld(ref builder, buffer); - finalizeWorldSystems.Add(CleanUpGltfContainerSystem.InjectToWorld(ref builder, assetsCache, sharedDependencies.EntityCollidersSceneCache, sharedDependencies.ScenePartition)); + finalizeWorldSystems.Add(CleanUpGltfContainerSystem.InjectToWorld(ref builder, assetsCache, sharedDependencies.EntityCollidersSceneCache, sharedDependencies.ScenePartition, sharedDependencies.SceneData.ISSDescriptor)); GatherGltfAssetsSystem.InjectToWorld(ref builder, sceneReadinessReportQueue, sharedDependencies.SceneData, buffer, sharedDependencies.SceneStateProvider, globalDeps.MemoryBudget, loadingStatus, diff --git a/Explorer/Assets/DCL/PluginSystem/World/TransformsPlugin.cs b/Explorer/Assets/DCL/PluginSystem/World/TransformsPlugin.cs index 3dc12f5c0ff..4f44f9655e4 100644 --- a/Explorer/Assets/DCL/PluginSystem/World/TransformsPlugin.cs +++ b/Explorer/Assets/DCL/PluginSystem/World/TransformsPlugin.cs @@ -2,7 +2,7 @@ using DCL.CharacterCamera; using DCL.Optimization.Pools; using DCL.PluginSystem.World.Dependencies; -using Decentraland.Common; +using DCL.SceneRunner.Scene; using DG.Tweening; using ECS.ComponentsPooling.Systems; using ECS.LifeCycle; @@ -64,9 +64,13 @@ private void CreateReservedTransforms(ArchSystemsWorldBuilder b { //The scene container, which is only modified by the client, starts in a position that cannot be seen by the player. Once it finished loading //in GatherGLTFAssetSystem.cs, it will be moved to the correct position. - //If the static scene is supported, the transition between LOD and scene is seamless. - var sceneRootContainerTransform = GetNewTransform(position: sharedDependencies.SceneData.SceneEntityDefinition.SupportInitialSceneState() ? - sharedDependencies.SceneData.Geometry.BaseParcelPosition : MordorConstants.SCENE_MORDOR_POSITION); + //If any form of ISS is in play (bundle or descriptor), start at the real parcel position so the LOD->scene transition is seamless. + // SceneData carries the resolved descriptor (null when there's no ISS); base-parcel start avoids + // the LOD→scene Mordor flicker when ISS pre-populated the world. + bool hasISS = sharedDependencies.SceneData.ISSDescriptor != null; + var sceneRootContainerTransform = GetNewTransform(position: hasISS + ? sharedDependencies.SceneData.Geometry.BaseParcelPosition + : MordorConstants.SCENE_MORDOR_POSITION); sceneRootContainerTransform.name = $"{sharedDependencies.SceneData.SceneShortInfo.BaseParcel}_{sharedDependencies.SceneData.SceneShortInfo.Name}_Container"; builder.World.Add(persistentEntities.SceneContainer, new TransformComponent(sceneRootContainerTransform)); diff --git a/Explorer/Assets/DCL/ResourcesUnloading/Tests/CacheCleanerIntegrationTests.cs b/Explorer/Assets/DCL/ResourcesUnloading/Tests/CacheCleanerIntegrationTests.cs index f13c7b6591b..f8f6d73dc0f 100644 --- a/Explorer/Assets/DCL/ResourcesUnloading/Tests/CacheCleanerIntegrationTests.cs +++ b/Explorer/Assets/DCL/ResourcesUnloading/Tests/CacheCleanerIntegrationTests.cs @@ -164,7 +164,7 @@ public void CacheCleaningAllocations(int cachedElementsAmount) public void DisposingShouldProperlyDereferenceDependencyChain() { // Arrange - var assetBundleData = new AssetBundleData(null, null, Array.Empty(), typeof(GameObject), null); + var assetBundleData = new AssetBundleData(null, Array.Empty(), typeof(GameObject), null); var gltfAsset = GltfContainerAsset.Create(new GameObject(), assetBundleData); assetBundleData.AddReference(); @@ -217,7 +217,7 @@ private void FillCachesWithElements(string hashID) audioClipsCache.AddReference(in audioClipIntention, audioClip); audioClip.Dereference(); - var assetBundleData = new AssetBundleData(null, null, new []{new GameObject()}, typeof(GameObject), Array.Empty()); + var assetBundleData = new AssetBundleData(null, new []{new GameObject()}, typeof(GameObject), Array.Empty()); assetBundleCache.Add(new GetAssetBundleIntention { Hash = hashID }, assetBundleData); var gltfContainerAsset = GltfContainerAsset.Create(new GameObject(), assetBundleData);