mini-arcade-core 1.0.1__tar.gz → 1.1.0__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.
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/PKG-INFO +1 -1
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/pyproject.toml +1 -1
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/engine/commands.py +30 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/engine/game.py +31 -6
- mini_arcade_core-1.1.0/src/mini_arcade_core/engine/render/context.py +48 -0
- mini_arcade_core-1.1.0/src/mini_arcade_core/engine/render/frame_packet.py +26 -0
- mini_arcade_core-1.1.0/src/mini_arcade_core/engine/render/passes/base.py +37 -0
- mini_arcade_core-1.1.0/src/mini_arcade_core/engine/render/passes/begin_frame.py +27 -0
- mini_arcade_core-1.1.0/src/mini_arcade_core/engine/render/passes/end_frame.py +28 -0
- mini_arcade_core-1.1.0/src/mini_arcade_core/engine/render/passes/lighting.py +28 -0
- mini_arcade_core-1.1.0/src/mini_arcade_core/engine/render/passes/postfx.py +28 -0
- mini_arcade_core-1.1.0/src/mini_arcade_core/engine/render/passes/ui.py +41 -0
- mini_arcade_core-1.1.0/src/mini_arcade_core/engine/render/passes/world.py +55 -0
- mini_arcade_core-1.1.0/src/mini_arcade_core/engine/render/pipeline.py +112 -0
- mini_arcade_core-1.1.0/src/mini_arcade_core/engine/render/render_service.py +22 -0
- mini_arcade_core-1.1.0/src/mini_arcade_core/runtime/render/render_port.py +22 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/scene/scene_adapter.py +29 -1
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/scene/scene_port.py +21 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/services.py +2 -0
- mini_arcade_core-1.1.0/src/mini_arcade_core/scenes/debug_overlay.py +70 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/scenes/registry.py +10 -10
- mini_arcade_core-1.1.0/src/mini_arcade_core/spaces/d2/__init__.py +0 -0
- mini_arcade_core-1.1.0/src/mini_arcade_core/ui/__init__.py +0 -0
- mini_arcade_core-1.0.1/src/mini_arcade_core/engine/render/pipeline.py +0 -63
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/LICENSE +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/README.md +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/__init__.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/backend/__init__.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/backend/backend.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/backend/events.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/backend/keys.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/backend/sdl_map.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/backend/types.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/bus.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/engine/__init__.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/engine/render/__init__.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/engine/render/packet.py +0 -0
- {mini_arcade_core-1.0.1/src/mini_arcade_core/managers → mini_arcade_core-1.1.0/src/mini_arcade_core/engine/render/passes}/__init__.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/engine/render/viewport.py +0 -0
- {mini_arcade_core-1.0.1/src/mini_arcade_core/runtime → mini_arcade_core-1.1.0/src/mini_arcade_core/managers}/__init__.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/managers/cheats.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/managers/inputs.py +0 -0
- {mini_arcade_core-1.0.1/src/mini_arcade_core/runtime/audio → mini_arcade_core-1.1.0/src/mini_arcade_core/runtime}/__init__.py +0 -0
- {mini_arcade_core-1.0.1/src/mini_arcade_core/runtime/capture → mini_arcade_core-1.1.0/src/mini_arcade_core/runtime/audio}/__init__.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/audio/audio_adapter.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/audio/audio_port.py +0 -0
- {mini_arcade_core-1.0.1/src/mini_arcade_core/runtime/file → mini_arcade_core-1.1.0/src/mini_arcade_core/runtime/capture}/__init__.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/capture/capture_adapter.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/capture/capture_port.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/context.py +0 -0
- {mini_arcade_core-1.0.1/src/mini_arcade_core/runtime/input → mini_arcade_core-1.1.0/src/mini_arcade_core/runtime/file}/__init__.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/file/file_adapter.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/file/file_port.py +0 -0
- {mini_arcade_core-1.0.1/src/mini_arcade_core/runtime/scene → mini_arcade_core-1.1.0/src/mini_arcade_core/runtime/input}/__init__.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/input/input_adapter.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/input/input_port.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/input_frame.py +0 -0
- {mini_arcade_core-1.0.1/src/mini_arcade_core/runtime/window → mini_arcade_core-1.1.0/src/mini_arcade_core/runtime/render}/__init__.py +0 -0
- {mini_arcade_core-1.0.1/src/mini_arcade_core/scenes → mini_arcade_core-1.1.0/src/mini_arcade_core/runtime/scene}/__init__.py +0 -0
- {mini_arcade_core-1.0.1/src/mini_arcade_core/scenes/systems → mini_arcade_core-1.1.0/src/mini_arcade_core/runtime/window}/__init__.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/window/window_adapter.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/window/window_port.py +0 -0
- {mini_arcade_core-1.0.1/src/mini_arcade_core/sim → mini_arcade_core-1.1.0/src/mini_arcade_core/scenes}/__init__.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/scenes/autoreg.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/scenes/sim_scene.py +0 -0
- {mini_arcade_core-1.0.1/src/mini_arcade_core/spaces → mini_arcade_core-1.1.0/src/mini_arcade_core/scenes/systems}/__init__.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/scenes/systems/base_system.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/scenes/systems/system_pipeline.py +0 -0
- {mini_arcade_core-1.0.1/src/mini_arcade_core/spaces/d2 → mini_arcade_core-1.1.0/src/mini_arcade_core/sim}/__init__.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/sim/protocols.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/sim/runner.py +0 -0
- {mini_arcade_core-1.0.1/src/mini_arcade_core/ui → mini_arcade_core-1.1.0/src/mini_arcade_core/spaces}/__init__.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/spaces/d2/boundaries2d.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/spaces/d2/collision2d.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/spaces/d2/geometry2d.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/spaces/d2/kinematics2d.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/spaces/d2/physics2d.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/ui/menu.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/utils/__init__.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/utils/deprecated_decorator.py +0 -0
- {mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/utils/logging.py +0 -0
|
@@ -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
|
|
7
|
+
version = "1.1.0"
|
|
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,31 @@ 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
|
+
"""
|
|
177
|
+
Toggle the debug overlay scene.
|
|
178
|
+
|
|
179
|
+
:cvar DEBUG_OVERLAY_ID: str: Identifier for the debug overlay scene.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
DEBUG_OVERLAY_ID = "debug_overlay"
|
|
183
|
+
|
|
184
|
+
def execute(self, context: CommandContext):
|
|
185
|
+
scenes = context.services.scenes
|
|
186
|
+
if scenes.has_scene(self.DEBUG_OVERLAY_ID):
|
|
187
|
+
scenes.remove_scene(self.DEBUG_OVERLAY_ID)
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
scenes.push(
|
|
191
|
+
self.DEBUG_OVERLAY_ID,
|
|
192
|
+
as_overlay=True,
|
|
193
|
+
policy=ScenePolicy(
|
|
194
|
+
blocks_update=False,
|
|
195
|
+
blocks_input=False,
|
|
196
|
+
is_opaque=False,
|
|
197
|
+
receives_input=False,
|
|
198
|
+
),
|
|
199
|
+
)
|
|
@@ -10,13 +10,18 @@ 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
|
)
|
|
20
|
+
from mini_arcade_core.engine.render.context import RenderContext
|
|
21
|
+
from mini_arcade_core.engine.render.frame_packet import FramePacket
|
|
18
22
|
from mini_arcade_core.engine.render.packet import RenderPacket
|
|
19
23
|
from mini_arcade_core.engine.render.pipeline import RenderPipeline
|
|
24
|
+
from mini_arcade_core.engine.render.render_service import RenderService
|
|
20
25
|
from mini_arcade_core.managers.cheats import CheatManager
|
|
21
26
|
from mini_arcade_core.runtime.audio.audio_adapter import SDLAudioAdapter
|
|
22
27
|
from mini_arcade_core.runtime.capture.capture_adapter import CaptureAdapter
|
|
@@ -186,6 +191,7 @@ class Game:
|
|
|
186
191
|
files=LocalFilesAdapter(),
|
|
187
192
|
capture=CaptureAdapter(self.backend),
|
|
188
193
|
input=InputAdapter(),
|
|
194
|
+
render=RenderService(),
|
|
189
195
|
)
|
|
190
196
|
|
|
191
197
|
self.command_queue = CommandQueue()
|
|
@@ -248,6 +254,9 @@ class Game:
|
|
|
248
254
|
w, h = e.size
|
|
249
255
|
logger.debug(f"Window resized event: {w}x{h}")
|
|
250
256
|
self.services.window.on_window_resized(w, h)
|
|
257
|
+
# if F1 pressed, toggle debug overlay
|
|
258
|
+
if e.type == EventType.KEYDOWN and e.key == Key.F1:
|
|
259
|
+
self.command_queue.push(ToggleDebugOverlayCommand())
|
|
251
260
|
timer.mark("events_polled")
|
|
252
261
|
|
|
253
262
|
input_frame = self.services.input.build(events, frame_index, dt)
|
|
@@ -299,23 +308,39 @@ class Game:
|
|
|
299
308
|
cmd.execute(command_context)
|
|
300
309
|
timer.mark("cmd_exec_end")
|
|
301
310
|
|
|
311
|
+
# ---------------- TO REPLACE WITH RENDERING PIPELINE ----------------
|
|
302
312
|
timer.mark("render_start")
|
|
303
|
-
backend.begin_frame()
|
|
304
|
-
timer.mark("begin_frame_done")
|
|
305
313
|
|
|
306
314
|
vp = self.services.window.get_viewport()
|
|
315
|
+
|
|
316
|
+
# gather visible packets
|
|
317
|
+
frame_packets: list[RenderPacket] = []
|
|
307
318
|
for entry in self.services.scenes.visible_entries():
|
|
308
319
|
scene = entry.scene
|
|
309
320
|
packet = packet_cache.get(id(scene))
|
|
310
321
|
if packet is None:
|
|
311
|
-
# bootstrap (first frame visible but not updated)
|
|
312
322
|
packet = scene.tick(_neutral_input(frame_index, 0.0), 0.0)
|
|
313
323
|
packet_cache[id(scene)] = packet
|
|
324
|
+
frame_packets.append(
|
|
325
|
+
FramePacket(
|
|
326
|
+
scene_id=entry.scene_id,
|
|
327
|
+
is_overlay=entry.is_overlay,
|
|
328
|
+
packet=packet,
|
|
329
|
+
)
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
render_ctx = RenderContext(
|
|
333
|
+
viewport=vp,
|
|
334
|
+
debug_overlay=getattr(self.settings, "debug_overlay", False),
|
|
335
|
+
frame_ms=dt * 1000.0,
|
|
336
|
+
)
|
|
314
337
|
|
|
315
|
-
|
|
338
|
+
self.services.render.last_frame_ms = render_ctx.frame_ms
|
|
339
|
+
self.services.render.last_stats = render_ctx.stats
|
|
340
|
+
pipeline.render_frame(backend, render_ctx, frame_packets)
|
|
316
341
|
|
|
317
|
-
timer.mark("
|
|
318
|
-
|
|
342
|
+
timer.mark("render_done")
|
|
343
|
+
# ---------------- END RENDERING PIPELINE ----------------------------
|
|
319
344
|
timer.mark("end_frame_done")
|
|
320
345
|
|
|
321
346
|
timer.mark("sleep_start")
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Render context and stats for a single frame render.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from mini_arcade_core.engine.render.viewport import ViewportState
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class RenderStats:
|
|
15
|
+
"""
|
|
16
|
+
Statistics about the rendering process for a single frame.
|
|
17
|
+
|
|
18
|
+
:ivar packets (int): Number of render packets processed.
|
|
19
|
+
:ivar ops (int): Number of rendering operations executed.
|
|
20
|
+
:ivar draw_groups (int): Number of draw groups processed.
|
|
21
|
+
:ivar renderables (int): Number of renderable objects processed.
|
|
22
|
+
:ivar draw_groups (int): Number of draw groups processed.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
packets: int = 0
|
|
26
|
+
ops: int = 0
|
|
27
|
+
draw_groups: int = 0 # approx ok
|
|
28
|
+
renderables: int = 0
|
|
29
|
+
draw_groups: int = 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class RenderContext:
|
|
34
|
+
"""
|
|
35
|
+
Context for rendering a single frame.
|
|
36
|
+
|
|
37
|
+
:ivar viewport: ViewportState: Current viewport state.
|
|
38
|
+
:ivar debug_overlay: bool: Whether to render debug overlays.
|
|
39
|
+
:ivar frame_ms: float: Time taken to render the frame in milliseconds.
|
|
40
|
+
:ivar stats: RenderStats: Statistics about the rendering process.
|
|
41
|
+
:ivar meta: dict[str, Any]: Additional metadata for rendering.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
viewport: ViewportState
|
|
45
|
+
debug_overlay: bool = False
|
|
46
|
+
frame_ms: float = 0.0
|
|
47
|
+
stats: RenderStats = field(default_factory=RenderStats)
|
|
48
|
+
meta: dict[str, Any] = field(default_factory=dict)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Frame packet for rendering.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from mini_arcade_core.engine.render.packet import RenderPacket
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class FramePacket:
|
|
14
|
+
"""
|
|
15
|
+
A packet representing a frame to be rendered, associated with a specific scene
|
|
16
|
+
and indicating whether it is an overlay.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
:ivar scene_id (str): Identifier of the scene.
|
|
20
|
+
:ivar is_overlay (bool): Whether the frame is an overlay.
|
|
21
|
+
:ivar packet (RenderPacket): The render packet containing rendering operations.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
scene_id: str
|
|
25
|
+
is_overlay: bool
|
|
26
|
+
packet: RenderPacket
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Render pass base protocol.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Protocol
|
|
8
|
+
|
|
9
|
+
from mini_arcade_core.backend import Backend
|
|
10
|
+
from mini_arcade_core.engine.render.context import RenderContext
|
|
11
|
+
from mini_arcade_core.engine.render.packet import RenderPacket
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RenderPass(Protocol):
|
|
15
|
+
"""
|
|
16
|
+
Render pass protocol.
|
|
17
|
+
|
|
18
|
+
:ivar name: str: Name of the render pass.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
name: str
|
|
22
|
+
|
|
23
|
+
def run(
|
|
24
|
+
self, backend: Backend, ctx: RenderContext, packets: list[RenderPacket]
|
|
25
|
+
):
|
|
26
|
+
"""
|
|
27
|
+
Run the render pass.
|
|
28
|
+
|
|
29
|
+
:param backend: Backend: The rendering backend.
|
|
30
|
+
:type backend: Backend
|
|
31
|
+
|
|
32
|
+
:param ctx: RenderContext: The rendering context.
|
|
33
|
+
:type ctx: RenderContext
|
|
34
|
+
|
|
35
|
+
:param packets: list[RenderPacket]: List of render packets to process.
|
|
36
|
+
:type packets: list[RenderPacket]
|
|
37
|
+
"""
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Begin Frame Render Pass
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from mini_arcade_core.backend import Backend
|
|
8
|
+
from mini_arcade_core.engine.render.context import RenderContext
|
|
9
|
+
from mini_arcade_core.engine.render.packet import RenderPacket
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class BeginFramePass:
|
|
14
|
+
"""
|
|
15
|
+
Begin Frame Render Pass.
|
|
16
|
+
This pass signals the start of a new frame to the backend.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
name: str = "BeginFrame"
|
|
20
|
+
|
|
21
|
+
# Justification: some arguments are unused but required by the protocol
|
|
22
|
+
# pylint: disable=unused-argument
|
|
23
|
+
def run(
|
|
24
|
+
self, backend: Backend, ctx: RenderContext, packets: list[RenderPacket]
|
|
25
|
+
):
|
|
26
|
+
"""Run the begin frame pass."""
|
|
27
|
+
backend.begin_frame()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
End Frame render pass implementation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from mini_arcade_core.backend import Backend
|
|
8
|
+
from mini_arcade_core.engine.render.context import RenderContext
|
|
9
|
+
from mini_arcade_core.engine.render.packet import RenderPacket
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class EndFramePass:
|
|
14
|
+
"""
|
|
15
|
+
End Frame Render Pass.
|
|
16
|
+
This pass signals the end of the current frame to the backend.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
name: str = "EndFrame"
|
|
20
|
+
|
|
21
|
+
# Justification: some arguments are unused but required by the protocol
|
|
22
|
+
# pylint: disable=unused-argument
|
|
23
|
+
def run(
|
|
24
|
+
self, backend: Backend, ctx: RenderContext, packets: list[RenderPacket]
|
|
25
|
+
):
|
|
26
|
+
"""Run the end frame pass."""
|
|
27
|
+
# Signal the end of the frame to the backend
|
|
28
|
+
backend.end_frame()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lighting render pass implementation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from mini_arcade_core.backend import Backend
|
|
8
|
+
from mini_arcade_core.engine.render.context import RenderContext
|
|
9
|
+
from mini_arcade_core.engine.render.packet import RenderPacket
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class LightingPass:
|
|
14
|
+
"""
|
|
15
|
+
Lighting Render Pass.
|
|
16
|
+
This pass handles scene lighting effects.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
name: str = "LightingPass"
|
|
20
|
+
|
|
21
|
+
# Justification: No implementation yet
|
|
22
|
+
# pylint: disable=unused-argument
|
|
23
|
+
def run(
|
|
24
|
+
self, backend: Backend, ctx: RenderContext, packets: list[RenderPacket]
|
|
25
|
+
):
|
|
26
|
+
"""Run the lighting render pass."""
|
|
27
|
+
# hook/no-op for now
|
|
28
|
+
return
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Post-processing effects render pass implementation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from mini_arcade_core.backend import Backend
|
|
8
|
+
from mini_arcade_core.engine.render.context import RenderContext
|
|
9
|
+
from mini_arcade_core.engine.render.packet import RenderPacket
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class PostFXPass:
|
|
14
|
+
"""
|
|
15
|
+
PostFX Render Pass.
|
|
16
|
+
This pass handles post-processing effects like CRT simulation.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
name: str = "PostFXPass"
|
|
20
|
+
|
|
21
|
+
# Justification: No implementation yet
|
|
22
|
+
# pylint: disable=unused-argument
|
|
23
|
+
def run(
|
|
24
|
+
self, backend: Backend, ctx: RenderContext, packets: list[RenderPacket]
|
|
25
|
+
):
|
|
26
|
+
"""Run the post-processing effects render pass."""
|
|
27
|
+
# hook/no-op for now (CRT later)
|
|
28
|
+
return
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
UI render pass implementation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from mini_arcade_core.backend import Backend
|
|
8
|
+
from mini_arcade_core.engine.render.context import RenderContext
|
|
9
|
+
from mini_arcade_core.engine.render.frame_packet import FramePacket
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class UIPass:
|
|
14
|
+
"""
|
|
15
|
+
UI Render Pass.
|
|
16
|
+
This pass handles rendering of UI overlays.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
name: str = "UIPass"
|
|
20
|
+
|
|
21
|
+
def run(
|
|
22
|
+
self, backend: Backend, ctx: RenderContext, packets: list[FramePacket]
|
|
23
|
+
):
|
|
24
|
+
"""Run the UI render pass."""
|
|
25
|
+
# UI overlays should be screen-space (no world transform / no clip unless you want it)
|
|
26
|
+
backend.clear_viewport_transform()
|
|
27
|
+
backend.clear_clip_rect()
|
|
28
|
+
|
|
29
|
+
for fp in packets:
|
|
30
|
+
if not fp.is_overlay:
|
|
31
|
+
continue
|
|
32
|
+
if not fp.packet or not fp.packet.ops:
|
|
33
|
+
continue
|
|
34
|
+
|
|
35
|
+
# count overlays too (optional; I’d count them)
|
|
36
|
+
ctx.stats.packets += 1
|
|
37
|
+
ctx.stats.renderables += len(fp.packet.ops)
|
|
38
|
+
ctx.stats.draw_groups += 1
|
|
39
|
+
|
|
40
|
+
for op in fp.packet.ops:
|
|
41
|
+
op(backend)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
World render pass implementation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from mini_arcade_core.backend import Backend
|
|
8
|
+
from mini_arcade_core.engine.render.context import RenderContext
|
|
9
|
+
from mini_arcade_core.engine.render.frame_packet import FramePacket
|
|
10
|
+
from mini_arcade_core.engine.render.packet import RenderPacket
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class WorldPass:
|
|
15
|
+
"""
|
|
16
|
+
World Render Pass.
|
|
17
|
+
This pass handles rendering of world-space objects.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
name: str = "WorldPass"
|
|
21
|
+
|
|
22
|
+
def run(
|
|
23
|
+
self, backend: Backend, ctx: RenderContext, packets: list[FramePacket]
|
|
24
|
+
):
|
|
25
|
+
"""Run the world render pass."""
|
|
26
|
+
for fp in packets:
|
|
27
|
+
if fp.is_overlay:
|
|
28
|
+
continue
|
|
29
|
+
self._draw_packet(backend, ctx, fp.packet)
|
|
30
|
+
|
|
31
|
+
def _draw_packet(
|
|
32
|
+
self, backend: Backend, ctx: RenderContext, packet: RenderPacket
|
|
33
|
+
):
|
|
34
|
+
if not packet or not packet.ops:
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
ctx.stats.packets += 1
|
|
38
|
+
ctx.stats.renderables += len(packet.ops)
|
|
39
|
+
ctx.stats.draw_groups += 1 # approx: 1 group per packet
|
|
40
|
+
|
|
41
|
+
backend.set_viewport_transform(
|
|
42
|
+
ctx.viewport.offset_x, ctx.viewport.offset_y, ctx.viewport.scale
|
|
43
|
+
)
|
|
44
|
+
backend.set_clip_rect(
|
|
45
|
+
ctx.viewport.offset_x,
|
|
46
|
+
ctx.viewport.offset_y,
|
|
47
|
+
ctx.viewport.viewport_w,
|
|
48
|
+
ctx.viewport.viewport_h,
|
|
49
|
+
)
|
|
50
|
+
try:
|
|
51
|
+
for op in packet.ops:
|
|
52
|
+
op(backend)
|
|
53
|
+
finally:
|
|
54
|
+
backend.clear_clip_rect()
|
|
55
|
+
backend.clear_viewport_transform()
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Render pipeline module.
|
|
3
|
+
Defines the RenderPipeline class for rendering RenderPackets.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# Justification: This code is duplicated in multiple places for clarity and separation
|
|
7
|
+
# of concerns.
|
|
8
|
+
# try:
|
|
9
|
+
# for op in packet.ops:
|
|
10
|
+
# op(backend)
|
|
11
|
+
# finally:
|
|
12
|
+
# backend.clear_clip_rect()
|
|
13
|
+
# backend.clear_viewport_transform() (duplicate-code)
|
|
14
|
+
# pylint: disable=duplicate-code
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
|
|
20
|
+
from mini_arcade_core.backend import Backend
|
|
21
|
+
from mini_arcade_core.engine.render.context import RenderContext
|
|
22
|
+
from mini_arcade_core.engine.render.packet import RenderPacket
|
|
23
|
+
from mini_arcade_core.engine.render.passes.base import RenderPass
|
|
24
|
+
from mini_arcade_core.engine.render.passes.begin_frame import BeginFramePass
|
|
25
|
+
from mini_arcade_core.engine.render.passes.end_frame import EndFramePass
|
|
26
|
+
from mini_arcade_core.engine.render.passes.lighting import LightingPass
|
|
27
|
+
from mini_arcade_core.engine.render.passes.postfx import PostFXPass
|
|
28
|
+
from mini_arcade_core.engine.render.passes.ui import UIPass
|
|
29
|
+
from mini_arcade_core.engine.render.passes.world import WorldPass
|
|
30
|
+
from mini_arcade_core.engine.render.viewport import ViewportState
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class RenderPipeline:
|
|
35
|
+
"""
|
|
36
|
+
Minimal pipeline for v1.
|
|
37
|
+
|
|
38
|
+
Later you can expand this into passes:
|
|
39
|
+
- build draw list
|
|
40
|
+
- cull
|
|
41
|
+
- sort
|
|
42
|
+
- backend draw pass
|
|
43
|
+
|
|
44
|
+
:cvar passes: list[RenderPass]: List of render passes to execute in order.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
passes: list[RenderPass] = field(
|
|
48
|
+
default_factory=lambda: [
|
|
49
|
+
BeginFramePass(),
|
|
50
|
+
WorldPass(),
|
|
51
|
+
LightingPass(),
|
|
52
|
+
UIPass(),
|
|
53
|
+
PostFXPass(),
|
|
54
|
+
EndFramePass(),
|
|
55
|
+
]
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def render_frame(
|
|
59
|
+
self, backend: Backend, ctx: RenderContext, packets: list[RenderPacket]
|
|
60
|
+
):
|
|
61
|
+
"""
|
|
62
|
+
Render a frame using the provided Backend, RenderContext, and list of RenderPackets.
|
|
63
|
+
|
|
64
|
+
:param backend: Backend to use for rendering.
|
|
65
|
+
:type backend: Backend
|
|
66
|
+
|
|
67
|
+
:param ctx: RenderContext containing rendering state.
|
|
68
|
+
:type ctx: RenderContext
|
|
69
|
+
|
|
70
|
+
:param packets: List of RenderPackets to render.
|
|
71
|
+
:type packets: list[RenderPacket]
|
|
72
|
+
"""
|
|
73
|
+
for p in self.passes:
|
|
74
|
+
p.run(backend, ctx, packets)
|
|
75
|
+
|
|
76
|
+
def draw_packet(
|
|
77
|
+
self,
|
|
78
|
+
backend: Backend,
|
|
79
|
+
packet: RenderPacket,
|
|
80
|
+
viewport_state: ViewportState,
|
|
81
|
+
):
|
|
82
|
+
"""
|
|
83
|
+
Draw the given RenderPacket using the provided Backend.
|
|
84
|
+
|
|
85
|
+
:param backend: Backend to use for drawing.
|
|
86
|
+
:type backend: Backend
|
|
87
|
+
|
|
88
|
+
:param packet: RenderPacket to draw.
|
|
89
|
+
:type packet: RenderPacket
|
|
90
|
+
"""
|
|
91
|
+
if not packet:
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
backend.set_viewport_transform(
|
|
95
|
+
viewport_state.offset_x,
|
|
96
|
+
viewport_state.offset_y,
|
|
97
|
+
viewport_state.scale,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
backend.set_clip_rect(
|
|
101
|
+
viewport_state.offset_x,
|
|
102
|
+
viewport_state.offset_y,
|
|
103
|
+
viewport_state.viewport_w,
|
|
104
|
+
viewport_state.viewport_h,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
for op in packet.ops:
|
|
109
|
+
op(backend)
|
|
110
|
+
finally:
|
|
111
|
+
backend.clear_clip_rect()
|
|
112
|
+
backend.clear_viewport_transform()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Render service definition.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from mini_arcade_core.engine.render.context import RenderStats
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class RenderService:
|
|
14
|
+
"""
|
|
15
|
+
Render Service.
|
|
16
|
+
This service manages rendering statistics and state.
|
|
17
|
+
:ivar last_frame_ms (float): Time taken for the last frame in milliseconds.
|
|
18
|
+
:ivar last_stats (RenderStats): Rendering statistics from the last frame.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
last_frame_ms: float = 0.0
|
|
22
|
+
last_stats: RenderStats = RenderStats()
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Render service definition.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Protocol
|
|
8
|
+
|
|
9
|
+
from mini_arcade_core.engine.render.context import RenderStats
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RenderServicePort(Protocol):
|
|
13
|
+
"""
|
|
14
|
+
Render Service.
|
|
15
|
+
This service manages rendering statistics and state.
|
|
16
|
+
|
|
17
|
+
:ivar last_frame_ms (float): Time taken for the last frame in milliseconds.
|
|
18
|
+
:ivar last_stats (RenderStats): Rendering statistics from the last frame.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
last_frame_ms: float
|
|
22
|
+
last_stats: RenderStats
|
|
@@ -94,4 +94,32 @@ class SceneAdapter(ScenePort):
|
|
|
94
94
|
|
|
95
95
|
def input_entry(self):
|
|
96
96
|
vis = self.visible_entries()
|
|
97
|
-
|
|
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
|
{mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/scene/scene_port.py
RENAMED
|
@@ -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):
|
|
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
|
+
"""
|
|
@@ -10,6 +10,7 @@ from mini_arcade_core.runtime.audio.audio_port import AudioPort
|
|
|
10
10
|
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
|
+
from mini_arcade_core.runtime.render.render_port import RenderServicePort
|
|
13
14
|
from mini_arcade_core.runtime.scene.scene_port import ScenePort
|
|
14
15
|
from mini_arcade_core.runtime.window.window_port import WindowPort
|
|
15
16
|
|
|
@@ -33,3 +34,4 @@ class RuntimeServices:
|
|
|
33
34
|
files: FilePort
|
|
34
35
|
capture: CapturePort
|
|
35
36
|
input: InputPort
|
|
37
|
+
render: RenderServicePort
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
rs = services.render
|
|
43
|
+
lines = [
|
|
44
|
+
f"FPS: {self._fps:5.1f}",
|
|
45
|
+
f"dt: {dt*1000.0:5.2f} ms",
|
|
46
|
+
f"frame: {rs.last_frame_ms:5.2f} ms",
|
|
47
|
+
f"renderables: {rs.last_stats.renderables}",
|
|
48
|
+
f"draw_groups~: {rs.last_stats.draw_groups}",
|
|
49
|
+
f"virtual: {vp.virtual_w}x{vp.virtual_h}",
|
|
50
|
+
f"window: {vp.window_w}x{vp.window_h}",
|
|
51
|
+
f"scale: {vp.scale:.3f}",
|
|
52
|
+
f"offset: ({vp.offset_x},{vp.offset_y})",
|
|
53
|
+
"stack:",
|
|
54
|
+
]
|
|
55
|
+
for e in stack:
|
|
56
|
+
lines.append(f" - {e.scene_id} overlay={e.is_overlay}")
|
|
57
|
+
|
|
58
|
+
def draw(backend):
|
|
59
|
+
# translucent background panel
|
|
60
|
+
backend.draw_rect(
|
|
61
|
+
8, 8, 360, 18 * (len(lines) + 1), color=(0, 0, 0, 0.65)
|
|
62
|
+
)
|
|
63
|
+
y = 14
|
|
64
|
+
for line in lines:
|
|
65
|
+
backend.draw_text(
|
|
66
|
+
16, y, line, color=(255, 255, 255), font_size=14
|
|
67
|
+
)
|
|
68
|
+
y += 18
|
|
69
|
+
|
|
70
|
+
return RenderPacket(ops=[draw])
|
|
@@ -15,8 +15,7 @@ from mini_arcade_core.runtime.context import RuntimeContext
|
|
|
15
15
|
from .autoreg import snapshot
|
|
16
16
|
|
|
17
17
|
if TYPE_CHECKING:
|
|
18
|
-
from mini_arcade_core.
|
|
19
|
-
from mini_arcade_core.sim import SimScene
|
|
18
|
+
from mini_arcade_core.sim.protocols import SimScene
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
class SceneFactory(Protocol):
|
|
@@ -93,22 +92,23 @@ class SceneRegistry:
|
|
|
93
92
|
for scene_id, cls in catalog.items():
|
|
94
93
|
self.register_cls(scene_id, cls)
|
|
95
94
|
|
|
96
|
-
def discover(self,
|
|
95
|
+
def discover(self, *packages: str) -> "SceneRegistry":
|
|
97
96
|
"""
|
|
98
97
|
Import all modules in a package so @scene decorators run.
|
|
99
98
|
|
|
100
|
-
:param
|
|
101
|
-
:type
|
|
99
|
+
:param packages: The package names to scan for scene modules.
|
|
100
|
+
:type packages: str
|
|
102
101
|
|
|
103
102
|
:return: The SceneRegistry instance (for chaining).
|
|
104
103
|
:rtype: SceneRegistry
|
|
105
104
|
"""
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
for package in packages:
|
|
106
|
+
pkg = importlib.import_module(package)
|
|
107
|
+
if not hasattr(pkg, "__path__"):
|
|
108
|
+
continue
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
for mod in pkgutil.walk_packages(pkg.__path__, pkg.__name__ + "."):
|
|
111
|
+
importlib.import_module(mod.name)
|
|
112
112
|
|
|
113
113
|
self.load_catalog(snapshot())
|
|
114
114
|
return self
|
|
File without changes
|
|
File without changes
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Render pipeline module.
|
|
3
|
-
Defines the RenderPipeline class for rendering RenderPackets.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from __future__ import annotations
|
|
7
|
-
|
|
8
|
-
from dataclasses import dataclass
|
|
9
|
-
|
|
10
|
-
from mini_arcade_core.backend import Backend
|
|
11
|
-
from mini_arcade_core.engine.render.packet import RenderPacket
|
|
12
|
-
from mini_arcade_core.engine.render.viewport import ViewportState
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@dataclass
|
|
16
|
-
class RenderPipeline:
|
|
17
|
-
"""
|
|
18
|
-
Minimal pipeline for v1.
|
|
19
|
-
|
|
20
|
-
Later you can expand this into passes:
|
|
21
|
-
- build draw list
|
|
22
|
-
- cull
|
|
23
|
-
- sort
|
|
24
|
-
- backend draw pass
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
def draw_packet(
|
|
28
|
-
self,
|
|
29
|
-
backend: Backend,
|
|
30
|
-
packet: RenderPacket,
|
|
31
|
-
viewport_state: ViewportState,
|
|
32
|
-
):
|
|
33
|
-
"""
|
|
34
|
-
Draw the given RenderPacket using the provided Backend.
|
|
35
|
-
|
|
36
|
-
:param backend: Backend to use for drawing.
|
|
37
|
-
:type backend: Backend
|
|
38
|
-
|
|
39
|
-
:param packet: RenderPacket to draw.
|
|
40
|
-
:type packet: RenderPacket
|
|
41
|
-
"""
|
|
42
|
-
if not packet:
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
backend.set_viewport_transform(
|
|
46
|
-
viewport_state.offset_x,
|
|
47
|
-
viewport_state.offset_y,
|
|
48
|
-
viewport_state.scale,
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
# backend.set_clip_rect(
|
|
52
|
-
# viewport_state.offset_x,
|
|
53
|
-
# viewport_state.offset_y,
|
|
54
|
-
# viewport_state.viewport_w,
|
|
55
|
-
# viewport_state.viewport_h,
|
|
56
|
-
# )
|
|
57
|
-
|
|
58
|
-
try:
|
|
59
|
-
for op in packet.ops:
|
|
60
|
-
op(backend)
|
|
61
|
-
finally:
|
|
62
|
-
backend.clear_clip_rect()
|
|
63
|
-
backend.clear_viewport_transform()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/engine/render/__init__.py
RENAMED
|
File without changes
|
{mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/engine/render/packet.py
RENAMED
|
File without changes
|
|
File without changes
|
{mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/engine/render/viewport.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/audio/audio_port.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/file/file_adapter.py
RENAMED
|
File without changes
|
{mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/file/file_port.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/input/input_port.py
RENAMED
|
File without changes
|
{mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/input_frame.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/runtime/window/window_port.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/scenes/systems/base_system.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/spaces/d2/boundaries2d.py
RENAMED
|
File without changes
|
{mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/spaces/d2/collision2d.py
RENAMED
|
File without changes
|
{mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/spaces/d2/geometry2d.py
RENAMED
|
File without changes
|
{mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/spaces/d2/kinematics2d.py
RENAMED
|
File without changes
|
{mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/spaces/d2/physics2d.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mini_arcade_core-1.0.1 → mini_arcade_core-1.1.0}/src/mini_arcade_core/utils/deprecated_decorator.py
RENAMED
|
File without changes
|
|
File without changes
|