Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions SlipeServer.Server.Tests/Integration/Elements/PlayerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
using SlipeServer.Server.Loggers;
using SlipeServer.Server.PacketHandling.Handlers.Player;
using SlipeServer.Server.PacketHandling.Handlers.QueueHandlers;
using SlipeServer.Server.Resources;
using SlipeServer.Server.TestTools;
using System.Linq;
using System;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace SlipeServer.Server.Tests.Integration.Elements;
Expand Down Expand Up @@ -67,4 +71,71 @@ public void KickingPlayerShouldDestroyAndDisconnectPlayer()
.Select(x => x.EventName)
.Should().BeEquivalentTo(["Kicked", "Disconnected", "Destroyed"]);
}

[Fact]
public async Task StartForAsyncShouldBeCancellable()
{
var server = new TestingServer();
var player = server.AddFakePlayer();

var resource = new Resource(server, server.RootElement, "test");

var cts = new CancellationTokenSource();
cts.Cancel();

var act = async () => await resource.StartForAsync(player, cts.Token);

await act.Should().ThrowAsync<OperationCanceledException>();
}

[Fact]
public async Task StartForAsyncShouldBeCancellable2()
{
var server = new TestingServer();
var player = server.AddFakePlayer();

var resource = new Resource(server, server.RootElement, "test");

var cts = new CancellationTokenSource();

var act = async () => await resource.StartForAsync(player, cts.Token);

var waitHandle = new ManualResetEvent(false);
var cancelled = false;
var _ = Task.Run(async () =>
{
await act.Should().ThrowAsync<TaskCanceledException>();
waitHandle.Set();
cancelled = true;
});

await Task.Delay(2000); // Let StartForAsync execute for a while to block on "await source.Task"
await cts.CancelAsync();
waitHandle.WaitOne(TimeSpan.FromSeconds(5));
cancelled.Should().BeTrue();
}

[Fact]
public void ResourceShouldStopStartingWhenPlayerDisconnectFromTheServer()
{
var server = new TestingServer();
var player = server.AddFakePlayer();
var resource = new Resource(server, server.RootElement, "test");
var cts = new CancellationTokenSource();

var act = async () => await resource.StartForAsync(player, cts.Token);

var waitHandle = new ManualResetEvent(false);
var cancelled = false;
var _ = Task.Run(async () =>
{
await act.Should().ThrowAsync<Exception>();
waitHandle.Set();
cancelled = true;
});

player.Kick();
waitHandle.WaitOne(TimeSpan.FromSeconds(5));
cancelled.Should().BeTrue();
}
}
51 changes: 34 additions & 17 deletions SlipeServer.Server/Resources/Resource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using SlipeServer.Server.Elements;
using SlipeServer.Server.Elements.Events;
using SlipeServer.Server.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
Expand Down Expand Up @@ -83,46 +84,62 @@ public void StartFor(Player player)
.SendTo(player);
}

public Task StartForAsync(Player player, CancellationToken cancelationToken = default)
public async Task StartForAsync(Player player, CancellationToken cancelationToken = default)
{
cancelationToken.ThrowIfCancellationRequested();
if (player.IsDestroyed || !player.Client.IsConnected)
throw new InvalidOperationException("Player is not connected to the server.");

using var cts = new CancellationTokenSource(300_000);
using var cts2 = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancelationToken);
var source = new TaskCompletionSource();

cancelationToken.Register(() =>
cts2.Token.Register(() =>
{
player.ResourceStarted -= HandleResourceStart;
player.Disconnected -= HandlePlayerDisconnected;
source.SetException(new TaskCanceledException());
});

player.ResourceStarted += HandleResourceStart;
player.Disconnected += HandlePlayerDisconnected;

void HandleResourceStart(Player sender, PlayerResourceStartedEventArgs e)
{
if (e.NetId != this.NetId)
if (e.NetId != this.NetId || cts2.Token.IsCancellationRequested)
return;

player.ResourceStarted -= HandleResourceStart;
player.Disconnected -= HandlePlayerDisconnected;

source.SetResult();
}

void HandlePlayerDisconnected(Player disconnectingPlayer, PlayerQuitEventArgs e)
{
if(player != disconnectingPlayer)
if(player != disconnectingPlayer || cts2.Token.IsCancellationRequested)
return;

player.ResourceStarted -= HandleResourceStart;
player.Disconnected -= HandlePlayerDisconnected;
source.SetException(new Exception("Player disconnected."));
}

void HandleDestroed(Element destroyedElement)
{
if(player != destroyedElement || cts2.Token.IsCancellationRequested)
return;

source.SetException(new System.Exception("Player disconnected."));
source.SetException(new Exception("Player destroyed."));
}

cts2.Token.ThrowIfCancellationRequested();

player.ResourceStarted += HandleResourceStart;
player.Disconnected += HandlePlayerDisconnected;
player.Destroyed += HandleDestroed;

StartFor(player);

return source.Task;
try
{
await source.Task;
}
finally
{
player.ResourceStarted -= HandleResourceStart;
player.Disconnected -= HandlePlayerDisconnected;
player.Destroyed -= HandleDestroed;
}
}

public void StopFor(Player player)
Expand Down