mini-arcade-core 1.1.1__py3-none-any.whl → 1.2.1__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 +182 -184
- mini_arcade_core/backend/types.py +5 -1
- mini_arcade_core/engine/commands.py +8 -8
- mini_arcade_core/engine/game.py +54 -354
- 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 +2 -2
- mini_arcade_core/engine/render/effects/crt.py +4 -4
- mini_arcade_core/engine/render/effects/registry.py +1 -1
- mini_arcade_core/engine/render/effects/vignette.py +8 -8
- 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 +1 -1
- 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/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 +53 -31
- mini_arcade_core/runtime/capture/capture_port.py +0 -4
- mini_arcade_core/runtime/capture/capture_worker.py +174 -0
- 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.1.dist-info → mini_arcade_core-1.2.1.dist-info}/METADATA +1 -1
- mini_arcade_core-1.2.1.dist-info/RECORD +93 -0
- {mini_arcade_core-1.1.1.dist-info → mini_arcade_core-1.2.1.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.1.dist-info/RECORD +0 -85
- /mini_arcade_core/{managers → engine}/cheats.py +0 -0
- /mini_arcade_core/{managers → engine/loop}/__init__.py +0 -0
- /mini_arcade_core/{sim → engine/scenes}/__init__.py +0 -0
- {mini_arcade_core-1.1.1.dist-info → mini_arcade_core-1.2.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Module providing runtime adapters for window and scene management.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from __future__ import annotations
|
|
6
|
-
|
|
7
|
-
from mini_arcade_core.runtime.context import RuntimeContext
|
|
8
|
-
from mini_arcade_core.runtime.scene.scene_port import (
|
|
9
|
-
SceneEntry,
|
|
10
|
-
ScenePolicy,
|
|
11
|
-
ScenePort,
|
|
12
|
-
StackItem,
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class SceneAdapter(ScenePort):
|
|
17
|
-
"""
|
|
18
|
-
Manages multiple scenes (not implemented).
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
def __init__(self, registry, game):
|
|
22
|
-
self._registry = registry
|
|
23
|
-
self._stack = []
|
|
24
|
-
self._game = game
|
|
25
|
-
|
|
26
|
-
@property
|
|
27
|
-
def current_scene(self):
|
|
28
|
-
return self._stack[-1].entry.scene if self._stack else None
|
|
29
|
-
|
|
30
|
-
@property
|
|
31
|
-
def visible_stack(self):
|
|
32
|
-
return [e.scene for e in self.visible_entries()]
|
|
33
|
-
|
|
34
|
-
def change(self, scene_id):
|
|
35
|
-
self.clean()
|
|
36
|
-
self.push(scene_id, as_overlay=False)
|
|
37
|
-
|
|
38
|
-
def push(
|
|
39
|
-
self,
|
|
40
|
-
scene_id,
|
|
41
|
-
*,
|
|
42
|
-
as_overlay=False,
|
|
43
|
-
policy=None,
|
|
44
|
-
):
|
|
45
|
-
# default policy based on overlay vs base
|
|
46
|
-
if policy is None:
|
|
47
|
-
# base scenes: do not block anything by default
|
|
48
|
-
policy = ScenePolicy()
|
|
49
|
-
runtime_context = RuntimeContext.from_game(self._game)
|
|
50
|
-
scene = self._registry.create(
|
|
51
|
-
scene_id, runtime_context
|
|
52
|
-
) # or whatever your factory call is
|
|
53
|
-
scene.on_enter()
|
|
54
|
-
|
|
55
|
-
entry = SceneEntry(
|
|
56
|
-
scene_id=scene_id,
|
|
57
|
-
scene=scene,
|
|
58
|
-
is_overlay=as_overlay,
|
|
59
|
-
policy=policy,
|
|
60
|
-
)
|
|
61
|
-
self._stack.append(StackItem(entry=entry))
|
|
62
|
-
|
|
63
|
-
def pop(self):
|
|
64
|
-
if not self._stack:
|
|
65
|
-
return
|
|
66
|
-
item = self._stack.pop()
|
|
67
|
-
item.entry.scene.on_exit()
|
|
68
|
-
|
|
69
|
-
def clean(self):
|
|
70
|
-
while self._stack:
|
|
71
|
-
self.pop()
|
|
72
|
-
|
|
73
|
-
def quit(self):
|
|
74
|
-
self._game.quit()
|
|
75
|
-
|
|
76
|
-
def visible_entries(self):
|
|
77
|
-
entries = [i.entry for i in self._stack]
|
|
78
|
-
# find highest opaque from top down; render starting there
|
|
79
|
-
for idx in range(len(entries) - 1, -1, -1):
|
|
80
|
-
if entries[idx].policy.is_opaque:
|
|
81
|
-
return entries[idx:]
|
|
82
|
-
return entries
|
|
83
|
-
|
|
84
|
-
def update_entries(self):
|
|
85
|
-
vis = self.visible_entries()
|
|
86
|
-
if not vis:
|
|
87
|
-
return []
|
|
88
|
-
out = []
|
|
89
|
-
for entry in reversed(vis): # top->down
|
|
90
|
-
out.append(entry)
|
|
91
|
-
if entry.policy.blocks_update:
|
|
92
|
-
break
|
|
93
|
-
return list(reversed(out)) # bottom->top order
|
|
94
|
-
|
|
95
|
-
def input_entry(self):
|
|
96
|
-
vis = self.visible_entries()
|
|
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):
|
|
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
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Service interfaces for runtime components.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from __future__ import annotations
|
|
6
|
-
|
|
7
|
-
from dataclasses import dataclass
|
|
8
|
-
from typing import TYPE_CHECKING, List
|
|
9
|
-
|
|
10
|
-
from mini_arcade_core.scenes.registry import SceneRegistry
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from mini_arcade_core.engine.game import Game
|
|
14
|
-
from mini_arcade_core.scenes.scene import Scene
|
|
15
|
-
from mini_arcade_core.sim.protocols import SimScene
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@dataclass(frozen=True)
|
|
19
|
-
class ScenePolicy:
|
|
20
|
-
"""
|
|
21
|
-
Controls how a scene behaves in the scene stack.
|
|
22
|
-
|
|
23
|
-
blocks_update: if True, scenes below do not tick/update (pause modal)
|
|
24
|
-
blocks_input: if True, scenes below do not receive input
|
|
25
|
-
is_opaque: if True, scenes below are not rendered
|
|
26
|
-
receives_input: if True, scene can receive input
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
blocks_update: bool = False
|
|
30
|
-
blocks_input: bool = False
|
|
31
|
-
is_opaque: bool = False
|
|
32
|
-
receives_input: bool = True
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@dataclass(frozen=True)
|
|
36
|
-
class SceneEntry:
|
|
37
|
-
"""
|
|
38
|
-
An entry in the scene stack.
|
|
39
|
-
|
|
40
|
-
:ivar scene_id (str): Identifier of the scene.
|
|
41
|
-
:ivar scene (Scene): The scene instance.
|
|
42
|
-
:ivar is_overlay (bool): Whether the scene is an overlay.
|
|
43
|
-
:ivar policy (ScenePolicy): The scene's policy.
|
|
44
|
-
"""
|
|
45
|
-
|
|
46
|
-
scene_id: str
|
|
47
|
-
scene: SimScene
|
|
48
|
-
is_overlay: bool
|
|
49
|
-
policy: ScenePolicy
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
@dataclass
|
|
53
|
-
class StackItem:
|
|
54
|
-
"""
|
|
55
|
-
An item in the scene stack.
|
|
56
|
-
|
|
57
|
-
:ivar entry (SceneEntry): The scene entry.
|
|
58
|
-
"""
|
|
59
|
-
|
|
60
|
-
entry: SceneEntry
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class ScenePort:
|
|
64
|
-
"""Interface for scene management operations."""
|
|
65
|
-
|
|
66
|
-
_registry: SceneRegistry
|
|
67
|
-
_stack: List[StackItem]
|
|
68
|
-
_game: Game
|
|
69
|
-
|
|
70
|
-
@property
|
|
71
|
-
def current_scene(self) -> "SimScene | None":
|
|
72
|
-
"""
|
|
73
|
-
Get the currently active scene.
|
|
74
|
-
|
|
75
|
-
:return: The active Scene instance, or None if no scene is active.
|
|
76
|
-
:rtype: SimScene | None
|
|
77
|
-
"""
|
|
78
|
-
|
|
79
|
-
@property
|
|
80
|
-
def visible_stack(self) -> List["SimScene"]:
|
|
81
|
-
"""
|
|
82
|
-
Return the list of scenes that should be drawn (base + overlays).
|
|
83
|
-
We draw from the top-most non-overlay scene upward.
|
|
84
|
-
|
|
85
|
-
:return: List of visible Scene instances.
|
|
86
|
-
:rtype: List[SimScene]
|
|
87
|
-
"""
|
|
88
|
-
|
|
89
|
-
def change(self, scene_id: str):
|
|
90
|
-
"""
|
|
91
|
-
Change the current scene to the specified scene.
|
|
92
|
-
|
|
93
|
-
:param scene_id: Identifier of the scene to switch to.
|
|
94
|
-
:type scene_id: str
|
|
95
|
-
"""
|
|
96
|
-
|
|
97
|
-
def push(self, scene_id: str, *, as_overlay: bool = False):
|
|
98
|
-
"""
|
|
99
|
-
Push a new scene onto the scene stack.
|
|
100
|
-
|
|
101
|
-
:param scene_id: Identifier of the scene to push.
|
|
102
|
-
:type scene_id: str
|
|
103
|
-
|
|
104
|
-
:param as_overlay: Whether to push the scene as an overlay.
|
|
105
|
-
:type as_overlay: bool
|
|
106
|
-
"""
|
|
107
|
-
|
|
108
|
-
def pop(self) -> "Scene | None":
|
|
109
|
-
"""
|
|
110
|
-
Pop the current scene from the scene stack.
|
|
111
|
-
|
|
112
|
-
:return: The popped Scene instance, or None if the stack was empty.
|
|
113
|
-
:rtype: Scene | None
|
|
114
|
-
"""
|
|
115
|
-
|
|
116
|
-
def clean(self):
|
|
117
|
-
"""
|
|
118
|
-
Clean up all scenes from the scene stack.
|
|
119
|
-
"""
|
|
120
|
-
|
|
121
|
-
def quit(self):
|
|
122
|
-
"""
|
|
123
|
-
Quit the game
|
|
124
|
-
"""
|
|
125
|
-
|
|
126
|
-
def visible_entries(self) -> list[SceneEntry]:
|
|
127
|
-
"""
|
|
128
|
-
Render from bottom->top unless an opaque entry exists; if so,
|
|
129
|
-
render only from that entry up.
|
|
130
|
-
|
|
131
|
-
:return: List of SceneEntry instances to render.
|
|
132
|
-
:rtype: list[SceneEntry]
|
|
133
|
-
"""
|
|
134
|
-
|
|
135
|
-
def update_entries(self) -> list[SceneEntry]:
|
|
136
|
-
"""
|
|
137
|
-
Tick/update scenes considering blocks_update.
|
|
138
|
-
Typical: pause overlay blocks update below it.
|
|
139
|
-
|
|
140
|
-
:return: List of SceneEntry instances to update.
|
|
141
|
-
:rtype: list[SceneEntry]
|
|
142
|
-
"""
|
|
143
|
-
|
|
144
|
-
def input_entry(self) -> SceneEntry | None:
|
|
145
|
-
"""
|
|
146
|
-
Who gets input this frame. If top blocks_input, only it receives input.
|
|
147
|
-
If not, top still gets input (v1 simple). Later you can allow fall-through.
|
|
148
|
-
|
|
149
|
-
:return: The SceneEntry that receives input, or None if no scenes are active.
|
|
150
|
-
:rtype: SceneEntry | None
|
|
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):
|
|
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
|
-
"""
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Simulation scene protocol module.
|
|
3
|
-
Defines the SimScene protocol for simulation scenes.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from __future__ import annotations
|
|
7
|
-
|
|
8
|
-
from dataclasses import dataclass
|
|
9
|
-
|
|
10
|
-
from mini_arcade_core.engine.render.packet import RenderPacket
|
|
11
|
-
from mini_arcade_core.runtime.context import RuntimeContext
|
|
12
|
-
from mini_arcade_core.runtime.input_frame import InputFrame
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@dataclass
|
|
16
|
-
class SimScene:
|
|
17
|
-
"""
|
|
18
|
-
Simulation-first scene protocol.
|
|
19
|
-
|
|
20
|
-
tick() advances the simulation and returns a RenderPacket for this scene.
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
context: RuntimeContext
|
|
24
|
-
|
|
25
|
-
def on_enter(self):
|
|
26
|
-
"""Called when the scene is entered."""
|
|
27
|
-
|
|
28
|
-
def on_exit(self):
|
|
29
|
-
"""Called when the scene is exited."""
|
|
30
|
-
|
|
31
|
-
def tick(self, input_frame: InputFrame, dt: float) -> RenderPacket:
|
|
32
|
-
"""
|
|
33
|
-
Advance the simulation by dt seconds, processing input_frame.
|
|
34
|
-
|
|
35
|
-
:param input_frame: InputFrame with input events for this frame.
|
|
36
|
-
:param dt: Time delta in seconds since the last tick.
|
|
37
|
-
|
|
38
|
-
:return: RenderPacket for this frame.
|
|
39
|
-
:rtype: RenderPacket
|
|
40
|
-
"""
|
|
41
|
-
raise NotImplementedError()
|
mini_arcade_core/sim/runner.py
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Simulation runner module.
|
|
3
|
-
Defines the SimRunner class for running simulation scenes.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from __future__ import annotations
|
|
7
|
-
|
|
8
|
-
from dataclasses import dataclass
|
|
9
|
-
from time import perf_counter, sleep
|
|
10
|
-
from typing import Dict, Optional
|
|
11
|
-
|
|
12
|
-
from mini_arcade_core.backend import Backend
|
|
13
|
-
from mini_arcade_core.engine.render.packet import RenderPacket
|
|
14
|
-
from mini_arcade_core.engine.render.pipeline import RenderPipeline
|
|
15
|
-
from mini_arcade_core.runtime.input_frame import InputFrame
|
|
16
|
-
from mini_arcade_core.runtime.scene.scene_port import SceneEntry
|
|
17
|
-
from mini_arcade_core.runtime.services import RuntimeServices
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def _neutral_input(frame_index: int, dt: float) -> InputFrame:
|
|
21
|
-
# InputFrame is frozen; create a clean snapshot for non-input scenes.
|
|
22
|
-
return InputFrame(frame_index=frame_index, dt=dt)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def _has_tick(scene: object) -> bool:
|
|
26
|
-
# Avoid isinstance(..., Protocol). Structural check.
|
|
27
|
-
return callable(getattr(scene, "tick", None))
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _has_draw(scene: object) -> bool:
|
|
31
|
-
return callable(getattr(scene, "draw", None))
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def _has_update(scene: object) -> bool:
|
|
35
|
-
return callable(getattr(scene, "update", None))
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def _has_handle_event(scene: object) -> bool:
|
|
39
|
-
return callable(getattr(scene, "handle_event", None))
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@dataclass(frozen=True)
|
|
43
|
-
class SimRunnerConfig:
|
|
44
|
-
"""
|
|
45
|
-
Config for sim runner.
|
|
46
|
-
|
|
47
|
-
- record: if True, capture a frame each tick using deterministic naming.
|
|
48
|
-
- run_id: required when record=True.
|
|
49
|
-
- max_frames: optional safety stop (useful for offline sims/tests).
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
fps: int = 60
|
|
53
|
-
record: bool = False
|
|
54
|
-
run_id: str = "run"
|
|
55
|
-
max_frames: Optional[int] = None
|
|
56
|
-
# If True, still forward raw events to the input scene's handle_event (legacy UI / text input).
|
|
57
|
-
forward_events_to_input_scene: bool = True
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class SimRunner:
|
|
61
|
-
"""
|
|
62
|
-
Simulation-first runner.
|
|
63
|
-
|
|
64
|
-
Uses:
|
|
65
|
-
- services.scenes.update_entries() for ticking (policy-aware)
|
|
66
|
-
- services.scenes.visible_entries() for rendering (opaque-aware)
|
|
67
|
-
- services.scenes.input_entry() for input focus
|
|
68
|
-
"""
|
|
69
|
-
|
|
70
|
-
def __init__(
|
|
71
|
-
self,
|
|
72
|
-
backend: Backend,
|
|
73
|
-
services: RuntimeServices,
|
|
74
|
-
*,
|
|
75
|
-
render_pipeline: Optional[RenderPipeline] = None,
|
|
76
|
-
):
|
|
77
|
-
if services.scenes is None:
|
|
78
|
-
raise ValueError("RuntimeServices.scenes must be set")
|
|
79
|
-
if services.input is None:
|
|
80
|
-
raise ValueError("RuntimeServices.input must be set")
|
|
81
|
-
if services.capture is None:
|
|
82
|
-
# recording is optional, but capture port should exist in v1
|
|
83
|
-
raise ValueError("RuntimeServices.capture must be set")
|
|
84
|
-
|
|
85
|
-
self.backend = backend
|
|
86
|
-
self.services = services
|
|
87
|
-
self.pipeline = render_pipeline or RenderPipeline()
|
|
88
|
-
|
|
89
|
-
# cache: scene object id -> last RenderPacket
|
|
90
|
-
self._packets: Dict[int, RenderPacket] = {}
|
|
91
|
-
|
|
92
|
-
self._running: bool = False
|
|
93
|
-
|
|
94
|
-
def stop(self):
|
|
95
|
-
"""
|
|
96
|
-
Stop the simulation loop.
|
|
97
|
-
"""
|
|
98
|
-
self._running = False
|
|
99
|
-
|
|
100
|
-
# TODO: Solve too-many-statements, too-many-branches and too-many-locals
|
|
101
|
-
# warning later
|
|
102
|
-
# Justification: The run method orchestrates multiple complex steps in the
|
|
103
|
-
# simulation loop.
|
|
104
|
-
# pylint: disable=too-many-statements,too-many-branches,too-many-locals
|
|
105
|
-
def run(
|
|
106
|
-
self, initial_scene_id: str, *, cfg: Optional[SimRunnerConfig] = None
|
|
107
|
-
):
|
|
108
|
-
"""
|
|
109
|
-
Run the simulation loop starting from the initial scene.
|
|
110
|
-
|
|
111
|
-
:param initial_scene_id: ID of the initial scene to load.
|
|
112
|
-
:type initial_scene_id: str
|
|
113
|
-
|
|
114
|
-
:param cfg: Optional SimRunnerConfig instance.
|
|
115
|
-
:type cfg: Optional[SimRunnerConfig]
|
|
116
|
-
"""
|
|
117
|
-
cfg = cfg or SimRunnerConfig()
|
|
118
|
-
|
|
119
|
-
scenes = self.services.scenes
|
|
120
|
-
assert scenes is not None
|
|
121
|
-
|
|
122
|
-
# start at initial scene
|
|
123
|
-
scenes.change(initial_scene_id)
|
|
124
|
-
|
|
125
|
-
self._running = True
|
|
126
|
-
target_dt = 1.0 / cfg.fps if cfg.fps > 0 else 0.0
|
|
127
|
-
|
|
128
|
-
last_time = perf_counter()
|
|
129
|
-
frame_index = 0
|
|
130
|
-
|
|
131
|
-
while self._running:
|
|
132
|
-
if cfg.max_frames is not None and frame_index >= cfg.max_frames:
|
|
133
|
-
break
|
|
134
|
-
|
|
135
|
-
now = perf_counter()
|
|
136
|
-
dt = now - last_time
|
|
137
|
-
last_time = now
|
|
138
|
-
|
|
139
|
-
# 1) poll events -> build InputFrame
|
|
140
|
-
events = list(self.backend.poll_events())
|
|
141
|
-
input_frame = self.services.input.build(events, frame_index, dt)
|
|
142
|
-
|
|
143
|
-
# 2) OS quit request is a hard stop
|
|
144
|
-
if input_frame.quit:
|
|
145
|
-
# use ScenePort.quit so Game.quit can be centralized there
|
|
146
|
-
scenes.quit()
|
|
147
|
-
break
|
|
148
|
-
|
|
149
|
-
# 3) input focus scene (top of visible stack)
|
|
150
|
-
input_entry: Optional[SceneEntry] = scenes.input_entry()
|
|
151
|
-
if input_entry is None:
|
|
152
|
-
break
|
|
153
|
-
|
|
154
|
-
# Optional legacy: forward raw events to focused scene
|
|
155
|
-
if cfg.forward_events_to_input_scene and _has_handle_event(
|
|
156
|
-
input_entry.scene
|
|
157
|
-
):
|
|
158
|
-
for ev in events:
|
|
159
|
-
input_entry.scene.handle_event(ev)
|
|
160
|
-
|
|
161
|
-
# 4) tick/update policy-aware scenes
|
|
162
|
-
for entry in scenes.update_entries():
|
|
163
|
-
scene_obj = entry.scene
|
|
164
|
-
scene_key = id(scene_obj)
|
|
165
|
-
|
|
166
|
-
# Only the input-focused scene receives the actual input_frame
|
|
167
|
-
effective_input = (
|
|
168
|
-
input_frame
|
|
169
|
-
if entry is input_entry
|
|
170
|
-
else _neutral_input(frame_index, dt)
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
if _has_tick(scene_obj):
|
|
174
|
-
packet = scene_obj.tick(effective_input, dt) # SimScene
|
|
175
|
-
if not isinstance(packet, RenderPacket):
|
|
176
|
-
raise TypeError(
|
|
177
|
-
f"{entry.scene_id}.tick() must "
|
|
178
|
-
f"return RenderPacket, got {type(packet)!r}"
|
|
179
|
-
)
|
|
180
|
-
self._packets[scene_key] = packet
|
|
181
|
-
elif _has_update(scene_obj):
|
|
182
|
-
# legacy scene; keep packet cache if any
|
|
183
|
-
scene_obj.update(dt)
|
|
184
|
-
|
|
185
|
-
# 5) render visible stack (policy-aware)
|
|
186
|
-
self.backend.begin_frame()
|
|
187
|
-
|
|
188
|
-
for entry in scenes.visible_entries():
|
|
189
|
-
scene_obj = entry.scene
|
|
190
|
-
scene_key = id(scene_obj)
|
|
191
|
-
|
|
192
|
-
if _has_tick(scene_obj):
|
|
193
|
-
packet = self._packets.get(scene_key)
|
|
194
|
-
# If first frame and no packet exists yet, do a dt=0 tick to bootstrap
|
|
195
|
-
if packet is None:
|
|
196
|
-
packet = scene_obj.tick(
|
|
197
|
-
_neutral_input(frame_index, 0.0), 0.0
|
|
198
|
-
)
|
|
199
|
-
self._packets[scene_key] = packet
|
|
200
|
-
self.pipeline.draw_packet(self.backend, packet)
|
|
201
|
-
|
|
202
|
-
elif _has_draw(scene_obj):
|
|
203
|
-
# legacy scene draw path
|
|
204
|
-
scene_obj.draw(self.backend)
|
|
205
|
-
|
|
206
|
-
self.backend.end_frame()
|
|
207
|
-
|
|
208
|
-
# 6) deterministic capture (optional)
|
|
209
|
-
if cfg.record:
|
|
210
|
-
# label could be "frame" or something semantic later
|
|
211
|
-
self.services.capture.screenshot_sim(
|
|
212
|
-
cfg.run_id, frame_index, label="frame"
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
# 7) frame pacing
|
|
216
|
-
if target_dt > 0 and dt < target_dt:
|
|
217
|
-
sleep(target_dt - dt)
|
|
218
|
-
|
|
219
|
-
frame_index += 1
|
|
220
|
-
|
|
221
|
-
# cleanup scenes
|
|
222
|
-
scenes.clean()
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
mini_arcade_core/__init__.py,sha256=axwl7fiQ2Zu2vPOTMUxwnvR746gI9RhpBWpBCId_yqo,3686
|
|
2
|
-
mini_arcade_core/backend/__init__.py,sha256=E9uOCttCkXwdN_5MlcFHUmG3Bj6RYMatNNOno4C_6aI,312
|
|
3
|
-
mini_arcade_core/backend/backend.py,sha256=1-lgjkcftYaeusWKhQrDTwgfbwH6y9S-PP-miy7tRNE,8619
|
|
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=SuiwXGNmXCZxfPsww6zj3V_NK7k4jpoCuzMn19afS-g,175
|
|
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/commands.py,sha256=SfhncRQvuwTQg5BEyS8yeFTaq5mixiFsvFUZSBK9y1Q,5420
|
|
11
|
-
mini_arcade_core/engine/game.py,sha256=HaLCew31UD8ujz3yOHGjWPzjpD7dkeQSMDdnMFD9JtI,15588
|
|
12
|
-
mini_arcade_core/engine/render/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
mini_arcade_core/engine/render/context.py,sha256=1zzycXDbQnK3wuk79cMjSewu8FinAp-EsFal7bbJFnI,1414
|
|
14
|
-
mini_arcade_core/engine/render/effects/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
mini_arcade_core/engine/render/effects/base.py,sha256=yq8qRZMbp4VTi02WnVbyHS3RYc56vvP69MzPbCO3m2s,2373
|
|
16
|
-
mini_arcade_core/engine/render/effects/crt.py,sha256=D8_7Bqzt_VeLusyXzeLQynxTIZwQ45NisSImTB-4Hpg,2141
|
|
17
|
-
mini_arcade_core/engine/render/effects/registry.py,sha256=ie1nuLJbQq9Ecb9tea3oGvSD9QaydbFFhH8ggEf-9uU,1220
|
|
18
|
-
mini_arcade_core/engine/render/effects/vignette.py,sha256=ogLTgaJ9srpiA59Lc7_nBD7wZk7y6t9HnHYNoK7YeSE,2569
|
|
19
|
-
mini_arcade_core/engine/render/frame_packet.py,sha256=nYHvR7CHlIZa6ZazmPO2dU2P91vEkBjBzUVQGrOkaYc,624
|
|
20
|
-
mini_arcade_core/engine/render/packet.py,sha256=OiAPwGoVHo04OcUWMAoA_N1AFPUMyf8yxNgJthGj4-c,1440
|
|
21
|
-
mini_arcade_core/engine/render/passes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
-
mini_arcade_core/engine/render/passes/base.py,sha256=LWgWhfafbCvRKIFbt3koW-ibjYxMfKOIXyLNazCakcM,864
|
|
23
|
-
mini_arcade_core/engine/render/passes/begin_frame.py,sha256=Yzlk8bv7mgjlnM1jH_kYjJ_d8469i8aPriEqv7hm9gE,699
|
|
24
|
-
mini_arcade_core/engine/render/passes/end_frame.py,sha256=ONFcNQJhYvaN-BK6wyxxsLDBUUHx-b-mvOYH__QXJJ4,760
|
|
25
|
-
mini_arcade_core/engine/render/passes/lighting.py,sha256=ugmHHNNZWArL_Xs6-1SQIxLptUTQwe4M2sSPEk8X-7s,677
|
|
26
|
-
mini_arcade_core/engine/render/passes/postfx.py,sha256=OWcayjffZr5xT7cNIsbm4gzxmGfYiQyLgycV2dC6Jsg,1417
|
|
27
|
-
mini_arcade_core/engine/render/passes/ui.py,sha256=JxMXI900le45nOZF2SzDuniVqUOJYvAbSDRxYcIFFkc,1122
|
|
28
|
-
mini_arcade_core/engine/render/passes/world.py,sha256=QPKDVkKk-AA7ZR1HCo-JfzjxjUT6vmxXbqo1BYpebiU,1550
|
|
29
|
-
mini_arcade_core/engine/render/pipeline.py,sha256=bp2pOuwwBvJjYWZJgNe6xTfEBwdEm5WiBnM8infV_AI,3268
|
|
30
|
-
mini_arcade_core/engine/render/render_service.py,sha256=1ueir8MZ6Six5gAHt5StoICPAbyppX4DqzWb8HEuS9g,531
|
|
31
|
-
mini_arcade_core/engine/render/viewport.py,sha256=fbzH3_rc27IGUtDalmxz5cukwHnpt1kopIeVqmTab20,5784
|
|
32
|
-
mini_arcade_core/managers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
|
-
mini_arcade_core/managers/cheats.py,sha256=jMx2a8YnaNCkCG5MPmIzz4uHuS7-_aYf0J45cv2-3v0,5569
|
|
34
|
-
mini_arcade_core/managers/inputs.py,sha256=9HZ0BnJyUX-elfGETPhhPZnTkz2bK83pEKj7GHPbPFU,8523
|
|
35
|
-
mini_arcade_core/runtime/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
|
-
mini_arcade_core/runtime/audio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
-
mini_arcade_core/runtime/audio/audio_adapter.py,sha256=9ithbInYUB72ErwvlNrbIlI55uw0DT2QD33geYNXT3c,522
|
|
38
|
-
mini_arcade_core/runtime/audio/audio_port.py,sha256=3Mqv7TchEVkmd-RVjUpCD-EqA-yiL0Jf2Sj3rQwP678,907
|
|
39
|
-
mini_arcade_core/runtime/capture/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
|
-
mini_arcade_core/runtime/capture/capture_adapter.py,sha256=qJ2JiOLJHbP00IesbAyyPGPBxSaxwPJRTMaMjMU4bXs,4660
|
|
41
|
-
mini_arcade_core/runtime/capture/capture_port.py,sha256=NVxMJrQJELiSYuUJ29tvsdIcCBq4f1dTT2rDLZs6gnI,1230
|
|
42
|
-
mini_arcade_core/runtime/context.py,sha256=IQpAhtbN0ZqJVXG6KXFq3FPk0sIGyPmyNgBYviqZl7A,1632
|
|
43
|
-
mini_arcade_core/runtime/file/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
44
|
-
mini_arcade_core/runtime/file/file_adapter.py,sha256=09q7G9Qijml9d4AAjo6HLC1yuoVTjE_7xaT8apT4mk0,523
|
|
45
|
-
mini_arcade_core/runtime/file/file_port.py,sha256=p1MouCSHXZw--rWNMw3aYBLU-of8mXaT_suopczPtM8,608
|
|
46
|
-
mini_arcade_core/runtime/input/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
47
|
-
mini_arcade_core/runtime/input/input_adapter.py,sha256=vExQiwFIWTI3zYD8lmnD9TvoQPZvJfI6IINPJUqAdQ0,1467
|
|
48
|
-
mini_arcade_core/runtime/input/input_port.py,sha256=d4ptftwf92_LJdyaUMFxIsLHXBINzQyJACHn4laNyxQ,746
|
|
49
|
-
mini_arcade_core/runtime/input_frame.py,sha256=34-RAfOD-YScVLyRQrarpm7byFTHjsWM77lIH0JsmT8,2384
|
|
50
|
-
mini_arcade_core/runtime/render/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
51
|
-
mini_arcade_core/runtime/render/render_port.py,sha256=Sqp-JBh-iRzzGtgnO_nU1KiJEqyrTYPRDQbg04HdR0A,507
|
|
52
|
-
mini_arcade_core/runtime/scene/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
|
-
mini_arcade_core/runtime/scene/scene_adapter.py,sha256=RthYyp_G3c9YVVQlnCuvtgFFtp9zvdjcykc0fXEpmic,3473
|
|
54
|
-
mini_arcade_core/runtime/scene/scene_port.py,sha256=t6-REznEVvxcJoq2DJXcDs8sg0nC9fww6J5NwGpweg8,4473
|
|
55
|
-
mini_arcade_core/runtime/services.py,sha256=dtatQisOAdWgdqGjrH0cO1GA6ZDt4CiXjOEzlaNL51E,1165
|
|
56
|
-
mini_arcade_core/runtime/window/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
57
|
-
mini_arcade_core/runtime/window/window_adapter.py,sha256=_yCcJwvHN0gEPh0rkgLEKtPZ50qRbONsUTbgtV1m7Y8,2689
|
|
58
|
-
mini_arcade_core/runtime/window/window_port.py,sha256=JbSH549De7fa4ifQ0EH5QQoq03Got1n9C4qViLgciUU,2682
|
|
59
|
-
mini_arcade_core/scenes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
60
|
-
mini_arcade_core/scenes/autoreg.py,sha256=wsuY7YUSZFmDyToKHFriAG78OU48-7J4BfL_X6T5GBg,1037
|
|
61
|
-
mini_arcade_core/scenes/debug_overlay.py,sha256=pe5eXmxVl0itBdlt5Q-TEyGNiONMztMeck6Vq93QCmo,2423
|
|
62
|
-
mini_arcade_core/scenes/registry.py,sha256=SKjaw1e2EHvLimxe4ixf0DFxQTJRVQxRi96MujGgCao,3376
|
|
63
|
-
mini_arcade_core/scenes/sim_scene.py,sha256=b2JsOvPFkHCdCf8pMLJZ90qB0JJ6B8Ka3o5QK4cVshI,1055
|
|
64
|
-
mini_arcade_core/scenes/systems/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
65
|
-
mini_arcade_core/scenes/systems/base_system.py,sha256=GfMrXsO8ynW3xOxWeav7Ug5XUbRnbF0vo8VzmG7gpec,1075
|
|
66
|
-
mini_arcade_core/scenes/systems/system_pipeline.py,sha256=Cy9y1DclbMLZZ-yx7OKYe34ORoGLNa6dReQfOdiO8SY,1642
|
|
67
|
-
mini_arcade_core/sim/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
68
|
-
mini_arcade_core/sim/protocols.py,sha256=b7d2WAKRokTNbteNhUaWdGx9vc9Fnccxb-5rPwozDaA,1099
|
|
69
|
-
mini_arcade_core/sim/runner.py,sha256=ZF-BZJw-NcaFrg4zsUu1zOUUBZwZbRYflqcdF1jDcmM,7446
|
|
70
|
-
mini_arcade_core/spaces/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
71
|
-
mini_arcade_core/spaces/d2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
72
|
-
mini_arcade_core/spaces/d2/boundaries2d.py,sha256=xeTnd0pW5DKfqaKsfSBXnufeb45aXNIspgHRyLXWejo,2804
|
|
73
|
-
mini_arcade_core/spaces/d2/collision2d.py,sha256=5IvgLnyVb8i0uzzZuum1noWsNhoxcvHOLaHkmrTMTxQ,1710
|
|
74
|
-
mini_arcade_core/spaces/d2/geometry2d.py,sha256=FuYzef-XdOyb1aeGLJbxINxr0WJHnqFFBgtbPi1WonY,1716
|
|
75
|
-
mini_arcade_core/spaces/d2/kinematics2d.py,sha256=AJ3DhPXNgm6wZYwCljMIE4_2BYx3E2rPcwhXTgQALkU,2030
|
|
76
|
-
mini_arcade_core/spaces/d2/physics2d.py,sha256=OQT7r-zMtmoKD2aWCSNmRAdI0OGIpxGX-pLR8LcAMbQ,1854
|
|
77
|
-
mini_arcade_core/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
78
|
-
mini_arcade_core/ui/menu.py,sha256=pUC6qfG3lNXa1Ga2Kz_cSgjiAfAWVMRevum61G_RE8k,24646
|
|
79
|
-
mini_arcade_core/utils/__init__.py,sha256=3Q9r6bTyqImYix8BnOGwWjAz25nbTQezGcRq3m5KEYE,189
|
|
80
|
-
mini_arcade_core/utils/deprecated_decorator.py,sha256=yrrW2ZqPskK-4MUTyIrMb465Wc54X2poV53ZQutZWqc,1140
|
|
81
|
-
mini_arcade_core/utils/logging.py,sha256=YyirsGRSpGtxegUl3HWz37mGNngK3QkYm2_aZjXJC84,5279
|
|
82
|
-
mini_arcade_core-1.1.1.dist-info/METADATA,sha256=uLI8iX6Z3XyhoHBQDw4vmWfXRkk-09IXqUvHMvwHrJQ,8188
|
|
83
|
-
mini_arcade_core-1.1.1.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
|
|
84
|
-
mini_arcade_core-1.1.1.dist-info/licenses/LICENSE,sha256=3lHAuV0584cVS5vAqi2uC6GcsVgxUijvwvtZckyvaZ4,1096
|
|
85
|
-
mini_arcade_core-1.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|