Load provider asset bundles on demand
This commit is contained in:
parent
56dbe63640
commit
3da246aba8
3 changed files with 148 additions and 25 deletions
|
|
@ -52,6 +52,14 @@ namespace GameCult.Eve.UnityScene
|
|||
private readonly Dictionary<string, IReadOnlyDictionary<string, string>> _nativeAssetMetadata =
|
||||
new Dictionary<string, IReadOnlyDictionary<string, string>>(StringComparer.Ordinal);
|
||||
private readonly List<AssetBundle> _assetBundles = new List<AssetBundle>();
|
||||
private readonly Dictionary<string, SelectedAsset> _assetSelections =
|
||||
new Dictionary<string, SelectedAsset>(StringComparer.Ordinal);
|
||||
private readonly Dictionary<string, List<SelectedAsset>> _bundleSelections =
|
||||
new Dictionary<string, List<SelectedAsset>>(StringComparer.Ordinal);
|
||||
private readonly Dictionary<string, EveAssetVariant> _bundleVariants =
|
||||
new Dictionary<string, EveAssetVariant>(StringComparer.Ordinal);
|
||||
private readonly HashSet<string> _loadedBundleUris = new HashSet<string>(StringComparer.Ordinal);
|
||||
private readonly HashSet<string> _loadingBundleUris = new HashSet<string>(StringComparer.Ordinal);
|
||||
private readonly List<ICultMeshBodyReadLease> _assetBodyLeases = new List<ICultMeshBodyReadLease>();
|
||||
private readonly Dictionary<string, int> _renderChannelLayers = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
private readonly ConcurrentQueue<object> _liveDocuments = new ConcurrentQueue<object>();
|
||||
|
|
@ -382,6 +390,11 @@ namespace GameCult.Eve.UnityScene
|
|||
_prefabs.Clear();
|
||||
_nativeAssets.Clear();
|
||||
_nativeAssetMetadata.Clear();
|
||||
_assetSelections.Clear();
|
||||
_bundleSelections.Clear();
|
||||
_bundleVariants.Clear();
|
||||
_loadedBundleUris.Clear();
|
||||
_loadingBundleUris.Clear();
|
||||
_mappedEntityFrameCursor?.Dispose();
|
||||
_mappedEntityFrameCursor = null;
|
||||
_mappedEntityFrameContract = null;
|
||||
|
|
@ -412,6 +425,7 @@ namespace GameCult.Eve.UnityScene
|
|||
public GameObject? ResolvePrefab(EveUnityPlayableWorldAssetBinding asset)
|
||||
{
|
||||
if (asset == null) throw new ArgumentNullException(nameof(asset));
|
||||
EnsureAssetLoaded(asset.AssetRef);
|
||||
return _prefabs.TryGetValue(asset.AssetRef, out var prefab) ? prefab : null;
|
||||
}
|
||||
|
||||
|
|
@ -419,6 +433,7 @@ namespace GameCult.Eve.UnityScene
|
|||
{
|
||||
if (asset == null) throw new ArgumentNullException(nameof(asset));
|
||||
if (assetType == null) throw new ArgumentNullException(nameof(assetType));
|
||||
EnsureAssetLoaded(asset.AssetRef);
|
||||
return _nativeAssets.TryGetValue(asset.AssetRef, out var value) && assetType.IsInstanceOfType(value)
|
||||
? value : null;
|
||||
}
|
||||
|
|
@ -1029,15 +1044,18 @@ namespace GameCult.Eve.UnityScene
|
|||
_prefabs.Clear();
|
||||
_nativeAssets.Clear();
|
||||
_nativeAssetMetadata.Clear();
|
||||
_assetSelections.Clear();
|
||||
_bundleSelections.Clear();
|
||||
_bundleVariants.Clear();
|
||||
_loadedBundleUris.Clear();
|
||||
_loadingBundleUris.Clear();
|
||||
_renderChannelLayers.Clear();
|
||||
var selected = catalog.Assets
|
||||
.Select(asset => new
|
||||
{
|
||||
Asset = asset,
|
||||
Variant = asset.Variants.FirstOrDefault(variant =>
|
||||
.Select(asset => new SelectedAsset(
|
||||
asset,
|
||||
asset.Variants.FirstOrDefault(variant =>
|
||||
string.Equals(variant.RuntimeId, "unity-scene", StringComparison.Ordinal) &&
|
||||
string.Equals(variant.Platform, CurrentBundlePlatform(), StringComparison.Ordinal))
|
||||
})
|
||||
string.Equals(variant.Platform, CurrentBundlePlatform(), StringComparison.Ordinal))))
|
||||
.Where(selection => selection.Variant != null)
|
||||
.ToArray();
|
||||
if (selected.Length == 0)
|
||||
|
|
@ -1051,53 +1069,112 @@ namespace GameCult.Eve.UnityScene
|
|||
$"Advertised variants: {string.Join(", ", advertisedVariants)}");
|
||||
}
|
||||
ReadCameraPolicies(selected.Select(selection => selection.Variant!));
|
||||
foreach (var group in selected.GroupBy(selection => selection.Variant!.Uri, StringComparer.Ordinal))
|
||||
foreach (var selection in selected)
|
||||
{
|
||||
var variant = selection.Variant!;
|
||||
_assetSelections[selection.Asset.AssetRef] = selection;
|
||||
_bundleVariants[variant.Uri] = variant;
|
||||
if (!_bundleSelections.TryGetValue(variant.Uri, out var group))
|
||||
{
|
||||
group = new List<SelectedAsset>();
|
||||
_bundleSelections[variant.Uri] = group;
|
||||
}
|
||||
group.Add(selection);
|
||||
var metadata = MergeAssetMetadata(selection.Asset.Metadata, variant.Metadata);
|
||||
_nativeAssetMetadata[selection.Asset.AssetRef] = metadata;
|
||||
if (selection.Asset.Metadata.TryGetValue("presentationRole", out var role) &&
|
||||
!string.IsNullOrWhiteSpace(role))
|
||||
{
|
||||
_assetSelections[role] = selection;
|
||||
_nativeAssetMetadata[role] = metadata;
|
||||
}
|
||||
}
|
||||
|
||||
CurrentAssetCatalogVersion = catalog.Version;
|
||||
}
|
||||
|
||||
private void EnsureAssetLoaded(string assetRef)
|
||||
{
|
||||
if (_nativeAssets.ContainsKey(assetRef) || !_assetSelections.TryGetValue(assetRef, out var selection))
|
||||
return;
|
||||
EnsureBundleLoaded(selection.Variant!.Uri);
|
||||
}
|
||||
|
||||
private void EnsureBundleLoaded(string uri)
|
||||
{
|
||||
if (_loadedBundleUris.Contains(uri)) return;
|
||||
if (!_bundleVariants.TryGetValue(uri, out var variant))
|
||||
throw new InvalidOperationException($"Provider asset bundle '{uri}' is not present in the selected runtime catalog.");
|
||||
if (!_loadingBundleUris.Add(uri))
|
||||
throw new InvalidOperationException($"Provider asset bundle dependency cycle includes '{uri}'.");
|
||||
try
|
||||
{
|
||||
if (variant.Metadata.TryGetValue("unity.bundleDependencyUris", out var dependencies))
|
||||
foreach (var dependency in dependencies.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
EnsureBundleLoaded(dependency);
|
||||
|
||||
var groupElapsed = Stopwatch.StartNew();
|
||||
var variant = group.First().Variant!;
|
||||
var manifest = _snapshot!.FetchDocumentsAsync<CultMeshCdnArtifactManifest>(
|
||||
recordKeys: new[] { variant.Uri },
|
||||
var descriptor = _snapshot!.FetchDocumentsAsync<CultMeshCdnArtifactManifest>(
|
||||
recordKeys: new[] { uri },
|
||||
schemaIds: new[] { CultMeshCdnSchemaVersions.ArtifactManifest })
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
var descriptor = manifest.FirstOrDefault();
|
||||
.GetResult()
|
||||
.FirstOrDefault();
|
||||
if (descriptor == null)
|
||||
throw new InvalidOperationException($"Provider asset bundle manifest '{variant.Uri}' was not available.");
|
||||
throw new InvalidOperationException($"Provider asset bundle manifest '{uri}' was not available.");
|
||||
TraceStartup("asset-manifest", groupElapsed);
|
||||
var bundle = LoadVerifiedBundle(descriptor, variant);
|
||||
TraceStartup("asset-transfer-and-bundle-load", groupElapsed);
|
||||
if (bundle == null)
|
||||
throw new InvalidOperationException($"Unity could not load provider asset bundle '{variant.Uri}'.");
|
||||
throw new InvalidOperationException($"Unity could not load provider asset bundle '{uri}'.");
|
||||
_assetBundles.Add(bundle);
|
||||
var bundleAssetNames = bundle.GetAllAssetNames();
|
||||
foreach (var selection in group)
|
||||
foreach (var selection in _bundleSelections[uri])
|
||||
{
|
||||
var bundleAssetName = ResolveBundleAssetName(bundleAssetNames, selection.Variant!.AssetKey);
|
||||
var value = bundleAssetName == null ? null : bundle.LoadAsset(bundleAssetName);
|
||||
var value = bundleAssetName == null
|
||||
? null
|
||||
: LoadBundleAsset(bundle, bundleAssetName, selection.Asset.AssetKind);
|
||||
if (value == null)
|
||||
throw new InvalidOperationException(
|
||||
$"Provider asset '{selection.Asset.AssetRef}' advertises missing Unity bundle asset " +
|
||||
$"'{selection.Variant.AssetKey}'. Available assets: " +
|
||||
string.Join(", ", bundleAssetNames));
|
||||
$"'{selection.Variant.AssetKey}'. Available assets: " + string.Join(", ", bundleAssetNames));
|
||||
_nativeAssets[selection.Asset.AssetRef] = value;
|
||||
var metadata = MergeAssetMetadata(selection.Asset.Metadata, selection.Variant.Metadata);
|
||||
_nativeAssetMetadata[selection.Asset.AssetRef] = metadata;
|
||||
if (selection.Asset.Metadata.TryGetValue("presentationRole", out var role) &&
|
||||
!string.IsNullOrWhiteSpace(role))
|
||||
{
|
||||
_nativeAssets[role] = value;
|
||||
_nativeAssetMetadata[role] = metadata;
|
||||
}
|
||||
if (value is GameObject prefab)
|
||||
{
|
||||
_prefabs[selection.Asset.AssetRef] = prefab;
|
||||
if (!string.IsNullOrWhiteSpace(role)) _prefabs[role] = prefab;
|
||||
}
|
||||
}
|
||||
_loadedBundleUris.Add(uri);
|
||||
TraceStartup("bundle-assets", groupElapsed);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_loadingBundleUris.Remove(uri);
|
||||
}
|
||||
}
|
||||
|
||||
CurrentAssetCatalogVersion = catalog.Version;
|
||||
private static UnityEngine.Object? LoadBundleAsset(AssetBundle bundle, string assetName, string assetKind)
|
||||
{
|
||||
return string.Equals(assetKind, "sprite", StringComparison.Ordinal)
|
||||
? bundle.LoadAsset<Sprite>(assetName)
|
||||
: bundle.LoadAsset(assetName);
|
||||
}
|
||||
|
||||
private sealed class SelectedAsset
|
||||
{
|
||||
public SelectedAsset(EveAssetCatalogEntry asset, EveAssetVariant? variant)
|
||||
{
|
||||
Asset = asset;
|
||||
Variant = variant;
|
||||
}
|
||||
|
||||
public EveAssetCatalogEntry Asset { get; }
|
||||
public EveAssetVariant? Variant { get; }
|
||||
}
|
||||
|
||||
private static string? ResolveBundleAssetName(IEnumerable<string> bundleAssetNames, string advertisedAssetKey)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.IO;
|
||||
using GameCult.Eve.Surface;
|
||||
using GameCult.Eve.UnityScene;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
|
|
@ -106,5 +108,49 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
Assert.That(result["unity.volume.texturePort.surfaceHeight"], Is.EqualTo("_NebulaSurfaceHeight"));
|
||||
Assert.That(result["unity.volume.pass.raymarch"], Is.EqualTo("0"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PublishingCatalogRegistersBundlesWithoutMaterializingThem()
|
||||
{
|
||||
var platformMethod = typeof(EveUnityCultMeshLiveProviderTransport).GetMethod(
|
||||
"CurrentBundlePlatform",
|
||||
BindingFlags.Static | BindingFlags.NonPublic);
|
||||
var publishMethod = typeof(EveUnityCultMeshLiveProviderTransport).GetMethod(
|
||||
"PublishAssetCatalog",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
var bundlesField = typeof(EveUnityCultMeshLiveProviderTransport).GetField(
|
||||
"_assetBundles",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
var selectionsField = typeof(EveUnityCultMeshLiveProviderTransport).GetField(
|
||||
"_assetSelections",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
Assert.That(platformMethod, Is.Not.Null);
|
||||
Assert.That(publishMethod, Is.Not.Null);
|
||||
|
||||
using var transport = new EveUnityCultMeshLiveProviderTransport(
|
||||
Path.Combine(Path.GetTempPath(), $"eve-lazy-assets-{Guid.NewGuid():N}.cc"),
|
||||
"cultnet+tcp://127.0.0.1:1",
|
||||
"provider",
|
||||
"surface");
|
||||
var variant = new EveAssetVariant(
|
||||
"unity-scene",
|
||||
(string)platformMethod!.Invoke(null, Array.Empty<object>())!,
|
||||
"unity-assetbundle",
|
||||
"cdn:manifest:lazy",
|
||||
"sha256:00",
|
||||
1,
|
||||
"assets/prefab.prefab");
|
||||
var catalog = new EveAssetCatalogDocument(
|
||||
"provider",
|
||||
"catalog",
|
||||
1,
|
||||
DateTimeOffset.UtcNow.ToString("O"),
|
||||
new[] { new EveAssetCatalogEntry("prefab.entity", "prefab", new[] { variant }) });
|
||||
|
||||
publishMethod!.Invoke(transport, new object[] { catalog });
|
||||
|
||||
Assert.That(((System.Collections.ICollection)bundlesField!.GetValue(transport)!).Count, Is.Zero);
|
||||
Assert.That(((System.Collections.IDictionary)selectionsField!.GetValue(transport)!).Count, Is.EqualTo(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "org.gamecult.eve.unity-scene",
|
||||
"displayName": "Eve Unity Scene Lowering",
|
||||
"version": "0.3.95",
|
||||
"version": "0.3.96",
|
||||
"unity": "6000.0",
|
||||
"description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.",
|
||||
"dependencies": {
|
||||
|
|
|
|||
Reference in a new issue