mikaeldui / riot-games-dotnet-client

An unofficial .NET Client for Riot Games and their games League of Legends, Legends of Runeterra, Teamfight Tactics and Valorant.
The Unlicense
22 stars 1 forks source link

LCU messages as events #63

Closed mikaeldui closed 2 years ago

mikaeldui commented 2 years ago

The most idiomatic way to consume LCU event messages would probably be trough event though they might be too advanced so a backup solution by still providing ReceiveAsync?

Current working code:

internal delegate void LeagueClientEventHandler<in T>(LcuClient sender, T args);

internal class LcuClient : LeagueClient
{
    private readonly ConcurrentDictionary<string, Action<JsonElement>> _subscriptions = new();
    private readonly SemaphoreSlim _connectionLock = new(1, 1);
    private readonly CancellationTokenSource _cancellationTokenSource = new();

    private readonly JsonSerializerOptions _jsonSerializerOptions = new() {PropertyNameCaseInsensitive = true};

    protected void Subscribe<T>(string topic, Action<T> handler) => Task.Run(async () =>
    {
        await _connectionLock.WaitAsync();
        try
        {
            if (_subscriptions.IsEmpty) // Then the connection is closed.
            {
                _cancellationTokenSource.TryReset();
                await WampClient.ConnectAsync(); // TODO: CancellationToken

                _ = Task.Run(async () =>
                {
                    while (!_cancellationTokenSource.IsCancellationRequested)
                    {
                        var response = await this.WampClient.ReceiveAsync(_cancellationTokenSource.Token);
                        if (!_subscriptions.TryGetValue(response.Topic, out var subscriber)) continue;
                        subscriber?.Invoke(response.Data);
                    }
                }, _cancellationTokenSource.Token).ConfigureAwait(false);
            }
        }
        finally
        {
            _connectionLock.Release();
        }

        if (_subscriptions.TryAdd(topic,
                data => handler.Invoke(data.Deserialize<T>(_jsonSerializerOptions) ??
                                       throw new LeagueClientException("Couldn't deserialize the event payload."))))
            await WampClient.SubscribeAsync(topic);
    }).ConfigureAwait(false);

    protected void Unsubscribe(string topic) => Task.Run(async () =>
    {
        await WampClient.UnsubscribeAsync("OnJsonApiEvent_lol-champ-select_v1_session");
        _subscriptions.Remove(topic, out _);

        await _connectionLock.WaitAsync();
        try
        {
            if (_subscriptions.IsEmpty) // Then the connection should be closed.
            {
                _cancellationTokenSource.Cancel();
                await WampClient.CloseAsync();
            }
        }
        finally
        {
            _connectionLock.Release();
        }
    }).ConfigureAwait(false);

    private event LeagueClientEventHandler<LolChampSelectChampSelectSession>? _onChampSelectSessionChanged;

    public event LeagueClientEventHandler<LolChampSelectChampSelectSession>? OnChampSelectSessionChanged
    {
        add
        {
            if (_onChampSelectSessionChanged == null)
                Subscribe("OnJsonApiEvent_lol-champ-select_v1_session",
                    (LolChampSelectChampSelectSession args) => _onChampSelectSessionChanged?.Invoke(this, args));

            _onChampSelectSessionChanged += value;
        }
        remove
        {
            _onChampSelectSessionChanged -= value;

            if (_onChampSelectSessionChanged == null)
                Unsubscribe("OnJsonApiEvent_lol-champ-select_v1_session");
        }
    }
}

with a test app:

Console.WriteLine("Welcome!");

using LcuClient client = new();

void OnClientOnOnChampSelectSessionChanged(LcuClient sender, LolChampSelectChampSelectSession session) => Console.WriteLine(string.Join(", ", new[] {session.MyTeam[0].SummonerId}));

client.OnChampSelectSessionChanged += OnClientOnOnChampSelectSessionChanged;

await Task.Delay(30000);

client.OnChampSelectSessionChanged -= OnClientOnOnChampSelectSessionChanged;

Console.WriteLine("Done!");
Console.ReadKey();