-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathLoadElementsByIntentionSystem.cs
More file actions
184 lines (150 loc) · 8.56 KB
/
LoadElementsByIntentionSystem.cs
File metadata and controls
184 lines (150 loc) · 8.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
using Arch.Core;
using CommunicationData.URLHelpers;
using Cysharp.Threading.Tasks;
using DCL.AvatarRendering.Loading.Components;
using DCL.AvatarRendering.Loading.DTO;
using DCL.AvatarRendering.Wearables.Components;
using DCL.Ipfs;
using DCL.WebRequests;
using ECS;
using ECS.Prioritization.Components;
using ECS.StreamableLoading.AssetBundles;
using ECS.StreamableLoading.Cache;
using ECS.StreamableLoading.Common.Components;
using ECS.StreamableLoading.Common.Systems;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Threading;
using DCL.AvatarRendering.Emotes;
using DCL.Diagnostics;
using Utility.Multithreading;
namespace DCL.AvatarRendering.Loading.Systems.Abstract
{
public abstract class LoadElementsByIntentionSystem<TAsset, TIntention, TAvatarElement, TAvatarElementDTO> :
LoadSystemBase<TAsset, TIntention>
where TIntention: struct, IAttachmentsLoadingIntention<TAvatarElement>, IEquatable<TIntention>
where TAvatarElementDTO : AvatarAttachmentDTO
where TAvatarElement : IAvatarAttachment<TAvatarElementDTO>
{
private readonly IAvatarElementStorage<TAvatarElement, TAvatarElementDTO> avatarElementStorage;
private readonly IWebRequestController webRequestController;
private readonly string? builderContentURL;
private readonly string? expectedBuilderItemType;
protected readonly IRealmData realmData;
protected LoadElementsByIntentionSystem(
World world,
IStreamableCache<TAsset, TIntention> cache,
IAvatarElementStorage<TAvatarElement, TAvatarElementDTO> avatarElementStorage,
IWebRequestController webRequestController,
IRealmData realmData,
string? builderContentURL = null,
string? expectedBuilderItemType = null
) : base(world, cache)
{
this.avatarElementStorage = avatarElementStorage;
this.webRequestController = webRequestController;
this.realmData = realmData;
this.builderContentURL = builderContentURL;
this.expectedBuilderItemType = expectedBuilderItemType;
}
protected sealed override async UniTask<StreamableLoadingResult<TAsset>> FlowInternalAsync(TIntention intention,
StreamableLoadingState state, IPartitionComponent partition, CancellationToken ct)
{
await realmData.WaitConfiguredAsync();
URLAddress url = BuildUrlFromIntention(in intention);
if (intention.NeedsBuilderAPISigning)
{
IBuilderLambdaResponse<IBuilderLambdaResponseElement<TAvatarElementDTO>>? lambdaResponse =
await ParseBuilderResponseAsync(
webRequestController.SignedFetchGetAsync(
new CommonArguments(url), string.Empty, ct)
);
await using (await ExecuteOnThreadPoolScope.NewScopeWithReturnOnMainThreadAsync())
LoadBuilderItem(ref intention, lambdaResponse);
}
else
{
IAttachmentLambdaResponse<ILambdaResponseElement<TAvatarElementDTO>>? lambdaResponse =
await ParseResponseAsync(
webRequestController.GetAsync(
new CommonArguments(url),
ct,
GetReportCategory()
)
);
await using (await ExecuteOnThreadPoolScope.NewScopeWithReturnOnMainThreadAsync())
{
//TODO (JUANI): This complexity can go away once the fallback helper is no longer needed; returning to the LOAD method that was here before
intention.SetTotal(lambdaResponse.TotalAmount);
// Process elements in parallel for better performance
IReadOnlyList<ILambdaResponseElement<TAvatarElementDTO>> pageElements = lambdaResponse.Page;
var elementTasks = new UniTask<TAvatarElement>[pageElements.Count];
for (var i = 0; i < pageElements.Count; i++)
{
ILambdaResponseElement<TAvatarElementDTO>? element = pageElements[i];
elementTasks[i] = ProcessElementAsync(element, partition, ct);
}
// Wait for all elements to be processed and add results to intention
TAvatarElement[]? processedWearables = await UniTask.WhenAll(elementTasks);
for (var i = 0; i < processedWearables.Length; i++) { intention.AppendToResult(processedWearables[i]); }
}
}
return new StreamableLoadingResult<TAsset>(AssetFromPreparedIntention(in intention));
}
private async UniTask<TAvatarElement> ProcessElementAsync(ILambdaResponseElement<TAvatarElementDTO> element, IPartitionComponent partition, CancellationToken ct)
{
TAvatarElementDTO elementDTO = element.Entity;
var avatarElement = avatarElementStorage.GetOrAddByDTO(elementDTO);
// Run the asset bundle fallback check in parallel
await AssetBundleManifestFallbackHelper.CheckAssetBundleManifestFallbackAsync(World, avatarElement.DTO, partition, ct);
// Process individual data (this part needs to remain sequential per element for thread safety)
// Note: We use API's amount directly for display, registry is only for token ID tracking
foreach (ElementIndividualDataDto individualData in element.IndividualData)
{
// Probably a base wearable, wrongly return individual data. Skip it
if (elementDTO.Metadata.id == individualData.id) continue;
long.TryParse(individualData.transferredAt, out long transferredAt);
decimal.TryParse(individualData.price, out decimal price);
avatarElementStorage.SetOwnedNft(
elementDTO.Metadata.id,
new NftBlockchainOperationEntry(
individualData.id,
individualData.tokenId,
DateTimeOffset.FromUnixTimeSeconds(transferredAt).DateTime,
price
)
);
ReportHub.Log(ReportCategory.OUTFITS, $"<color=green>[WEARABLE_STORAGE_POPULATED]</color> Key: '{elementDTO.Metadata.id}' now maps to Value: '{individualData.id}' (Token: {individualData.tokenId})");
}
int ownedAmount = avatarElementStorage.GetOwnedNftCount(elementDTO.Metadata.id);
avatarElement.SetAmount(ownedAmount);
return avatarElement;
}
private void LoadBuilderItem(ref TIntention intention, IBuilderLambdaResponse<IBuilderLambdaResponseElement<TAvatarElementDTO>> lambdaResponse)
{
if (string.IsNullOrEmpty(builderContentURL)) return;
if (lambdaResponse.CollectionElements is { Count: > 0 })
{
var totalCount = 0;
foreach (IBuilderLambdaResponseElement<TAvatarElementDTO>? element in lambdaResponse.CollectionElements)
{
TAvatarElementDTO elementDTO = element.BuildElementDTO(builderContentURL);
if (!string.IsNullOrEmpty(expectedBuilderItemType) && elementDTO.type != expectedBuilderItemType)
continue;
TAvatarElement avatarElement = avatarElementStorage.GetOrAddByDTO(elementDTO, false);
//Builder items will never have an asset bundle
if (avatarElement.DTO.assetBundleManifestVersion == null)
avatarElement.DTO.assetBundleManifestVersion = AssetBundleManifestVersion.CreateLSDAsset();
intention.AppendToResult(avatarElement);
totalCount++;
}
intention.SetTotal(totalCount);
}
}
protected abstract UniTask<IAttachmentLambdaResponse<ILambdaResponseElement<TAvatarElementDTO>>> ParseResponseAsync(GenericDownloadHandlerUtils.Adapter<GenericGetRequest, GenericGetArguments> adapter);
protected abstract UniTask<IBuilderLambdaResponse<IBuilderLambdaResponseElement<TAvatarElementDTO>>> ParseBuilderResponseAsync(GenericDownloadHandlerUtils.Adapter<GenericGetRequest, GenericGetArguments> adapter);
protected abstract TAsset AssetFromPreparedIntention(in TIntention intention);
protected abstract URLAddress BuildUrlFromIntention(in TIntention intention);
}
}