Lease schema clients from CultMesh sessions
This commit is contained in:
parent
1277c6bd2e
commit
b4e06bc762
2 changed files with 74 additions and 0 deletions
|
|
@ -174,6 +174,7 @@ namespace GameCult.Mesh
|
|||
internal ICultNetSchemaClient Channel { get; }
|
||||
public CultMeshSessionState State { get; private set; }
|
||||
public Observable<CultMeshSessionState> WatchState() => _states;
|
||||
public ICultNetSchemaClient OpenSchemaClient() => new SessionSchemaClient(this);
|
||||
public void SendCultNet<T>(T message) where T : ICultNetSchemaMessage => Channel.SendCultNet(message);
|
||||
public IDisposable OnCultNet<T>(Action<T> callback) where T : ICultNetSchemaMessage
|
||||
{
|
||||
|
|
@ -200,6 +201,36 @@ namespace GameCult.Mesh
|
|||
public void Invoke(T message) => Volatile.Read(ref _callback)?.Invoke(message);
|
||||
public void Dispose() => Interlocked.Exchange(ref _callback, null);
|
||||
}
|
||||
|
||||
private sealed class SessionSchemaClient : ICultNetSchemaClient
|
||||
{
|
||||
private readonly CultMeshSession _session;
|
||||
private readonly ConcurrentBag<IDisposable> _registrations = new();
|
||||
private bool _disposed;
|
||||
public SessionSchemaClient(CultMeshSession session) { _session = session; }
|
||||
public bool Connected => !_disposed && _session.State.Status == CultMeshSessionStatus.Online;
|
||||
public void Connect(string host, int port)
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(SessionSchemaClient));
|
||||
if (!Connected) throw new InvalidOperationException("The managed CultMesh session is not online.");
|
||||
}
|
||||
public void SendCultNet<T>(T message) where T : ICultNetSchemaMessage
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(SessionSchemaClient));
|
||||
_session.SendCultNet(message);
|
||||
}
|
||||
public void OnCultNet<T>(Action<T> callback) where T : ICultNetSchemaMessage
|
||||
{
|
||||
if (_disposed) throw new ObjectDisposedException(nameof(SessionSchemaClient));
|
||||
_registrations.Add(_session.OnCultNet(callback));
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
foreach (var registration in _registrations) registration.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CultMeshSessionManagerOptions
|
||||
|
|
|
|||
|
|
@ -166,6 +166,32 @@ public sealed class CultMeshSessionManagerTests
|
|||
client.SentCount.Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SchemaClientLeasesDisposeHandlersWithoutClosingSession()
|
||||
{
|
||||
var clock = new ManualSessionClock();
|
||||
using var discovery = Discovery(clock, () => new[] { "rudp://direct:3076" });
|
||||
var client = new LeaseSchemaClient();
|
||||
var connector = new FakeConnector((_, _) => Task.FromResult<ICultNetSchemaClient>(client));
|
||||
using var manager = new CultMeshSessionManager(discovery, new[] { connector }, new CultMeshSessionManagerOptions { Clock = clock });
|
||||
var session = await manager.ConnectAsync(CultMeshEndpointId.Parse("odin:aetheria"), CultMeshProtocols.Subscriptions);
|
||||
using var first = session.OpenSchemaClient();
|
||||
using var second = session.OpenSchemaClient();
|
||||
var firstCount = 0;
|
||||
var secondCount = 0;
|
||||
first.OnCultNet<CultNetErrorMessage>(_ => firstCount++);
|
||||
second.OnCultNet<CultNetErrorMessage>(_ => secondCount++);
|
||||
|
||||
client.Emit(new CultNetErrorMessage { Error = "first" });
|
||||
first.Dispose();
|
||||
client.Emit(new CultNetErrorMessage { Error = "second" });
|
||||
|
||||
firstCount.Should().Be(1);
|
||||
secondCount.Should().Be(2);
|
||||
client.DisposeCount.Should().Be(0);
|
||||
session.State.Status.Should().Be(CultMeshSessionStatus.Online);
|
||||
}
|
||||
|
||||
private static CultMeshDiscoveryService Discovery(
|
||||
ManualSessionClock clock,
|
||||
Func<string[]> endpoints,
|
||||
|
|
@ -284,6 +310,23 @@ public sealed class CultMeshSessionManagerTests
|
|||
public void Dispose() { }
|
||||
}
|
||||
|
||||
private sealed class LeaseSchemaClient : ICultNetSchemaClient
|
||||
{
|
||||
private readonly List<Action<CultNetErrorMessage>> _handlers = new();
|
||||
private int _disposeCount;
|
||||
public bool Connected => true;
|
||||
public int DisposeCount => Volatile.Read(ref _disposeCount);
|
||||
public void Connect(string host, int port) { }
|
||||
public void SendCultNet<T>(T message) where T : ICultNetSchemaMessage { }
|
||||
public void OnCultNet<T>(Action<T> callback) where T : ICultNetSchemaMessage
|
||||
{
|
||||
if (typeof(T) == typeof(CultNetErrorMessage))
|
||||
_handlers.Add(message => callback((T)(object)message));
|
||||
}
|
||||
public void Emit(CultNetErrorMessage message) { foreach (var handler in _handlers.ToArray()) handler(message); }
|
||||
public void Dispose() => Interlocked.Increment(ref _disposeCount);
|
||||
}
|
||||
|
||||
private sealed class ManualSessionClock : ICultMeshClock
|
||||
{
|
||||
public DateTimeOffset UtcNow { get; private set; } = new DateTimeOffset(2026, 7, 12, 21, 0, 0, TimeSpan.Zero);
|
||||
|
|
|
|||
Loading…
Reference in a new issue