Skip to content

[Automate] Refactor to support global inventory chests #1081

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

Draft
wants to merge 2 commits into
base: develop
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
9 changes: 4 additions & 5 deletions Automate/Framework/Commands/Summary/GroupContainerStats.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal class GroupContainerStats
public int TotalSlots { get; }

/// <summary>Whether the container is a Junimo chest.</summary>
public bool IsJunimoChest { get; }
public HashSet<string> GlobalInventoryChests { get; } = new(StringComparer.OrdinalIgnoreCase);


/*********
Expand All @@ -47,12 +47,11 @@ public GroupContainerStats(string name, AutomateContainerPreference storagePrefe

foreach (IContainer container in containers)
{
// only track Junimo chests once
if (container.IsJunimoChest)
// only track same global inventory chest once
if (container.IsGlobalChest)
{
if (this.IsJunimoChest)
if (this.GlobalInventoryChests.Add(container.GlobalInventoryId))
continue;
this.IsJunimoChest = true;
}

// track stats
Expand Down
4 changes: 2 additions & 2 deletions Automate/Framework/Commands/Summary/GroupStats.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ public GroupStats(IMachineGroup machineGroup)
{
this.MachineGroup = machineGroup;

if (machineGroup.IsJunimoGroup)
if (machineGroup.IsGlobalGroup)
this.Name = "Distributed group";
else
{
Vector2 tile = machineGroup.GetTiles(machineGroup.LocationKey).FirstOrDefault();
this.Name = $"Group at ({tile.X}, {tile.Y})";
}

this.IsJunimoGroup = machineGroup.IsJunimoGroup;
this.IsJunimoGroup = machineGroup.IsGlobalGroup;

this.Machines = machineGroup.Machines
.GroupBy(p => p.MachineTypeID)
Expand Down
2 changes: 1 addition & 1 deletion Automate/Framework/Commands/SummaryCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ public override void Handle(string[] args)
.ThenBy(p => p.TileArea.Y)
.ToArray();

IContainer[] junimoChests = chests.Where(p => p.IsJunimoChest).ToArray();
IContainer[] junimoChests = chests.Where(p => p.IsGlobalChest).ToArray();
return junimoChests.Any()
? junimoChests
: chests; // special case: no Junimo chests in this location, but we're still connected somehow. This is most likely a custom connected chest from another mod, so just list all of them.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace Pathoschild.Stardew.Automate.Framework;

/// <summary>An aggregate collection of machine groups linked by Junimo chests.</summary>
internal class JunimoMachineGroup : MachineGroup
internal class GlobalMachineGroup : MachineGroup
{
/*********
** Fields
Expand Down Expand Up @@ -38,7 +38,7 @@ internal class JunimoMachineGroup : MachineGroup
/// <param name="sortMachines">Sort machines by priority.</param>
/// <param name="buildStorage">Build a storage manager for the given containers.</param>
/// <param name="monitor">Encapsulates monitoring and logging.</param>
public JunimoMachineGroup(Func<IEnumerable<IMachine>, IEnumerable<IMachine>> sortMachines, Func<IContainer[], StorageManager> buildStorage, IMonitor monitor)
public GlobalMachineGroup(Func<IEnumerable<IMachine>, IEnumerable<IMachine>> sortMachines, Func<IContainer[], StorageManager> buildStorage, IMonitor monitor)
: base(
locationKey: null,
machines: [],
Expand All @@ -48,7 +48,7 @@ public JunimoMachineGroup(Func<IEnumerable<IMachine>, IEnumerable<IMachine>> sor
monitor: monitor
)
{
this.IsJunimoGroup = true;
this.IsGlobalGroup = true;
this.SortMachines = sortMachines;
}

Expand All @@ -64,12 +64,14 @@ public IEnumerable<IMachineGroup> GetAll()
public void Add(IList<IMachineGroup> groups)
{
this.MachineGroups.AddRange(groups);
this.GlobalContainerKeys.UnionWith(groups.SelectMany(p => p.GlobalContainerKeys));
}

/// <summary>Remove all machine groups in the collection.</summary>
public void Clear()
{
this.MachineGroups.Clear();
this.GlobalContainerKeys.Clear();

this.StorageManager.SetContainers([]);

Expand All @@ -94,6 +96,8 @@ public void Rebuild()
this.Machines = this.SortMachines(this.MachineGroups.SelectMany(p => p.Machines)).ToArray();
this.Tiles = null;

this.GlobalContainerKeys.Clear();
this.GlobalContainerKeys.UnionWith(this.MachineGroups.SelectMany(p => p.GlobalContainerKeys));
this.StorageManager.SetContainers(this.Containers);
}

Expand Down
13 changes: 8 additions & 5 deletions Automate/Framework/IMachineGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,23 @@ namespace Pathoschild.Stardew.Automate.Framework;
internal interface IMachineGroup
{
/*********
** Accessors
*********/
** Accessors
*********/
/// <summary>The main location containing the group (as formatted by <see cref="MachineGroupFactory.GetLocationKey"/>), unless this is an aggregate machine group.</summary>
string? LocationKey { get; }

/// <summary>The machines in the group.</summary>
/// <summary>The keys for all containers which are linked to global inventories.</summary>
HashSet<string> GlobalContainerKeys { get; }

/// <summary>The machines in the group.</summary>
IMachine[] Machines { get; }

/// <summary>The containers in the group.</summary>
IContainer[] Containers { get; }

/// <summary>Whether the machine group is linked to a Junimo chest.</summary>
/// <summary>Whether the machine group is linked to a global inventory.</summary>
[MemberNotNullWhen(false, nameof(IMachineGroup.LocationKey))]
bool IsJunimoGroup { get; }
bool IsGlobalGroup { get; }

/// <summary>Whether the group has the minimum requirements to enable internal automation (i.e., at least one chest and one machine).</summary>
bool HasInternalAutomation { get; }
Expand Down
4 changes: 2 additions & 2 deletions Automate/Framework/MachineDataForLocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ internal record MachineDataForLocation(string LocationKey, IReadOnlyCollection<I
*********/
/// <summary>Get whether the tile area intersects a machine group which meets the minimum requirements for automation (regardless of whether the machines are currently running).</summary>
/// <param name="tileArea">The tile area to check.</param>
/// <remarks>This is the normal-chest equivalent of <see cref="JunimoMachineGroup.IntersectsAutomatedGroup"/>.</remarks>
/// <remarks>This is the normal-chest equivalent of <see cref="GlobalMachineGroup.IntersectsAutomatedGroup"/>.</remarks>
public bool IntersectsAutomatedGroup(Rectangle tileArea)
{
var activeTiles = this.ActiveTiles;
Expand All @@ -59,7 +59,7 @@ public bool IntersectsAutomatedGroup(Rectangle tileArea)

/// <summary>Get whether a tile area contains or is adjacent to a tracked automateable.</summary>
/// <param name="tileArea">The tile area to check.</param>
/// <remarks>This is the normal-chest equivalent of <see cref="JunimoMachineGroup.ContainsOrAdjacent"/>.</remarks>
/// <remarks>This is the normal-chest equivalent of <see cref="GlobalMachineGroup.ContainsOrAdjacent"/>.</remarks>
public bool ContainsOrAdjacent(Rectangle tileArea)
{
var activeTiles = this.ActiveTiles;
Expand Down
18 changes: 15 additions & 3 deletions Automate/Framework/MachineGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Microsoft.Xna.Framework;
using Pathoschild.Stardew.Common;
Expand Down Expand Up @@ -59,6 +60,9 @@ internal class MachineGroup : IMachineGroup
/// <inheritdoc />
public string? LocationKey { get; }

/// <inheritdoc />
public HashSet<string> GlobalContainerKeys { get; } = new(StringComparer.OrdinalIgnoreCase);

/// <inheritdoc />
public IMachine[] Machines { get; protected set; }

Expand All @@ -67,10 +71,10 @@ internal class MachineGroup : IMachineGroup

/// <inheritdoc />
[MemberNotNullWhen(false, nameof(IMachineGroup.LocationKey))]
public bool IsJunimoGroup { get; protected set; }
public bool IsGlobalGroup { get; protected set; }

/// <inheritdoc />
public virtual bool HasInternalAutomation => this.IsJunimoGroup || (this.Machines.Length > 0 && this.Containers.Any(p => !p.IsJunimoChest));
public virtual bool HasInternalAutomation => this.IsGlobalGroup || (this.Machines.Length > 0 && this.Containers.Any(p => !p.IsGlobalChest));


/*********
Expand All @@ -91,7 +95,15 @@ public MachineGroup(string? locationKey, IEnumerable<IMachine> machines, IEnumer
this.Tiles = [.. tiles];
this.Monitor = monitor;

this.IsJunimoGroup = this.Containers.Any(p => p.IsJunimoChest);
foreach (IContainer container in this.Containers)
{
if (container.IsGlobalChest)
{
this.GlobalContainerKeys.Add(container.GlobalInventoryId);
}
}

this.IsGlobalGroup = this.GlobalContainerKeys.Count > 0;
this.StorageManager = buildStorage(this.GetUniqueContainers(this.Containers));
}

Expand Down
104 changes: 79 additions & 25 deletions Automate/Framework/MachineManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ internal class MachineManager
public MachineGroupFactory Factory { get; }

/// <summary>An aggregate collection of machine groups linked by Junimo chests.</summary>
public JunimoMachineGroup JunimoMachineGroup { get; }
public List<GlobalMachineGroup> GlobalMachineGroups { get; } = new();


/*********
Expand All @@ -67,7 +67,7 @@ public MachineManager(Func<ModConfig> config, DataModel data, IAutomationFactory
this.Factory = new(this.GetMachineOverride, this.BuildStorage, monitor);
this.Factory.Add(defaultFactory);

this.JunimoMachineGroup = new(this.Factory.SortMachines, this.BuildStorage, this.Monitor);
//this.GlobalMachineGroups = new(this.Factory.SortMachines, this.BuildStorage, this.Monitor);
}

/****
Expand All @@ -76,8 +76,9 @@ public MachineManager(Func<ModConfig> config, DataModel data, IAutomationFactory
/// <summary>Get the machine groups in every location.</summary>
public IEnumerable<IMachineGroup> GetActiveMachineGroups()
{
if (this.JunimoMachineGroup.HasInternalAutomation)
yield return this.JunimoMachineGroup;
foreach (IMachineGroup group in this.GlobalMachineGroups)
if (group.HasInternalAutomation)
yield return group;

foreach (IMachineGroup group in this.ActiveMachineGroups)
yield return group;
Expand All @@ -92,7 +93,7 @@ public IEnumerable<IMachineGroup> GetForApi(GameLocation location)
return this
.ActiveMachineGroups
.Concat(this.DisabledMachineGroups)
.Concat(this.JunimoMachineGroup.GetAll())
.Concat(this.GlobalMachineGroups.SelectMany(group => group.GetAll()))
.Where(p => p.LocationKey == locationKey);
}

Expand Down Expand Up @@ -141,15 +142,16 @@ public void Clear()
this.MachineData.Clear();
this.ActiveMachineGroups = [];
this.DisabledMachineGroups = [];
this.JunimoMachineGroup.Clear();
this.GlobalMachineGroups.Clear();
}

/// <summary>Clear all registered machines and add all locations to the reload queue.</summary>
public void Reset()
{
this.Clear();

this.JunimoMachineGroup.Rebuild();
foreach (GlobalMachineGroup group in this.GlobalMachineGroups)
group.Rebuild();

this.ReloadQueue.AddRange(CommonHelper.GetLocations());
}
Expand Down Expand Up @@ -213,7 +215,8 @@ private StorageManager BuildStorage(IContainer[] containers)
/// <param name="removedLocations">The locations which have been removed, and whose machines should be reloaded if they still exist.</param>
private void ReloadMachinesIn(ISet<GameLocation> locations, ISet<GameLocation> removedLocations)
{
bool junimoGroupChanged = false;
List<IMachineGroup> globalAdded = [];
HashSet<IMachineGroup> globalChanged = [];
bool anyChanged = false;

// remove old groups
Expand All @@ -225,10 +228,13 @@ private void ReloadMachinesIn(ISet<GameLocation> locations, ISet<GameLocation> r
foreach (string locationKey in locationKeys)
anyChanged |= this.MachineData.Remove(locationKey);

if (this.JunimoMachineGroup.RemoveLocations(locationKeys))
foreach (GlobalMachineGroup globalGroup in this.GlobalMachineGroups)
{
anyChanged = true;
junimoGroupChanged = true;
if (globalGroup.RemoveLocations(locationKeys))
{
anyChanged = true;
globalChanged.Add(globalGroup);
}
}
}

Expand All @@ -240,31 +246,26 @@ private void ReloadMachinesIn(ISet<GameLocation> locations, ISet<GameLocation> r
// collect new groups
List<IMachineGroup> active = [];
List<IMachineGroup> disabled = [];
List<IMachineGroup> junimo = [];
foreach (IMachineGroup group in this.Factory.GetMachineGroups(location, this.Monitor))
{
if (!group.HasInternalAutomation)
disabled.Add(group);

else if (group.IsJunimoGroup)
junimo.Add(group);
else if (!group.IsGlobalGroup)
active.Add(group);

else
active.Add(group);
{
globalAdded.Add(group);
globalChanged.Add(group);
}
}

// add groups
this.MachineData[locationKey] = new MachineDataForLocation(locationKey, active, disabled);

// track change
if (junimo.Any())
{
this.JunimoMachineGroup.Add(junimo);
junimoGroupChanged = true;
anyChanged = true;
}
else if (active.Any())
anyChanged = true;
anyChanged |= active.Any();
}

// rebuild caches
Expand All @@ -283,7 +284,60 @@ private void ReloadMachinesIn(ISet<GameLocation> locations, ISet<GameLocation> r
this.DisabledMachineGroups = disabled.ToArray();
}

if (junimoGroupChanged)
this.JunimoMachineGroup.Rebuild();
if (!globalChanged.Any())
return;

// determine distinct groups
List<HashSet<string>> distinctGlobalGroups = [];
foreach (HashSet<string> groupKeys in globalChanged.Select(p => p.GlobalContainerKeys))
{
HashSet<string>? existing = distinctGlobalGroups.FirstOrDefault(p => p.Overlaps(groupKeys));
if (existing != null)
existing.UnionWith(groupKeys);
else
distinctGlobalGroups.Add(groupKeys);
}

foreach (HashSet<string> groupKeys in distinctGlobalGroups)
{
GlobalMachineGroup? selectedGroup = null;
int total = this.GlobalMachineGroups.Count;

for (int i = 0; i < total; i++)
{
GlobalMachineGroup globalGroup = this.GlobalMachineGroups[i];
if (!globalGroup.GlobalContainerKeys.Overlaps(groupKeys))
continue;

selectedGroup ??= globalGroup;
if (selectedGroup == globalGroup)
globalChanged.Add(selectedGroup);

else
{
selectedGroup.Add([.. globalGroup.GetAll()]);
this.GlobalMachineGroups.Remove(globalGroup);
total--;
}
}

// create new group
if (selectedGroup == null)
{
selectedGroup = new GlobalMachineGroup(this.Factory.SortMachines, this.BuildStorage, this.Monitor);
this.GlobalMachineGroups.Add(selectedGroup);
}

// add groups to selected
IList<IMachineGroup> groups = [.. globalAdded.Where(p => p.GlobalContainerKeys.Overlaps(groupKeys))];
if (groups.Any())
selectedGroup.Add(groups);
}

// rebuild groups
foreach (GlobalMachineGroup globalGroup in globalChanged.OfType<GlobalMachineGroup>())
{
globalGroup.Rebuild();
}
}
}
Loading