Lease immutable Eve asset catalog generations

This commit is contained in:
Meta Vens 2026-08-19 10:14:37 +02:00
commit 959641c3a5
3 changed files with 50 additions and 13 deletions

View file

@ -1464,6 +1464,8 @@ namespace GameCult.Eve.UnityScene
ConfigureAssetCatalog(candidate, catalog);
ReuseCompatibleBundles(_assetGeneration, candidate);
await PreloadAssetsAsync(candidate, cancellationToken);
if (source.RequiredCatalogVersion > 0)
return candidate;
var latest = await candidate.CatalogLease!.Handle.LatestAsync();
candidate.ObserveCatalog(latest);
if (latest.Version != candidate.CatalogVersion)
@ -1504,7 +1506,6 @@ namespace GameCult.Eve.UnityScene
long requiredCatalogVersion,
CancellationToken cancellationToken)
{
var requiredCatalog = new TaskCompletionSource<EveAssetCatalogDocument>();
var lease = await AssetMeshClient(candidate)
.LeaseDocumentAsync<EveAssetCatalogDocument>(
candidate.Source.Target,
@ -1513,22 +1514,24 @@ namespace GameCult.Eve.UnityScene
IDisposable? watch = null;
try
{
watch = lease.Handle.Watch(catalog =>
var catalog = await lease.Handle.LatestAsync();
candidate.ObserveCatalog(catalog);
if (requiredCatalogVersion > 0)
{
var pending = candidate.ObserveCatalog(catalog);
if (requiredCatalogVersion > 0 && catalog.Version == requiredCatalogVersion)
requiredCatalog.TrySetResult(catalog);
if (pending != null && requiredCatalogVersion <= 0)
RequireCatalogVersion(catalog, requiredCatalogVersion, candidate.Source.ManifestRecordRef);
candidate.CatalogLease = lease;
return catalog;
}
watch = lease.Handle.Watch(observedCatalog =>
{
var pending = candidate.ObserveCatalog(observedCatalog);
if (pending != null)
QueueAssetCatalogUpdate(candidate.SourceIdentity, pending);
});
candidate.CatalogWatch = watch;
candidate.CatalogLease = lease;
var catalog = await lease.Handle.LatestAsync();
candidate.ObserveCatalog(catalog);
if (requiredCatalogVersion <= 0 || catalog.Version == requiredCatalogVersion)
return catalog;
using (cancellationToken.Register(() => requiredCatalog.TrySetCanceled()))
return await requiredCatalog.Task;
return catalog;
}
catch
{
@ -1538,6 +1541,19 @@ namespace GameCult.Eve.UnityScene
}
}
private static void RequireCatalogVersion(
EveAssetCatalogDocument catalog,
long requiredCatalogVersion,
string manifestRecordRef)
{
if (catalog == null) throw new ArgumentNullException(nameof(catalog));
if (requiredCatalogVersion <= 0 || catalog.Version == requiredCatalogVersion)
return;
throw new InvalidDataException(
$"Asset catalog record '{manifestRecordRef}' contains version {catalog.Version}, " +
$"but the Eve surface requires immutable version {requiredCatalogVersion}.");
}
private void CommitAssetGeneration(AssetGeneration candidate, EveSurfaceDocument? surface = null)
{
PreparedSurface prepared;

View file

@ -742,6 +742,27 @@ namespace GameCult.Eve.UnityScene.Tests
Assert.That(pendingField.GetValue(transport), Is.Null);
}
[Test]
public void PinnedSurfaceRejectsWrongImmutableCatalogRecordImmediately()
{
var require = typeof(EveUnityCultMeshLiveProviderTransport).GetMethod(
"RequireCatalogVersion",
BindingFlags.Static | BindingFlags.NonPublic)!;
var catalog42 = new EveAssetCatalogDocument(
"provider-a",
"eve:assets:aetheria.daemon:version:42",
42,
DateTimeOffset.UtcNow.ToString("O"),
Array.Empty<EveAssetCatalogEntry>());
var error = Assert.Throws<TargetInvocationException>(() => require.Invoke(
null,
new object[] { catalog42, 41L, "eve:assets:aetheria.daemon:version:41" }));
Assert.That(error!.InnerException, Is.TypeOf<InvalidDataException>());
Assert.That(error.InnerException!.Message, Does.Contain("requires immutable version 41"));
}
[Test]
public void CrossTargetAssetsUseConfiguredRemoteTrust()
{

View file

@ -1,7 +1,7 @@
{
"name": "org.gamecult.eve.unity-scene",
"displayName": "Eve Unity Scene Lowering",
"version": "0.3.133",
"version": "0.3.134",
"unity": "6000.0",
"description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.",
"dependencies": {