-
Notifications
You must be signed in to change notification settings - Fork 17
fix: evict only the changed GLTF on LSD hot reload instead of draining every cache #9667
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
8251beb
8ecafde
4f6a10f
f1233ef
7d6e06b
80156d7
79cc500
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||
| using Arch.Core; | ||||||
| using Cysharp.Threading.Tasks; | ||||||
| using DCL.Character.Components; | ||||||
| using DCL.Ipfs; | ||||||
| using DCL.ResourcesUnloading; | ||||||
| using ECS.LifeCycle.Components; | ||||||
| using ECS.SceneLifeCycle.Components; | ||||||
|
|
@@ -14,6 +15,22 @@ | |||||
|
|
||||||
| namespace ECS.SceneLifeCycle | ||||||
| { | ||||||
| /// <summary> | ||||||
| /// The single GLTF model a local-development hot reload reported as changed, carried from the | ||||||
| /// dev server's websocket message so the reload can evict just that asset instead of the whole cache. | ||||||
| /// </summary> | ||||||
| public readonly struct ChangedGltfModel | ||||||
| { | ||||||
| public readonly string Src; | ||||||
| public readonly string Hash; | ||||||
|
|
||||||
| public ChangedGltfModel(string src, string hash) | ||||||
| { | ||||||
| Src = src; | ||||||
| Hash = hash; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| public class ECSReloadScene | ||||||
| { | ||||||
| private readonly IScenesCache scenesCache; | ||||||
|
|
@@ -44,19 +61,19 @@ public ECSReloadScene(IScenesCache scenesCache, | |||||
| var foundEntity = FindSceneEntity(sceneInCache); | ||||||
| if (foundEntity == Entity.Null) return null; | ||||||
|
|
||||||
| await DisposeAndRestartAsync(foundEntity, sceneInCache, ct); | ||||||
| await DisposeAndRestartAsync(foundEntity, sceneInCache, null, ct); | ||||||
|
|
||||||
| return sceneInCache; | ||||||
| } | ||||||
|
|
||||||
| public async UniTask<ISceneFacade?> TryReloadSceneAsync(CancellationToken ct, string sceneId) | ||||||
| public async UniTask<ISceneFacade?> TryReloadSceneAsync(CancellationToken ct, string sceneId, ChangedGltfModel? changedModel = null) | ||||||
| { | ||||||
| if (!scenesCache.TryGetBySceneId(sceneId, out var sceneInCache)) return null; | ||||||
|
|
||||||
| var foundEntity = FindSceneEntity(sceneInCache!); | ||||||
| if (foundEntity == Entity.Null) return null; | ||||||
|
|
||||||
| await DisposeAndRestartAsync(foundEntity, sceneInCache!, ct); | ||||||
| await DisposeAndRestartAsync(foundEntity, sceneInCache!, changedModel, ct); | ||||||
|
|
||||||
| return sceneInCache; | ||||||
| } | ||||||
|
|
@@ -74,10 +91,15 @@ private Entity FindSceneEntity(ISceneFacade targetScene) | |||||
| return sceneEntity; | ||||||
| } | ||||||
|
|
||||||
| private async UniTask DisposeAndRestartAsync(Entity entity, ISceneFacade currentScene, CancellationToken ct) | ||||||
| private async UniTask DisposeAndRestartAsync(Entity entity, ISceneFacade currentScene, ChangedGltfModel? changedModel, CancellationToken ct) | ||||||
| { | ||||||
| ct.ThrowIfCancellationRequested(); | ||||||
|
|
||||||
| // Captured before the teardown below strips the scene entity's components. | ||||||
| SceneEntityDefinition? definition = localSceneDevelopment | ||||||
| ? world.Get<SceneDefinitionComponent>(entity).Definition | ||||||
| : null; | ||||||
|
|
||||||
| //There is a lingering promise we need to remove, and add the DeleteEntityIntention to make the standard unload flow. | ||||||
| world.Add<DeleteEntityIntention>(entity); | ||||||
|
|
||||||
|
|
@@ -96,11 +118,21 @@ private async UniTask DisposeAndRestartAsync(Entity entity, ISceneFacade current | |||||
| world.Query(in new QueryDescription().WithAll<RealmComponent>(), | ||||||
| (ref StaticScenePointers staticScenePointers) => { staticScenePointers.Promise = null; }); | ||||||
|
|
||||||
| // Force-drain dereferenced caches on LSD reload. The local dev server derives hashes | ||||||
| // from the file path, not content, so an updated model keeps the same hash and cache | ||||||
| // hits would return stale assets. Draining guarantees fresh loads. | ||||||
| cacheCleaner.UnloadCache(budgeted: false); | ||||||
| Resources.UnloadUnusedAssets(); | ||||||
| if (changedModel is { } model && IsRawGltfModel(definition, model.Hash)) | ||||||
| { | ||||||
| // The dev server named the exact model that changed. In raw-GLTF development its | ||||||
| // cache key is the bare content hash, so evict just that asset and let every other | ||||||
| // cache stay warm across the reload. | ||||||
| cacheCleaner.EvictGltfModel(model.Hash, model.Src); | ||||||
| } | ||||||
| else | ||||||
| { | ||||||
| // Force-drain dereferenced caches on LSD reload. The local dev server derives hashes | ||||||
| // from the file path, not content, so an updated model keeps the same hash and cache | ||||||
| // hits would return stale assets. Draining guarantees fresh loads. | ||||||
| cacheCleaner.UnloadCache(budgeted: false); | ||||||
| Resources.UnloadUnusedAssets(); | ||||||
| } | ||||||
|
|
||||||
| await WaitUntilNewSceneIsFullyLoadedAsync(); | ||||||
| } | ||||||
|
|
@@ -134,5 +166,19 @@ await UniTask.WaitUntil(() => | |||||
| }, cancellationToken: ct); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// True when the hash addresses a raw GLTF, i.e. no asset-bundle manifest maps it. The GLTF | ||||||
| /// container cache is keyed by <see cref="AssetBundleManifestVersion.ComposeCacheKey" />, | ||||||
| /// which returns the bare hash only in that case; under <c>--local-ab</c> the key differs and | ||||||
| /// the model lives in the asset-bundle caches instead, so scoped eviction must not be used. | ||||||
| /// </summary> | ||||||
| internal static bool IsRawGltfModel(SceneEntityDefinition? definition, string hash) | ||||||
|
dalkia marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Nullability annotation (repeat from previous review) —
Suggested change
|
||||||
| { | ||||||
| if (definition == null || string.IsNullOrEmpty(hash)) | ||||||
| return false; | ||||||
|
|
||||||
| return definition.AssetBundleManifestVersionOrFailed.ComposeCacheKey(hash) == hash; | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| using DCL.Ipfs; | ||
| using ECS.SceneLifeCycle; | ||
| using NUnit.Framework; | ||
| using System; | ||
|
|
||
| namespace DCL.SceneLifeCycle.Tests | ||
| { | ||
| public class ECSReloadSceneShould | ||
| { | ||
| [Test] | ||
| public void TreatDefinitionWithoutManifestAsRawGltf() | ||
| { | ||
| //Arrange: no asset-bundle manifest -> the container cache is keyed by the bare hash | ||
| SceneEntityDefinition definition = CreateDefinition(); | ||
|
|
||
| //Act & Assert | ||
| Assert.That(ECSReloadScene.IsRawGltfModel(definition, "b64-somehash"), Is.True); | ||
| } | ||
|
|
||
| [Test] | ||
| public void NotTreatMissingDefinitionOrEmptyHashAsRawGltf() | ||
| { | ||
| SceneEntityDefinition definition = CreateDefinition(); | ||
|
|
||
| Assert.That(ECSReloadScene.IsRawGltfModel(null, "b64-somehash"), Is.False); | ||
| Assert.That(ECSReloadScene.IsRawGltfModel(definition, string.Empty), Is.False); | ||
| Assert.That(ECSReloadScene.IsRawGltfModel(definition, null!), Is.False); | ||
| } | ||
|
|
||
| private static SceneEntityDefinition CreateDefinition() => | ||
| new ("test-scene", new SceneMetadata()) | ||
| { | ||
| pointers = new[] { "0,0" }, | ||
| content = Array.Empty<ContentDefinition>(), | ||
| }; | ||
| } | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.