Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure visualizer operator override #1889

Merged
merged 7 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 70 additions & 32 deletions Bonsai.Core.Tests/InspectBuilderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Bonsai.Core.Tests
[TestClass]
public class InspectBuilderTests
{
void RunInspector(params ExpressionBuilder[] builders)
static void RunInspector(params ExpressionBuilder[] builders)
{
var workflowBuilder = new WorkflowBuilder();
var previous = default(Node<ExpressionBuilder, ExpressionBuilderArgument>);
Expand Down Expand Up @@ -83,42 +83,80 @@ public void Build_CombinatorTransformError_ThrowsRuntimeException()
[TestMethod]
public void Build_GroupInspectBuilder_ReturnNestedVisualizerElement()
{
var workflowBuilder = new WorkflowBuilder();
var source = workflowBuilder.Workflow.Add(new UnitBuilder());
var group = workflowBuilder.Workflow.Add(new GroupWorkflowBuilder());
var groupBuilder = (GroupWorkflowBuilder)group.Value;
workflowBuilder.Workflow.AddEdge(source, group, new ExpressionBuilderArgument());

var input = groupBuilder.Workflow.Add(new WorkflowInputBuilder());
var combinator = groupBuilder.Workflow.Add(new UnitBuilder());
var output = groupBuilder.Workflow.Add(new WorkflowOutputBuilder());
groupBuilder.Workflow.AddEdge(input, combinator, new ExpressionBuilderArgument());
groupBuilder.Workflow.AddEdge(combinator, output, new ExpressionBuilderArgument());

var inspectable = workflowBuilder.Workflow.ToInspectableGraph();
var inspectGroup = (InspectBuilder)inspectable.ElementAt(1).Value;
var result = inspectable.Build();
var visualizerElement = ExpressionBuilder.GetVisualizerElement(inspectGroup);
Assert.AreEqual(combinator.Value, visualizerElement.Builder);
ExpressionBuilder target = default;
glopesdev marked this conversation as resolved.
Show resolved Hide resolved
var workflow = Workflow
.New()
.AppendUnit()
.AppendNested(
input => input.AppendUnit().Do(builder => target = builder).AppendOutput(),
glopesdev marked this conversation as resolved.
Show resolved Hide resolved
workflow => new GroupWorkflowBuilder(workflow))
.ToInspectableGraph();
workflow.Build();

var output = workflow[workflow.Count - 1].Value;
var visualizerElement = ExpressionBuilder.GetVisualizerElement(output);
Assert.AreSame(target, visualizerElement.Builder);
}

[TestMethod]
public void Build_PropertyMappedInspectBuilderToWorkflowOutput_ReturnVisualizerElement()
{
var workflowBuilder = new WorkflowBuilder();
var source = workflowBuilder.Workflow.Add(new CombinatorBuilder { Combinator = new Reactive.Range() });
var valueSource = workflowBuilder.Workflow.Add(new CombinatorBuilder { Combinator = new IntProperty() });
var propertyMapping = workflowBuilder.Workflow.Add(new PropertyMappingBuilder { PropertyMappings = { new PropertyMapping { Name = "Count" } } });
var output = workflowBuilder.Workflow.Add(new WorkflowOutputBuilder());
workflowBuilder.Workflow.AddEdge(valueSource, propertyMapping, new ExpressionBuilderArgument());
workflowBuilder.Workflow.AddEdge(propertyMapping, source, new ExpressionBuilderArgument());
workflowBuilder.Workflow.AddEdge(source, output, new ExpressionBuilderArgument());

var inspectable = workflowBuilder.Workflow.ToInspectableGraph();
var inspectOutput = (InspectBuilder)inspectable.ElementAt(workflowBuilder.Workflow.Count - 1).Value;
inspectable.Build();
var visualizerElement = ExpressionBuilder.GetVisualizerElement(inspectOutput);
Assert.AreEqual(source.Value, visualizerElement.Builder);
ExpressionBuilder target = default;
var workflow = Workflow
.New()
.AppendValue(0)
.AppendPropertyMapping(nameof(Reactive.Range.Count))
.AppendCombinator(new Reactive.Range())
.Do(builder => target = builder)
glopesdev marked this conversation as resolved.
Show resolved Hide resolved
.AppendOutput()
.ToInspectableGraph();
workflow.Build();

var output = workflow[workflow.Count - 1].Value;
var visualizerElement = ExpressionBuilder.GetVisualizerElement(output);
Assert.AreSame(target, visualizerElement.Builder);
}

[TestMethod]
glopesdev marked this conversation as resolved.
Show resolved Hide resolved
public void Build_SinkInspectBuilder_ReturnSourceVisualizerElement()
{
var workflow = Workflow
.New()
.AppendValue(1)
.AppendNested(
input => input.AppendValue(string.Empty).AppendOutput(),
workflow => new Reactive.Sink(workflow))
.AppendOutput()
.ToInspectableGraph();
workflow.Build();

var sourceVisualizer = ExpressionBuilder.GetVisualizerElement(workflow[0].Value);
var outputVisualizer = ExpressionBuilder.GetVisualizerElement(workflow[workflow.Count - 1].Value);
Assert.AreSame(sourceVisualizer, outputVisualizer);
}

[TestMethod]
public void Build_VisualizerInspectBuilder_ReplaceSourceVisualizerElement()
{
var workflow = Workflow
.New()
.AppendValue(1)
.AppendNested(
input => input.AppendValue(string.Empty).AppendOutput(),
workflow => new Reactive.Visualizer(workflow))
.AppendOutput()
.ToInspectableGraph();
workflow.Build();

var sourceBuilder = (InspectBuilder)workflow[0].Value;
var outputBuilder = (InspectBuilder)workflow[workflow.Count - 1].Value;
Assert.AreEqual(typeof(int), sourceBuilder.ObservableType);
Assert.AreEqual(sourceBuilder.ObservableType, outputBuilder.ObservableType);

var sourceVisualizer = ExpressionBuilder.GetVisualizerElement(sourceBuilder);
var outputVisualizer = ExpressionBuilder.GetVisualizerElement(outputBuilder);
Assert.AreNotSame(sourceVisualizer, outputVisualizer);
Assert.AreEqual(typeof(string), outputVisualizer.ObservableType);
}

#region Error Classes
Expand Down
130 changes: 130 additions & 0 deletions Bonsai.Core.Tests/Workflow.cs
glopesdev marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
using System;
using Bonsai.Dag;
using Bonsai.Expressions;

namespace Bonsai.Core.Tests
{
static class Workflow
glopesdev marked this conversation as resolved.
Show resolved Hide resolved
glopesdev marked this conversation as resolved.
Show resolved Hide resolved
{
public static WorkflowCursor New()
glopesdev marked this conversation as resolved.
Show resolved Hide resolved
{
var workflow = new ExpressionBuilderGraph();
return FromGraph(workflow);
}

public static WorkflowCursor FromGraph(ExpressionBuilderGraph workflow)
{
return new WorkflowCursor(workflow, null);
}

public static WorkflowCursor FromInspectableGraph(ExpressionBuilderGraph workflow)
glopesdev marked this conversation as resolved.
Show resolved Hide resolved
{
return FromGraph(workflow.FromInspectableGraph());
}

public static WorkflowCursor ResetCursor(this WorkflowCursor workflowCursor)
{
var (workflow, cursor) = workflowCursor;
if (cursor != null)
return new WorkflowCursor(workflow, null);
else
return workflowCursor;
}

public static WorkflowCursor Do(this WorkflowCursor workflowCursor, Action<ExpressionBuilder> action)
{
action(workflowCursor.Cursor?.Value);
return workflowCursor;
}

public static WorkflowCursor Append(this WorkflowCursor workflowCursor, ExpressionBuilder builder)
{
var (workflow, cursor) = workflowCursor;
var node = workflow.Add(builder);
if (cursor != null)
workflow.AddEdge(cursor, node, new ExpressionBuilderArgument());
return new WorkflowCursor(workflow, node);
}

public static WorkflowCursor AppendCombinator<TCombinator>(this WorkflowCursor workflowCursor, TCombinator combinator)
{
var combinatorBuilder = new CombinatorBuilder { Combinator = combinator };
return workflowCursor.Append(combinatorBuilder);
}

public static WorkflowCursor AppendValue<TValue>(this WorkflowCursor workflowCursor, TValue value)
{
var workflowProperty = new WorkflowProperty<TValue> { Value = value };
return workflowCursor.AppendCombinator(workflowProperty);
}

public static WorkflowCursor AppendUnit(this WorkflowCursor workflowCursor)
{
return workflowCursor.Append(new UnitBuilder());
}

public static WorkflowCursor AppendInput(this WorkflowCursor workflowCursor, int index = 0)
{
return workflowCursor.ResetCursor().Append(new WorkflowInputBuilder { Index = index });
}

public static WorkflowCursor AppendOutput(this WorkflowCursor workflowCursor)
{
return workflowCursor.Append(new WorkflowOutputBuilder());
}

public static WorkflowCursor AppendPropertyMapping(this WorkflowCursor workflowCursor, params string[] propertyNames)
{
var mappingBuilder = new PropertyMappingBuilder();
foreach (var name in propertyNames)
{
mappingBuilder.PropertyMappings.Add(new PropertyMapping { Name = name });
}
return workflowCursor.Append(mappingBuilder);
}

public static WorkflowCursor AppendNested<TWorkflowExpressionBuilder>(
this WorkflowCursor workflowCursor,
Func<WorkflowCursor, WorkflowCursor> selector,
Func<ExpressionBuilderGraph, TWorkflowExpressionBuilder> constructor)
where TWorkflowExpressionBuilder : ExpressionBuilder, IWorkflowExpressionBuilder
{
var (_, cursor) = workflowCursor;
var nestedCursor = New();
if (cursor != null)
nestedCursor = nestedCursor.AppendInput();
nestedCursor = selector(nestedCursor);
var workflowBuilder = constructor(nestedCursor.Workflow);
return workflowCursor.Append(workflowBuilder);
}

public static ExpressionBuilderGraph ToGraph(this WorkflowCursor workflowCursor)
{
return workflowCursor.Workflow;
}

public static ExpressionBuilderGraph ToInspectableGraph(this WorkflowCursor workflowCursor)
{
return workflowCursor.Workflow.ToInspectableGraph();
}
}

class WorkflowCursor
glopesdev marked this conversation as resolved.
Show resolved Hide resolved
{
public WorkflowCursor(ExpressionBuilderGraph workflow, Node<ExpressionBuilder, ExpressionBuilderArgument> cursor)
{
Workflow = workflow ?? throw new ArgumentNullException(nameof(workflow));
Cursor = cursor;
}

public ExpressionBuilderGraph Workflow { get; }

public Node<ExpressionBuilder, ExpressionBuilderArgument> Cursor { get; }

public void Deconstruct(out ExpressionBuilderGraph workflow, out Node<ExpressionBuilder, ExpressionBuilderArgument> cursor)
glopesdev marked this conversation as resolved.
Show resolved Hide resolved
{
workflow = Workflow;
cursor = Cursor;
}
}
}
11 changes: 8 additions & 3 deletions Bonsai.Core/Reactive/Sink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.ComponentModel;
using System.Xml.Serialization;
using Bonsai.Expressions;
using System.Reflection;

namespace Bonsai.Reactive
{
Expand Down Expand Up @@ -62,13 +63,17 @@ public override Expression Build(IEnumerable<Expression> arguments)
var selector = Expression.Lambda(selectorBody, selectorParameter);
var selectorObservableType = selector.ReturnType.GetGenericArguments()[0];
return Expression.Call(
typeof(Sink), nameof(Process),
new [] { source.Type.GetGenericArguments()[0], selectorObservableType },
GetProcessMethod(source.Type.GetGenericArguments()[0], selectorObservableType),
source, selector);
});
}

static IObservable<TSource> Process<TSource, TSink>(IObservable<TSource> source, Func<IObservable<TSource>, IObservable<TSink>> sink)
internal virtual MethodInfo GetProcessMethod(params Type[] typeArguments)
{
return typeof(Sink).GetMethod(nameof(Process), BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(typeArguments);
}

internal static IObservable<TSource> Process<TSource, TSink>(IObservable<TSource> source, Func<IObservable<TSource>, IObservable<TSink>> sink)
{
return source.Publish(ps => MergeDependencies(ps, sink(ps).IgnoreElements().Select(xs => default(TSource))));
}
Expand Down
14 changes: 13 additions & 1 deletion Bonsai.Core/Reactive/Visualizer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.ComponentModel;
using System;
using System.ComponentModel;
using System.Reflection;
using System.Xml.Serialization;
using Bonsai.Expressions;

Expand Down Expand Up @@ -33,5 +35,15 @@ public Visualizer(ExpressionBuilderGraph workflow)
: base(workflow)
{
}

internal override MethodInfo GetProcessMethod(params Type[] typeArguments)
{
return typeof(Visualizer).GetMethod(nameof(Process), BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(typeArguments);
}

static new IObservable<TSource> Process<TSource, TSink>(IObservable<TSource> source, Func<IObservable<TSource>, IObservable<TSink>> sink)
{
return Sink.Process(source, sink);
}
}
}