Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Added basic expression building to all node types
  • Loading branch information
snakex64 committed Jun 27, 2024
commit 3353b6ff4c0bc63adce1e08d643ca39bb44cc6d7
4 changes: 2 additions & 2 deletions src/NodeDev.Blazor/NodeDev.Blazor.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.3" />
<PackageReference Include="MudBlazor" Version="6.19.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.6" />
<PackageReference Include="MudBlazor" Version="6.20.0" />
</ItemGroup>

<ItemGroup>
Expand Down
12 changes: 12 additions & 0 deletions src/NodeDev.Core/Class/NodeClassMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ public IEnumerable<IMethodParameterInfo> GetParameters()
return Parameters;
}

public MethodInfo CreateMethodInfo()
{
var classType = Class.ClassTypeBase.MakeRealType();

var method = classType.GetMethod(Name, GetParameters().Select(x => x.ParameterType.MakeRealType()).ToArray());

if(method == null)
throw new Exception("Unable to find method: " + Name);

return method;
}

#region Serialization

private SerializedNodeClassMethod? SavedDataDuringDeserializationStep1 { get; set; }
Expand Down
2 changes: 2 additions & 0 deletions src/NodeDev.Core/Class/NodeClassProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public NodeClassProperty(NodeClass ownerClass, string name, TypeBase propertyTyp

public bool CanSet => true;

public bool IsField => false;

#region UI Actions

public void Rename(string newName)
Expand Down
8 changes: 7 additions & 1 deletion src/NodeDev.Core/Class/NodeClassTypeCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ public Assembly CreateProjectClassesAndAssembly(Project project)
ILGenerator ctor0IL = ctor0.GetILGenerator();
ctor0IL.Emit(OpCodes.Ret);

foreach(var method in nodeClass.Methods)
{
// create the method

}

foreach (var property in nodeClass.Properties)
{

Expand Down Expand Up @@ -91,7 +97,7 @@ public Assembly CreateProjectClassesAndAssembly(Project project)
$"set_{property.Name}",
getSetAttr,
null,
new Type[] { propertyType });
[propertyType]);

var numberSetIL = mbNumberSetAccessor.GetILGenerator();
// Load the instance and then the numeric argument, then store the
Expand Down
75 changes: 74 additions & 1 deletion src/NodeDev.Core/Graph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using NodeDev.Core.Nodes;
using NodeDev.Core.Nodes.Flow;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;

Expand All @@ -22,7 +23,7 @@ static Graph()
NodeProvider.Initialize();
}

#region Compile
#region GetChunks

public class BadMergeException(Connection input) : Exception($"Error merging path to {input.Name} of tool {input.Parent.Name}") { }
public class DeadEndNotAllowed(List<Connection> inputs) : Exception(inputs.Count == 1 ? $"Dead end not allowed in {inputs[0].Name} of tool {inputs[0].Parent.Name}" : $"Dead end not allowed in {inputs.Count} tools") { }
Expand Down Expand Up @@ -92,7 +93,12 @@ internal NodePathChunks GetChunks(Connection execOutput, bool allowDeadEnd)

var currentInput = execOutput.Connections.FirstOrDefault();
if (currentInput == null)
{
if (!allowDeadEnd)
throw new DeadEndNotAllowed([]);

return new NodePathChunks(execOutput, chunks, null, null); // the path led nowhere
}

while (true)
{
Expand Down Expand Up @@ -225,6 +231,73 @@ private Dictionary<Connection, NodePathChunks> GetChunks(Connection input, Node

#endregion

#region BuildExpression

public Expression BuildExpression(BuildExpressionOptions options)
{
var entry = (Nodes.Values.FirstOrDefault(x => x is EntryNode)?.Outputs.FirstOrDefault()) ?? throw new Exception($"No entry node found in graph {SelfMethod.Name}");
var returnLabelTarget = SelfMethod.ReturnType == Project.TypeFactory.Void ? Expression.Label("ReturnLabel") : Expression.Label(SelfMethod.ReturnType.MakeRealType(), "ReturnLabel");

var info = new BuildExpressionInfo(returnLabelTarget, options, SelfMethod.IsStatic ? null : Expression.Parameter(SelfClass.ClassTypeBase.MakeRealType(), "this"));

// Create a variable for each output parameter
foreach (var parameter in SelfMethod.Parameters)
{
if (parameter.ParameterType.IsExec)
continue;

var type = parameter.ParameterType.MakeRealType();
var variable = Expression.Parameter(type, parameter.Name);
info.MethodParametersExpression[parameter.Name] = variable;
}

// Create a variable for each node input and output
foreach (var node in Nodes.Values)
{
// normal nodes each have their own local variable for every input and output
foreach ((var connection, var variable) in node.CreateLocalVariableExpressionsForEachInputOutput())
info.LocalVariables[connection] = variable;
}

var chunks = GetChunks(entry, false);

var expressions = BuildExpression(chunks, info);

var expressionBlock = Expression.Block(expressions.Append(Expression.Label(returnLabelTarget)));

return expressionBlock;
}

internal static Expression[] BuildExpression(NodePathChunks chunks, BuildExpressionInfo info)
{
var expressions = new Expression[chunks.Chunks.Count];

for (int i = 0; i < chunks.Chunks.Count; ++i)
{
var chunk = chunks.Chunks[i];

if (chunk.Output != null)
{
expressions[i] = chunk.Output.Parent.BuildExpression(null, info);
}
else if (chunk.SubChunk != null)
{
// Each sub chunk has the key of the output connection of that node
// Therefor, the parent of that output connection is the node itself that we're trying to build
// such as the "Branch" node with 2 outputs. There will be 2 sub chunks
var node = chunk.SubChunk.First().Key.Parent;

expressions[i] = node.BuildExpression(chunk.SubChunk, info);
}
else
throw new Exception("Invalid chunk data, either Output or SubChunk must be set");
}

return expressions;
}

#endregion

#region PreprocessGraph

public int NbConnections { get; private set; }
Expand Down
2 changes: 1 addition & 1 deletion src/NodeDev.Core/NodeDev.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Reactive" Version="6.0.0" />
<PackageReference Include="System.Reactive" Version="6.0.1" />
</ItemGroup>

</Project>
28 changes: 28 additions & 0 deletions src/NodeDev.Core/Nodes/BuildExpressionInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using NodeDev.Core.Connections;
using NodeDev.Core.Types;
using System.Linq.Expressions;

namespace NodeDev.Core.Nodes;

internal class BuildExpressionInfo
{
public BuildExpressionInfo(LabelTarget returnLabel, BuildExpressionOptions buildExpressionOptions, ParameterExpression? thisExpression)
{
ReturnLabel = returnLabel;
BuildExpressionOptions = buildExpressionOptions;
ThisExpression = thisExpression;
}

public Dictionary<string, Expression> MethodParametersExpression { get; } = [];

public Dictionary<Connection, Expression> LocalVariables { get; } = [];

public LabelTarget ReturnLabel { get; }

public BuildExpressionOptions BuildExpressionOptions { get; }

/// <summary>
/// Represent 'this', if the method being built is not static
/// </summary>
public ParameterExpression? ThisExpression { get; }
}
6 changes: 6 additions & 0 deletions src/NodeDev.Core/Nodes/BuildExpressionOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace NodeDev.Core.Nodes;

public class BuildExpressionOptions
{
public bool AddDebugInfo { get; set; } = false;
}
46 changes: 27 additions & 19 deletions src/NodeDev.Core/Nodes/Debug/WriteLine.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
using NodeDev.Core.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NodeDev.Core.Connections;
using NodeDev.Core.Types;
using System.Linq.Expressions;

namespace NodeDev.Core.Nodes.Debug
namespace NodeDev.Core.Nodes.Debug;

public class WriteLine : NormalFlowNode
{
public class WriteLine : NormalFlowNode
{
public WriteLine(Graph graph, string? id = null) : base(graph, id)
{
Name = "WriteLine";
public WriteLine(Graph graph, string? id = null) : base(graph, id)
{
Name = "WriteLine";

Inputs.Add(new("Line", this, new UndefinedGenericType("T")));
}

internal override Expression BuildExpression(Dictionary<Connection, Graph.NodePathChunks>? subChunks, BuildExpressionInfo info)
{
if (subChunks != null)
throw new Exception("WriteLine node should not have subchunks");

var method = typeof(Console).GetMethod(nameof(Console.WriteLine), System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static);
if (method == null)
throw new Exception("Unable to find Console.WriteLine method");

Inputs.Add(new("Line", this, new UndefinedGenericType("T")));
}
return Expression.Call(null, method, info.LocalVariables[Inputs[1]]);
}

protected override void ExecuteInternal(GraphExecutor executor, object? self, Span<object?> inputs, Span<object?> outputs, ref object? state)
{
Console.WriteLine(inputs[1]);
}
}
protected override void ExecuteInternal(GraphExecutor executor, object? self, Span<object?> inputs, Span<object?> outputs, ref object? state)
{
Console.WriteLine(inputs[1]);
}
}
67 changes: 37 additions & 30 deletions src/NodeDev.Core/Nodes/Flow/Branch.cs
Original file line number Diff line number Diff line change
@@ -1,45 +1,52 @@
using NodeDev.Core.Connections;
using NodeDev.Core.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Linq.Expressions;

namespace NodeDev.Core.Nodes.Flow
namespace NodeDev.Core.Nodes.Flow;

public class Branch : FlowNode
{
public class Branch : FlowNode
public override bool IsFlowNode => true;

public Branch(Graph graph, string? id = null) : base(graph, id)
{
public override bool IsFlowNode => true;
Name = "Branch";

public Branch(Graph graph, string? id = null) : base(graph, id)
{
Name = "Branch";
Inputs.Add(new("Exec", this, TypeFactory.ExecType));
Inputs.Add(new("Condition", this, TypeFactory.Get<bool>()));

Inputs.Add(new("Exec", this, TypeFactory.ExecType));
Inputs.Add(new("Condition", this, TypeFactory.Get<bool>()));
Outputs.Add(new("IfTrue", this, TypeFactory.ExecType));
Outputs.Add(new("IfFalse", this, TypeFactory.ExecType));
}

Outputs.Add(new("IfTrue", this, TypeFactory.ExecType));
Outputs.Add(new("IfFalse", this, TypeFactory.ExecType));
}
public override string GetExecOutputPathId(string pathId, Connection execOutput)
{
return pathId + "-" + execOutput.Id; // every path is unique
}

public override string GetExecOutputPathId(string pathId, Connection execOutput)
{
return pathId + "-" + execOutput.Id; // every path is unique
}
public override bool DoesOutputPathAllowDeadEnd(Connection execOutput) => false;

public override bool DoesOutputPathAllowDeadEnd(Connection execOutput) => false;
public override bool DoesOutputPathAllowMerge(Connection execOutput) => true;

public override bool DoesOutputPathAllowMerge(Connection execOutput) => true;
internal override Expression BuildExpression(Dictionary<Connection, Graph.NodePathChunks>? subChunks, BuildExpressionInfo info)
{
ArgumentNullException.ThrowIfNull(subChunks);

public override Connection? Execute(GraphExecutor executor, object? self, Connection? connectionBeingExecuted, Span<object?> inputs, Span<object?> nodeOutputs, ref object? state, out bool alterExecutionStackOnPop)
{
alterExecutionStackOnPop = false;
var ifTrue = Graph.BuildExpression(subChunks[Outputs[0]], info);
var ifFalse = Graph.BuildExpression(subChunks[Outputs[1]], info);

var ifThenElse = Expression.IfThenElse(info.LocalVariables[Inputs[1]], Expression.Block(ifTrue), Expression.Block(ifFalse));

return ifThenElse;
}

public override Connection? Execute(GraphExecutor executor, object? self, Connection? connectionBeingExecuted, Span<object?> inputs, Span<object?> nodeOutputs, ref object? state, out bool alterExecutionStackOnPop)
{
alterExecutionStackOnPop = false;

if (inputs[1] is bool b && b == true)
return Outputs[0];
else
return Outputs[1];
}
if (inputs[1] is bool b && b == true)
return Outputs[0];
else
return Outputs[1];
}
}
Loading