diff --git a/Editor/API/Attributes/BuildPhase.cs b/Editor/API/Attributes/BuildPhase.cs
index 7c3d31c8..0d90b307 100644
--- a/Editor/API/Attributes/BuildPhase.cs
+++ b/Editor/API/Attributes/BuildPhase.cs
@@ -6,7 +6,17 @@
namespace nadena.dev.ndmf
{
- public class BuildPhase
+ ///
+ /// Build Phases provide a coarse mechanism for grouping passes for execution. Each build phase has a recommended
+ /// usage to help avoid ordering conflicts without needing explicit constraints.
+ ///
+ /// Currently, the following phases are defined:
+ /// - Resolving
+ /// - Generating
+ /// - Transforming
+ /// - Optimizing
+ ///
+ public sealed class BuildPhase
{
public string Name { get; }
@@ -16,11 +26,39 @@ internal BuildPhase(string name)
Name = name;
}
+ ///
+ /// The resolving phase is intended for use by passes which perform very early processing of components and
+ /// avatar state, before any large-scale changes have been made. For example, Modular Avatar uses this phase
+ /// to resolve string-serialized object passes to their destinations, and to clone animation controllers before
+ /// any changes are made to them.
+ ///
+ /// NDMF also has a built-in phase in Resolving, which removes EditorOnly objects. For more information,
+ /// see nadena.dev.ndmf.builtin.RemoveEditorOnlyPass.
+ ///
+ ///
public static readonly BuildPhase Resolving = new BuildPhase("Resolving");
+
+ ///
+ /// The generating phase is intended for use by asses which generate components used by later plugins. For
+ /// example, if you want to generate components that will be used by Modular Avatar, this would be the place
+ /// to do it.
+ ///
public static readonly BuildPhase Generating = new BuildPhase("Generating");
+
+ ///
+ /// The transforming phase is intended for general-purpose avatar transformations. Most of Modular Avatar's
+ /// logic runs here.
+ ///
public static readonly BuildPhase Transforming = new BuildPhase("Transforming");
+
+ ///
+ /// The optimizing phase is intended for pure optimizations that need to run late in the build process.
+ ///
public static readonly BuildPhase Optimizing = new BuildPhase("Optimizing");
+ ///
+ /// This list contains all built-in phases in the order that they will be executed.
+ ///
public static readonly ImmutableList BuiltInPhases
= ImmutableList.Create(Resolving, Generating, Transforming, Optimizing);
diff --git a/Editor/API/Attributes/ExportsPlugin.cs b/Editor/API/Attributes/ExportsPlugin.cs
index 23e929e5..115befca 100644
--- a/Editor/API/Attributes/ExportsPlugin.cs
+++ b/Editor/API/Attributes/ExportsPlugin.cs
@@ -8,9 +8,13 @@ namespace nadena.dev.ndmf
{
///
/// This attribute declares a plugin to be registered with NDMF.
- ///
+ ///
///
/// [assembly: ExportsPlugin(typeof(MyPlugin))]
+ ///
+ /// class MyPlugin : Plugin<MyPlugin> {
+ /// // ...
+ /// }
///
///
[AttributeUsage(AttributeTargets.Assembly)]
diff --git a/Editor/API/Fluent/Sequence/Extensions.cs b/Editor/API/Fluent/Sequence/Extensions.cs
index 857038bb..bec57509 100644
--- a/Editor/API/Fluent/Sequence/Extensions.cs
+++ b/Editor/API/Fluent/Sequence/Extensions.cs
@@ -119,6 +119,7 @@ public void WithCompatibleExtension(Type extension, Action action)
/// sequence.WithRequiredExtensions(new[] {typeof(foo.bar.MyExtension)}, s => {
/// s.Run(typeof(MyPass));
/// });
+ ///
///
/// The extension to request
/// An action that will be invoked with the extensions marked required
@@ -145,6 +146,7 @@ public void WithRequiredExtensions(IEnumerable extensions, Action {
/// s.Run(typeof(MyPass));
/// });
+ ///
///
/// The extension to request
/// An action that will be invoked with the extensions marked required
diff --git a/Editor/API/Fluent/Sequence/Sequence.cs b/Editor/API/Fluent/Sequence/Sequence.cs
index aa5e5f36..6e8361c6 100644
--- a/Editor/API/Fluent/Sequence/Sequence.cs
+++ b/Editor/API/Fluent/Sequence/Sequence.cs
@@ -1,5 +1,6 @@
#region
+using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using nadena.dev.ndmf.model;
@@ -18,6 +19,7 @@ namespace nadena.dev.ndmf.fluent
///
/// sequence.Run(typeof(MyPass)) // returns DeclaringPass
/// .BeforePass(typeof(OtherPass)); // valid only on DeclaringPass
+ /// .Then.Run(typeof(OtherPass));
///
///
public sealed class DeclaringPass
@@ -25,12 +27,27 @@ public sealed class DeclaringPass
private readonly SolverContext _solverContext;
private readonly BuildPhase _phase;
private readonly SolverPass _pass;
+ private readonly Sequence _seq;
- internal DeclaringPass(SolverPass pass, SolverContext solverContext, BuildPhase phase)
+ ///
+ /// Returns the original sequence that returned this DeclaringPass. This is useful for chaining multiple
+ /// pass declarations, like so:
+ ///
+ ///
+ /// InPhase(Generating)
+ /// .Run(typeof(PassOne))
+ /// .Then.Run(typeof(PassTwo));
+ ///
+ ///
+ [SuppressMessage("ReSharper", "ConvertToAutoProperty")]
+ public Sequence Then => _seq;
+
+ internal DeclaringPass(SolverPass pass, SolverContext solverContext, BuildPhase phase, Sequence seq)
{
_pass = pass;
_solverContext = solverContext;
_phase = phase;
+ _seq = seq;
}
///
@@ -208,7 +225,7 @@ private DeclaringPass InternalRun(IPass pass, string sourceFile, int sourceLine)
_priorPass = solverPass;
OnNewPass(solverPass);
- return new DeclaringPass(solverPass, _solverContext, _phase);
+ return new DeclaringPass(solverPass, _solverContext, _phase, this);
}
///
diff --git a/Editor/API/IExtensionContext.cs b/Editor/API/IExtensionContext.cs
index 23305f51..23d67ec0 100644
--- a/Editor/API/IExtensionContext.cs
+++ b/Editor/API/IExtensionContext.cs
@@ -1,8 +1,20 @@
namespace nadena.dev.ndmf
{
+ ///
+ /// The IExtensionContext is declared by custom extension contexts.
+ ///
public interface IExtensionContext
{
+ ///
+ /// Invoked when the extension is activated.
+ ///
+ ///
void OnActivate(BuildContext context);
+
+ ///
+ /// Invoked when the extension is deactivated.
+ ///
+ ///
void OnDeactivate(BuildContext context);
}
}
\ No newline at end of file
diff --git a/Editor/API/Util/VisitAssets.cs b/Editor/API/Util/VisitAssets.cs
index 19e6f1aa..6589d075 100644
--- a/Editor/API/Util/VisitAssets.cs
+++ b/Editor/API/Util/VisitAssets.cs
@@ -8,10 +8,22 @@
namespace nadena.dev.ndmf.util
{
+ ///
+ /// This class provides helpers to traverse assets or asset properties referenced from a given root object.
+ ///
public static class VisitAssets
{
public delegate bool AssetFilter(Object obj);
+ ///
+ /// Returns an enumerable of all assets referenced by the given root object.
+ ///
+ /// The asset to start traversal from
+ /// If false, traversal will not return assets that are saved
+ /// If false, scene assets will not be returned
+ /// If provided, this filter will be queried for each object encountered; if it
+ /// returns false, the selected object and all objects referenced from it will be ignored.
+ /// An enumerable of objects found
public static IEnumerable
- public static Action delayCall { get; internal set; }
+ public static Action DelayCall { get; internal set; }
static RuntimeUtil()
{
- delayCall = action => { throw new Exception("delayCall() cannot be called during static initialization"); };
+ DelayCall = action => { throw new Exception("delayCall() cannot be called during static initialization"); };
}
// Shadow the VRC-provided methods to avoid deprecation warnings
@@ -39,9 +39,9 @@ internal static T GetOrAddComponent(this Component obj) where T : Component
/// Returns whether the editor is in play mode.
///
#if UNITY_EDITOR
- public static bool isPlaying => UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode;
+ public static bool IsPlaying => UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode;
#else
- public static bool isPlaying => true;
+ public static bool IsPlaying => true;
#endif
///
diff --git a/docfx~/api/index.md b/docfx~/api/index.md
index 78dc9c00..7eeececc 100644
--- a/docfx~/api/index.md
+++ b/docfx~/api/index.md
@@ -1,2 +1,3 @@
-# PLACEHOLDER
-TODO: Add .NET projects to the *src* folder and run `docfx` to generate **REAL** *API Documentation*!
+# API Documentation
+
+Click the namespace entries on the left to see detailed API documentation.
\ No newline at end of file
diff --git a/docfx~/articles/intro.md b/docfx~/articles/intro.md
deleted file mode 100644
index c0478ced..00000000
--- a/docfx~/articles/intro.md
+++ /dev/null
@@ -1 +0,0 @@
-# Add your introductions here!
diff --git a/docfx~/articles/toc.yml b/docfx~/articles/toc.yml
deleted file mode 100644
index ff89ef1f..00000000
--- a/docfx~/articles/toc.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-- name: Introduction
- href: intro.md
diff --git a/docfx~/docfx.json b/docfx~/docfx.json
index 00be47ef..c0ecb72b 100644
--- a/docfx~/docfx.json
+++ b/docfx~/docfx.json
@@ -20,10 +20,10 @@
"includePrivateMembers": false,
"disableGitFeatures": false,
"disableDefaultFilter": false,
- "noRestore": false,
- "namespaceLayout": "flattened",
+ "noRestore": true,
+ "namespaceLayout": "nested",
"memberLayout": "samePage",
- "EnumSortOrder": "alphabetic",
+ "EnumSortOrder": "declaringOrder",
"allowCompilationErrors": true
}
],
diff --git a/docfx~/execution-model.md b/docfx~/execution-model.md
new file mode 100644
index 00000000..52a2dda7
--- /dev/null
+++ b/docfx~/execution-model.md
@@ -0,0 +1,126 @@
+# Execution Model
+
+NDMF supports a rich set of ordering constraints that can control what order processing occurs in.
+
+## High level model
+
+At a high level, there are a few main concepts behind NDMF's sequencing model.
+
+First, we have plugins. Plugins are intended to be the main end-user-visible unit of sequencing. Each plugin then
+contains some number of _passes_, which are organized into _sequences_, which are further within _build phases_.
+
+A pass is simply a callback which is executed at a particular point in the build. A sequence is a collection of passes
+that occur in a particular order. Finally, build phases provide a coarse way of grouping sequences together.
+
+## Build phases
+
+The following build phases are defined:
+
+#### Resolving
+
+The resolving phase is intended for use by passes which perform very early processing of components and
+avatar state, before any large-scale changes have been made. For example, Modular Avatar uses this phase
+to resolve string-serialized object passes to their destinations, and to clone animation controllers before
+any changes are made to them.
+
+NDMF also has a built-in phase in Resolving, which removes EditorOnly objects. For more information,
+see nadena.dev.ndmf.builtin.RemoveEditorOnlyPass.
+
+#### Generating
+
+The generating phase is intended for use by asses which generate components used by later plugins. For
+example, if you want to generate components that will be used by Modular Avatar, this would be the place
+to do it.
+
+#### Transforming
+
+The transforming phase is intended for general-purpose avatar transformations. Most of Modular Avatar's
+logic runs here.
+
+#### Optimizing
+
+The optimizing phase is intended for pure optimizations that need to run late in the build process.
+
+## Sequences and pass constraints
+
+When declaring passes, you first create a sequence, then declare passes within that sequence. If necessary,
+you can apply additional constraints, which let you inject additional passes at almost arbitrary points in the build
+process.
+
+```csharp
+
+public class MyPlugin : Plugin
+{
+ public override string DisplayName => "Baby's first plugin";
+
+ protected override void Configure()
+ {
+ Sequence seq = InPhase(BuildPhase.Transforming);
+ seq
+ .AfterPass(typeof(SomePriorPass))
+ .Run(typeof(Pass1))
+ .BeforePass(typeof(SomeOtherPass))
+ .Then.Run(typeof(Pass2));
+ }
+}
+```
+
+Sequences enforce that passes are executed in the order they are declared. Generally, NDMF will try to run passes in a
+sequence right after each other, unless some constraints prevent that from happening.
+
+If you declare multiple sequences in the same build phase, those sequences might be executed in any order relative to
+each other (or even interleaved!).
+
+### Constraint types
+
+There are several types of constraints that can be applied to passes:
+
+#### Before/AfterPlugin constraints.
+
+These declare that this particular _sequence_ runs, in its entirety, before or after _all_ processing by some other
+plugin. Note that this does not enforce that the other plugin is loaded; it only enforces that if the other plugin is
+loaded, it will run before or after this sequence.
+
+```csharp
+
+sequence.BeforePlugin("other.plugin.name");
+sequence.AfterPlugin(typeof(OtherPlugin));
+
+```
+
+Because this is intended to help resolve conflicts between optional dependencies, this accepts string names as well as
+types. If you use a string name, it must be the fully-qualified name of the plugin (by default, this will be the
+fully-qualified type name of the plugin).
+
+#### Before/AfterPass constraints.
+
+These declare that a single pass within a larger sequence runs before or after another pass.
+
+```csharp
+
+sequence.AfterPass(typeof(OtherPass))
+ .Run(typeof(MyPass))
+ .BeforePass(typeof(SomeOtherPass));
+
+```
+
+Note that the declaration order mirrors the order in which these passes will be executed. In the above example,
+the execution order will be OtherPass, MyPass, SomeOtherPass - though, other passes might happen in between.
+
+#### WaitFor constraints
+
+The WaitFor constraint is similar to AfterPass, but NDMF will attempt to run the pass as soon as possible after the
+specified pass. This is useful when you want to insert some processing between two passes within another plugin.
+
+```csharp
+
+sequence.WaitFor(typeof(OtherPass))
+ .Run(typeof(MyPass));
+
+```
+
+Note that this does not guarantee that the pass will run _immediately_ after the specified pass. If you have other
+dependencies on `MyPass` that are not yet satisfied, then `MyPass` will not run until those dependencies are satisfied.
+
+Additionally, there might be multiple `WaitFor` dependencies, in which case the order in which these are executed -
+absent other constraints - is undefined.
\ No newline at end of file
diff --git a/docfx~/extension-context.md b/docfx~/extension-context.md
new file mode 100644
index 00000000..d3b3a371
--- /dev/null
+++ b/docfx~/extension-context.md
@@ -0,0 +1,25 @@
+# Extension Contexts
+
+Extension contexts are designed to help improve build performance by amortizing some kind of processing across
+multiple passes. For example, if you have multiple passes which need to do the same analysis across all animation
+controllers, you can use an extension context to do that analysis once, then share the results with all passes.
+
+Passes declare that they either require or are compatible with specific extension context. When a pass declares that it
+requires an extension context, the extension context will be "activated" before the pass is executed. The extension
+context will then be "deactivated" when any pass that is not compatible with it is executed. This allows for any
+deferred operations (e.g. updating animations after objects move around) to be performed.
+
+The compatibility declaration is important as - for example - there might be deferred work that needs to be performed
+before a pass that is oblivious to the extension context can be allowed to execute.
+
+You can declare that a pass requires or is compatible with an extension context like so:
+
+```csharp
+
+sequence.WithCompatibleExtension("foo.bar.ExtensionClass", seq2 => {
+ seq2.WithRequiredExtension(typeof(OtherExtensionClass), seq3 => {
+ seq3.Run(typeof(MyPass));
+ });
+});
+
+```
\ No newline at end of file
diff --git a/docfx~/index.md b/docfx~/index.md
index 3ae25063..f61914c2 100644
--- a/docfx~/index.md
+++ b/docfx~/index.md
@@ -1,4 +1,44 @@
-# This is the **HOMEPAGE**.
-Refer to [Markdown](http://daringfireball.net/projects/markdown/) for how to write markdown files.
-## Quick Start Notes:
-1. Add images to the *images* folder if the file is referencing an image.
+# NDM Framework
+
+NFM Framework ("Nademof" for short) is a framework for running non-destructive build plugins when building avatars for
+VRChat (and, eventually, for other VRSNS platforms).
+
+## Why is this needed?
+
+While the VRChat SDK has support for running callbacks at build time, it does not provide support for running callbacks
+when entering play mode. If each plugin were to develop its own logic for running in play mode, this would lead to
+compatibility issues (and, in fact, this has happened already!)
+
+NDM Framework's primary goal is to improve compatibility when multiple nondestructive plugins are loaded. It also aims
+to make writing nondestructive build plugins easier.
+
+## How do I get started?
+
+Getting started is easy; a simple nondestructive plugin can look like this:
+
+```csharp
+
+[assembly: ExportsPlugin(typeof(MyPlugin))]
+
+public class MyPlugin : Plugin
+{
+ public override string DisplayName => "Baby's first plugin";
+
+ protected override void Configure()
+ {
+ InPhase(BuildPhase.Transforming).Run("Do the thing", ctx =>
+ {
+ Debug.Log("Hello world!");
+ });
+ }
+}
+
+```
+
+NDM Framework supports more advanced features such as dependency ordering, but this is enough to get started.
+
+For more information, see the [API documentation](api/index.html) or the other articles linked from the sidebar.
+
+## Support model
+
+NDMF is currently in an alpha state. The API is not fully stable yet - hopefully soon, though!
\ No newline at end of file
diff --git a/docfx~/toc.yml b/docfx~/toc.yml
index 59f80104..7daaf996 100644
--- a/docfx~/toc.yml
+++ b/docfx~/toc.yml
@@ -1,5 +1,11 @@
-- name: Articles
- href: articles/
+- name: Introduction
+ href: index.md
- name: Api Documentation
href: api/
homepage: api/index.md
+- name: Concepts
+ items:
+ - name: Execution Model
+ href: execution-model.md
+ - name: Extension Contexts
+ href: extension-context.md
\ No newline at end of file