refactor(net): cut GameWindow over to session owner
This commit is contained in:
parent
783ef1d6db
commit
6a5d9e2e6a
5 changed files with 363 additions and 268 deletions
|
|
@ -0,0 +1,37 @@
|
|||
using System.Reflection;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Net;
|
||||
using AcDream.Core.Net;
|
||||
|
||||
namespace AcDream.App.Tests.Net;
|
||||
|
||||
public sealed class GameWindowLiveSessionOwnershipTests
|
||||
{
|
||||
private const BindingFlags PrivateInstance =
|
||||
BindingFlags.Instance | BindingFlags.NonPublic;
|
||||
|
||||
[Fact]
|
||||
public void GameWindowRetainsOnlyTheLifecycleControllerSessionOwner()
|
||||
{
|
||||
FieldInfo[] fields = typeof(GameWindow).GetFields(PrivateInstance);
|
||||
|
||||
Assert.Contains(
|
||||
fields,
|
||||
field => field.Name == "_liveSessionController"
|
||||
&& field.FieldType == typeof(LiveSessionController));
|
||||
Assert.DoesNotContain(fields, field => field.Name == "_liveSession");
|
||||
Assert.DoesNotContain(fields, field => field.FieldType == typeof(WorldSession));
|
||||
Assert.DoesNotContain(fields, field => field.Name == "_liveSessionEvents");
|
||||
Assert.DoesNotContain(fields, field => field.Name == "_liveSessionCommands");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("TryStartLiveSession")]
|
||||
[InlineData("ClearInboundEntityState")]
|
||||
[InlineData("WireLiveSessionEvents")]
|
||||
[InlineData("DisposeLiveSessionRouting")]
|
||||
public void DisplacedLifecycleBodiesAreAbsent(string methodName)
|
||||
{
|
||||
Assert.Null(typeof(GameWindow).GetMethod(methodName, PrivateInstance));
|
||||
}
|
||||
}
|
||||
110
tests/AcDream.App.Tests/Net/LiveSessionLifecycleHostTests.cs
Normal file
110
tests/AcDream.App.Tests/Net/LiveSessionLifecycleHostTests.cs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
using System.Net;
|
||||
using AcDream.App.Net;
|
||||
using AcDream.Core.Net;
|
||||
using AcDream.UI.Abstractions;
|
||||
|
||||
namespace AcDream.App.Tests.Net;
|
||||
|
||||
public sealed class LiveSessionLifecycleHostTests
|
||||
{
|
||||
[Fact]
|
||||
public void HostRoutesLifecycleAndReleasesOnlyTheExactBoundSession()
|
||||
{
|
||||
var calls = new List<string>();
|
||||
using var sessionA = CreateSession(9000);
|
||||
using var sessionB = CreateSession(9001);
|
||||
var host = CreateHost(calls);
|
||||
|
||||
LiveSessionBinding binding = host.BindSession(sessionA);
|
||||
host.ResetSessionState();
|
||||
host.ReportConnecting("host", 9000, "user");
|
||||
host.ReportConnected();
|
||||
var selection = new LiveSessionCharacterSelection(2, 3u, "toon", "account");
|
||||
host.ApplySelectedCharacter(selection);
|
||||
binding.ActivateCommands();
|
||||
host.ApplyEnteredWorld(selection);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => host.DetachSession(sessionB));
|
||||
binding.Dispose();
|
||||
host.DetachSession(sessionA);
|
||||
LiveSessionBinding replacement = host.BindSession(sessionB);
|
||||
|
||||
Assert.Equal(
|
||||
[
|
||||
"bind", "reset", "connecting:host:9000:user",
|
||||
"connected", "selected:toon", "activate", "entered:toon",
|
||||
"deactivate", "detach-events", "bind",
|
||||
],
|
||||
calls);
|
||||
replacement.Dispose();
|
||||
host.DetachSession(sessionB);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FailedBindingFactoryDoesNotClaimTheHost()
|
||||
{
|
||||
var calls = new List<string>();
|
||||
using var session = CreateSession(9000);
|
||||
bool fail = true;
|
||||
LiveSessionLifecycleHost host = CreateHost(calls, _ =>
|
||||
{
|
||||
if (fail)
|
||||
{
|
||||
fail = false;
|
||||
throw new InvalidOperationException("bind failure");
|
||||
}
|
||||
return CreateBinding(session, calls);
|
||||
});
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => host.BindSession(session));
|
||||
LiveSessionBinding retry = host.BindSession(session);
|
||||
|
||||
retry.Dispose();
|
||||
host.DetachSession(session);
|
||||
}
|
||||
|
||||
private static LiveSessionLifecycleHost CreateHost(
|
||||
List<string> calls,
|
||||
Func<WorldSession, LiveSessionBinding>? bind = null) =>
|
||||
new(new LiveSessionLifecycleBindings(
|
||||
Bind: bind ?? (session => CreateBinding(session, calls)),
|
||||
Reset: () => calls.Add("reset"),
|
||||
Connecting: (host, port, user) =>
|
||||
calls.Add($"connecting:{host}:{port}:{user}"),
|
||||
Connected: () => calls.Add("connected"),
|
||||
Selected: selection => calls.Add($"selected:{selection.CharacterName}"),
|
||||
Entered: selection => calls.Add($"entered:{selection.CharacterName}")));
|
||||
|
||||
private static LiveSessionBinding CreateBinding(
|
||||
WorldSession session,
|
||||
List<string> calls)
|
||||
{
|
||||
calls.Add("bind");
|
||||
return new LiveSessionBinding(
|
||||
session,
|
||||
NullCommandBus.Instance,
|
||||
activateCommands: () => calls.Add("activate"),
|
||||
deactivateCommands: () => calls.Add("deactivate"),
|
||||
detachEvents: () => calls.Add("detach-events"));
|
||||
}
|
||||
|
||||
private static WorldSession CreateSession(int port) =>
|
||||
new(
|
||||
new IPEndPoint(IPAddress.Loopback, port),
|
||||
new TestTransport());
|
||||
|
||||
private sealed class TestTransport : IWorldSessionTransport
|
||||
{
|
||||
public void Send(ReadOnlySpan<byte> datagram) { }
|
||||
|
||||
public void Send(IPEndPoint remote, ReadOnlySpan<byte> datagram) { }
|
||||
|
||||
public byte[]? Receive(TimeSpan timeout, out IPEndPoint? from)
|
||||
{
|
||||
from = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue