/// <summary>
        /// Walks graph starting from nodes without inputs and then follows connections from them.
        /// Calls <paramref name="nodeAction"/> on every node and <paramref name="connectionAction"/> on every connection.
        /// <paramref name="nodeAction"/> must set values to all output ports of a node.
        /// </summary>
        /// <param name="graph">Graph to execute</param>
        /// <param name="nodeAction">Action to perform on each node</param>
        /// <param name="connectionAction">Action to perform on each connection</param>
        /// <returns>Execution result</returns>
        /// <exception cref="ArgumentNullException">When <paramref name="graph"/> is null</exception>
        /// <exception cref="ArgumentNullException">When <paramref name="nodeAction"/> is null</exception>
        public static IGraphExecutionResult Execute(this GraphModel graph, Action <INodeExecutionContext> nodeAction, Action <IConnectionExecutionContext> connectionAction = null)
        {
            if (graph == null)
            {
                throw new ArgumentNullException(nameof(graph));
            }

            if (nodeAction == null)
            {
                throw new ArgumentNullException(nameof(nodeAction));
            }

            var ctx           = new GraphExecutionContext();
            var executionList = GetExecutionList(graph);

            for (int i = 0, k = executionList.Count; i < k; ++i)
            {
                try
                {
                    switch (executionList[i])
                    {
                    case NodeModel node:
                    {
                        ctx.Node = node;
                        nodeAction.Invoke(ctx);
                        ctx.ValidateOutputs();
                        break;
                    }

                    case ConnectionModel connection:
                    {
                        ctx.Connection = connection;
                        connectionAction?.Invoke(ctx);
                        break;
                    }
                    }
                }
                catch (Exception e)
                {
                    ctx.AddException(e);
                }
            }

            ctx.Node       = null;
            ctx.Connection = null;
            return(ctx);
        }
        private static async Task <IGraphExecutionResult> ExecuteAsyncInner(GraphModel graph, Func <INodeExecutionContext, Task> nodeAction, Func <IConnectionExecutionContext, Task> connectionAction)
        {
            var ctx           = new GraphExecutionContext();
            var executionList = GetExecutionList(graph);

            for (int i = 0, k = executionList.Count; i < k; ++i)
            {
                try
                {
                    switch (executionList[i])
                    {
                    case NodeModel node:
                    {
                        ctx.Node = node;
                        await nodeAction.Invoke(ctx);

                        ctx.ValidateOutputs();
                        break;
                    }

                    case ConnectionModel connection:
                    {
                        ctx.Connection = connection;
                        await connectionAction?.Invoke(ctx);

                        break;
                    }
                    }
                }
                catch (Exception e)
                {
                    ctx.AddException(e);
                }
            }

            ctx.Node       = null;
            ctx.Connection = null;
            return(ctx);
        }