diff --git a/Difficult_Rocket/__init__.py b/Difficult_Rocket/__init__.py index f654bda..fefefe9 100644 --- a/Difficult_Rocket/__init__.py +++ b/Difficult_Rocket/__init__.py @@ -5,6 +5,7 @@ # ------------------------------- import sys +import time import importlib import traceback import contextlib diff --git a/Difficult_Rocket/client/__init__.py b/Difficult_Rocket/client/__init__.py index 14568ae..16a28d2 100644 --- a/Difficult_Rocket/client/__init__.py +++ b/Difficult_Rocket/client/__init__.py @@ -210,8 +210,6 @@ class ClientWindow(Window): def start_game(self) -> None: self.set_icon(pyglet.image.load('./textures/icon.png')) - self.run_input = True - self.read_input() try: pyglet.app.event_loop.run(1 / self.main_config['runtime']['fps']) except KeyboardInterrupt: @@ -221,22 +219,6 @@ class ClientWindow(Window): self.dispatch_event("on_close") sys.exit(0) - @new_thread('window read_input', daemon=True) - def read_input(self): - self.logger.debug('read_input start') - while self.run_input: - try: - get = input(">") - except (EOFError, KeyboardInterrupt): - self.run_input = False - break - if get in ('', ' ', '\n', '\r'): - continue - if get == 'stop': - self.run_input = False - self.command_list.append(get) - self.logger.debug('read_input end') - @new_thread('window save_info') def save_info(self): self.logger.info(tr().client.config.save.start()) @@ -266,10 +248,8 @@ class ClientWindow(Window): @_call_screen_after def on_draw(self, *dt): - if self.command_list: - for command in self.command_list: - self.on_command(line.CommandText(command)) - self.command_list.pop(0) + while command := self.game.console.get_command(): + self.on_command(line.CommandText(command)) pyglet.gl.glClearColor(0.1, 0, 0, 0.0) self.clear() self.draw_update(float(self.SPF)) @@ -310,25 +290,26 @@ class ClientWindow(Window): @_call_screen_after def on_command(self, command: line.CommandText): - print(command.re_match('/')) + print(command.find('/')) self.logger.info(tr().window.command.text().format(command)) - if command.re_match('stop'): + if command.find('stop'): # self.dispatch_event('on_exit') + print("command stop!") pyglet.app.platform_event_loop.stop() self.dispatch_event('on_close', 'command') # source = command - elif command.re_match('fps'): - if command.re_match('log'): + elif command.find('fps'): + if command.find('log'): self.logger.debug(self.fps_log.fps_list) - elif command.re_match('max'): + elif command.find('max'): self.logger.info(self.fps_log.max_fps) self.command.push_line(self.fps_log.max_fps, block_line=True) - elif command.re_match('min'): + elif command.find('min'): self.logger.info(self.fps_log.min_fps) self.command.push_line(self.fps_log.min_fps, block_line=True) - elif command.re_match('default'): + elif command.find('default'): self.set_size(int(self.main_config['window_default']['width']), int(self.main_config['window_default']['height'])) - elif command.re_match('lang'): + elif command.find('lang'): try: lang = command.text[5:] tr._language = lang @@ -439,10 +420,13 @@ class ClientWindow(Window): self.game.dispatch_event('on_close', game=self.game, client=self, source=source) self.logger.info(tr().window.game.stop_get().format(tr().game[source]())) self.logger.info(tr().window.game.stop()) - self.fps_log.check_list = False + # self.fps_log.check_list = False DR_runtime.running = False if self.run_input: self.run_input = False self.save_info() super().on_close() self.logger.info(tr().window.game.end()) + + +ClientWindow.register_event_type("on_command") diff --git a/Difficult_Rocket/command/api.py b/Difficult_Rocket/command/api.py index 0ddbf27..b9b770a 100644 --- a/Difficult_Rocket/command/api.py +++ b/Difficult_Rocket/command/api.py @@ -43,7 +43,14 @@ class CommandText: break i += 1 - def find(self, text: str) -> Union[str, bool]: + def find(self, text: str) -> bool: + find = self.text.find(text) + if find != -1: + self.text = self.text[find + len(text):] + return True + return False + + def re_find(self, text: str) -> Union[str, bool]: return finding.group() if (finding := re.match(text, self.text)) else False def re_match(self, text: str) -> bool: diff --git a/Difficult_Rocket/main.py b/Difficult_Rocket/main.py index 1318e7b..383117c 100644 --- a/Difficult_Rocket/main.py +++ b/Difficult_Rocket/main.py @@ -11,10 +11,10 @@ github: @shenjackyuanjie gitee: @shenjackyuanjie """ -import os import sys import time import logging +import traceback import importlib import importlib.util import logging.config @@ -22,59 +22,83 @@ import multiprocessing from io import StringIO from pathlib import Path -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List, Optional, Dict, TypeVar if __name__ == '__main__': # been start will not run this sys.path.append('/bin/libs') sys.path.append('/bin') -from Difficult_Rocket import client, server, DR_option, DR_runtime if TYPE_CHECKING: from Difficult_Rocket.api.mod import ModInfo -from Difficult_Rocket.crash import write_info_to_cache +else: + ModInfo = TypeVar('ModInfo') from Difficult_Rocket.utils import tools +from Difficult_Rocket.api.types import Options from Difficult_Rocket.utils.translate import tr +from Difficult_Rocket.utils.thread import new_thread +from Difficult_Rocket.crash import write_info_to_cache +from Difficult_Rocket import client, server, DR_option, DR_runtime -class Game: - def __init__(self): - # basic config - self.on_python_v_info = sys.version_info - self.on_python_v = sys.version.split(' ')[0] - self.start_time = time.strftime('%Y-%m-%d %H-%M-%S', time.gmtime(time.time())) - # lang_config - self.language = tools.load_file('configs/main.toml', 'runtime')['language'] - DR_option.language = self.language - # logging config - log_config = tools.load_file('configs/logger.toml') - file_name = log_config['handlers']['file']['filename'] - del log_config['handlers']['file']['datefmt'] - log_config['handlers']['file']['filename'] = f'logs/{file_name.format(self.start_time)}' - try: - logging.config.dictConfig(log_config) - self.logger = logging.getLogger('main') - except ValueError: # it should be no 'logs/' folder - os.mkdir('logs') - logging.config.dictConfig(log_config) - self.logger = logging.getLogger('main') +class Console(Options): + name = 'python stdin console' + + running: bool = False + + @new_thread('python console', daemon=True, log_thread=True) + def main(self): + while self.running: + try: + get_str = input('>>>') + except (EOFError, KeyboardInterrupt): + get_str = 'stop' + self.caches.append(get_str) + if get_str == 'stop': + self.running = False + break + + def start(self): + self.running = True + self.caches: List[str] = [] + self.main() + + def stop(self): + self.running = False + + def get_command(self) -> str: + return self.caches.pop(0) + + +class Game(Options): + name = 'MainGame' + + client: client.Client + server: server.Server + console: Console + console_class: Console = Console + + main_config: Dict + logging_config: Dict + logger: logging.Logger + + mod_module: List["ModInfo"] + + def init_logger(self) -> None: + log_path = self.logging_config['handlers']['file']['filename'] + log_path = Path(f"logs/{log_path.format(time.strftime('%Y-%m-%d %H-%M-%S' , time.gmtime(DR_runtime.start_time_ns / 1000_000_000)))}") + mkdir = False + if not Path("logs/").exists(): + log_path.mkdir(parents=True) + mkdir = True + self.logging_config['handlers']['file']['filename'] = str(log_path.absolute()) + logging.config.dictConfig(self.logging_config) + self.logger = logging.getLogger('main') + if mkdir: self.logger.info(tr().main.logger.mkdir()) - self.logger.info(tr().language_set_to()) - self.logger.info(tr().main.logger.created()) - # version check - self.log_env() - self.python_version_check() - self.loaded_mods = [] - # self.client = client.Client - # self.server = server.Server - self.setup() - - def log_env(self) -> None: - cache_steam = StringIO() - write_info_to_cache(cache_steam) - text = cache_steam.getvalue() - self.logger.info(text) - def load_mods(self) -> None: + def init_mods(self) -> None: + """验证/加载 mod""" + print(self) mods = [] mod_path = Path(DR_runtime.mod_path) if not mod_path.exists(): @@ -114,9 +138,9 @@ class Game: module.append(mod_class) self.logger.info(tr().main.mod.load.info().format(mod_class.mod_id, mod_class.version)) except ImportError as e: - self.logger.warning(tr().main.mod.load.faild().format(mod, e)) + self.logger.warning(tr().main.mod.load.faild.info().format(mod, e)) self.logger.info(tr().main.mod.load.done()) - self.loaded_mods = module + self.mod_module = module mod_list = [] for mod in module: mod_list.append((mod.mod_id, mod.version)) @@ -124,30 +148,11 @@ class Game: self.dispatch_event('on_load', game=self) DR_runtime.DR_Mod_List = mod_list - def dispatch_event(self, event_name: str, *args, **kwargs) -> None: - for mod in self.loaded_mods: - if hasattr(mod, event_name): - try: - getattr(mod, event_name)(*args, **kwargs) - except Exception as e: - self.logger.error(tr().main.mod.event.error().format(event_name, e, mod.mod_id)) + def init_console(self) -> None: + self.console = self.console_class() + self.console.start() - def setup(self) -> None: - self.load_mods() - self.client = client.Client(game=self, net_mode='local') - self.server = server.Server(net_mode='local') - - def python_version_check(self) -> None: # best 3.8+ and write at 3.8.10 - self.logger.info(f"{tr().main.version.now_on()} {self.on_python_v}") - if self.on_python_v_info[0] == 2: - self.logger.critical(tr().main.version.need3p()) - raise SystemError(tr().main.version.need3p()) - elif self.on_python_v_info[1] < 8: - warning = tools.name_handler(tr.main.version.best38p()) - self.logger.warning(warning) - - # @new_thread('main') - def _start(self): + def start(self): self.server.run() if DR_option.use_multiprocess: try: @@ -161,5 +166,42 @@ class Game: else: self.client.start() - def start(self) -> None: - self._start() + def dispatch_event(self, event_name: str, *args, **kwargs) -> None: + """向 mod 分发事件""" + for mod in self.mod_module: + if hasattr(mod, event_name): + try: + getattr(mod, event_name)(*args, **kwargs) + except Exception: + error = traceback.format_exc() + self.logger.error(tr().main.mod.event.error().format(event_name, error, mod.mod_id)) + + def log_env(self) -> None: + cache_steam = StringIO() + write_info_to_cache(cache_steam) + text = cache_steam.getvalue() + self.logger.info(text) + self.flush_option() + config_cache = self.logging_config.copy() + self.logging_config = {"logging_config": "too long to show"} + self.logger.info(f"\n{self.as_markdown()}") + self.logging_config = config_cache + + def setup(self) -> None: + self.client = client.Client(game=self, net_mode='local') + self.server = server.Server(net_mode='local') + + def init(self, **kwargs) -> bool: + self.load_file() + self.setup() + self.log_env() + return True + + def load_file(self) -> bool: + """加载文件""" + self.logging_config = tools.load_file('configs/logger.toml') + self.init_logger() + self.init_mods() + self.init_console() + return True + diff --git a/Difficult_Rocket/utils/options.py b/Difficult_Rocket/utils/options.py index b8586e9..1be17c2 100644 --- a/Difficult_Rocket/utils/options.py +++ b/Difficult_Rocket/utils/options.py @@ -66,9 +66,11 @@ class Options: if option not in self.cached_options: raise OptionNameNotDefined(f"option: {option} with value: {value} is not defined") setattr(self, option, value) + run_load_file = True if hasattr(self, 'init'): - self.init(**kwargs) - if hasattr(self, 'load_file'): + run_load_file = self.init(**kwargs) # 默认 False/None + run_load_file = not run_load_file + if hasattr(self, 'load_file') and run_load_file: try: self.load_file() except Exception: @@ -78,8 +80,10 @@ class Options: if TYPE_CHECKING: _options: Dict[str, Union[Callable, object]] = {} - def init(self, **kwargs) -> None: - """ 如果子类定义了这个函数,则会在 __init__ 之后调用这个函数 """ + def init(self, **kwargs) -> bool: + """ 如果子类定义了这个函数,则会在 __init__ 之后调用这个函数 + 返回值为 True 则不会调用 load_file 函数 + """ def load_file(self) -> bool: """如果子类定义了这个函数,则会在 __init__ 和 init 之后再调用这个函数 diff --git a/configs/logger.toml b/configs/logger.toml index 927a7cd..5a65de0 100644 --- a/configs/logger.toml +++ b/configs/logger.toml @@ -27,7 +27,6 @@ level = "DEBUG" [handlers.file] class = "logging.FileHandler" filename = "{} DR.log" -datefmt = "%Y-%m-%d %H:%M:%S" encoding = "utf-8" formatter = "file" level = "DEBUG" diff --git a/docs/src/update_logs.md b/docs/src/update_logs.md index f299b20..3725763 100644 --- a/docs/src/update_logs.md +++ b/docs/src/update_logs.md @@ -59,6 +59,9 @@ - `get_box(&self, part_type: &SR1PartType) -> (f64, f64, f64, f64)` - `types::SR1Ship` - `from_file` +- 添加了 `Console_rs` + - 用于使用 Rust 多线程读取 stdin + - Use Rust mutithread to read stdin ### Remove @@ -114,6 +117,13 @@ - 修改 返回值 类型+类型注释 - Modify the return value type + type annotation - `List[Union[List[Tuple[str, Any, Any]], int, Any]]:` -> `Tuple[List[Tuple[str, Union[Any, Type], Type]], int, int, int]` +- `Difficult_Roocket.main.Game` + - 使用 `Options` 完全重构 + - 分离 `init mods` `init console` `init logger` `load_file` + - Completely refactored using `Options` + - Separate `init mods` `init console` `init logger` `load_file` +- `Difficult_Rocket.command.api.CommandText` + - `find` -> `re_find` ### Add @@ -122,6 +132,10 @@ - 用于方便的用人类可读的 Markdown 格式 直接输出一个已经实例化的 `Options` 类的所有字段 - Add `as_markdown` method - Used to easily output all fields of an instantiated `Options` class in a human-readable Markdown format +- `Difficult_Rocket.command.api.CommandText` + - 添加基于 `str.find` 的 `find(text: str) -> bool` 方法 + - Add method `find(text: str)` based on `str.find` + ### Docs diff --git a/mods/dr_game/Difficult_Rocket_rs/__init__.py b/mods/dr_game/Difficult_Rocket_rs/__init__.py index f1d1832..94c6dd5 100644 --- a/mods/dr_game/Difficult_Rocket_rs/__init__.py +++ b/mods/dr_game/Difficult_Rocket_rs/__init__.py @@ -104,5 +104,6 @@ if TYPE_CHECKING: class Console_rs: def __init__(self) -> None: ... - def stop_console(self) -> None: ... + def start(self) -> None: ... + def stop(self) -> bool: ... def get_command(self) -> Optional[str]: ... diff --git a/mods/dr_game/Difficult_Rocket_rs/src/src/lib.rs b/mods/dr_game/Difficult_Rocket_rs/src/src/lib.rs index 861fd2e..97c570f 100644 --- a/mods/dr_game/Difficult_Rocket_rs/src/src/lib.rs +++ b/mods/dr_game/Difficult_Rocket_rs/src/src/lib.rs @@ -18,16 +18,17 @@ use pyo3::prelude::*; // const MOD_PATH: String = String::from("mods"); +#[allow(unused)] enum LoadState { - init, - wait_start, - pre_start, - running, - clean, + Init, + WaitStart, + PreStart, + Running, + Clean, } #[pyfunction] -fn get_version_str() -> String { "0.2.7.0".to_string() } +fn get_version_str() -> String { "0.2.8.0".to_string() } #[pyfunction] fn test_call(py_obj: &PyAny) -> PyResult { diff --git a/mods/dr_game/Difficult_Rocket_rs/src/src/python.rs b/mods/dr_game/Difficult_Rocket_rs/src/src/python.rs index 4564c46..32cccc2 100644 --- a/mods/dr_game/Difficult_Rocket_rs/src/src/python.rs +++ b/mods/dr_game/Difficult_Rocket_rs/src/src/python.rs @@ -176,15 +176,21 @@ pub mod console { #[pyo3(name = "Console_rs")] pub struct PyConsole { /// 向子线程发送结束信号 - pub stop_sender: std::sync::mpsc::Sender<()>, - /// - pub keyboard_input_receiver: std::sync::mpsc::Receiver, + pub stop_sender: Option>, + pub keyboard_input_receiver: Option>, } #[pymethods] impl PyConsole { #[new] fn new() -> Self { + Self { + stop_sender: None, + keyboard_input_receiver: None, + } + } + + fn start(&mut self) { let (stop_sender, stop_receiver) = std::sync::mpsc::channel(); let (keyboard_input_sender, keyboard_input_receiver) = std::sync::mpsc::channel(); std::thread::spawn(move || { @@ -194,27 +200,31 @@ pub mod console { break; } let mut input = String::new(); - print!(">>"); let _ = std_in.read_line(&mut input); if !input.is_empty() { keyboard_input_sender.send(input).unwrap(); } + print!(">>"); } }); - - Self { - stop_sender, - keyboard_input_receiver, - } + self.stop_sender = Some(stop_sender); + self.keyboard_input_receiver = Some(keyboard_input_receiver); } - fn stop_console(&self) { self.stop_sender.send(()).unwrap(); } + fn stop(&self) -> bool { + if let Some(sender) = &self.stop_sender { + sender.send(()).unwrap(); + return true; + } + false + } fn get_command(&self) -> Option { // 获取输入 - if let Ok(string) = self.keyboard_input_receiver.try_recv() { - println!("rust recv input: {}", string); - return Some(string); + if let Some(receiver) = &self.keyboard_input_receiver { + if let Ok(string) = receiver.try_recv() { + return Some(string); + } } None } diff --git a/mods/dr_game/__init__.py b/mods/dr_game/__init__.py index 79ab6f6..712c039 100644 --- a/mods/dr_game/__init__.py +++ b/mods/dr_game/__init__.py @@ -9,14 +9,13 @@ import traceback from typing import Optional - from libs.MCDR.version import Version -from Difficult_Rocket.main import Game +from Difficult_Rocket.main import Game, Console from Difficult_Rocket.api.mod import ModInfo from Difficult_Rocket.api.types import Options from Difficult_Rocket.client import ClientWindow -DR_rust_version = Version("0.2.7.0") # DR_mod 的 Rust 编写部分的兼容版本 +DR_rust_version = Version("0.2.8.0") # DR_mod 的 Rust 编写部分的兼容版本 class _DR_mod_runtime(Options): @@ -68,6 +67,10 @@ class DR_mod(ModInfo): def on_load(self, game: Game, old_self: Optional["DR_mod"] = None) -> bool: if not DR_mod_runtime.DR_rust_available: return False + from .console import RustConsole + + game.console_class = RustConsole # 替换掉原来的 console 类 + if old_self: game.client.window.add_sub_screen("SR1_ship", old_self.screen) else: diff --git a/mods/dr_game/console.py b/mods/dr_game/console.py new file mode 100644 index 0000000..62f88f8 --- /dev/null +++ b/mods/dr_game/console.py @@ -0,0 +1,24 @@ +from . import DR_mod_runtime +from Difficult_Rocket.main import Console + +if DR_mod_runtime.use_DR_rust: + from .Difficult_Rocket_rs import Console_rs + + +class RustConsole(Console): + name = 'Rust stdin Console' + + running: bool = False + console: Console_rs + + def start(self): + self.console.start() + + def stop(self): + return self.console.stop() + + def init(self, **kwargs) -> None: + self.console = Console_rs() + + def get_command(self) -> str: + return self.console.get_command()