Skip to content

Commit

Permalink
Merge pull request #1942 from glopesdev/issue-1915
Browse files Browse the repository at this point in the history
Allow building workflows as typed sequences
  • Loading branch information
glopesdev authored Aug 1, 2024
2 parents 2fd6474 + 9cd6591 commit 8f2d560
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 15 deletions.
30 changes: 30 additions & 0 deletions Bonsai.Core.Tests/ExpressionBuilderGraphTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,36 @@ public void Build_BranchPropertyMapping_AvoidMulticastExpression()
Assert.IsFalse(visitor.HasPublishBranch);
}

[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void BuildObservable_InvalidWorkflowType_ThrowsArgumentException()
{
new TestWorkflow()
.AppendValue(0)
.AppendOutput()
.BuildObservable<Unit>();
}

[TestMethod]
public void BuildObservable_CovariantWorkflowType_IsCompatibleAssignment()
{
var workflow = new TestWorkflow()
.AppendValue("")
.AppendOutput()
.BuildObservable<object>();
Assert.IsNotNull(workflow);
}

[TestMethod]
public void BuildObservable_ConvertibleWorkflowType_IsCompatibleAssignment()
{
var workflow = new TestWorkflow()
.AppendValue(1)
.AppendOutput()
.BuildObservable<double>();
Assert.IsNotNull(workflow);
}

class MergeBranchVisitor : ExpressionVisitor
{
public int BranchCount { get; private set; }
Expand Down
4 changes: 1 addition & 3 deletions Bonsai.Core.Tests/TestWorkflow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,7 @@ public ExpressionBuilderGraph ToInspectableGraph()

public IObservable<T> BuildObservable<T>()
{
var expression = Workflow.Build();
var observableFactory = Expression.Lambda<Func<IObservable<T>>>(expression).Compile();
return observableFactory();
return Workflow.BuildObservable<T>();
}
}
}
19 changes: 10 additions & 9 deletions Bonsai.Core/Expressions/ExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ static Expression[] ExpandParamArguments(ParameterInfo[] parameters, Expression[
var argument = arguments[k + offset];
if (argument.Type != arrayType)
{
argument = CoerceMethodArgument(arrayType, argument);
argument = ConvertExpression(argument, arrayType);
}
initializers[k] = argument;
}
Expand All @@ -489,22 +489,23 @@ static Expression[] ExpandParamArguments(ParameterInfo[] parameters, Expression[
return expandedArguments;
}

internal static Expression CoerceMethodArgument(Type parameterType, Expression argument)
internal static Expression ConvertExpression(Expression expression, Type parameterType)
{
if (argument.Type.IsGenericType && parameterType.IsGenericType &&
argument.Type.GetGenericTypeDefinition() == typeof(IObservable<>) &&
parameterType.GetGenericTypeDefinition() == typeof(IObservable<>))
if (expression.Type.IsGenericType && parameterType.IsGenericType &&
expression.Type.GetGenericTypeDefinition() == typeof(IObservable<>) &&
parameterType.GetGenericTypeDefinition() == typeof(IObservable<>) &&
!parameterType.IsAssignableFrom(expression.Type))
{
var argumentObservableType = argument.Type.GetGenericArguments()[0];
var argumentObservableType = expression.Type.GetGenericArguments()[0];
var parameterObservableType = parameterType.GetGenericArguments()[0];
var conversionParameter = Expression.Parameter(argumentObservableType);
var conversion = Expression.Convert(conversionParameter, parameterObservableType);
var select = selectMethod.MakeGenericMethod(argumentObservableType, parameterObservableType);
return Expression.Call(select, argument, Expression.Lambda(conversion, conversionParameter));
return Expression.Call(select, expression, Expression.Lambda(conversion, conversionParameter));
}
else
{
return Expression.Convert(argument, parameterType);
return Expression.Convert(expression, parameterType);
}
}

Expand All @@ -518,7 +519,7 @@ static Expression[] MatchMethodParameters(ParameterInfo[] parameters, Expression
if (argument.Type != parameterType)
{
isCoerced |= !parameterType.IsAssignableFrom(argument.Type);
argument = CoerceMethodArgument(parameterType, argument);
argument = ConvertExpression(argument, parameterType);
}
return argument;
});
Expand Down
25 changes: 25 additions & 0 deletions Bonsai.Core/Expressions/ExpressionBuilderGraphExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,31 @@ public static IObservable<Unit> BuildObservable(this ExpressionBuilderGraph sour
return observableFactory();
}

/// <summary>
/// Builds and compiles an expression builder workflow into an observable sequence
/// with the specified element type.
/// </summary>
/// <typeparam name="TResult">The type of the elements in the observable sequence.</typeparam>
/// <param name="source">The expression builder workflow to compile.</param>
/// <returns>
/// An observable sequence with the specified element type.
/// </returns>
/// <exception cref="ArgumentException">
/// The specified expression builder workflow does not compile into an observable sequence
/// with the expected element type.
/// </exception>
public static IObservable<TResult> BuildObservable<TResult>(this ExpressionBuilderGraph source)
{
var workflow = source.Build();
if (!typeof(IObservable<TResult>).IsAssignableFrom(workflow.Type))
{
workflow = ExpressionBuilder.ConvertExpression(workflow, typeof(IObservable<TResult>));
}

var observableFactory = Expression.Lambda<Func<IObservable<TResult>>>(workflow).Compile();
return observableFactory();
}

static WorkflowExpressionBuilder Clone(this WorkflowExpressionBuilder builder, ExpressionBuilderGraph workflow)
{
var workflowExpression = (WorkflowExpressionBuilder)Activator.CreateInstance(builder.GetType(), workflow);
Expand Down
2 changes: 1 addition & 1 deletion Bonsai.Core/Expressions/ExternalizedDateTimeOffset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public override Expression Build(IEnumerable<Expression> arguments)
var propertySourceType = typeof(IObservable<DateTimeOffset>);
if (source.Type != propertySourceType)
{
source = CoerceMethodArgument(propertySourceType, source);
source = ConvertExpression(source, propertySourceType);
}

return source;
Expand Down
2 changes: 1 addition & 1 deletion Bonsai.Core/Expressions/ExternalizedProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ public override Expression Build(IEnumerable<Expression> arguments)
var propertySourceType = typeof(IObservable<TValue>);
if (source.Type != propertySourceType)
{
source = CoerceMethodArgument(propertySourceType, source);
source = ConvertExpression(source, propertySourceType);
}

return source;
Expand Down
2 changes: 1 addition & 1 deletion Bonsai.Core/Expressions/ExternalizedTimeSpan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public override Expression Build(IEnumerable<Expression> arguments)
var propertySourceType = typeof(IObservable<TimeSpan>);
if (source.Type != propertySourceType)
{
source = CoerceMethodArgument(propertySourceType, source);
source = ConvertExpression(source, propertySourceType);
}

return source;
Expand Down

0 comments on commit 8f2d560

Please sign in to comment.