#框架 #gamedev #工具

nightly node_tree

由自主执行服务组成的可扩展系统,这些服务称为节点,并组织成进程树。灵感来源于Godot!

5个版本

0.2.1 2024年4月23日
0.2.0 2024年4月22日
0.1.2 2024年4月8日
0.1.1 2024年4月8日
0.1.0 2024年4月8日

#357 in 游戏开发

Download history 116/week @ 2024-04-09 64/week @ 2024-04-16 178/week @ 2024-04-23 2/week @ 2024-04-30 5/week @ 2024-05-21

每月 208 次下载

MIT/Apache

85KB
1K SLoC

NodeTree

Crates.io License Crates.io Version Documentation

NodeTree 是一个通过进程树创建大型可扩展程序和游戏的框架。每个进程都是完全自主的,能够存储自己的状态或数据,并与其他进程通信。这些进程被称为节点。

⚠️警告⚠️
这是一个依赖于nightly版本的crate。
此crate处于早期开发阶段。请注意可能存在的错误或安全问题。
此crate使用的特定nightly版本是v1.78。

入门!

简单地在项目目录的终端中运行 cargo add node_tree,或者将 node_tree = X.X 添加到您的 cargo.toml 文件中。

为了开始使用 NodeTree 创建Rust程序,我们首先需要创建一个根 Node。为了减少冗余,我们将使用包含的 NodeSys derive宏来实现所需的 DynamicNodeAbstract 特性。然后我们将自己实现 Node 特性。

#![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.
    }
}

最后,为了激活我们的 NodeTree,我们必须实例化根 Node 并将其传递给 NodeTree 构造函数。

// ...previous implementations

fn main() -> () {

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

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

日志记录也受支持。以下是一个带有几个警告和崩溃输出的示例设置。请注意,崩溃头/尾是可定制的,并且输出在真实终端中实际上是彩色的。

/// Root Node
#[derive(Debug, Clone, NodeSys)]
pub struct LoggerNode {
    base: Rc<NodeBase>
}

impl LoggerNode {
    fn new(name: String) -> Hp<Self> {
        Hp::new(LoggerNode { base: NodeBase::new(name) })
    }
}

impl Node for LoggerNode {
    fn ready(self: Hp<Self>) -> () {
        if self.depth() < 3 {
            self.add_child(LoggerNode::new(format!("{}_Node", self.depth() + 1)));
            self.add_child(LoggerNode::new(format!("{}_Node", self.depth() + 1)));
            self.add_child(LoggerNode::new(format!("{}_Node", self.depth() + 1)));
        }
    }

    fn process(self: Hp<Self>, _delta: f32) -> () {
        if self.name() == "3_Node2" && self.parent().unwrap().parent().unwrap().name() == "1_Node" {   // In the real world, you should probably have a better way of doing this.
            self.post_to_log(Log::Warn("Simulating warning!"));
        }

        if self.name() == "3_Node2" && self.parent().unwrap().parent().unwrap().name() == "1_Node2"{
            self.post_to_log(Log::Panic("Simulating panic!"));
        }
    }
}
<22/04/2024 17:25:46 UTC> | [Root/1_Node/2_Node/3_Node2] | WARN | Simulating warning!
<22/04/2024 17:25:46 UTC> | [Root/1_Node/2_Node1/3_Node2] | WARN | Simulating warning!
<22/04/2024 17:25:46 UTC> | [Root/1_Node/2_Node2/3_Node2] | WARN | Simulating warning!
<22/04/2024 17:25:46 UTC> | [Root/1_Node2/2_Node/3_Node2] | PANIC! | Simulating panic!

Unfortunately the program has crashed. Please contact the development team with the following crash report as well as the attachment of the log posted during the time of the crash.

[REPORT START]

Root
├── 1_Node
   ├── 2_Node
   │   ├── 3_Node
   │   ├── 3_Node1
   │   └── 3_Node2
   ├── 2_Node1
   │   ├── 3_Node
   │   ├── 3_Node1
   │   └── 3_Node2
   └── 2_Node2
       ├── 3_Node
       ├── 3_Node1
       └── 3_Node2
├── 1_Node1
   ├── 2_Node
   │   ├── 3_Node
   │   ├── 3_Node1
   │   └── 3_Node2
   ├── 2_Node1
   │   ├── 3_Node
   │   ├── 3_Node1
   │   └── 3_Node2
   └── 2_Node2
       ├── 3_Node
       ├── 3_Node1
       └── 3_Node2
└── 1_Node2
    ├── 2_Node
       ├── 3_Node
       ├── 3_Node1
       └── 3_Node2
    ├── 2_Node1
       ├── 3_Node
       ├── 3_Node1
       └── 3_Node2
    └── 2_Node2
        ├── 3_Node
        ├── 3_Node1
        └── 3_Node2

[Same-Frame Warnings]
3_Node2 - Simulating warning!
3_Node2 - Simulating warning!
3_Node2 - Simulating warning!

[Same-Frame Panics]
3_Node2 - Simulating panic!

[REPORT END]
Time of Crash: 22/04/2024 17:25:46
Exit Code: 1

Goodbye World! (Program Exited)

特性

  • 🏗️ 一个易于抽象的框架,不同进程可以以可扩展的方式相互通信和交互。灵感来源于Godot!
  • ⏯️ 可以暂停和恢复 pause()unpause() NodeTree,并针对树暂停/恢复时调整单个 Node 的行为。
  • 📡 与其他节点通信的各种方法,例如 owner()parent()get_child()children()get_node()
  • 🔗 一种称为 Hp<T> 的抽象智能指针,它隐式克隆以减少语法噪声,并允许低模板化。
  • 👪 具有使用 add_child()remove_child() 管理节点的能力。
  • 📝 包含一个与节点框架深度集成的动态日志系统。
  • 🌲 允许通过节点的 root() 函数直接引用 NodeTree
  • 📚 TODO:在 NodeTree 上托管一个缓存系统,作为确保 Hp<T> 正确性和提高性能的安全接口!
  • 📜 TODO:包括保存和处理单个节点场景的方法,例如方便的视觉宏 Scene!

依赖项

~3MB
~59K SLoC