From c2df3f28e4d1f288ffa1d945733ccd3650177124 Mon Sep 17 00:00:00 2001 From: Atralupus Date: Sat, 28 Dec 2024 03:42:05 +0900 Subject: [PATCH 1/2] Restore all rune slot related codes --- Lib9c.Models/Runes/RuneSlot.cs | 12 +- Lib9c.Models/States/RuneSlotState.cs | 2 + ...SlotDocumentTest.JsonSnapshot.verified.txt | 1 + .../Bson/RuneSlotDocumentTest.cs | 19 ++ .../Mimir.MongoDB.Tests.csproj | 4 + Mimir.MongoDB/Bson/RuneSlotDocument.cs | 13 + Mimir.MongoDB/CollectionNames.cs | 1 + .../Repositories/RuneSlotRepository.cs | 40 +++ ...RuneSlot_Returns_CorrectValue.verified.txt | 16 ++ Mimir.Tests/QueryTests/RuneSlotTest.cs | 63 +++++ .../ActionHandler/RuneSlotStateHandler.cs | 260 ++++++++++++++++++ .../RuneSlotCollectionUpdater.cs | 115 ++++++++ .../HostApplicationBuilderExtensions.cs | 27 +- Mimir/GraphQL/Queries/Query.cs | 12 + Mimir/Program.cs | 1 + 15 files changed, 572 insertions(+), 14 deletions(-) create mode 100644 Mimir.MongoDB.Tests/Bson/RuneSlotDocumentTest.JsonSnapshot.verified.txt create mode 100644 Mimir.MongoDB.Tests/Bson/RuneSlotDocumentTest.cs create mode 100644 Mimir.MongoDB/Bson/RuneSlotDocument.cs create mode 100644 Mimir.MongoDB/Repositories/RuneSlotRepository.cs create mode 100644 Mimir.Tests/QueryTests/RuneSlotTest.GraphQL_Query_RuneSlot_Returns_CorrectValue.verified.txt create mode 100644 Mimir.Tests/QueryTests/RuneSlotTest.cs create mode 100644 Mimir.Worker/ActionHandler/RuneSlotStateHandler.cs create mode 100644 Mimir.Worker/CollectionUpdaters/RuneSlotCollectionUpdater.cs diff --git a/Lib9c.Models/Runes/RuneSlot.cs b/Lib9c.Models/Runes/RuneSlot.cs index 6728b9ea..af01f4ec 100644 --- a/Lib9c.Models/Runes/RuneSlot.cs +++ b/Lib9c.Models/Runes/RuneSlot.cs @@ -20,12 +20,14 @@ public record RuneSlot : IBencodable public bool IsLock { get; init; } public int? RuneId { get; init; } + public RuneSlot() { } + public IValue Bencoded { get { - var l = List.Empty - .Add(Index.Serialize()) + var l = List + .Empty.Add(Index.Serialize()) .Add(RuneSlotType.Serialize()) .Add(RuneType.Serialize()) .Add(IsLock.Serialize()); @@ -46,7 +48,8 @@ public RuneSlot(IValue bencoded) throw new UnsupportedArgumentValueException( nameof(bencoded), new[] { ValueKind.List }, - bencoded.Kind); + bencoded.Kind + ); } Index = l[0].ToInteger(); @@ -64,7 +67,8 @@ public RuneSlot( RuneSlotType runeSlotType, RuneType runeType, bool isLock, - int? runeId = null) + int? runeId = null + ) { Index = index; RuneSlotType = runeSlotType; diff --git a/Lib9c.Models/States/RuneSlotState.cs b/Lib9c.Models/States/RuneSlotState.cs index 3374b5e5..2f530519 100644 --- a/Lib9c.Models/States/RuneSlotState.cs +++ b/Lib9c.Models/States/RuneSlotState.cs @@ -24,6 +24,8 @@ public record RuneSlotState : IBencodable public IValue Bencoded => List.Empty.Add(BattleType.Serialize()).Add(new List(Slots.Select(x => x.Bencoded))); + public RuneSlotState() { } + public RuneSlotState(IValue bencoded) { if (bencoded is not List l) diff --git a/Mimir.MongoDB.Tests/Bson/RuneSlotDocumentTest.JsonSnapshot.verified.txt b/Mimir.MongoDB.Tests/Bson/RuneSlotDocumentTest.JsonSnapshot.verified.txt new file mode 100644 index 00000000..2629feff --- /dev/null +++ b/Mimir.MongoDB.Tests/Bson/RuneSlotDocumentTest.JsonSnapshot.verified.txt @@ -0,0 +1 @@ +{"Object":{"BattleType":"Adventure","Slots":[{"Index":0,"RuneSlotType":"Default","RuneType":"Stat","IsLock":false,"RuneId":10012},{"Index":1,"RuneSlotType":"Ncg","RuneType":"Stat","IsLock":true},{"Index":2,"RuneSlotType":"Stake","RuneType":"Stat","IsLock":true},{"Index":3,"RuneSlotType":"Default","RuneType":"Skill","IsLock":false},{"Index":4,"RuneSlotType":"Ncg","RuneType":"Skill","IsLock":true},{"Index":5,"RuneSlotType":"Stake","RuneType":"Skill","IsLock":true},{"Index":6,"RuneSlotType":"Crystal","RuneType":"Stat","IsLock":true},{"Index":7,"RuneSlotType":"Crystal","RuneType":"Skill","IsLock":true}]},"Metadata":{"SchemaVersion":1,"StoredBlockIndex":0}} \ No newline at end of file diff --git a/Mimir.MongoDB.Tests/Bson/RuneSlotDocumentTest.cs b/Mimir.MongoDB.Tests/Bson/RuneSlotDocumentTest.cs new file mode 100644 index 00000000..66e6599d --- /dev/null +++ b/Mimir.MongoDB.Tests/Bson/RuneSlotDocumentTest.cs @@ -0,0 +1,19 @@ +using Lib9c.Models.States; +using Mimir.MongoDB.Bson; +using Mimir.MongoDB.Tests.TestDatas; + +namespace Mimir.MongoDB.Tests.Bson; + +public class RuneSlotDocumentTest +{ + [Fact] + public Task JsonSnapshot() + { + var docs = new RuneSlotDocument( + default, + default, + new RuneSlotState(TestDataHelpers.LoadState("RuneSlotState.bin")) + ); + return Verify(docs.ToJson()); + } +} diff --git a/Mimir.MongoDB.Tests/Mimir.MongoDB.Tests.csproj b/Mimir.MongoDB.Tests/Mimir.MongoDB.Tests.csproj index ca1dfd95..ed78fe28 100644 --- a/Mimir.MongoDB.Tests/Mimir.MongoDB.Tests.csproj +++ b/Mimir.MongoDB.Tests/Mimir.MongoDB.Tests.csproj @@ -34,6 +34,10 @@ PetStateDocumentTest QuestListDocumentTest.cs + + RuneSlotDocumentTest + StakeDocumentTest.cs + StakeDocumentTest WorldBossDocumentTest.cs diff --git a/Mimir.MongoDB/Bson/RuneSlotDocument.cs b/Mimir.MongoDB/Bson/RuneSlotDocument.cs new file mode 100644 index 00000000..5ce4ec66 --- /dev/null +++ b/Mimir.MongoDB/Bson/RuneSlotDocument.cs @@ -0,0 +1,13 @@ +using Lib9c.Models.States; +using Libplanet.Crypto; +using MongoDB.Bson.Serialization.Attributes; +using Newtonsoft.Json; + +namespace Mimir.MongoDB.Bson; + +[BsonIgnoreExtraElements] +public record RuneSlotDocument( + [property: BsonIgnore, JsonIgnore] long StoredBlockIndex, + [property: BsonIgnore, JsonIgnore] Address Address, + RuneSlotState Object +) : MimirBsonDocument(Address.ToHex(), new DocumentMetadata(1, StoredBlockIndex)); diff --git a/Mimir.MongoDB/CollectionNames.cs b/Mimir.MongoDB/CollectionNames.cs index 9187de5c..bafcecc9 100644 --- a/Mimir.MongoDB/CollectionNames.cs +++ b/Mimir.MongoDB/CollectionNames.cs @@ -115,6 +115,7 @@ private static void RegisterCollectionAndStateMappings() CollectionAndStateTypeMappings.Add(typeof(PledgeDocument), "pledge"); CollectionAndStateTypeMappings.Add(typeof(ProductsStateDocument), "products"); CollectionAndStateTypeMappings.Add(typeof(ProductDocument), "product"); + CollectionAndStateTypeMappings.Add(typeof(RuneSlotDocument), "rune_slot"); // CollectionAndStateTypeMappings.Add(typeof(QuestListDocument), "quest_list"); CollectionAndStateTypeMappings.Add(typeof(RaiderStateDocument), "raider"); CollectionAndStateTypeMappings.Add(typeof(SheetDocument), "table_sheet"); diff --git a/Mimir.MongoDB/Repositories/RuneSlotRepository.cs b/Mimir.MongoDB/Repositories/RuneSlotRepository.cs new file mode 100644 index 00000000..c6b07569 --- /dev/null +++ b/Mimir.MongoDB/Repositories/RuneSlotRepository.cs @@ -0,0 +1,40 @@ +using Libplanet.Crypto; +using Mimir.MongoDB.Bson; +using Mimir.MongoDB.Exceptions; +using Mimir.MongoDB.Services; +using MongoDB.Driver; +using Nekoyume.Model.EnumType; + +namespace Mimir.MongoDB.Repositories; + +public interface IRuneSlotRepository +{ + Task GetByAddressAsync(Address avatarAddress, BattleType battleType); +} + +public class RuneSlotRepository(MongoDbService dbService) : IRuneSlotRepository +{ + public async Task GetByAddressAsync( + Address avatarAddress, + BattleType battleType + ) + { + var runeSlotAddress = Nekoyume.Model.State.RuneSlotState.DeriveAddress( + avatarAddress, + battleType + ); + var collectionName = CollectionNames.GetCollectionName(); + var collection = dbService.GetCollection(collectionName); + var filter = Builders.Filter.Eq("Address", runeSlotAddress.ToHex()); + var document = await collection.Find(filter).FirstOrDefaultAsync(); + if (document is null) + { + throw new DocumentNotFoundInMongoCollectionException( + collection.CollectionNamespace.CollectionName, + $"'Address' equals to '{runeSlotAddress.ToHex()}'" + ); + } + + return document; + } +} diff --git a/Mimir.Tests/QueryTests/RuneSlotTest.GraphQL_Query_RuneSlot_Returns_CorrectValue.verified.txt b/Mimir.Tests/QueryTests/RuneSlotTest.GraphQL_Query_RuneSlot_Returns_CorrectValue.verified.txt new file mode 100644 index 00000000..1a9bb61b --- /dev/null +++ b/Mimir.Tests/QueryTests/RuneSlotTest.GraphQL_Query_RuneSlot_Returns_CorrectValue.verified.txt @@ -0,0 +1,16 @@ +{ + "data": { + "runeSlot": { + "battleType": "ADVENTURE", + "slots": [ + { + "index": 1, + "runeSlotType": "DEFAULT", + "runeType": "STAT", + "isLock": false, + "runeId": 10 + } + ] + } + } +} \ No newline at end of file diff --git a/Mimir.Tests/QueryTests/RuneSlotTest.cs b/Mimir.Tests/QueryTests/RuneSlotTest.cs new file mode 100644 index 00000000..e70e3e9d --- /dev/null +++ b/Mimir.Tests/QueryTests/RuneSlotTest.cs @@ -0,0 +1,63 @@ +using Lib9c.Models.Runes; +using Lib9c.Models.States; +using Libplanet.Crypto; +using Mimir.MongoDB.Bson; +using Mimir.MongoDB.Repositories; +using Moq; + +namespace Mimir.Tests.QueryTests; + +public class RuneSlotTest +{ + [Fact] + public async Task GraphQL_Query_RuneSlot_Returns_CorrectValue() + { + var address = new Address("0x0000000001000000000200000000030000000004"); + var state = new RuneSlotState + { + BattleType = Nekoyume.Model.EnumType.BattleType.Adventure, + Slots = new List + { + new RuneSlot + { + Index = 1, + RuneSlotType = Nekoyume.Model.EnumType.RuneSlotType.Default, + RuneType = Nekoyume.Model.EnumType.RuneType.Stat, + IsLock = false, + RuneId = 10 + } + }, + }; + var mockRepo = new Mock(); + mockRepo + .Setup(repo => + repo.GetByAddressAsync( + It.IsAny
(), + Nekoyume.Model.EnumType.BattleType.Adventure + ) + ) + .ReturnsAsync(new RuneSlotDocument(1, address, state)); + var serviceProvider = TestServices.Builder.With(mockRepo.Object).Build(); + var query = $$""" + query { + runeSlot(address: "{{address}}", battleType: ADVENTURE) { + battleType, + slots { + index + runeSlotType + runeType + isLock + runeId + } + } + } + """; + + var result = await TestServices.ExecuteRequestAsync( + serviceProvider, + b => b.SetDocument(query) + ); + + await Verify(result); + } +} diff --git a/Mimir.Worker/ActionHandler/RuneSlotStateHandler.cs b/Mimir.Worker/ActionHandler/RuneSlotStateHandler.cs new file mode 100644 index 00000000..9d12b997 --- /dev/null +++ b/Mimir.Worker/ActionHandler/RuneSlotStateHandler.cs @@ -0,0 +1,260 @@ +using System.Text.RegularExpressions; +using Bencodex.Types; +using Libplanet.Crypto; +using Mimir.MongoDB.Bson; +using Mimir.Worker.Client; +using Mimir.Worker.CollectionUpdaters; +using Mimir.Worker.Initializer; +using Mimir.Worker.Initializer.Manager; +using Mimir.Worker.Services; +using MongoDB.Bson; +using MongoDB.Driver; +using Nekoyume.Action; +using Nekoyume.Model.EnumType; +using Serilog; + +namespace Mimir.Worker.ActionHandler; + +public class RuneSlotStateHandler( + IStateService stateService, + MongoDbService store, + IHeadlessGQLClient headlessGqlClient, + IInitializerManager initializerManager +) + : BaseActionHandler( + stateService, + store, + headlessGqlClient, + initializerManager, + "^(battle_arena[0-9]*|event_dungeon_battle[0-9]*|hack_and_slash[0-9]*|hack_and_slash_sweep[0-9]*|join_arena[0-9]*|raid[0-9]*|unlock_rune_slot[0-9]*)$", + Log.ForContext() + ) +{ + private static readonly BattleType[] BattleTypes = Enum.GetValues(); + + protected override async Task>> HandleActionAsync( + long blockIndex, + Address signer, + IValue actionPlainValue, + string actionType, + IValue? actionPlainValueInternal, + IClientSessionHandle? session = null, + CancellationToken stoppingToken = default + ) + { + return ( + await TryProcessRuneSlotStateAsync(actionPlainValue, actionType, stoppingToken) + ).Concat( + await TryProcessUnlockRuneSlotAsync( + actionPlainValue, + actionType, + session, + stoppingToken + ) + ); + } + + private async Task>> TryProcessRuneSlotStateAsync( + IValue actionPlainValue, + string actionType, + CancellationToken stoppingToken = default + ) + { + if (Regex.IsMatch(actionType, "^battle_arena[0-9]*$")) + { + var action = new BattleArena(); + action.LoadPlainValue(actionPlainValue); + return await TryProcessRuneSlotStateAsync(action, stoppingToken); + } + + if (Regex.IsMatch(actionType, "^event_dungeon_battle[0-9]*$")) + { + var action = new EventDungeonBattle(); + action.LoadPlainValue(actionPlainValue); + return await TryProcessRuneSlotStateAsync(action, stoppingToken); + } + + if (Regex.IsMatch(actionType, "^hack_and_slash[0-9]*$")) + { + var action = new HackAndSlash(); + action.LoadPlainValue(actionPlainValue); + return await TryProcessRuneSlotStateAsync(action, stoppingToken); + } + + if (Regex.IsMatch(actionType, "^hack_and_slash_sweep[0-9]*$")) + { + var action = new HackAndSlashSweep(); + action.LoadPlainValue(actionPlainValue); + return await TryProcessRuneSlotStateAsync(action, stoppingToken); + } + + if (Regex.IsMatch(actionType, "^join_arena[0-9]*$")) + { + var action = new JoinArena(); + action.LoadPlainValue(actionPlainValue); + return await TryProcessRuneSlotStateAsync(action, stoppingToken); + } + + if (Regex.IsMatch(actionType, "^raid[0-9]*$")) + { + var action = new Raid(); + action.LoadPlainValue(actionPlainValue); + return await TryProcessRuneSlotStateAsync(action, stoppingToken); + } + + return []; + } + + private async Task>> TryProcessRuneSlotStateAsync( + BattleArena action, + CancellationToken stoppingToken = default + ) + { + if (action.runeInfos is null) + { + // ignore + return []; + } + + return await RuneSlotCollectionUpdater.UpdateAsync( + StateService, + BattleType.Arena, + action.myAvatarAddress, + stoppingToken + ); + } + + private async Task>> TryProcessRuneSlotStateAsync( + EventDungeonBattle action, + CancellationToken stoppingToken = default + ) + { + if (action.RuneInfos is null) + { + // ignore + return []; + } + + return await RuneSlotCollectionUpdater.UpdateAsync( + StateService, + BattleType.Arena, + action.AvatarAddress, + stoppingToken + ); + } + + private async Task>> TryProcessRuneSlotStateAsync( + HackAndSlash action, + CancellationToken stoppingToken = default + ) + { + if (action.RuneInfos is null) + { + // ignore + return []; + } + + return await RuneSlotCollectionUpdater.UpdateAsync( + StateService, + BattleType.Arena, + action.AvatarAddress, + stoppingToken + ); + } + + private async Task>> TryProcessRuneSlotStateAsync( + HackAndSlashSweep action, + CancellationToken stoppingToken = default + ) + { + if (action.runeInfos is null) + { + // ignore + return []; + } + + return await RuneSlotCollectionUpdater.UpdateAsync( + StateService, + BattleType.Arena, + action.avatarAddress, + stoppingToken + ); + } + + private async Task>> TryProcessRuneSlotStateAsync( + JoinArena action, + CancellationToken stoppingToken = default + ) + { + if (action.runeInfos is null) + { + // ignore + return []; + } + + return await RuneSlotCollectionUpdater.UpdateAsync( + StateService, + BattleType.Arena, + action.avatarAddress, + stoppingToken + ); + } + + private async Task>> TryProcessRuneSlotStateAsync( + Raid action, + CancellationToken stoppingToken = default + ) + { + if (action.RuneInfos is null) + { + // ignore + return []; + } + + return await RuneSlotCollectionUpdater.UpdateAsync( + StateService, + BattleType.Arena, + action.AvatarAddress, + stoppingToken + ); + } + + private async Task>> TryProcessUnlockRuneSlotAsync( + IValue actionPlainValue, + string actionType, + IClientSessionHandle? session = null, + CancellationToken stoppingToken = default + ) + { + if (Regex.IsMatch(actionType, "^unlock_rune_slot[0-9]*$")) + { + var action = new UnlockRuneSlot(); + action.LoadPlainValue(actionPlainValue); + return await TryProcessUnlockRuneSlotAsync(action, session, stoppingToken); + } + + return []; + } + + private async Task>> TryProcessUnlockRuneSlotAsync( + UnlockRuneSlot action, + IClientSessionHandle? session = null, + CancellationToken stoppingToken = default + ) + { + var ops = new List>(); + foreach (var battleType in BattleTypes) + { + ops.AddRange( + await RuneSlotCollectionUpdater.UpdateAsync( + StateService, + battleType, + action.AvatarAddress, + stoppingToken + ) + ); + } + + return ops; + } +} diff --git a/Mimir.Worker/CollectionUpdaters/RuneSlotCollectionUpdater.cs b/Mimir.Worker/CollectionUpdaters/RuneSlotCollectionUpdater.cs new file mode 100644 index 00000000..9cfe8566 --- /dev/null +++ b/Mimir.Worker/CollectionUpdaters/RuneSlotCollectionUpdater.cs @@ -0,0 +1,115 @@ +using Bencodex.Types; +using Libplanet.Crypto; +using Mimir.MongoDB.Bson; +using Mimir.Worker.Services; +using MongoDB.Bson; +using MongoDB.Driver; +using Nekoyume.Action; +using Nekoyume.Model.EnumType; +using RuneSlotState = Lib9c.Models.States.RuneSlotState; + +namespace Mimir.Worker.CollectionUpdaters; + +public static class RuneSlotCollectionUpdater +{ + public static async Task>> UpdateAsync( + IStateService stateService, + BattleType battleType, + Address avatarAddress, + CancellationToken stoppingToken = default + ) + { + var runeSlotAddress = Nekoyume.Model.State.RuneSlotState.DeriveAddress( + avatarAddress, + battleType + ); + if (await stateService.GetState(runeSlotAddress, stoppingToken) is not List serialized) + { + return []; + } + + var runeSlotState = new RuneSlotState(serialized); + var runeSlotDocument = new RuneSlotDocument(runeSlotAddress, runeSlotState); + return [runeSlotDocument.ToUpdateOneModel()]; + } + + private static bool HasChanged( + IMongoCollection collection, + Address runeSlotAddress, + List runeSlotInfos + ) + { + var filter = Builders.Filter.Eq("Address", runeSlotAddress.ToHex()); + var document = collection.Find(filter).FirstOrDefault(); + if (document is null) + { + return true; + } + + try + { + var storedRuneSlots = document["Object"] + ["Slots"] + .AsBsonArray.OfType() + .Select(e => + ( + slotIndex: e["Index"].AsInt32, + runeId: e.TryGetValue("RuneSheetId", out var runeSheetIdBsonValue) + ? runeSheetIdBsonValue.AsNullableInt32 + : null + ) + ) + .OrderBy(tuple => tuple.slotIndex) + .ToArray(); + if (storedRuneSlots.Length != runeSlotInfos.Count) + { + return true; + } + + for (var i = 0; i < storedRuneSlots.Length; i++) + { + if ( + storedRuneSlots[i].slotIndex != runeSlotInfos[i].SlotIndex + || storedRuneSlots[i].runeId != runeSlotInfos[i].RuneId + ) + { + return true; + } + } + } + catch (KeyNotFoundException) + { + return true; + } + + return false; + } + + private static bool HasChanged( + IMongoCollection collection, + Address runeSlotAddress, + int runeSlotIndexToUnlock + ) + { + var filter = Builders.Filter.Eq("Address", runeSlotAddress.ToHex()); + var document = collection.Find(filter).FirstOrDefault(); + if (document is null) + { + return true; + } + + try + { + return document["Object"] + ["Slots"] + .AsBsonArray.Select(e => + (slotIndex: e["SlotIndex"].AsInt32, isLock: e["IsLock"].AsBoolean) + ) + .Any(tuple => tuple.slotIndex == runeSlotIndexToUnlock && tuple.isLock); + } + catch (KeyNotFoundException) + { + return true; + } + } +} diff --git a/Mimir.Worker/HostApplicationBuilderExtensions.cs b/Mimir.Worker/HostApplicationBuilderExtensions.cs index 17e05c96..f20b1a87 100644 --- a/Mimir.Worker/HostApplicationBuilderExtensions.cs +++ b/Mimir.Worker/HostApplicationBuilderExtensions.cs @@ -9,10 +9,13 @@ namespace Mimir.Worker; public static class HostApplicationBuilderExtensions { - public static HostApplicationBuilder ConfigureHandlers(this HostApplicationBuilder builder) + public static HostApplicationBuilder ConfigureHandlers(this HostApplicationBuilder builder) { - if (builder.Configuration.GetSection("Configuration").GetValue("PollerType") is { } pollerType && - pollerType == PollerType.TxPoller) + if ( + builder.Configuration.GetSection("Configuration").GetValue("PollerType") + is { } pollerType + && pollerType == PollerType.TxPoller + ) { builder.Services.AddBackgroundService(); builder.Services.AddBackgroundService(); @@ -23,13 +26,14 @@ public static HostApplicationBuilder ConfigureHandlers(this HostApplicationBuil builder.Services.AddBackgroundService(); builder.Services.AddBackgroundService(); builder.Services.AddBackgroundService(); + builder.Services.AddBackgroundService(); builder.Services.AddBackgroundService(); builder.Services.AddBackgroundService(); builder.Services.AddBackgroundService(); } else { - builder.Services.AddBackgroundService(); + builder.Services.AddBackgroundService(); builder.Services.AddBackgroundService(); builder.Services.AddBackgroundService(); builder.Services.AddBackgroundService(); @@ -39,7 +43,7 @@ public static HostApplicationBuilder ConfigureHandlers(this HostApplicationBuil builder.Services.AddBackgroundService(); // builder.Services.AddBackgroundService(); builder.Services.AddBackgroundService(); - + // Balance Handlers builder.Services.AddBackgroundService(); builder.Services.AddBackgroundService(); @@ -54,19 +58,22 @@ public static HostApplicationBuilder ConfigureHandlers(this HostApplicationBuil return builder; } - - public static HostApplicationBuilder ConfigureInitializers(this HostApplicationBuilder builder) + + public static HostApplicationBuilder ConfigureInitializers(this HostApplicationBuilder builder) { - if (builder.Configuration.GetSection("Configuration").GetValue("EnableInitializing") is true) + if ( + builder.Configuration.GetSection("Configuration").GetValue("EnableInitializing") + is true + ) { builder.Services.AddBackgroundService(); builder.Services.AddBackgroundService(); - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); } else { - builder.Services.AddSingleton(); + builder.Services.AddSingleton(); } return builder; diff --git a/Mimir/GraphQL/Queries/Query.cs b/Mimir/GraphQL/Queries/Query.cs index f91be5d3..d1eeaee7 100644 --- a/Mimir/GraphQL/Queries/Query.cs +++ b/Mimir/GraphQL/Queries/Query.cs @@ -198,6 +198,18 @@ [Service] IStakeRepository repo [Service] IItemSlotRepository repo ) => (await repo.GetByAddressAsync(address, battleType)).Object; + /// + /// Get a runeSlot state by avatar address. + /// + /// The address of the avatar. + /// The battleType. + /// The stake state. + public async Task GetRuneSlotAsync( + Address address, + BattleType battleType, + [Service] IRuneSlotRepository repo + ) => (await repo.GetByAddressAsync(address, battleType)).Object; + /// /// Get the world boss. /// diff --git a/Mimir/Program.cs b/Mimir/Program.cs index d2b5ac83..b1b126aa 100644 --- a/Mimir/Program.cs +++ b/Mimir/Program.cs @@ -58,6 +58,7 @@ builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); From 98cef801bee136ea8aa932ac06318a296654ed56 Mon Sep 17 00:00:00 2001 From: Atralupus Date: Sat, 28 Dec 2024 04:00:07 +0900 Subject: [PATCH 2/2] Update handlers --- ...SlotDocumentTest.JsonSnapshot.verified.txt | 2 +- ...SlotDocumentTest.JsonSnapshot.verified.txt | 2 +- .../Bson/RuneSlotDocumentTest.cs | 1 + Mimir.MongoDB/Bson/ItemSlotDocument.cs | 6 +-- Mimir.MongoDB/Bson/RuneSlotDocument.cs | 5 +- .../Repositories/RuneSlotRepository.cs | 2 +- Mimir.Tests/QueryTests/RuneSlotTest.cs | 2 +- .../ActionHandler/RuneSlotStateHandler.cs | 46 ++++++++++++++----- .../RuneSlotCollectionUpdater.cs | 8 +++- 9 files changed, 52 insertions(+), 22 deletions(-) diff --git a/Mimir.MongoDB.Tests/Bson/ItemSlotDocumentTest.JsonSnapshot.verified.txt b/Mimir.MongoDB.Tests/Bson/ItemSlotDocumentTest.JsonSnapshot.verified.txt index f2936ff6..cf8515d0 100644 --- a/Mimir.MongoDB.Tests/Bson/ItemSlotDocumentTest.JsonSnapshot.verified.txt +++ b/Mimir.MongoDB.Tests/Bson/ItemSlotDocumentTest.JsonSnapshot.verified.txt @@ -1 +1 @@ -{"AvatarAddress":"0000000000000000000000000000000000000000","Object":{"BattleType":"Adventure","Costumes":[],"Equipments":["0468bfd9-7c76-4379-b1bc-10a3da90c584","0c4c772f-70d9-4a0d-b7e6-4cf25ad7f8f9","13164d27-1948-44a9-9bc7-b23a00aacc5f","2d9d4118-0e3b-445e-aa16-8203e50d2523","4da15d1b-503c-4e08-8476-6f405192258c","98c3abdb-17bc-49a0-8f9c-a61f48662dc7","a45c62d6-e612-4a0e-957a-9ecb7ae6f04f"]},"Metadata":{"SchemaVersion":2,"StoredBlockIndex":0}} \ No newline at end of file +{"ItemSlotAddress":"0000000000000000000000000000000000000000","Object":{"BattleType":"Adventure","Costumes":[],"Equipments":["0468bfd9-7c76-4379-b1bc-10a3da90c584","0c4c772f-70d9-4a0d-b7e6-4cf25ad7f8f9","13164d27-1948-44a9-9bc7-b23a00aacc5f","2d9d4118-0e3b-445e-aa16-8203e50d2523","4da15d1b-503c-4e08-8476-6f405192258c","98c3abdb-17bc-49a0-8f9c-a61f48662dc7","a45c62d6-e612-4a0e-957a-9ecb7ae6f04f"]},"Metadata":{"SchemaVersion":2,"StoredBlockIndex":0}} \ No newline at end of file diff --git a/Mimir.MongoDB.Tests/Bson/RuneSlotDocumentTest.JsonSnapshot.verified.txt b/Mimir.MongoDB.Tests/Bson/RuneSlotDocumentTest.JsonSnapshot.verified.txt index 2629feff..e162a2a5 100644 --- a/Mimir.MongoDB.Tests/Bson/RuneSlotDocumentTest.JsonSnapshot.verified.txt +++ b/Mimir.MongoDB.Tests/Bson/RuneSlotDocumentTest.JsonSnapshot.verified.txt @@ -1 +1 @@ -{"Object":{"BattleType":"Adventure","Slots":[{"Index":0,"RuneSlotType":"Default","RuneType":"Stat","IsLock":false,"RuneId":10012},{"Index":1,"RuneSlotType":"Ncg","RuneType":"Stat","IsLock":true},{"Index":2,"RuneSlotType":"Stake","RuneType":"Stat","IsLock":true},{"Index":3,"RuneSlotType":"Default","RuneType":"Skill","IsLock":false},{"Index":4,"RuneSlotType":"Ncg","RuneType":"Skill","IsLock":true},{"Index":5,"RuneSlotType":"Stake","RuneType":"Skill","IsLock":true},{"Index":6,"RuneSlotType":"Crystal","RuneType":"Stat","IsLock":true},{"Index":7,"RuneSlotType":"Crystal","RuneType":"Skill","IsLock":true}]},"Metadata":{"SchemaVersion":1,"StoredBlockIndex":0}} \ No newline at end of file +{"RuneSlotAddress":"0000000000000000000000000000000000000000","Object":{"BattleType":"Adventure","Slots":[{"Index":0,"RuneSlotType":"Default","RuneType":"Stat","IsLock":false,"RuneId":10012},{"Index":1,"RuneSlotType":"Ncg","RuneType":"Stat","IsLock":true},{"Index":2,"RuneSlotType":"Stake","RuneType":"Stat","IsLock":true},{"Index":3,"RuneSlotType":"Default","RuneType":"Skill","IsLock":false},{"Index":4,"RuneSlotType":"Ncg","RuneType":"Skill","IsLock":true},{"Index":5,"RuneSlotType":"Stake","RuneType":"Skill","IsLock":true},{"Index":6,"RuneSlotType":"Crystal","RuneType":"Stat","IsLock":true},{"Index":7,"RuneSlotType":"Crystal","RuneType":"Skill","IsLock":true}]},"Metadata":{"SchemaVersion":1,"StoredBlockIndex":0}} \ No newline at end of file diff --git a/Mimir.MongoDB.Tests/Bson/RuneSlotDocumentTest.cs b/Mimir.MongoDB.Tests/Bson/RuneSlotDocumentTest.cs index 66e6599d..65d44007 100644 --- a/Mimir.MongoDB.Tests/Bson/RuneSlotDocumentTest.cs +++ b/Mimir.MongoDB.Tests/Bson/RuneSlotDocumentTest.cs @@ -10,6 +10,7 @@ public class RuneSlotDocumentTest public Task JsonSnapshot() { var docs = new RuneSlotDocument( + default, default, default, new RuneSlotState(TestDataHelpers.LoadState("RuneSlotState.bin")) diff --git a/Mimir.MongoDB/Bson/ItemSlotDocument.cs b/Mimir.MongoDB/Bson/ItemSlotDocument.cs index 9fc6d68c..5f28718b 100644 --- a/Mimir.MongoDB/Bson/ItemSlotDocument.cs +++ b/Mimir.MongoDB/Bson/ItemSlotDocument.cs @@ -8,7 +8,7 @@ namespace Mimir.MongoDB.Bson; [BsonIgnoreExtraElements] public record ItemSlotDocument( [property: BsonIgnore, JsonIgnore] long StoredBlockIndex, - [property: BsonIgnore, JsonIgnore] Address Address, - Address AvatarAddress, + [property: BsonIgnore, JsonIgnore] Address AvatarAddress, + Address ItemSlotAddress, ItemSlotState Object -) : MimirBsonDocument(Address.ToHex(), new DocumentMetadata(2, StoredBlockIndex)); +) : MimirBsonDocument(AvatarAddress.ToHex(), new DocumentMetadata(2, StoredBlockIndex)); diff --git a/Mimir.MongoDB/Bson/RuneSlotDocument.cs b/Mimir.MongoDB/Bson/RuneSlotDocument.cs index 5ce4ec66..298f9c16 100644 --- a/Mimir.MongoDB/Bson/RuneSlotDocument.cs +++ b/Mimir.MongoDB/Bson/RuneSlotDocument.cs @@ -8,6 +8,7 @@ namespace Mimir.MongoDB.Bson; [BsonIgnoreExtraElements] public record RuneSlotDocument( [property: BsonIgnore, JsonIgnore] long StoredBlockIndex, - [property: BsonIgnore, JsonIgnore] Address Address, + [property: BsonIgnore, JsonIgnore] Address AvatarAddress, + Address RuneSlotAddress, RuneSlotState Object -) : MimirBsonDocument(Address.ToHex(), new DocumentMetadata(1, StoredBlockIndex)); +) : MimirBsonDocument(AvatarAddress.ToHex(), new DocumentMetadata(1, StoredBlockIndex)); diff --git a/Mimir.MongoDB/Repositories/RuneSlotRepository.cs b/Mimir.MongoDB/Repositories/RuneSlotRepository.cs index c6b07569..acf5f661 100644 --- a/Mimir.MongoDB/Repositories/RuneSlotRepository.cs +++ b/Mimir.MongoDB/Repositories/RuneSlotRepository.cs @@ -25,7 +25,7 @@ BattleType battleType ); var collectionName = CollectionNames.GetCollectionName(); var collection = dbService.GetCollection(collectionName); - var filter = Builders.Filter.Eq("Address", runeSlotAddress.ToHex()); + var filter = Builders.Filter.Eq("_id", runeSlotAddress.ToHex()); var document = await collection.Find(filter).FirstOrDefaultAsync(); if (document is null) { diff --git a/Mimir.Tests/QueryTests/RuneSlotTest.cs b/Mimir.Tests/QueryTests/RuneSlotTest.cs index e70e3e9d..6231fe4e 100644 --- a/Mimir.Tests/QueryTests/RuneSlotTest.cs +++ b/Mimir.Tests/QueryTests/RuneSlotTest.cs @@ -36,7 +36,7 @@ public async Task GraphQL_Query_RuneSlot_Returns_CorrectValue() Nekoyume.Model.EnumType.BattleType.Adventure ) ) - .ReturnsAsync(new RuneSlotDocument(1, address, state)); + .ReturnsAsync(new RuneSlotDocument(1, address, address, state)); var serviceProvider = TestServices.Builder.With(mockRepo.Object).Build(); var query = $$""" query { diff --git a/Mimir.Worker/ActionHandler/RuneSlotStateHandler.cs b/Mimir.Worker/ActionHandler/RuneSlotStateHandler.cs index 9d12b997..7647b1c6 100644 --- a/Mimir.Worker/ActionHandler/RuneSlotStateHandler.cs +++ b/Mimir.Worker/ActionHandler/RuneSlotStateHandler.cs @@ -43,9 +43,15 @@ protected override async Task>> HandleActio ) { return ( - await TryProcessRuneSlotStateAsync(actionPlainValue, actionType, stoppingToken) + await TryProcessRuneSlotStateAsync( + blockIndex, + actionPlainValue, + actionType, + stoppingToken + ) ).Concat( await TryProcessUnlockRuneSlotAsync( + blockIndex, actionPlainValue, actionType, session, @@ -55,6 +61,7 @@ await TryProcessUnlockRuneSlotAsync( } private async Task>> TryProcessRuneSlotStateAsync( + long blockIndex, IValue actionPlainValue, string actionType, CancellationToken stoppingToken = default @@ -64,48 +71,49 @@ private async Task>> TryProcessRuneSlotStat { var action = new BattleArena(); action.LoadPlainValue(actionPlainValue); - return await TryProcessRuneSlotStateAsync(action, stoppingToken); + return await TryProcessRuneSlotStateAsync(blockIndex, action, stoppingToken); } if (Regex.IsMatch(actionType, "^event_dungeon_battle[0-9]*$")) { var action = new EventDungeonBattle(); action.LoadPlainValue(actionPlainValue); - return await TryProcessRuneSlotStateAsync(action, stoppingToken); + return await TryProcessRuneSlotStateAsync(blockIndex, action, stoppingToken); } if (Regex.IsMatch(actionType, "^hack_and_slash[0-9]*$")) { var action = new HackAndSlash(); action.LoadPlainValue(actionPlainValue); - return await TryProcessRuneSlotStateAsync(action, stoppingToken); + return await TryProcessRuneSlotStateAsync(blockIndex, action, stoppingToken); } if (Regex.IsMatch(actionType, "^hack_and_slash_sweep[0-9]*$")) { var action = new HackAndSlashSweep(); action.LoadPlainValue(actionPlainValue); - return await TryProcessRuneSlotStateAsync(action, stoppingToken); + return await TryProcessRuneSlotStateAsync(blockIndex, action, stoppingToken); } if (Regex.IsMatch(actionType, "^join_arena[0-9]*$")) { var action = new JoinArena(); action.LoadPlainValue(actionPlainValue); - return await TryProcessRuneSlotStateAsync(action, stoppingToken); + return await TryProcessRuneSlotStateAsync(blockIndex, action, stoppingToken); } if (Regex.IsMatch(actionType, "^raid[0-9]*$")) { var action = new Raid(); action.LoadPlainValue(actionPlainValue); - return await TryProcessRuneSlotStateAsync(action, stoppingToken); + return await TryProcessRuneSlotStateAsync(blockIndex, action, stoppingToken); } return []; } private async Task>> TryProcessRuneSlotStateAsync( + long blockIndex, BattleArena action, CancellationToken stoppingToken = default ) @@ -117,6 +125,7 @@ private async Task>> TryProcessRuneSlotStat } return await RuneSlotCollectionUpdater.UpdateAsync( + blockIndex, StateService, BattleType.Arena, action.myAvatarAddress, @@ -125,6 +134,7 @@ private async Task>> TryProcessRuneSlotStat } private async Task>> TryProcessRuneSlotStateAsync( + long blockIndex, EventDungeonBattle action, CancellationToken stoppingToken = default ) @@ -136,14 +146,16 @@ private async Task>> TryProcessRuneSlotStat } return await RuneSlotCollectionUpdater.UpdateAsync( + blockIndex, StateService, - BattleType.Arena, + BattleType.Adventure, action.AvatarAddress, stoppingToken ); } private async Task>> TryProcessRuneSlotStateAsync( + long blockIndex, HackAndSlash action, CancellationToken stoppingToken = default ) @@ -155,14 +167,16 @@ private async Task>> TryProcessRuneSlotStat } return await RuneSlotCollectionUpdater.UpdateAsync( + blockIndex, StateService, - BattleType.Arena, + BattleType.Adventure, action.AvatarAddress, stoppingToken ); } private async Task>> TryProcessRuneSlotStateAsync( + long blockIndex, HackAndSlashSweep action, CancellationToken stoppingToken = default ) @@ -174,14 +188,16 @@ private async Task>> TryProcessRuneSlotStat } return await RuneSlotCollectionUpdater.UpdateAsync( + blockIndex, StateService, - BattleType.Arena, + BattleType.Adventure, action.avatarAddress, stoppingToken ); } private async Task>> TryProcessRuneSlotStateAsync( + long blockIndex, JoinArena action, CancellationToken stoppingToken = default ) @@ -193,6 +209,7 @@ private async Task>> TryProcessRuneSlotStat } return await RuneSlotCollectionUpdater.UpdateAsync( + blockIndex, StateService, BattleType.Arena, action.avatarAddress, @@ -201,6 +218,7 @@ private async Task>> TryProcessRuneSlotStat } private async Task>> TryProcessRuneSlotStateAsync( + long blockIndex, Raid action, CancellationToken stoppingToken = default ) @@ -212,14 +230,16 @@ private async Task>> TryProcessRuneSlotStat } return await RuneSlotCollectionUpdater.UpdateAsync( + blockIndex, StateService, - BattleType.Arena, + BattleType.Raid, action.AvatarAddress, stoppingToken ); } private async Task>> TryProcessUnlockRuneSlotAsync( + long blockIndex, IValue actionPlainValue, string actionType, IClientSessionHandle? session = null, @@ -230,13 +250,14 @@ private async Task>> TryProcessUnlockRuneSl { var action = new UnlockRuneSlot(); action.LoadPlainValue(actionPlainValue); - return await TryProcessUnlockRuneSlotAsync(action, session, stoppingToken); + return await TryProcessUnlockRuneSlotAsync(blockIndex, action, session, stoppingToken); } return []; } private async Task>> TryProcessUnlockRuneSlotAsync( + long blockIndex, UnlockRuneSlot action, IClientSessionHandle? session = null, CancellationToken stoppingToken = default @@ -247,6 +268,7 @@ private async Task>> TryProcessUnlockRuneSl { ops.AddRange( await RuneSlotCollectionUpdater.UpdateAsync( + blockIndex, StateService, battleType, action.AvatarAddress, diff --git a/Mimir.Worker/CollectionUpdaters/RuneSlotCollectionUpdater.cs b/Mimir.Worker/CollectionUpdaters/RuneSlotCollectionUpdater.cs index 9cfe8566..2c71cb6d 100644 --- a/Mimir.Worker/CollectionUpdaters/RuneSlotCollectionUpdater.cs +++ b/Mimir.Worker/CollectionUpdaters/RuneSlotCollectionUpdater.cs @@ -13,6 +13,7 @@ namespace Mimir.Worker.CollectionUpdaters; public static class RuneSlotCollectionUpdater { public static async Task>> UpdateAsync( + long blockIndex, IStateService stateService, BattleType battleType, Address avatarAddress, @@ -29,7 +30,12 @@ public static async Task>> UpdateAsync( } var runeSlotState = new RuneSlotState(serialized); - var runeSlotDocument = new RuneSlotDocument(runeSlotAddress, runeSlotState); + var runeSlotDocument = new RuneSlotDocument( + blockIndex, + avatarAddress, + runeSlotAddress, + runeSlotState + ); return [runeSlotDocument.ToUpdateOneModel()]; }