Serve Aetheria bundle chunks without cache republish

This commit is contained in:
Meta Vens 2026-07-20 03:06:30 +01:00
commit b838862b2f
2 changed files with 65 additions and 38 deletions

View file

@ -775,17 +775,14 @@ static async Task<AetheriaClientCultMeshHost> StartClientCultMeshHostAsync(
startupPhase.Restart();
}
var contentCache = new CultCache(CultMesh.CreateCultCacheDocumentRegistry(
typeof(CultMeshCdnArtifactManifest),
typeof(CultMeshCdnArtifactChunk)));
var bundleCdnDocuments = BuildBundleCdnDocuments(node, contentCache, unityBundles, options.DaemonId);
var bundleCdnManifests = BuildBundleCdnManifestIndex(unityBundles);
Trace("cdn-documents");
var server = new TcpFramedCultNetSchemaServer(new TcpListener(
ParseBindAddress(options.ClientCultMeshHost),
options.ClientCultMeshPort));
var content = new CultMeshTcpContentServer(new TcpListener(
ParseBindAddress(options.ClientCultMeshHost),
options.ClientCultMeshContentPort), contentCache);
options.ClientCultMeshContentPort), unityBundles.ResolveChunk);
Trace("tcp-listeners");
var realtimeCertificate = LoadOrCreateRealtimeCertificate(
options.ClientCultMeshAdvertiseHost,
@ -808,7 +805,6 @@ static async Task<AetheriaClientCultMeshHost> StartClientCultMeshHostAsync(
realtimeCertificate.Dispose();
content.Dispose();
server.Dispose();
contentCache.Dispose();
throw;
}
var advertisedEndpoint = $"cultnet+tcp://{options.ClientCultMeshAdvertiseHost}:{server.LocalEndPoint.Port}";
@ -1242,7 +1238,12 @@ static async Task<AetheriaClientCultMeshHost> StartClientCultMeshHostAsync(
}
}
InjectCultMeshCdnManifestSnapshots(bundleCdnDocuments, request, response);
InjectCultMeshCdnManifestSnapshots(
node,
options.DaemonId,
bundleCdnManifests,
request,
response);
peer.SendCultNet(response);
}
catch (Exception ex)
@ -1270,7 +1271,6 @@ static async Task<AetheriaClientCultMeshHost> StartClientCultMeshHostAsync(
content,
realtime,
realtimeCertificate,
contentCache,
advertisedEndpoint,
advertisedContentEndpoint,
advertisedRealtimeEndpoint);
@ -1347,7 +1347,9 @@ static X509Certificate2 LoadOrCreateRealtimeCertificate(string advertisedHost, s
}
static void InjectCultMeshCdnManifestSnapshots(
IReadOnlyDictionary<string, CultNetRawDocumentRecord> bundleCdnDocuments,
AetheriaStateNode node,
string daemonId,
IReadOnlyDictionary<string, CultMeshCdnArtifactManifest> bundleCdnManifests,
CultNetSnapshotRequestMessage request,
CultNetSnapshotResponseRawMessage response)
{
@ -1358,8 +1360,18 @@ static void InjectCultMeshCdnManifestSnapshots(
var documents = new List<CultNetRawDocumentRecord>();
foreach (var recordKey in recordKeys.Distinct(StringComparer.Ordinal))
{
if (bundleCdnDocuments.TryGetValue(recordKey, out var bundleDocument))
if (bundleCdnManifests.TryGetValue(recordKey, out var manifest))
{
var bundleDocument = node.Database.Documents.CreateRawDocumentPutMessage(
$"aetheria-cdn:{recordKey}",
new CultRecordHandle<CultMeshCdnArtifactManifest>(new CultRecordKey(recordKey)),
manifest,
new CultNetDocumentMessageOptions
{
SourceRuntimeId = daemonId,
SourceRole = "aetheria-cultmesh-cdn",
Tags = ["aetheria", "cultmesh-cdn", "asset-bundle"]
}).Document;
if (string.Equals(Environment.GetEnvironmentVariable("AETHERIA_TRACE_EVE_SNAPSHOTS"), "1", StringComparison.Ordinal))
Console.WriteLine($"Eve CDN snapshot schema={bundleDocument.SchemaId} record={recordKey}");
documents.Add(bundleDocument);
@ -1376,35 +1388,16 @@ static void InjectCultMeshCdnManifestSnapshots(
.ToArray();
}
static IReadOnlyDictionary<string, CultNetRawDocumentRecord> BuildBundleCdnDocuments(
AetheriaStateNode node,
CultCache contentCache,
AetheriaUnityBundleArtifactSet unityBundles,
string daemonId)
static IReadOnlyDictionary<string, CultMeshCdnArtifactManifest> BuildBundleCdnManifestIndex(
AetheriaUnityBundleArtifactSet unityBundles)
{
var documents = new Dictionary<string, CultNetRawDocumentRecord>(StringComparer.Ordinal);
var manifests = new Dictionary<string, CultMeshCdnArtifactManifest>(StringComparer.Ordinal);
foreach (var bundle in unityBundles.Bundles)
{
var artifact = bundle.Artifact;
CultMeshCdn.PublishAsync(contentCache, artifact).GetAwaiter().GetResult();
Add(artifact.ManifestKey, artifact.Manifest);
}
return documents;
void Add<T>(CultRecordKey recordKey, T document) where T : class
{
var put = node.Database.Documents.CreateRawDocumentPutMessage(
$"aetheria-cdn:{recordKey.Value}",
new CultRecordHandle<T>(recordKey),
document,
new CultNetDocumentMessageOptions
{
SourceRuntimeId = daemonId,
SourceRole = "aetheria-cultmesh-cdn",
Tags = ["aetheria", "cultmesh-cdn", "asset-bundle"]
});
documents[recordKey.Value] = put.Document;
manifests[artifact.ManifestKey.ToString()] = artifact.Manifest;
}
return manifests;
}
static async Task InjectEveSurfaceSnapshotAsync(
@ -3827,12 +3820,24 @@ internal sealed class AetheriaDaemonIngressState
internal sealed class AetheriaUnityBundleArtifactSet
{
private readonly IReadOnlyDictionary<string, CultMeshCdnArtifactChunk> _chunks;
public AetheriaUnityBundleArtifactSet(IReadOnlyList<AetheriaUnityBundleArtifact> bundles)
{
Bundles = bundles ?? throw new ArgumentNullException(nameof(bundles));
var chunks = new Dictionary<string, CultMeshCdnArtifactChunk>(StringComparer.Ordinal);
foreach (var chunk in bundles.SelectMany(bundle => bundle.Artifact.Chunks))
chunks.TryAdd(CultMeshCdnArtifactChunk.CreateRecordKey(chunk).ToString(), chunk);
_chunks = chunks;
}
public IReadOnlyList<AetheriaUnityBundleArtifact> Bundles { get; }
public CultMeshCdnArtifactChunk? ResolveChunk(string hash)
{
var key = CultMeshCdnArtifactChunk.CreateRecordKey(hash).ToString();
return _chunks.TryGetValue(key, out var chunk) ? chunk : null;
}
}
internal sealed class AetheriaUnityBundleArtifact
@ -3868,7 +3873,6 @@ internal sealed class AetheriaClientCultMeshHost : IAsyncDisposable
CultMeshTcpContentServer content,
CultMeshQuicRealtimeServer realtime,
X509Certificate2 realtimeCertificate,
CultCache contentCache,
string controlEndpoint,
string contentEndpoint,
string realtimeEndpoint)
@ -3877,7 +3881,6 @@ internal sealed class AetheriaClientCultMeshHost : IAsyncDisposable
Content = content ?? throw new ArgumentNullException(nameof(content));
Realtime = realtime ?? throw new ArgumentNullException(nameof(realtime));
RealtimeCertificate = realtimeCertificate ?? throw new ArgumentNullException(nameof(realtimeCertificate));
ContentCache = contentCache ?? throw new ArgumentNullException(nameof(contentCache));
ControlEndpoint = controlEndpoint ?? throw new ArgumentNullException(nameof(controlEndpoint));
ContentEndpoint = contentEndpoint ?? throw new ArgumentNullException(nameof(contentEndpoint));
RealtimeEndpoint = realtimeEndpoint ?? throw new ArgumentNullException(nameof(realtimeEndpoint));
@ -3887,7 +3890,6 @@ internal sealed class AetheriaClientCultMeshHost : IAsyncDisposable
public CultMeshTcpContentServer Content { get; }
public CultMeshQuicRealtimeServer Realtime { get; }
private X509Certificate2 RealtimeCertificate { get; }
private CultCache ContentCache { get; }
public string ControlEndpoint { get; }
public string ContentEndpoint { get; }
public string RealtimeEndpoint { get; }
@ -3897,7 +3899,6 @@ internal sealed class AetheriaClientCultMeshHost : IAsyncDisposable
await Realtime.DisposeAsync().ConfigureAwait(false);
RealtimeCertificate.Dispose();
Content.Dispose();
ContentCache.Dispose();
Control.Dispose();
}
}

View file

@ -176,6 +176,32 @@ certificate setup fell from 153 ms of RSA generation to 27 ms of PFX loading,
and total client-host startup fell from about 319 ms to 183 ms. The first boot
still pays certificate creation once.
### Provider-owned CDN bodies
- **Owner:** the Aetheria bundle artifact set owns the immutable packed chunks
and manifests for the lifetime of the daemon. The TCP content server owns
framing and byte delivery, not storage.
- **Inputs:** provider-authored bundle files are packed once into content-addressed
manifests and chunks. The control plane indexes manifests by exact record key;
the content plane resolves chunks by normalized SHA-256 hash.
- **Outputs:** requested manifests remain typed CultNet snapshot records, while
chunk payloads travel on the dedicated TCP byte stream outside snapshot
messages.
- **Derived state:** raw manifest snapshot records are serialized only when a
client requests that manifest. No second CultCache mirrors the packed chunk
set.
- **Forbidden writers:** client-host startup must not republish provider-owned
chunks into an in-memory cache merely to satisfy a transport constructor, and
asset bodies must not fall back into batched state snapshots.
- **Cut line:** `CultMeshTcpContentServer` accepts a provider-owned chunk resolver;
its CultCache constructor is now only a convenience adapter for providers that
genuinely keep content in CultCache.
- **Verification layer:** CultMesh content-session and transport-modularity tests
stream a 700 KB provider-owned body directly and retain warm-cache reuse. On
the Aetheria artifact, eager CDN setup fell from about 132 ms to 1.9 ms. The
13 MB cold released-client transfer still requires a fresh live witness before
it can be claimed healthy.
### Restored-frame client readiness
- **Owner:** the latest hard-flushed daemon frame owns restart world truth. Its