Giter Club home page Giter Club logo

behave-graph's Introduction

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 required 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.

Documentation

Community Resources

You can join our Discord here:

https://discord.gg/mrags8WyuH

Feature Overview

This library, while small, contains a nearly complete implementation of behavior graphs along with extensions for 3D math types and scenes. It also contained a graph editor React library.

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.
  • High Performance Currently in performance testing, the library achieves over 2M node executions per second.

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: Branch, Delay, Debounce, Throttle, FlipFlop, Sequence, Gate, MultiGate, DoOnce, DoN, ForLoop
  • Variables You can create, set and get variable values.
  • Custom Events You can create, listen to and trigger custom events.

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.

Developer Setup

Install all dependencies:

npm install

To do a full development build with hot reloading and a built-in dev server (via vite) run:

npm run dev

If you are curious about the monorepository setup, you can read more about it here:

https://github.com/bhouston/template-modern-typescript-monorepo

Command Line 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:

npx exec-graph ./graphs/[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!

/graphs/core/HelloWorld.json

Console output:

npx exec-graph ./graphs/core/HelloWorld.json

Console output:

Hello World!

Setting, Reading, And Listening to Variables

In this example, we use set a variable and also listen to when it changes.

/graphs/variables/Changed.json

Console output:

npx exec-graph ./graphs/core/variables/Changed.json

Console output:

391

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.

/graphs/core/flow/Branch.json

Command:

npx exec-graph ./graphs/core/flow/Branch.json

Console output:

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.

/graphs/core/logic/Polynomial.json

Command:

npx exec-graph ./graphs/core/logic/Polynomial.json

Console output:

-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.

/graphs/core/async/Delay.json

Command:

npx exec-graph ./graphs/core/async/Delay.json

Console output:

Waiting...
One Second Later!

For Loops

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

/graphs/core/flow/ForLoop.json

Command:

npx exec-graph ./graphs/core/flow/ForLoop.json

Console output:

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!

Custom Events

You can register custom events, trigger then and listen on them.

/graphs/core/events/CustomEvents.json

Console output:

npx exec-graph ./graphs/core/events/CustomEvents.json

Console output:

myCustomEvent Fired!
myCustomEvent Fired!
myCustomEvent Fired!
myCustomEvent Fired!
myCustomEvent Fired!

Performance Testing

Here is a test of 10,000,000 iteration for loop:

/graphs/core/flow/PerformanceTest.json

Here is the command running with verbose logging, e.g. "-l 0":

npx exec-graph ./graphs/core/flow/PerformanceTest.json -l 0

Console output:

Starting 10,000,000 iteration for-loop...
1,000,000 more iterations...
1,000,000 more iterations...
1,000,000 more iterations...
1,000,000 more iterations...
1,000,000 more iterations...
1,000,000 more iterations...
1,000,000 more iterations...
1,000,000 more iterations...
1,000,000 more iterations...
1,000,000 more iterations...
Completed all iterations!

  Profile Results: 30000014 nodes executed in 2.742 seconds, at a rate of 10940924 steps/second

behave-graph's People

Contributors

aitorllj93 avatar arodic avatar beeglebug avatar bhouston avatar evilfant avatar kayhhh avatar oveddan avatar sybiote avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

behave-graph's Issues

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...

Create Three.js example.

It should be relatively easy to create a Three.jS example once we have it running in the browser via #3.

Need a logo

Something to use on the website, discord, etc.

Support multi-outputs per node execution

In order to support for-loops and delayed sequences, I need to be able to return outputs multiple times so that the downstream graph executes multiple times.

How should this be done?

I think at minimum a promise should be returned and then it will resolve when the next set of outputs are ready. And part of that response will be another promise if there are subsequent outputs that should be waited upon.

This needs a nice API.

Create reusable library form.

Currently this is not in a true library form, rather it just allows for local tests to be run. For this to be truly usable, it needs to be converted and published to an npm accessible library.

Enable library users to add SocketValueTypes

The types that flow through the graph should not be limited to only those pre-defined in this library. This probably means we have to move away from an enum and towards a list of registered types.

remove .vscode directory

Having this checked in makes it a bit tougher for other developers to use their own workspace settings when working on the project, it should probably be removed and gitignored.

Implement assertion nodes for unit testing.

It would be nice to have Assert nodes that check for a specific input and fail otherwise. This would allow one to create a large series of graphs that if they run successful are basically unit tests for the system.

Enable custom events.

It should be possible to listen to any event by a string identifier. And it should be possible to call any custom event by string identifier.

Add types for json data.

At the moment, readGraphFromJSON and writeGraphToJSON both deal with a type of any, which is fine, but open to errors.

For better interoperability with other code, it would be good to specify a type for the JSON version of a graph.

Create web browser example.

Right now I only have command line examples. Having an example that runs in the web browser would be very nice.

Allow an upstream node to "wait" for a downstream subgraph to complete execution.

This is required for the "for loop" node in Unreal Engine and Unity Visual Scripting.

https://docs.unrealengine.com/4.26/en-US/ProgrammingAndScripting/Blueprints/UserGuide/FlowControl/

Also required by "Sequence". These nodes wait for the subgraph connected to their output flow socket to execute and then they will pass execution to either to the same output flow socket (loopBody) or to another flow output socket (complete.).

dist has two copies of the library and type definitions

this is causing vscode to show everything twice in autocomplete, possibly no other side effects, but should tidy up anyway

i think this was introduced when the examples got bundled, the del commands probably need double checking

image

Support event/stateChanged event - when state changes

It should be possible to listen to state identifiers and have an event fire when the state is changed.

Q: Should there be a central registry of state data? Like a list of member variables, or should I keep the very arbitrary map-based method? The central registry on a per graph basis may lead to better type checking.

Maybe easier to use a stack to manage execution rather than work queue?

I am starting to wonder if the main method of evaluating these behavior graphs is actually a stack rather than a work queue.

The reason why a stack would be easier is:

  • The sequence node would just put its children on the stack in reverse order.
  • The for-loop would put itself on the stack and then its loopBody. When it gets executed again after the loopBody is done, it will put itself again on the stack and then the next iteration of loopBody.

I think that once the stack is completed, you go back to look for new events?

I think this doesn't handle the case where you want to "break" a for-loop. In those cases you either need a loop in the graph (non-DAG) or as Unity does it, it has an event that is raised in the loopBody and then executes and sets a "break" flow socket on the for-loop (but this violates the stack execution model, as the events wouldn't be fired until the for-loop is done.)

Thus this isn't yet sufficient.

Instead of relying on Events being triggered externally, allow them to self-register

In the current implementation, it is required that one trigger Events explicitly. You can see this in all of the examples where after loading the graph, one triggers the "events/start" event explicitly.

Instead, one could have all events in the graph execute immediately upon startup and they all return promises, which means that they are pending. And then when they fire, they resolve their promises. This would make it much simpler to add new event types to the system.

eslint fails to run

ESLint couldn't find the plugin "eslint-plugin-react". looks like a dependency of eslint-config-airbnb?

Validate Graph: Type alignment

Enforcing typing alignment:

  • Connected Input and Output Sockets agree on their type.
  • Input Socket types align with the type of their constant values.

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)

Make sure state get/set is type safe.

Right now state is stored in the graph without any type information. Thus it can pass out a number to a getString state node. This is unsafe. The state store should be typed and enforce those types at run-time.

saveGraph to JSON

Implement a means to save in a JSON format compatible with the readGraph call.

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).

how does a graph connect to an external environment?

This is a larger question about how a behave-graph graph interacts/binds itself to some external environment, be it a threejs scene or something else.

This codesandbox is me experimenting with a simple connection between a graph and a threejs scene, but at the moment the connection is very manual.

https://codesandbox.io/s/behave-graph-three-test-ijdzj6

What i'm wondering, is what do we think that connection will eventually look like? I imagine a node such as my test "input/mouseClick" would also have an input of some sort to select a mesh in the scene (in this case the cube), and then would only be triggered by clicks on that cube.

We would then also want a way to transmit effects back into the scene, for example I might want a graph which looks like the following:

const graphDef: GraphJSON = {
  nodes: [
    {
      id: "1",
      type: "input/mouseClick",
    },
    {
      id: "2",
      type: "material/setColor",
      inputs: {
        text: {
          value: "#0000FF",
        },
        flow: {
          links: [
            {
              nodeId: "1",
              socket: "flow",
            },
          ],
        },
      },
    },
  ],
};

ie, when the user clicks on the cube, the material changes colour to blue.

I think it would be really useful to sketch out some more "real world" examples of graphs which actually do something in a less abstract environment to get an idea of what changes might be required in the core library to support the required nodes.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.