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 unreachable milestones #207

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
18 changes: 11 additions & 7 deletions YAFCmodel/Analysis/Dependencies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,21 @@ private class DependencyCollector : IDependencyCollector
{
private readonly List<DependencyList> list = new List<DependencyList>();

public void Add(FactorioId[] raw, DependencyList.Flags flags)
public void Add(FactorioId[] elements, DependencyList.Flags flags)
{
list.Add(new DependencyList {elements = raw, flags = flags});
// Only add lists that actually contain elements, lists that are used to hide objects, or lists to unlock technologies (because of the lack of unlocking dependencies those should be unavailable)
if (elements.Length > 0 || flags == DependencyList.Flags.Hidden || flags == DependencyList.Flags.TechnologyUnlock)
{
list.Add(new DependencyList { elements = elements, flags = flags });
}
}

public void Add(IReadOnlyList<FactorioObject> raw, DependencyList.Flags flags)
public void Add(IReadOnlyList<FactorioObject> readOnlyList, DependencyList.Flags flags)
{
var elems = new FactorioId[raw.Count];
for (var i = 0; i < raw.Count; i++)
elems[i] = raw[i].id;
list.Add(new DependencyList {elements = elems, flags = flags});
var elems = new FactorioId[readOnlyList.Count];
for (var i = 0; i < readOnlyList.Count; i++)
elems[i] = readOnlyList[i].id;
Add(elems, flags);
}

public DependencyList[] Pack()
Expand Down
90 changes: 60 additions & 30 deletions YAFCmodel/Analysis/Milestones.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ namespace YAFC.Model
public class Milestones : Analysis
{
public static readonly Milestones Instance = new Milestones();
public FactorioObject[] currentMilestones;

public FactorioObject[] currentMilestones;
public Mapping<FactorioObject, ulong> milestoneResult;
public ulong lockedMask { get; private set; }
private Project project;
Expand Down Expand Up @@ -54,10 +54,10 @@ public FactorioObject GetHighest(FactorioObject target, bool all)
ms &= lockedMask;
if (ms == 0)
return null;
var msb = MathUtils.HighestBitSet(ms)-1;
var msb = MathUtils.HighestBitSet(ms) - 1;
return msb < 0 || msb >= currentMilestones.Length ? null : currentMilestones[msb];
}

[Flags]
private enum ProcessingFlags : byte
{
Expand All @@ -81,12 +81,12 @@ public void ComputeWithParameters(Project project, ErrorCollector warnings, Fact
this.project = project;
project.settings.changed += ProjectSettingsChanged;
}

var time = Stopwatch.StartNew();
var result = Database.objects.CreateMapping<ulong>();
var processing = Database.objects.CreateMapping<ProcessingFlags>();
var processingQueue = new Queue<FactorioId>();

foreach (var rootAccessbile in Database.rootAccessible)
{
result[rootAccessbile] = 1;
Expand All @@ -101,13 +101,14 @@ public void ComputeWithParameters(Project project, ErrorCollector warnings, Fact
result[obj] = 1;
processingQueue.Enqueue(obj.id);
processing[obj] = ProcessingFlags.Initial | ProcessingFlags.InQueue;
} else if (flag.HasFlag(ProjectPerItemFlags.MarkedInaccessible))
}
else if (flag.HasFlag(ProjectPerItemFlags.MarkedInaccessible))
processing[obj] = ProcessingFlags.ForceInaccessible;
}

if (autoSort)
{
// Adding default milestones AND special flag to auto-order them
// Set special flag to auto-order the milestones, keep currentMilestones empty, as the milestones needs to be added later so they get ordered according to their dependencies
foreach (var milestone in milestones)
processing[milestone] |= ProcessingFlags.MilestoneNeedOrdering;
currentMilestones = new FactorioObject[milestones.Length];
Expand All @@ -126,14 +127,14 @@ public void ComputeWithParameters(Project project, ErrorCollector warnings, Fact
var nextMilestoneMask = 0x2ul;
var nextMilestoneIndex = 0;
var accessibleObjects = 0;

var flagMask = 0ul;
for (var i = 0; i <= currentMilestones.Length; i++)
{
flagMask |= 1ul << i;
if (i > 0)
{
var milestone = currentMilestones[i-1];
var milestone = currentMilestones[i - 1];
if (milestone == null)
{
milestonesNotReachable = new List<FactorioObject>();
Expand All @@ -148,45 +149,66 @@ public void ComputeWithParameters(Project project, ErrorCollector warnings, Fact
Array.Resize(ref currentMilestones, nextMilestoneIndex);
break;
}
Console.WriteLine("Processing milestone "+milestone.locName);
processingQueue.Enqueue(milestone.id);
processing[milestone] = ProcessingFlags.Initial | ProcessingFlags.InQueue;
else
{
Console.WriteLine("Queuing milestone {0} ({1}) [{2}]", milestone.name, milestone.id, milestone.GetType().Name);

processingQueue.Enqueue(milestone.id);
processing[milestone] = ProcessingFlags.Initial | ProcessingFlags.InQueue;
}
}

while (processingQueue.Count > 0)
{
var elem = processingQueue.Dequeue();
var entry = dependencyList[elem];
var elemDeps = dependencyList[elem];

var cur = result[elem];
var eflags = cur;
var isInitial = (processing[elem] & ProcessingFlags.Initial) != 0;
processing[elem] &= ProcessingFlags.MilestoneNeedOrdering;

foreach (var list in entry)
// Console.WriteLine("Processing {0} ({1}) [{2}] -> isInitial: {3}", Database.objects[elem].name, Database.objects[elem].id, Database.objects[elem].GetType().Name, isInitial);

foreach (var list in elemDeps)
{
// Console.WriteLine(" -> {0}: req all {1}", list.flags, list.flags & DependencyList.Flags.RequireEverything);
if ((list.flags & DependencyList.Flags.RequireEverything) != 0)
{
foreach (var req in list.elements)
{
var reqFlags = result[req];
// Console.WriteLine(" -> dep all {0} ({1}) [{2}]: reqflags {3}", Database.objects[req].name, Database.objects[req].id, Database.objects[req].GetType().Name, reqFlags);
if (reqFlags == 0 && !isInitial)
goto skip;
eflags |= result[req];
eflags |= reqFlags;
}
}
else
{
if (list.elements.Length == 0)
{
if ((list.flags & DependencyList.Flags.Hidden) != DependencyList.Flags.Hidden && (list.flags & DependencyList.Flags.TechnologyUnlock) != DependencyList.Flags.TechnologyUnlock)
{
Console.WriteLine("Unexpected: {0} ({1}) [{2}] - {3} group deps empty, will cause unreachable elements", Database.objects[elem].name, Database.objects[elem].id, Database.objects[elem].GetType().Name, list.flags);
}
}

// Minimize group (dependency) cost
var groupFlags = 0ul;
foreach (var req in list.elements)
{
var acc = result[req];
if (acc == 0)
var reqFlags = result[req];
// Console.WriteLine(" -> dep grp {0} ({1}) [{2}]: reqflags {3}, groupFlags {4}", Database.objects[req].name, Database.objects[req].id, Database.objects[req].GetType().Name, reqFlags, groupFlags);
if (reqFlags == 0)
// Dependency is not available/processed yet, so check next
continue;
if (acc < groupFlags || groupFlags == 0ul)
groupFlags = acc;

if (reqFlags < groupFlags || groupFlags == 0ul)
groupFlags = reqFlags;
}

// Console.WriteLine(" -> group flags {0}", groupFlags);
if (groupFlags == 0 && !isInitial)
goto skip;
eflags |= groupFlags;
Expand All @@ -195,32 +217,40 @@ public void ComputeWithParameters(Project project, ErrorCollector warnings, Fact
if (!isInitial)
{
if (eflags == cur || (eflags | flagMask) != flagMask)
{
// Console.WriteLine(" -> Skipping: eflags {0}, flagMask {1}, cur {2}", eflags, flagMask, cur);
continue;
}
}
else eflags &= flagMask;

accessibleObjects++;
//var obj = Database.objects[elem];
//Console.WriteLine("Added object "+obj.locName+" ["+obj.GetType().Name+"] with mask "+eflags.ToString("X") + " (was "+cur.ToString("X")+")");
// Console.WriteLine(" -> Added object {0} ({1}) [{2}] with eflags {3} (was {4})", Database.objects[elem].name, Database.objects[elem].id, Database.objects[elem].GetType().Name, eflags, cur);

if (processing[elem] == ProcessingFlags.MilestoneNeedOrdering)
{
// Auto-sorting, elem was not added to currentMilestones yet, so add now its dependencies are solved
processing[elem] = 0;
eflags |= nextMilestoneMask;
nextMilestoneMask <<= 1;
currentMilestones[nextMilestoneIndex++] = Database.objects[elem];
}

result[elem] = eflags;
// Add reverse dependencies to feed the algorithm with new processable elements
foreach (var revdep in reverseDependencies[elem])
{
if ((processing[revdep] & ~ProcessingFlags.MilestoneNeedOrdering) != 0 || result[revdep] != 0)
// Already/About to be processed
continue;

// Console.WriteLine(" -> Queuing rev dep {0} ({1}) [{2}]", Database.objects[revdep].name, Database.objects[revdep].id, Database.objects[revdep].GetType().Name);

processing[revdep] |= ProcessingFlags.InQueue;
processingQueue.Enqueue(revdep);
}
skip:;

skip:;
}
}

Expand All @@ -235,20 +265,20 @@ public void ComputeWithParameters(Project project, ErrorCollector warnings, Fact
var hasAutomatableRocketLaunch = result[Database.objectsByTypeName["Special.launch"]] != 0;
if (accessibleObjects < Database.objects.count / 2)
{
warnings.Error("More than 50% of all in-game objects appear to be inaccessible in this project with your current mod list. This can have a variety of reasons like objects being accessible via scripts," +
warnings.Error("More than 50% of all in-game objects appear to be inaccessible in this project with your current mod list. This can have a variety of reasons like objects being accessible via scripts," +
MaybeBug + MilestoneAnalysisIsImportant + UseDependencyExplorer, ErrorSeverity.AnalysisWarning);
}
}
else if (!hasAutomatableRocketLaunch)
{
warnings.Error("Rocket launch appear to be inaccessible. This means that rocket may not be launched in this mod pack, or it requires mod script to spawn or unlock some items," +
warnings.Error("Rocket launch appear to be inaccessible. This means that rocket may not be launched in this mod pack, or it requires mod script to spawn or unlock some items," +
MaybeBug + MilestoneAnalysisIsImportant + UseDependencyExplorer, ErrorSeverity.AnalysisWarning);
}
}
else if (milestonesNotReachable != null)
{
warnings.Error("There are some milestones that are not accessible: " + string.Join(", ", milestonesNotReachable.Select(x => x.locName)) + ". You may remove these from milestone list," +
MaybeBug + MilestoneAnalysisIsImportant + UseDependencyExplorer, ErrorSeverity.AnalysisWarning);
}
Console.WriteLine("Milestones calculation finished in "+time.ElapsedMilliseconds+" ms.");
Console.WriteLine("Milestones calculation finished in {0} ms.", time.ElapsedMilliseconds);
milestoneResult = result;
}

Expand Down