diff --git a/Satistools.Calculator.Test/CateriumProductsTest.cs b/Satistools.Calculator.Test/CateriumProductsTest.cs
new file mode 100644
index 0000000..83192ad
--- /dev/null
+++ b/Satistools.Calculator.Test/CateriumProductsTest.cs
@@ -0,0 +1,42 @@
+using System.Threading.Tasks;
+using FluentAssertions;
+using Microsoft.Extensions.DependencyInjection;
+using Satistools.Calculator.Graph;
+using Satistools.Calculator.Test.SetUp;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Satistools.Calculator.Test;
+
+public class CateriumProductsTest : CalcTest
+{
+ public CateriumProductsTest(CalculatorFactory factory, ITestOutputHelper testOutputHelper) : base(factory, testOutputHelper)
+ {
+ }
+
+ ///
+ /// Tests production of Caterium Ingot via alternate: Pure Caterium Ingot.
+ ///
+ [Fact]
+ public async Task Test_PureCateriumIngot()
+ {
+ IProductionCalculator calculator = ServiceProvider.GetRequiredService();
+ calculator.AddTargetProduct("Desc_GoldIngot_C", 36f);
+ calculator.UseAlternateRecipe("Recipe_Alternate_PureCateriumIngot_C");
+ ProductionGraph graph = await calculator.Calculate();
+
+ graph.Count.Should().Be(3);
+
+ GraphNode ingot = graph["Desc_GoldIngot_C"];
+ ingot.Product.TargetAmount.Should().Be(36f);
+ ingot.BuildingAmount.Should().Be(3f);
+ ingot.Building!.Id.Should().Be("Build_OilRefinery_C");
+ ingot.NeededProducts.Should().HaveCount(2);
+
+ GraphNode water = graph["Desc_Water_C"];
+ water.Product.TargetAmount.Should().Be(72f);
+
+ GraphNode ore = graph["Desc_OreGold_C"];
+ ore.Product.TargetAmount.Should().Be(72f);
+ }
+}
\ No newline at end of file
diff --git a/Satistools.Calculator.Test/IronProductsTest.cs b/Satistools.Calculator.Test/IronProductsTest.cs
new file mode 100644
index 0000000..e515a90
--- /dev/null
+++ b/Satistools.Calculator.Test/IronProductsTest.cs
@@ -0,0 +1,173 @@
+using System.Linq;
+using System.Threading.Tasks;
+using FluentAssertions;
+using Microsoft.Extensions.DependencyInjection;
+using Satistools.Calculator.Graph;
+using Satistools.Calculator.Test.SetUp;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Satistools.Calculator.Test;
+
+public class IronProductsTest : CalcTest
+{
+ public IronProductsTest(CalculatorFactory factory, ITestOutputHelper testOutputHelper) : base(factory, testOutputHelper)
+ {
+ }
+
+ ///
+ /// Tests calculation of basic iron ingot production.
+ ///
+ [Fact]
+ public async Task Test_IronIngot()
+ {
+ IProductionCalculator calculator = ServiceProvider.GetRequiredService();
+ calculator.AddTargetProduct("Desc_IronIngot_C", 30f);
+ ProductionGraph graph = await calculator.Calculate();
+
+ graph.Count.Should().Be(2);
+ GraphNode topNode = graph.First();
+ topNode.Product.TargetAmount.Should().Be(30f);
+ topNode.Product.Id.Should().Be("Desc_IronIngot_C");
+ topNode.NeededProducts.Should().HaveCount(1);
+ topNode.UsedBy.Should().HaveCount(0);
+
+ GraphNode lastNode = graph.Last();
+ lastNode.Product.TargetAmount.Should().Be(30f);
+ lastNode.Product.Id.Should().Be("Desc_OreIron_C");
+ lastNode.UsedBy.Should().HaveCount(1);
+ lastNode.NeededProducts.Should().HaveCount(0);
+
+ NodeRelation oreIsUsed = lastNode.UsedBy.First();
+ oreIsUsed.UnitsAmount.Should().Be(30f);
+ }
+
+ ///
+ /// Test production of bigger amount of iron plate.
+ ///
+ [Fact]
+ public async Task Test_IronPlate()
+ {
+ IProductionCalculator calculator = ServiceProvider.GetRequiredService();
+ calculator.AddTargetProduct("Desc_IronPlate_C", 50f);
+ ProductionGraph graph = await calculator.Calculate();
+
+ graph.Count.Should().Be(3);
+
+ GraphNode ingot = graph["Desc_IronIngot_C"];
+ ingot.UsedBy.Should().HaveCount(1);
+ ingot.NeededProducts.Should().HaveCount(1);
+ ingot.BuildingAmount.Should().Be(2.5f);
+
+ GraphNode plate = graph["Desc_IronPlate_C"];
+ plate.NeededProducts.Should().HaveCount(1);
+ plate.BuildingAmount.Should().Be(2.5f);
+ }
+
+ ///
+ /// Tests two target products. Iron Plate & Iron Rod.
+ ///
+ [Fact]
+ public async Task Test_DoubleProduction()
+ {
+ IProductionCalculator calculator = ServiceProvider.GetRequiredService();
+ calculator.AddTargetProduct("Desc_IronPlate_C", 20f);
+ calculator.AddTargetProduct("Desc_IronRod_C", 15f);
+ ProductionGraph graph = await calculator.Calculate();
+
+ graph.Count.Should().Be(4);
+
+ GraphNode ore = graph["Desc_OreIron_C"];
+ ore.Product.TargetAmount.Should().Be(45f);
+
+ GraphNode ingot = graph["Desc_IronIngot_C"];
+ ingot.Product.TargetAmount.Should().Be(45f);
+ ingot.UsedBy.Should().HaveCount(2);
+ }
+
+ ///
+ /// Tests production of Reinforced Iron Plate.
+ ///
+ [Fact]
+ public async Task Test_ReinforcedIronPlate()
+ {
+ IProductionCalculator calculator = ServiceProvider.GetRequiredService();
+ calculator.AddTargetProduct("Desc_IronPlateReinforced_C", 5f);
+ ProductionGraph graph = await calculator.Calculate();
+
+ graph.Should().HaveCount(6);
+
+ GraphNode ore = graph["Desc_OreIron_C"];
+ ore.Product.TargetAmount.Should().Be(60f);
+
+ GraphNode ingot = graph["Desc_IronIngot_C"];
+ ingot.Product.TargetAmount.Should().Be(60f);
+ ingot.UsedBy.Should().HaveCount(2);
+
+ GraphNode rip = graph["Desc_IronPlateReinforced_C"];
+ rip.NeededProducts.Should().HaveCount(2);
+ }
+
+ ///
+ /// Tests production of all advanced iron products - Rotors, Modular Frames & Reinforced Plates.
+ ///
+ [Fact]
+ public async Task Test_AdvancedIronProducts()
+ {
+ IProductionCalculator calculator = ServiceProvider.GetRequiredService();
+ calculator.AddTargetProduct("Desc_IronPlateReinforced_C", 5f);
+ calculator.AddTargetProduct("Desc_Rotor_C", 4f);
+ calculator.AddTargetProduct("Desc_ModularFrame_C", 2f);
+ ProductionGraph graph = await calculator.Calculate();
+
+ graph.Should().HaveCount(8);
+
+ GraphNode ingot = graph["Desc_IronIngot_C"];
+ ingot.Product.TargetAmount.Should().Be(153f);
+ ingot.UsedBy.Should().HaveCount(2);
+
+ GraphNode rip = graph["Desc_IronPlateReinforced_C"];
+ rip.Product.TargetAmount.Should().Be(8);
+ rip.NeededProducts.Should().HaveCount(2);
+ rip.UsedBy.Should().HaveCount(1);
+ }
+
+ ///
+ /// Just like previous test, but every manufactuerer is producing at 100%.
+ ///
+ [Fact]
+ public async Task Test_BalancedProduction()
+ {
+ IProductionCalculator calculator = ServiceProvider.GetRequiredService();
+ calculator.AddTargetProduct("Desc_IronPlateReinforced_C", 7f);
+ calculator.AddTargetProduct("Desc_Rotor_C", 4f);
+ calculator.AddTargetProduct("Desc_ModularFrame_C", 2f);
+ calculator.AddTargetProduct("Desc_IronScrew_C", 20f);
+ calculator.AddTargetProduct("Desc_IronRod_C", 13f);
+ calculator.AddTargetProduct("Desc_IronIngot_C", 15f);
+ ProductionGraph graph = await calculator.Calculate();
+
+ graph.Should().HaveCount(8);
+ graph.Sum(n => n.BuildingAmount).Should().Be(27f);
+ }
+
+ ///
+ /// Tests production of rotot via Alternate: Steel Rotor.
+ ///
+ [Fact]
+ public async Task Test_AlternateSteelRotor()
+ {
+ IProductionCalculator calculator = ServiceProvider.GetRequiredService();
+ calculator.AddTargetProduct("Desc_Rotor_C", 5f);
+ calculator.UseAlternateRecipe("Recipe_Alternate_Rotor_C");
+ ProductionGraph graph = await calculator.Calculate();
+
+ graph.Should().HaveCount(8);
+
+ GraphNode ironOre = graph["Desc_OreIron_C"];
+ ironOre.Product.TargetAmount.Should().Be(15f);
+
+ GraphNode coal = graph["Desc_Coal_C"];
+ coal.Product.TargetAmount.Should().Be(15f);
+ }
+}
\ No newline at end of file
diff --git a/Satistools.Calculator.Test/OilProductsTest.cs b/Satistools.Calculator.Test/OilProductsTest.cs
new file mode 100644
index 0000000..2b553c6
--- /dev/null
+++ b/Satistools.Calculator.Test/OilProductsTest.cs
@@ -0,0 +1,48 @@
+using System.Threading.Tasks;
+using FluentAssertions;
+using Microsoft.Extensions.DependencyInjection;
+using Satistools.Calculator.Graph;
+using Satistools.Calculator.Test.SetUp;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Satistools.Calculator.Test;
+
+public class OilProductsTest : CalcTest
+{
+ public OilProductsTest(CalculatorFactory factory, ITestOutputHelper testOutputHelper) : base(factory, testOutputHelper)
+ {
+ }
+
+ ///
+ /// Tests production of plastic.
+ ///
+ [Fact]
+ public async Task Test_PlasticProduction()
+ {
+ IProductionCalculator calculator = ServiceProvider.GetRequiredService();
+ calculator.AddTargetProduct("Desc_Plastic_C", 20f);
+ ProductionGraph graph = await calculator.Calculate();
+
+ graph.Should().HaveCount(2);
+
+ GraphNode plastic = graph["Desc_Plastic_C"];
+ plastic.Byproduct.Should().NotBeNull();
+ plastic.Byproduct!.TargetAmount.Should().Be(10f);
+
+ GraphNode residue = graph["Desc_HeavyOilResidue_C"]; // We are able to find the node also by byproduct
+ residue.ProductId.Should().Be(plastic.ProductId);
+ residue.ByproductId.Should().Be(residue.ByproductId);
+ }
+
+ [Fact]
+ public async Task Test_PlasticAndFuel()
+ {
+ IProductionCalculator calculator = ServiceProvider.GetRequiredService();
+ calculator.AddTargetProduct("Desc_Plastic_C", 20f);
+ calculator.AddTargetProduct("Desc_LiquidFuel_C", 6.66f);
+ ProductionGraph graph = await calculator.Calculate();
+
+ graph.Should().HaveCount(3);
+ }
+}
\ No newline at end of file
diff --git a/Satistools.Calculator.Test/Satistools.Calculator.Test.csproj b/Satistools.Calculator.Test/Satistools.Calculator.Test.csproj
new file mode 100644
index 0000000..381ac49
--- /dev/null
+++ b/Satistools.Calculator.Test/Satistools.Calculator.Test.csproj
@@ -0,0 +1,37 @@
+
+
+
+ net6.0
+ enable
+
+ false
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
+ FactoryGame.json
+ PreserveNewest
+
+
+
+
diff --git a/Satistools.Calculator.Test/SetUp/CalcTest.cs b/Satistools.Calculator.Test/SetUp/CalcTest.cs
new file mode 100644
index 0000000..80862bb
--- /dev/null
+++ b/Satistools.Calculator.Test/SetUp/CalcTest.cs
@@ -0,0 +1,12 @@
+using Sagittaras.Model.TestFramework;
+using Satistools.GameData;
+using Xunit.Abstractions;
+
+namespace Satistools.Calculator.Test.SetUp;
+
+public abstract class CalcTest : UnitTest
+{
+ protected CalcTest(CalculatorFactory factory, ITestOutputHelper testOutputHelper) : base(factory, testOutputHelper)
+ {
+ }
+}
\ No newline at end of file
diff --git a/Satistools.Calculator.Test/SetUp/CalculatorFactory.cs b/Satistools.Calculator.Test/SetUp/CalculatorFactory.cs
new file mode 100644
index 0000000..245e9e4
--- /dev/null
+++ b/Satistools.Calculator.Test/SetUp/CalculatorFactory.cs
@@ -0,0 +1,27 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Sagittaras.Model.TestFramework;
+using Sagittaras.Repository.Extensions;
+using Satistools.Calculator.Extensions;
+using Satistools.GameData;
+using Satistools.GameData.Items;
+using Satistools.GameData.Recipes;
+
+namespace Satistools.Calculator.Test.SetUp;
+
+public class CalculatorFactory : TestFactory
+{
+ protected override void OnConfiguring(ServiceCollection services)
+ {
+ services.AddDbContext(options =>
+ {
+ options.UseInMemoryDatabase(GetConnectionString(Engine.InMemory));
+ });
+ services.UseRepositoryPattern(options =>
+ {
+ options.AddRepository();
+ options.AddRepository();
+ });
+ services.AddCalculator();
+ }
+}
\ No newline at end of file
diff --git a/Satistools.Calculator/AnalyseResult.cs b/Satistools.Calculator/AnalyseResult.cs
new file mode 100644
index 0000000..c75e4d8
--- /dev/null
+++ b/Satistools.Calculator/AnalyseResult.cs
@@ -0,0 +1,23 @@
+using Satistools.Calculator.Graph;
+
+namespace Satistools.Calculator;
+
+public readonly struct AnalyseResult
+{
+ public GraphNode ProductNode { get; }
+ public float ProductAmount { get; }
+ public IEnumerable Byproducts { get; } = Array.Empty();
+
+ public AnalyseResult(GraphNode productNode, float productAmount)
+ {
+ ProductNode = productNode;
+ ProductAmount = productAmount;
+ }
+
+ public AnalyseResult(GraphNode productNode, float productAmount, IEnumerable byproducts)
+ {
+ ProductNode = productNode;
+ ProductAmount = productAmount;
+ Byproducts = byproducts;
+ }
+}
\ No newline at end of file
diff --git a/Satistools.Calculator/Extensions/ServiceExtension.cs b/Satistools.Calculator/Extensions/ServiceExtension.cs
new file mode 100644
index 0000000..9c0097c
--- /dev/null
+++ b/Satistools.Calculator/Extensions/ServiceExtension.cs
@@ -0,0 +1,11 @@
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Satistools.Calculator.Extensions;
+
+public static class ServiceExtension
+{
+ public static void AddCalculator(this IServiceCollection services)
+ {
+ services.AddTransient();
+ }
+}
\ No newline at end of file
diff --git a/Satistools.Calculator/Graph/GraphNode.cs b/Satistools.Calculator/Graph/GraphNode.cs
new file mode 100644
index 0000000..f431a98
--- /dev/null
+++ b/Satistools.Calculator/Graph/GraphNode.cs
@@ -0,0 +1,119 @@
+using Satistools.GameData.Buildings;
+using Satistools.GameData.Items;
+
+namespace Satistools.Calculator.Graph;
+
+///
+/// A single node of network graph for production.
+///
+public class GraphNode
+{
+ ///
+ /// Initializes a new node in production graph.
+ ///
+ /// Building needed for the production of selected item.
+ /// How many buildings are needed to produce target amount.
+ /// Item which is being produced by this node.
+ /// How units of the product is needed per minute.
+ public GraphNode(Building? building, float buildingAmount, Item product, float productAmount)
+ {
+ Building = building;
+ BuildingAmount = buildingAmount;
+
+ Product = new NodeProduct(product, productAmount);
+ }
+
+ public GraphNode(Building? building, float buildingAmount, Item product, float productAmount, Item byproduct, float byproductAmount)
+ {
+ Building = building;
+ BuildingAmount = buildingAmount;
+
+ Product = new NodeProduct(product, productAmount);
+ Byproduct = new NodeProduct(byproduct, byproductAmount);
+ }
+
+ ///
+ /// ID of item which is produced as the main product.
+ ///
+ public string ProductId => Product.Item.Id;
+
+ ///
+ /// ID of byproduct if exists.
+ ///
+ public string ByproductId => Byproduct?.Item.Id ?? string.Empty;
+
+ ///
+ /// In which building is the product manufactured.
+ ///
+ public Building? Building { get; }
+
+ ///
+ /// How many buildings are required.
+ ///
+ public float BuildingAmount { get; set; }
+
+ ///
+ /// Informations about item produced by this node.
+ ///
+ public NodeProduct Product { get; }
+
+ ///
+ /// Informations about byproduct if it's produced by the recipe.
+ ///
+ public NodeProduct? Byproduct { get; }
+
+ ///
+ /// List of all nodes which are using the selected product.
+ ///
+ public ICollection UsedBy { get; } = new List();
+
+ ///
+ /// List of all nodes which are used for the product of this one.
+ ///
+ public ICollection NeededProducts { get; } = new List();
+
+ ///
+ /// Checks if the node is producing the item.
+ ///
+ /// Item identification.
+ /// True if the item is produced as product or byproduct.
+ public bool Produces(string itemId)
+ {
+ return ProductId == itemId || ByproductId == itemId;
+ }
+
+ public void UpdateUsage(NodeRelation nodeRelation)
+ {
+ NodeRelation? existing = UsedBy.SingleOrDefault(u => u.TargetNode.ProductId == nodeRelation.TargetNode.ProductId);
+ if (existing is not null)
+ {
+ existing.UnitsAmount += nodeRelation.UnitsAmount;
+ return;
+ }
+
+ UsedBy.Add(nodeRelation);
+ }
+
+ public void UpdateNeeds(NodeRelation nodeRelation)
+ {
+ NodeRelation? existing = NeededProducts.SingleOrDefault(u => u.TargetNode.ProductId == nodeRelation.TargetNode.ProductId);
+ if (existing is not null)
+ {
+ existing.UnitsAmount += nodeRelation.UnitsAmount;
+ return;
+ }
+
+ NeededProducts.Add(nodeRelation);
+ }
+
+ public override string ToString()
+ {
+ string description = $"{Product.Item.DisplayName} {Product.TargetAmount}/min ({Product.UsedAmount} used) ({Product.PercentageUsage}%)";
+ if (Building is not null)
+ {
+ description += $"; {BuildingAmount}x {Building.DisplayName}";
+ }
+
+ return description;
+ }
+}
\ No newline at end of file
diff --git a/Satistools.Calculator/Graph/NodeProduct.cs b/Satistools.Calculator/Graph/NodeProduct.cs
new file mode 100644
index 0000000..93c0664
--- /dev/null
+++ b/Satistools.Calculator/Graph/NodeProduct.cs
@@ -0,0 +1,35 @@
+using Satistools.GameData.Items;
+
+namespace Satistools.Calculator.Graph;
+
+public class NodeProduct
+{
+ public NodeProduct(Item item, float targetAmount)
+ {
+ Item = item;
+ TargetAmount = targetAmount;
+ UsedAmount = 0;
+ }
+
+ public string Id => Item.Id;
+
+ ///
+ /// Which item is being produced by this node.
+ ///
+ public Item Item { get; }
+
+ ///
+ /// How many parts of the product are being produced by minute.
+ ///
+ public float TargetAmount { get; set; }
+
+ ///
+ /// How many units of the product is used.
+ ///
+ public float UsedAmount { get; set; }
+
+ ///
+ /// How many percents of the production is used.
+ ///
+ public float PercentageUsage => UsedAmount / TargetAmount * 100;
+}
\ No newline at end of file
diff --git a/Satistools.Calculator/Graph/NodeRelation.cs b/Satistools.Calculator/Graph/NodeRelation.cs
new file mode 100644
index 0000000..b6a788d
--- /dev/null
+++ b/Satistools.Calculator/Graph/NodeRelation.cs
@@ -0,0 +1,35 @@
+using Satistools.GameData.Items;
+
+namespace Satistools.Calculator.Graph;
+
+///
+/// Represents a single relation between nodes in both directions.
+///
+public class NodeRelation
+{
+ ///
+ /// Initializes a new relation between two nodes.
+ ///
+ /// The target node of the relation.
+ /// How many units is required in the relation.
+ public NodeRelation(GraphNode targetNode, float unitsAmount)
+ {
+ TargetNode = targetNode;
+ UnitsAmount = unitsAmount;
+ }
+
+ ///
+ /// Reference to the other node.
+ ///
+ public GraphNode TargetNode { get; }
+
+ ///
+ /// How many units of the product are need by the target node.
+ ///
+ public float UnitsAmount { get; set; }
+
+ public override string ToString()
+ {
+ return $"{TargetNode.Product.Item.DisplayName} {UnitsAmount} units/min";
+ }
+}
\ No newline at end of file
diff --git a/Satistools.Calculator/Graph/ProductionGraph.cs b/Satistools.Calculator/Graph/ProductionGraph.cs
new file mode 100644
index 0000000..22f288d
--- /dev/null
+++ b/Satistools.Calculator/Graph/ProductionGraph.cs
@@ -0,0 +1,87 @@
+using System.Collections;
+
+namespace Satistools.Calculator.Graph;
+
+///
+/// A collection of all nodes representing network graph describing how to produce selected items.
+///
+public class ProductionGraph : IEnumerable
+{
+ ///
+ /// List of all nodes inside the graph.
+ ///
+ private readonly Dictionary _nodes = new();
+
+ ///
+ /// Gets node by the ID of item which is producing.
+ ///
+ /// Identification of the produced item by node.
+ public GraphNode this[string id]
+ {
+ get { return _nodes.Values.Single(n => n.Produces(id)); }
+ }
+
+ ///
+ /// How many nodes are already in the graph.
+ ///
+ public int Count => _nodes.Count;
+
+ ///
+ /// Adds new node without any relation. Or update the existing one.
+ ///
+ /// Instance of new node.
+ public GraphNode AddOrUpdate(GraphNode node)
+ {
+ if (_nodes.ContainsKey(node.ProductId))
+ {
+ GraphNode existing = _nodes[node.ProductId];
+ existing.BuildingAmount += node.BuildingAmount;
+
+ existing.Product.TargetAmount += node.Product.TargetAmount;
+ if (node.Byproduct is not null)
+ {
+ existing.Byproduct!.TargetAmount += node.Byproduct.TargetAmount;
+ }
+
+ return existing;
+ }
+
+ _nodes.Add(node.ProductId, node);
+ return node;
+ }
+
+ ///
+ /// Creates a relation between two nodes.
+ ///
+ /// Instance of node which is used by the product.
+ ///
+ /// Amount of product parts used in this relation.
+ public void NodeIsUsedBy(GraphNode node, NodeProduct product, float amount)
+ {
+ // node is like Iron Ore & neededNode is like Iron Ingot
+ GraphNode neededNode = this[product.Id];
+ neededNode.UpdateNeeds(new NodeRelation(node, amount));
+ node.UpdateUsage(new NodeRelation(neededNode, amount));
+
+ node.Product.UsedAmount += amount;
+ }
+
+ /*public void NodeNeedsProduct(GraphNode node, string id, float amount)
+ {
+ GraphNode usedNode = this[id];
+ usedNode.UpdateUsage(new NodeRelation(node, amount));
+ node.UpdateNeeds(new NodeRelation(usedNode, amount));
+ }*/
+
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ return _nodes.Values.GetEnumerator();
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+}
\ No newline at end of file
diff --git a/Satistools.Calculator/IProductionCalculator.cs b/Satistools.Calculator/IProductionCalculator.cs
new file mode 100644
index 0000000..aa47c80
--- /dev/null
+++ b/Satistools.Calculator/IProductionCalculator.cs
@@ -0,0 +1,25 @@
+using Satistools.Calculator.Graph;
+
+namespace Satistools.Calculator;
+
+public interface IProductionCalculator
+{
+ ///
+ /// Adds a new final product to the calculator.
+ ///
+ /// ID of item which will be final.
+ /// How many parts of the product should be produced.
+ void AddTargetProduct(string itemId, float amount);
+
+ ///
+ /// Adds a new alternate recipe for calculations.
+ ///
+ /// Identification of the recipe.
+ void UseAlternateRecipe(string recipeId);
+
+ ///
+ /// Calculates the production graph for the set inputs.
+ ///
+ /// Returns an instance of calculated graph of production.
+ Task Calculate();
+}
\ No newline at end of file
diff --git a/Satistools.Calculator/ProductionCalculator.cs b/Satistools.Calculator/ProductionCalculator.cs
new file mode 100644
index 0000000..3fff1d0
--- /dev/null
+++ b/Satistools.Calculator/ProductionCalculator.cs
@@ -0,0 +1,136 @@
+using Satistools.Calculator.Graph;
+using Satistools.GameData.Items;
+using Satistools.GameData.Recipes;
+
+namespace Satistools.Calculator;
+
+///
+/// Calculates graph for production of needed products.
+///
+public class ProductionCalculator : IProductionCalculator
+{
+ private readonly IRecipeRepository _recipeRepository;
+ private readonly IItemRepository _itemRepository;
+
+ ///
+ /// Contains IDs of all items which production should calculated in Tuple with target amount.
+ ///
+ private readonly List<(string, float)> _targetIds = new();
+
+ ///
+ /// Contains IDs of used alternate recipes for calculations.
+ ///
+ private readonly List _alternateIds = new();
+
+ ///
+ /// Contains all found alternate recipes.
+ ///
+ private readonly List _alternateRecipes = new();
+
+ public ProductionCalculator(IRecipeRepository recipeRepository, IItemRepository itemRepository)
+ {
+ _recipeRepository = recipeRepository;
+ _itemRepository = itemRepository;
+ }
+
+ ///
+ public void AddTargetProduct(string itemId, float amount)
+ {
+ _targetIds.Add((itemId, amount));
+ }
+
+ ///
+ public void UseAlternateRecipe(string recipeId)
+ {
+ _alternateIds.Add(recipeId);
+ }
+
+ ///
+ public async Task Calculate()
+ {
+ if (_alternateIds.Count > 0)
+ {
+ _alternateRecipes.AddRange(await _recipeRepository.FindByIds(_alternateIds));
+ }
+
+ ProductionGraph graph = new();
+
+ foreach ((string targetId, float targetAmount) in _targetIds)
+ {
+ Item? item = await _itemRepository.Get(targetId);
+ if (item is null)
+ {
+ throw new Exception($"Target item {targetId} was not found");
+ }
+
+ Recipe? recipe = await GetRecipe(targetId);
+ if (recipe is null)
+ {
+ throw new Exception($"Recipe for {item.Id} was not found");
+ }
+
+ RecipeProduct product = recipe.GetProduct(targetId);
+ float productionRate = targetAmount / product.AmountPerMin;
+ await AnalyseRecipePart(graph, product, productionRate);
+ }
+
+ return graph;
+ }
+
+ private async Task AnalyseRecipePart(ProductionGraph graph, RecipePart part, float productionRate)
+ {
+ float amount;
+ GraphNode node;
+
+ Recipe? recipe = await GetRecipe(part.ItemId);
+ if (recipe is null)
+ {
+ // Recipe is not available, thus this item must be resource.
+ amount = part.AmountPerMin * productionRate;
+ node = new GraphNode(null, 0, part.Item, amount);
+ return new AnalyseResult(graph.AddOrUpdate(node), amount);
+ }
+
+ RecipeProduct product = recipe.GetProduct(part.ItemId);
+ amount = part.AmountPerMin * productionRate;
+ float buildingsCount = amount / product.AmountPerMin; // Serves also as the production rate for the next ingredient.
+
+ RecipeProduct? byproduct = recipe.Products.FirstOrDefault(p => p.ItemId != part.ItemId);
+
+ node = byproduct is null
+ ? new GraphNode(recipe.ProducedIn, buildingsCount, part.Item, amount)
+ : new GraphNode(recipe.ProducedIn, buildingsCount, part.Item, amount, byproduct.Item, byproduct.AmountPerMin * productionRate);
+
+ node = graph.AddOrUpdate(node);
+
+ foreach (RecipeIngredient ingredient in recipe.Ingredients)
+ {
+ AnalyseResult result = await AnalyseRecipePart(graph, ingredient, buildingsCount);
+ graph.NodeIsUsedBy(result.ProductNode, node.Product, result.ProductAmount);
+ }
+
+ return new AnalyseResult(node, amount);
+ }
+
+ ///
+ /// Gets recipe for the product.
+ ///
+ ///
+ /// First searches in the list of used alternate recipes, then queries the database for the original one.
+ ///
+ /// Identification of product.
+ /// Found recipe or null if the product does not have any recipe.
+ private async Task GetRecipe(string productId)
+ {
+ if (_alternateRecipes.Count > 0)
+ {
+ Recipe? alternate = _alternateRecipes.Find(r => r.Products.Any(p => p.ItemId == productId));
+ if (alternate is not null)
+ {
+ return alternate;
+ }
+ }
+
+ return await _recipeRepository.GetOriginalRecipe(productId);
+ }
+}
\ No newline at end of file
diff --git a/Satistools.Model.Repository/Satistools.Model.Repository.csproj b/Satistools.Calculator/Satistools.Calculator.csproj
similarity index 74%
rename from Satistools.Model.Repository/Satistools.Model.Repository.csproj
rename to Satistools.Calculator/Satistools.Calculator.csproj
index c4d18ef..7506567 100644
--- a/Satistools.Model.Repository/Satistools.Model.Repository.csproj
+++ b/Satistools.Calculator/Satistools.Calculator.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/Satistools.GameData.Test/Satistools.GameData.Test.csproj b/Satistools.GameData.Test/Satistools.GameData.Test.csproj
index 3177df8..67d546e 100644
--- a/Satistools.GameData.Test/Satistools.GameData.Test.csproj
+++ b/Satistools.GameData.Test/Satistools.GameData.Test.csproj
@@ -9,7 +9,9 @@
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -23,7 +25,6 @@
-
diff --git a/Satistools.GameData.Test/SetUp/GameDataFactory.cs b/Satistools.GameData.Test/SetUp/GameDataFactory.cs
index f6b9e44..9ceb154 100644
--- a/Satistools.GameData.Test/SetUp/GameDataFactory.cs
+++ b/Satistools.GameData.Test/SetUp/GameDataFactory.cs
@@ -1,6 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
-using Satistools.ModelTest;
+using Sagittaras.Model.TestFramework;
namespace Satistools.GameData.Test.SetUp;
@@ -10,7 +10,7 @@ protected override void OnConfiguring(ServiceCollection services)
{
services.AddDbContext(options =>
{
- options.UseInMemoryDatabase(GetDatabaseName());
+ options.UseInMemoryDatabase(GetConnectionString(Engine.InMemory));
});
}
}
\ No newline at end of file
diff --git a/Satistools.GameData.Test/SetUp/GameDataTest.cs b/Satistools.GameData.Test/SetUp/GameDataTest.cs
index 35437cb..f6a5c26 100644
--- a/Satistools.GameData.Test/SetUp/GameDataTest.cs
+++ b/Satistools.GameData.Test/SetUp/GameDataTest.cs
@@ -1,4 +1,4 @@
-using Satistools.ModelTest;
+using Sagittaras.Model.TestFramework;
using Xunit.Abstractions;
namespace Satistools.GameData.Test.SetUp;
@@ -6,7 +6,7 @@ namespace Satistools.GameData.Test.SetUp;
///
/// TestFixture implementation for tests of GameData.
///
-public abstract class GameDataTest : TestFixture
+public abstract class GameDataTest : UnitTest
{
protected GameDataTest(GameDataFactory factory, ITestOutputHelper testOutputHelper) : base(factory, testOutputHelper)
{
diff --git a/Satistools.GameData/Extensions/ServiceExtension.cs b/Satistools.GameData/Extensions/ServiceExtension.cs
index e9cebeb..257d57d 100644
--- a/Satistools.GameData/Extensions/ServiceExtension.cs
+++ b/Satistools.GameData/Extensions/ServiceExtension.cs
@@ -1,10 +1,9 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Sagittaras.Repository.Extensions;
using Satistools.GameData.Items;
using Satistools.GameData.Recipes;
-using Satistools.Model.Repository;
-using Satistools.Model.Repository.Extensions;
namespace Satistools.GameData.Extensions;
@@ -21,7 +20,6 @@ public static void AddGameDataModel(this IServiceCollection services, IConfigura
{
options.UseSqlite(configuration.GetConnectionString("sqlite"));
});
- services.AddScoped(b => b.GetRequiredService());
services.UseRepositoryPattern(options =>
{
options.AddRepository();
diff --git a/Satistools.GameData/GameDataContext.cs b/Satistools.GameData/GameDataContext.cs
index 1c3141d..f252a2d 100644
--- a/Satistools.GameData/GameDataContext.cs
+++ b/Satistools.GameData/GameDataContext.cs
@@ -9,11 +9,10 @@
using Satistools.GameData.Items;
using Satistools.GameData.Recipes;
using Satistools.GameData.Recipes.Mappers;
-using Satistools.Model.Repository;
namespace Satistools.GameData;
-public class GameDataContext : RepositoryContext
+public class GameDataContext : DbContext
{
///
/// If the context is already preconfigured, the database is not populated with game date.
@@ -37,9 +36,8 @@ public GameDataContext()
public GameDataContext(
DbContextOptions options,
- IConfiguration configuration,
- IEnumerable repositories
- ) : base(options, repositories)
+ IConfiguration configuration
+ ) : base(options)
{
_isDevelopment = _populateData = configuration["ASPNETCORE_ENVIRONMENT"] == "Development";
}
@@ -70,7 +68,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
return;
}
- FactoryGameReader reader = new(Path.Combine(Directory.GetCurrentDirectory(), ".."), "FactoryGame.json");
+ FactoryGameReader reader = new(Directory.GetCurrentDirectory(), "FactoryGame.json");
List- items = ImportItems(modelBuilder, reader);
IEnumerable buildings = ImportBuildings(modelBuilder, reader);
ImportRecipes(modelBuilder, reader, buildings, items);
diff --git a/Satistools.GameData/Items/IItemRepository.cs b/Satistools.GameData/Items/IItemRepository.cs
index 76c73c2..8a3007f 100644
--- a/Satistools.GameData/Items/IItemRepository.cs
+++ b/Satistools.GameData/Items/IItemRepository.cs
@@ -1,4 +1,4 @@
-using Satistools.Model.Repository;
+using Sagittaras.Repository;
namespace Satistools.GameData.Items;
diff --git a/Satistools.GameData/Items/ItemRepository.cs b/Satistools.GameData/Items/ItemRepository.cs
index bfb7864..307129c 100644
--- a/Satistools.GameData/Items/ItemRepository.cs
+++ b/Satistools.GameData/Items/ItemRepository.cs
@@ -1,12 +1,12 @@
using Microsoft.EntityFrameworkCore;
+using Sagittaras.Repository;
using Satistools.DataReader.Entities.Items;
-using Satistools.Model.Repository;
namespace Satistools.GameData.Items;
public class ItemRepository : Repository
- , IItemRepository
{
- public ItemRepository(RepositoryContext dbContext) : base(dbContext)
+ public ItemRepository(DbContext dbContext) : base(dbContext)
{
}
diff --git a/Satistools.GameData/Recipes/IRecipeRepository.cs b/Satistools.GameData/Recipes/IRecipeRepository.cs
index 6955f3c..df30fc2 100644
--- a/Satistools.GameData/Recipes/IRecipeRepository.cs
+++ b/Satistools.GameData/Recipes/IRecipeRepository.cs
@@ -1,9 +1,26 @@
-using Satistools.Model.Repository;
+using Sagittaras.Repository;
namespace Satistools.GameData.Recipes;
public interface IRecipeRepository : IRepository
{
+ ///
+ /// Gets the original which is used for production of the item.
+ ///
+ ///
+ /// By original recipe is meant the one, which is not marked as alternate.
+ ///
+ /// ID of item used for production of the item.
+ /// Instance of original recipe for the item or null, if there is no original recipe.
+ Task GetOriginalRecipe(string itemId);
+
+ ///
+ /// Search recipes by their IDs.
+ ///
+ /// Enumerable of all IDs which should be found.
+ /// Enumeration of all found recipes.
+ Task> FindByIds(IEnumerable recipeIds);
+
///
/// Finds all recipes which are producing selected item.
///
diff --git a/Satistools.GameData/Recipes/Mappers/RecipeMapper.cs b/Satistools.GameData/Recipes/Mappers/RecipeMapper.cs
index b26a726..ba40405 100644
--- a/Satistools.GameData/Recipes/Mappers/RecipeMapper.cs
+++ b/Satistools.GameData/Recipes/Mappers/RecipeMapper.cs
@@ -9,6 +9,18 @@ namespace Satistools.GameData.Recipes.Mappers;
///
public static class RecipeMapper
{
+ ///
+ /// List of recipes which is alternate to the original one, yet they don't need to be unlocked via HDD
+ /// and are not by default marked as alternate.
+ ///
+ private static readonly List OriginalAlternatives = new()
+ {
+ "Recipe_ResidualPlastic_C",
+ "Recipe_ResidualRubber_C",
+ "Recipe_LiquidFuel_C",
+ "Recipe_UnpackageFuel_C" // TODO: All unpackaging recipes should not be default choice
+ };
+
public static IMapper Create(IEnumerable buildings)
{
return new MapperConfiguration(cfg =>
@@ -22,7 +34,13 @@ public static IMapper Create(IEnumerable buildings)
.ForMember(d => d.Products, opt => opt.Ignore())
.ForMember(d => d.ProducedInId, opt => opt.MapFrom(src => src.ProducedIn.Where(p => buildings.Any(b => b.ClassName == p.ClassName)).Select(p => p.ClassName).Single()))
.ForMember(d => d.ProducedIn, opt => opt.Ignore())
- .ForMember(d => d.IsAlternate, opt => opt.MapFrom(src => src.ClassName.Contains("_Alternate_")));
+ .ForMember(d => d.IsAlternate, opt => opt.MapFrom(src => src.ClassName.Contains("_Alternate_")))
+ .ForMember(d => d.IsDefault, opt => opt.MapFrom(src => IsRecipeDefault(src)));
}).CreateMapper();
}
+
+ private static bool IsRecipeDefault(RecipeDescriptor descriptor)
+ {
+ return !descriptor.ClassName.Contains("_Alternate_") && OriginalAlternatives.All(d => d != descriptor.ClassName);
+ }
}
\ No newline at end of file
diff --git a/Satistools.GameData/Recipes/Recipe.cs b/Satistools.GameData/Recipes/Recipe.cs
index a5c8550..4533e0c 100644
--- a/Satistools.GameData/Recipes/Recipe.cs
+++ b/Satistools.GameData/Recipes/Recipe.cs
@@ -36,6 +36,11 @@ public class Recipe
/// Marks if the recipe is alternative.
///
public bool IsAlternate { get; set; }
+
+ ///
+ /// Marks if the recipe is default choice when there is multiple alternative unlocked by default.
+ ///
+ public bool IsDefault { get; set; }
///
///
@@ -44,4 +49,14 @@ public class Recipe
public ICollection Ingredients { get; set; } = null!;
public ICollection Products { get; set; } = null!;
+
+ ///
+ /// Gets the recipe product by ID of item.
+ ///
+ /// Item identification.
+ /// Found recipe product.
+ public RecipeProduct GetProduct(string id)
+ {
+ return Products.Single(p => p.ItemId == id);
+ }
}
\ No newline at end of file
diff --git a/Satistools.GameData/Recipes/RecipeRepository.cs b/Satistools.GameData/Recipes/RecipeRepository.cs
index ff33e8c..7799dbb 100644
--- a/Satistools.GameData/Recipes/RecipeRepository.cs
+++ b/Satistools.GameData/Recipes/RecipeRepository.cs
@@ -1,11 +1,12 @@
using Microsoft.EntityFrameworkCore;
-using Satistools.Model.Repository;
+using Sagittaras.Repository;
+using Satistools.DataReader.Entities.Items;
namespace Satistools.GameData.Recipes;
public class RecipeRepository : Repository, IRecipeRepository
{
- public RecipeRepository(RepositoryContext dbContext) : base(dbContext)
+ public RecipeRepository(DbContext dbContext) : base(dbContext)
{
}
@@ -20,18 +21,22 @@ public RecipeRepository(RepositoryContext dbContext) : base(dbContext)
.AsSplitQuery();
///
- public async Task> FindOrderedByName()
+ public async Task GetOriginalRecipe(string itemId)
{
- return await FullInfoSource
- .OrderBy(r => r.DisplayName)
- .ToListAsync();
+ return await FullInfoSource.SingleOrDefaultAsync(r => r.IsDefault && !r.IsAlternate && r.Products.Any(p => p.ItemId == itemId && p.Item.ItemCategory != ItemCategory.Resource));
+ }
+
+ ///
+ public async Task> FindByIds(IEnumerable recipeIds)
+ {
+ return await FullInfoSource.Where(r => recipeIds.Contains(r.Id)).ToListAsync();
}
///
public async Task> FindRecipesProducingItem(string itemId)
{
return await (from recipe in FullInfoSource
- join product in JoinDbSet() on recipe.Id equals product.RecipeId
+ join product in Join() on recipe.Id equals product.RecipeId
where product.ItemId == itemId
select recipe).ToListAsync();
}
@@ -40,7 +45,7 @@ join product in JoinDbSet() on recipe.Id equals product.RecipeId
public async Task> FindRecipesUsingItem(string itemId)
{
return await (from recipe in FullInfoSource
- join ingredient in JoinDbSet() on recipe.Id equals ingredient.RecipeId
+ join ingredient in Join() on recipe.Id equals ingredient.RecipeId
where ingredient.ItemId == itemId
select recipe).ToListAsync();
}
diff --git a/Satistools.GameData/Satistools.GameData.csproj b/Satistools.GameData/Satistools.GameData.csproj
index 08fbc23..24b94c8 100644
--- a/Satistools.GameData/Satistools.GameData.csproj
+++ b/Satistools.GameData/Satistools.GameData.csproj
@@ -14,6 +14,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
+
@@ -25,7 +26,6 @@
-
diff --git a/Satistools.Model.Repository/Extensions/ServiceExtension.cs b/Satistools.Model.Repository/Extensions/ServiceExtension.cs
deleted file mode 100644
index e88119a..0000000
--- a/Satistools.Model.Repository/Extensions/ServiceExtension.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-
-namespace Satistools.Model.Repository.Extensions
-{
- ///
- ///
- ///
- public static class ServiceExtension
- {
- ///
- /// Enables usage of repository pattern with the data model.
- ///
- ///
- ///
- public static void UseRepositoryPattern(this IServiceCollection services, Action? options)
- {
- options?.Invoke(new RepositoryPatternBuilderOptions(services));
- }
- }
-}
\ No newline at end of file
diff --git a/Satistools.Model.Repository/IRepository.cs b/Satistools.Model.Repository/IRepository.cs
deleted file mode 100644
index dbb30ce..0000000
--- a/Satistools.Model.Repository/IRepository.cs
+++ /dev/null
@@ -1,117 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-
-namespace Satistools.Model.Repository;
-
-///
-/// Basic non-geric repository definition.
-///
-public interface IRepository
-{
- ///
- /// The type of entity used with this repository.
- ///
- Type EntityType { get; }
-
- ///
- /// Gets whether this repository has unsaved changes.
- ///
- bool HasChanges { get; }
-
- ///
- /// Saves all changes made to this repository.
- ///
- /// How many rows was saved to the database from this repository.
- void SaveChanges();
-
- ///
- /// Saves all changes made to this repository as async task.
- ///
- /// How many rows was saved to the database from this repository.
- Task SaveChangesAsync();
-}
-
-///
-/// Generic repository controlling the type of used entity.
-///
-/// The data of entity used with repository.
-public interface IRepository : IRepository where TEntity : class
-{
- ///
- /// Access to the DbSet of repository for entity.
- ///
- DbSet Table { get; }
-
- ///
- /// Gets all entities in this repository.
- ///
- /// An enumerable of all entites.
- Task> GetAll();
-
- ///
- /// Inserts a new record to the repository.
- ///
- /// Entity to be saved list.
- void Insert(TEntity entity);
-
- ///
- /// Inserts a new range of entites to the repisitory.
- ///
- /// Enumerable of entities to be saved.
- void InsertRange(IEnumerable entities);
-
- ///
- /// Updates a entity in repository.
- ///
- /// Entity to be updated.
- void Update(TEntity entity);
-
- ///
- /// Updates a range of entites.
- ///
- /// Enumerable of entities to be updated.
- void UpdateRange(IEnumerable entities);
-
- ///
- /// Removes an entity from repository.
- ///
- /// Entity to be removed.
- void Remove(TEntity entity);
-
- ///
- /// Removes a range of entities from repository.
- ///
- /// Enumerable of netities to be removed.
- void RemoveRange(IEnumerable entities);
-}
-
-///
-/// Generic repository expanding posibilities of query by the generic type of primary key.
-///
-/// The datatype of saved entity.
-/// The datatype of primary key on entity.
-public interface IRepository : IRepository where TEntity : class
-{
- ///
- /// Gets an entity by the primary key value.
- ///
- /// Value of primary key.
- /// Awaitable task resulting in entity or null if not found.
- Task Get(TKey id);
-}
-
-///
-/// Repository supporting composite key identification.
-///
-/// The type of used entity.
-/// The type of first part of primary key.
-/// The type of second part of primary key.
-public interface IRepository : IRepository where TEntity : class
-{
- ///
- /// Gets the entity by the both primary key types.
- ///
- /// Value of first part of PK.
- /// Value of second part of PK.
- /// Entity if found or null.
- Task Get(TFirstKey firstKey, TSecondKey secondKey);
-}
\ No newline at end of file
diff --git a/Satistools.Model.Repository/Operations/IRepositoryOperation.cs b/Satistools.Model.Repository/Operations/IRepositoryOperation.cs
deleted file mode 100644
index 97475ad..0000000
--- a/Satistools.Model.Repository/Operations/IRepositoryOperation.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Satistools.Model.Repository.Operations
-{
-
- ///
- /// Represents an postponed operation which whould be applied when the repository is saving its changes.
- ///
- public interface IRepositoryOperation
- {
- ///
- /// Apply the changes made by this operation.
- ///
- void Apply();
- }
-}
\ No newline at end of file
diff --git a/Satistools.Model.Repository/Operations/InsertOperation.cs b/Satistools.Model.Repository/Operations/InsertOperation.cs
deleted file mode 100644
index 6e6de47..0000000
--- a/Satistools.Model.Repository/Operations/InsertOperation.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-
-namespace Satistools.Model.Repository.Operations
-{
- ///
- /// Represents an insert operation.
- ///
- public class InsertOperation : IRepositoryOperation where TEntity : class
- {
- private readonly DbContext _dbContext;
- private readonly TEntity _entity;
-
- ///
- ///
- ///
- /// Accessed context by repository.
- /// Entity to be saved.
- public InsertOperation(DbContext dbContext, TEntity entity)
- {
- _dbContext = dbContext;
- _entity = entity;
- }
-
- ///
- public void Apply()
- {
- _dbContext.Add(_entity);
- }
- }
-}
\ No newline at end of file
diff --git a/Satistools.Model.Repository/Operations/InsertRangeOperation.cs b/Satistools.Model.Repository/Operations/InsertRangeOperation.cs
deleted file mode 100644
index af3e931..0000000
--- a/Satistools.Model.Repository/Operations/InsertRangeOperation.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-
-namespace Satistools.Model.Repository.Operations
-{
- ///
- /// Operation inserting a range of entities.
- ///
- /// Used entity.
- public class InsertRangeOperation : IRepositoryOperation where TEntity : class
- {
- private readonly DbContext _context;
- private readonly IEnumerable _entities;
-
- ///
- ///
- ///
- ///
- ///
- public InsertRangeOperation(DbContext context, IEnumerable entities)
- {
- _context = context;
- _entities = entities;
- }
-
- ///
- public void Apply()
- {
- _context.AddRange(_entities);
- }
- }
-}
\ No newline at end of file
diff --git a/Satistools.Model.Repository/Operations/RemoveOperation.cs b/Satistools.Model.Repository/Operations/RemoveOperation.cs
deleted file mode 100644
index 90b1eea..0000000
--- a/Satistools.Model.Repository/Operations/RemoveOperation.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-
-namespace Satistools.Model.Repository.Operations
-{
- ///
- /// Operation removing entity from database.
- ///
- /// Type of used entity.
- public class RemoveOperation : IRepositoryOperation where TEntity : class
- {
- private readonly DbContext _context;
- private readonly TEntity _entity;
-
- ///
- ///
- ///
- ///
- ///
- public RemoveOperation(DbContext context, TEntity entity)
- {
- _context = context;
- _entity = entity;
- }
-
- ///
- public void Apply()
- {
- _context.Remove(_entity);
- }
- }
-}
\ No newline at end of file
diff --git a/Satistools.Model.Repository/Operations/RemoveRangeOperation.cs b/Satistools.Model.Repository/Operations/RemoveRangeOperation.cs
deleted file mode 100644
index 151b991..0000000
--- a/Satistools.Model.Repository/Operations/RemoveRangeOperation.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-
-namespace Satistools.Model.Repository.Operations
-{
- ///
- /// Operation removing set of entities from database.
- ///
- /// Type of used entities.
- public class RemoveRangeOperation : IRepositoryOperation where TEntity : class
- {
- private readonly DbContext _context;
- private readonly IEnumerable _entities;
-
- ///
- ///
- ///
- ///
- ///
- public RemoveRangeOperation(DbContext context, IEnumerable entities)
- {
- _context = context;
- _entities = entities;
- }
-
- ///
- public void Apply()
- {
- _context.RemoveRange(_entities);
- }
- }
-}
\ No newline at end of file
diff --git a/Satistools.Model.Repository/Operations/UpdateOperation.cs b/Satistools.Model.Repository/Operations/UpdateOperation.cs
deleted file mode 100644
index 17f880c..0000000
--- a/Satistools.Model.Repository/Operations/UpdateOperation.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-
-namespace Satistools.Model.Repository.Operations
-{
- ///
- /// Operation updating an entity in database.
- ///
- /// Type of used entity.
- public class UpdateOperation : IRepositoryOperation where TEntity : class
- {
- private readonly DbContext _context;
- private readonly TEntity _entity;
-
- ///
- ///
- ///
- ///
- ///
- public UpdateOperation(DbContext context, TEntity entity)
- {
- _context = context;
- _entity = entity;
- }
-
- ///
- public void Apply()
- {
- _context.Update(_entity);
- }
- }
-}
\ No newline at end of file
diff --git a/Satistools.Model.Repository/Operations/UpdateRangeOperation.cs b/Satistools.Model.Repository/Operations/UpdateRangeOperation.cs
deleted file mode 100644
index c8374db..0000000
--- a/Satistools.Model.Repository/Operations/UpdateRangeOperation.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-
-namespace Satistools.Model.Repository.Operations
-{
- ///
- /// Operation updating a range of entities in database.
- ///
- /// Type of used entity.
- public class UpdateRangeOperation : IRepositoryOperation where TEntity : class
- {
- private readonly DbContext _context;
- private readonly IEnumerable _entities;
-
- ///
- ///
- ///
- ///
- ///
- public UpdateRangeOperation(DbContext context, IEnumerable entities)
- {
- _context = context;
- _entities = entities;
- }
-
- ///
- public void Apply()
- {
- _context.UpdateRange(_entities);
- }
- }
-}
\ No newline at end of file
diff --git a/Satistools.Model.Repository/Repository.cs b/Satistools.Model.Repository/Repository.cs
deleted file mode 100644
index d20b99a..0000000
--- a/Satistools.Model.Repository/Repository.cs
+++ /dev/null
@@ -1,191 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using Satistools.Model.Repository.Operations;
-
-namespace Satistools.Model.Repository;
-
- ///
- /// Internal implementation of repository.
- ///
- ///
- /// Repository without its generic type is useless.
- ///
- public abstract class Repository : IRepository
- {
- ///
- ///
- ///
- ///
- ///
- protected Repository(RepositoryContext dbContext, Type entityType)
- {
- Context = dbContext;
- EntityType = entityType;
- }
-
- ///
- public Type EntityType { get; }
-
- ///
- public bool HasChanges => Operations.Count > 0;
-
- ///
- /// Protected access to the context of database the repository is working with.
- ///
- internal RepositoryContext Context { get; }
-
- ///
- /// Queue of operations which should be executed the repository is saving changes.
- ///
- internal Queue Operations { get; } = new();
-
- ///
- public void SaveChanges()
- {
- SaveChangesAsync().Wait();
- }
-
- ///
- public async Task SaveChangesAsync()
- {
- while (Operations.TryDequeue(out IRepositoryOperation? operation))
- {
- operation.Apply();
- }
-
- await Context.SaveChangesAsync();
- }
- }
-
- ///
- /// Generic implementation of repository.
- ///
- /// Target type of entity the repository is working with.
- public abstract class Repository : Repository, IRepository where TEntity : class
- {
- ///
- ///
- ///
- ///
- protected Repository(RepositoryContext dbContext) : base(dbContext, typeof(TEntity))
- {
- Table = dbContext.Set();
- Queryable = Table.AsQueryable();
- }
-
- ///
- /// Original for entity.
- ///
- public DbSet Table { get; }
-
- ///
- /// Queryable data source of
- ///
- protected IQueryable Queryable { get; }
-
- ///
- public async Task> GetAll()
- {
- return await Queryable.ToListAsync();
- }
-
- ///
- public void Insert(TEntity entity)
- {
- Operations.Enqueue(new InsertOperation(Context, entity));
- }
-
- ///
- public void InsertRange(IEnumerable entities)
- {
- Operations.Enqueue(new InsertRangeOperation(Context, entities));
- }
-
- ///
- public void Update(TEntity entity)
- {
- Operations.Enqueue(new UpdateOperation(Context, entity));
- }
-
- ///
- public void UpdateRange(IEnumerable entities)
- {
- Operations.Enqueue(new UpdateRangeOperation(Context, entities));
- }
-
- ///
- public void Remove(TEntity entity)
- {
- Operations.Enqueue(new RemoveOperation(Context, entity));
- }
-
- ///
- public void RemoveRange(IEnumerable entities)
- {
- Operations.Enqueue(new RemoveRangeOperation(Context, entities));
- }
-
- ///
- /// Finds a of repository by the type of target entity.
- ///
- /// The type of target entity.
- /// DbSet of repository for the entity.
- protected DbSet JoinRepository() where TAnotherEntity : class
- {
- return Context.GetRepository().Table;
- }
-
- ///
- /// Finds a on context by the target type of entites.
- ///
- /// The type of target entity.
- /// DbSet of repository for the entity.
- protected DbSet JoinDbSet() where TAnotherEntity : class
- {
- return Context.Set();
- }
- }
-
- ///
- /// Generic implementation of Repository controlling the data type of primary key.
- ///
- /// The type of entity.
- /// The type of primary key of entity.
- public abstract class Repository : Repository, IRepository where TEntity : class
- {
- ///
- ///
- ///
- ///
- protected Repository(RepositoryContext dbContext) : base(dbContext)
- {
- }
-
- ///
- public async Task Get(TKey id)
- {
- return await Table.FindAsync(id).AsTask();
- }
- }
-
- ///
- /// Repository supporting identification via composite PK.
- ///
- /// Data type of used entity
- /// Data type of first part of PK
- /// Data type of second part of PK
- public abstract class Repository : Repository, IRepository where TEntity : class
- {
- ///
- ///
- ///
- ///
- protected Repository(RepositoryContext dbContext) : base(dbContext)
- {
- }
-
- ///
- public async Task Get(TFirstKey firstKey, TSecondKey secondKey)
- {
- return await Table.FindAsync(firstKey, secondKey).AsTask();
- }
- }
\ No newline at end of file
diff --git a/Satistools.Model.Repository/RepositoryContext.cs b/Satistools.Model.Repository/RepositoryContext.cs
deleted file mode 100644
index f1867d6..0000000
--- a/Satistools.Model.Repository/RepositoryContext.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System.Collections.ObjectModel;
-using Microsoft.EntityFrameworkCore;
-
-namespace Satistools.Model.Repository;
-
-///
-/// Database Context expanding its workflow by registered repositories.
-///
-public abstract class RepositoryContext : DbContext
-{
- ///
- /// Dictionary of registered repositories assigned to its types.
- ///
- private readonly IDictionary _repositories;
-
- protected RepositoryContext()
- {
- _repositories = new ReadOnlyDictionary(new Dictionary());
- }
-
- ///
- ///
- ///
- ///
- ///
- protected RepositoryContext(DbContextOptions options, IEnumerable repositories) : base(options)
- {
- _repositories = new ReadOnlyDictionary(repositories.ToDictionary(repo => repo.EntityType, repo => repo));
- }
-
- ///
- /// Gets target repository by the type of entity.
- ///
- /// The type of used entity.
- /// Found instance of entity.
- public IRepository GetRepository() where TEntity : class
- {
- return (IRepository) _repositories[typeof(TEntity)];
- }
-}
\ No newline at end of file
diff --git a/Satistools.Model.Repository/RepositoryPatternBuilderOptions.cs b/Satistools.Model.Repository/RepositoryPatternBuilderOptions.cs
deleted file mode 100644
index 6254351..0000000
--- a/Satistools.Model.Repository/RepositoryPatternBuilderOptions.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-
-namespace Satistools.Model.Repository
-{
- ///
- /// Options for registering a repository pattern.
- ///
- public class RepositoryPatternBuilderOptions
- {
- internal RepositoryPatternBuilderOptions(IServiceCollection services)
- {
- Services = services;
- }
-
- ///
- /// Collection of registered services.
- ///
- private IServiceCollection Services { get; }
-
- ///
- /// Register a new repository to services as direct type.
- ///
- /// Implementation type of repository.
- public void AddRepository() where TImplementation : class, IRepository
- {
- AddRepository();
- }
-
- ///
- /// Registers a new repository to services.
- ///
- /// Interface of repository
- /// The implementation type of repository.
- public void AddRepository()
- where TInterface : class, IRepository
- where TImplementation : class, TInterface
- {
- Services.AddScoped();
- }
- }
-}
\ No newline at end of file
diff --git a/Satistools.ModelTest/Satistools.ModelTest.csproj b/Satistools.ModelTest/Satistools.ModelTest.csproj
deleted file mode 100644
index be96d4b..0000000
--- a/Satistools.ModelTest/Satistools.ModelTest.csproj
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
- net6.0
- enable
- enable
-
-
-
-
-
-
-
-
-
-
diff --git a/Satistools.ModelTest/TestFactory.cs b/Satistools.ModelTest/TestFactory.cs
deleted file mode 100644
index 666ccad..0000000
--- a/Satistools.ModelTest/TestFactory.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using Microsoft.Extensions.DependencyInjection;
-
-namespace Satistools.ModelTest;
-
-///
-/// Factory class preparing the environment for unit tests.
-///
-public abstract class TestFactory
-{
- ///
- /// Collection for building
- ///
- private readonly ServiceCollection _serviceCollection = new();
-
- ///
- /// Backing field for
- ///
- private IServiceProvider? _serviceProvider;
-
- ///
- /// Initializes a new instance of with configured services.
- ///
- protected TestFactory()
- {
- }
-
- ///
- /// Access to Dependency Container.
- ///
- ///
- /// Provider is created on demand. If the provider has no instance yet, configuration and building process
- /// will be called.
- ///
- public IServiceProvider ServiceProvider
- {
- get
- {
- if (_serviceProvider is null)
- {
- OnConfiguring(_serviceCollection);
- _serviceProvider = _serviceCollection.BuildServiceProvider();
- }
-
- return _serviceProvider;
- }
- }
-
- ///
- /// Method providing additional way to register custom services.
- ///
- /// Services collection
- protected virtual void OnConfiguring(ServiceCollection services)
- {
- }
-
- ///
- /// Loads up a default connection string from and adds
- /// randomly generated database name to ensure each test will be running in separate database
- /// context.
- ///
- /// Default connection string.
- /// Unable to determine current location.
- protected static string GetDatabaseName()
- {
- return $"xunit_{Guid.NewGuid():N}";
- }
-}
\ No newline at end of file
diff --git a/Satistools.ModelTest/TestFixture.cs b/Satistools.ModelTest/TestFixture.cs
deleted file mode 100644
index 48558c8..0000000
--- a/Satistools.ModelTest/TestFixture.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using Microsoft.Extensions.DependencyInjection;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Satistools.ModelTest;
-
-///
-/// Base class for all unit tests
-///
-/// The type of used test factory
-/// The type of used database context class
-public abstract class TestFixture : IClassFixture, IAsyncLifetime
- where TFactory : TestFactory
- where TDbContext : DbContext
-{
- protected TestFixture(TFactory factory, ITestOutputHelper testOutputHelper)
- {
- Factory = factory;
- TestOutputHelper = testOutputHelper;
- ServiceProvider = factory.ServiceProvider.CreateScope().ServiceProvider;
- Context = ServiceProvider.GetRequiredService();
- }
-
- ///
- /// Instance of Factory creating Test Context.
- ///
- protected TFactory Factory { get; }
-
- ///
- /// Instance of for output debugging purposes.
- ///
- protected ITestOutputHelper TestOutputHelper { get; }
-
- ///
- /// Instance of new Scope of in current context.
- ///
- protected IServiceProvider ServiceProvider { get; }
-
- ///
- /// Instance of actual used
- ///
- protected TDbContext Context { get; }
-
- ///
- public virtual async Task InitializeAsync()
- {
- await Context.Database.EnsureCreatedAsync();
- }
-
- ///
- public virtual async Task DisposeAsync()
- {
- await Context.Database.EnsureDeletedAsync();
- }
-}
\ No newline at end of file
diff --git a/Satistools.Web/Satistools.Web.csproj b/Satistools.Web/Satistools.Web.csproj
index d26060e..8c6531a 100644
--- a/Satistools.Web/Satistools.Web.csproj
+++ b/Satistools.Web/Satistools.Web.csproj
@@ -30,6 +30,7 @@
+
diff --git a/Satistools.Web/Startup.cs b/Satistools.Web/Startup.cs
index 3f81dca..3e52241 100644
--- a/Satistools.Web/Startup.cs
+++ b/Satistools.Web/Startup.cs
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.EntityFrameworkCore;
+using Satistools.Calculator.Extensions;
using Satistools.GameData;
using Satistools.GameData.Extensions;
@@ -19,6 +20,7 @@ public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddGameDataModel(_configuration);
+ services.AddCalculator();
}
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env)
diff --git a/Satistools.sln b/Satistools.sln
index 3e2ec49..446b1ad 100644
--- a/Satistools.sln
+++ b/Satistools.sln
@@ -17,17 +17,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Satistools.DataReader.Test"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Satistools.GameData.Test", "Satistools.GameData.Test\Satistools.GameData.Test.csproj", "{1A5B0491-7105-40A0-BAFA-2EE067D8DF4A}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{E74876A2-78FA-4AC0-845F-4364E12ACBF7}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Satistools.ModelTest", "Satistools.ModelTest\Satistools.ModelTest.csproj", "{7680AEEC-B421-445B-A144-ED226614382B}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Satistools.Web", "Satistools.Web\Satistools.Web.csproj", "{D9906108-E5E6-4485-AD7C-AA24689910DD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{B7495254-A6B0-4A1A-87BD-40C8F34BAE95}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Satistools.ImageMove", "Satistools.ImageMove\Satistools.ImageMove.csproj", "{8E0C4DF3-36FC-4CF5-BA1A-3047CDAB13B3}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Satistools.Model.Repository", "Satistools.Model.Repository\Satistools.Model.Repository.csproj", "{FA5ED6EC-F9E2-4D76-8D36-42EB0BE79FF8}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Satistools.Calculator", "Satistools.Calculator\Satistools.Calculator.csproj", "{6CC7143E-7EC0-48C4-9AA5-A8F5BC5ED088}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Satistools.Calculator.Test", "Satistools.Calculator.Test\Satistools.Calculator.Test.csproj", "{42B8821B-B2C4-4C4E-9D65-C0B22AE8E8B4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -51,10 +49,6 @@ Global
{1A5B0491-7105-40A0-BAFA-2EE067D8DF4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A5B0491-7105-40A0-BAFA-2EE067D8DF4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A5B0491-7105-40A0-BAFA-2EE067D8DF4A}.Release|Any CPU.Build.0 = Release|Any CPU
- {7680AEEC-B421-445B-A144-ED226614382B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7680AEEC-B421-445B-A144-ED226614382B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7680AEEC-B421-445B-A144-ED226614382B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7680AEEC-B421-445B-A144-ED226614382B}.Release|Any CPU.Build.0 = Release|Any CPU
{D9906108-E5E6-4485-AD7C-AA24689910DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D9906108-E5E6-4485-AD7C-AA24689910DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9906108-E5E6-4485-AD7C-AA24689910DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -63,16 +57,19 @@ Global
{8E0C4DF3-36FC-4CF5-BA1A-3047CDAB13B3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8E0C4DF3-36FC-4CF5-BA1A-3047CDAB13B3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8E0C4DF3-36FC-4CF5-BA1A-3047CDAB13B3}.Release|Any CPU.Build.0 = Release|Any CPU
- {FA5ED6EC-F9E2-4D76-8D36-42EB0BE79FF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {FA5ED6EC-F9E2-4D76-8D36-42EB0BE79FF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {FA5ED6EC-F9E2-4D76-8D36-42EB0BE79FF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {FA5ED6EC-F9E2-4D76-8D36-42EB0BE79FF8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6CC7143E-7EC0-48C4-9AA5-A8F5BC5ED088}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6CC7143E-7EC0-48C4-9AA5-A8F5BC5ED088}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6CC7143E-7EC0-48C4-9AA5-A8F5BC5ED088}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6CC7143E-7EC0-48C4-9AA5-A8F5BC5ED088}.Release|Any CPU.Build.0 = Release|Any CPU
+ {42B8821B-B2C4-4C4E-9D65-C0B22AE8E8B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {42B8821B-B2C4-4C4E-9D65-C0B22AE8E8B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {42B8821B-B2C4-4C4E-9D65-C0B22AE8E8B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {42B8821B-B2C4-4C4E-9D65-C0B22AE8E8B4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{852B5F63-A368-42FB-A2AF-38C8F599D436} = {1B36C950-076F-4D86-9875-87248184E888}
{1A5B0491-7105-40A0-BAFA-2EE067D8DF4A} = {1B36C950-076F-4D86-9875-87248184E888}
- {7680AEEC-B421-445B-A144-ED226614382B} = {E74876A2-78FA-4AC0-845F-4364E12ACBF7}
{8E0C4DF3-36FC-4CF5-BA1A-3047CDAB13B3} = {B7495254-A6B0-4A1A-87BD-40C8F34BAE95}
- {FA5ED6EC-F9E2-4D76-8D36-42EB0BE79FF8} = {E74876A2-78FA-4AC0-845F-4364E12ACBF7}
+ {42B8821B-B2C4-4C4E-9D65-C0B22AE8E8B4} = {1B36C950-076F-4D86-9875-87248184E888}
EndGlobalSection
EndGlobal
diff --git a/Satistools.sln.DotSettings.user b/Satistools.sln.DotSettings.user
index d9f5df6..3022b46 100644
--- a/Satistools.sln.DotSettings.user
+++ b/Satistools.sln.DotSettings.user
@@ -1,15 +1,7 @@
- C:\Users\Zechy\AppData\Local\JetBrains\Rider2022.1\resharper-host\temp\Rider\vAny\CoverageData\_Satistools.652722255\Snapshot\snapshot.utdcvr
- <SessionState ContinuousTestingMode="0" IsActive="True" Name="Test_ReadJson" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
- <TestAncestor>
- <TestId>NUnit3x::852B5F63-A368-42FB-A2AF-38C8F599D436::net6.0::Satistools.DataReader.Test.DataParserTest</TestId>
- <TestId>NUnit3x::852B5F63-A368-42FB-A2AF-38C8F599D436::net6.0::Satistools.DataReader.Test.Others.RegexTest.Test_ColorRegexGroups</TestId>
- <TestId>NUnit3x::852B5F63-A368-42FB-A2AF-38C8F599D436::net6.0::Satistools.DataReader.Test.RegexTest.Test_Regex</TestId>
- <TestId>NUnit3x::852B5F63-A368-42FB-A2AF-38C8F599D436::net6.0::Satistools.DataReader.Test.EntityResolverTest.Test_ResolveEntities</TestId>
- <TestId>xUnit::1A5B0491-7105-40A0-BAFA-2EE067D8DF4A::net6.0::Satistools.GameData.Test.ItemTest.Test_CRUD</TestId>
- <TestId>xUnit::1A5B0491-7105-40A0-BAFA-2EE067D8DF4A::net6.0::Satistools.GameData.Test.RecipeTest.Test_CRUD</TestId>
- <TestId>xUnit::1A5B0491-7105-40A0-BAFA-2EE067D8DF4A::net6.0::Satistools.GameData.Test.BuildableManufacturerTest.Test_CRUD</TestId>
- <TestId>xUnit::1A5B0491-7105-40A0-BAFA-2EE067D8DF4A::net6.0::Satistools.GameData.Test.RecipeMapperTest.Test_RecipeMapper</TestId>
- <TestId>xUnit::1A5B0491-7105-40A0-BAFA-2EE067D8DF4A::net6.0::Satistools.GameData.Test.Helpers.ColorHelperTest.Test_FromHexaString</TestId>
- </TestAncestor>
-</SessionState>
\ No newline at end of file
+
+ <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from <Tests>\<Satistools.Calculator.Test>" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
+ <Project Location="E:\WorkingDir\RiderProjects\Satistools\Satistools.Calculator.Test" Presentation="<Tests>\<Satistools.Calculator.Test>" />
+</SessionState>
+
+
\ No newline at end of file