aether-engine 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. aether/__init__.py +54 -0
  2. aether/audio/__init__.py +0 -0
  3. aether/audio/audio_manager.py +211 -0
  4. aether/audio/music.py +6 -0
  5. aether/audio/sound.py +6 -0
  6. aether/core/__init__.py +211 -0
  7. aether/core/achievement_system.py +304 -0
  8. aether/core/application.py +19 -0
  9. aether/core/config.py +75 -0
  10. aether/core/console.py +366 -0
  11. aether/core/engine.py +50 -0
  12. aether/core/event_bus.py +24 -0
  13. aether/core/input.py +58 -0
  14. aether/core/plugin_system.py +93 -0
  15. aether/core/save_system.py +409 -0
  16. aether/core/time_manager.py +25 -0
  17. aether/core/window.py +57 -0
  18. aether/graphics/__init__.py +0 -0
  19. aether/graphics/camera.py +69 -0
  20. aether/graphics/framebuffer.py +119 -0
  21. aether/graphics/light.py +32 -0
  22. aether/graphics/lod_system.py +170 -0
  23. aether/graphics/material.py +25 -0
  24. aether/graphics/mesh.py +83 -0
  25. aether/graphics/mesh_factory.py +67 -0
  26. aether/graphics/particle_system.py +268 -0
  27. aether/graphics/post_process.py +279 -0
  28. aether/graphics/renderer.py +135 -0
  29. aether/graphics/shader.py +61 -0
  30. aether/graphics/shader_library.py +157 -0
  31. aether/graphics/sky_atmosphere.py +309 -0
  32. aether/graphics/terrain.py +408 -0
  33. aether/graphics/texture.py +34 -0
  34. aether/graphics/water.py +432 -0
  35. aether/physics/__init__.py +0 -0
  36. aether/physics/bullet_backend.py +395 -0
  37. aether/physics/collider.py +22 -0
  38. aether/physics/physics_world.py +436 -0
  39. aether/physics/raycast.py +25 -0
  40. aether/physics/rigidbody.py +7 -0
  41. aether/resources/__init__.py +0 -0
  42. aether/resources/asset_database.py +76 -0
  43. aether/resources/resource_manager.py +147 -0
  44. aether/resources/serialization.py +114 -0
  45. aether/scene/__init__.py +0 -0
  46. aether/scene/component.py +368 -0
  47. aether/scene/entity.py +46 -0
  48. aether/scene/scene.py +33 -0
  49. aether/scene/scene_graph.py +252 -0
  50. aether/scene/scene_manager.py +191 -0
  51. aether/ui/__init__.py +0 -0
  52. aether/ui/button.py +297 -0
  53. aether/ui/canvas.py +11 -0
  54. aether/ui/text.py +286 -0
  55. aether/utils/__init__.py +0 -0
  56. aether/utils/logger.py +16 -0
  57. aether/utils/math_utils.py +31 -0
  58. aether/utils/object_pool.py +137 -0
  59. aether/utils/profiler.py +187 -0
  60. aether_engine-1.0.0.dist-info/METADATA +71 -0
  61. aether_engine-1.0.0.dist-info/RECORD +64 -0
  62. aether_engine-1.0.0.dist-info/WHEEL +5 -0
  63. aether_engine-1.0.0.dist-info/licenses/LICENSE +21 -0
  64. aether_engine-1.0.0.dist-info/top_level.txt +1 -0
aether/core/console.py ADDED
@@ -0,0 +1,366 @@
1
+ """Система консольных команд"""
2
+ from typing import Dict, List, Callable, Any, Optional
3
+ import inspect
4
+ import shlex
5
+
6
+
7
+ class ConsoleCommand:
8
+ """Консольная команда"""
9
+
10
+ def __init__(self,
11
+ name: str,
12
+ func: Callable,
13
+ description: str = "",
14
+ usage: str = "",
15
+ category: str = "General"):
16
+ self.name = name
17
+ self.func = func
18
+ self.description = description
19
+ self.usage = usage or name
20
+ self.category = category
21
+
22
+ def execute(self, args: List[str]) -> str:
23
+ """Выполняет команду"""
24
+ try:
25
+ return str(self.func(*args))
26
+ except Exception as e:
27
+ return f"Error: {e}"
28
+
29
+
30
+ class ConsoleVariable:
31
+ """Консольная переменная"""
32
+
33
+ def __init__(self,
34
+ name: str,
35
+ default_value: Any,
36
+ description: str = "",
37
+ min_value: Optional[float] = None,
38
+ max_value: Optional[float] = None,
39
+ callback: Optional[Callable] = None):
40
+ self.name = name
41
+ self._value = default_value
42
+ self.default_value = default_value
43
+ self.description = description
44
+ self.min_value = min_value
45
+ self.max_value = max_value
46
+ self.callback = callback
47
+
48
+ @property
49
+ def value(self) -> Any:
50
+ return self._value
51
+
52
+ @value.setter
53
+ def value(self, new_value: Any):
54
+ # Проверка типа
55
+ if isinstance(self._value, bool):
56
+ if isinstance(new_value, str):
57
+ new_value = new_value.lower() in ('true', '1', 'yes', 'on')
58
+ else:
59
+ new_value = bool(new_value)
60
+ elif isinstance(self._value, int):
61
+ new_value = int(new_value)
62
+ elif isinstance(self._value, float):
63
+ new_value = float(new_value)
64
+
65
+ # Проверка диапазона
66
+ if isinstance(new_value, (int, float)):
67
+ if self.min_value is not None:
68
+ new_value = max(self.min_value, new_value)
69
+ if self.max_value is not None:
70
+ new_value = min(self.max_value, new_value)
71
+
72
+ old_value = self._value
73
+ self._value = new_value
74
+
75
+ # Вызываем callback
76
+ if self.callback and old_value != new_value:
77
+ self.callback(old_value, new_value)
78
+
79
+ def toggle(self):
80
+ """Переключает булеву переменную"""
81
+ if isinstance(self._value, bool):
82
+ self.value = not self._value
83
+
84
+ def reset(self):
85
+ """Сбрасывает к значению по умолчанию"""
86
+ self.value = self.default_value
87
+
88
+
89
+ class Console:
90
+ """Консоль движка"""
91
+
92
+ _instance = None
93
+
94
+ def __new__(cls):
95
+ if cls._instance is None:
96
+ cls._instance = super().__new__(cls)
97
+ cls._instance._initialize()
98
+ return cls._instance
99
+
100
+ def _initialize(self):
101
+ """Инициализация консоли"""
102
+ self._commands: Dict[str, ConsoleCommand] = {}
103
+ self._variables: Dict[str, ConsoleVariable] = {}
104
+ self._history: List[str] = []
105
+ self._history_index = -1
106
+ self._max_history = 100
107
+
108
+ # Состояние
109
+ self.visible = False
110
+ self._input_buffer = ""
111
+ self._output_lines: List[str] = []
112
+ self._max_output_lines = 200
113
+
114
+ # Регистрируем встроенные команды
115
+ self._register_default_commands()
116
+ self._register_default_variables()
117
+
118
+ print("[Console] Initialized")
119
+
120
+ def _register_default_commands(self):
121
+ """Регистрирует стандартные команды"""
122
+ self.register_command("help", self._cmd_help, "Shows available commands")
123
+ self.register_command("list", self._cmd_list, "Lists all commands and variables")
124
+ self.register_command("clear", self._cmd_clear, "Clears the console")
125
+ self.register_command("echo", self._cmd_echo, "Prints a message")
126
+ self.register_command("quit", self._cmd_quit, "Exits the engine")
127
+ self.register_command("fps", self._cmd_fps, "Shows FPS")
128
+ self.register_command("time_scale", self._cmd_time_scale, "Sets time scale")
129
+
130
+ def _register_default_variables(self):
131
+ """Регистрирует стандартные переменные"""
132
+ self.register_variable("r_wireframe", False, "Toggle wireframe mode")
133
+ self.register_variable("r_vsync", True, "Toggle VSync")
134
+ self.register_variable("r_msaa", 4, "MSAA samples", 0, 16)
135
+ self.register_variable("g_gravity", -9.81, "Gravity value")
136
+ self.register_variable("g_time_scale", 1.0, "Time scale", 0.0, 10.0)
137
+ self.register_variable("dbg_show_colliders", False, "Show collider debug")
138
+ self.register_variable("dbg_show_fps", True, "Show FPS counter")
139
+ self.register_variable("s_volume_master", 1.0, "Master volume", 0.0, 1.0)
140
+ self.register_variable("s_volume_sfx", 1.0, "SFX volume", 0.0, 1.0)
141
+ self.register_variable("s_volume_music", 0.8, "Music volume", 0.0, 1.0)
142
+
143
+ def register_command(self,
144
+ name: str,
145
+ func: Callable,
146
+ description: str = "",
147
+ usage: str = "",
148
+ category: str = "General"):
149
+ """Регистрирует команду"""
150
+ cmd = ConsoleCommand(name, func, description, usage, category)
151
+ self._commands[name.lower()] = cmd
152
+
153
+ def unregister_command(self, name: str):
154
+ """Удаляет команду"""
155
+ if name.lower() in self._commands:
156
+ del self._commands[name.lower()]
157
+
158
+ def register_variable(self,
159
+ name: str,
160
+ default_value: Any,
161
+ description: str = "",
162
+ min_value: Optional[float] = None,
163
+ max_value: Optional[float] = None,
164
+ callback: Optional[Callable] = None):
165
+ """Регистрирует переменную"""
166
+ var = ConsoleVariable(name, default_value, description,
167
+ min_value, max_value, callback)
168
+ self._variables[name.lower()] = var
169
+
170
+ def get_variable(self, name: str) -> Any:
171
+ """Получает значение переменной"""
172
+ var = self._variables.get(name.lower())
173
+ return var.value if var else None
174
+
175
+ def set_variable(self, name: str, value: Any):
176
+ """Устанавливает значение переменной"""
177
+ var = self._variables.get(name.lower())
178
+ if var:
179
+ var.value = value
180
+ self._output(f"Set {name} = {var.value}")
181
+ else:
182
+ self._output(f"Unknown variable: {name}")
183
+
184
+ def execute(self, input_str: str) -> str:
185
+ """Выполняет команду или устанавливает переменную"""
186
+ if not input_str.strip():
187
+ return ""
188
+
189
+ # Добавляем в историю
190
+ self._history.append(input_str)
191
+ if len(self._history) > self._max_history:
192
+ self._history.pop(0)
193
+ self._history_index = len(self._history)
194
+
195
+ # Парсим
196
+ try:
197
+ parts = shlex.split(input_str)
198
+ except ValueError:
199
+ parts = input_str.split()
200
+
201
+ if not parts:
202
+ return ""
203
+
204
+ command = parts[0].lower()
205
+ args = parts[1:]
206
+
207
+ # Проверяем, это команда или переменная
208
+ if command in self._commands:
209
+ result = self._commands[command].execute(args)
210
+ self._output(f"> {input_str}")
211
+ if result:
212
+ self._output(result)
213
+ return result
214
+
215
+ elif command in self._variables:
216
+ if args:
217
+ # Установка значения
218
+ self.set_variable(command, args[0])
219
+ else:
220
+ # Получение значения
221
+ value = self.get_variable(command)
222
+ self._output(f"{command} = {value}")
223
+ return str(self.get_variable(command))
224
+
225
+ else:
226
+ msg = f"Unknown command: {command}. Type 'help' for available commands."
227
+ self._output(f"> {input_str}")
228
+ self._output(msg)
229
+ return msg
230
+
231
+ def _output(self, text: str):
232
+ """Добавляет строку в вывод"""
233
+ self._output_lines.append(text)
234
+ if len(self._output_lines) > self._max_output_lines:
235
+ self._output_lines.pop(0)
236
+
237
+ def get_output(self) -> List[str]:
238
+ """Возвращает вывод консоли"""
239
+ return self._output_lines.copy()
240
+
241
+ def get_history(self) -> List[str]:
242
+ """Возвращает историю команд"""
243
+ return self._history.copy()
244
+
245
+ # ---- ВСТРОЕННЫЕ КОМАНДЫ ----
246
+
247
+ def _cmd_help(self, *args) -> str:
248
+ """Показывает справку"""
249
+ if args:
250
+ cmd_name = args[0].lower()
251
+ if cmd_name in self._commands:
252
+ cmd = self._commands[cmd_name]
253
+ return f"{cmd.name}: {cmd.description}\nUsage: {cmd.usage}"
254
+ else:
255
+ return f"Unknown command: {cmd_name}"
256
+
257
+ categories = {}
258
+ for cmd in self._commands.values():
259
+ if cmd.category not in categories:
260
+ categories[cmd.category] = []
261
+ categories[cmd.category].append(cmd)
262
+
263
+ output = ["Available commands:\n"]
264
+ for category, cmds in sorted(categories.items()):
265
+ output.append(f"[{category}]")
266
+ for cmd in cmds:
267
+ output.append(f" {cmd.name:<20} {cmd.description}")
268
+
269
+ return "\n".join(output)
270
+
271
+ def _cmd_list(self, *args) -> str:
272
+ """Список всех команд и переменных"""
273
+ output = ["Commands:"]
274
+ for cmd in sorted(self._commands.values(), key=lambda c: c.name):
275
+ output.append(f" {cmd.name:<20} {cmd.description}")
276
+
277
+ output.append("\nVariables:")
278
+ for name, var in sorted(self._variables.items()):
279
+ output.append(f" {name:<20} = {var.value} ({var.description})")
280
+
281
+ return "\n".join(output)
282
+
283
+ def _cmd_clear(self, *args) -> str:
284
+ """Очищает консоль"""
285
+ self._output_lines.clear()
286
+ return ""
287
+
288
+ def _cmd_echo(self, *args) -> str:
289
+ """Выводит сообщение"""
290
+ return " ".join(args)
291
+
292
+ def _cmd_quit(self, *args) -> str:
293
+ """Выход из движка"""
294
+ # Здесь должен быть вызов engine.shutdown()
295
+ return "Quitting..."
296
+
297
+ def _cmd_fps(self, *args) -> str:
298
+ """Показывает FPS"""
299
+ from aether.core.engine import Engine
300
+ engine = Engine()
301
+ return f"FPS: {engine.time.fps:.1f}"
302
+
303
+ def _cmd_time_scale(self, *args) -> str:
304
+ """Устанавливает масштаб времени"""
305
+ if args:
306
+ try:
307
+ scale = float(args[0])
308
+ self.set_variable("g_time_scale", scale)
309
+ return f"Time scale set to {scale}"
310
+ except ValueError:
311
+ return "Invalid time scale value"
312
+ return f"Time scale: {self.get_variable('g_time_scale')}"
313
+
314
+ def toggle(self):
315
+ """Переключает видимость консоли"""
316
+ self.visible = not self.visible
317
+ if self.visible:
318
+ self._input_buffer = ""
319
+
320
+ def handle_input(self, char: str):
321
+ """Обрабатывает ввод символа"""
322
+ if char == '\n': # Enter
323
+ self.execute(self._input_buffer)
324
+ self._input_buffer = ""
325
+ elif char == '\b': # Backspace
326
+ self._input_buffer = self._input_buffer[:-1]
327
+ elif char == '\t': # Tab (автодополнение)
328
+ self._autocomplete()
329
+ else:
330
+ self._input_buffer += char
331
+
332
+ def _autocomplete(self):
333
+ """Автодополнение команд"""
334
+ if not self._input_buffer:
335
+ return
336
+
337
+ matches = []
338
+
339
+ for cmd_name in self._commands:
340
+ if cmd_name.startswith(self._input_buffer.lower()):
341
+ matches.append(cmd_name)
342
+
343
+ for var_name in self._variables:
344
+ if var_name.startswith(self._input_buffer.lower()):
345
+ matches.append(var_name)
346
+
347
+ if len(matches) == 1:
348
+ self._input_buffer = matches[0]
349
+ elif len(matches) > 1:
350
+ # Показываем варианты
351
+ common_prefix = self._input_buffer
352
+ for i in range(len(self._input_buffer), len(matches[0])):
353
+ char = matches[0][i]
354
+ if all(m.startswith(common_prefix + char) for m in matches):
355
+ common_prefix += char
356
+ else:
357
+ break
358
+
359
+ self._input_buffer = common_prefix
360
+
361
+ if len(matches) <= 10:
362
+ self._output(" ".join(matches))
363
+
364
+ @property
365
+ def input_text(self) -> str:
366
+ return self._input_buffer
aether/core/engine.py ADDED
@@ -0,0 +1,50 @@
1
+ def run(self):
2
+ """Главный игровой цикл"""
3
+ self._running = True
4
+ print("[Aether] Starting game loop...")
5
+
6
+ from aether.utils.profiler import Profiler, ScopedProfiler
7
+ profiler = Profiler()
8
+
9
+ while self._running and not self.window.should_close():
10
+ profiler.begin_frame()
11
+
12
+ with ScopedProfiler("Input"):
13
+ self.time.update()
14
+ dt = min(self.time.delta_time, 0.1)
15
+ self.input.update()
16
+
17
+ if self.input.get_key(glfw.KEY_ESCAPE):
18
+ self.event_bus.emit('engine_quit')
19
+
20
+ if self.input.get_key_down(glfw.KEY_P):
21
+ self.event_bus.emit('engine_pause', paused=not self._paused)
22
+
23
+ # Профилировщик по F3
24
+ if self.input.get_key_down(glfw.KEY_F3):
25
+ profiler.print_report()
26
+
27
+ if not self._paused:
28
+ with ScopedProfiler("Physics"):
29
+ self._update_physics(dt)
30
+
31
+ with ScopedProfiler("Scene Update"):
32
+ if self.scene:
33
+ self.scene.update(dt)
34
+
35
+ self._on_update(dt)
36
+
37
+ with ScopedProfiler("Render"):
38
+ self._render()
39
+
40
+ with ScopedProfiler("Window"):
41
+ self.window.swap_buffers()
42
+ self.window.poll_events()
43
+
44
+ if self._frame_count % 60 == 0:
45
+ self._update_title()
46
+
47
+ profiler.end_frame()
48
+ self._frame_count += 1
49
+
50
+ self.shutdown()
@@ -0,0 +1,24 @@
1
+ """Система событий (Observer pattern)"""
2
+ from typing import Callable, Dict, List
3
+
4
+
5
+ class EventBus:
6
+ def __init__(self):
7
+ self._listeners: Dict[str, List[Callable]] = {}
8
+
9
+ def subscribe(self, event_type: str, callback: Callable):
10
+ if event_type not in self._listeners:
11
+ self._listeners[event_type] = []
12
+ self._listeners[event_type].append(callback)
13
+
14
+ def unsubscribe(self, event_type: str, callback: Callable):
15
+ if event_type in self._listeners:
16
+ self._listeners[event_type].remove(callback)
17
+
18
+ def emit(self, event_type: str, **data):
19
+ if event_type in self._listeners:
20
+ for callback in self._listeners[event_type]:
21
+ callback(data)
22
+
23
+ def clear(self):
24
+ self._listeners.clear()
aether/core/input.py ADDED
@@ -0,0 +1,58 @@
1
+ """Обработка ввода: клавиатура, мышь, геймпад"""
2
+ import glfw
3
+
4
+
5
+ class Input:
6
+ def __init__(self, window):
7
+ self.window = window
8
+ self._keys = {}
9
+ self._keys_prev = {}
10
+ self._mouse_buttons = {}
11
+ self._mouse_buttons_prev = {}
12
+ self.mouse_x = 0.0
13
+ self.mouse_y = 0.0
14
+ self.mouse_dx = 0.0
15
+ self.mouse_dy = 0.0
16
+ self._first_mouse = True
17
+
18
+ # Коллбэки
19
+ glfw.set_key_callback(window.handle, self._key_callback)
20
+ glfw.set_mouse_button_callback(window.handle, self._mouse_button_callback)
21
+ glfw.set_cursor_pos_callback(window.handle, self._cursor_callback)
22
+
23
+ def _key_callback(self, window, key, scancode, action, mods):
24
+ self._keys[key] = (action == glfw.PRESS or action == glfw.REPEAT)
25
+
26
+ def _mouse_button_callback(self, window, button, action, mods):
27
+ self._mouse_buttons[button] = (action == glfw.PRESS)
28
+
29
+ def _cursor_callback(self, window, xpos, ypos):
30
+ if self._first_mouse:
31
+ self.mouse_x = xpos
32
+ self.mouse_y = ypos
33
+ self._first_mouse = False
34
+ self.mouse_dx = xpos - self.mouse_x
35
+ self.mouse_dy = ypos - self.mouse_y
36
+ self.mouse_x = xpos
37
+ self.mouse_y = ypos
38
+
39
+ def update(self):
40
+ self._keys_prev = self._keys.copy()
41
+ self._mouse_buttons_prev = self._mouse_buttons.copy()
42
+ self.mouse_dx = 0.0
43
+ self.mouse_dy = 0.0
44
+
45
+ def get_key(self, key) -> bool:
46
+ return self._keys.get(key, False)
47
+
48
+ def get_key_down(self, key) -> bool:
49
+ return self._keys.get(key, False) and not self._keys_prev.get(key, False)
50
+
51
+ def get_key_up(self, key) -> bool:
52
+ return not self._keys.get(key, False) and self._keys_prev.get(key, False)
53
+
54
+ def get_mouse_button(self, button) -> bool:
55
+ return self._mouse_buttons.get(button, False)
56
+
57
+ def get_mouse_button_down(self, button) -> bool:
58
+ return self._mouse_buttons.get(button, False) and not self._mouse_buttons_prev.get(button, False)
@@ -0,0 +1,93 @@
1
+ """Система плагинов — расширение движка без правки ядра"""
2
+ from abc import ABC, abstractmethod
3
+ from typing import List, Type, Dict
4
+ import importlib
5
+ import os
6
+ import inspect
7
+
8
+
9
+ class Plugin(ABC):
10
+ """Базовый класс для всех плагинов"""
11
+
12
+ @abstractmethod
13
+ def on_register(self, engine):
14
+ """Вызывается при регистрации плагина"""
15
+ pass
16
+
17
+ @abstractmethod
18
+ def on_unregister(self, engine):
19
+ """Вызывается при удалении плагина"""
20
+ pass
21
+
22
+ @property
23
+ @abstractmethod
24
+ def name(self) -> str:
25
+ """Уникальное имя плагина"""
26
+ pass
27
+
28
+ @property
29
+ @abstractmethod
30
+ def version(self) -> str:
31
+ """Версия плагина"""
32
+ pass
33
+
34
+
35
+ class PluginManager:
36
+ """Менеджер плагинов"""
37
+
38
+ def __init__(self):
39
+ self._plugins: Dict[str, Plugin] = {}
40
+ self._plugin_dirs: List[str] = ["./plugins"]
41
+
42
+ def add_plugin_dir(self, path: str):
43
+ """Добавляет директорию для поиска плагинов"""
44
+ self._plugin_dirs.append(path)
45
+
46
+ def discover_plugins(self):
47
+ """Автоматически находит плагины в указанных директориях"""
48
+ for plugin_dir in self._plugin_dirs:
49
+ if not os.path.exists(plugin_dir):
50
+ continue
51
+
52
+ for filename in os.listdir(plugin_dir):
53
+ if filename.endswith('.py') and not filename.startswith('_'):
54
+ module_name = filename[:-3]
55
+ spec = importlib.util.spec_from_file_location(
56
+ module_name,
57
+ os.path.join(plugin_dir, filename)
58
+ )
59
+ module = importlib.util.module_from_spec(spec)
60
+ spec.loader.exec_module(module)
61
+
62
+ for name, obj in inspect.getmembers(module):
63
+ if (inspect.isclass(obj) and
64
+ issubclass(obj, Plugin) and
65
+ obj != Plugin):
66
+ self.register_plugin(obj())
67
+
68
+ def register_plugin(self, plugin: Plugin, engine=None):
69
+ """Регистрирует плагин"""
70
+ if plugin.name in self._plugins:
71
+ raise ValueError(f"Plugin {plugin.name} already registered!")
72
+
73
+ self._plugins[plugin.name] = plugin
74
+ if engine:
75
+ plugin.on_register(engine)
76
+ print(f"[Plugin] Registered: {plugin.name} v{plugin.version}")
77
+
78
+ def unregister_plugin(self, name: str, engine=None):
79
+ """Удаляет плагин"""
80
+ if name in self._plugins:
81
+ plugin = self._plugins[name]
82
+ if engine:
83
+ plugin.on_unregister(engine)
84
+ del self._plugins[name]
85
+
86
+ def get_plugin(self, name: str) -> Plugin:
87
+ """Получает плагин по имени"""
88
+ return self._plugins.get(name)
89
+
90
+ def initialize_all(self, engine):
91
+ """Инициализирует все плагины"""
92
+ for plugin in self._plugins.values():
93
+ plugin.on_register(engine)