Implementing ICustomRegion in c#

I’m trying to implement ICustomRegion in C#, i want it that way so i can use it as a context provider to inject services in processes also written in C#. However there is something I can’t really understand:

using VL.Core;
using VL.Core.Import;
using VL.Core.PublicAPI;
using VL.Lib.Collections;

namespace VL.SharpRegion
{
    [ProcessNode(
        Name = "SharpRegion",
        HasStateOutput = true,
        FragmentSelection = FragmentSelection.Explicit
    )]
    public class SharpRegion : ICustomRegion
    {
        // Test something i want to provide
        public record SharpRegionRecord(int Test);

        private ICustomRegionPatch? _currentPatch;
        private SharpRegionRecord _sharedRecord;

        // ICustomRegion implementation
        public Spread<BorderControlPointDescription> Inputs { get; private set; }
        public Spread<BorderControlPointDescription> Outputs { get; private set; }
        public Spread<IncomingLinkDescription> IncomingLinks { get; private set; }
        public Spread<object> InputValues { get; private set; }
        public IReadOnlyList<object> OutputValues { private get; set; }
        public Spread<object> IncomingLinkValues { get; private set; }
        public bool PatchHasChanged { get; private set; }

        [Fragment]
        public SharpRegion()
        {
            _sharedRecord = new SharpRegionRecord(42);

            // Define no border control points initially
            Inputs = Spread<BorderControlPointDescription>.Empty;
            Outputs = Spread<BorderControlPointDescription>.Empty;
            IncomingLinks = Spread<IncomingLinkDescription>.Empty;
            InputValues = Spread<object>.Empty;
            IncomingLinkValues = Spread<object>.Empty;
        }

        [Fragment]
        public void Update(ICustomRegion input) { 
             // not sure what to do here
        }

        public ICustomRegionPatch CreateRegionPatch(
            NodeContext context,
            IReadOnlyList<object> initialInputs,
            out Spread<object> initialOutputs
        )
        {
            // Create the user's patch and inject the record
            initialOutputs = Spread<object>.Empty;

            // What to do here ???
            return new SharpRegionPatch(_sharedRecord);
        }

        // Question: How to get actual patch in here?
        private class SharpRegionPatch : ICustomRegionPatch
        {
            private readonly SharpRegionRecord _record;

            public SharpRegionPatch(SharpRegionRecord record)
            {
                _record = record;
            }

            public ICustomRegionPatch Update(
                IReadOnlyList<object> inputs,
                out Spread<object> outputs,
                IReadOnlyList<object> incomingLinks
            )
            {
                // How to expose _record to nodes inside?
                outputs = Spread<object>.Empty;
                return this;
            }
        }
    }
}

That’s about i can get:

and that’s a repo

getting somewhere

Seems there we go, minimum functionality

Now the question is how to go if I want a Custom Input, or Output in the region:

With 7.0 we introduced IRegiony<TInlay>, please have a look at the docs.

2 Likes

Sorry @Elias I don’t understand what I have to do to get:

I see I can connect splicer, accumulator, bcp, but nor input, output (link i presume):

Code
    [ProcessNode(HasStateOutput = true)]
    [Region(
        SupportedBorderControlPoints = ControlPointType.Splicer
            | ControlPointType.Accumulator
            | ControlPointType.Border
    )]
  public class InlayRegion : IRegion<InlayRegion.IInlay>
  {
      private readonly Dictionary<InputDescription, object?> _inputs = new() { };
        public InlayRegion()
        {
            // Let's try different combinations to see what works for "inner outputs"
            _inputs = new Dictionary<InputDescription, object?>()
            {
                // Option 1: Mark as Link (most likely candidate)
                // A link that originates from inside and flows to the user patch
                {
                    new InputDescription(
                        Id: "ContextString",
                        OuterType: typeof(string),
                        InnerType: typeof(string),
                        Name: "Context String",
                        IsLink: true, // <-- This indicates it's a link, not a control point
                        IsSplicer: false,
                        AccumulatorId: null
                    ),
                    _contextString
                },
                // Option 2: Similar for float
                {
                    new InputDescription(
                        Id: "ContextFloat",
                        OuterType: typeof(float),
                        InnerType: typeof(float),
                        Name: "Context Float",
                        IsLink: false, // <-- Does not differ
                        IsSplicer: false,
                        AccumulatorId: null
                    ),
                    _contextFloat
                },
                // Option 3: Try with different Id vs Name
                {
                    new InputDescription(
                        Id: "ctx_time",
                        OuterType: typeof(string),
                        InnerType: typeof(string),
                        Name: "Current Time",
                        IsLink: true
                    ),
                    DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss")
                },
                // Option 4: Try with same OuterType and InnerType (non-splicer)
                {
                    new InputDescription(
                        Id: "ctx_counter",
                        OuterType: typeof(int),
                        InnerType: typeof(int),
                        Name: "Counter",
                        IsLink: true
                    ),
                    0
                },
                // Option 5: Try with a splicer (different inner/outer types)
                // Maybe context provides a collection but inside sees individual items?
                {
                    new InputDescription(
                        Id: "ctx_splicer",
                        OuterType: typeof(IEnumerable<float>),
                        InnerType: typeof(float),
                        Name: "Splicer Example",
                        IsLink: true,
                        IsSplicer: true
                    ),
                    0.0f
                },
            };
        }

Finally:

  public interface IInlay
    {
        // Parameters here become available as links inside the region!
        void Execute(string contextString, float contextFloat);
    }

This prolly needs some better docs or better to add this as example

Just added ForEach (Key) as an example as well. That one has an input in the patch inlay.

1 Like

And a final pice of puzzle, with scope provider:

1 Like

For anyone who might be interested:

CustomRegionNewAPI2025.vl (38.9 KB)

1 Like