Open, extensible, small and simple behaviour-graph execution engine

Overview

Behave-Graph

GitHub license npm version

Behave-Graph is a standalone library that implements the concept of "behavior graphs" as a portable TypeScript library with no external run-time dependencies. Behavior graphs are expressive, deterministic, and extensible state machines that can encode arbitrarily complex behavior.

Behavior graphs are used extensively in game development as a visual scripting language. For example, look at Unreal Engine Blueprints or Unity's Visual Scripting or NVIDIA Omniverse's OmniGraph behavior graphs.

This library is intended to follow industry best practices in terms of behavior graphs. It is also designed to be compatible with these existing implementations in terms of capabilities. Although, like all node-based systems, behavior graphs are always limited by their node implementations.

Another neat fact about behavior graphs is that they offer a sand boxed execution model. Because one can only execute what is defined by nodes exposed by the host system, you can restrict what can be executed by these graphs. This type of sand-boxing is not possible when you just load and execute arbitrary scripts.

Discord

You can join our Discord here:

https://discord.gg/mrags8WyuH

Feature Overview

This library, while small, contains a nearly complete implementation of behavior graphs.

Features:

  • Customizable While this library contains a lot of nodes, you do not have to expose all of them. For example, just because this supports for-loops and state, does not mean you have to register that node type as being available.
  • Type Safe This library is implemented in TypeScript and fully makes use of its type safety features.
  • Small This is a very small library with no external dependencies.
  • Simple This library is implemented in a forward fashion without unnecessary complexity.

Node Types:

  • Events You can implement arbitrary events that start execution: Start, Tick
  • Actions You can implement actions that trigger animations, scene scene variations, or update internal state: Log
  • Logic You can do arithmetic, trigonometry as well as vector operations and string manipulation: Add, Subtract, Multiply, Divide, Pow, Exp, Log, Log2, Log10, Min, Max, Round, Ceil, Floor, Sign, Abs, Trunc, Sqrt, Negate, And, Or, Not, ==, >, >=, <, <=, isNan, isInfinity, concat, includes.
  • Queries You can query the state from the system.
  • Flow Control Control execution flow using familiar structures: Branches, delays, if-then, sequences and for-loops.
  • State You can set and load state arbitrarily: Set, Get.
  • Time Time nodes allow you to wait: Delay.

Designed for Integration into Other Systems

This library is designed to be extended with context dependent nodes, specifically Actions, Events and Queries that match the capabilities and requirements of your system. For example, if you integrate into a 3D engine, you can query for player state or 3D positions of your scene graph, set scene graph properties and also react to overlaps, and player movements. Or if you want to integrate into an AR system, you can react to face-detected, tracking-loss.

Command Line Usage

Building

After cloning out this git project locally, run the following:

npm install
npm run build

Examples

The example behavior graphs are in the /examples folder. You can execute these from the command line to test out how this library works.

The main syntax is this one:

npm run exec-graph -- ./examples/[examplename].json

Here are some example graphs in their native JSON form:

Hello World

Print out the text "Hello World!" as soon as the graph starts up!

[
    {
        "type": "event/start"
    },
    {
        "type": "action/log",
        "inputs": {
            "flow": { "links": [ { "node": 0, "socket": "flow" } ] },
            "text": { "value": "Hello World!" }
        }
    }
]

Console output:

> npm run exec-graph -- ./examples/basics/HelloWorld.json

Hello World!

Setting and Reading Variables

In this example, we use a pre-declared variable called "counter" to 1000 and then later read it and print it out.

[
    {
        "type": "event/start",
        "id": "0"
    },
    {
        "type": "state/setNumber",
        "id": "1",
        "inputs": {
            "flow": {
                "links": [
                    {
                        "nodeId": "0",
                        "socket": "flow"
                    }
                ]
            },
            "variable": {
                "value": "0"
            },
            "value": {
                "value": 1000
            }
        }
    },
    {
        "type": "state/getNumber",
        "id": "2",
        "inputs": {
            "variable": {
                "value": "0"
            }
        }
    },
    {
        "type": "logic/numberToString",
        "id": "3",
        "inputs": {
            "a": {
                "links": [
                    {
                        "nodeId": "2",
                        "socket": "result"
                    }
                ]
            }
        }
    },
    {
        "type": "action/log",
        "id": "4",
        "inputs": {
            "flow": {
                "links": [
                    {
                        "nodeId": "1",
                        "socket": "flow"
                    }
                ]
            },
            "text": {
                "links": [
                    {
                        "nodeId": "3",
                        "socket": "result"
                    }
                ]
            }
        }
    }
]

Console output:

> npm run exec-graph -- ./examples/variables/GetSet.json

1000

Branching

This example shows how to branching execution works. The "flow/branch" node has two flow outputs, "true" and "false". The value of it's "condition" input determines the path of execution.

[
    {
        "type": "event/start"
    },
    {
        "type": "flow/branch",
        "inputs": {
            "flow": { "links": [ { "node": 0, "socket": "flow" } ] },
            "condition": { "value": false }
        }
    },
    {
        "type": "action/log",
        "inputs": {
            "flow": { "links": [ { "node": 1, "socket": "true" } ] },
            "text": { "value": "Condition is true!" }
        }
    },
    {
        "type": "action/log",
        "inputs": {
            "flow": { "links": [ { "node": 1, "socket": "false" } ] },
            "text": { "value": "Condition is false!" }
        }
    }
]

Console output:

> npm run exec-graph -- ./examples/basics/Branch.json

Condition is false!

Polynomial Math Formula

This shows how to create math formulas in logic nodes. In this case the equation is: ( a^1 * 3 + a^2 + (-a^3) ), where a = 3. The answer is -9.

[
    {
        "type": "event/start"
    },
    {
        "type": "logic/numberConstant",
        "inputs": {
            "a": { "value": 3 }
        }
    },
    {
        "type": "logic/numberPow",
        "inputs": {
            "a": { "links": [ { "node": 1, "socket": "result" } ] },
            "b": { "value": 1 }
        }
    },
    {
        "type": "logic/numberPow",
        "inputs": {
            "a": { "links": [ { "node": 1, "socket": "result" } ] },
            "b": { "value": 2 }
        }
    },
    {
        "type": "logic/numberPow",
        "inputs": {
            "a": { "links": [ { "node": 1, "socket": "result" } ] },
            "b": { "value": 3 }
        }
    },
    {
        "type": "logic/numberMultiply",
        "inputs": {
            "a": { "links": [ { "node": 2, "socket": "result" } ] },
            "b": { "value": 3 }
        }
    },
    {
        "type": "logic/numberAdd",
        "inputs": {
            "a": { "links": [ { "node": 5, "socket": "result" } ] },
            "b": { "links": [ { "node": 3, "socket": "result" } ] }
        }
    },
    {
        "type": "logic/numberNegate",
        "inputs": {
            "a": { "links": [ { "node": 4, "socket": "result" } ] },
            "b": { "value": 10 }
        }
    },
    {
        "type": "logic/numberAdd",
        "inputs": {
            "a": { "links": [ { "node": 6, "socket": "result" } ] },
            "b": { "links": [ { "node": 7, "socket": "result" } ] }
        }
    },
    {
        "type": "logic/numberToString",
        "inputs": {
            "a": { "links": [ { "node": 8, "socket": "result" } ] }
        }
    },
    {
        "type": "action/log",
        "inputs": {
            "flow": { "links": [ { "node": 0, "socket": "flow" } ] },
            "text": { "links": [ { "node": 9, "socket": "result" } ]}
        }
    }
]

Console output:

> npm run exec-graph -- ./examples/basics/Math.json

-9

Asynchronous Execution

Behave-Graph support asynchronous nodes. These are nodes which will continue execution non-immediately but on their own self-determined schedule. This allows for things such as "Delay" nodes that can sleep for a period of time.

[
    {
        "type": "event/start"
    },
    {
        "type": "action/log",
        "inputs": {
            "flow": { "links": [ { "node": 0, "socket": "flow" } ] },
            "text": { "value": "Waiting..." }
        }
    },
    {
        "type": "time/delay",
        "inputs": {
            "flow": { "links": [ { "node": 1, "socket": "flow" } ] },
            "duration": { "value": 1 }
        }
    },
    {
        "type": "action/log",
        "inputs": {
            "flow": { "links": [ { "node": 2, "socket": "flow" } ] },
            "text": { "value": "One Second Later!" }
        }
    }
]

Console output:

> npm run exec-graph -- ./examples/async/Delay.json

Waiting...
One Second Later!

Sequences

Behave-Graph support waiting for the completion of downstream nodes. This allows for "Sequence" nodes which will execute a series of flow outputs in order.

[
    {
        "type": "event/start",
        "id": "0"
    },
    {
        "type": "action/log",
        "id": "1",
        "inputs": {
            "flow": {
                "links": [
                    {
                        "nodeId": "0",
                        "socket": "flow"
                    }
                ]
            },
            "text": {
                "value": "Starting Sequence..."
            }
        }
    },
    {
        "type": "flow/sequence",
        "id": "2",
        "inputs": {
            "flow": {
                "links": [
                    {
                        "nodeId": "1",
                        "socket": "flow"
                    }
                ]
            }
        }
    },
    {
        "type": "action/log",
        "id": "3",
        "inputs": {
            "flow": {
                "links": [
                    {
                        "nodeId": "2",
                        "socket": "1"
                    }
                ]
            },
            "text": {
                "value": "First Sequence Output!"
            }
        }
    },
    {
        "type": "action/log",
        "id": "4",
        "inputs": {
            "flow": {
                "links": [
                    {
                        "nodeId": "2",
                        "socket": "2"
                    }
                ]
            },
            "text": {
                "value": "Second Sequence Output!"
            }
        }
    },
    {
        "type": "action/log",
        "id": "5",
        "inputs": {
            "flow": {
                "links": [
                    {
                        "nodeId": "2",
                        "socket": "3"
                    }
                ]
            },
            "text": {
                "value": "Third Sequence Output!"
            }
        }
    }
]

Console output:

> npm run exec-graph -- ./examples/flow/Sequence.json

Starting Sequence...
First Sequence Output!
Second Sequence Output!
Third Sequence Output!

For Loops

Building upon waiting for downstream nodes to execute, you can also execute For Loops within Behave-Graph.

[
    {
        "type": "event/start",
        "id": "0"
    },
    {
        "type": "action/log",
        "id": "1",
        "inputs": {
            "flow": {
                "links": [
                    {
                        "nodeId": "0",
                        "socket": "flow"
                    }
                ]
            },
            "text": {
                "value": "Starting For Loop..."
            }
        }
    },
    {
        "type": "flow/forLoop",
        "id": "2",
        "inputs": {
            "startIndex": {
                "value": 0
            },
            "endIndex": {
                "value": 10
            },
            "flow": {
                "links": [
                    {
                        "nodeId": "1",
                        "socket": "flow"
                    }
                ]
            }
        }
    },
    {
        "type": "action/log",
        "id": "3",
        "inputs": {
            "flow": {
                "links": [
                    {
                        "nodeId": "2",
                        "socket": "loopBody"
                    }
                ]
            },
            "text": {
                "value": "Loop Body!"
            }
        }
    },
    {
        "type": "action/log",
        "id": "4",
        "inputs": {
            "flow": {
                "links": [
                    {
                        "nodeId": "2",
                        "socket": "completed"
                    }
                ]
            },
            "text": {
                "value": "Completed For Loop!"
            }
        }
    }
]

Console output:

> npm run exec-graph -- ./examples/flow/ForLoop.json

Starting For Loop...
Loop Body!
Loop Body!
Loop Body!
Loop Body!
Loop Body!
Loop Body!
Loop Body!
Loop Body!
Loop Body!
Loop Body!
Completed For Loop!
Comments
  • Import behave flow + flow side by side model viewer

    Import behave flow + flow side by side model viewer

    This PR imports the code from @beeglebug's behave-flow into the main behave-graph monorepo, so that the flow code and be developed and updated in sync with the core library code.

    A future V2 pr would separate out reusable components and hooks so that developers could import them and integrate them into their existing solution.

    It also adds the following improvements, which it incorporates from @oveddan's interX:

    Interactive 3d Scene Preview

    GLB files can be rendered and interactive in a three.js scene side-by-side with the flow editor, and updates to the editor apply in real-time to the scene. flowEditorGif

    adjustingEditorInRealTime

    User can upload their own 3d model:

    (todo: gif)

    User can load examples that contain both a model and behave graph

    ...if only behave-graph loaded (without a 3d model), then view will not be split:

    loadingScenes

    Split pane can be adjusted

    A user can drag the split in the middle, and also change the split to be vertical or horizontal:

    splitPaneAdjustment

    • react-three-fiber renderer for the 3d scene.
    • new example with button that you press, it starts and elevator animation, and the button turns green
    • a bunch of useful reusable hooks that encapsulates the react-flow functionality
    • an extension of IScene that lets you query properties on a model
    • a way to let the user generate the json path by selecting values from a dropdown

    handle

    ToDo:

    • Update the main readme with instructions on how to run
    • Deploy this somewhere (netlify?)
    • Allow the user to adjust the environment of the scene stage.
    • Documentation!
    opened by oveddan 33
  • export 'ValueType'

    export 'ValueType'

    To be able to add custom value types For example

    import { ValueType } from 'behave-graph`
    
    type Vector3 = { x: number, y: number, z: number };
    
    valuesRegistry.register(new ValueType('vector3', () => ({ x: 0, y: 0, z: 0 }), (text: any) => text, (value: Vector3) => value));
    
    opened by evilfant 6
  • workspaces with vite for demo running, and rollup for module building

    workspaces with vite for demo running, and rollup for module building

    This PR breaks up the examples folder from the lib folder into separate workspaces, thus removing the dependency of three.js from the core lib and reducing its bundle size, and enabling examples to more freely import other dependencies that are not needed by the core lib, such as react, or @react-three/fiber.

    Addresses #128

    It takes inspiration from the workspace setup of react-three-fiber.

    • It uses preconstruct to handle the monorepo dev and code needs, including being able to locally link packages (in examples we can now do import { Vec3 } frombehave-graph`
    • It builds the code with rollup and babel - this also generates nice source maps which i don't believe exist in the current build (I'm currently unable to step into code from the lib in chrome debugger when importing it from another app)
    • It runs the example using vite, a fast, modern, and well-documented front-end webdev environment.

    Now, examples can import behave-graph code the 'normal' way (not by relative path), enabling the code to be copied and pasted more easily to those who want to do their own modifications on it:

    Example code before:

    import { Assert } from '../../lib/Diagnostics/Assert.js';
    import { EventEmitter } from '../../lib/Events/EventEmitter.js';
    import { IScene } from '../../lib/Profiles/Scene/Abstractions/IScene.js';
    import { Vec3 } from '../../lib/Profiles/Scene/Values/Internal/Vec3.js';
    import { Vec4 } from '../../lib/Profiles/Scene/Values/Internal/Vec4.js';
    

    After:

    import { Assert, EventEmitter, IScene, Vec3, Vec4 } from 'behave-graph';
    

    Main organizational changes:

    • created /lib folder for the core package - maybe this can be eventually put into packaages/core
    • src/lib -> lib/src
    • src/graphs ->lib/graph`
    • src/examples ->examples/src`
    • added package.json to lib - this is the core lib package.json, and now behave-graph is the package name in there - should it be? Or should we do something like @behave-graph/core

    Still todo:

    • [x] fix export-node-spec script
    • [x] figure out proper entrypoint for examples - right now there is an index.html in the root of examples that points to the three.js example
    • [x] FIx the three example
    • [ ] update readme
    opened by oveddan 5
  • Bundle library

    Bundle library

    Hi, I was interested in using this library, but noticed an open issue to get it bundled and published, so thought i'd have a quick stab at a basic rollup config.

    In this initial version i've only exported the files necessary to replicate the code in runner.js (GraphEvaluator, loadGraph, NodeRegistry and registerGenericNodes).

    I assume there is other code which would be helpful to a user of the library, but until I can play around with it a bit more, I thought it best to raise it now and ask what else to add.

    opened by beeglebug 5
  • add prettier config

    add prettier config

    From experience, prettier is the nicest solution to keeping a clean, consistently formatted codebase while removing any possible arguments about the details. It's used by thousands of other open source JS projects, so contributors will be familiar with it.

    We could auto format everything once in a single cleanup commit and be done with it, most modern IDE's will handle prettier automatically and format on save.

    We can then strip out most of the eslint config (certainly any bits pertaining to code formatting).

    opened by beeglebug 4
  • Configuration

    Configuration

    This adopts the convention that nodes now have a "configuration" section that is for non-run-time parameters.

    For example, Sequence node can have variable number of outputs. Now you can specify numOutputs for sequence in its configuration.

    Here is the list of nodes that currently support configuration:

    • Sequence: numOutputs
    • WaitAll: numInputs
    • VariableSet: variableId
    • VariableGet: variableId
    • CustomEvent Trigger: customEventId
    • CustomEvent On Trigger: customEventId

    For example this:

       {
           "type": "customEvent/trigger/0",
           "id": "1"
         },
    

    Becomes this:

      {
           "type": "customEvent/trigger",
           "configuration": {
             "customEventId": 0
           },
           "id": "9"
         }
    
    opened by bhouston 3
  • Typed abstraction registry

    Typed abstraction registry

    This PR makes the getter and setters from AbstractionRegistry strongly typed, making the API easier to consume when using typescript.

    For example, before, if you wanted to call:

    get on the AbstractionRegistry there's no way to know what the possible keys are, nor what the type returned is without finding everywhere set is called.

    Existing api call:

    const logger = context.graph.registry.abstractions.get<ILogger>('ILogger');
    

    New api call:

    const logger = context.graph.registry.abstractions.get('ILogger');
    

    You can now now intellicence for abstractionName when calling the api. Screenshot 2022-10-31 at 1 48 56 PM

    opened by oveddan 3
  • Suggestion: split up lib into different workspaces with their own package.json

    Suggestion: split up lib into different workspaces with their own package.json

    I would suggest splitting up this monorepo into multiple workspaces within the monorepo, each with its own package.json.

    I would like to do an implementation of ThreeScene using @react-three/fiber but I don't wan't to pollute the dependencies in the main package.json with those dependencies. So I think it would make sense to have each of the following workspaces, each with their own package.jsons:

    • one that defines the json schema/standard for this behavior-graph data format
    • a lib with the current implementation to parse it. - this can be imported directly using npmjs without needing to import three.js
    • a folder for 3d scene functionality with three.js as a dependency.
    • different folders for different example implementations - one for three.js and one for @react-three/fiber
    • eventually a folder could be created for unity implementation
    opened by oveddan 3
  • Support glTF standard types: integer, vec2, vec3, vec4/quat, mat2, mat3, mat4

    Support glTF standard types: integer, vec2, vec3, vec4/quat, mat2, mat3, mat4

    Confirm that integer is a real thing. I think it is.

    Interestingly enough, vec4 and quat are the same type. I may not want that in this system because quat operations are different than vec4. But I could have a transformer that converts both to the same underlying type.

    opened by bhouston 3
  • nodes attempting to set output values on flow sockets

    nodes attempting to set output values on flow sockets

    Several flow nodes are throwing errors when you attempt to run a graph, complaining about attempting to set output values on flow sockets.

    I think i've fixed flipflop, as that was the one I was attempting to use when I saw the issue, but I suspect some of the others are still doing it (I was hesitant to touch things like "flow/sequence" which seem incomplete anyway)

    opened by beeglebug 3
  • Setup package alias so one can import in examples easily.

    Setup package alias so one can import in examples easily.

    it would be nice to see in the examples this:

    import { Node } from 'behaviour-graph';
    

    Rather than:

    import { Node } from '../../../dist/lib/index';
    

    I think this can be done with some config variables in the tsconfig.json file...

    opened by bhouston 3
  • Proposal: support union types

    Proposal: support union types

    While navigating through the Unreal Blueprint API documentation to get some inspiration for our docs, I noticed for some Nodes they do support Union types. I think this could be a nice to have feature, specially if we include structured data support, but also for some generic functions which might accept ie both float & integer values

    image

    opened by aitorllj93 0
  • Proposal: Add Structured data support

    Proposal: Add Structured data support

    Although perhaps outside the scope of this project, I think it would be great to support structured data as well. Depending on what you think, we have 3 options in order to do this:

    • Implement the value types and very tiny nodes to work with on the core package and a separate package/profile with some extended tools
    • Implement it as a separate package/profile inside this repository (officially supported/maintained)
    • Implement it as a separate package/profile in other repository (community maintained)

    I have a working example here with some examples I was working on in the last days: behave-graph/struct I also deployed them here to test the github-actions and the docs I did for this repo, but in this case just with that specific profile

    I personally would prefer the 1st or 2nd option and include it in the main repository as an optional profile, same as the Scene one but it's up to you as I don't know if this makes sense.

    cc @bhouston @oveddan

    opened by aitorllj93 0
  • Add web documentation

    Add web documentation

    As discussed here I have been working on improving the docs. Here's a first approach by using Docusaurus

    Landing Page:

    image

    Core Concepts (+ dark theme which I personally prefer):

    image

    image

    Auto-generated pages for Values and Nodes (for Core and Scene profiles):

    image

    image

    Auto-generated pages with the Examples found in the graphs folder:

    image

    Easy to use blog in case we need it (can be disabled if there's no plans to use it):

    image

    That said, there are some troubles I found during the development and should be discussed:

    • Many different interfaces for Nodes (NodeDescription, NodeDescription2, Node Instance, NodeSpecJSON) and some misalingments between them. It would be nice to be able to generate the Node pages just with the NodeSpecJSON but right now it's not possible:

      • writeNodeSpecsToJSON fails when executing on a Registry with just the Scene profile so I need to load 2 registries, to be able to check which Nodes are from each Profile
      • the NodeSpecJSON doesn't include some fields such as the helperText which in this case it's quite useful
      • the NodeSpecJSON doesn't include any information about dynamic I/O as they are defined with the new configuration property, so I have to do really weird tricks to do this. Again, I think would be nice to be able to determine this based only on the NodeSpecJSON, this is the main interface for the Node definitions on the reactflow implementation
    • The reactflow component has some issues that makes it hard to reuse it right now:

      • The styles are defined by some kind of css class utility library (maybe Tailwind? Idk) so in order to use them inside the docs I would need to find/redefine them and load them into the blog and also taking the risk to arise conflicts. I would favor some styles-in-js implementation such as emotion.
      • The presentational components are very tied to the reactflow engine (mainly because of the hooks they're using) so I hard to rewrite the entire node inside the docs
      • The runner function is defined deep inside the components so there's no way to override it with custom profiles rn

    As we're having many changes on the core interfaces (NodeDescription) and the way we define the nodes, I don't think it makes sense to update the reactflow component till those changes are completed. But once we have polished those details, I would like to improve the visual editor so we can integrate it inside the docs and provide a visual execution environment for the examples section.

    Also, we should include a GH Action to deploy the website to GH pages. I can work on that before we merge this PR and include it with the changes.

    cc @bhouston @oveddan

    opened by aitorllj93 8
  • Node definition api using interfaces and purely functional approach + separate state from node definition

    Node definition api using interfaces and purely functional approach + separate state from node definition

    This PR is the start of doing what was discussed in #191:

    • It creates interfaces for NodeDefinitions.
    • Changes node definitions to be purely functional and stateless, which improves testability; they can be setup easily without needing to define a graph first.
    • When defining nodes, gets rid of tons of boilerplate code; instead of having to create a class that inherits another class and have to pass a definition and graph to the super class, you can just now define the inputs/outputs and the execution function.
    • on trigger/exec functions, read, write, and commit now restrict what socket name can be called based on the defined input/output sockets.
    • for flow nodes, state is passed as an argument, and updated state must be returned from the triggered function; the state is strongly typed based on the initialState parameter.
    • There is a new IGraphApi which minimally defines an api for what is needed by nodes for their execution - this is passed to the nodes instead of the whole Graph class.
    • Engine and Fiber now consume nodes as interfaces: INode which means the implementation of Node can be abstracted.

    This makes everything more testable, and allows someone to more easily create their own graph execution engine and integrate it with these definitions.

    It also reduces the amount of boilerplate needed to create node defs.

    So far, all of the Value and Event based nodes have been migrated to use this new definition format which was made easy by the awesome work done in #192 . Some Flow nodes have been migrated too.

    An example of before / after:

    Counter Node

    Before:

    import { Fiber } from '../../../Execution/Fiber';
    import { Graph } from '../../../Graphs/Graph';
    import { FlowNode } from '../../../Nodes/FlowNode';
    import { NodeDescription } from '../../../Nodes/Registry/NodeDescription';
    import { Socket } from '../../../Sockets/Socket';
    
    export class Counter extends FlowNode {
      public static Description = new NodeDescription(
        'flow/counter',
        'Flow',
        'Counter',
        (description, graph) => new Counter(description, graph)
      );
    
      constructor(description: NodeDescription, graph: Graph) {
        super(
          description,
          graph,
          [new Socket('flow', 'flow'), new Socket('flow', 'reset')],
          [new Socket('flow', 'flow'), new Socket('integer', 'count')]
        );
      }
    
      private count = 0;
    
      triggered(fiber: Fiber, triggeringSocketName: string) {
        switch (triggeringSocketName) {
          case 'flow': {
            this.count++;
            this.writeOutput('count', this.count);
            fiber.commit(this, 'flow');
            break;
          }
          case 'reset': {
            this.count = 0;
            break;
          }
          default:
            throw new Error('should not get here');
        }
      }
    }
    
    

    after

    // we now only need to import one helper function
    import { makeFlowNodeDefinition, NodeCategory } from 'packages/core/src/Nodes/NodeDefinition';
    
    // helper function that allows us to declare a strongly typed flow node definition without needing to explicitly define the generic params
    export const Counter = makeFlowNodeDefinition({
      typeName: 'flow/counter',
      label: 'Counter',
      in: {
        flow: 'flow',
        reset: 'flow'
      },
      out: {
        flow: 'flow',
        count: 'integer'
      },
      initialState: {
        count: 0
      },
      category: NodeCategory.Flow,
      triggered: ({ commit, write, triggeringSocketName, state }) => {
        // state has the same type as initialState
        let count = state.count;
        switch (triggeringSocketName) {
          case 'flow': {
            count++;
            // through type enforcement, write and commit can only write to one of the keys of `out`
            write('count', count);
            commit('flow');
            break;
          }
          case 'reset': {
            count = 0;
            break;
          }
          default:
            throw new Error('should not get here');
        }
    
        // return updated state - must have same type as initial state
        return {
          count
        };
      }
    });
    

    Opening this PR to get some feedback, before proceeding further. @bhouston let me know what you think!

    I have some freetime this week so I'd be glad to migrate the rest of the node defs.

    opened by oveddan 8
  • refactored function node desc to have cleaner input and less code

    refactored function node desc to have cleaner input and less code

    Did some refactoring on FunctionDesc to make it more readable and simpler to consume:

    • Got rid of being able to have inputs/outputs be declared either as an array or key<->value, its confusing to have an api that can be called in multiple ways like this. I know the desire is to move to key<-> value pairs, but it doesn't seem like that is being used now for function node, so why don't we wait til we do it the right way (i.e. as top level api) first.
    • Refactored the code to have a reusable function: toOrderedSockets that converts from Array -> Socket, with an argument for how to determine the keys of each element.
    opened by oveddan 1
  • Update Flow with support for

    Update Flow with support for "configuration" on Sequence nodes

    Some nodes now support "configuration". This is for non-run-time configuration of nodes, right now that exclusively deals with changing the actual socket structure of the nodes.

    The first few nodes I've applied this to are: (1) Sequence, (2) WaitAll, (3) Custom Event Trigger and (4) On Custom Event. There is also a PR for (5) Switch.

    We now need to update the Flow library to handle modifying nodes by changing their configuration.

    For example for Sequence, we have to increase/decrease the "numOutputs" of the configuration and then recreate the node and replace the existing node with this new version. When replacing the node with the new version, we should aim to keep as many of the existing connections as possible.

    I think we probably need to make custom UI elements for each of these nodes that has configuration parameters? Or maybe we can formalize it so that we can auto-create UI elements for them?

    What do people think?

    opened by bhouston 0
Owner
Ben Houston
Coding computer graphics since 1990. Founder & CTO of Threekit (3D for e-commerce). Previously: http://Clara.io, Exocortex, Krakatoa, Deadline.
Ben Houston
A lightweight, powerful and highly extensible templating engine. In the browser or on Node.js, with or without jQuery.

JsRender: best-of-breed templating Simple and intuitive, powerful and extensible, lightning fast For templated content in the browser or on Node.js (w

Boris Moore 2.7k Jan 2, 2023
API dot Open Sauced is NestJS and SupaBase powered OAS3 backend designed to remove client complexity and provide a structured graph of all @open-sauced integrations

?? Open Sauced Nest Supabase API ?? The path to your next Open Source contribution ?? Prerequisites In order to run the project we need the following

TED Vortex (Teodor-Eugen Duțulescu) 13 Dec 18, 2022
A simple in-memory time-based cache for both objects and function execution.

What is this? A simple in-memory time-based cache for both objects and function execution. How do I install it? You can install it by using the follow

cadienvan 7 Dec 15, 2022
A simple in-memory key-value cache for function execution, allowing both sync and async operations using the same methods

A simple in-memory key-value cache for function execution, allowing both sync and async operations using the same methods. It provides an invalidation mechanism based both on exact string and regex.

cadienvan 10 Dec 15, 2022
ROP userland execution for PS5 (4.03)

# Exploring the Playstation 5 Security - Userland Introduction The PlayStation 5 was released on November 12th 2020. While it's similar to the PS4 in

null 230 Dec 2, 2022
"Lerna & Distributed Task Execution" Example

Lerna Distributed Task Execution (DTE) Example/Benchmark On how to make your CI 23 times faster with a small config change New versions of Lerna can u

Victor Savkin 9 Nov 27, 2022
jQuery Plugin For Delayed Event Execution

bindWithDelay jQuery plugin Author: Brian Grinstead MIT license: http://www.opensource.org/licenses/mit-license.php http://github.com/bgrins/bindWith

Brian Grinstead 152 Dec 31, 2022
✏️ A small jQuery extension to turn a static HTML table into an editable one. For quickly populating a small table with JSON data, letting the user modify it with validation, and then getting JSON data back out.

jquery-editable-table A small jQuery extension to turn an HTML table editable for fast data entry and validation Demo ?? https://jsfiddle.net/torrobin

Tor 7 Jul 31, 2022
The project integrates workflow engine, report engine and organization authority management background, which can be applied to the development of OA, HR, CRM, PM and other systems. With tlv8 IDE, business system development, testing and deployment can be realized quickly.

介绍 项目集成了工作流引擎、报表引擎和组织机构权限管理后台,可以应用于OA、HR、CRM、PM等系统开发。配合使用tlv8 ide可以快速实现业务系统开发、测试、部署。 后台采用Spring MVC架构简单方便,前端使用流行的layui界面美观大方。 采用组件开发技术,提高系统的灵活性和可扩展性;采

Qian Chen 38 Dec 27, 2022
Simple and Extensible Markdown Parser for Svelte, however its simplicity can be extended to any framework.

svelte-simple-markdown This is a fork of Simple-Markdown, modified to target Svelte, however due to separating the parsing and outputting steps, it ca

Dave Caruso 3 May 22, 2022
Generate static open graph images for Next.js at build time

next-static-og-images Generate static Open Graph images for Next.js at build time Getting started Installation npm i -D next-static-og-images or yarn

Adam Hwang 5 Jan 26, 2022
A small javascript DOM manipulation library based on Jquery's syntax. Acts as a small utility library with the most common functions.

Quantdom JS Quantdom is a very small (about 600 bytes when ran through terser & gzipped) dom danipulation library that uuses a Jquery like syntax and

Sean McQuaid 7 Aug 16, 2022
An extensible HTML DOM window manager with a professional look and feel

Wingman An extensible HTML DOM window manager with a professional look and feel. Installation Wingman only requires two files: wingman.css and wingman

nethe550 1 Jan 21, 2022
Pintora is an extensible javascript text-to-diagrams library that works in both browser and Node.js.

Pintora Documentation | Live Editor Pintora is an extensible javascript text-to-diagrams library that works in both browser and Node.js. Expressing yo

hikerpig 652 Dec 30, 2022
Lexical is an extensible JavaScript web text-editor framework with an emphasis on reliability, accessibility and performance

Lexical is an extensible JavaScript web text-editor framework with an emphasis on reliability, accessibility and performance. Lexical aims to provide a best-in-class developer experience, so you can easily prototype and build features with confidence.

Meta 12.7k Dec 30, 2022
LogTure - A minimal designed, fully customizable, and extensible modern personal blogging framework, built with Nextjs.

LogTure - A minimal designed, fully customizable, and extensible modern personal blogging framework, built with Nextjs.

Sam Zhang 14 Aug 26, 2022
Complete, flexible, extensible and easy to use page transition library for your static web.

We're looking for maintainers! Complete, flexible, extensible and easy to use page transition library for your static web. Here's what's new in v2. Ch

null 3.7k Jan 2, 2023
A Versatile, Extensible Dapp Boilerplate built with Rainbowkit, Next.js, and Chakra-ui.

rainplate • A Versatile, Extensible Dapp Boilerplate built with Rainbowkit, Next.js, and Chakra-ui. Getting Started Click use this template to create

White Noise 12 Nov 22, 2022
A flexible and extensible javascript library for letters animation simulations.

LetterLoading LetterLoading js is a javascript library for making awesome letter simulations. It default simulation is a letter loading simulation. Co

kelvinsekx 5 Mar 22, 2022