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

Fix Issue 542: Implement CombatPoints, Crystal and CrystalPerPrice Calculation in ProductStateHandler #564

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
12 changes: 12 additions & 0 deletions Lib9c.Models/Extensions/EquipmentExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Bencodex.Types;
using Lib9c.Models.Items;

namespace Lib9c.Models.Extensions;

public static class EquipmentExtensions
{
public static Nekoyume.Model.Item.Equipment ToNekoyumeEquipment(this Equipment equipment)
{
return new Nekoyume.Model.Item.Equipment((Dictionary)equipment.Bencoded);
}
}
1 change: 1 addition & 0 deletions Mimir.Initializer/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class Configuration
public string? MongoDbCAFile { get; init; }
public string ChainStorePath { get; init; }
public string[] TargetAccounts { get; init; }
public RunOptions RunOptions { get; init; }

public Address[] GetTargetAddresses() =>
TargetAccounts.Select(adr => new Address(adr)).ToArray();
Expand Down
29 changes: 29 additions & 0 deletions Mimir.Initializer/HostExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Mimir.Initializer.Initializer;
using Mimir.Initializer.Migrators;

namespace Mimir.Initializer;

public static class HostExtensions
{
public static async Task RunSnapShotInitializerAsync(this IHost host)
{
using var scope = host.Services.CreateScope();
var initializer = scope.ServiceProvider.GetRequiredService<SnapshotInitializer>();

var stoppingToken = new CancellationTokenSource().Token;

await initializer.RunAsync(stoppingToken);
}

public static async Task RunProductMigratorAsync(this IHost host)
{
using var scope = host.Services.CreateScope();
var productMigrator = scope.ServiceProvider.GetRequiredService<ProductMigrator>();

var stoppingToken = new CancellationTokenSource().Token;

await productMigrator.AddCpAndCrystalsToProduct(stoppingToken);
}
}
70 changes: 70 additions & 0 deletions Mimir.Initializer/Migrators/ProductMigrator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Mimir.MongoDB;
using Mimir.MongoDB.Bson;
using Mimir.Worker.Services;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using Nekoyume.Model.Market;
using Serilog;
using ItemProduct = Lib9c.Models.Market.ItemProduct;

namespace Mimir.Initializer.Migrators;

public class ProductMigrator
{
private readonly MongoDbService _dbService;
private readonly IItemProductCalculationService _itemProductCalculationService;
private readonly ILogger _logger;

public ProductMigrator(MongoDbService dbService, IItemProductCalculationService itemProductCalculationService, ILogger logger)
{
_dbService = dbService;
_itemProductCalculationService = itemProductCalculationService;
_logger = logger;
}

public async Task AddCpAndCrystalsToProduct(CancellationToken cancellationToken)
{
var collectionName = CollectionNames.GetCollectionName<ProductDocument>();
var collection = _dbService.GetCollection(collectionName);

var builder = new FilterDefinitionBuilder<BsonDocument>();
var filter = builder.Not(builder.Exists(nameof(ProductDocument.Crystal)));
filter |= builder.Not(builder.Exists(nameof(ProductDocument.CombatPoint)));
filter = builder.And(filter, builder.Eq("Object.ProductType", Enum.GetName(ProductType.NonFungible)));

var asyncCursor = await collection.FindAsync(filter, cancellationToken: cancellationToken);
var bsonDocs = await asyncCursor.ToListAsync(cancellationToken: cancellationToken);

var updateDocuments = new List<WriteModel<BsonDocument>>();
_logger.Information("Updating {BsonDocsCount} ProductDocuments with CombatPoints and Crystals", bsonDocs.Count);
for (var index = 0; index < bsonDocs.Count; index++)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}

var productDocument = BsonSerializer.Deserialize<ProductDocument>(bsonDocs[index]);
if (productDocument?.Object is not ItemProduct itemProduct)
{
continue;
}

var (crystal, crystalPerPrice) = await _itemProductCalculationService.CalculateCrystalMetricsAsync(itemProduct);
var cp = await _itemProductCalculationService.CalculateCombatPointAsync(itemProduct);

var newProductDocument = productDocument with { Crystal = crystal, CrystalPerPrice = crystalPerPrice, CombatPoint = cp };
updateDocuments.Add(newProductDocument.ToUpdateOneModel());

_logger.Debug("\rUpdated of {Index} of {BsonDocsCount}", index + 1, bsonDocs.Count);
}

_logger.Information("Saving changed models to {CollectionName} collection", collectionName);

await _dbService.UpsertStateDataManyAsync(
collectionName,
updateDocuments,
cancellationToken: cancellationToken);
}
}
5 changes: 5 additions & 0 deletions Mimir.Initializer/Mimir.Initializer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@
<ProjectReference Include="..\Mimir.Worker\Mimir.Worker.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="appsettings.json">
Atralupus marked this conversation as resolved.
Show resolved Hide resolved
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
20 changes: 14 additions & 6 deletions Mimir.Initializer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
using Microsoft.Extensions.Options;
using Mimir.Initializer;
using Mimir.Initializer.Initializer;
using Mimir.Initializer.Migrators;
using Mimir.Worker.Services;
using Serilog;

var builder = Host.CreateDefaultBuilder(args)
var host = Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(
(hostingContext, config) =>
{
Expand Down Expand Up @@ -44,6 +45,9 @@
var targetAccounts = config.GetTargetAddresses();
return new SnapshotInitializer(dbService, config.ChainStorePath, targetAccounts);
});

services.AddSingleton<IItemProductCalculationService, ItemProductCalculationService>();
Atralupus marked this conversation as resolved.
Show resolved Hide resolved
services.AddSingleton<ProductMigrator>();
}
)
.UseSerilog(
Expand All @@ -54,9 +58,13 @@
)
.Build();

using var scope = builder.Services.CreateScope();
var initializer = scope.ServiceProvider.GetRequiredService<SnapshotInitializer>();
var config = host.Services.GetRequiredService<IOptions<Configuration>>().Value;

var stoppingToken = new CancellationTokenSource().Token;

await initializer.RunAsync(stoppingToken);
if(config.RunOptions.HasFlag(RunOptions.SnapShotInitializer))
Spoomer marked this conversation as resolved.
Show resolved Hide resolved
{
await host.RunSnapShotInitializerAsync();
}
if(config.RunOptions.HasFlag(RunOptions.ProductMigrator))
{
await host.RunProductMigratorAsync();
}
11 changes: 11 additions & 0 deletions Mimir.Initializer/RunOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Text.Json.Serialization;

namespace Mimir.Initializer;

[Flags]
[JsonConverter(typeof(JsonStringEnumConverter<RunOptions>))]
public enum RunOptions
{
SnapShotInitializer = 1,
ProductMigrator = 2
}
3 changes: 2 additions & 1 deletion Mimir.Initializer/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"000000000000000000000000000000000000001a",
"000000000000000000000000000000000000001b"
],
"EnableInitializing": true
"EnableInitializing": true,
"RunOptions" : "SnapShotInitializer, ProductMigrator"
}
}
128 changes: 22 additions & 106 deletions Mimir.Worker/ActionHandler/ProductStateHandler.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Text.RegularExpressions;
using Bencodex.Types;
using Lib9c.Models.Extensions;
using Lib9c.Models.Items;
using Lib9c.Models.Market;
using Libplanet.Crypto;
using Mimir.MongoDB.Bson;
Expand All @@ -9,6 +11,10 @@
using Mimir.Worker.Services;
using MongoDB.Bson;
using MongoDB.Driver;
using Nekoyume.Battle;
using Nekoyume.Helper;
using Nekoyume.TableData;
using Nekoyume.TableData.Crystal;
using Serilog;

namespace Mimir.Worker.ActionHandler;
Expand All @@ -17,7 +23,8 @@ public class ProductStateHandler(
IStateService stateService,
MongoDbService store,
IHeadlessGQLClient headlessGqlClient,
IInitializerManager initializerManager
IInitializerManager initializerManager,
IItemProductCalculationService itemProductCalculationService
)
: BaseActionHandler<ProductDocument>(
stateService,
Expand Down Expand Up @@ -122,7 +129,7 @@ await AddNewProductsAsync(
private async Task<List<Guid>> GetExistingProductIds(Address productsStateAddress)
{
var filter = Builders<BsonDocument>.Filter.Eq(
"Object.ProductsStateAddress",
"ProductsStateAddress",
productsStateAddress.ToHex()
);
var projection = Builders<BsonDocument>.Projection.Include("Object.ProductId");
Expand Down Expand Up @@ -151,7 +158,7 @@ private async Task<IEnumerable<WriteModel<BsonDocument>>> AddNewProductsAsync(
try
{
var product = await StateGetter.GetProductState(productId, stoppingToken);
var document = CreateProductDocumentAsync(
var document = await CreateProductDocumentAsync(
blockIndex,
avatarAddress,
productsStateAddress,
Expand All @@ -173,7 +180,7 @@ private async Task<IEnumerable<WriteModel<BsonDocument>>> AddNewProductsAsync(
return documents.Select(doc => doc.ToUpdateOneModel());
}

private ProductDocument CreateProductDocumentAsync(
private async Task<ProductDocument> CreateProductDocumentAsync(
long blockIndex,
Address avatarAddress,
Address productsStateAddress,
Expand All @@ -186,29 +193,19 @@ Product product
case ItemProduct itemProduct:
{
var unitPrice = CalculateUnitPrice(itemProduct);
// var combatPoint = await CalculateCombatPointAsync(itemProduct);
// var (crystal, crystalPerPrice) = await CalculateCrystalMetricsAsync(itemProduct);

// return new ProductDocument(
// productAddress,
// avatarAddress,
// productsStateAddress,
// product,
// unitPrice,
// combatPoint,
// crystal,
// crystalPerPrice
// );
var combatPoint = await itemProductCalculationService.CalculateCombatPointAsync(itemProduct);
var (crystal, crystalPerPrice) = await itemProductCalculationService.CalculateCrystalMetricsAsync(itemProduct);

return new ProductDocument(
blockIndex,
productAddress,
avatarAddress,
productsStateAddress,
product,
unitPrice,
null,
null,
null
combatPoint,
crystal,
crystalPerPrice
);
}
case FavProduct favProduct:
Expand Down Expand Up @@ -248,94 +245,13 @@ private static decimal CalculateUnitPrice(FavProduct favProduct)
return decimal.Parse(favProduct.Price.GetQuantityString())
/ decimal.Parse(favProduct.Asset.GetQuantityString());
}

// private async Task<int?> CalculateCombatPointAsync(ItemProduct itemProduct)
// {
// try
// {
// var costumeStatSheet = await Store.GetSheetAsync<CostumeStatSheet>();

// if (costumeStatSheet != null)
// {
// int? cp = itemProduct.TradableItem switch
// {
// ItemUsable itemUsable
// => CPHelper.GetCP(
// (Nekoyume.Model.Item.ItemUsable)
// Nekoyume.Model.Item.ItemFactory.Deserialize(
// (Dictionary)itemUsable.Bencoded
// )
// ),
// Costume costume => CPHelper.GetCP(new Nekoyume.Model.Item.Costume((Dictionary)costume.Bencoded), costumeStatSheet),
// _ => null
// };
// return cp;
// }
// }
// catch (Exception ex)
// {
// Logger.Error(
// $"Error calculating combat point for itemProduct {itemProduct.ProductId}: {ex.Message}"
// );
// }

// return null;
// }

// private async Task<(int? crystal, int? crystalPerPrice)> CalculateCrystalMetricsAsync(
// ItemProduct itemProduct
// )
// {
// try
// {
// var crystalEquipmentGrindingSheet =
// await Store.GetSheetAsync<CrystalEquipmentGrindingSheet>();
// var crystalMonsterCollectionMultiplierSheet =
// await Store.GetSheetAsync<CrystalMonsterCollectionMultiplierSheet>();

// if (
// crystalEquipmentGrindingSheet != null
// && crystalMonsterCollectionMultiplierSheet != null
// && itemProduct.TradableItem is Equipment equipment
// )
// {
// var rawCrystal = CrystalCalculator.CalculateCrystal(
// [equipment],
// false,
// crystalEquipmentGrindingSheet,
// crystalMonsterCollectionMultiplierSheet,
// 0
// );

// int crystal = (int)rawCrystal.MajorUnit;
// int crystalPerPrice = (int)
// rawCrystal.DivRem(itemProduct.Price.MajorUnit).Quotient.MajorUnit;

// return (crystal, crystalPerPrice);
// }
// }
// catch (Exception ex)
// {
// Logger.Error(
// $"Error calculating crystal metrics for itemProduct {itemProduct.ProductId}: {ex.Message}"
// );
// }

// return (null, null);
// }


private IEnumerable<WriteModel<BsonDocument>> RemoveOldProducts(List<Guid> productsToRemove)
{
var ops = new List<WriteModel<BsonDocument>>();
foreach (var productId in productsToRemove)
{
var productFilter = Builders<BsonDocument>.Filter.Eq(
"Object.TradableItem.TradableId",
productId.ToString()
var productFilter = Builders<BsonDocument>.Filter.In(
"Object.ProductId",
productsToRemove.Select(x=>x.ToString())
Spoomer marked this conversation as resolved.
Show resolved Hide resolved
);
ops.Add(new DeleteOneModel<BsonDocument>(productFilter));
}

return ops;
yield return new DeleteManyModel<BsonDocument>(productFilter);
Spoomer marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading
Loading