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

输入设备枚举

KEYBOARD = <InputDevice.KEYBOARD: 0>

键盘输入

MOUSE = <InputDevice.MOUSE: 1>

鼠标输入

GAMEPAD = <InputDevice.GAMEPAD: 2>

手柄输入

Inherited Members
enum.Enum
name
value
class Engine:
 30class Engine:
 31    """ 引擎类 """
 32    from robotengine.node import Node
 33    def __init__(self, root: Node, frequency: float=180, input_devices: 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()

引擎类

Engine( root: robotengine.node.Node, frequency: float = 180, input_devices: InputDevice = [])
33    def __init__(self, root: Node, frequency: float=180, input_devices: 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)

初始化引擎

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

开始运行引擎

def exit(self):
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()

停止运行引擎

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

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

获取当前帧数

def get_timestamp(self) -> float:
250    def get_timestamp(self) -> float:
251        """ 
252        获取当前时间戳,单位为微秒 
253        """
254        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 基类