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 all 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
107 changes: 75 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,85 @@ 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 = null;
var workflow = new TestWorkflow()
.AppendUnit()
.AppendNested(
input => input
.AppendUnit()
.Capture(out target)
.AppendOutput(),
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;
var workflow = new TestWorkflow()
.AppendValue(0)
.AppendPropertyMapping(nameof(Reactive.Range.Count))
.AppendCombinator(new Reactive.Range())
.Capture(out target)
.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()
{
// related to https://github.com/bonsai-rx/bonsai/issues/1860
var workflow = new TestWorkflow()
.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()
{
// related to https://github.com/bonsai-rx/bonsai/issues/1860
var workflow = new TestWorkflow()
.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
101 changes: 101 additions & 0 deletions Bonsai.Core.Tests/TestWorkflow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using System;
using Bonsai.Dag;
using Bonsai.Expressions;

namespace Bonsai.Core.Tests
{
public readonly struct TestWorkflow
{
public TestWorkflow()
: this(new ExpressionBuilderGraph(), null)
{
}

private TestWorkflow(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 TestWorkflow ResetCursor()
{
if (Cursor != null)
return new TestWorkflow(Workflow, null);
else
return this;
}

public TestWorkflow Capture(out ExpressionBuilder builder)
{
builder = Cursor?.Value;
return this;
}

public TestWorkflow Append(ExpressionBuilder builder)
{
var node = Workflow.Add(builder);
if (Cursor != null)
Workflow.AddEdge(Cursor, node, new ExpressionBuilderArgument());
return new TestWorkflow(Workflow, node);
}

public TestWorkflow AppendCombinator<TCombinator>(TCombinator combinator) where TCombinator : new()
{
var combinatorBuilder = new CombinatorBuilder { Combinator = combinator };
return Append(combinatorBuilder);
}

public TestWorkflow AppendValue<TValue>(TValue value)
{
var workflowProperty = new WorkflowProperty<TValue> { Value = value };
return AppendCombinator(workflowProperty);
}

public TestWorkflow AppendUnit()
{
return Append(new UnitBuilder());
}

public TestWorkflow AppendInput(int index = 0)
{
return ResetCursor().Append(new WorkflowInputBuilder { Index = index });
}

public TestWorkflow AppendOutput()
{
return Append(new WorkflowOutputBuilder());
}

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

public TestWorkflow AppendNested<TWorkflowExpressionBuilder>(
Func<TestWorkflow, TestWorkflow> selector,
Func<ExpressionBuilderGraph, TWorkflowExpressionBuilder> constructor)
where TWorkflowExpressionBuilder : ExpressionBuilder, IWorkflowExpressionBuilder
{
var nestedWorkflow = new TestWorkflow();
if (Cursor != null)
nestedWorkflow = nestedWorkflow.AppendInput();
nestedWorkflow = selector(nestedWorkflow);
var workflowBuilder = constructor(nestedWorkflow.Workflow);
return Append(workflowBuilder);
}

public ExpressionBuilderGraph ToInspectableGraph()
{
return Workflow.ToInspectableGraph();
}
}
}
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);
}
}
}
2 changes: 1 addition & 1 deletion tooling/Common.csproj.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<!-- Common C# Properties -->
<LangVersion>9.0</LangVersion>
<LangVersion>10.0</LangVersion>
<Features>strict</Features>
<GenerateDocumentationFile>true</GenerateDocumentationFile>

Expand Down