vvvv / VL-Language

The official repo for the design of the VL programming language
31 stars 0 forks source link

[Proposal] Custom region API using attributes #53

Open azeno opened 2 years ago

azeno commented 2 years ago

The proposal addresses quest #51. It's similar to #52 but instead of a DSL it uses an attribute one would add to the input pin taking the ICustomRegion.

  public class RegionAttribute : Attribute
  {
      public bool ShowAsNode { get; set; } // This flag is already in the UI

      public Type Accumulator { get; set; }

      public (Type inner, Type outer) InputSplicer { get; set; }

      public (Type inner, Type outer) OutputSplicer { get; set; }
  }

By predefining a set of type parameter marker classes (class T : Marker, class T1 : Marker, ...) we can define the type constraints of the inner and outer parts of a splicer, for example:

Region(
  Accumulator = typeof(T),
  InputSplicer = (inner: typeof(T), outer: typeof(IObservable<T>)),
  OutputSplicer = (inner: typeof(T), outer: typeof(IObservable<T>)))

or the GPU loop example (no splicers)

Region(Accumulator = typeof(GpuValue<T>))

The availble in- and outputs inside the region (as well as what moments would be available) could be modelled through an interface added by the region designer and implemented by the system.

If we'd define the ICustomRegion interface as

  public interface ICustomRegion<TUserPatch> where TUserPatch : ICustomRegionPatch
  {
      TUserPatch CreateRegionPatch(NodeContext Context, IReadOnlyList<object> initialInputs, out Spread<object> initialOutputs);
...
  }

  public interface ICustomRegionPatch
  {
      IReadOnlyList<object> Inputs { set; }
      IReadOnlyList<object> IncomingLinks { set; }
      Spread<object> Outputs { get; }
  }

The region designer could annotate the pin with say ICustomRegion<ITryCatchUserPatch> where ITryCatchUserPatch is defined by the designer with say

    // Will get implemented by the compiler
    interface ITryCatchUserPatch : ICustomRegionPatch
    {
        void Try();
        void Catch(Exception exception);
    }

extactly defining how the inner part of a region looks like.

Another example for a GPU loop could look like this

    // Will get implemented by the compiler
    interface IGpuLoopPatch : ICustomRegionPatch
    {
        void Update(GpuValue<int> iteration);
    }
gregsn commented 2 years ago

Wow! Nice! It looks to me that we should break up the discussion into 2 parts:

Attributes

I see this as an alternative to the DSL approach. Or in other words: The Multi-moment idea should also be "compatible" with the DSL idea (the Input Index statement however wouldn't be necessary in that case).

Detail question: Where would the marker classes need to be declared so that I can use them in my Attribute?

Multi-moments

This is a very strong concept! Being able to define TryCatch or IfElse or even an enum Switch would be awesome.

Output BCPs

I guess the region designer would like to define which moments are allowed to write into the BCPs. In both IfElse and TryCatch it would be great to allow links in every moment into the output BCPs.

However, it may be that the region works in a way that writing into the BCPs doesn't make sense as they get overwritten by another moment invocation. E.g. in the most trivial Do region, it doesn't make sense to write into a BCP on Create and on Update, as the value is written on Update will always win. Maybe there are more examples. And maybe it depends on the moment? Do we have to reason about cases where the region designer might want to allow the user to write certain BCPs in one moment and others in another? This might not affect the API, but just the internals of the compiler, which would only instruct certain values to be written back when implementing the interface defined by the user.

azeno commented 2 years ago

They would be declared at a rather central spot - VL.Core.Markers? (for reference, saw that trick the first time here)