< Summary

Class:EmotesCatalogService
Assembly:EmotesCatalog
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/DCLServices/EmotesCatalog/EmotesCatalogService.cs
Covered lines:78
Uncovered lines:43
Coverable lines:121
Total lines:245
Line coverage:64.4% (78 of 121)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
EmotesCatalogService(...)0%110100%
Initialize()0%110100%
OnEmotesReceived(...)0%660100%
OnEmoteRejected(...)0%12300%
OnOwnedEmotesReceived(...)0%42600%
EmbedEmotes(...)0%220100%
TryGetLoadedEmote(...)0%2100%
RequestOwnedEmotes(...)0%12300%
RequestOwnedEmotesAsync()0%56700%
RequestEmote(...)0%550100%
RequestEmotes(...)0%220100%
RequestEmoteAsync()0%990100%
RequestEmotesAsync()0%440100%
ForgetEmote(...)0%440100%
ForgetEmotes(...)0%6200%
Dispose()0%110100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/DCLServices/EmotesCatalog/EmotesCatalogService.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Threading;
 4using Cysharp.Threading.Tasks;
 5using DCL.Helpers;
 6
 7public class EmotesCatalogService : IEmotesCatalogService
 8{
 9    private readonly IEmotesCatalogBridge bridge;
 10
 4311    internal readonly Dictionary<string, WearableItem> emotes = new Dictionary<string, WearableItem>();
 4312    internal readonly Dictionary<string, HashSet<Promise<WearableItem>>> promises = new Dictionary<string, HashSet<Promi
 4313    internal readonly Dictionary<string, int> emotesOnUse = new Dictionary<string, int>();
 4314    internal readonly Dictionary<string, HashSet<Promise<WearableItem[]>>> ownedEmotesPromisesByUser = new Dictionary<st
 15
 4316    public EmotesCatalogService(IEmotesCatalogBridge bridge, WearableItem[] embeddedEmotes)
 17    {
 4318        this.bridge = bridge;
 4319        EmbedEmotes(embeddedEmotes);
 4320    }
 21
 22    public void Initialize()
 23    {
 4224        bridge.OnEmotesReceived += OnEmotesReceived;
 4225        bridge.OnEmoteRejected += OnEmoteRejected;
 4226        bridge.OnOwnedEmotesReceived += OnOwnedEmotesReceived;
 4227    }
 28
 29    private void OnEmotesReceived(WearableItem[] receivedEmotes)
 30    {
 2431        for (var i = 0; i < receivedEmotes.Length; i++)
 32        {
 633            WearableItem emote = receivedEmotes[i];
 34
 635            if (!emotesOnUse.ContainsKey(emote.id) || emotesOnUse[emote.id] <= 0)
 36                continue;
 37
 538            emotes[emote.id] = emote;
 39
 540            if (promises.TryGetValue(emote.id, out var emotePromises))
 41            {
 2242                foreach (Promise<WearableItem> promise in emotePromises)
 43                {
 644                    promise.Resolve(emote);
 45                }
 46
 547                promises.Remove(emote.id);
 48            }
 49        }
 650    }
 51
 52    private void OnEmoteRejected(string emoteId, string errorMessage)
 53    {
 054        if (promises.TryGetValue(emoteId, out var setOfPromises))
 55        {
 056            foreach(var promise in setOfPromises)
 057                promise.Reject(errorMessage);
 58
 059            promises.Remove(emoteId);
 060            emotesOnUse.Remove(emoteId);
 061            emotes.Remove(emoteId);
 62        }
 063    }
 64
 65    private void OnOwnedEmotesReceived(WearableItem[] receivedEmotes, string userId)
 66    {
 067        if (!ownedEmotesPromisesByUser.TryGetValue(userId, out HashSet<Promise<WearableItem[]>> ownedEmotesPromises) || 
 068            ownedEmotesPromises = new HashSet<Promise<WearableItem[]>>();
 69
 70        //Update emotes on use
 071        for (var i = 0; i < receivedEmotes.Length; i++)
 72        {
 073            var emote = receivedEmotes[i];
 074            if (!emotesOnUse.ContainsKey(emote.id))
 075                emotesOnUse[emote.id] = 0;
 076            emotesOnUse[emote.id] += ownedEmotesPromises.Count;
 77        }
 78
 079        OnEmotesReceived(receivedEmotes);
 80
 81        //Resolve ownedEmotesPromise
 082        ownedEmotesPromisesByUser.Remove(userId);
 083        foreach (Promise<WearableItem[]> promise in ownedEmotesPromises)
 84        {
 085            promise.Resolve(receivedEmotes);
 86        }
 087    }
 88
 89    private void EmbedEmotes(WearableItem[] embeddedEmotes)
 90    {
 133291        foreach (WearableItem embeddedEmote in embeddedEmotes)
 92        {
 62393            emotes[embeddedEmote.id] = embeddedEmote;
 62394            emotesOnUse[embeddedEmote.id] = 5000;
 95        }
 4396    }
 97
 098    public bool TryGetLoadedEmote(string id, out WearableItem emote) { return emotes.TryGetValue(id, out emote); }
 99
 100    public Promise<WearableItem[]> RequestOwnedEmotes(string userId)
 101    {
 0102        var promise = new Promise<WearableItem[]>();
 0103        if (!ownedEmotesPromisesByUser.ContainsKey(userId) || ownedEmotesPromisesByUser[userId] == null)
 0104            ownedEmotesPromisesByUser[userId] = new HashSet<Promise<WearableItem[]>>();
 0105        ownedEmotesPromisesByUser[userId].Add(promise);
 0106        bridge.RequestOwnedEmotes(userId);
 107
 0108        return promise;
 109    }
 110
 111    public async UniTask<WearableItem[]> RequestOwnedEmotesAsync(string userId, CancellationToken ct = default)
 112    {
 113        const int TIMEOUT = 60;
 0114        CancellationTokenSource timeoutCTS = new CancellationTokenSource();
 0115        var timeout = timeoutCTS.CancelAfterSlim(TimeSpan.FromSeconds(TIMEOUT));
 0116        var promise = RequestOwnedEmotes(userId);
 117        try
 118        {
 0119            ct.ThrowIfCancellationRequested();
 0120            var linkedCt = CancellationTokenSource.CreateLinkedTokenSource(ct, timeoutCTS.Token);
 0121            await promise.WithCancellation(linkedCt.Token);
 0122        }
 0123        catch (OperationCanceledException e)
 124        {
 0125            return null;
 126        }
 127        finally
 128        {
 0129            timeout?.Dispose();
 0130            timeoutCTS?.Dispose();
 131        }
 132
 0133        return promise.value;
 0134    }
 135
 136    public Promise<WearableItem> RequestEmote(string id)
 137    {
 42138        var promise = new Promise<WearableItem>();
 42139        if (!emotesOnUse.ContainsKey(id))
 21140            emotesOnUse[id] = 0;
 42141        emotesOnUse[id]++;
 42142        if (emotes.TryGetValue(id, out var emote))
 143        {
 3144            promise.Resolve(emote);
 3145            return promise;
 146        }
 147
 39148        if (!promises.ContainsKey(id) || promises[id] == null)
 21149            promises[id] = new HashSet<Promise<WearableItem>>();
 39150        promises[id].Add(promise);
 39151        bridge.RequestEmote(id);
 39152        return promise;
 153    }
 154
 155    public List<Promise<WearableItem>> RequestEmotes(IList<string> ids)
 156    {
 6157        List<Promise<WearableItem>> requestedPromises = new List<Promise<WearableItem>>(ids.Count);
 158
 38159        for (int i = 0; i < ids.Count; i++)
 160        {
 13161            string id = ids[i];
 13162            requestedPromises.Add(RequestEmote(id));
 163        }
 164
 6165        return requestedPromises;
 166    }
 167
 168    public async UniTask<WearableItem> RequestEmoteAsync(string id, CancellationToken ct = default)
 169    {
 170        const int TIMEOUT = 45;
 19171        CancellationTokenSource timeoutCTS = new CancellationTokenSource();
 19172        var timeout = timeoutCTS.CancelAfterSlim(TimeSpan.FromSeconds(TIMEOUT));
 19173        ct.ThrowIfCancellationRequested();
 19174        Promise<WearableItem> promise = RequestEmote(id);
 175        try
 176        {
 19177            var linkedCt = CancellationTokenSource.CreateLinkedTokenSource(ct, timeoutCTS.Token);
 51178            await promise.WithCancellation(linkedCt.Token);
 3179        }
 16180        catch (OperationCanceledException ex)
 181        {
 16182            if (promises.ContainsKey(id))
 183            {
 16184                promises[id].Remove(promise);
 16185                if (promises[id].Count == 0)
 6186                    promises.Remove(id);
 187            }
 188
 16189            return null;
 190        }
 191        finally
 192        {
 19193            timeout?.Dispose();
 19194            timeoutCTS?.Dispose();
 195        }
 196
 3197        return promise.value;
 19198    }
 199
 200    public async UniTask<WearableItem[]> RequestEmotesAsync(IList<string> ids, CancellationToken ct = default)
 201    {
 6202        ct.ThrowIfCancellationRequested();
 203        try
 204        {
 18205            var tasks = ids.Select(x => RequestEmoteAsync(x, ct));
 16206            return await UniTask.WhenAll(tasks).AttachExternalCancellation(ct);
 207        }
 5208        catch (OperationCanceledException e)
 209        {
 5210            return null;
 211        }
 6212    }
 213
 214    public void ForgetEmote(string id)
 215    {
 3216        if (emotesOnUse.ContainsKey(id))
 217        {
 2218            emotesOnUse[id]--;
 2219            if (emotesOnUse[id] > 0) //We are still using this emote
 1220                return;
 1221            emotesOnUse.Remove(id);
 222        }
 223
 2224        if (!emotes.TryGetValue(id, out WearableItem emote))
 1225            return;
 226
 1227        emotes.Remove(id);
 1228    }
 229
 230    public void ForgetEmotes(IList<string> ids)
 231    {
 0232        for (int i = 0; i < ids.Count; i++)
 233        {
 0234            string id = ids[i];
 0235            ForgetEmote(id);
 236        }
 0237    }
 238
 239    public void Dispose()
 240    {
 20241        bridge.OnEmotesReceived -= OnEmotesReceived;
 20242        bridge.OnEmoteRejected -= OnEmoteRejected;
 20243        bridge.OnOwnedEmotesReceived -= OnOwnedEmotesReceived;
 20244    }
 245}