Skip to content

Commit

Permalink
Add PostLoad dependencies and a stable dag evaluator.
Browse files Browse the repository at this point in the history
  • Loading branch information
zorbathut committed Sep 26, 2024
1 parent c431c05 commit cecc044
Show file tree
Hide file tree
Showing 6 changed files with 365 additions and 17 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
All notable changes to this project will be documented in this file.


## [Unreleased]
### Added
* Added a method of passing class dependencies into Parser.Finish() and ParserModular.Finish(). This is a prototype and will probably change in the future.
* Added a general-purpose stable dag evaluator, which is exposed mostly because it's often convenient.


## [v0.7.1]
### Added
* Proper support for Nullable.
Expand Down
104 changes: 104 additions & 0 deletions src/Dag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@

using System;
using System.Collections.Generic;
using System.Linq;

namespace Dec
{
/// <summary>
/// A simple stable Dag evaluation class, made public for utility usage.
/// </summary>
/// <remarks>
/// This isn't *really* part of Dec, it's just here for convenience because Dec needs it.
/// </remarks>
public static class Dag<T>
{
/// <summary>
/// Information on a single dag dependency.
/// </summary>
public struct Dependency
{
public T before;
public T after;
}

private enum Status
{
Unvisited,
Visiting,
Visited
}

/// <summary>
/// Given an input list and a list of dependencies, calculate a stable order.
/// </summary>
/// <remarks>
/// This is guaranteed to return the same order every run.
///
/// Some effort is made to minimize output changes if more items are added or more dependencies are added.
///
/// Output may change after Dec is updated; this is not guaranteed stable between versions!
/// </remarks>
public static List<T> CalculateOrder<U>(IEnumerable<T> input, List<Dependency> dependencies, Func<T, U> tiebreaker) where U : IComparable<U>
{
// OrderBy is a stable sort, which is important for us
var inputOrder = input.OrderBy(tiebreaker).ToArray();
var seen = new Status[inputOrder.Length];

List<List<T>> dependenciesCompiled = new List<List<T>>();
foreach (var t in inputOrder)
{
// unnecessary allocations but whatever
dependenciesCompiled.Add(new List<T>());
}

foreach (var dep in dependencies)
{
int beforeIndex = Array.IndexOf(inputOrder, dep.before);
int afterIndex = Array.IndexOf(inputOrder, dep.after);
if (beforeIndex == -1 || afterIndex == -1)
{
Dbg.Err($"Dependency references an item not in the input list: {dep.before} -> {dep.after}. If you want this to work, go pester Zorba on Discord.");
continue;
}

dependenciesCompiled[afterIndex].Add(dep.before);
}

List<T> result = new List<T>();
for (int i = 0; i < inputOrder.Length; i++)
{
Visit(inputOrder, i, seen, dependenciesCompiled, result);
}

return result;
}

private static void Visit(T[] inputOrder, int i, Status[] status, List<List<T>> dependenciesCompiled, List<T> result)
{
if (status[i] == Status.Visited)
{
return;
}

if (status[i] == Status.Visiting)
{
Dbg.Err($"Cycle detected in dependency graph involving {inputOrder[i]}");
return;
}

status[i] = Status.Visiting;

foreach (var dep in dependenciesCompiled[i])
{
// this is absolutely a lot slower than it needs to be
int depIndex = Array.IndexOf(inputOrder, dep);
Visit(inputOrder, depIndex, status, dependenciesCompiled, result);
}

status[i] = Status.Visited;

result.Add(inputOrder[i]);
}
}
}
33 changes: 33 additions & 0 deletions src/Database.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ public static IEnumerable<Dec> List
}
}

/// <summary>
/// All registered dec root types.
/// </summary>
/// <remarks>
/// Types are listed in no guaranteed or stable order.
///
/// As of this writing, this will return only Dec types that have instances. This may change someday.
/// </remarks>
public static IEnumerable<Type> ListTypes
{
get
{
return Lookup.Keys.Where(type => type.GetDecDatabaseStatus() == UtilType.DecDatabaseStatus.Root);
}
}

/// <summary>
/// Retrieves a dec by base dec type and name.
/// </summary>
Expand All @@ -63,6 +79,23 @@ public static Dec Get(Type type, string name)
return typedict.TryGetValue(name);
}

/// <summary>
/// Retrieves a list of decs of a given type.
/// </summary>
/// <remarks>
/// Returns null if no such dec exists.
/// </remarks>
public static IEnumerable<Dec> ListOfType(Type type)
{
if (!Lookup.ContainsKey(type))
{
WarnOnEmpty();
return Enumerable.Empty<Dec>();
}

return Lookup[type].Values;
}

/// <summary>
/// Creates a Dec.
/// </summary>
Expand Down
8 changes: 6 additions & 2 deletions src/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,13 @@ public void AddStream(Parser.FileType fileType, Stream stream, string identifier
/// <summary>
/// Finish all parsing.
/// </summary>
public void Finish()
/// <remarks>
/// The `dependencies` parameter can be used to feed in dependencies for the PostLoad function.
/// This is a placeholder and is probably going to be replaced at some point, though only with something more capable.
/// </remarks>
public void Finish(List<Dag<Type>.Dependency> postLoadDependencies = null)
{
parserModdable.Finish();
parserModdable.Finish(postLoadDependencies);
}
}
}
43 changes: 28 additions & 15 deletions src/ParserModular.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,11 @@ public Module CreateModule(string name)
/// <summary>
/// Finish all parsing.
/// </summary>
public void Finish()
/// <remarks>
/// The `dependencies` parameter can be used to feed in dependencies for the PostLoad function.
/// This is a placeholder and is probably going to be replaced at some point, though only with something more capable.
/// </remarks>
public void Finish(List<Dag<Type>.Dependency> postLoadDependencies = null)
{
using (var _ = new CultureInfoScope(Config.CultureInfo))
{
Expand Down Expand Up @@ -460,27 +464,36 @@ public void Finish()
}
s_Status = Status.Finalizing;

foreach (var dec in Database.List)
// figure out our config/postload order
var postprocessOrder = Dag<Type>.CalculateOrder(Database.ListTypes, postLoadDependencies ?? new List<Dag<Type>.Dependency>(), t => t.FullName);

foreach (var type in postprocessOrder)
{
try
{
dec.ConfigErrors(err => Dbg.Err($"{dec}: {err}"));
}
catch (Exception e)
foreach (var dec in Database.ListOfType(type))
{
Dbg.Ex(e);
try
{
dec.ConfigErrors(err => Dbg.Err($"{dec}: {err}"));
}
catch (Exception e)
{
Dbg.Ex(e);
}
}
}

foreach (var dec in Database.List)
foreach (var type in postprocessOrder)
{
try
foreach (var dec in Database.ListOfType(type))
{
dec.PostLoad(err => Dbg.Err($"{dec}: {err}"));
}
catch (Exception e)
{
Dbg.Ex(e);
try
{
dec.PostLoad(err => Dbg.Err($"{dec}: {err}"));
}
catch (Exception e)
{
Dbg.Ex(e);
}
}
}

Expand Down
Loading

0 comments on commit cecc044

Please sign in to comment.