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