Arlodotexe / OwlCore

Have you ever seen an Owl do a barrel roll? Me neither. Essential supplemental tooling for .NET development.
MIT License
24 stars 3 forks source link

Classware: Middleware for classes #12

Open Arlodotexe opened 11 months ago

Arlodotexe commented 11 months ago

Overview

Introduce a source generator to facilitate compile-time dynamic behavior injection. This generator will produce "plugin" implementations for classes or interfaces, allowing for behavior extension and customization without altering the original code.

Inspiration

This concept is inspired by the Strix Music SDK's "model plugin" system, which lets developers enhance the SDK by wrapping around and overriding SDK model members. Our goal is to generalize this idea for any class or interface.

Key Features

Workflow

  1. Identify classes/interfaces marked for plugin generation.
  2. Generate a derived "plugin" for each, implementing both T and IDelegable<T>.
  3. The plugin accepts a T and an IDelegable<T> instance during construction.
  4. Developers can extend the generated base class to craft custom plugins.
  5. Non-overridden plugin members delegate to IDelegable<T>.Inner.
  6. Use base or Inner to invoke the next plugin or the base class.
  7. Generate a Plugin Container class that holds all the ChainedProxyBuilder instances for possible plugins.
  8. Generate a Wrapper class for each concrete class that uses the Plugin Container to apply the relevant plugins and delegates calls to the enhanced instance.

Applications

Conclusion

This source generator promises a robust method to extend and customize class/interface behavior during compile-time. By crafting "plugin" implementations that can encapsulate and override originals, we're paving the way for a modular and scalable software architecture.

Sources

Example

Usage:

// Unsealed class, original implementation.
public class MyClass : SomeBase, ISomething, ISomethingElse
{
    public void DoSomething() { }
    public void MyMethod() { } 
}

// Custom plugin derived from the generated plugin base class.
// `Inner` may be the original implementation, a plugin, or a collapsed chain of plugins.
public partial class LoggingPlugin : SomethingPlugin
{
    public LoggingPlugin(ISomething inner) : base(inner) { }

    public override void DoSomething()
    {
        Console.WriteLine("Before DoSomething");
        base.DoSomething();
        Console.WriteLine("After DoSomething");
    }
}

// Use the source generator to request a plugin and trigger Wrapper generation
MyClass original = new();

MyClass wrapped = original.CreatePlugin(plugins: x => new LoggingPlugin(x));

Generated code:

// Generated plugin base class (by the source generator)
// Generate virtual call delegation for all members in ISomething.
public partial class SomethingPlugin : ISomething, IDelegable<ISomething>
{
    protected ISomething Inner { get; }

    public SomethingPlugin(ISomething inner)
    {
        Inner = inner;
    }

    public virtual void DoSomething()
    {
        Inner.DoSomething();
    }
}

// Generated plugin container (by the source generator)
public class MyClassPluginContainer
{
    public ChainedProxyBuilder<ISomething> SomethingPlugins { get; } = new ChainedProxyBuilder<ISomething>();
    // ... other ChainedProxyBuilders for other interfaces and base class
}

// Generated plugin wrapper class (by the source generator)
public partial class MyClassWrapper : MyClass
{
    private readonly MyClass _inner;
    private readonly ISomething _innerSomething;

    public MyClassWrapper(MyClass inner, MyClassPluginContainer pluginContainer)
    {
        _inner = inner;
        _innerSomething = pluginContainer.SomethingPlugins.Execute(inner);
        // ... apply other plugins from the container
    }

    public override void DoSomething()
    {
        _innerSomething.DoSomething();
    }

    public override void MyMethod()
    {
        _inner.MyMethod();
    }
}
Arlodotexe commented 11 months ago

Since this is basically just middleware for classes, I'll be calling this OwlCore.Classware.