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 """ 手柄输入 """
输入设备枚举
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() 函数将不会被调用。
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()
停止运行引擎
目前退出引擎的方式是极不安全的,正常应该在所有线程和进程退出后再退出引擎
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 基类