An extendable system made up of autonomous execution services known as nodes organized in a tree of processes. Inspired by Godot!

Overview

NodeTree

Crates.io License Crates.io Version Documentation

NodeTree is a framework to create large scalable programs and games through a tree of processes. Each process is fully autonomous and is capable of storing its own state or data, and communicating with other processes. These processes are known as Nodes.

โš ๏ธ WARNING โš ๏ธ
THIS IS A NIGHTLY-DEPENDENT CRATE.
This crate is in early development. Beware of possible bugs or safety violations.
The specific nightly version this crate uses is v1.78.

Getting Started!

Simply either run cargo add node_tree at the terminal directed towards the directory of your project, or add node_tree = X.X to your cargo.toml file.

To begin creating a program in Rust that utilizes a NodeTree, we must first create a root Node. In order to reduce boilerplate, we will use the included NodeSys derive macro to implement the required Dynamic and NodeAbstract traits. We will then implement the Node trait ourselves.

#![feature(arbitrary_self_types)]   // Required for now.
use node_tree::prelude::*;


#[derive(Debug, Clone, NodeSys)]
pub struct NodeA {
    base: Rc<NodeBase>   // Required for Nodes.
}

// To make things simple, it is advised to have most node constructors return the node
// instance wrapped inside of this crate's `Hp<T>` pointer.
impl NodeA {
    fn new(name: String) -> Hp<Self> {
        Hp::new(NodeA { base: NodeBase::new(name) })
    }
}

// Example implementation of the Node trait with custom behaviours.
impl Node for NodeA {

    /// Runs once the Node is added to the NodeTree.
    fn ready(self: Hp<Self>) -> () {

        // To show off how you could add children nodes.
        if self.depth() < 3 {
            self.add_child(NodeA::new(format!("{}_Node", self.depth() + 1)));
            self.add_child(NodeA::new(format!("{}_Node", self.depth() + 1)));
            self.add_child(NodeA::new(format!("{}_Node", self.depth() + 1)));
        }

        if self.is_root() {
            println!("{:#?}", self.children());
        }
    }

    /// Runs once per frame. Provides a delta value in seconds between frames.
    fn process(self: Hp<Self>, delta: f32) -> () {

        // Example of using the delta value to calculate the current framerate.
        println!("{} | {}", self.name(), 1f32 / delta);

        // Using the NodePath, you can reference other nodes in the NodeTree from this node.
        if self.is_root() {
            match self.get_node(NodePath::from_str("1_Node/2_Node1/3_Node2")) {
                Some(node) => println!("{:?}", node),
                None       => ()
            }
        }

        // Nodes can be destroyed. When destroyed, their references from the NodeTree are cleaned up as well.
        // If the root node is destroyed, then the program automatically exits. (There are other ways to
        // terminate the program such as the queue_termination() function on the NodeTree instance).
        if self.children().is_empty() {
            self.free();   // We test the progressive destruction of nodes from the tip of the tree
                           // to the base.
        }
    }

    /// Runs once a Node is removed from the NodeTree, whether that is from the program itself terminating or not.
    fn terminal(self: Hp<Self>) -> () {}   // We do not do anything here for this example.

    /// Returns this node's process mode.
    /// Each process mode controls how the process() function behaves when the NodeTree is paused or not.
    /// (The NodeTree can be paused or unpaused with the pause() or unpause() functions respectively.)
    fn process_mode(self: Hp<Self>) -> ProcessMode {
        ProcessMode::Inherit    // We will return the default value, which inherits the behaviour from
                                // the parent node.
    }
}

Finally, in order to activate our NodeTree, we must instance the root Node and feed it into the NodeTree constructor.

// ...previous implementations

fn main() -> () {

    // Create the tree.
    let root: Hp<NodeA>    = NodeA::new("Root".to_string());
    let tree: Hp<NodeTree> = NodeTree::new(root);

    // Begin operations on the tree.
    tree.start();
    tree.process();   // This will run an indefinite loop until the program exits.
}

Features

  • ๐Ÿ—๏ธ An easy abstraction framework for different processes to communicate and interact with each other in a scalable manner. Inspired by Godot!
  • โฏ๏ธ The ability to pause() and unpause() the NodeTree, and fine tune individual Node behaviours for when a tree is paused/unpaused.
  • ๐Ÿ“ก Various methods to communicate with other nodes, such as owner(), parent(), get_child(), children(), and get_node().
  • ๐Ÿ”— An abstracted smart pointer known as Hp<T> which clones implicitly to reduce syntax noise and allows for low boilerplate.
  • ๐Ÿ‘ช The ability to manage nodes with add_child() and remove_child().
  • ๐ŸŒฒ Allows for the direct referencing of the NodeTree through a node's root() function.
  • ๐Ÿ“š TODO: A caching system hosted on the NodeTree to act as a safe interface to ensure the Hp<T> soundness, and increase performance!
  • ๐Ÿ“œ TODO: Includes a method to save and handle individual node scenes, such as the handy visual macro Scene!.
You might also like...
A nine slice/patch plugin for bevy ui nodes as single component using a fragment shader.
A nine slice/patch plugin for bevy ui nodes as single component using a fragment shader.

Bevy nine slice/patch Material Plugin Quick and easy auto-scaling nine slice/patch material for bevy ui nodes implemented as Fragment Shader. Features

Utilities for creating strictly ordered execution graphs of systems for the Bevy game engine

bevy_system_graph This crate provides the utilities for creating strictly ordered execution graphs of systems for the Bevy game engine. Bevy Version S

a prototype crate for creating modular and performant 3D CPU particle systems, inspired by Unity's Shuriken Particle System.

bevy_prototype_particles This is a prototype crate for creating modular and performant 3D CPU particle systems, inspired by Unity's Shuriken Particle

Generate rust command line executables from gRPC protobuf services.

Rust template repository. An opinionated starting point for rust projects such as systemd services command line tools client programs server programs

rpg-cli is a bare-bones JRPG-inspired terminal game written in Rust
rpg-cli is a bare-bones JRPG-inspired terminal game written in Rust

rpg-cli is a bare-bones JRPG-inspired terminal game written in Rust. It can work as an alternative to cd where you randomly encounter enemies as you change directories.

Dwarf Fortress inspired frontend to Veloren, the multiplayer RPG voxel game written in Rust
Dwarf Fortress inspired frontend to Veloren, the multiplayer RPG voxel game written in Rust

velobracket ('veloren' + 'bracket-lib') velobracket is Dwarf Fortress inspired frontend to Veloren, the multiplayer RPG voxel game written in Rust. Us

Sandbox is a pixel physics simulator inspired by other such like Sandspiel and Noita
Sandbox is a pixel physics simulator inspired by other such like Sandspiel and Noita

Sandbox Sandbox is a pixel physics simulator inspired by other such like Sandspiel and Noita. It's also a precursor for an ongoing game project. Sandb

Examples inspired by 'Nature of Code' in Bevy 3D and Rust
Examples inspired by 'Nature of Code' in Bevy 3D and Rust

rust-bevy and the Nature of Code The book The Nature of Code from Daniel Shiffman is a wonderful book about programming. https://natureofcode.com/book

Rust-lang Continuous Wavelet Transform(CWT) library inspired by fCWT.

fastcwt Rust-lang Continuous Wavelet Transform(CWT) library inspired by fCWT. This crate is a direct translation of fCWT Library written in C++ by Art

Comments
  • `NodeScene` Integration: Introduce the `Scene!` macro for visually setting up NodeScenes, and add saving & loading.

    `NodeScene` Integration: Introduce the `Scene!` macro for visually setting up NodeScenes, and add saving & loading.

    As this crate is heavily inspired by Godot's system of ease of use and simplicity, I think adding a visual system for working with trees would shorten the initialization code that most "systems of nodes" need in order to set up their services. This visual system would also tie into an instantiation system and load()/save() systems for systems of nodes to not have duplicate code or to be able to save their internal state for later reuse.

    The Scene! Macro

    The Scene! Macro is actually quite simple. All it is is a bunch of expressions that return Hp<Dyn Node> organized in a tree dictated by tabs. Here is an example of how it would be used:

    use node_tree::prelude::{ Scene, NodeScene };
    use crate::custom_nodes::*;
    
    fn main() -> () {
        let scene: NodeScene = Scene! {
            Root::new()
                ChildA::new()
                ChildB::new()
                ChildD::new()
                    Tail::new()
        }
    }
    

    Scene Systems

    The aforementioned macro will output a NodeScene, which can be instantiated by adding it as a child, just like any other Node. There would be a new trait for this behaviour called InstanceableAsChild which Node and NodeScene would inherit. NodeScenes could also be saved/loaded to/from a file - which would most likely be a binary file.

    enhancement 
    opened by LunaticWyrm467 0
  • Rewrite the `NodeQuery` System

    Rewrite the `NodeQuery` System

    NodeQuery is a left over type from earlier development that I feel could still have a use in the project to ensure safety when using the Hp<T> smart pointer to reference nodes.

    The Safety Problem

    Currently, when used within the scope of the ready(), process(), terminal(), or any other functions that reference it in the Node trait it is safe to use as the NodeTree/Node system ensures that all Hp<T> pointers are valid and do not point to null allocations. This is not the case when a user decides to save an Hp<T> pointer as a field within a Node, whether as a shortcut reference to a Node or for some other reason. When this happens, the node system cannot guarantee that said pointer will reference a valid allocation.

    The Solution

    This is a multipart solution that may be somewhat complex but should be doable:

    • Manage a hashmap on the NodeTree itself which stores identifying cache for each nodes. Each Node would get its own unique identifier relative to the whole tree. These would be managed by nodes automatically, where new nodes create IDs for themselves and old nodes destroy IDs. A tree would then have the option to get a Node by ID, which could return None.
    • In order to make this operation less repetitive, we could save a NodeQuery from a Node, which contains a node's ID and when queried will run the ID through the NodeTree to ensure that it exists, or returns None if not.
    • In order to encourage to save a NodeQuery instance instead of a raw Hp<T> reference, we could tie in lifetimes to those smart pointers using a PhantomData<'a, ()> instance.
    enhancement 
    opened by LunaticWyrm467 0
Owner
LunaticWyrm
LunaticWyrm
A CLI tool to manage your godot-rust projects

ftw A CLI tool to manage your godot-rust project! Table of contents General Information Setup Usage Contact General Information This is a tool to help

Michael Angelo Calimlim 77 Dec 13, 2022
compare gdnative rust based physics against Godot built-in physics

Godot vs. Rapier Rapier is an open source physics framework written in Rust. This project pits godots built-in physics against Rapier. It uses godot-r

Stephan Dilly 75 Nov 17, 2022
A tool to generate inbetweens for animated sprites, written in godot-rust

Bitmapflow is a tool to help you generate inbetweens for animated sprites. In other words, it makes your animations smoother. It uses optical flow to

null 411 Dec 21, 2022
An egui backend for godot-rust

Godot Egui An egui backend for godot-rust. Rationale Godot has a perfectly valid GUI system, so why egui? Here are my personal reasons: Simplicity: No

null 109 Jan 4, 2023
GDDB is a superfast in-memory database designed for use in Godot

GDDB GDDB is a superfast in-memory database designed for use in Godot. This database aims to provide an easy frontend to an efficient in-memory databa

Richard Patching 5 Dec 4, 2022
jlang--godot bridge, built in rust

jlang-rs-gd J is an extremely high-level mathematical notation and programming language. Godot is a game / gui / multimedia engine. jlang-rs-gd lets y

tangentstorm 2 Feb 15, 2022
A Godot 3.4 binding for Live2D

godot-cubism A Godot 3.4 binding for cubism-rs which itself is a binding for the native cubism sdk. Usage var factory = load("path_to_your_native_scri

null 16 Dec 23, 2022
A crate for using Bevy with the Godot Engine.

bevy_godot A crate for using Bevy with the Godot Engine. This crate is in active development and is not ready for production use. Features Godot Scene

Abby Bryant 63 Dec 17, 2022
Plugin to generate flowfields from tilemaps in the Godot Engine!

Godot Tilemap Flowfields RTS-Style optimized path-finding for crowds of agents. Built for the Godot game engine, written with performance in mind in R

Arne Winter 18 Jan 10, 2023
A direct ecs to low-level server implementation for Godot 4.1

godot_ecs What if Godot 4.1 and Bevy got married? Well, you'd get one interesting duo of data driven goodness. In Development This crate is not produc

null 5 Oct 6, 2023