CultLib/src/GameCult.Networking/CultNetLocal.cs
Meta Vens 85f2089f0e Delete CultCache wrappers and unused members
Removes FlushBackingStore, both PrepareForReloadOrShutdown overloads,
PullBackingStoreRecordsAsync, TryGet<T>, TryGetByName, TryGetByIndex,
Resolve<T>, the cache and store Logger, and LastSuccessfulFlushAtUtc on the
cache and stores. GetStoredDocuments<T> folds into GetAll<T>.

The soft flag goes from FlushAllBackingStores, ExecuteTransactionAsync,
CommitBatch and PushAll. It stays on FlushAsync(bool) because
CultCacheStudioWindow reflects on that signature until Cut 6.
CultMesh.FlushAsync and CultNetLocal.FlushAsync keep their parameter and
stop forwarding it.

The store's EntryAdded/EntryUpdated/EntryDeleted subjects become
protected internal Loaded/Unloaded callbacks set by the cache at attach.
AddStoredDocumentInternal is synchronous, so the GetAwaiter().GetResult()
bridges on the store subscriptions and the global materializer are gone.

Tests: drop the LastSuccessfulFlushAtUtc asserts; OpenAsync and GetAll tests
read through Get/GetAll; the persisted-global test proves the durable global
loads without staging a default instead of going through selective hydration;
the lease-before-observers test observes through CultCache.Watch; the
PullSelected selective-hydration test is deleted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 16:44:11 +02:00

212 lines
7.3 KiB
C#

using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using GameCult.Caching;
using GameCult.Caching.MessagePack;
namespace GameCult.Networking
{
/// <summary>
/// Options for creating a local CultNet host over CultCache and the canonical MessagePack store.
/// </summary>
public sealed class CultNetHostOptions
{
/// <summary>
/// Gets or sets the cache-open options used for the underlying CultCache.
/// </summary>
public CultCacheOpenOptions? CacheOptions { get; set; }
/// <summary>
/// Gets or sets the server security configuration. When omitted, development defaults are used.
/// </summary>
public ServerSecurityOptions? Security { get; set; }
/// <summary>
/// Gets or sets whether the server should start immediately after creation.
/// </summary>
public bool StartServer { get; set; } = true;
/// <summary>
/// Gets or sets the database sharding options used for the hosted CultCache.
/// </summary>
public CultNetDatabaseOptions? DatabaseOptions { get; set; }
/// <summary>
/// Gets or sets the database server bridge options used for shard routing and forwarding.
/// </summary>
public CultNetDatabaseServerOptions? DatabaseServerOptions { get; set; }
/// <summary>
/// Gets or sets an optional callback used to customize the server before start.
/// </summary>
public Action<Server>? ConfigureServer { get; set; }
}
/// <summary>
/// Convenience wrapper for a locally hosted CultNet server over a durable CultCache.
/// </summary>
public sealed class CultNetHost : IDisposable
{
internal CultNetHost(
CultCache cache,
SingleFileMessagePackBackingStore? store,
Server server,
CultNetDatabase database,
CultNetDatabaseServer databaseServer)
{
Cache = cache;
Store = store;
Server = server;
Database = database;
DatabaseServer = databaseServer;
}
/// <summary>
/// Gets the durable cache used by the host.
/// </summary>
public CultCache Cache { get; }
/// <summary>
/// Gets the canonical single-file MessagePack store, when one is attached.
/// </summary>
public SingleFileMessagePackBackingStore? Store { get; }
/// <summary>
/// Gets the underlying CultNet server.
/// </summary>
public Server Server { get; }
/// <summary>
/// Gets the distributed database facade over the hosted cache.
/// </summary>
public CultNetDatabase Database { get; }
/// <summary>
/// Gets the schema-v0 server bridge for distributed database messages.
/// </summary>
public CultNetDatabaseServer DatabaseServer { get; }
/// <summary>
/// Starts the server.
/// </summary>
public void Start()
{
Server.Start();
}
/// <summary>
/// Flushes the durable cache.
/// </summary>
public Task FlushAsync(bool soft = false)
{
return Cache.FlushAsync();
}
/// <summary>
/// Stops the server and disposes the host resources.
/// </summary>
public void Dispose()
{
DatabaseServer.Dispose();
Server.Dispose();
Database.Dispose();
Cache.Dispose();
}
}
/// <summary>
/// Friendly local entrypoints for CultNet host/client creation.
/// </summary>
public static class CultNetLocal
{
/// <summary>
/// Creates a local host with a durable CultCache and the canonical MessagePack backing store.
/// </summary>
public static async Task<CultNetHost> CreateHostAsync(string cachePath, CultNetHostOptions? options = null)
{
if (string.IsNullOrWhiteSpace(cachePath))
{
throw new ArgumentException("Cache path must be non-empty.", nameof(cachePath));
}
options ??= new CultNetHostOptions();
var traceStartup = string.Equals(
Environment.GetEnvironmentVariable("CULTMESH_TRACE_STARTUP_PHASES"),
"1",
StringComparison.Ordinal);
var startupPhase = Stopwatch.StartNew();
void Trace(string phase)
{
if (traceStartup)
Console.WriteLine($"CultMesh local-host phase {phase} took {startupPhase.Elapsed.TotalMilliseconds:0.###}ms.");
startupPhase.Restart();
}
var cache = await CultCacheMessagePack.OpenAsync(cachePath, options.CacheOptions).ConfigureAwait(false);
Trace("cache-open");
var store = cache.BackingStores.OfType<SingleFileMessagePackBackingStore>().FirstOrDefault();
var server = new Server(cache, options.Security ?? ServerSecurityOptions.Development());
Trace("server");
var databaseOptions = options.DatabaseOptions ?? new CultNetDatabaseOptions();
var database = new CultNetDatabase(cache, databaseOptions);
Trace("database");
var databaseServer = new CultNetDatabaseServer(server, database, options.DatabaseServerOptions);
Trace("database-server");
options.ConfigureServer?.Invoke(server);
var host = new CultNetHost(cache, store, server, database, databaseServer);
if (options.StartServer)
{
host.Start();
}
Trace("server-start");
return host;
}
/// <summary>
/// Creates and starts a local host with development-friendly defaults.
/// </summary>
public static Task<CultNetHost> StartHostAsync(string cachePath, CultNetHostOptions? options = null)
{
options ??= new CultNetHostOptions();
options.StartServer = true;
return CreateHostAsync(cachePath, options);
}
/// <summary>
/// Creates a client configured for local CultNet use.
/// </summary>
public static Client CreateClient(ClientSecurityOptions? security = null, Action<Client>? configureClient = null)
{
var client = new Client(security ?? ClientSecurityOptions.Development());
configureClient?.Invoke(client);
return client;
}
/// <summary>
/// Creates and connects a client to the specified host.
/// </summary>
public static Client ConnectClient(
string host = "localhost",
int port = 3075,
ClientSecurityOptions? security = null,
Action<Client>? configureClient = null)
{
var client = CreateClient(security, configureClient);
client.Connect(host, port);
return client;
}
/// <summary>
/// Creates and connects a client to a local development host.
/// </summary>
public static Client ConnectLocal(
ClientSecurityOptions? security = null,
Action<Client>? configureClient = null)
{
return ConnectClient("localhost", 3075, security, configureClient);
}
}
}