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
2 changes: 2 additions & 0 deletions SlipeServer.Console/Logic/ServerTestLogic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using SlipeServer.Packets.Lua.Camera;
using SlipeServer.Packets.Structs;
using SlipeServer.Server;
using SlipeServer.Server.Concepts;
using SlipeServer.Server.Constants;
using SlipeServer.Server.ElementCollections;
using SlipeServer.Server.Elements;
Expand Down Expand Up @@ -185,6 +186,7 @@ private void SetupTestLogic()
private void SetupTestElements()
{
this.testResource = this.resourceProvider.GetResource("TestResource");
this.testResource.AddClientTaskHelper();
this.secondTestResource = this.resourceProvider.GetResource("SecondTestResource");
this.secondTestResource.NoClientScripts[$"{this.secondTestResource!.Name}/testfile.lua"] =
Encoding.UTF8.GetBytes("outputChatBox(\"I AM A NOT CACHED MESSAGE\")");
Expand Down
25 changes: 24 additions & 1 deletion SlipeServer.Console/Resources/TestResource/test.lua
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,27 @@ addEventHandler("onClientRender", root, function()
k = k + 1
printDebugVehicle(k, v);
end
end)
end)

local clientTasks = {}
addEvent("testClientTask", true)
addEventHandler("testClientTask", root, function(clientTask)
outputChatBox("ClientTask created.");
clientTasks[#clientTasks + 1] = clientTask;
end)

addCommandHandler("resolveTasks", function()
for i,task in ipairs(clientTasks)do
ClientTask.Resolve(task, "Ok");
end
outputChatBox("Resolved: "..#clientTasks.." tasks.")
clientTasks = {}
end)

addCommandHandler("rejectTasks", function()
for i,task in ipairs(clientTasks)do
ClientTask.Reject(task, "Ok");
end
outputChatBox("Failed: "..#clientTasks.." tasks.")
clientTasks = {}
end)
54 changes: 53 additions & 1 deletion SlipeServer.Example/ServerExampleLogic.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using SlipeServer.Server;
using SlipeServer.Server.Concepts;
using SlipeServer.Server.Elements;
using SlipeServer.Server.Exceptions;
using SlipeServer.Server.Services;

namespace SlipeServer.Example;
Expand All @@ -8,12 +10,13 @@ public class ServerExampleLogic
{
private readonly CommandService commandService;
private readonly ChatBox chatBox;
private readonly MtaServer mtaServer;

public ServerExampleLogic(CommandService commandService, ChatBox chatBox, MtaServer mtaServer)
{
this.commandService = commandService;
this.chatBox = chatBox;

this.mtaServer = mtaServer;
AddCommand("hello", player =>
{
this.chatBox.OutputTo(player, "Hello world");
Expand Down Expand Up @@ -83,6 +86,40 @@ private void AddVehiclesCommands()
vehicle.Fix();
this.chatBox.OutputTo(player, "Vehicle fixed");
});

AddCommand("clienttask", async player =>
{
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
var task = this.mtaServer.CreateClientTask(player, cts.Token);

player.TriggerLuaEvent("testClientTask", player, task);

try
{
await task;
}
catch (PlayerDisconnectedException e) // When player left the server
{
Console.WriteLine("Result: PlayerDisconnectedException");
}
catch (InvalidOperationException e) // When client sent invalid response
{
Console.WriteLine("Result: InvalidOperationException");
}
catch (ClientErrorException e) // When client on purpose rejected task
{
Console.WriteLine("Result: ClientErrorException");
}
catch (OperationCanceledException e) // Exceptin from cts from above
{
Console.WriteLine("Result: OperationCanceledException");
}
finally
{
this.chatBox.OutputTo(player, "Task completed");
}

});
}

private void AddCommand(string command, Action<Player> callback)
Expand All @@ -92,4 +129,19 @@ private void AddCommand(string command, Action<Player> callback)
callback(e.Player);
};
}

private void AddCommand(string command, Func<Player, Task> callback)
{
this.commandService.AddCommand(command).Triggered += async (object? sender, Server.Events.CommandTriggeredEventArgs e) =>
{
try
{
await callback(e.Player);
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
};
}
}
131 changes: 131 additions & 0 deletions SlipeServer.Server/Concepts/ClientTask.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using SlipeServer.Packets.Definitions.Lua;
using SlipeServer.Server.Elements;
using SlipeServer.Server.Exceptions;
using SlipeServer.Server.Resources;
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace SlipeServer.Server.Concepts;

public static class ResourceExtensions
{
public static Resource AddClientTaskHelper(this Resource resource)
{
resource.NoClientScripts["clientTaskHelper.lua"] = System.Text.UTF8Encoding.UTF8.GetBytes(ClientTask.luaHelperCode);

return resource;
}
}

public sealed class ClientErrorException : Exception
{
public ClientErrorException(string? message) : base(message)
{

}
}

public sealed class ClientTask : LuaValue, IDisposable
{
public const string luaHelperCode = """
ClientTask = {
Resolve = function(clientTask, ...)
if(clientTask._completed)then
error("ClientTask already completed");
end
clientTask._completed = true;
triggerServerEvent("clientTask_"..clientTask._id, localPlayer, "success", ...)
end,
Reject = function(clientTask, ...)
if(clientTask._completed)then
error("ClientTask already completed");
end
clientTask._completed = true;
triggerServerEvent("clientTask_"..clientTask._id, localPlayer, "error", ...)
end,
}
""";

private readonly TaskCompletionSource taskCompletionSource;
private readonly string eventName;
public MtaServer MtaServer { get; }
public Player Player { get; }
public string Id { get; }

internal ClientTask(MtaServer mtaServer, Player player, string id, CancellationToken cancellationToken) : base(new LuaTable
{
["_id"] = id
})
{
this.taskCompletionSource = new TaskCompletionSource();
this.MtaServer = mtaServer;
this.Player = player;
this.Id = id;
this.eventName = $"clientTask_{this.Id}";
this.MtaServer.LuaEventTriggered += HandleLuaEventTriggered;
this.Player.Disconnected += HandleDisconnected;
cancellationToken.Register(() =>
{
this.taskCompletionSource.TrySetCanceled();
});
}

private void HandleDisconnected(Player sender, Elements.Events.PlayerQuitEventArgs e)
{
this.taskCompletionSource.TrySetException(new PlayerDisconnectedException(sender));
Dispose();
}

private void HandleLuaEventTriggered(Events.LuaEvent luaEvent)
{
if (luaEvent.Name != this.eventName || luaEvent.Player != this.Player)
return;

try
{
var result = luaEvent.Parameters[0].StringValue;

if (result == "success")
{
this.taskCompletionSource.TrySetResult();
}
else if (result == "error")
{
if(luaEvent.Parameters.Length >= 2)
{
this.taskCompletionSource.TrySetException(new ClientErrorException(luaEvent.Parameters[1].StringValue));
} else
{
this.taskCompletionSource.TrySetException(new ClientErrorException(null));
}
}
else
{
this.taskCompletionSource.TrySetException(new InvalidOperationException());
}
}
catch (Exception ex)
{
this.taskCompletionSource.TrySetException(ex);
}
finally
{
Dispose();
}
}

public void Dispose()
{
this.MtaServer.LuaEventTriggered -= HandleLuaEventTriggered;
this.Player.Disconnected -= HandleDisconnected;
this.taskCompletionSource.TrySetException(new ObjectDisposedException(nameof(ClientTask)));
}

public TaskAwaiter GetAwaiter()
{
return this.taskCompletionSource.Task.GetAwaiter();
}
}
14 changes: 14 additions & 0 deletions SlipeServer.Server/Exceptions/PlayerDisconnectedException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using SlipeServer.Server.Elements;
using System;

namespace SlipeServer.Server.Exceptions;

public sealed class PlayerDisconnectedException : Exception
{
public Player Player { get; }

public PlayerDisconnectedException(Player player)
{
this.Player = player;
}
}
13 changes: 13 additions & 0 deletions SlipeServer.Server/MtaServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using SlipeServer.Server.AllSeeingEye;
using SlipeServer.Server.Bans;
using SlipeServer.Server.Clients;
using SlipeServer.Server.Concepts;
using SlipeServer.Server.ElementCollections;
using SlipeServer.Server.Elements;
using SlipeServer.Server.Elements.IdGeneration;
Expand All @@ -32,6 +33,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;

namespace SlipeServer.Server;

Expand Down Expand Up @@ -678,6 +680,17 @@ public static MtaServer Create(IServiceProvider serviceProvider, Action<ServerBu
public static MtaServer<TPlayer> CreateWithDiSupport<TPlayer>(Action<ServerBuilder> builderAction) where TPlayer : Player
=> new MtaDiPlayerServer<TPlayer>(builderAction);

/// <summary>
/// Creates
/// </summary>
/// <param name="player"></param>
/// <returns></returns>
public ClientTask CreateClientTask(Player player, CancellationToken cancellationToken = default)
{
var id = Guid.NewGuid().ToString();
return new ClientTask(this, player, id, cancellationToken);
}

/// <summary>
/// Triggered when any element is created on the server through the .AssociateElement method
/// </summary>
Expand Down