Skip to content

Commit 4b33b4a

Browse files
fix: account for outfits deploy window from catalysts (#7731)
* fix: account for deploy window from catalysts * minor fix --------- Co-authored-by: Ashley Canning <ashley.canning@decentraland.org>
1 parent 8c79b59 commit 4b33b4a

2 files changed

Lines changed: 87 additions & 18 deletions

File tree

Lines changed: 86 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
using Cysharp.Threading.Tasks;
1+
using Cysharp.Threading.Tasks;
22
using DCL.Diagnostics;
3+
using DCL.FeatureFlags;
34
using DCL.Ipfs;
45
using System;
56
using System.Collections.Generic;
@@ -12,6 +13,7 @@
1213
using Newtonsoft.Json;
1314
using Utility;
1415
using Utility.Json;
16+
using Utility.Times;
1517

1618
namespace DCL.Backpack.AvatarSection.Outfits.Repository
1719
{
@@ -26,18 +28,48 @@ public class OutfitsRepository
2628
private static readonly JsonSerializerSettings SERIALIZER_SETTINGS = new ()
2729
{ Converters = new List<JsonConverter> { new ColorJsonConverter() } };
2830

31+
private const int DEFAULT_DEPLOY_WINDOW_IN_SECONDS = 15;
32+
2933
private readonly PublishIpfsEntityCommand publishIpfsEntityCommand;
3034
private readonly INftNamesProvider nftNamesProvider;
3135

36+
private ulong passedTimeSinceLastDeployment = 0;
37+
private ulong lastDeployTimestampInSeconds = 0;
38+
39+
private UniTaskCompletionSource? currentResolutionTask;
40+
private List<OutfitItem>? currentOutfits;
41+
private int currentVersion;
42+
3243
public OutfitsRepository(PublishIpfsEntityCommand publishIpfsEntityCommand,
3344
INftNamesProvider nftNamesProvider)
3445
{
3546
this.publishIpfsEntityCommand = publishIpfsEntityCommand;
3647
this.nftNamesProvider = nftNamesProvider;
3748
}
3849

50+
private static int deployWindowInSeconds
51+
{
52+
get
53+
{
54+
if (FeatureFlagsConfiguration.Instance.TryGetJsonPayload(FeatureFlagsStrings.OUTFITS_DEPLOY_WINDOW,
55+
"deploy_window_in_seconds",
56+
out OutfitsDeployWindowConfig? config) && config.HasValue && config.Value.DeployWindowInSeconds > 0)
57+
return config.Value.DeployWindowInSeconds;
58+
59+
return DEFAULT_DEPLOY_WINDOW_IN_SECONDS;
60+
}
61+
}
62+
63+
[Serializable]
64+
private struct OutfitsDeployWindowConfig
65+
{
66+
[JsonProperty("deploy_window_in_seconds")] public int DeployWindowInSeconds;
67+
}
68+
3969
/// <summary>
4070
/// Deploys the complete set of outfits for a user to the Catalyst network.
71+
/// Multiple rapid calls are coalesced: only the latest state is deployed,
72+
/// after a 15-second delay that respects the backend rate limit.
4173
/// </summary>
4274
public async UniTask SetAsync(Profile? profile, List<OutfitItem> outfits, CancellationToken ct)
4375
{
@@ -47,33 +79,69 @@ public async UniTask SetAsync(Profile? profile, List<OutfitItem> outfits, Cancel
4779
if (string.IsNullOrEmpty(profile?.UserId))
4880
throw new ArgumentException("Cannot save outfits for a user with an empty UserId");
4981

50-
INftNamesProvider.PaginatedNamesResponse namesForExtraSlots = await nftNamesProvider.GetAsync(new Web3Address(profile.UserId), 1, 1, ct);
82+
currentOutfits = outfits;
83+
currentVersion++;
84+
var deployWindow = (ulong)deployWindowInSeconds;
5185

52-
var metadata = new OutfitsMetadata
86+
if (currentResolutionTask != null)
5387
{
54-
outfits = outfits, namesForExtraSlots = namesForExtraSlots.Names.Count > 0
55-
? new List<string>
56-
{
57-
namesForExtraSlots.Names[0],
58-
}
59-
: new List<string>(),
60-
};
88+
await UniTask.WhenAny(currentResolutionTask.Task, UniTask.WaitUntilCanceled(ct));
89+
return;
90+
}
6191

62-
var outfitsEntity = new OutfitsEntity(string.Empty, metadata)
92+
currentResolutionTask = new UniTaskCompletionSource();
93+
94+
try
6395
{
64-
version = OutfitsEntity.DEFAULT_VERSION, pointers = new[]
96+
passedTimeSinceLastDeployment = Math.Clamp((DateTime.UtcNow.UnixTimeAsMilliseconds() / 1000) - lastDeployTimestampInSeconds, 0, deployWindow);
97+
98+
int localVersion;
99+
100+
do
65101
{
66-
$"{profile.UserId}:outfits",
67-
},
68-
timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), type = IpfsRealmEntityType.Outfits.ToEntityString(), content = Array.Empty<ContentDefinition>(),
69-
};
102+
localVersion = currentVersion;
103+
List<OutfitItem> localOutfits = currentOutfits!;
104+
105+
await UniTask.Delay(TimeSpan.FromSeconds(deployWindow - (double)passedTimeSinceLastDeployment), cancellationToken: ct);
106+
107+
INftNamesProvider.PaginatedNamesResponse namesForExtraSlots = await nftNamesProvider.GetAsync(new Web3Address(profile.UserId), 1, 1, ct);
108+
109+
var metadata = new OutfitsMetadata
110+
{
111+
outfits = localOutfits, namesForExtraSlots = namesForExtraSlots.Names.Count > 0
112+
? new List<string>
113+
{
114+
namesForExtraSlots.Names[0],
115+
}
116+
: new List<string>(),
117+
};
70118

71-
try { await publishIpfsEntityCommand.ExecuteAsync(outfitsEntity, ct, SERIALIZER_SETTINGS); }
119+
var outfitsEntity = new OutfitsEntity(string.Empty, metadata)
120+
{
121+
version = OutfitsEntity.DEFAULT_VERSION, pointers = new[]
122+
{
123+
$"{profile.UserId}:outfits",
124+
},
125+
timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), type = IpfsRealmEntityType.Outfits.ToEntityString(), content = Array.Empty<ContentDefinition>(),
126+
};
127+
128+
await publishIpfsEntityCommand.ExecuteAsync(outfitsEntity, ct, SERIALIZER_SETTINGS);
129+
passedTimeSinceLastDeployment = 0;
130+
}
131+
while (localVersion != currentVersion);
132+
133+
currentResolutionTask.TrySetResult();
134+
}
72135
catch (Exception e)
73136
{
74-
ReportHub.LogException(e, ReportCategory.OUTFITS);
137+
currentResolutionTask.TrySetException(e);
75138
throw;
76139
}
140+
finally
141+
{
142+
currentResolutionTask = null;
143+
lastDeployTimestampInSeconds = DateTime.UtcNow.UnixTimeAsMilliseconds() / 1000;
144+
}
77145
}
78146
}
79147
}

Explorer/Assets/DCL/FeatureFlags/FeatureFlagsStrings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public static class FeatureFlagsStrings
4949
public const string MINIMUM_REQUIREMENTS = "alfa-minimum-requirements";
5050
public const string CHAT_TRANSLATION_ENABLED = "alfa-chat-translation";
5151
public const string OUTFITS_ENABLED = "alfa-outfits";
52+
public const string OUTFITS_DEPLOY_WINDOW = "alfa-outfits-deploy-window";
5253
public const string GIFTING_ENABLED = "alfa-gifting";
5354
public const string BANNED_USERS_FROM_SCENE = "alfa-banned-users-from-scene";
5455
public const string CHAT_MESSAGE_BUFFER_CONFIG = "alfa-chat-message-buffer-config";

0 commit comments

Comments
 (0)