mini-arcade-core 1.0.1__tar.gz → 1.0.2__tar.gz

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 (67) hide show
  1. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/PKG-INFO +1 -1
  2. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/pyproject.toml +1 -1
  3. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/engine/commands.py +26 -0
  4. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/engine/game.py +5 -0
  5. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/scene/scene_adapter.py +29 -1
  6. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/scene/scene_port.py +21 -0
  7. mini_arcade_core-1.0.2/src/mini_arcade_core/scenes/debug_overlay.py +67 -0
  8. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/scenes/registry.py +9 -8
  9. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/LICENSE +0 -0
  10. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/README.md +0 -0
  11. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/__init__.py +0 -0
  12. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/backend/__init__.py +0 -0
  13. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/backend/backend.py +0 -0
  14. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/backend/events.py +0 -0
  15. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/backend/keys.py +0 -0
  16. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/backend/sdl_map.py +0 -0
  17. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/backend/types.py +0 -0
  18. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/bus.py +0 -0
  19. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/engine/__init__.py +0 -0
  20. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/engine/render/__init__.py +0 -0
  21. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/engine/render/packet.py +0 -0
  22. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/engine/render/pipeline.py +0 -0
  23. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/engine/render/viewport.py +0 -0
  24. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/managers/__init__.py +0 -0
  25. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/managers/cheats.py +0 -0
  26. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/managers/inputs.py +0 -0
  27. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/__init__.py +0 -0
  28. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/audio/__init__.py +0 -0
  29. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/audio/audio_adapter.py +0 -0
  30. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/audio/audio_port.py +0 -0
  31. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/capture/__init__.py +0 -0
  32. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/capture/capture_adapter.py +0 -0
  33. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/capture/capture_port.py +0 -0
  34. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/context.py +0 -0
  35. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/file/__init__.py +0 -0
  36. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/file/file_adapter.py +0 -0
  37. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/file/file_port.py +0 -0
  38. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/input/__init__.py +0 -0
  39. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/input/input_adapter.py +0 -0
  40. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/input/input_port.py +0 -0
  41. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/input_frame.py +0 -0
  42. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/scene/__init__.py +0 -0
  43. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/services.py +0 -0
  44. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/window/__init__.py +0 -0
  45. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/window/window_adapter.py +0 -0
  46. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/runtime/window/window_port.py +0 -0
  47. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/scenes/__init__.py +0 -0
  48. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/scenes/autoreg.py +0 -0
  49. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/scenes/sim_scene.py +0 -0
  50. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/scenes/systems/__init__.py +0 -0
  51. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/scenes/systems/base_system.py +0 -0
  52. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/scenes/systems/system_pipeline.py +0 -0
  53. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/sim/__init__.py +0 -0
  54. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/sim/protocols.py +0 -0
  55. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/sim/runner.py +0 -0
  56. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/spaces/__init__.py +0 -0
  57. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/spaces/d2/__init__.py +0 -0
  58. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/spaces/d2/boundaries2d.py +0 -0
  59. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/spaces/d2/collision2d.py +0 -0
  60. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/spaces/d2/geometry2d.py +0 -0
  61. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/spaces/d2/kinematics2d.py +0 -0
  62. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/spaces/d2/physics2d.py +0 -0
  63. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/ui/__init__.py +0 -0
  64. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/ui/menu.py +0 -0
  65. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/utils/__init__.py +0 -0
  66. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/utils/deprecated_decorator.py +0 -0
  67. {mini_arcade_core-1.0.1 → mini_arcade_core-1.0.2}/src/mini_arcade_core/utils/logging.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mini-arcade-core
3
- Version: 1.0.1
3
+ Version: 1.0.2
4
4
  Summary: Tiny scene-based game loop core for small arcade games.
5
5
  License: Copyright (c) 2025 Santiago Rincón
6
6
 
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [project]
6
6
  name = "mini-arcade-core"
7
- version = "1.0.1"
7
+ version = "1.0.2"
8
8
  description = "Tiny scene-based game loop core for small arcade games."
9
9
  authors = [
10
10
  { name = "Santiago Rincon", email = "rincores@gmail.com" },
@@ -7,6 +7,8 @@ from __future__ import annotations
7
7
  from dataclasses import dataclass, field
8
8
  from typing import TYPE_CHECKING, List, Optional, Protocol, TypeVar
9
9
 
10
+ from mini_arcade_core.runtime.scene.scene_port import ScenePolicy
11
+
10
12
  if TYPE_CHECKING:
11
13
  from mini_arcade_core.runtime.services import RuntimeServices
12
14
 
@@ -167,3 +169,27 @@ class ChangeSceneCommand(Command):
167
169
  context: CommandContext,
168
170
  ):
169
171
  context.services.scenes.change(self.scene_id)
172
+
173
+
174
+ @dataclass(frozen=True)
175
+ class ToggleDebugOverlayCommand(Command):
176
+ """Toggle the debug overlay scene."""
177
+
178
+ DEBUG_OVERLAY_ID = "debug_overlay"
179
+
180
+ def execute(self, context: CommandContext) -> None:
181
+ scenes = context.services.scenes
182
+ if scenes.has_scene(self.DEBUG_OVERLAY_ID):
183
+ scenes.remove_scene(self.DEBUG_OVERLAY_ID)
184
+ return
185
+
186
+ scenes.push(
187
+ self.DEBUG_OVERLAY_ID,
188
+ as_overlay=True,
189
+ policy=ScenePolicy(
190
+ blocks_update=False,
191
+ blocks_input=False,
192
+ is_opaque=False,
193
+ receives_input=False,
194
+ ),
195
+ )
@@ -10,10 +10,12 @@ from typing import Dict, Literal
10
10
 
11
11
  from mini_arcade_core.backend import Backend, WindowSettings
12
12
  from mini_arcade_core.backend.events import EventType
13
+ from mini_arcade_core.backend.keys import Key
13
14
  from mini_arcade_core.engine.commands import (
14
15
  CommandContext,
15
16
  CommandQueue,
16
17
  QuitCommand,
18
+ ToggleDebugOverlayCommand,
17
19
  )
18
20
  from mini_arcade_core.engine.render.packet import RenderPacket
19
21
  from mini_arcade_core.engine.render.pipeline import RenderPipeline
@@ -248,6 +250,9 @@ class Game:
248
250
  w, h = e.size
249
251
  logger.debug(f"Window resized event: {w}x{h}")
250
252
  self.services.window.on_window_resized(w, h)
253
+ # if F1 pressed, toggle debug overlay
254
+ if e.type == EventType.KEYDOWN and e.key == Key.F1:
255
+ self.command_queue.push(ToggleDebugOverlayCommand())
251
256
  timer.mark("events_polled")
252
257
 
253
258
  input_frame = self.services.input.build(events, frame_index, dt)
@@ -94,4 +94,32 @@ class SceneAdapter(ScenePort):
94
94
 
95
95
  def input_entry(self):
96
96
  vis = self.visible_entries()
97
- return vis[-1] if vis else None
97
+ if not vis:
98
+ return None
99
+
100
+ # If some scene blocks input, only scenes at/above it can receive.
101
+ start_idx = 0
102
+ for idx in range(len(vis) - 1, -1, -1):
103
+ if vis[idx].policy.blocks_input:
104
+ start_idx = idx
105
+ break
106
+
107
+ candidates = vis[start_idx:]
108
+
109
+ # Pick the top-most candidate that actually receives input.
110
+ for entry in reversed(candidates):
111
+ if entry.policy.receives_input:
112
+ return entry
113
+
114
+ return None
115
+
116
+ def has_scene(self, scene_id: str) -> bool:
117
+ return any(item.entry.scene_id == scene_id for item in self._stack)
118
+
119
+ def remove_scene(self, scene_id: str) -> None:
120
+ # remove first match from top (overlay is usually near top)
121
+ for i in range(len(self._stack) - 1, -1, -1):
122
+ if self._stack[i].entry.scene_id == scene_id:
123
+ item = self._stack.pop(i)
124
+ item.entry.scene.on_exit()
125
+ return
@@ -23,11 +23,13 @@ class ScenePolicy:
23
23
  blocks_update: if True, scenes below do not tick/update (pause modal)
24
24
  blocks_input: if True, scenes below do not receive input
25
25
  is_opaque: if True, scenes below are not rendered
26
+ receives_input: if True, scene can receive input
26
27
  """
27
28
 
28
29
  blocks_update: bool = False
29
30
  blocks_input: bool = False
30
31
  is_opaque: bool = False
32
+ receives_input: bool = True
31
33
 
32
34
 
33
35
  @dataclass(frozen=True)
@@ -147,3 +149,22 @@ class ScenePort:
147
149
  :return: The SceneEntry that receives input, or None if no scenes are active.
148
150
  :rtype: SceneEntry | None
149
151
  """
152
+
153
+ def has_scene(self, scene_id: str) -> bool:
154
+ """
155
+ Check if a scene with the given ID exists in the stack.
156
+
157
+ :param scene_id: Identifier of the scene to check.
158
+ :type scene_id: str
159
+
160
+ :return: True if the scene exists in the stack, False otherwise.
161
+ :rtype: bool
162
+ """
163
+
164
+ def remove_scene(self, scene_id: str) -> None:
165
+ """
166
+ Remove a scene with the given ID from the stack.
167
+
168
+ :param scene_id: Identifier of the scene to remove.
169
+ :type scene_id: str
170
+ """
@@ -0,0 +1,67 @@
1
+ """
2
+ Debug overlay scene that displays FPS, window size, and scene stack information.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from mini_arcade_core.engine.render.packet import RenderPacket
8
+ from mini_arcade_core.runtime.context import RuntimeContext
9
+ from mini_arcade_core.runtime.input_frame import InputFrame
10
+ from mini_arcade_core.scenes.autoreg import register_scene
11
+ from mini_arcade_core.sim.protocols import SimScene
12
+
13
+
14
+ @register_scene("debug_overlay")
15
+ class DebugOverlayScene(SimScene):
16
+ """
17
+ A debug overlay scene that displays FPS, window size, and scene stack information.
18
+ """
19
+
20
+ def __init__(self, ctx: RuntimeContext):
21
+ super().__init__(ctx)
22
+ self._accum = 0.0
23
+ self._frames = 0
24
+ self._fps = 0.0
25
+
26
+ def tick(self, input_frame: InputFrame, dt: float) -> RenderPacket:
27
+ self._accum += dt
28
+ self._frames += 1
29
+ if self._accum >= 0.5:
30
+ self._fps = self._frames / self._accum
31
+ self._accum = 0.0
32
+ self._frames = 0
33
+
34
+ services = (
35
+ self.context.services
36
+ ) # or ctx.services (depends on your scene base)
37
+ # Justification: type checker can't infer type here
38
+ # pylint: disable=assignment-from-no-return
39
+ vp = services.window.get_viewport()
40
+ stack = services.scenes.visible_entries()
41
+ # pylint: enable=assignment-from-no-return
42
+
43
+ lines = [
44
+ f"FPS: {self._fps:5.1f}",
45
+ f"dt: {dt*1000.0:5.2f} ms",
46
+ f"virtual: {vp.virtual_w}x{vp.virtual_h}",
47
+ f"window: {vp.window_w}x{vp.window_h}",
48
+ f"scale: {vp.scale:.3f}",
49
+ f"offset: ({vp.offset_x},{vp.offset_y})",
50
+ "stack:",
51
+ ]
52
+ for e in stack:
53
+ lines.append(f" - {e.scene_id} overlay={e.is_overlay}")
54
+
55
+ def draw(backend):
56
+ # translucent background panel
57
+ backend.draw_rect(
58
+ 8, 8, 360, 18 * (len(lines) + 1), color=(0, 0, 0, 0.65)
59
+ )
60
+ y = 14
61
+ for line in lines:
62
+ backend.draw_text(
63
+ 16, y, line, color=(255, 255, 255), font_size=14
64
+ )
65
+ y += 18
66
+
67
+ return RenderPacket(ops=[draw])
@@ -93,22 +93,23 @@ class SceneRegistry:
93
93
  for scene_id, cls in catalog.items():
94
94
  self.register_cls(scene_id, cls)
95
95
 
96
- def discover(self, package: str) -> "SceneRegistry":
96
+ def discover(self, *packages: str) -> "SceneRegistry":
97
97
  """
98
98
  Import all modules in a package so @scene decorators run.
99
99
 
100
- :param package: The package name to scan for scene modules.
101
- :type package: str
100
+ :param packages: The package names to scan for scene modules.
101
+ :type packages: str
102
102
 
103
103
  :return: The SceneRegistry instance (for chaining).
104
104
  :rtype: SceneRegistry
105
105
  """
106
- pkg = importlib.import_module(package)
107
- if not hasattr(pkg, "__path__"):
108
- return self # not a package
106
+ for package in packages:
107
+ pkg = importlib.import_module(package)
108
+ if not hasattr(pkg, "__path__"):
109
+ continue
109
110
 
110
- for mod in pkgutil.walk_packages(pkg.__path__, pkg.__name__ + "."):
111
- importlib.import_module(mod.name)
111
+ for mod in pkgutil.walk_packages(pkg.__path__, pkg.__name__ + "."):
112
+ importlib.import_module(mod.name)
112
113
 
113
114
  self.load_catalog(snapshot())
114
115
  return self