Skip to content

Commit

Permalink
JoinLobby and SIHost support
Browse files Browse the repository at this point in the history
  • Loading branch information
VladimirKhil committed Nov 25, 2023
1 parent a036d8a commit 817fcad
Show file tree
Hide file tree
Showing 19 changed files with 379 additions and 71 deletions.
2 changes: 1 addition & 1 deletion src/Common/SI.GameServer.Client/GameServerClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ public async ValueTask DisposeAsync()
}

public Task JoinLobbyAsync(CancellationToken cancellationToken = default) =>
_connection.InvokeAsync("JoinLobby", cancellationToken);
_connection.InvokeAsync("JoinLobby2", Thread.CurrentThread.CurrentUICulture.Name, cancellationToken);

public Task LeaveLobbyAsync(CancellationToken cancellationToken = default) =>
_connection.InvokeAsync("LeaveLobby", cancellationToken);
Expand Down
37 changes: 18 additions & 19 deletions src/Common/SI.GameServer.Client/GameServerClientOptions.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
using System;

namespace SI.GameServer.Client
namespace SI.GameServer.Client;

/// <summary>
/// Provides SIGame server client options.
/// </summary>
public sealed class GameServerClientOptions
{
public const string ConfigurationSectionName = "GameServerClient";

/// <summary>
/// Provides SIGame server client options.
/// SIGame server Uri.
/// </summary>
public sealed class GameServerClientOptions
{
public const string ConfigurationSectionName = "GameServerClient";
public string? ServiceUri { get; set; }

/// <summary>
/// SIGame server Uri.
/// </summary>
public string? ServiceUri { get; set; }

/// <summary>
/// SIGame service discovery Uri.
/// </summary>
public Uri? ServiceDiscoveryUri { get; set; } = new Uri("https://vladimirkhil.com/api/si/servers");
/// <summary>
/// SIGame service discovery Uri.
/// </summary>
public Uri? ServiceDiscoveryUri { get; set; } = new Uri("https://vladimirkhil.com/api/si/servers");

/// <summary>
/// Client timeout.
/// </summary>
public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(6); // Large value for uploading packages
}
/// <summary>
/// Client timeout.
/// </summary>
public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(6); // Large value for uploading packages
}
39 changes: 39 additions & 0 deletions src/Common/SI.GameServer.Client/IGameClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using SIData;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace SI.GameServer.Client;

/// <summary>
/// Defines common SI server client.
/// </summary>
public interface IGameClient : IAsyncDisposable
{
/// <summary>
/// Message received event.
/// </summary>
event Action<Message> IncomingMessage;

/// <summary>
/// Reconnecting event.
/// </summary>
event Func<Exception?, Task> Reconnecting;

/// <summary>
/// Reconnected event.
/// </summary>
event Func<string?, Task> Reconnected;

/// <summary>
/// Connection closed event.
/// </summary>
event Func<Exception?, Task> Closed;

/// <summary>
/// Sends message to server.
/// </summary>
/// <param name="message">Message to send.</param>
/// <param name="cancellationToken">Cancellation token.</param>
Task SendMessageAsync(Message message, CancellationToken cancellationToken = default);
}
20 changes: 13 additions & 7 deletions src/Common/SI.GameServer.Client/IGameServerClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace SI.GameServer.Client;
/// <summary>
/// Provides a client to SIGame server.
/// </summary>
public interface IGameServerClient : IAsyncDisposable
public interface IGameServerClient : IGameClient
{
string ServiceUri { get; }

Expand All @@ -22,13 +22,7 @@ public interface IGameServerClient : IAsyncDisposable
event Action<string> Leaved;
event Action<string, string> Receieve;

event Func<Exception?, Task> Reconnecting;
event Func<string?, Task> Reconnected;

event Func<Exception?, Task> Closed;

event Action<int> UploadProgress;
event Action<Message> IncomingMessage;

Task OpenAsync(string userName, CancellationToken token = default);

Expand All @@ -38,6 +32,18 @@ public interface IGameServerClient : IAsyncDisposable
/// <param name="cancellationToken">Cancellation token.</param>
Task<HostInfo> GetGamesHostInfoAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Joins game lobby.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
Task JoinLobbyAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Leaves game lobby.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
Task LeaveLobbyAsync(CancellationToken cancellationToken = default);

Task<string> GetNewsAsync(CancellationToken cancellationToken = default);

Task<string[]> GetUsersAsync(CancellationToken cancellationToken = default);
Expand Down
100 changes: 100 additions & 0 deletions src/Common/SI.GameServer.Client/SIHostClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;
using SI.GameServer.Client.Properties;
using SI.GameServer.Contract;
using SIData;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace SI.GameServer.Client;

/// <summary>
/// Defines SIHost client.
/// </summary>
public sealed class SIHostClient : IGameClient
{
private readonly HubConnection _connection;
private readonly SIHostClientOptions _options;

public event Action<Message>? IncomingMessage;

public event Func<Exception?, Task>? Reconnecting;
public event Func<string?, Task>? Reconnected;
public event Func<Exception?, Task>? Closed;

public SIHostClient(HubConnection connection, SIHostClientOptions options)
{
_connection = connection;
_options = options;

_connection.Reconnecting += async e =>
{
if (Reconnecting != null)
{
await Reconnecting(e);
}
};

_connection.Reconnected += async s =>
{
if (Reconnected != null)
{
await Reconnected(s);
}
};

_connection.Closed += OnConnectionClosedAsync;

_connection.On<Message>("Receive", (message) => IncomingMessage?.Invoke(message));

_connection.On("Disconnect", async () =>
{
IncomingMessage?.Invoke(new Message(Resources.YourWereKicked, "@", isSystem: false));

await _connection.StopAsync();
});
}

public static async Task<SIHostClient> CreateAsync(SIHostClientOptions options, CancellationToken cancellationToken)
{
var serviceUri = options.ServiceUri?.ToString() ?? "";

if (!serviceUri.EndsWith('/'))
{
serviceUri += "/";
}

var connection = new HubConnectionBuilder()
.WithUrl($"{serviceUri}sihost")
.WithAutomaticReconnect()
.AddMessagePackProtocol()
.Build();

connection.HandshakeTimeout = options.HandshakeTimeout;

await connection.StartAsync(cancellationToken);

return new SIHostClient(connection, options);
}

public Task<JoinGameResponse> JoinGameAsync(
JoinGameRequest joinGameRequest,
CancellationToken cancellationToken = default) =>
_connection.InvokeAsync<JoinGameResponse>("JoinGame", joinGameRequest, cancellationToken);

public Task SendMessageAsync(Message message, CancellationToken cancellationToken = default) =>
_connection.InvokeAsync("SendMessage", message, cancellationToken);

public Task LeaveGameAsync(CancellationToken cancellationToken = default) =>
_connection.InvokeAsync("LeaveGame", cancellationToken);

private Task OnConnectionClosedAsync(Exception? exc)
{
// TODO: recreate connection and retry

return Closed != null ? Closed(exc) : Task.CompletedTask;
}

public ValueTask DisposeAsync() => _connection.DisposeAsync();
}
19 changes: 19 additions & 0 deletions src/Common/SI.GameServer.Client/SIHostClientOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;

namespace SI.GameServer.Client;

/// <summary>
/// Defines SIHost client options.
/// </summary>
public sealed class SIHostClientOptions
{
/// <summary>
/// SIHost service Uri.
/// </summary>
public Uri? ServiceUri { get; set; }

/// <summary>
/// Initial handshake timeout.
/// </summary>
public TimeSpan HandshakeTimeout { get; set; } = TimeSpan.FromMinutes(2);
}
5 changes: 5 additions & 0 deletions src/Common/SI.GameServer.Contract/HostInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,9 @@ public sealed class HostInfo
/// Maximum allowed package size in MB.
/// </summary>
public int MaxPackageSizeMb { get; set; } = 100;

/// <summary>
/// Base uri for game links.
/// </summary>
public Uri? BaseGameLinkUri { get; set; }
}
2 changes: 1 addition & 1 deletion src/SICore/SICore.Network/Contracts/INode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace SICore.Network.Contracts;

/// <summary>
/// Represent a SI network node. Nodes could be connected to each other.
/// Represents a SI network node. Nodes could be connected to each other.
/// Clients connect to some node and communicate with other client via it.
/// </summary>
public interface INode : IAsyncDisposable
Expand Down
17 changes: 3 additions & 14 deletions src/SICore/SICore/Clients/Game/GameLogic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,8 @@ private void Engine_QuestionPostInfo()
_data.AllowAppellation = _data.Settings.AppSettings.UseApellations;
_data.IsPlayingMedia = false;

_gameActions.SendMessageWithArgs(Messages.QuestionEnd);

ScheduleExecution(Tasks.QuestSourComm, 10, 1, force: true);
}

Expand Down Expand Up @@ -4470,20 +4472,7 @@ internal void OnComplexAnswer()

internal void OnRightAnswerOption(string rightOptionLabel)
{
var rightIndex = ClientData.QuestionPlayState.AnswerOptions != null
? Array.FindIndex(ClientData.QuestionPlayState.AnswerOptions, o => o.Label == rightOptionLabel)
: -1;

if (rightIndex > -1)
{
_gameActions.SendMessageWithArgs(Messages.RightAnswer, ContentTypes.Text, rightIndex);
}
else
{
var printedAnswer = $"{LO[nameof(R.RightAnswer)]}: {rightOptionLabel}";
_gameActions.ShowmanReplic(printedAnswer);
}

_gameActions.SendMessageWithArgs(Messages.RightAnswer, ContentTypes.Text, rightOptionLabel);
var answerTime = _data.Settings.AppSettings.TimeSettings.TimeForRightAnswer;
ScheduleExecution(Tasks.MoveNext, (answerTime == 0 ? 2 : answerTime) * 10);
}
Expand Down
14 changes: 11 additions & 3 deletions src/SICore/SICore/Clients/Viewer/ViewerHumanLogic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1076,14 +1076,22 @@ public void OnRightAnswer(string answer)
{
var options = TInfo.AnswerOptions.Options;

if (!int.TryParse(answer, out var answerIndex))
if (options == null)
{
answerIndex = -1;
return;
}

var rightIndex = Array.FindIndex(options, o => o.Label == answer);

if (rightIndex == -1)
{
OnReplic(ReplicCodes.Showman.ToString(), $"{_localizer[nameof(R.RightAnswer)]}: {answer}");
return;
}

for (int i = 0; i < options.Length; i++)
{
if (i == answerIndex)
if (i == rightIndex)
{
options[i].State = ItemState.Right;
}
Expand Down
5 changes: 5 additions & 0 deletions src/SICore/SICore/Messages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,11 @@ public static class Messages
/// </summary>
public const string QuestionCaption = "QUESTIONCAPTION";

/// <summary>
/// Question has ended.
/// </summary>
public const string QuestionEnd = "QUESTION_END";

/// <summary>
/// Реплика игры/участника
/// </summary>
Expand Down
26 changes: 13 additions & 13 deletions src/SIGame/SIGame.ViewModel/Implementation/GameServerConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ namespace SIGame.ViewModel.Implementation;

internal sealed class GameServerConnection : ConnectionBase
{
private readonly IGameServerClient _gameServerClient;
private readonly IGameClient _gameClient;
private bool _isDisposed;

public GameServerConnection(IGameServerClient gameServerClient)
public GameServerConnection(IGameClient gameClient)
{
_gameServerClient = gameServerClient;
_gameServerClient.IncomingMessage += OnMessageReceived;
_gameServerClient.Reconnecting += GameServerClient_Reconnecting;
_gameServerClient.Reconnected += GameServerClient_Reconnected;
_gameServerClient.Closed += GameServerClient_Closed;
_gameClient = gameClient;
_gameClient.IncomingMessage += OnMessageReceived;
_gameClient.Reconnecting += GameServerClient_Reconnecting;
_gameClient.Reconnected += GameServerClient_Reconnected;
_gameClient.Closed += GameServerClient_Closed;
}

private Task GameServerClient_Closed(Exception? arg)
Expand Down Expand Up @@ -49,7 +49,7 @@ public override ValueTask SendMessageAsync(Message m)

try
{
return new ValueTask(_gameServerClient.SendMessageAsync(m));
return new ValueTask(_gameClient.SendMessageAsync(m));
}
catch (TaskCanceledException exc)
{
Expand All @@ -69,13 +69,13 @@ public override ValueTask SendMessageAsync(Message m)

protected override ValueTask DisposeAsync(bool disposing)
{
_gameServerClient.IncomingMessage -= OnMessageReceived;
_gameServerClient.Reconnecting -= GameServerClient_Reconnecting;
_gameServerClient.Reconnected -= GameServerClient_Reconnected;
_gameServerClient.Closed -= GameServerClient_Closed;
_gameClient.IncomingMessage -= OnMessageReceived;
_gameClient.Reconnecting -= GameServerClient_Reconnecting;
_gameClient.Reconnected -= GameServerClient_Reconnected;
_gameClient.Closed -= GameServerClient_Closed;

_isDisposed = true;

return _gameServerClient.DisposeAsync();
return _gameClient.DisposeAsync();
}
}
Loading

0 comments on commit 817fcad

Please sign in to comment.