mini-arcade-core 1.1.0__py3-none-any.whl → 1.2.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 (58) hide show
  1. mini_arcade_core/__init__.py +14 -42
  2. mini_arcade_core/backend/__init__.py +1 -2
  3. mini_arcade_core/backend/backend.py +185 -154
  4. mini_arcade_core/backend/types.py +5 -1
  5. mini_arcade_core/engine/commands.py +26 -7
  6. mini_arcade_core/engine/game.py +79 -319
  7. mini_arcade_core/engine/game_config.py +40 -0
  8. mini_arcade_core/engine/gameplay_settings.py +24 -0
  9. mini_arcade_core/engine/loop/config.py +20 -0
  10. mini_arcade_core/engine/loop/hooks.py +77 -0
  11. mini_arcade_core/engine/loop/runner.py +272 -0
  12. mini_arcade_core/engine/loop/state.py +32 -0
  13. mini_arcade_core/engine/managers.py +24 -0
  14. mini_arcade_core/engine/render/context.py +0 -2
  15. mini_arcade_core/engine/render/effects/base.py +88 -0
  16. mini_arcade_core/engine/render/effects/crt.py +68 -0
  17. mini_arcade_core/engine/render/effects/registry.py +50 -0
  18. mini_arcade_core/engine/render/effects/vignette.py +79 -0
  19. mini_arcade_core/engine/render/passes/begin_frame.py +1 -1
  20. mini_arcade_core/engine/render/passes/end_frame.py +1 -1
  21. mini_arcade_core/engine/render/passes/postfx.py +25 -4
  22. mini_arcade_core/engine/render/passes/ui.py +1 -1
  23. mini_arcade_core/engine/render/passes/world.py +6 -6
  24. mini_arcade_core/engine/render/pipeline.py +7 -6
  25. mini_arcade_core/engine/render/viewport.py +10 -4
  26. mini_arcade_core/engine/scenes/__init__.py +0 -0
  27. mini_arcade_core/engine/scenes/models.py +54 -0
  28. mini_arcade_core/engine/scenes/scene_manager.py +213 -0
  29. mini_arcade_core/runtime/audio/audio_adapter.py +4 -3
  30. mini_arcade_core/runtime/audio/audio_port.py +0 -4
  31. mini_arcade_core/runtime/capture/capture_adapter.py +13 -6
  32. mini_arcade_core/runtime/capture/capture_port.py +0 -4
  33. mini_arcade_core/runtime/context.py +8 -6
  34. mini_arcade_core/runtime/scene/scene_query_adapter.py +31 -0
  35. mini_arcade_core/runtime/scene/scene_query_port.py +38 -0
  36. mini_arcade_core/runtime/services.py +3 -2
  37. mini_arcade_core/runtime/window/window_adapter.py +43 -41
  38. mini_arcade_core/runtime/window/window_port.py +3 -17
  39. mini_arcade_core/scenes/debug_overlay.py +5 -4
  40. mini_arcade_core/scenes/registry.py +11 -1
  41. mini_arcade_core/scenes/sim_scene.py +14 -14
  42. mini_arcade_core/ui/menu.py +54 -16
  43. mini_arcade_core/utils/__init__.py +2 -1
  44. mini_arcade_core/utils/logging.py +47 -18
  45. mini_arcade_core/utils/profiler.py +283 -0
  46. {mini_arcade_core-1.1.0.dist-info → mini_arcade_core-1.2.0.dist-info}/METADATA +1 -1
  47. mini_arcade_core-1.2.0.dist-info/RECORD +92 -0
  48. {mini_arcade_core-1.1.0.dist-info → mini_arcade_core-1.2.0.dist-info}/WHEEL +1 -1
  49. mini_arcade_core/managers/inputs.py +0 -284
  50. mini_arcade_core/runtime/scene/scene_adapter.py +0 -125
  51. mini_arcade_core/runtime/scene/scene_port.py +0 -170
  52. mini_arcade_core/sim/protocols.py +0 -41
  53. mini_arcade_core/sim/runner.py +0 -222
  54. mini_arcade_core-1.1.0.dist-info/RECORD +0 -80
  55. /mini_arcade_core/{managers → engine}/cheats.py +0 -0
  56. /mini_arcade_core/{managers → engine/loop}/__init__.py +0 -0
  57. /mini_arcade_core/{sim → engine/render/effects}/__init__.py +0 -0
  58. {mini_arcade_core-1.1.0.dist-info → mini_arcade_core-1.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,283 @@
1
+ """
2
+ Game core module defining the Game class and configuration.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import enum
8
+ import logging
9
+ from dataclasses import dataclass, field
10
+ from time import perf_counter
11
+ from typing import Dict, Iterable, Mapping
12
+
13
+ perf_logger = logging.getLogger("mini-arcade-core.perf")
14
+
15
+
16
+ class Ansi(enum.Enum):
17
+ """
18
+ ANSI escape codes for terminal text formatting.
19
+
20
+ cvar RESET (str): Reset all formatting.
21
+ cvar BOLD (str): Bold text.
22
+ cvar DIM (str): Dim text.
23
+ cvar RED (str): Red text.
24
+ cvar GREEN (str): Green text.
25
+ cvar YELLOW (str): Yellow text.
26
+ cvar CYAN (str): Cyan text.
27
+ cvar MAGENTA (str): Magenta text.
28
+ cvar WHITE (str): White text.
29
+ """
30
+
31
+ RESET = "\033[0m"
32
+ BOLD = "\033[1m"
33
+ DIM = "\033[2m"
34
+
35
+ RED = "\033[91m"
36
+ GREEN = "\033[92m"
37
+ YELLOW = "\033[93m"
38
+ CYAN = "\033[96m"
39
+ MAGENTA = "\033[95m"
40
+ WHITE = "\033[97m"
41
+
42
+
43
+ def _c(text: str, *codes: str) -> str:
44
+ """Convenience function to wrap text with ANSI codes."""
45
+ return "".join(codes) + text + Ansi.RESET.value
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class FrameTimingReport:
50
+ """
51
+ Report of frame timing data.
52
+
53
+ :ivar frame_index (int): Index of the frame.
54
+ :ivar diffs_ms (Dict[str, float]): Dictionary of time differences in milliseconds.
55
+ :ivar total_ms (float): Total time in milliseconds.
56
+ :ivar budget_ms (float): Frame budget in milliseconds.
57
+ """
58
+
59
+ frame_index: int
60
+ diffs_ms: Dict[str, float]
61
+ total_ms: float
62
+ budget_ms: float
63
+
64
+
65
+ @dataclass(frozen=True)
66
+ class FrameTimingFormatter:
67
+ """
68
+ Formats a FrameTimingReport into a colored, multi-line table string.
69
+ Keeps FrameTimer lean and avoids pylint complexity in the timer itself.
70
+
71
+ :ivar target_fps (int): Target frames per second for budget calculation.
72
+ :ivar top_n (int): Number of top time-consuming segments to display.
73
+ :ivar min_ms (float): Minimum time in milliseconds to include in the top list.
74
+ :ivar phases (tuple[tuple[str, str], ...]): Tuples of (display name, mark key)
75
+ for table columns.
76
+ """
77
+
78
+ target_fps: int = 60
79
+ top_n: int = 6
80
+ min_ms: float = 0.05
81
+
82
+ # These are the “headline” segments you want as columns.
83
+ phases: tuple[tuple[str, str], ...] = (
84
+ ("events", "frame_start->events_polled"),
85
+ ("input", "events_polled->input_built"),
86
+ ("tick", "tick_start->tick_end"),
87
+ ("render", "render_start->render_done"),
88
+ ("sleep", "sleep_start->sleep_end"),
89
+ )
90
+
91
+ def make_report(
92
+ self, frame_index: int, diffs_ms: Dict[str, float]
93
+ ) -> FrameTimingReport:
94
+ """
95
+ Create a FrameTimingReport from the given diffs.
96
+
97
+ :param frame_index: Index of the frame.
98
+ :type frame_index: int
99
+ :param diffs_ms: Dictionary of time differences in milliseconds.
100
+ :type diffs_ms: Dict[str, float]
101
+ :return: FrameTimingReport instance.
102
+ :rtype: FrameTimingReport
103
+ """
104
+ total = sum(diffs_ms.values()) if diffs_ms else 0.0
105
+ budget = (1000.0 / self.target_fps) if self.target_fps > 0 else 0.0
106
+ return FrameTimingReport(
107
+ frame_index=frame_index,
108
+ diffs_ms=diffs_ms,
109
+ total_ms=total,
110
+ budget_ms=budget,
111
+ )
112
+
113
+ def format(self, report: FrameTimingReport) -> str:
114
+ """
115
+ Format the FrameTimingReport into a colored string.
116
+
117
+ :param report: FrameTimingReport instance.
118
+ :type report: FrameTimingReport
119
+ :return: Formatted string.
120
+ :rtype: str
121
+ """
122
+ header = self._format_header(report)
123
+ table = self._format_table(report.diffs_ms)
124
+ top = self._format_top(report.diffs_ms)
125
+ return f"{header}\n{table}\n{top}\n"
126
+
127
+ def _format_header(self, report: FrameTimingReport) -> str:
128
+ over = report.budget_ms > 0 and report.total_ms > report.budget_ms
129
+ status = (
130
+ _c("OVER", Ansi.BOLD.value, Ansi.RED.value)
131
+ if over
132
+ else _c("OK", Ansi.BOLD.value, Ansi.GREEN.value)
133
+ )
134
+
135
+ frame = _c(
136
+ f"[Frame {report.frame_index}]", Ansi.BOLD.value, Ansi.WHITE.value
137
+ )
138
+ total = _c(
139
+ f"{report.total_ms:.2f}ms", Ansi.BOLD.value, Ansi.WHITE.value
140
+ )
141
+ budget = _c(f"{report.budget_ms:.2f}ms", Ansi.DIM.value)
142
+ dim = Ansi.DIM.value
143
+
144
+ return (
145
+ f"{frame} {_c('total', dim)}={total} "
146
+ f"{_c('budget', dim)}={budget} {_c('status', dim)}={status}"
147
+ )
148
+
149
+ def _format_table(self, diffs: Mapping[str, float]) -> str:
150
+ # Header line
151
+ headers = [name for name, _ in self.phases]
152
+ line_h = self._pipe_row((_c(h, Ansi.DIM.value) for h in headers))
153
+
154
+ # Values line
155
+ values = [diffs.get(key, 0.0) for _, key in self.phases]
156
+ line_v = self._pipe_row(self._color_values(values))
157
+
158
+ return f"{line_h}\n{line_v}"
159
+
160
+ def _color_values(self, values: Iterable[float]) -> list[str]:
161
+ # Keep coloring policy centralized and easy to tweak.
162
+ # events/input: cyan, tick: yellow, render: magenta, sleep: green
163
+ colors = [
164
+ Ansi.CYAN.value,
165
+ Ansi.CYAN.value,
166
+ Ansi.YELLOW.value,
167
+ Ansi.MAGENTA.value,
168
+ Ansi.GREEN.value,
169
+ ]
170
+ out: list[str] = []
171
+ for v, col in zip(values, colors):
172
+ out.append(_c(f"{v:6.2f}", col))
173
+ return out
174
+
175
+ def _format_top(self, diffs: Mapping[str, float]) -> str:
176
+ dim = Ansi.DIM.value
177
+ items = [
178
+ (k, float(v)) for k, v in diffs.items() if float(v) >= self.min_ms
179
+ ]
180
+ items.sort(key=lambda kv: kv[1], reverse=True)
181
+ items = items[: self.top_n]
182
+
183
+ if not items:
184
+ return f"{_c('top:', dim)} (none >= {self.min_ms:.2f}ms)"
185
+
186
+ top_str = ", ".join(f"{k}:{v:.2f}ms" for k, v in items)
187
+ return f"{_c('top:', dim)} {top_str}"
188
+
189
+ @staticmethod
190
+ def _pipe_row(cells: Iterable[str]) -> str:
191
+ # Keeps lines short and avoids long f-strings.
192
+ return " | ".join(cells)
193
+
194
+
195
+ @dataclass
196
+ class FrameTimerConfig:
197
+ """
198
+ Configuration for FrameTimer.
199
+
200
+ :ivar enabled (bool): Whether timing is enabled.
201
+ :ivar report_every (int): Number of frames between reports.
202
+ """
203
+
204
+ enabled: bool = False
205
+ report_every: int = 60
206
+
207
+
208
+ @dataclass
209
+ class FrameTimer:
210
+ """
211
+ Simple frame timer for marking and reporting time intervals.
212
+
213
+ :ivar config (FrameTimerConfig): Configuration for the timer.
214
+ :ivar formatter (FrameTimingFormatter): Formatter for timing reports.
215
+ :ivar marks (Dict[str, float]): Recorded time marks.
216
+ """
217
+
218
+ config: FrameTimerConfig = field(default_factory=FrameTimerConfig)
219
+ formatter: FrameTimingFormatter = field(
220
+ default_factory=FrameTimingFormatter
221
+ )
222
+ marks: Dict[str, float] = field(default_factory=dict)
223
+
224
+ def clear(self):
225
+ """Clear all recorded marks."""
226
+ if not self.config.enabled:
227
+ return
228
+ self.marks.clear()
229
+
230
+ def mark(self, name: str):
231
+ """
232
+ Record a time mark with the given name.
233
+
234
+ :param name: Name of the mark.
235
+ :type name: str
236
+ """
237
+ if not self.config.enabled:
238
+ return
239
+ self.marks[name] = perf_counter()
240
+
241
+ def report_ms(self) -> Dict[str, float]:
242
+ """
243
+ Returns diffs between consecutive marks in insertion order.
244
+
245
+ :return: Dictionary mapping "start->end" to time difference in milliseconds.
246
+ :rtype: Dict[str, float]
247
+ """
248
+ if not self.config.enabled:
249
+ return {}
250
+ keys = list(self.marks.keys())
251
+ out: Dict[str, float] = {}
252
+ for a, b in zip(keys, keys[1:]):
253
+ out[f"{a}->{b}"] = (self.marks[b] - self.marks[a]) * 1000.0
254
+ return out
255
+
256
+ def should_report(self, frame_index: int) -> bool:
257
+ """
258
+ Determine if a report should be emitted for the given frame index.
259
+
260
+ :param frame_index: Current frame index.
261
+ :type frame_index: int
262
+ :return: True if a report should be emitted, False otherwise.
263
+ :rtype: bool
264
+ """
265
+ return (
266
+ self.config.enabled
267
+ and self.config.report_every > 0
268
+ and frame_index > 0
269
+ and (frame_index % self.config.report_every == 0)
270
+ )
271
+
272
+ def emit(self, frame_index: int):
273
+ """
274
+ Emit a timing report to the performance logger.
275
+
276
+ :param frame_index: Current frame index.
277
+ :type frame_index: int
278
+ """
279
+ if not self.config.enabled:
280
+ return
281
+ diffs = self.report_ms()
282
+ report = self.formatter.make_report(frame_index, diffs)
283
+ perf_logger.info(self.formatter.format(report))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-arcade-core
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: Tiny scene-based game loop core for small arcade games.
5
5
  License: Copyright (c) 2025 Santiago Rincón
6
6
 
@@ -0,0 +1,92 @@
1
+ mini_arcade_core/__init__.py,sha256=AdhR8GzhJnPYBkwIdNag1cnB8e4JqCHNZxsZfrw5vCQ,2564
2
+ mini_arcade_core/backend/__init__.py,sha256=J1wZBHX-aqmBP3zh_ey9PK2b_gnWp72zxMfKcs3iwSw,274
3
+ mini_arcade_core/backend/backend.py,sha256=tJYjHqjWZst8P3uikStndEHIVWLvF0ypmibVtCErbag,8699
4
+ mini_arcade_core/backend/events.py,sha256=5Ohve3CQ6n2CztiOhbCoz6yFDY4z0j4v4R9FBKRDRjc,2929
5
+ mini_arcade_core/backend/keys.py,sha256=LTg20SwLBI3kpPIiTNpq2yBft_QUGj-iNFSNm9M-Fus,3010
6
+ mini_arcade_core/backend/sdl_map.py,sha256=_yBRtvaFUcQKy1kcoIf-SPhbbKEW7dzvzBcI6TLmKjc,2060
7
+ mini_arcade_core/backend/types.py,sha256=EW0bW4MvsEZKot0Z1h_5LuFSzoYGiJBphTquBz4oXf4,244
8
+ mini_arcade_core/bus.py,sha256=2Etpoa-UWhk33xJjqDlY5YslPDJEjxNoIEVtF3C73vs,1558
9
+ mini_arcade_core/engine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ mini_arcade_core/engine/cheats.py,sha256=jMx2a8YnaNCkCG5MPmIzz4uHuS7-_aYf0J45cv2-3v0,5569
11
+ mini_arcade_core/engine/commands.py,sha256=Cw1tAwVRO5U2--hFX1Jq00LH_84oe1Oqw-Ngc0RnkGI,5383
12
+ mini_arcade_core/engine/game.py,sha256=DDdvlv0cN4sjBKtoIuP19W3YQCF1LwZbC_jYCz-1E4U,5767
13
+ mini_arcade_core/engine/game_config.py,sha256=4AP8n0Uk1HKEdPLOrV1xsySzBljAh8VhZASNrxPIMMc,1034
14
+ mini_arcade_core/engine/gameplay_settings.py,sha256=W8WBwfAvGZftkL4aMnOTx6SsGxwG-9Ou1Ey0AeWPCxs,549
15
+ mini_arcade_core/engine/loop/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ mini_arcade_core/engine/loop/config.py,sha256=Sj1LrdnD_aACmHUuRQuKB7bDbDZy60WVe8sJHRmzmIU,461
17
+ mini_arcade_core/engine/loop/hooks.py,sha256=nmZi-35iMsZoPedTdZzsIKJP6O_iFUeKjB8xc4-XTHU,2417
18
+ mini_arcade_core/engine/loop/runner.py,sha256=uB4onDMO6lWFt40YNJsovkVHXiyaVdEuEFj8kYqWn6k,8858
19
+ mini_arcade_core/engine/loop/state.py,sha256=fzXQ9GP05PVNXEBTgIwA4qjMujxdUae3CXM6uRQz92Y,858
20
+ mini_arcade_core/engine/managers.py,sha256=eQJYe-xYtRha-FWxzJ3DcpwlcHwiT5sGt4oCD9ZPxEE,664
21
+ mini_arcade_core/engine/render/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ mini_arcade_core/engine/render/context.py,sha256=igoBmsasv3AJtrIIe2IGkjHXEm5ouEobvdN7Gmm0vxY,1327
23
+ mini_arcade_core/engine/render/effects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ mini_arcade_core/engine/render/effects/base.py,sha256=uix-kfzvN5j3dx655h_Yxe-a3rN1WTtN4tj5zeqFi3c,2357
25
+ mini_arcade_core/engine/render/effects/crt.py,sha256=75ejQVNhftN_GwZV9CYkHv2D672V84zDygmO9h_fo1A,2154
26
+ mini_arcade_core/engine/render/effects/registry.py,sha256=ZBbyyhF4K2gxb4f6hV35uh6RuSj7kOhQgcxwKocqJpY,1212
27
+ mini_arcade_core/engine/render/effects/vignette.py,sha256=K87CjIWpjlWN_tJVrnY3tW2njOvLvnuRezCwk05OgVw,2610
28
+ mini_arcade_core/engine/render/frame_packet.py,sha256=nYHvR7CHlIZa6ZazmPO2dU2P91vEkBjBzUVQGrOkaYc,624
29
+ mini_arcade_core/engine/render/packet.py,sha256=OiAPwGoVHo04OcUWMAoA_N1AFPUMyf8yxNgJthGj4-c,1440
30
+ mini_arcade_core/engine/render/passes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
+ mini_arcade_core/engine/render/passes/base.py,sha256=LWgWhfafbCvRKIFbt3koW-ibjYxMfKOIXyLNazCakcM,864
32
+ mini_arcade_core/engine/render/passes/begin_frame.py,sha256=RowMYnkSSQVlMCDVRHXEiy4Hb5Ru-VPBqxsmMzzkXuU,706
33
+ mini_arcade_core/engine/render/passes/end_frame.py,sha256=BBNyVhD8s7B9c_s373g9DDH_eVNGGVnMl5n0GhKIgiw,767
34
+ mini_arcade_core/engine/render/passes/lighting.py,sha256=ugmHHNNZWArL_Xs6-1SQIxLptUTQwe4M2sSPEk8X-7s,677
35
+ mini_arcade_core/engine/render/passes/postfx.py,sha256=YLLcJb1qwNwxg40f_6Nh1xXeGN1tsCnGd8_FdKsfRAM,1424
36
+ mini_arcade_core/engine/render/passes/ui.py,sha256=cct8v0Jjqv6w77IlKz2medPkQjPvG8cCifRv943qyFM,1129
37
+ mini_arcade_core/engine/render/passes/world.py,sha256=OhvDB8aPTkFGlHiROlL9A13pdjWAYGbo8NadkWCUK-c,1522
38
+ mini_arcade_core/engine/render/pipeline.py,sha256=A-Pxw0t0RbW_pswVtBEGNIgC-1dSNqwaK8p7JoXiw8k,3288
39
+ mini_arcade_core/engine/render/render_service.py,sha256=1ueir8MZ6Six5gAHt5StoICPAbyppX4DqzWb8HEuS9g,531
40
+ mini_arcade_core/engine/render/viewport.py,sha256=Fi7O04KRC6d3s01sP0cfchdwegt6s_vOdwomck9yryc,5972
41
+ mini_arcade_core/engine/scenes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
+ mini_arcade_core/engine/scenes/models.py,sha256=UKKGUWyFf_XFC09hICTFQ6_szDXytoI5KauCmr9ugoM,1217
43
+ mini_arcade_core/engine/scenes/scene_manager.py,sha256=xGzMH7tBsHExN9aYLZsx462aY9r5j-cDNqQvwnK3cI8,6455
44
+ mini_arcade_core/runtime/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
+ mini_arcade_core/runtime/audio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
+ mini_arcade_core/runtime/audio/audio_adapter.py,sha256=lnP35txPzSKX1_il0nXcK7RMF5Qp9Qhi9YMh_7LTdPM,588
47
+ mini_arcade_core/runtime/audio/audio_port.py,sha256=jBd9WabN41uK3MHjg_1n4AOw83NivJlGE2m430WZTnk,831
48
+ mini_arcade_core/runtime/capture/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
+ mini_arcade_core/runtime/capture/capture_adapter.py,sha256=XBtiKw3AS2dzB4QogPm9kjhiQAenS25guX87tg-zK58,4882
50
+ mini_arcade_core/runtime/capture/capture_port.py,sha256=niHi0pAo10mC9p73FxFkYBIGLOLRN0PiOvxE4Zgo5fM,1162
51
+ mini_arcade_core/runtime/context.py,sha256=ONKQryO3KEOOqHaByxCUola07kdjrnvr4WfXwgwTobk,1777
52
+ mini_arcade_core/runtime/file/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
+ mini_arcade_core/runtime/file/file_adapter.py,sha256=09q7G9Qijml9d4AAjo6HLC1yuoVTjE_7xaT8apT4mk0,523
54
+ mini_arcade_core/runtime/file/file_port.py,sha256=p1MouCSHXZw--rWNMw3aYBLU-of8mXaT_suopczPtM8,608
55
+ mini_arcade_core/runtime/input/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
+ mini_arcade_core/runtime/input/input_adapter.py,sha256=vExQiwFIWTI3zYD8lmnD9TvoQPZvJfI6IINPJUqAdQ0,1467
57
+ mini_arcade_core/runtime/input/input_port.py,sha256=d4ptftwf92_LJdyaUMFxIsLHXBINzQyJACHn4laNyxQ,746
58
+ mini_arcade_core/runtime/input_frame.py,sha256=34-RAfOD-YScVLyRQrarpm7byFTHjsWM77lIH0JsmT8,2384
59
+ mini_arcade_core/runtime/render/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
+ mini_arcade_core/runtime/render/render_port.py,sha256=Sqp-JBh-iRzzGtgnO_nU1KiJEqyrTYPRDQbg04HdR0A,507
61
+ mini_arcade_core/runtime/scene/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
+ mini_arcade_core/runtime/scene/scene_query_adapter.py,sha256=FNkqXgwxfugX_xqqFlZl0ELXsrW_gco5Au0tJhMGLgQ,909
63
+ mini_arcade_core/runtime/scene/scene_query_port.py,sha256=qTikQVxOkJCdoMoH_lbe_ctJj7SWeJnnqDo6Ee0N_pQ,1019
64
+ mini_arcade_core/runtime/services.py,sha256=iYcXt2CTapgDzSb54DsPasYZ4jTN7tA_B0lV1Sl5b1g,1243
65
+ mini_arcade_core/runtime/window/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
+ mini_arcade_core/runtime/window/window_adapter.py,sha256=VLZGYBVl7sGMmnk5mVowDleTyciAfE-Tc2woNFvRrgE,2890
67
+ mini_arcade_core/runtime/window/window_port.py,sha256=HBy2OjsZzlxbBDQiTqlKEbIaejpN1zDp5whgvKxZxaY,2322
68
+ mini_arcade_core/scenes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
69
+ mini_arcade_core/scenes/autoreg.py,sha256=wsuY7YUSZFmDyToKHFriAG78OU48-7J4BfL_X6T5GBg,1037
70
+ mini_arcade_core/scenes/debug_overlay.py,sha256=t7zWeTxosCUWj3gBDkYF2448EBM5zwLCOEmWVHXghMk,2495
71
+ mini_arcade_core/scenes/registry.py,sha256=DHliUGGiSLWugtRU9R6JeH5gK3GUDICmS-3iie6GtH8,3631
72
+ mini_arcade_core/scenes/sim_scene.py,sha256=32GVR9XHMak-afyyH9M6_UkCPAjRz8XiUKj2lcpAGAE,1061
73
+ mini_arcade_core/scenes/systems/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
+ mini_arcade_core/scenes/systems/base_system.py,sha256=GfMrXsO8ynW3xOxWeav7Ug5XUbRnbF0vo8VzmG7gpec,1075
75
+ mini_arcade_core/scenes/systems/system_pipeline.py,sha256=Cy9y1DclbMLZZ-yx7OKYe34ORoGLNa6dReQfOdiO8SY,1642
76
+ mini_arcade_core/spaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
+ mini_arcade_core/spaces/d2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
+ mini_arcade_core/spaces/d2/boundaries2d.py,sha256=xeTnd0pW5DKfqaKsfSBXnufeb45aXNIspgHRyLXWejo,2804
79
+ mini_arcade_core/spaces/d2/collision2d.py,sha256=5IvgLnyVb8i0uzzZuum1noWsNhoxcvHOLaHkmrTMTxQ,1710
80
+ mini_arcade_core/spaces/d2/geometry2d.py,sha256=FuYzef-XdOyb1aeGLJbxINxr0WJHnqFFBgtbPi1WonY,1716
81
+ mini_arcade_core/spaces/d2/kinematics2d.py,sha256=AJ3DhPXNgm6wZYwCljMIE4_2BYx3E2rPcwhXTgQALkU,2030
82
+ mini_arcade_core/spaces/d2/physics2d.py,sha256=OQT7r-zMtmoKD2aWCSNmRAdI0OGIpxGX-pLR8LcAMbQ,1854
83
+ mini_arcade_core/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
84
+ mini_arcade_core/ui/menu.py,sha256=t2YpwiKqy4KZHfU9U7c6CUE3SNamBSyipWnd9Y4BVxA,25738
85
+ mini_arcade_core/utils/__init__.py,sha256=id1C0au8r1oIzGha42xXwnI9ojcU1hxPgto6QSh9H8c,236
86
+ mini_arcade_core/utils/deprecated_decorator.py,sha256=yrrW2ZqPskK-4MUTyIrMb465Wc54X2poV53ZQutZWqc,1140
87
+ mini_arcade_core/utils/logging.py,sha256=ygKpey6nikp30PrNDP_yRs8pxPPRbsQ0ivR6LUuEn3Q,6413
88
+ mini_arcade_core/utils/profiler.py,sha256=vLzrxDfAplgKGxpuzk4eFJx4t5DU5M3DQAn6sfS5D_4,8733
89
+ mini_arcade_core-1.2.0.dist-info/METADATA,sha256=PeD3ImQ4FG5bjuYKFdLThR59fOvr-lgOS3OaF1XVRis,8188
90
+ mini_arcade_core-1.2.0.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
91
+ mini_arcade_core-1.2.0.dist-info/licenses/LICENSE,sha256=3lHAuV0584cVS5vAqi2uC6GcsVgxUijvwvtZckyvaZ4,1096
92
+ mini_arcade_core-1.2.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.3.0
2
+ Generator: poetry-core 2.3.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,284 +0,0 @@
1
- """
2
- Input manager for handling input bindings and commands.
3
- """
4
-
5
- # TODO: Implement this manager into the new input system
6
- # Justification: These module will be used later.
7
- # pylint: disable=no-name-in-module,import-error,used-before-assignment
8
-
9
- from __future__ import annotations
10
-
11
- import logging
12
- from dataclasses import dataclass
13
- from typing import TYPE_CHECKING, Callable, Dict, Optional
14
-
15
- from mini_arcade_core.backend import Event, EventType
16
- from mini_arcade_core.keymaps import Key
17
-
18
- if TYPE_CHECKING:
19
- from mini_arcade_core.engine.commands import BaseCommand, BaseSceneCommand
20
- from mini_arcade_core.scenes.scene import Scene
21
-
22
- logger = logging.getLogger(__name__)
23
-
24
-
25
- Predicate = Callable[["Event"], bool]
26
-
27
-
28
- @dataclass(frozen=True)
29
- class InputBinding:
30
- """
31
- Defines an input binding.
32
-
33
- :ivar action (str): The action name.
34
- :ivar command (BaseCommand): The command to execute.
35
- :ivar predicate (Predicate): Predicate to match events.
36
- """
37
-
38
- action: str
39
- command: BaseCommand
40
- predicate: Predicate # decides whether this binding matches an event
41
-
42
-
43
- class InputManager:
44
- """
45
- Manager for handling input bindings and commands.
46
- """
47
-
48
- def __init__(self):
49
- # event_type -> key -> action -> command
50
- self._bindings: Dict[EventType, Dict[Key, Dict[str, BaseCommand]]] = {}
51
-
52
- # Justification: The method needs multiple optional parameters for flexibility.
53
- # pylint: disable=too-many-arguments
54
- def bind(
55
- self,
56
- event_type: EventType,
57
- action: str,
58
- command: BaseCommand,
59
- *,
60
- key: Optional[Key] = None,
61
- button: Optional[int] = None,
62
- predicate: Optional[Predicate] = None,
63
- ):
64
- """
65
- Generic binding.
66
-
67
- You can filter by:
68
- - key: for KEYDOWN/KEYUP
69
- - button: for MOUSEBUTTONDOWN/MOUSEBUTTONUP (if your Event exposes it)
70
- - predicate: custom matcher (for anything)
71
-
72
- :param event_type: The type of event to bind to.
73
- :type event_type: EventType
74
-
75
- :param action: The action name for the binding.
76
- :type action: str
77
-
78
- :param command: The command to execute when the binding is triggered.
79
- :type command: BaseCommand
80
-
81
- :param key: Optional key to filter KEYDOWN/KEYUP events.
82
- :type key: Key | None
83
-
84
- :param button: Optional button to filter MOUSEBUTTONDOWN/MOUSEBUTTONUP events.
85
- :type button: int | None
86
-
87
- :param predicate: Optional custom predicate to match events.
88
- :type predicate: Predicate | None
89
- """
90
- logger.debug(
91
- f"Binding {action} to {event_type} with key={key}, button={button}"
92
- )
93
-
94
- def default_predicate(ev: Event) -> bool:
95
- if key is not None and getattr(ev, "key", None) != key:
96
- return False
97
- if button is not None and getattr(ev, "button", None) != button:
98
- return False
99
- return True
100
-
101
- pred = predicate or default_predicate
102
- self._bindings.setdefault(event_type, []).append(
103
- InputBinding(action=action, command=command, predicate=pred)
104
- )
105
-
106
- # pylint: enable=too-many-arguments
107
-
108
- def unbind(self, event_type: EventType, action: str):
109
- """
110
- Remove bindings by action for an event type.
111
-
112
- :param event_type: The type of event to unbind from.
113
- :type event_type: EventType
114
-
115
- :param action: The action name of the binding to remove.
116
- :type action: str
117
- """
118
- lst = self._bindings.get(event_type, [])
119
- self._bindings[event_type] = [b for b in lst if b.action != action]
120
-
121
- def clear(self):
122
- """Clear all input bindings."""
123
- self._bindings.clear()
124
-
125
- def handle_event(self, event: Event, scene: Scene):
126
- """
127
- Handle an incoming event, executing any matching commands.
128
-
129
- :param event: The event to handle.
130
- :type event: Event
131
-
132
- :param scene: The current scene context.
133
- :type scene: Scene
134
- """
135
- et = event.type
136
-
137
- for binding in self._bindings.get(et, []):
138
- if binding.predicate(event):
139
- to_inject = (
140
- scene.model
141
- if isinstance(binding.command, BaseSceneCommand)
142
- else scene.game
143
- )
144
- binding.command.execute(to_inject)
145
-
146
- def on_quit(self, command: BaseCommand, action: str = "quit"):
147
- """
148
- Bind a command to the QUIT event.
149
-
150
- :param command: The command to execute on quit.
151
- :type command: BaseCommand
152
-
153
- :param action: The action name for the binding.
154
- :type action: str
155
- """
156
- self.bind(EventType.QUIT, action=action, command=command)
157
-
158
- def on_key_down(self, key: Key, command: BaseCommand, action: str):
159
- """
160
- Bind a command to a key down event.
161
-
162
- :param key: The key to bind to.
163
- :type key: Key
164
-
165
- :param command: The command to execute on key down.
166
- :type command: BaseCommand
167
-
168
- :param action: The action name for the binding.
169
- :type action: str
170
- """
171
- self.bind(EventType.KEYDOWN, key=key, action=action, command=command)
172
-
173
- def on_key_up(self, key: Key, command: BaseCommand, action: str):
174
- """
175
- Bind a command to a key up event.
176
-
177
- :param key: The key to bind to.
178
- :type key: Key
179
-
180
- :param command: The command to execute on key up.
181
- :type command: BaseCommand
182
-
183
- :param action: The action name for the binding.
184
- :type action: str
185
- """
186
- self.bind(EventType.KEYUP, key=key, action=action, command=command)
187
-
188
- def on_mouse_button_down(
189
- self, button: int, command: BaseCommand, action: str
190
- ):
191
- """
192
- Bind a command to a mouse button down event.
193
-
194
- :param button: The mouse button to bind to.
195
- :type button: int
196
-
197
- :param command: The command to execute on mouse button down.
198
- :type command: BaseCommand
199
-
200
- :param action: The action name for the binding.
201
- :type action: str
202
- """
203
- self.bind(
204
- EventType.MOUSEBUTTONDOWN,
205
- button=button,
206
- action=action,
207
- command=command,
208
- )
209
-
210
- def on_mouse_button_up(
211
- self, button: int, command: BaseCommand, action: str
212
- ):
213
- """
214
- Bind a command to a mouse button up event.
215
-
216
- :param button: The mouse button to bind to.
217
- :type button: int
218
-
219
- :param command: The command to execute on mouse button up.
220
- :type command: BaseCommand
221
-
222
- :param action: The action name for the binding.
223
- :type action: str
224
- """
225
- self.bind(
226
- EventType.MOUSEBUTTONUP,
227
- button=button,
228
- action=action,
229
- command=command,
230
- )
231
-
232
- def on_mouse_motion(
233
- self, command: BaseCommand, action: str = "mouse_motion"
234
- ):
235
- """
236
- Bind a command to mouse motion events.
237
-
238
- :param command: The command to execute on mouse motion.
239
- :type command: BaseCommand
240
-
241
- :param action: The action name for the binding.
242
- :type action: str
243
- """
244
- self.bind(EventType.MOUSEMOTION, action=action, command=command)
245
-
246
- def on_mouse_wheel(
247
- self, command: BaseCommand, action: str = "mouse_wheel"
248
- ):
249
- """
250
- Bind a command to mouse wheel events.
251
-
252
- :param command: The command to execute on mouse wheel.
253
- :type command: BaseCommand
254
-
255
- :param action: The action name for the binding.
256
- :type action: str
257
- """
258
- self.bind(EventType.MOUSEWHEEL, action=action, command=command)
259
-
260
- def on_window_resized(
261
- self, command: BaseCommand, action: str = "window_resized"
262
- ):
263
- """
264
- Bind a command to window resized events.
265
-
266
- :param command: The command to execute on window resize.
267
- :type command: BaseCommand
268
-
269
- :param action: The action name for the binding.
270
- :type action: str
271
- """
272
- self.bind(EventType.WINDOWRESIZED, action=action, command=command)
273
-
274
- def on_text_input(self, command: BaseCommand, action: str = "text_input"):
275
- """
276
- Bind a command to text input events.
277
-
278
- :param command: The command to execute on text input.
279
- :type command: BaseCommand
280
-
281
- :param action: The action name for the binding.
282
- :type action: str
283
- """
284
- self.bind(EventType.TEXTINPUT, action=action, command=command)