robotengine.engine

引擎是 robotengine 的核心部分,负责管理节点的初始化、运行和更新。

Engine 同时还存储了一些全局变量,如帧数 frame 和时间戳 timestamp等。

在 Node 类中可以通过使用 self.engine 来访问引擎。

  1""" 
  2
  3引擎是 robotengine 的核心部分,负责管理节点的初始化、运行和更新。
  4
  5Engine 同时还存储了一些全局变量,如帧数 frame 和时间戳 timestamp等。
  6
  7在 Node 类中可以通过使用 self.engine 来访问引擎。
  8
  9"""
 10import threading
 11import time
 12from enum import Enum
 13from robotengine.input import Input, GamepadListener
 14from robotengine.node import ProcessMode
 15from robotengine.tools import warning, error
 16
 17class InputDevice(Enum):
 18    """ 输入设备枚举 """
 19    KEYBOARD = 0
 20    """ 键盘输入 """
 21    MOUSE = 1
 22    """ 鼠标输入 """
 23    GAMEPAD = 2
 24    """ 手柄输入 """
 25
 26
 27class Engine:
 28    """ 引擎类 """
 29    from robotengine.node import Node
 30    def __init__(self, root: Node, frequency: float=240, input_devices: InputDevice=[]):
 31        """
 32        初始化引擎
 33
 34        参数:
 35
 36            root (Node): 根节点
 37
 38            frequency (int, optional): 影响所有节点的 _process 函数的调用频率。默认值为 240。
 39
 40            input_devices (list, optional): 输入设备列表,当为空时,节点的 _input() 函数将不会被调用。默认值为 []。
 41        """
 42        self.root = root
 43        """ 根节点 """
 44        self.paused = False
 45        """ 是否暂停 """
 46
 47        self._frequency = frequency
 48        self._frame = 0
 49        self._timestamp = 0.0
 50
 51        self._time_frequency = 30
 52
 53        self.input = Input()
 54        """ 输入类, 在 Engine 初始化完成后,每个 Node 都可以通过 self.input 来访问输入类 """
 55
 56        self._initialize()
 57
 58        self._shutdown = threading.Event()
 59        if input_devices:
 60            if InputDevice.GAMEPAD in input_devices:
 61                self._gamepad_listener = GamepadListener()
 62
 63            self._input_thread = threading.Thread(target=self._input, daemon=True)
 64            self._input_thread.start()
 65
 66        self._update_thread = threading.Thread(target=self._update, daemon=True)
 67        self._update_thread.start()
 68
 69        self._timer_thread = threading.Thread(target=self._timer, daemon=True)
 70        self._timer_thread.start()
 71
 72    def _initialize(self):
 73        from robotengine.node import Node
 74        def init_recursive(node: Node):
 75            for child in node.get_children():
 76                init_recursive(child)  # 先初始化子节点
 77            
 78            node.engine = self  # 设置引擎引用
 79            node.input = self.input  # 设置输入引用
 80            
 81            node._init()  # 当前节点初始化
 82
 83        def ready_recursive(node: Node):
 84            for child in node.get_children():
 85                ready_recursive(child)  # 子节点准备完成
 86            node._ready_execute()
 87
 88        init_recursive(self.root)
 89        ready_recursive(self.root)
 90
 91    def _process_update(self, delta):
 92        from robotengine.node import Node
 93        def update_recursive(node: Node, delta):
 94            for child in node.get_children():
 95                update_recursive(child, delta)
 96            node._update(delta)
 97        update_recursive(self.root, delta)
 98
 99    def _update(self):
100        self._run_loop(1, precise_control=False, process_func=self._process_update)
101
102    def _process_timer(self, delta):
103        from robotengine.node import Node
104        def timer_recursive(node: Node, delta):
105            for child in node.get_children():
106                timer_recursive(child, delta)
107            node._timer(delta)
108        timer_recursive(self.root, delta)
109
110    def _timer(self):
111        self._run_loop(self._time_frequency, precise_control=False, process_func=self._process_timer)
112            
113    def _input(self):
114        from robotengine.node import Node
115        from robotengine.input import InputEvent
116        def input_recursive(node: Node, event: InputEvent):
117            for child in node.get_children():
118                input_recursive(child, event)
119            node._input(event)
120
121        while not self._shutdown.is_set():
122            if self._gamepad_listener:
123                for _gamepad_event in self._gamepad_listener.listen():
124                    self.input.update(_gamepad_event)
125
126                    input_recursive(self.root, _gamepad_event)
127
128    def _process(self, delta):
129        from robotengine.node import Node
130        def process_recursive(node: Node):
131            if self.paused:
132                if node.process_mode == ProcessMode.WHEN_PAUSED or node.process_mode == ProcessMode.ALWAYS:
133                    node._process(delta)
134            else:
135                if node.process_mode == ProcessMode.PAUSABLE or node.process_mode == ProcessMode.ALWAYS:
136                    node._process(delta)
137            for child in node.get_children():
138                process_recursive(child)
139
140        process_recursive(self.root)
141
142    def run(self):
143        """ 开始运行引擎 """
144        self._run_loop(self._frequency, precise_control=True, process_func=self._process, main_loop=True)
145
146    def stop(self):
147        """ 停止运行引擎 """
148        self._shutdown.set()
149
150    def _run_loop(self, frequency, precise_control=False, process_func=None, main_loop=False):
151        interval = 1.0 / frequency
152        threshold = 0.03
153
154        last_time = time.perf_counter()
155        next_time = last_time
156        first_frame = True
157
158        while not self._shutdown.is_set():
159            current_time = time.perf_counter()
160            delta = current_time - last_time
161            last_time = current_time
162
163            if not first_frame and process_func:
164                process_func(delta)
165                if main_loop:
166                    self._frame += 1
167                    self._timestamp += delta
168            else:
169                first_frame = False
170
171            next_time += interval
172            sleep_time = next_time - time.perf_counter()
173
174            if precise_control:
175                if sleep_time > threshold:
176                    time.sleep(sleep_time - threshold)
177
178                while time.perf_counter() < next_time:
179                    pass
180
181            else:
182                if sleep_time > 0:
183                    time.sleep(max(0, sleep_time))
184
185            if sleep_time <= 0 and main_loop:
186                warning(f"当前帧{self._frame}耗时过长,耗时{delta:.5f}s")
187
188            
189    def get_frame(self) -> int:
190        """获取当前帧数"""
191        return self._frame
192    
193    def get_timestamp(self) -> float:
194        """获取当前时间戳"""
195        return self._timestamp
196
197    def print_tree(self):
198        """打印节点树"""
199        from .node import Node
200        def print_recursive(node: Node, prefix="", is_last=False, is_root=False):
201            if is_root:
202                print(f"{node}")  # 根节点
203            else:
204                if is_last:
205                    print(f"{prefix}└── {node}")  # 最后一个子节点
206                else:
207                    print(f"{prefix}├── {node}")  # 其他子节点
208
209            for i, child in enumerate(node.get_children()):
210                is_last_child = (i == len(node.get_children()) - 1)
211                print_recursive(child, prefix + "    ", is_last=is_last_child, is_root=False)
212
213        print_recursive(self.root, is_last=False, is_root=True)
class InputDevice(enum.Enum):
18class InputDevice(Enum):
19    """ 输入设备枚举 """
20    KEYBOARD = 0
21    """ 键盘输入 """
22    MOUSE = 1
23    """ 鼠标输入 """
24    GAMEPAD = 2
25    """ 手柄输入 """

输入设备枚举

KEYBOARD = <InputDevice.KEYBOARD: 0>

键盘输入

MOUSE = <InputDevice.MOUSE: 1>

鼠标输入

GAMEPAD = <InputDevice.GAMEPAD: 2>

手柄输入

Inherited Members
enum.Enum
name
value
class Engine:
 28class Engine:
 29    """ 引擎类 """
 30    from robotengine.node import Node
 31    def __init__(self, root: Node, frequency: float=240, input_devices: InputDevice=[]):
 32        """
 33        初始化引擎
 34
 35        参数:
 36
 37            root (Node): 根节点
 38
 39            frequency (int, optional): 影响所有节点的 _process 函数的调用频率。默认值为 240。
 40
 41            input_devices (list, optional): 输入设备列表,当为空时,节点的 _input() 函数将不会被调用。默认值为 []。
 42        """
 43        self.root = root
 44        """ 根节点 """
 45        self.paused = False
 46        """ 是否暂停 """
 47
 48        self._frequency = frequency
 49        self._frame = 0
 50        self._timestamp = 0.0
 51
 52        self._time_frequency = 30
 53
 54        self.input = Input()
 55        """ 输入类, 在 Engine 初始化完成后,每个 Node 都可以通过 self.input 来访问输入类 """
 56
 57        self._initialize()
 58
 59        self._shutdown = threading.Event()
 60        if input_devices:
 61            if InputDevice.GAMEPAD in input_devices:
 62                self._gamepad_listener = GamepadListener()
 63
 64            self._input_thread = threading.Thread(target=self._input, daemon=True)
 65            self._input_thread.start()
 66
 67        self._update_thread = threading.Thread(target=self._update, daemon=True)
 68        self._update_thread.start()
 69
 70        self._timer_thread = threading.Thread(target=self._timer, daemon=True)
 71        self._timer_thread.start()
 72
 73    def _initialize(self):
 74        from robotengine.node import Node
 75        def init_recursive(node: Node):
 76            for child in node.get_children():
 77                init_recursive(child)  # 先初始化子节点
 78            
 79            node.engine = self  # 设置引擎引用
 80            node.input = self.input  # 设置输入引用
 81            
 82            node._init()  # 当前节点初始化
 83
 84        def ready_recursive(node: Node):
 85            for child in node.get_children():
 86                ready_recursive(child)  # 子节点准备完成
 87            node._ready_execute()
 88
 89        init_recursive(self.root)
 90        ready_recursive(self.root)
 91
 92    def _process_update(self, delta):
 93        from robotengine.node import Node
 94        def update_recursive(node: Node, delta):
 95            for child in node.get_children():
 96                update_recursive(child, delta)
 97            node._update(delta)
 98        update_recursive(self.root, delta)
 99
100    def _update(self):
101        self._run_loop(1, precise_control=False, process_func=self._process_update)
102
103    def _process_timer(self, delta):
104        from robotengine.node import Node
105        def timer_recursive(node: Node, delta):
106            for child in node.get_children():
107                timer_recursive(child, delta)
108            node._timer(delta)
109        timer_recursive(self.root, delta)
110
111    def _timer(self):
112        self._run_loop(self._time_frequency, precise_control=False, process_func=self._process_timer)
113            
114    def _input(self):
115        from robotengine.node import Node
116        from robotengine.input import InputEvent
117        def input_recursive(node: Node, event: InputEvent):
118            for child in node.get_children():
119                input_recursive(child, event)
120            node._input(event)
121
122        while not self._shutdown.is_set():
123            if self._gamepad_listener:
124                for _gamepad_event in self._gamepad_listener.listen():
125                    self.input.update(_gamepad_event)
126
127                    input_recursive(self.root, _gamepad_event)
128
129    def _process(self, delta):
130        from robotengine.node import Node
131        def process_recursive(node: Node):
132            if self.paused:
133                if node.process_mode == ProcessMode.WHEN_PAUSED or node.process_mode == ProcessMode.ALWAYS:
134                    node._process(delta)
135            else:
136                if node.process_mode == ProcessMode.PAUSABLE or node.process_mode == ProcessMode.ALWAYS:
137                    node._process(delta)
138            for child in node.get_children():
139                process_recursive(child)
140
141        process_recursive(self.root)
142
143    def run(self):
144        """ 开始运行引擎 """
145        self._run_loop(self._frequency, precise_control=True, process_func=self._process, main_loop=True)
146
147    def stop(self):
148        """ 停止运行引擎 """
149        self._shutdown.set()
150
151    def _run_loop(self, frequency, precise_control=False, process_func=None, main_loop=False):
152        interval = 1.0 / frequency
153        threshold = 0.03
154
155        last_time = time.perf_counter()
156        next_time = last_time
157        first_frame = True
158
159        while not self._shutdown.is_set():
160            current_time = time.perf_counter()
161            delta = current_time - last_time
162            last_time = current_time
163
164            if not first_frame and process_func:
165                process_func(delta)
166                if main_loop:
167                    self._frame += 1
168                    self._timestamp += delta
169            else:
170                first_frame = False
171
172            next_time += interval
173            sleep_time = next_time - time.perf_counter()
174
175            if precise_control:
176                if sleep_time > threshold:
177                    time.sleep(sleep_time - threshold)
178
179                while time.perf_counter() < next_time:
180                    pass
181
182            else:
183                if sleep_time > 0:
184                    time.sleep(max(0, sleep_time))
185
186            if sleep_time <= 0 and main_loop:
187                warning(f"当前帧{self._frame}耗时过长,耗时{delta:.5f}s")
188
189            
190    def get_frame(self) -> int:
191        """获取当前帧数"""
192        return self._frame
193    
194    def get_timestamp(self) -> float:
195        """获取当前时间戳"""
196        return self._timestamp
197
198    def print_tree(self):
199        """打印节点树"""
200        from .node import Node
201        def print_recursive(node: Node, prefix="", is_last=False, is_root=False):
202            if is_root:
203                print(f"{node}")  # 根节点
204            else:
205                if is_last:
206                    print(f"{prefix}└── {node}")  # 最后一个子节点
207                else:
208                    print(f"{prefix}├── {node}")  # 其他子节点
209
210            for i, child in enumerate(node.get_children()):
211                is_last_child = (i == len(node.get_children()) - 1)
212                print_recursive(child, prefix + "    ", is_last=is_last_child, is_root=False)
213
214        print_recursive(self.root, is_last=False, is_root=True)

引擎类

Engine( root: robotengine.node.Node, frequency: float = 240, input_devices: InputDevice = [])
31    def __init__(self, root: Node, frequency: float=240, input_devices: InputDevice=[]):
32        """
33        初始化引擎
34
35        参数:
36
37            root (Node): 根节点
38
39            frequency (int, optional): 影响所有节点的 _process 函数的调用频率。默认值为 240。
40
41            input_devices (list, optional): 输入设备列表,当为空时,节点的 _input() 函数将不会被调用。默认值为 []。
42        """
43        self.root = root
44        """ 根节点 """
45        self.paused = False
46        """ 是否暂停 """
47
48        self._frequency = frequency
49        self._frame = 0
50        self._timestamp = 0.0
51
52        self._time_frequency = 30
53
54        self.input = Input()
55        """ 输入类, 在 Engine 初始化完成后,每个 Node 都可以通过 self.input 来访问输入类 """
56
57        self._initialize()
58
59        self._shutdown = threading.Event()
60        if input_devices:
61            if InputDevice.GAMEPAD in input_devices:
62                self._gamepad_listener = GamepadListener()
63
64            self._input_thread = threading.Thread(target=self._input, daemon=True)
65            self._input_thread.start()
66
67        self._update_thread = threading.Thread(target=self._update, daemon=True)
68        self._update_thread.start()
69
70        self._timer_thread = threading.Thread(target=self._timer, daemon=True)
71        self._timer_thread.start()

初始化引擎

参数:

root (Node): 根节点

frequency (int, optional): 影响所有节点的 _process 函数的调用频率。默认值为 240。

input_devices (list, optional): 输入设备列表,当为空时,节点的 _input() 函数将不会被调用。默认值为 []。
root

根节点

paused

是否暂停

input

输入类, 在 Engine 初始化完成后,每个 Node 都可以通过 self.input 来访问输入类

def run(self):
143    def run(self):
144        """ 开始运行引擎 """
145        self._run_loop(self._frequency, precise_control=True, process_func=self._process, main_loop=True)

开始运行引擎

def stop(self):
147    def stop(self):
148        """ 停止运行引擎 """
149        self._shutdown.set()

停止运行引擎

def get_frame(self) -> int:
190    def get_frame(self) -> int:
191        """获取当前帧数"""
192        return self._frame

获取当前帧数

def get_timestamp(self) -> float:
194    def get_timestamp(self) -> float:
195        """获取当前时间戳"""
196        return self._timestamp

获取当前时间戳

def print_tree(self):
198    def print_tree(self):
199        """打印节点树"""
200        from .node import Node
201        def print_recursive(node: Node, prefix="", is_last=False, is_root=False):
202            if is_root:
203                print(f"{node}")  # 根节点
204            else:
205                if is_last:
206                    print(f"{prefix}└── {node}")  # 最后一个子节点
207                else:
208                    print(f"{prefix}├── {node}")  # 其他子节点
209
210            for i, child in enumerate(node.get_children()):
211                is_last_child = (i == len(node.get_children()) - 1)
212                print_recursive(child, prefix + "    ", is_last=is_last_child, is_root=False)
213
214        print_recursive(self.root, is_last=False, is_root=True)

打印节点树

class Engine.Node:
 49class Node:
 50    """ Node 基类 """
 51    from robotengine.input import InputEvent
 52
 53    def __init__(self, name="Node"):
 54        """ 初始化节点, 需要指定节点名称 """
 55        self.name = name
 56        """ 节点名称 """
 57        self.owner = None
 58        """
 59        节点的所有者
 60
 61        注意:owner的指定与节点的创建顺序有关,例如:
 62
 63            A = Node("A")
 64            B = Node("B")
 65            C = Node("C")
 66            D = Node("D")
 67
 68            A.add_child(B)
 69            A.add_child(C)
 70            B.add_child(D)
 71
 72        此时,A的子节点为B、C,B的子节点为D,B、C、D的owner均为A。
 73
 74        而如果继续添加节点:
 75
 76            E = Node("E")
 77            E.add_child(A)
 78
 79        此时,E的子节点为A,A的owner为E,但是B、C、D的owner仍然为A。
 80        """
 81        self._children = []
 82        self._parent = None
 83
 84        # 全局属性
 85        from robotengine.engine import Engine
 86        from robotengine.input import Input
 87
 88        self.engine: Engine = None
 89        """ 节点的 Engine 实例 """
 90        self.input: Input = None
 91        """ 节点的 Input 实例 """
 92
 93        self.process_mode: ProcessMode = ProcessMode.PAUSABLE
 94        """ 节点的process模式 """
 95
 96        # 信号
 97        self.ready: Signal = Signal()
 98        """ 信号,节点 _ready 执行结束后触发 """
 99
100    def add_child(self, child_node):
101        """ 添加子节点 """
102        if child_node._parent is not None:
103            error(f"{self.name}{child_node.name} 已经有父节点!")
104            return
105        for child in self._children:
106            if child.name == child_node.name:
107                error(f"节点 {self.name} 已经有同名子节点{child_node.name} !")
108                return
109
110        child_node._parent = self  # 设置子节点的 _parent 属性
111        if self.owner is not None:
112            child_node.owner = self.owner
113        else:
114            child_node.owner = self
115
116        self._children.append(child_node)
117
118    def remove_child(self, child_node):
119        """ 移除子节点 """
120        if child_node in self._children:
121            self._children.remove(child_node)
122            child_node._parent = None  # 解除 _parent 绑定
123        else:
124            warning(f"{self.name}{child_node.name} 并未被找到,未执行移除操作")
125
126    def _update(self, delta) -> None:
127        """ 引擎内部的节点更新函数,会以很低的频率调用 """
128        pass
129
130    def _timer(self, delta) -> None:
131        """ 引擎内部的定时器更新函数,负责 Timer 相关的更新 """
132        pass
133
134    def _init(self) -> None:
135        """ 初始化节点,会在 _ready() 之前被调用,尽量不要覆写此函数 """
136        pass
137    
138    def _ready(self) -> None:
139        """ 节点 _ready 函数,会在 _init() 之后被调用,可以在此函数中执行一些初始化操作 """
140        pass
141
142    def _ready_execute(self) -> None:
143        self._ready()
144        self.ready.emit()
145
146    def _process(self, delta) -> None:
147        """ 节点 process 函数,会根据 Engine 中设置的 frequency 进行连续调用 """
148        pass
149
150    def _input(self, event: InputEvent) -> None:
151        """ 节点 input 函数,会在接收到输入事件时被调用 """
152        pass
153
154    def get_child(self, name) -> "Node":
155        """ 通过节点名称获取子节点 """
156        for child in self._children:
157            if child.name == name:
158                return child
159        return None
160    
161    def get_children(self) -> List["Node"]:
162        """ 获取所有子节点 """
163        return self._children
164    
165    def get_parent(self) -> "Node":
166        """ 获取父节点 """
167        return self._parent
168    
169    def rbprint(self, str, end="\n"):
170        print(f"[{self.engine.get_frame()}] {str}", end=end)
171
172    def __repr__(self):
173        return f"{self.name}"

Node 基类