Skip to content

Commit

Permalink
Link summary related (#413)
Browse files Browse the repository at this point in the history
A list of nice-to-have things:
* [x] A changelog 
* [ ] The link from the PR to the issue that it fixes. -> Not a direct
fix, but this is work towards better link visualization to aid #409,
#414
* [x] A description of what testing was done.
I clicked around on the new unrelated links and you navigate to new
links from there.

As a followup to #408 this is an implementation of #414 (Only the
visualization part):


![afbeelding](https://github.com/user-attachments/assets/bef76af3-ded1-4f2c-b290-dca8d76346f2)

As seen above, the link summary now not only display the currently
selected link, but also recipes from other links covering the same
recipe. It also tells you where in the recipe tree those links are,
parent means they're in the same branch but higher up, child means the
same but lower, unrelated is any other recipe.

There are a few more unimplemented ideas in #414 to add buttons to
manipulate the other recipes, those can be tackled later.


![afbeelding](https://github.com/user-attachments/assets/2a2b592b-b68a-4b8a-b26e-667113464c79)
  • Loading branch information
shpaass authored Feb 20, 2025
2 parents a4b06b3 + 4efbfcc commit a22298f
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 23 deletions.
18 changes: 3 additions & 15 deletions Yafc.Model/Model/ProductionTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,21 +203,9 @@ public void AddRecipe(ObjectWithQuality<RecipeOrTechnology> recipe, IComparer<Go
/// <summary>
/// Get all <see cref="RecipeRow"/>s contained in this <see cref="ProductionTable"/>, in a depth-first ordering. (The same as in the UI when all nested tables are expanded.)
/// </summary>
public IEnumerable<RecipeRow> GetAllRecipes() {
return flatten(recipes);

static IEnumerable<RecipeRow> flatten(IEnumerable<RecipeRow> rows) {
foreach (var row in rows) {
yield return row;

if (row.subgroup is not null) {
foreach (var row2 in flatten(row.subgroup.GetAllRecipes())) {
yield return row2;
}
}
}
}
}
/// <param name="skip">Filter out all these recipes and do not step into their childeren.</param>
public IEnumerable<RecipeRow> GetAllRecipes(ProductionTable? skip = null) => this == skip ? [] : recipes
.SelectMany<RecipeRow, RecipeRow>(row => [row, .. row?.subgroup?.GetAllRecipes(skip) ?? []]);

private static void AddFlow(RecipeRow recipe, Dictionary<IObjectWithQuality<Goods>, (double prod, double cons)> summer) {
foreach (var product in recipe.Products) {
Expand Down
110 changes: 102 additions & 8 deletions Yafc/Workspace/ProductionTable/ProductionLinkSummaryScreen.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using SDL2;
Expand Down Expand Up @@ -39,8 +40,82 @@ private void BuildScrollArea(ImGui gui) {
gui.BuildText((amount > 0 ? "Overproduction: " : "Overconsumption: ") + DataUtils.FormatAmount(MathF.Abs(amount), link.flowUnitOfMeasure),
new TextBlockDisplayStyle(Font.subheader, Color: SchemeColor.Error));
}
ShowRelatedLinks(gui);
}

private void ShowRelatedLinks(ImGui gui) {
ModelObject o = link;
List<ModelObject> parents = [];
while (o.ownerObject is not ProjectPage) {
var t = o.ownerObject;
if (t is null) {
break;
}
o = t;
if (t == o.ownerObject) continue;
parents.Add(t);
}
if (o is ProductionTable page) {
Dictionary<ProductionLink, List<(RecipeRow row, float flow)>> childLinks = [], parentLinks = [], otherLinks = [];
List<(RecipeRow row, float flow)> unlinked = [], table;
foreach (var (row, relationLinks) in
page.GetAllRecipes(link.owner)
.Select(e => (e, IsLinkParent(e, parents) ? parentLinks : otherLinks))
.Concat(link.owner.GetAllRecipes().Select(e => (e, childLinks)))) {
if (isPartOfCurrentLink(row)
|| isNotRelatedToCurrentLink(row)) {
continue;
}
float localFlow = DetermineFlow(link.goods, row);

if ((row.FindLink(link.goods, out var otherLink) && otherLink != link)) {
if (!relationLinks.ContainsKey(otherLink)) {
relationLinks.Add(otherLink, []);
}
table = relationLinks[otherLink];
}
else {
table = unlinked;
}

table.Add((row, localFlow));
}
var color = 1;
gui.spacing = 0.75f;
if (childLinks.Values.Any(e => e.Any())) {
gui.BuildText("Child links: ", Font.productionTableHeader);
foreach (var relTable in childLinks.Values) {
BuildFlow(gui, relTable, relTable.Sum(e => Math.Abs(e.flow)), false, color++);
}
}
if (parentLinks.Values.Any(e => e.Any())) {
gui.BuildText("Parent links: ", Font.productionTableHeader);
foreach (var relTable in parentLinks.Values) {
BuildFlow(gui, relTable, relTable.Sum(e => Math.Abs(e.flow)), false, color++);
}
}
if (otherLinks.Values.Any(e => e.Any())) {
gui.BuildText("Unrelated links: ", Font.productionTableHeader);
foreach (var relTable in otherLinks.Values) {
BuildFlow(gui, relTable, relTable.Sum(e => Math.Abs(e.flow)), false, color++);
}
}
if (unlinked.Any()) {
gui.BuildText("Unlinked: ", Font.subheader);
BuildFlow(gui, unlinked, 0, false);
}
}
}

private bool isNotRelatedToCurrentLink(RecipeRow? row) => (!row.Ingredients.Any(e => e.Goods == link.goods)
&& !row.Products.Any(e => e.Goods == link.goods)
&& !(row.fuel is not null && row.fuel == link.goods));
private bool isPartOfCurrentLink(RecipeRow row) => link.capturedRecipes.Any(e => e == row);
private bool IsLinkParent(RecipeRow row, List<ModelObject> parents) => row.Ingredients.Select(e => e.Link).Concat(row.Products.Select(e => e.Link)).Append(row.FuelInformation.Link)
.Where(e => e?.ownerObject is not null)
.Where(e => e!.goods == link.goods)
.Any(e => parents.Contains(e!.ownerObject!));

public override void Build(ImGui gui) {
BuildHeader(gui, "Link summary");
using (gui.EnterRow()) {
Expand All @@ -64,7 +139,7 @@ private void DestroyLink(ProductionLink link) {

protected override void ReturnPressed() => Close();

private void BuildFlow(ImGui gui, List<(RecipeRow row, float flow)> list, float total, bool isLinkOutput) {
private void BuildFlow(ImGui gui, List<(RecipeRow row, float flow)> list, float total, bool isLinkOutput, int colorIndex = 0) {
gui.spacing = 0f;
foreach (var (row, flow) in list) {
string amount = DataUtils.FormatAmount(flow, link.flowUnitOfMeasure);
Expand All @@ -78,7 +153,10 @@ private void BuildFlow(ImGui gui, List<(RecipeRow row, float flow)> list, float
links.Remove(link.goods); // except the current one, only applicable for recipes like kovarex that have catalysts.

// Jump to the only link, or offer a selection of links to inspect next.
if (links.Count == 1) {
if (row.FindLink(link.goods, out var otherLink) && otherLink != link) {
changeLinkView(otherLink);
}
else if (links.Count == 1) {
changeLinkView(links.First().Value);
}
else {
Expand Down Expand Up @@ -107,8 +185,8 @@ void drawLinks(ImGui gui) {

if (gui.isBuilding) {
var lastRect = gui.lastRect;
lastRect.Width *= (flow / total);
gui.DrawRectangle(lastRect, SchemeColor.Primary);
lastRect.Width *= Math.Abs(flow / total);
gui.DrawRectangle(lastRect, GetFlowColor(colorIndex));
}
}

Expand All @@ -118,6 +196,17 @@ void changeLinkView(ProductionLink newLink) {
}
}

private static SchemeColor GetFlowColor(int colorIndex) => (colorIndex % 7) switch {
0 => SchemeColor.Primary,
1 => SchemeColor.Secondary,
2 => SchemeColor.Green,
3 => SchemeColor.Magenta,
4 => SchemeColor.Grey,
5 => SchemeColor.TagColorRed,
6 => SchemeColor.TagColorYellow,
_ => throw new UnreachableException(),
};

/// <summary>
/// Returns a delegate that will be called when drawing <see cref="ObjectTooltip"/>, to provide nesting information when hovering recipes and links.
/// </summary>
Expand Down Expand Up @@ -159,10 +248,7 @@ private void CalculateFlow(ProductionLink link) {
List<(RecipeRow row, float flow)> input = [], output = [];
totalInput = totalOutput = 0;
foreach (var recipe in link.capturedRecipes) {
float production = recipe.GetProductionForRow(link.goods);
float consumption = recipe.GetConsumptionForRow(link.goods);
float fuelUsage = recipe.fuel == link.goods ? recipe.FuelInformation.Amount : 0;
float localFlow = production - consumption - fuelUsage;
float localFlow = DetermineFlow(link.goods, recipe);
if (localFlow > 0) {
input.Add((recipe, localFlow));
totalInput += localFlow;
Expand All @@ -183,6 +269,14 @@ private void CalculateFlow(ProductionLink link) {
scrollArea.RebuildContents();
}

private static float DetermineFlow(IObjectWithQuality<Goods> goods, RecipeRow recipe) {
float production = recipe.GetProductionForRow(goods);
float consumption = recipe.GetConsumptionForRow(goods);
float fuelUsage = recipe.fuel == goods ? recipe.FuelInformation.Amount : 0;
float localFlow = production - consumption - fuelUsage;
return localFlow;
}

public static void Show(ProductionLink link) => _ = MainScreen.Instance.ShowPseudoScreen(new ProductionLinkSummaryScreen(link));

public int Compare((RecipeRow row, float flow) x, (RecipeRow row, float flow) y) => y.flow.CompareTo(x.flow);
Expand Down
5 changes: 5 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
// Internal changes:
// Changes to the code that do not affect the behavior of the program.
----------------------------------------------------------------------------------------------------------------------
Version:
Date:
Features:
- Add related recipes to the Link Summary screen.
----------------------------------------------------------------------------------------------------------------------
Version: 2.8.1
Date: February 20th 2025
Fixes:
Expand Down

0 comments on commit a22298f

Please sign in to comment.