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.
- mini_arcade_core/__init__.py +14 -42
- mini_arcade_core/backend/__init__.py +1 -2
- mini_arcade_core/backend/backend.py +185 -154
- mini_arcade_core/backend/types.py +5 -1
- mini_arcade_core/engine/commands.py +26 -7
- mini_arcade_core/engine/game.py +79 -319
- mini_arcade_core/engine/game_config.py +40 -0
- mini_arcade_core/engine/gameplay_settings.py +24 -0
- mini_arcade_core/engine/loop/config.py +20 -0
- mini_arcade_core/engine/loop/hooks.py +77 -0
- mini_arcade_core/engine/loop/runner.py +272 -0
- mini_arcade_core/engine/loop/state.py +32 -0
- mini_arcade_core/engine/managers.py +24 -0
- mini_arcade_core/engine/render/context.py +0 -2
- mini_arcade_core/engine/render/effects/base.py +88 -0
- mini_arcade_core/engine/render/effects/crt.py +68 -0
- mini_arcade_core/engine/render/effects/registry.py +50 -0
- mini_arcade_core/engine/render/effects/vignette.py +79 -0
- mini_arcade_core/engine/render/passes/begin_frame.py +1 -1
- mini_arcade_core/engine/render/passes/end_frame.py +1 -1
- mini_arcade_core/engine/render/passes/postfx.py +25 -4
- mini_arcade_core/engine/render/passes/ui.py +1 -1
- mini_arcade_core/engine/render/passes/world.py +6 -6
- mini_arcade_core/engine/render/pipeline.py +7 -6
- mini_arcade_core/engine/render/viewport.py +10 -4
- mini_arcade_core/engine/scenes/__init__.py +0 -0
- mini_arcade_core/engine/scenes/models.py +54 -0
- mini_arcade_core/engine/scenes/scene_manager.py +213 -0
- mini_arcade_core/runtime/audio/audio_adapter.py +4 -3
- mini_arcade_core/runtime/audio/audio_port.py +0 -4
- mini_arcade_core/runtime/capture/capture_adapter.py +13 -6
- mini_arcade_core/runtime/capture/capture_port.py +0 -4
- mini_arcade_core/runtime/context.py +8 -6
- mini_arcade_core/runtime/scene/scene_query_adapter.py +31 -0
- mini_arcade_core/runtime/scene/scene_query_port.py +38 -0
- mini_arcade_core/runtime/services.py +3 -2
- mini_arcade_core/runtime/window/window_adapter.py +43 -41
- mini_arcade_core/runtime/window/window_port.py +3 -17
- mini_arcade_core/scenes/debug_overlay.py +5 -4
- mini_arcade_core/scenes/registry.py +11 -1
- mini_arcade_core/scenes/sim_scene.py +14 -14
- mini_arcade_core/ui/menu.py +54 -16
- mini_arcade_core/utils/__init__.py +2 -1
- mini_arcade_core/utils/logging.py +47 -18
- mini_arcade_core/utils/profiler.py +283 -0
- {mini_arcade_core-1.1.0.dist-info → mini_arcade_core-1.2.0.dist-info}/METADATA +1 -1
- mini_arcade_core-1.2.0.dist-info/RECORD +92 -0
- {mini_arcade_core-1.1.0.dist-info → mini_arcade_core-1.2.0.dist-info}/WHEEL +1 -1
- mini_arcade_core/managers/inputs.py +0 -284
- mini_arcade_core/runtime/scene/scene_adapter.py +0 -125
- mini_arcade_core/runtime/scene/scene_port.py +0 -170
- mini_arcade_core/sim/protocols.py +0 -41
- mini_arcade_core/sim/runner.py +0 -222
- mini_arcade_core-1.1.0.dist-info/RECORD +0 -80
- /mini_arcade_core/{managers → engine}/cheats.py +0 -0
- /mini_arcade_core/{managers → engine/loop}/__init__.py +0 -0
- /mini_arcade_core/{sim → engine/render/effects}/__init__.py +0 -0
- {mini_arcade_core-1.1.0.dist-info → mini_arcade_core-1.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -24,7 +24,7 @@ class UIPass:
|
|
|
24
24
|
"""Run the UI render pass."""
|
|
25
25
|
# UI overlays should be screen-space (no world transform / no clip unless you want it)
|
|
26
26
|
backend.clear_viewport_transform()
|
|
27
|
-
backend.clear_clip_rect()
|
|
27
|
+
backend.render.clear_clip_rect()
|
|
28
28
|
|
|
29
29
|
for fp in packets:
|
|
30
30
|
if not fp.is_overlay:
|
|
@@ -41,15 +41,15 @@ class WorldPass:
|
|
|
41
41
|
backend.set_viewport_transform(
|
|
42
42
|
ctx.viewport.offset_x, ctx.viewport.offset_y, ctx.viewport.scale
|
|
43
43
|
)
|
|
44
|
-
backend.set_clip_rect(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
ctx.viewport.
|
|
48
|
-
ctx.viewport.
|
|
44
|
+
backend.render.set_clip_rect(
|
|
45
|
+
0,
|
|
46
|
+
0,
|
|
47
|
+
ctx.viewport.virtual_w,
|
|
48
|
+
ctx.viewport.virtual_h,
|
|
49
49
|
)
|
|
50
50
|
try:
|
|
51
51
|
for op in packet.ops:
|
|
52
52
|
op(backend)
|
|
53
53
|
finally:
|
|
54
|
-
backend.clear_clip_rect()
|
|
54
|
+
backend.render.clear_clip_rect()
|
|
55
55
|
backend.clear_viewport_transform()
|
|
@@ -19,6 +19,7 @@ from dataclasses import dataclass, field
|
|
|
19
19
|
|
|
20
20
|
from mini_arcade_core.backend import Backend
|
|
21
21
|
from mini_arcade_core.engine.render.context import RenderContext
|
|
22
|
+
from mini_arcade_core.engine.render.frame_packet import FramePacket
|
|
22
23
|
from mini_arcade_core.engine.render.packet import RenderPacket
|
|
23
24
|
from mini_arcade_core.engine.render.passes.base import RenderPass
|
|
24
25
|
from mini_arcade_core.engine.render.passes.begin_frame import BeginFramePass
|
|
@@ -56,10 +57,10 @@ class RenderPipeline:
|
|
|
56
57
|
)
|
|
57
58
|
|
|
58
59
|
def render_frame(
|
|
59
|
-
self, backend: Backend, ctx: RenderContext, packets: list[
|
|
60
|
+
self, backend: Backend, ctx: RenderContext, packets: list[FramePacket]
|
|
60
61
|
):
|
|
61
62
|
"""
|
|
62
|
-
Render a frame using the provided Backend, RenderContext, and list of
|
|
63
|
+
Render a frame using the provided Backend, RenderContext, and list of FramePackets.
|
|
63
64
|
|
|
64
65
|
:param backend: Backend to use for rendering.
|
|
65
66
|
:type backend: Backend
|
|
@@ -67,8 +68,8 @@ class RenderPipeline:
|
|
|
67
68
|
:param ctx: RenderContext containing rendering state.
|
|
68
69
|
:type ctx: RenderContext
|
|
69
70
|
|
|
70
|
-
:param packets: List of
|
|
71
|
-
:type packets: list[
|
|
71
|
+
:param packets: List of FramePackets to render.
|
|
72
|
+
:type packets: list[FramePacket]
|
|
72
73
|
"""
|
|
73
74
|
for p in self.passes:
|
|
74
75
|
p.run(backend, ctx, packets)
|
|
@@ -98,8 +99,8 @@ class RenderPipeline:
|
|
|
98
99
|
)
|
|
99
100
|
|
|
100
101
|
backend.set_clip_rect(
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
0,
|
|
103
|
+
0,
|
|
103
104
|
viewport_state.viewport_w,
|
|
104
105
|
viewport_state.viewport_h,
|
|
105
106
|
)
|
|
@@ -4,6 +4,7 @@ Viewport management for virtual to screen coordinate transformations.
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
import math
|
|
7
8
|
from dataclasses import dataclass
|
|
8
9
|
from enum import Enum
|
|
9
10
|
|
|
@@ -130,10 +131,15 @@ class Viewport:
|
|
|
130
131
|
sy = window_h / self._virtual_h
|
|
131
132
|
scale = min(sx, sy) if self._mode == ViewportMode.FIT else max(sx, sy)
|
|
132
133
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
if self._mode == ViewportMode.FIT:
|
|
135
|
+
vw = int(math.floor(self._virtual_w * scale))
|
|
136
|
+
vh = int(math.floor(self._virtual_h * scale))
|
|
137
|
+
else: # FILL
|
|
138
|
+
vw = int(math.ceil(self._virtual_w * scale))
|
|
139
|
+
vh = int(math.ceil(self._virtual_h * scale))
|
|
140
|
+
|
|
141
|
+
ox = (window_w - vw) // 2
|
|
142
|
+
oy = (window_h - vh) // 2
|
|
137
143
|
|
|
138
144
|
self._state = ViewportState(
|
|
139
145
|
virtual_w=self._virtual_w,
|
|
File without changes
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Models for scene management in the engine.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from mini_arcade_core.scenes.sim_scene import SimScene
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class ScenePolicy:
|
|
14
|
+
"""
|
|
15
|
+
Controls how a scene behaves in the scene stack.
|
|
16
|
+
|
|
17
|
+
blocks_update: if True, scenes below do not tick/update (pause modal)
|
|
18
|
+
blocks_input: if True, scenes below do not receive input
|
|
19
|
+
is_opaque: if True, scenes below are not rendered
|
|
20
|
+
receives_input: if True, scene can receive input
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
blocks_update: bool = False
|
|
24
|
+
blocks_input: bool = False
|
|
25
|
+
is_opaque: bool = False
|
|
26
|
+
receives_input: bool = True
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class SceneEntry:
|
|
31
|
+
"""
|
|
32
|
+
An entry in the scene stack.
|
|
33
|
+
|
|
34
|
+
:ivar scene_id (str): Identifier of the scene.
|
|
35
|
+
:ivar scene (SimScene): The scene instance.
|
|
36
|
+
:ivar is_overlay (bool): Whether the scene is an overlay.
|
|
37
|
+
:ivar policy (ScenePolicy): The scene's policy.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
scene_id: str
|
|
41
|
+
scene: SimScene
|
|
42
|
+
is_overlay: bool
|
|
43
|
+
policy: ScenePolicy
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class StackItem:
|
|
48
|
+
"""
|
|
49
|
+
An item in the scene stack.
|
|
50
|
+
|
|
51
|
+
:ivar entry (SceneEntry): The scene entry.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
entry: SceneEntry
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module providing runtime adapters for window and scene management.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING, List
|
|
8
|
+
|
|
9
|
+
from mini_arcade_core.engine.scenes.models import (
|
|
10
|
+
SceneEntry,
|
|
11
|
+
ScenePolicy,
|
|
12
|
+
StackItem,
|
|
13
|
+
)
|
|
14
|
+
from mini_arcade_core.runtime.context import RuntimeContext
|
|
15
|
+
from mini_arcade_core.scenes.registry import SceneRegistry
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from mini_arcade_core.engine.game import Game
|
|
19
|
+
from mini_arcade_core.scenes.sim_scene import SimScene
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SceneAdapter:
|
|
23
|
+
"""
|
|
24
|
+
Manages multiple scenes (not implemented).
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, registry: SceneRegistry, game: Game):
|
|
28
|
+
self._registry = registry
|
|
29
|
+
self._stack: List[StackItem] = []
|
|
30
|
+
self._game = game
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def current_scene(self) -> SimScene | None:
|
|
34
|
+
"""
|
|
35
|
+
Get the currently active scene.
|
|
36
|
+
|
|
37
|
+
:return: The active Scene instance, or None if no scene is active.
|
|
38
|
+
:rtype: SimScene | None
|
|
39
|
+
"""
|
|
40
|
+
return self._stack[-1].entry.scene if self._stack else None
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def visible_stack(self) -> List[SimScene]:
|
|
44
|
+
"""
|
|
45
|
+
Return the list of scenes that should be drawn (base + overlays).
|
|
46
|
+
We draw from the top-most non-overlay scene upward.
|
|
47
|
+
|
|
48
|
+
:return: List of visible Scene instances.
|
|
49
|
+
:rtype: List[SimScene]
|
|
50
|
+
"""
|
|
51
|
+
return [e.scene for e in self.visible_entries()]
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def listed_scenes(self) -> List[SimScene]:
|
|
55
|
+
"""
|
|
56
|
+
Return all scenes in the stack.
|
|
57
|
+
|
|
58
|
+
:return: List of all Scene instances in the stack.
|
|
59
|
+
:rtype: List[SimScene]
|
|
60
|
+
"""
|
|
61
|
+
return [item.entry.scene for item in self._stack]
|
|
62
|
+
|
|
63
|
+
def change(self, scene_id: str):
|
|
64
|
+
"""
|
|
65
|
+
Change the current scene to the specified scene.
|
|
66
|
+
|
|
67
|
+
:param scene_id: Identifier of the scene to switch to.
|
|
68
|
+
:type scene_id: str
|
|
69
|
+
"""
|
|
70
|
+
self.clean()
|
|
71
|
+
self.push(scene_id, as_overlay=False)
|
|
72
|
+
|
|
73
|
+
def push(
|
|
74
|
+
self,
|
|
75
|
+
scene_id: str,
|
|
76
|
+
*,
|
|
77
|
+
as_overlay: bool = False,
|
|
78
|
+
policy: ScenePolicy | None = None,
|
|
79
|
+
):
|
|
80
|
+
"""
|
|
81
|
+
Push a new scene onto the scene stack.
|
|
82
|
+
|
|
83
|
+
:param scene_id: Identifier of the scene to push.
|
|
84
|
+
:type scene_id: str
|
|
85
|
+
|
|
86
|
+
:param as_overlay: Whether to push the scene as an overlay.
|
|
87
|
+
:type as_overlay: bool
|
|
88
|
+
"""
|
|
89
|
+
# default policy based on overlay vs base
|
|
90
|
+
if policy is None:
|
|
91
|
+
# base scenes: do not block anything by default
|
|
92
|
+
policy = ScenePolicy()
|
|
93
|
+
runtime_context = RuntimeContext.from_game(self._game)
|
|
94
|
+
scene = self._registry.create(
|
|
95
|
+
scene_id, runtime_context
|
|
96
|
+
) # or whatever your factory call is
|
|
97
|
+
scene.on_enter()
|
|
98
|
+
|
|
99
|
+
entry = SceneEntry(
|
|
100
|
+
scene_id=scene_id,
|
|
101
|
+
scene=scene,
|
|
102
|
+
is_overlay=as_overlay,
|
|
103
|
+
policy=policy,
|
|
104
|
+
)
|
|
105
|
+
self._stack.append(StackItem(entry=entry))
|
|
106
|
+
|
|
107
|
+
def pop(self) -> SimScene | None:
|
|
108
|
+
"""
|
|
109
|
+
Pop the current scene from the scene stack.
|
|
110
|
+
|
|
111
|
+
:return: The popped Scene instance, or None if the stack was empty.
|
|
112
|
+
:rtype: SimScene | None
|
|
113
|
+
"""
|
|
114
|
+
if not self._stack:
|
|
115
|
+
return
|
|
116
|
+
item = self._stack.pop()
|
|
117
|
+
item.entry.scene.on_exit()
|
|
118
|
+
|
|
119
|
+
def clean(self):
|
|
120
|
+
"""Clean up all scenes from the scene stack."""
|
|
121
|
+
while self._stack:
|
|
122
|
+
self.pop()
|
|
123
|
+
|
|
124
|
+
def quit(self):
|
|
125
|
+
"""Quit the game"""
|
|
126
|
+
self._game.quit()
|
|
127
|
+
|
|
128
|
+
def visible_entries(self) -> list[SceneEntry]:
|
|
129
|
+
"""
|
|
130
|
+
Render from bottom->top unless an opaque entry exists; if so,
|
|
131
|
+
render only from that entry up.
|
|
132
|
+
|
|
133
|
+
:return: List of SceneEntry instances to render.
|
|
134
|
+
:rtype: list[SceneEntry]
|
|
135
|
+
"""
|
|
136
|
+
entries = [i.entry for i in self._stack]
|
|
137
|
+
# find highest opaque from top down; render starting there
|
|
138
|
+
for idx in range(len(entries) - 1, -1, -1):
|
|
139
|
+
if entries[idx].policy.is_opaque:
|
|
140
|
+
return entries[idx:]
|
|
141
|
+
return entries
|
|
142
|
+
|
|
143
|
+
def update_entries(self) -> list[SceneEntry]:
|
|
144
|
+
"""
|
|
145
|
+
Tick/update scenes considering blocks_update.
|
|
146
|
+
Typical: pause overlay blocks update below it.
|
|
147
|
+
|
|
148
|
+
:return: List of SceneEntry instances to update.
|
|
149
|
+
:rtype: list[SceneEntry]
|
|
150
|
+
"""
|
|
151
|
+
vis = self.visible_entries()
|
|
152
|
+
if not vis:
|
|
153
|
+
return []
|
|
154
|
+
out = []
|
|
155
|
+
for entry in reversed(vis): # top->down
|
|
156
|
+
out.append(entry)
|
|
157
|
+
if entry.policy.blocks_update:
|
|
158
|
+
break
|
|
159
|
+
return list(reversed(out)) # bottom->top order
|
|
160
|
+
|
|
161
|
+
def input_entry(self) -> SceneEntry | None:
|
|
162
|
+
"""
|
|
163
|
+
Who gets input this frame. If top blocks_input, only it receives input.
|
|
164
|
+
If not, top still gets input (v1 simple). Later you can allow fall-through.
|
|
165
|
+
|
|
166
|
+
:return: The SceneEntry that receives input, or None if no scenes are active.
|
|
167
|
+
:rtype: SceneEntry | None
|
|
168
|
+
"""
|
|
169
|
+
vis = self.visible_entries()
|
|
170
|
+
if not vis:
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
# If some scene blocks input, only scenes at/above it can receive.
|
|
174
|
+
start_idx = 0
|
|
175
|
+
for idx in range(len(vis) - 1, -1, -1):
|
|
176
|
+
if vis[idx].policy.blocks_input:
|
|
177
|
+
start_idx = idx
|
|
178
|
+
break
|
|
179
|
+
|
|
180
|
+
candidates = vis[start_idx:]
|
|
181
|
+
|
|
182
|
+
# Pick the top-most candidate that actually receives input.
|
|
183
|
+
for entry in reversed(candidates):
|
|
184
|
+
if entry.policy.receives_input:
|
|
185
|
+
return entry
|
|
186
|
+
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
def has_scene(self, scene_id: str) -> bool:
|
|
190
|
+
"""
|
|
191
|
+
Check if a scene with the given ID exists in the stack.
|
|
192
|
+
|
|
193
|
+
:param scene_id: Identifier of the scene to check.
|
|
194
|
+
:type scene_id: str
|
|
195
|
+
|
|
196
|
+
:return: True if the scene exists in the stack, False otherwise.
|
|
197
|
+
:rtype: bool
|
|
198
|
+
"""
|
|
199
|
+
return any(item.entry.scene_id == scene_id for item in self._stack)
|
|
200
|
+
|
|
201
|
+
def remove_scene(self, scene_id: str):
|
|
202
|
+
"""
|
|
203
|
+
Remove a scene with the given ID from the stack.
|
|
204
|
+
|
|
205
|
+
:param scene_id: Identifier of the scene to remove.
|
|
206
|
+
:type scene_id: str
|
|
207
|
+
"""
|
|
208
|
+
# remove first match from top (overlay is usually near top)
|
|
209
|
+
for i in range(len(self._stack) - 1, -1, -1):
|
|
210
|
+
if self._stack[i].entry.scene_id == scene_id:
|
|
211
|
+
item = self._stack.pop(i)
|
|
212
|
+
item.entry.scene.on_exit()
|
|
213
|
+
return
|
|
@@ -4,17 +4,18 @@ Module providing runtime adapters for window and scene management.
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
from mini_arcade_core.backend import Backend
|
|
7
8
|
from mini_arcade_core.runtime.audio.audio_port import AudioPort
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class SDLAudioAdapter(AudioPort):
|
|
11
12
|
"""A no-op audio adapter."""
|
|
12
13
|
|
|
13
|
-
def __init__(self, backend):
|
|
14
|
+
def __init__(self, backend: Backend):
|
|
14
15
|
self.backend = backend
|
|
15
16
|
|
|
16
17
|
def load_sound(self, sound_id: str, file_path: str):
|
|
17
|
-
self.backend.load_sound(sound_id, file_path)
|
|
18
|
+
self.backend.audio.load_sound(sound_id, file_path)
|
|
18
19
|
|
|
19
20
|
def play(self, sound_id: str, loops: int = 0):
|
|
20
|
-
self.backend.play_sound(sound_id, loops)
|
|
21
|
+
self.backend.audio.play_sound(sound_id, loops)
|
|
@@ -4,14 +4,10 @@ Service interfaces for runtime components.
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
from mini_arcade_core.backend.backend import Backend
|
|
8
|
-
|
|
9
7
|
|
|
10
8
|
class AudioPort:
|
|
11
9
|
"""Interface for audio playback operations."""
|
|
12
10
|
|
|
13
|
-
backend: Backend
|
|
14
|
-
|
|
15
11
|
def load_sound(self, sound_id: str, file_path: str):
|
|
16
12
|
"""
|
|
17
13
|
Load a sound from a file and associate it with an identifier.
|
|
@@ -74,7 +74,14 @@ class CapturePathBuilder:
|
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
class CaptureAdapter(CapturePort):
|
|
77
|
-
"""
|
|
77
|
+
"""
|
|
78
|
+
Adapter for capturing frames.
|
|
79
|
+
|
|
80
|
+
:param backend: Backend instance to use for capturing frames.
|
|
81
|
+
:type backend: Backend
|
|
82
|
+
:param path_builder: Optional CapturePathBuilder for building file paths.
|
|
83
|
+
:type path_builder: CapturePathBuilder | None
|
|
84
|
+
"""
|
|
78
85
|
|
|
79
86
|
def __init__(
|
|
80
87
|
self,
|
|
@@ -96,9 +103,9 @@ class CaptureAdapter(CapturePort):
|
|
|
96
103
|
# If backend only saves BMP, capture to a temp bmp next to output
|
|
97
104
|
bmp_path = out_path.with_suffix(".bmp")
|
|
98
105
|
|
|
99
|
-
self.backend.
|
|
106
|
+
self.backend.capture.bmp(str(bmp_path))
|
|
100
107
|
if not bmp_path.exists():
|
|
101
|
-
raise RuntimeError("Backend
|
|
108
|
+
raise RuntimeError("Backend capture.bmp did not create BMP file")
|
|
102
109
|
|
|
103
110
|
self._bmp_to_image(str(bmp_path), str(out_path))
|
|
104
111
|
try:
|
|
@@ -112,7 +119,7 @@ class CaptureAdapter(CapturePort):
|
|
|
112
119
|
return str(out_path)
|
|
113
120
|
|
|
114
121
|
def screenshot_bytes(self) -> bytes:
|
|
115
|
-
data = self.backend.
|
|
122
|
+
data = self.backend.capture.bmp(path=None)
|
|
116
123
|
if data is None:
|
|
117
124
|
raise RuntimeError("Backend returned None for screenshot_bytes()")
|
|
118
125
|
return data
|
|
@@ -125,10 +132,10 @@ class CaptureAdapter(CapturePort):
|
|
|
125
132
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
126
133
|
|
|
127
134
|
bmp_path = out_path.with_suffix(".bmp")
|
|
128
|
-
self.backend.
|
|
135
|
+
self.backend.capture.bmp(str(bmp_path))
|
|
129
136
|
|
|
130
137
|
if not bmp_path.exists():
|
|
131
|
-
raise RuntimeError("Backend
|
|
138
|
+
raise RuntimeError("Backend capture.bmp did not create BMP file")
|
|
132
139
|
|
|
133
140
|
self._bmp_to_image(str(bmp_path), str(out_path))
|
|
134
141
|
|
|
@@ -4,14 +4,10 @@ Service interfaces for runtime components.
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
from mini_arcade_core.backend import Backend
|
|
8
|
-
|
|
9
7
|
|
|
10
8
|
class CapturePort:
|
|
11
9
|
"""Interface for frame capture operations."""
|
|
12
10
|
|
|
13
|
-
backend: Backend
|
|
14
|
-
|
|
15
11
|
def screenshot(self, label: str | None = None) -> str:
|
|
16
12
|
"""
|
|
17
13
|
Capture the current frame.
|
|
@@ -9,12 +9,14 @@ from dataclasses import dataclass
|
|
|
9
9
|
from typing import TYPE_CHECKING
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
|
+
from mini_arcade_core.engine.cheats import CheatManager
|
|
12
13
|
from mini_arcade_core.engine.commands import CommandQueue
|
|
13
|
-
from mini_arcade_core.engine.game import Game, GameConfig
|
|
14
|
-
from mini_arcade_core.
|
|
14
|
+
from mini_arcade_core.engine.game import Game, GameConfig
|
|
15
|
+
from mini_arcade_core.engine.gameplay_settings import GamePlaySettings
|
|
15
16
|
from mini_arcade_core.runtime.services import RuntimeServices
|
|
16
17
|
|
|
17
18
|
|
|
19
|
+
# TODO: Remove cheats and command_queue from here later if unused.
|
|
18
20
|
@dataclass(frozen=True)
|
|
19
21
|
class RuntimeContext:
|
|
20
22
|
"""
|
|
@@ -22,14 +24,14 @@ class RuntimeContext:
|
|
|
22
24
|
|
|
23
25
|
:ivar services (RuntimeServices): Runtime services.
|
|
24
26
|
:ivar config (GameConfig): Game configuration.
|
|
25
|
-
:ivar settings (
|
|
27
|
+
:ivar settings (GamePlaySettings): Game settings.
|
|
26
28
|
:ivar command_queue (CommandQueue | None): Optional command queue.
|
|
27
29
|
:ivar cheats (CheatManager | None): Optional cheat manager.
|
|
28
30
|
"""
|
|
29
31
|
|
|
30
32
|
services: RuntimeServices
|
|
31
33
|
config: GameConfig
|
|
32
|
-
settings:
|
|
34
|
+
settings: GamePlaySettings
|
|
33
35
|
command_queue: CommandQueue | None = None
|
|
34
36
|
cheats: CheatManager | None = None
|
|
35
37
|
|
|
@@ -48,6 +50,6 @@ class RuntimeContext:
|
|
|
48
50
|
services=game_entity.services,
|
|
49
51
|
config=game_entity.config,
|
|
50
52
|
settings=game_entity.settings,
|
|
51
|
-
command_queue=game_entity.command_queue,
|
|
52
|
-
cheats=game_entity.
|
|
53
|
+
command_queue=game_entity.managers.command_queue,
|
|
54
|
+
cheats=game_entity.managers.cheats,
|
|
53
55
|
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scene query adapter implementation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Sequence
|
|
9
|
+
|
|
10
|
+
from mini_arcade_core.engine.scenes.models import SceneEntry
|
|
11
|
+
from mini_arcade_core.engine.scenes.scene_manager import SceneAdapter
|
|
12
|
+
from mini_arcade_core.runtime.scene.scene_query_port import SceneQueryPort
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class SceneQueryAdapter(SceneQueryPort):
|
|
17
|
+
"""Adapter that exposes a read-only view of the SceneAdapter manager."""
|
|
18
|
+
|
|
19
|
+
_scenes: SceneAdapter
|
|
20
|
+
|
|
21
|
+
def visible_entries(self) -> Sequence[SceneEntry]:
|
|
22
|
+
return list(self._scenes.visible_entries())
|
|
23
|
+
|
|
24
|
+
def input_entry(self) -> SceneEntry | None:
|
|
25
|
+
return self._scenes.input_entry()
|
|
26
|
+
|
|
27
|
+
def stack_summary(self) -> list[str]:
|
|
28
|
+
out: list[str] = []
|
|
29
|
+
for e in self._scenes.visible_entries():
|
|
30
|
+
out.append(f"{e.scene_id} overlay={e.is_overlay}")
|
|
31
|
+
return out
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scene query port protocol.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Protocol, Sequence, runtime_checkable
|
|
8
|
+
|
|
9
|
+
from mini_arcade_core.engine.scenes.models import SceneEntry
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@runtime_checkable
|
|
13
|
+
class SceneQueryPort(Protocol):
|
|
14
|
+
"""Read-only queries over the engine scene stack."""
|
|
15
|
+
|
|
16
|
+
def visible_entries(self) -> Sequence[SceneEntry]:
|
|
17
|
+
"""
|
|
18
|
+
Scenes that should be rendered (policy-aware).
|
|
19
|
+
|
|
20
|
+
:return: Sequence of SceneEntry instances that are visible.
|
|
21
|
+
:rtype: Sequence[SceneEntry]
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def input_entry(self) -> SceneEntry | None:
|
|
25
|
+
"""
|
|
26
|
+
The scene that currently receives input (top-most eligible).
|
|
27
|
+
|
|
28
|
+
:return: SceneEntry that receives input, or None if stack is empty.
|
|
29
|
+
:rtype: SceneEntry | None
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def stack_summary(self) -> list[str]:
|
|
33
|
+
"""
|
|
34
|
+
Convenience: human-readable stack lines for debug overlays.
|
|
35
|
+
|
|
36
|
+
:return: List of strings summarizing the scene stack.
|
|
37
|
+
:rtype: list[str]
|
|
38
|
+
"""
|
|
@@ -11,7 +11,7 @@ from mini_arcade_core.runtime.capture.capture_port import CapturePort
|
|
|
11
11
|
from mini_arcade_core.runtime.file.file_port import FilePort
|
|
12
12
|
from mini_arcade_core.runtime.input.input_port import InputPort
|
|
13
13
|
from mini_arcade_core.runtime.render.render_port import RenderServicePort
|
|
14
|
-
from mini_arcade_core.runtime.scene.
|
|
14
|
+
from mini_arcade_core.runtime.scene.scene_query_port import SceneQueryPort
|
|
15
15
|
from mini_arcade_core.runtime.window.window_port import WindowPort
|
|
16
16
|
|
|
17
17
|
|
|
@@ -26,12 +26,13 @@ class RuntimeServices:
|
|
|
26
26
|
:ivar files (FilePort): File service port.
|
|
27
27
|
:ivar capture (CapturePort): Capture service port.
|
|
28
28
|
:ivar input (InputPort): Input handling service port.
|
|
29
|
+
:ivar render (RenderServicePort): Rendering service port.
|
|
29
30
|
"""
|
|
30
31
|
|
|
31
32
|
window: WindowPort
|
|
32
|
-
scenes: ScenePort
|
|
33
33
|
audio: AudioPort
|
|
34
34
|
files: FilePort
|
|
35
35
|
capture: CapturePort
|
|
36
36
|
input: InputPort
|
|
37
37
|
render: RenderServicePort
|
|
38
|
+
scenes: SceneQueryPort
|