Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rc v200180 arena worker #2487

Draft
wants to merge 6 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Lib9c
6 changes: 5 additions & 1 deletion NineChronicles.Headless.Executable/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ public class Configuration

public int ArenaParticipantsSyncInterval { get; set; } = 1000;

public string RedisConnectionString { get; set; } = "";

public void Overwrite(
string? appProtocolVersionString,
string[]? trustedAppProtocolVersionSignerStrings,
Expand Down Expand Up @@ -150,7 +152,8 @@ public void Overwrite(
string? sentryDsn,
double? sentryTraceSampleRate,
int? arenaParticipantsSyncInterval,
bool? remoteKeyValueService
bool? remoteKeyValueService,
string? redisConnectionString
)
{
AppProtocolVersionString = appProtocolVersionString ?? AppProtocolVersionString;
Expand Down Expand Up @@ -205,6 +208,7 @@ public void Overwrite(
SentryTraceSampleRate = sentryTraceSampleRate ?? SentryTraceSampleRate;
ArenaParticipantsSyncInterval = arenaParticipantsSyncInterval ?? ArenaParticipantsSyncInterval;
RemoteKeyValueService = remoteKeyValueService ?? RemoteKeyValueService;
RedisConnectionString = redisConnectionString ?? RedisConnectionString;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<PackageReference Include="GraphQL.Client" Version="5.1.1" />
<PackageReference Include="GraphQL.Client.Serializer.SystemTextJson" Version="5.1.1" />
<PackageReference Include="Destructurama.Attributed" Version="2.0.0" />
<PackageReference Include="Pyroscope" Version="0.8.14" />
<PackageReference Include="Sentry.Serilog" Version="3.23.0" />
<PackageReference Include="Sentry" Version="3.23.0" />
<PackageReference Include="Sentry.AspNetCore.Grpc" Version="3.22.0" />
Expand Down
19 changes: 17 additions & 2 deletions NineChronicles.Headless.Executable/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
using OpenTelemetry;
using OpenTelemetry.Metrics;
using Nekoyume;
using StackExchange.Redis;
using ITransaction = Sentry.ITransaction;

namespace NineChronicles.Headless.Executable
{
Expand Down Expand Up @@ -224,6 +226,8 @@ public async Task Run(
bool arenaParticipantsSync = true,
[Option(Description = "[DANGER] Turn on RemoteKeyValueService to debug.")]
bool remoteKeyValueService = false,
[Option(Description = "redis cache connection string")]
string? redisConnectionString = "localhost:6379",
[Ignore] CancellationToken? cancellationToken = null
)
{
Expand Down Expand Up @@ -310,7 +314,7 @@ public async Task Run(
txLifeTime, messageTimeout, tipTimeout, demandBuffer, skipPreload,
minimumBroadcastTarget, bucketSize, chainTipStaleBehaviorType, txQuotaPerSigner, maximumPollPeers,
consensusPort, consensusPrivateKeyString, consensusSeedStrings, consensusTargetBlockIntervalMilliseconds, consensusProposeSecondBase,
maxTransactionPerBlock, sentryDsn, sentryTraceSampleRate, arenaParticipantsSyncInterval, remoteKeyValueService
maxTransactionPerBlock, sentryDsn, sentryTraceSampleRate, arenaParticipantsSyncInterval, remoteKeyValueService, redisConnectionString
);

#if SENTRY || ! DEBUG
Expand Down Expand Up @@ -474,6 +478,16 @@ IActionLoader MakeSingleActionLoader()
MaxTransactionPerBlock = headlessConfig.MaxTransactionPerBlock
};
var arenaMemoryCache = new StateMemoryCache();
var configurationOptions = new ConfigurationOptions
{
EndPoints = { headlessConfig.RedisConnectionString },
ConnectTimeout = 500,
SyncTimeout = 500,
};

var redis = await ConnectionMultiplexer.ConnectAsync(configurationOptions);
var db = redis.GetDatabase();

hostBuilder.ConfigureServices(services =>
{
services.AddSingleton(_ => standaloneContext);
Expand All @@ -487,9 +501,10 @@ IActionLoader MakeSingleActionLoader()
// worker
if (arenaParticipantsSync)
{
services.AddHostedService(_ => new ArenaParticipantsWorker(arenaMemoryCache, standaloneContext, headlessConfig.ArenaParticipantsSyncInterval));
services.AddHostedService(_ => new ArenaParticipantsWorker(standaloneContext, headlessConfig.ArenaParticipantsSyncInterval, db));
}
services.AddSingleton(arenaMemoryCache);
services.AddSingleton(db);
});

NineChroniclesNodeService service =
Expand Down
9 changes: 8 additions & 1 deletion NineChronicles.Headless.Tests/ArenaParticipantsWorkerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using Libplanet.Action.State;
using Libplanet.Crypto;
using Libplanet.Mocks;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Nekoyume;
using Nekoyume.Action;
using Nekoyume.Model.Arena;
Expand All @@ -13,6 +15,7 @@
using Nekoyume.Model.State;
using Nekoyume.Module;
using Nekoyume.TableData;
using Nekoyume.TableData.Rune;
using Xunit;
using Random = Libplanet.Extensions.ActionEvaluatorCommonComponents.Random;

Expand Down Expand Up @@ -183,7 +186,11 @@ public void GetArenaParticipants()
state = state.SetLegacyState(Addresses.GetSheetAddress(key), s.Serialize());
}
var avatarAddrAndScoresWithRank = ArenaParticipantsWorker.AvatarAddrAndScoresWithRank(participants.AvatarAddresses, currentRoundData, state);
var actual = ArenaParticipantsWorker.GetArenaParticipants(state, participants.AvatarAddresses, avatarAddrAndScoresWithRank);
var cache = new MemoryCache(new OptionsWrapper<MemoryCacheOptions>(new MemoryCacheOptions
{
SizeLimit = null
}));
var actual = ArenaParticipantsWorker.GetArenaParticipants(state, participants.AvatarAddresses, avatarAddrAndScoresWithRank, cache.GetSheet<RuneListSheet>(state), cache.GetSheet<CostumeStatSheet>(state), cache.GetSheet<CharacterSheet>(state), cache.GetSheet<RuneOptionSheet>(state));
Assert.Equal(2, actual.Count);
var first = actual.First();
Assert.Equal(avatarAddress, first.AvatarAddr);
Expand Down
2 changes: 0 additions & 2 deletions NineChronicles.Headless.Tests/GraphTypes/StateQueryTest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
Expand All @@ -22,7 +21,6 @@
using Nekoyume.TableData;
using NineChronicles.Headless.GraphTypes;
using NineChronicles.Headless.GraphTypes.States;
using NineChronicles.Headless.Tests.Common;
using Xunit;
using static NineChronicles.Headless.Tests.GraphQLTestUtils;

Expand Down
22 changes: 22 additions & 0 deletions NineChronicles.Headless.Tests/MemoryCacheExtensionsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
using System.Threading.Tasks;
using Bencodex;
using Bencodex.Types;
using Libplanet.Action.State;
using Libplanet.Mocks;
using MessagePack;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Nekoyume;
using Nekoyume.Module;
using Nekoyume.TableData;
using Xunit;

Expand Down Expand Up @@ -36,4 +39,23 @@ public async Task Sheet()
await Task.Delay(100);
Assert.False(cache.TryGetValue(cacheKey, out byte[] _));
}

[Fact]
public void GetSheet_With_Type()
{
var cache = new MemoryCache(new OptionsWrapper<MemoryCacheOptions>(new MemoryCacheOptions
{
SizeLimit = null
}));

var sheets = TableSheetsImporter.ImportSheets();
var tableName = nameof(ItemRequirementSheet);
var csv = sheets[tableName];
var sheetAddress = Addresses.GetSheetAddress(tableName);
var value = (Text)csv;
var state = (IWorld)new World(MockWorldState.CreateModern());
state = state.SetLegacyState(sheetAddress, value);
var cachedSheet = cache.GetSheet<ItemRequirementSheet>(state);
Assert.Equal(value, cachedSheet.Serialize());
}
}
22 changes: 13 additions & 9 deletions NineChronicles.Headless/ArenaParticipant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@ namespace NineChronicles.Headless;

public class ArenaParticipant
{
public readonly Address AvatarAddr;
public readonly int Score;
public readonly int Rank;
public int WinScore;
public int LoseScore;
public readonly int Cp;
public readonly int PortraitId;
public readonly string NameWithHash;
public readonly int Level;
public Address AvatarAddr { get; set; }
public int Score { get; set; }
public int Rank { get; set; }
public int WinScore { get; set; }
public int LoseScore { get; set; }
public int Cp { get; set; }
public int PortraitId { get; set; }
public string NameWithHash { get; set; } = "";
public int Level { get; set; }

public ArenaParticipant()
{
}

public ArenaParticipant(
Address avatarAddr,
Expand Down
43 changes: 28 additions & 15 deletions NineChronicles.Headless/ArenaParticipantsWorker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Bencodex.Types;
using Libplanet.Action.State;
using Libplanet.Crypto;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Hosting;
using Nekoyume;
using Nekoyume.Battle;
Expand All @@ -20,23 +20,25 @@
using Nekoyume.TableData;
using Nekoyume.TableData.Rune;
using NineChronicles.Headless.GraphTypes;
using NRedisStack.RedisStackCommands;
using Serilog;
using StackExchange.Redis;

namespace NineChronicles.Headless;

public class ArenaParticipantsWorker : BackgroundService
{
private ILogger _logger;

private StateMemoryCache _cache;
private IDatabase _db;

private StandaloneContext _context;

private int _interval;

public ArenaParticipantsWorker(StateMemoryCache memoryCache, StandaloneContext context, int interval)
public ArenaParticipantsWorker(StandaloneContext context, int interval, IDatabase database)
{
_cache = memoryCache;
_db = database;
_context = context;
_logger = Log.Logger.ForContext<ArenaParticipantsWorker>();
_interval = interval;
Expand Down Expand Up @@ -193,14 +195,21 @@ public static ArenaSheet.RoundData GetRoundData(IWorldState worldState, long blo
/// <param name="worldState">The world state from which to retrieve the arena participants.</param>
/// <param name="avatarAddrList">The list of avatar addresses to filter the matching participants.</param>
/// <param name="avatarAddrAndScoresWithRank">The list of avatar addresses with their scores and ranks.</param>
/// <param name="runeListSheet"></param>
/// <param name="costumeStatSheet"></param>
/// <param name="characterSheet"></param>
/// <param name="runeOptionSheet"></param>
/// <returns>A list of arena participants.</returns>
public static List<ArenaParticipant> GetArenaParticipants(IWorldState worldState, List<Address> avatarAddrList, List<(Address avatarAddr, int score, int rank)> avatarAddrAndScoresWithRank)
public static List<ArenaParticipant> GetArenaParticipants(
IWorldState worldState,
List<Address> avatarAddrList,
List<(Address avatarAddr, int score, int rank)> avatarAddrAndScoresWithRank,
RuneListSheet runeListSheet,
CostumeStatSheet costumeStatSheet,
CharacterSheet characterSheet,
RuneOptionSheet runeOptionSheet
)
{
var runeListSheet = worldState.GetSheet<RuneListSheet>();
var costumeSheet = worldState.GetSheet<CostumeStatSheet>();
var characterSheet = worldState.GetSheet<CharacterSheet>();
var runeOptionSheet = worldState.GetSheet<RuneOptionSheet>();
var runeIds = runeListSheet.Values.Select(x => x.Id).ToList();
var row = characterSheet[GameConfig.DefaultAvatarCharacterId];
CollectionSheet collectionSheet = new CollectionSheet();
var collectionStates = worldState.GetCollectionStates(avatarAddrList);
Expand All @@ -218,7 +227,7 @@ public static List<ArenaParticipant> GetArenaParticipants(IWorldState worldState
{
var (avatarAddr, score, rank) = tuple;
var runeStates = worldState.GetRuneState(avatarAddr, out _);
var avatar = worldState.GetAvatarState(avatarAddr);
var avatar = worldState.GetAvatarState(avatarAddr, getWorldInformation: false, getQuestList: false);
var itemSlotState =
worldState.GetLegacyState(ItemSlotState.DeriveAddress(avatarAddr, BattleType.Arena)) is
List itemSlotList
Expand Down Expand Up @@ -265,7 +274,7 @@ List runeSlotList
}
}

var cp = CPHelper.TotalCP(equipments, costumes, runeOptions, avatar.level, row, costumeSheet, collectionModifiers,
var cp = CPHelper.TotalCP(equipments, costumes, runeOptions, avatar.level, row, costumeStatSheet, collectionModifiers,
RuneHelper.CalculateRuneLevelBonus(runeStates, runeListSheet, worldState.GetSheet<RuneLevelBonusSheet>())
);
var portraitId = StateQuery.GetPortraitId(equipments, costumes);
Expand Down Expand Up @@ -308,15 +317,19 @@ public void PrepareArenaParticipants()
var cacheKey = $"{currentRoundData.ChampionshipId}_{currentRoundData.Round}";
if (participants is null)
{
_cache.ArenaParticipantsCache.Set(cacheKey, new List<ArenaParticipant>());
_db.StringSet(cacheKey, JsonSerializer.Serialize(new List<ArenaParticipant>()));
_logger.Information("[ArenaParticipantsWorker] participants({CacheKey}) is null. set empty list", cacheKey);
return;
}

var avatarAddrList = participants.AvatarAddresses;
var avatarAddrAndScoresWithRank = AvatarAddrAndScoresWithRank(avatarAddrList, currentRoundData, worldState);
var result = GetArenaParticipants(worldState, avatarAddrList, avatarAddrAndScoresWithRank);
_cache.ArenaParticipantsCache.Set(cacheKey, result, TimeSpan.FromHours(1));
var runeListSheet = worldState.GetSheet<RuneListSheet>();
var costumeStatSheet = worldState.GetSheet<CostumeStatSheet>();
var characterSheet = worldState.GetSheet<CharacterSheet>();
var runeOptionSheet = worldState.GetSheet<RuneOptionSheet>();
var result = GetArenaParticipants(worldState, avatarAddrList, avatarAddrAndScoresWithRank, runeListSheet, costumeStatSheet, characterSheet, runeOptionSheet);
_db.StringSet(cacheKey, JsonSerializer.Serialize(result), TimeSpan.FromHours(1));
sw.Stop();
_logger.Information("[ArenaParticipantsWorker]Set Arena Cache[{CacheKey}]: {Elapsed}", cacheKey, sw.Elapsed);
}
Expand Down
13 changes: 9 additions & 4 deletions NineChronicles.Headless/GraphTypes/StateQuery.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Bencodex;
using Bencodex.Types;
using GraphQL;
Expand Down Expand Up @@ -29,16 +30,20 @@
using NineChronicles.Headless.GraphTypes.States.Models.Item;
using NineChronicles.Headless.GraphTypes.States.Models.Item.Enum;
using NineChronicles.Headless.GraphTypes.States.Models.Table;
using StackExchange.Redis;

namespace NineChronicles.Headless.GraphTypes
{
public partial class StateQuery : ObjectGraphType<StateContext>
{
private readonly Codec _codec = new Codec();

public StateQuery()
private readonly IDatabase Database;

public StateQuery(IDatabase database)
{
Name = "StateQuery";
Database = database;

AvatarStateType.AvatarStateContext? GetAvatarState(StateContext context, Address address)
{
Expand Down Expand Up @@ -679,10 +684,10 @@ public StateQuery()
{
playerScore = (Integer)scores[1];
}
if (context.Source.StateMemoryCache.ArenaParticipantsCache.TryGetValue(cacheKey,
out var cachedResult))

if (Database.StringGet(cacheKey) is { } cachedResult)
{
result = (cachedResult as List<ArenaParticipant>)!;
result = JsonSerializer.Deserialize<List<ArenaParticipant>>(cachedResult.ToString())!;
foreach (var arenaParticipant in result)
{
var (win, lose, _) = ArenaHelper.GetScores(playerScore, arenaParticipant.Score);
Expand Down
Loading
Loading