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, info
 16from robotengine.signal import Signal
 17import multiprocessing
 18from typing import List, Tuple
 19
 20class InputDevice(Enum):
 21    """ 输入设备枚举 """
 22    KEYBOARD = 0
 23    """ 键盘输入 """
 24    MOUSE = 1
 25    """ 鼠标输入 """
 26    GAMEPAD = 2
 27    """ 手柄输入 """
 28
 29
 30class Engine:
 31    """ 引擎类 """
 32    from robotengine.node import Node
 33    def __init__(self, root: Node, frequency: float=180, input_devices: List[InputDevice]=[]):
 34        """
 35        初始化引擎
 36
 37            :param root (Node): 根节点
 38            :param frequency (int, optional): 影响所有节点的 _process 函数的调用频率。
 39            :param input_devices (list, optional): 输入设备列表,当为空时,节点的 _input() 函数将不会被调用。
 40        """
 41        self.root = root
 42        """ 根节点 """
 43        self.paused = False
 44        """ 是否暂停 """
 45
 46        self._frequency = frequency
 47        self._frame = 0
 48        self._time_frequency = 30
 49
 50        self.input = Input()
 51        """ 输入类, 在 Engine 初始化完成后,每个 Node 都可以通过 self.input 来访问输入类 """
 52
 53        self.engine_exit = Signal()
 54        """ 退出信号,当引擎退出时触发 """
 55
 56        self._initialize()
 57
 58        self._start_timestamp = 0
 59
 60        self._threads = []
 61        self._shutdown = threading.Event()
 62        if input_devices:
 63            if InputDevice.GAMEPAD in input_devices:
 64                self._gamepad_listener = GamepadListener()
 65
 66            self._input_thread = threading.Thread(target=self._do_input, daemon=True, name="EngineInputThread")
 67            self._threads.append(self._input_thread)
 68
 69        self._update_thread = threading.Thread(target=self._do_update, daemon=True, name="EngineUpdateThread")
 70        self._threads.append(self._update_thread)
 71
 72        self._timer_thread = threading.Thread(target=self._do_timer, daemon=True, name="EngineTimerThread")
 73        self._threads.append(self._timer_thread)
 74
 75
 76    def _initialize(self):
 77        from robotengine.node import Node
 78        def init_recursive(node: Node):
 79            for child in node.get_children():
 80                init_recursive(child)
 81            
 82            node.engine = self
 83            node.input = self.input
 84            
 85            node._init()
 86            self.engine_exit.connect(node._on_engine_exit)
 87
 88        def ready_recursive(node: Node):
 89            for child in node.get_children():
 90                ready_recursive(child)
 91            node._do_ready()
 92
 93        init_recursive(self.root)
 94        ready_recursive(self.root)
 95
 96    def _do_update(self):
 97        from robotengine.node import Node
 98        def process_update(delta):
 99            def update_recursive(node: Node, delta):
100                for child in node.get_children():
101                    update_recursive(child, delta)
102                node._update(delta)
103            update_recursive(self.root, delta)
104
105        self._run_loop(1, precise_control=False, process_func=process_update)
106
107    def _do_timer(self):
108        from robotengine.node import Node
109        def process_timer(delta):
110            def timer_recursive(node: Node, delta):
111                for child in node.get_children():
112                    timer_recursive(child, delta)
113                node._timer(delta)
114            timer_recursive(self.root, delta)
115
116        self._run_loop(self._time_frequency, precise_control=False, process_func=process_timer)
117            
118    def _do_input(self):
119        from robotengine.node import Node
120        from robotengine.input import InputEvent
121        def input_recursive(node: Node, event: InputEvent):
122            for child in node.get_children():
123                input_recursive(child, event)
124            node._input(event)
125
126        while not self._shutdown.is_set():
127            if self._gamepad_listener:
128                for _gamepad_event in self._gamepad_listener.listen():
129                    self.input._update(_gamepad_event)
130                    input_recursive(self.root, _gamepad_event)
131
132    def run(self):
133        """ 
134        开始运行引擎 
135        """
136        from robotengine.node import Node
137        def do_process(delta):
138            def process_recursive(node: Node):
139                if self.paused:
140                    if node.process_mode == ProcessMode.WHEN_PAUSED or node.process_mode == ProcessMode.ALWAYS:
141                        node._process(delta)
142                else:
143                    if node.process_mode == ProcessMode.PAUSABLE or node.process_mode == ProcessMode.ALWAYS:
144                        node._process(delta)
145                for child in node.get_children():
146                    process_recursive(child)
147            process_recursive(self.root)
148
149        for _thread in self._threads:
150            _thread.start()
151
152        self._run_loop(self._frequency, precise_control=True, process_func=do_process, main_loop=True)
153
154    def exit(self):
155        """ 
156        停止运行引擎
157
158        目前退出引擎的方式是极不安全的,正常应该在所有线程和进程退出后再退出引擎
159        """
160        import sys
161        import os
162
163        
164        info("正在退出引擎")
165        info("Threading 模块正在运行的线程有: ")
166        for _thread in threading.enumerate():
167            info(f"{_thread.ident} {_thread.name}")
168
169        info("Multiprocessing 模块正在运行的进程有: ")
170        for _process in multiprocessing.active_children():
171            info(f"{_process.pid} {_process.name}")
172
173        info("当前使用强制退出,注意可能导致后续不稳定")
174
175        os._exit(0)  # 强制退出,返回状态码为 0
176
177
178
179        # self._shutdown.set()
180
181    def _do_exit(self) -> None:
182        pass
183        # for _thread in self._threads:
184        #     _thread.join()
185
186        # self.engine_exit.emit()
187
188        # time.sleep(1.0)
189        # exit(0)
190        
191    def _run_loop(self, frequency, precise_control=False, process_func=None, main_loop=False):
192        interval = 1.0 / frequency
193        threshold = 0.03
194
195        last_time = time.perf_counter()
196        next_time = last_time
197        first_frame = True
198
199        if main_loop:
200            self._start_timestamp = time.perf_counter_ns()
201
202        while not self._shutdown.is_set():
203            current_time = time.perf_counter()
204            delta = current_time - last_time
205            last_time = current_time
206
207            if frequency == -1:
208                if not first_frame and process_func:
209                    process_func(delta)
210                    if main_loop:
211                        self._frame += 1
212                else:
213                    first_frame = False
214
215            else:
216                if not first_frame and process_func:
217                    process_func(delta)
218                    if main_loop:
219                        self._frame += 1
220                else:
221                    first_frame = False
222
223                if frequency != -1:
224                    next_time += interval
225                    sleep_time = next_time - time.perf_counter()
226
227                    if precise_control:
228                        if sleep_time > threshold:
229                            time.sleep(sleep_time - threshold)
230
231                        while time.perf_counter() < next_time:
232                            pass
233
234                    else:
235                        if sleep_time > 0:
236                            time.sleep(max(0, sleep_time))
237
238                    if sleep_time < 0 and main_loop:
239                        warning(f"当前帧{self._frame}耗时过长,超时:{-sleep_time*1000:.3f}ms")
240
241        if main_loop:
242            self._do_exit()
243
244    def get_frame(self) -> int:
245        """ 
246        获取当前帧数 
247        """
248        return self._frame
249    
250    def get_timestamp(self) -> float:
251        """ 
252        获取当前时间戳,单位为微秒 
253        """
254        return time.perf_counter_ns() - self._start_timestamp
255    
256    def __del__(self):
257        self.exit()
class InputDevice(enum.Enum):
21class InputDevice(Enum):
22    """ 输入设备枚举 """
23    KEYBOARD = 0
24    """ 键盘输入 """
25    MOUSE = 1
26    """ 鼠标输入 """
27    GAMEPAD = 2
28    """ 手柄输入 """

输入设备枚举

KEYBOARD = <InputDevice.KEYBOARD: 0>

键盘输入

MOUSE = <InputDevice.MOUSE: 1>

鼠标输入

GAMEPAD = <InputDevice.GAMEPAD: 2>

手柄输入

Inherited Members
enum.Enum
name
value
class Engine:
 31class Engine:
 32    """ 引擎类 """
 33    from robotengine.node import Node
 34    def __init__(self, root: Node, frequency: float=180, input_devices: List[InputDevice]=[]):
 35        """
 36        初始化引擎
 37
 38            :param root (Node): 根节点
 39            :param frequency (int, optional): 影响所有节点的 _process 函数的调用频率。
 40            :param 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._time_frequency = 30
 50
 51        self.input = Input()
 52        """ 输入类, 在 Engine 初始化完成后,每个 Node 都可以通过 self.input 来访问输入类 """
 53
 54        self.engine_exit = Signal()
 55        """ 退出信号,当引擎退出时触发 """
 56
 57        self._initialize()
 58
 59        self._start_timestamp = 0
 60
 61        self._threads = []
 62        self._shutdown = threading.Event()
 63        if input_devices:
 64            if InputDevice.GAMEPAD in input_devices:
 65                self._gamepad_listener = GamepadListener()
 66
 67            self._input_thread = threading.Thread(target=self._do_input, daemon=True, name="EngineInputThread")
 68            self._threads.append(self._input_thread)
 69
 70        self._update_thread = threading.Thread(target=self._do_update, daemon=True, name="EngineUpdateThread")
 71        self._threads.append(self._update_thread)
 72
 73        self._timer_thread = threading.Thread(target=self._do_timer, daemon=True, name="EngineTimerThread")
 74        self._threads.append(self._timer_thread)
 75
 76
 77    def _initialize(self):
 78        from robotengine.node import Node
 79        def init_recursive(node: Node):
 80            for child in node.get_children():
 81                init_recursive(child)
 82            
 83            node.engine = self
 84            node.input = self.input
 85            
 86            node._init()
 87            self.engine_exit.connect(node._on_engine_exit)
 88
 89        def ready_recursive(node: Node):
 90            for child in node.get_children():
 91                ready_recursive(child)
 92            node._do_ready()
 93
 94        init_recursive(self.root)
 95        ready_recursive(self.root)
 96
 97    def _do_update(self):
 98        from robotengine.node import Node
 99        def process_update(delta):
100            def update_recursive(node: Node, delta):
101                for child in node.get_children():
102                    update_recursive(child, delta)
103                node._update(delta)
104            update_recursive(self.root, delta)
105
106        self._run_loop(1, precise_control=False, process_func=process_update)
107
108    def _do_timer(self):
109        from robotengine.node import Node
110        def process_timer(delta):
111            def timer_recursive(node: Node, delta):
112                for child in node.get_children():
113                    timer_recursive(child, delta)
114                node._timer(delta)
115            timer_recursive(self.root, delta)
116
117        self._run_loop(self._time_frequency, precise_control=False, process_func=process_timer)
118            
119    def _do_input(self):
120        from robotengine.node import Node
121        from robotengine.input import InputEvent
122        def input_recursive(node: Node, event: InputEvent):
123            for child in node.get_children():
124                input_recursive(child, event)
125            node._input(event)
126
127        while not self._shutdown.is_set():
128            if self._gamepad_listener:
129                for _gamepad_event in self._gamepad_listener.listen():
130                    self.input._update(_gamepad_event)
131                    input_recursive(self.root, _gamepad_event)
132
133    def run(self):
134        """ 
135        开始运行引擎 
136        """
137        from robotengine.node import Node
138        def do_process(delta):
139            def process_recursive(node: Node):
140                if self.paused:
141                    if node.process_mode == ProcessMode.WHEN_PAUSED or node.process_mode == ProcessMode.ALWAYS:
142                        node._process(delta)
143                else:
144                    if node.process_mode == ProcessMode.PAUSABLE or node.process_mode == ProcessMode.ALWAYS:
145                        node._process(delta)
146                for child in node.get_children():
147                    process_recursive(child)
148            process_recursive(self.root)
149
150        for _thread in self._threads:
151            _thread.start()
152
153        self._run_loop(self._frequency, precise_control=True, process_func=do_process, main_loop=True)
154
155    def exit(self):
156        """ 
157        停止运行引擎
158
159        目前退出引擎的方式是极不安全的,正常应该在所有线程和进程退出后再退出引擎
160        """
161        import sys
162        import os
163
164        
165        info("正在退出引擎")
166        info("Threading 模块正在运行的线程有: ")
167        for _thread in threading.enumerate():
168            info(f"{_thread.ident} {_thread.name}")
169
170        info("Multiprocessing 模块正在运行的进程有: ")
171        for _process in multiprocessing.active_children():
172            info(f"{_process.pid} {_process.name}")
173
174        info("当前使用强制退出,注意可能导致后续不稳定")
175
176        os._exit(0)  # 强制退出,返回状态码为 0
177
178
179
180        # self._shutdown.set()
181
182    def _do_exit(self) -> None:
183        pass
184        # for _thread in self._threads:
185        #     _thread.join()
186
187        # self.engine_exit.emit()
188
189        # time.sleep(1.0)
190        # exit(0)
191        
192    def _run_loop(self, frequency, precise_control=False, process_func=None, main_loop=False):
193        interval = 1.0 / frequency
194        threshold = 0.03
195
196        last_time = time.perf_counter()
197        next_time = last_time
198        first_frame = True
199
200        if main_loop:
201            self._start_timestamp = time.perf_counter_ns()
202
203        while not self._shutdown.is_set():
204            current_time = time.perf_counter()
205            delta = current_time - last_time
206            last_time = current_time
207
208            if frequency == -1:
209                if not first_frame and process_func:
210                    process_func(delta)
211                    if main_loop:
212                        self._frame += 1
213                else:
214                    first_frame = False
215
216            else:
217                if not first_frame and process_func:
218                    process_func(delta)
219                    if main_loop:
220                        self._frame += 1
221                else:
222                    first_frame = False
223
224                if frequency != -1:
225                    next_time += interval
226                    sleep_time = next_time - time.perf_counter()
227
228                    if precise_control:
229                        if sleep_time > threshold:
230                            time.sleep(sleep_time - threshold)
231
232                        while time.perf_counter() < next_time:
233                            pass
234
235                    else:
236                        if sleep_time > 0:
237                            time.sleep(max(0, sleep_time))
238
239                    if sleep_time < 0 and main_loop:
240                        warning(f"当前帧{self._frame}耗时过长,超时:{-sleep_time*1000:.3f}ms")
241
242        if main_loop:
243            self._do_exit()
244
245    def get_frame(self) -> int:
246        """ 
247        获取当前帧数 
248        """
249        return self._frame
250    
251    def get_timestamp(self) -> float:
252        """ 
253        获取当前时间戳,单位为微秒 
254        """
255        return time.perf_counter_ns() - self._start_timestamp
256    
257    def __del__(self):
258        self.exit()

引擎类

Engine( root: robotengine.node.Node, frequency: float = 180, input_devices: List[InputDevice] = [])
34    def __init__(self, root: Node, frequency: float=180, input_devices: List[InputDevice]=[]):
35        """
36        初始化引擎
37
38            :param root (Node): 根节点
39            :param frequency (int, optional): 影响所有节点的 _process 函数的调用频率。
40            :param 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._time_frequency = 30
50
51        self.input = Input()
52        """ 输入类, 在 Engine 初始化完成后,每个 Node 都可以通过 self.input 来访问输入类 """
53
54        self.engine_exit = Signal()
55        """ 退出信号,当引擎退出时触发 """
56
57        self._initialize()
58
59        self._start_timestamp = 0
60
61        self._threads = []
62        self._shutdown = threading.Event()
63        if input_devices:
64            if InputDevice.GAMEPAD in input_devices:
65                self._gamepad_listener = GamepadListener()
66
67            self._input_thread = threading.Thread(target=self._do_input, daemon=True, name="EngineInputThread")
68            self._threads.append(self._input_thread)
69
70        self._update_thread = threading.Thread(target=self._do_update, daemon=True, name="EngineUpdateThread")
71        self._threads.append(self._update_thread)
72
73        self._timer_thread = threading.Thread(target=self._do_timer, daemon=True, name="EngineTimerThread")
74        self._threads.append(self._timer_thread)

初始化引擎

:param root (Node): 根节点
:param frequency (int, optional): 影响所有节点的 _process 函数的调用频率。
:param input_devices (list, optional): 输入设备列表,当为空时,节点的 _input() 函数将不会被调用。
root

根节点

paused

是否暂停

input

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

engine_exit

退出信号,当引擎退出时触发

def run(self):
133    def run(self):
134        """ 
135        开始运行引擎 
136        """
137        from robotengine.node import Node
138        def do_process(delta):
139            def process_recursive(node: Node):
140                if self.paused:
141                    if node.process_mode == ProcessMode.WHEN_PAUSED or node.process_mode == ProcessMode.ALWAYS:
142                        node._process(delta)
143                else:
144                    if node.process_mode == ProcessMode.PAUSABLE or node.process_mode == ProcessMode.ALWAYS:
145                        node._process(delta)
146                for child in node.get_children():
147                    process_recursive(child)
148            process_recursive(self.root)
149
150        for _thread in self._threads:
151            _thread.start()
152
153        self._run_loop(self._frequency, precise_control=True, process_func=do_process, main_loop=True)

开始运行引擎

def exit(self):
155    def exit(self):
156        """ 
157        停止运行引擎
158
159        目前退出引擎的方式是极不安全的,正常应该在所有线程和进程退出后再退出引擎
160        """
161        import sys
162        import os
163
164        
165        info("正在退出引擎")
166        info("Threading 模块正在运行的线程有: ")
167        for _thread in threading.enumerate():
168            info(f"{_thread.ident} {_thread.name}")
169
170        info("Multiprocessing 模块正在运行的进程有: ")
171        for _process in multiprocessing.active_children():
172            info(f"{_process.pid} {_process.name}")
173
174        info("当前使用强制退出,注意可能导致后续不稳定")
175
176        os._exit(0)  # 强制退出,返回状态码为 0
177
178
179
180        # self._shutdown.set()

停止运行引擎

目前退出引擎的方式是极不安全的,正常应该在所有线程和进程退出后再退出引擎

def get_frame(self) -> int:
245    def get_frame(self) -> int:
246        """ 
247        获取当前帧数 
248        """
249        return self._frame

获取当前帧数

def get_timestamp(self) -> float:
251    def get_timestamp(self) -> float:
252        """ 
253        获取当前时间戳,单位为微秒 
254        """
255        return time.perf_counter_ns() - self._start_timestamp

获取当前时间戳,单位为微秒

class Engine.Node:
 49class Node:
 50    """ Node 基类 """
 51    from robotengine.input import InputEvent
 52
 53    def __init__(self, name="Node"):
 54        """ 
 55        初始化节点
 56
 57            :param name: 节点名称
 58        """
 59        self.name = name
 60        """ 节点名称 """
 61        self.owner = None
 62        """
 63        节点的所有者
 64
 65        注意:owner的指定与节点的创建顺序有关,例如:
 66
 67            A = Node("A")
 68            B = Node("B")
 69            C = Node("C")
 70            D = Node("D")
 71
 72            A.add_child(B)
 73            A.add_child(C)
 74            B.add_child(D)
 75
 76        此时,A的子节点为B、C,B的子节点为D,B、C、D的owner均为A。
 77
 78        而如果继续添加节点:
 79
 80            E = Node("E")
 81            E.add_child(A)
 82
 83        此时,E的子节点为A,A的owner为E,但是B、C、D的owner仍然为A。
 84        """
 85        self._children = []
 86        self._parent = None
 87
 88        # 全局属性
 89        from robotengine.engine import Engine
 90        from robotengine.input import Input
 91
 92        self.engine: Engine = None
 93        """ 节点的 Engine 实例 """
 94        self.input: Input = None
 95        """ 节点的 Input 实例 """
 96
 97        self.process_mode: ProcessMode = ProcessMode.PAUSABLE
 98        """ 节点的process模式 """
 99
100        # 信号
101        self.ready: Signal = Signal()
102        """ 信号,节点 _ready 执行结束后触发 """
103
104    def add_child(self, child_node):
105        """ 
106        添加子节点 
107        
108            :param child_node: 子节点
109        """
110        if child_node._parent is not None:
111            error(f"{self.name}{child_node.name} 已经有父节点!")
112            return
113        for child in self._children:
114            if child.name == child_node.name:
115                error(f"节点 {self.name} 已经有同名子节点{child_node.name} !")
116                return
117
118        child_node._parent = self  # 设置子节点的 _parent 属性
119        if self.owner is not None:
120            child_node.owner = self.owner
121        else:
122            child_node.owner = self
123
124        self._children.append(child_node)
125
126    def remove_child(self, child_node):
127        """ 
128        移除子节点 
129        
130            :param child_node: 子节点
131        """
132        if child_node in self._children:
133            self._children.remove(child_node)
134            child_node._parent = None  # 解除 _parent 绑定
135        else:
136            warning(f"{self.name}{child_node.name} 并未被找到,未执行移除操作")
137
138    def _update(self, delta) -> None:
139        """ 
140        引擎内部的节点更新函数,会以很低的频率调用 
141        """
142        pass
143
144    def _timer(self, delta) -> None:
145        """ 
146        引擎内部的定时器更新函数,负责 Timer 相关的更新 
147        """
148        pass
149
150    def _init(self) -> None:
151        """ 
152        初始化节点,会在 _ready() 之前被调用,尽量不要覆写此函数 
153        """
154        pass
155    
156    def _ready(self) -> None:
157        """ 
158        节点 _ready 函数,会在 _init() 之后被调用,可以在此函数中执行一些初始化操作 
159        """
160        pass
161
162    def _do_ready(self) -> None:
163        self._ready()
164        self.ready.emit()
165
166    def _process(self, delta) -> None:
167        """ 
168        节点 process 函数,会根据 Engine 中设置的 frequency 进行连续调用 
169        """
170        pass
171
172    def _input(self, event: InputEvent) -> None:
173        """ 
174        节点 input 函数,会在接收到输入事件时被调用 
175        
176            :param event: 输入事件
177        """
178        pass
179
180    def _on_engine_exit(self) -> None:
181        """ 引擎退出时调用的函数 """
182        pass
183
184    def get_child(self, name) -> "Node":
185        """ 
186        通过节点名称获取子节点 
187        
188            :param name: 节点名称
189        """
190        for child in self._children:
191            if child.name == name:
192                return child
193        return None
194    
195    def get_children(self) -> List["Node"]:
196        """ 
197        获取所有子节点 
198        """
199        return self._children
200    
201    def get_parent(self) -> "Node":
202        """ 
203        获取父节点 
204        """
205        return self._parent
206    
207    def print_tree(self):
208        """ 
209        打印节点树 
210        """
211        def print_recursive(node: "Node", prefix="", is_last=False, is_root=False):
212            if is_root:
213                print(f"{node}")  # 根节点
214            else:
215                if is_last:
216                    print(f"{prefix}└── {node}")  # 最后一个子节点
217                else:
218                    print(f"{prefix}├── {node}")  # 其他子节点
219
220            for i, child in enumerate(node.get_children()):
221                is_last_child = (i == len(node.get_children()) - 1)
222                print_recursive(child, prefix + "    ", is_last=is_last_child, is_root=False)
223
224        print_recursive(self, is_last=False, is_root=True)
225    
226    def rbprint(self, str, end="\n"):
227        """
228        打印带有帧号的字符串
229        
230            :param str: 要打印的字符串
231            :param end: 结束符
232        """
233        print(f"[{self.engine.get_frame()}] {str}", end=end)
234
235    def __repr__(self):
236        return f"{self.name}"

Node 基类