Skip to content

Commit

Permalink
Rewrite the NDMF public API to use a fluent style
Browse files Browse the repository at this point in the history
This also implements stronger constraint types, to help give better control
over sequencing.
  • Loading branch information
bdunderscore committed Sep 10, 2023
1 parent 6548f4c commit bfe7ac2
Show file tree
Hide file tree
Showing 62 changed files with 1,328 additions and 525 deletions.
24 changes: 24 additions & 0 deletions Editor/API/Attributes/BuildPhase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Collections.Immutable;

namespace nadena.dev.ndmf
{
public class BuildPhase
{
public string Name { get; }
// Prevent extension for now
internal BuildPhase(string name)
{
Name = name;
}

public static readonly BuildPhase Resolving = new BuildPhase("Resolving");
public static readonly BuildPhase Generating = new BuildPhase("Generating");
public static readonly BuildPhase Transforming = new BuildPhase("Transforming");
public static readonly BuildPhase Optimizing = new BuildPhase("Optimizing");

public static readonly ImmutableList<BuildPhase> BuiltInPhases
= ImmutableList.Create(Resolving, Generating, Transforming, Optimizing);

public override string ToString() => Name;
}
}
3 changes: 3 additions & 0 deletions Editor/API/Attributes/BuildPhase.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 0 additions & 20 deletions Editor/API/Attributes/DefineAvatarBuildPlugin.cs

This file was deleted.

3 changes: 0 additions & 3 deletions Editor/API/Attributes/DefineAvatarBuildPlugin.cs.meta

This file was deleted.

15 changes: 15 additions & 0 deletions Editor/API/Attributes/ExportsPlugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace nadena.dev.ndmf
{
[AttributeUsage(AttributeTargets.Assembly)]
public class ExportsPlugin : Attribute
{
public Type PluginType { get; }

public ExportsPlugin(Type pluginType)
{
PluginType = pluginType;
}
}
}
3 changes: 3 additions & 0 deletions Editor/API/Attributes/ExportsPlugin.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Editor/API/Attributes/NDMFInternalEarlyPass.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;

namespace nadena.dev.Av3BuildFramework
namespace nadena.dev.ndmf
{
[System.AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
internal class NDMFInternalEarlyPass : System.Attribute
Expand Down
2 changes: 1 addition & 1 deletion Editor/API/BuildContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ internal void RunPass(ConcretePass pass)

Stopwatch passTimer = new Stopwatch();
passTimer.Start();
pass.Process(this);
pass.Execute(this);
passTimer.Stop();

BuildEvent.Dispatch(new BuildEvent.PassExecuted(
Expand Down
3 changes: 3 additions & 0 deletions Editor/API/Fluent.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 63 additions & 0 deletions Editor/API/Fluent/Pass.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using System.Threading;
using NUnit.Framework;

namespace nadena.dev.ndmf.fluent
{
internal interface IPass
{
string QualifiedName { get; }
string DisplayName { get; }

PassKey PassKey { get; }
bool IsPhantom { get; }

void Execute(BuildContext context);
}

internal class AnonymousPass : IPass
{
public string QualifiedName { get; }
public string DisplayName { get; }

private readonly InlinePass _executor;
public PassKey PassKey => new PassKey(QualifiedName);

internal bool IsPhantom { get; set; }
bool IPass.IsPhantom => IsPhantom;

public AnonymousPass(string qualifiedName, string displayName, InlinePass execute)
{
QualifiedName = qualifiedName;
DisplayName = displayName;
_executor = execute;
}

public void Execute(BuildContext context)
{
_executor(context);
}
}

public abstract class Pass<T> : IPass where T : Pass<T>, new()
{
private static Lazy<T> _instance = new Lazy<T>(() => new T(),
LazyThreadSafetyMode.ExecutionAndPublication);

public static T Instance => _instance.Value;

PassKey IPass.PassKey => new PassKey(QualifiedName);

public virtual string QualifiedName => typeof(T).FullName;
public virtual string DisplayName => typeof(T).Name;
bool IPass.IsPhantom => false;

protected abstract void Execute(BuildContext context);

// Prevent Pass handles from being used to execute passes arbitrarily by exposing it via an internal interface
void IPass.Execute(BuildContext context)
{
Execute(context);
}
}
}
3 changes: 3 additions & 0 deletions Editor/API/Fluent/Pass.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions Editor/API/Fluent/PassKey.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace nadena.dev.ndmf.fluent
{
internal class PassKey
{
public readonly string QualifiedName;

public PassKey(string qualifiedName)
{
QualifiedName = qualifiedName;
}

public override string ToString()
{
return QualifiedName;
}
}
}
3 changes: 3 additions & 0 deletions Editor/API/Fluent/PassKey.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 53 additions & 0 deletions Editor/API/Fluent/Plugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Threading;
using nadena.dev.ndmf;

namespace nadena.dev.ndmf.fluent
{
internal interface IPlugin
{
string QualifiedName { get; }
string DisplayName { get; }

void Configure(PluginInfo info);
}

public abstract class Plugin<T> : IPlugin where T: Plugin<T>, new()
{
private static object _lock = new object();
private static Lazy<Plugin<T>> _instance = new Lazy<Plugin<T>>(() => new T(),
LazyThreadSafetyMode.ExecutionAndPublication);

public static Plugin<T> Instance => _instance.Value;

private PluginInfo _info;

public virtual string QualifiedName => typeof(T).FullName;
public virtual string DisplayName => QualifiedName;

void IPlugin.Configure(PluginInfo info)
{
_info = info;
try
{
Configure();
}
finally
{
_info = null;
}
}

protected abstract void Configure();

protected Sequence InPhase(BuildPhase phase)
{
if (_info == null)
{
throw new Exception("InPhase can only be called from within Configure()");
}

return _info.NewSequence(phase);
}
}
}
3 changes: 3 additions & 0 deletions Editor/API/Fluent/Plugin.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions Editor/API/Fluent/PluginInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using nadena.dev.ndmf.model;

namespace nadena.dev.ndmf.fluent
{
internal class PluginInfo
{
private readonly SolverContext _solverContext;
private readonly IPlugin _plugin;
private int sequenceIndex = 0;

public PluginInfo(SolverContext solverContext, IPlugin plugin)
{
_solverContext = solverContext;
_plugin = plugin;
}

internal Sequence NewSequence(BuildPhase phase)
{
string sequencePrefix = _plugin.QualifiedName + "/sequence#" + sequenceIndex++;
return new Sequence(phase, _solverContext, _plugin, sequencePrefix);
}
}
}
3 changes: 3 additions & 0 deletions Editor/API/Fluent/PluginInfo.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Editor/API/Fluent/Sequence.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

97 changes: 97 additions & 0 deletions Editor/API/Fluent/Sequence/Constraints.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using nadena.dev.ndmf.model;

namespace nadena.dev.ndmf.fluent
{
public sealed partial class Sequence
{
private List<Action<SolverPass>> _pendingDependencies = new List<Action<SolverPass>>();

private void OnNewPass(SolverPass pass)
{
foreach (var dep in _pendingDependencies)
{
dep(pass);
}
_pendingDependencies.Clear();
}

public Sequence BeforePlugin(string QualifiedName, string sourceFile = "", int sourceLine = 0)
{
_solverContext.Constraints.Add(new Constraint()
{
First = _sequenceEnd.PassKey,
Second = _solverContext.GetPluginPhases(_phase, QualifiedName).PluginStart.PassKey,
Type = ConstraintType.WeakOrder,
DeclaredFile = sourceFile,
DeclaredLine = sourceLine,
});

return this;
}

public Sequence BeforePlugin<T>(T plugin, string sourceFile = "", int sourceLine = 0) where T : fluent.Plugin<T>, new()
{
return BeforePlugin(plugin.QualifiedName, sourceFile, sourceLine);
}

public Sequence AfterPlugin(string qualifiedName, string sourceFile = "", int sourceLine = 0)
{
_solverContext.Constraints.Add(new Constraint()
{
First = _solverContext.GetPluginPhases(_phase, qualifiedName).PluginEnd.PassKey,
Second = _sequenceStart.PassKey,
Type = ConstraintType.WeakOrder,
DeclaredFile = sourceFile,
DeclaredLine = sourceLine,
});

return this;
}

public Sequence AfterPlugin<T>(T plugin, string sourceFile = "", int sourceLine = 0) where T : Plugin<T>, new()
{
return AfterPlugin(plugin.QualifiedName, sourceFile, sourceLine);
}

public Sequence WaitFor<T>(T pass, string sourceFile = "", int sourceLine = 0) where T : fluent.Pass<T>, new()
{
_pendingDependencies.Add(nextPass =>
{
_solverContext.Constraints.Add(new Constraint()
{
First = ((IPass) pass).PassKey,
Second = nextPass.PassKey,
Type = ConstraintType.WaitFor,
DeclaredFile = sourceFile,
DeclaredLine = sourceLine,
});
});

return this;
}

public Sequence AfterPass(string qualifiedName, string sourceFile = "", int sourceLine = 0)
{
_pendingDependencies.Add(nextPass =>
{
_solverContext.Constraints.Add(new Constraint()
{
First = nextPass.PassKey,
Second = _solverContext.Passes.Find(p => p.PassKey.QualifiedName == qualifiedName).PassKey,
Type = ConstraintType.WeakOrder,
DeclaredFile = sourceFile,
DeclaredLine = sourceLine,
});
});

return this;
}

public Sequence AfterPass<T>(T pass, string sourceFile = "", int sourceLine = 0) where T : Pass<T>, new()
{
return AfterPass(pass.QualifiedName, sourceFile, sourceLine);
}
}
}
3 changes: 3 additions & 0 deletions Editor/API/Fluent/Sequence/Constraints.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit bfe7ac2

Please sign in to comment.