Serve Aetheria bundle chunks without cache republish
This commit is contained in:
parent
c20752cb85
commit
b838862b2f
2 changed files with 65 additions and 38 deletions
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue