mini-arcade-core 0.10.0__py3-none-any.whl → 1.0.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 +43 -60
- mini_arcade_core/backend/__init__.py +0 -5
- mini_arcade_core/backend/backend.py +9 -0
- mini_arcade_core/backend/events.py +1 -1
- mini_arcade_core/{keymaps/sdl.py → backend/sdl_map.py} +1 -1
- mini_arcade_core/engine/__init__.py +0 -0
- mini_arcade_core/engine/commands.py +169 -0
- mini_arcade_core/engine/game.py +354 -0
- mini_arcade_core/engine/render/__init__.py +0 -0
- mini_arcade_core/engine/render/packet.py +56 -0
- mini_arcade_core/engine/render/pipeline.py +39 -0
- mini_arcade_core/managers/__init__.py +0 -22
- mini_arcade_core/managers/cheats.py +71 -240
- mini_arcade_core/managers/inputs.py +5 -1
- mini_arcade_core/runtime/__init__.py +0 -0
- mini_arcade_core/runtime/audio/__init__.py +0 -0
- mini_arcade_core/runtime/audio/audio_adapter.py +13 -0
- mini_arcade_core/runtime/audio/audio_port.py +17 -0
- mini_arcade_core/runtime/capture/__init__.py +0 -0
- mini_arcade_core/runtime/capture/capture_adapter.py +143 -0
- mini_arcade_core/runtime/capture/capture_port.py +32 -0
- mini_arcade_core/runtime/context.py +53 -0
- mini_arcade_core/runtime/file/__init__.py +0 -0
- mini_arcade_core/runtime/file/file_adapter.py +20 -0
- mini_arcade_core/runtime/file/file_port.py +31 -0
- mini_arcade_core/runtime/input/__init__.py +0 -0
- mini_arcade_core/runtime/input/input_adapter.py +49 -0
- mini_arcade_core/runtime/input/input_port.py +31 -0
- mini_arcade_core/runtime/input_frame.py +71 -0
- mini_arcade_core/runtime/scene/__init__.py +0 -0
- mini_arcade_core/runtime/scene/scene_adapter.py +97 -0
- mini_arcade_core/runtime/scene/scene_port.py +149 -0
- mini_arcade_core/runtime/services.py +35 -0
- mini_arcade_core/runtime/window/__init__.py +0 -0
- mini_arcade_core/runtime/window/window_adapter.py +26 -0
- mini_arcade_core/runtime/window/window_port.py +47 -0
- mini_arcade_core/scenes/__init__.py +0 -22
- mini_arcade_core/scenes/autoreg.py +1 -1
- mini_arcade_core/scenes/registry.py +21 -19
- mini_arcade_core/scenes/sim_scene.py +41 -0
- mini_arcade_core/scenes/systems/__init__.py +0 -0
- mini_arcade_core/scenes/systems/base_system.py +40 -0
- mini_arcade_core/scenes/systems/system_pipeline.py +57 -0
- mini_arcade_core/sim/__init__.py +0 -0
- mini_arcade_core/sim/protocols.py +41 -0
- mini_arcade_core/sim/runner.py +222 -0
- mini_arcade_core/spaces/__init__.py +0 -12
- mini_arcade_core/spaces/d2/__init__.py +0 -30
- mini_arcade_core/spaces/d2/collision2d.py +25 -28
- mini_arcade_core/spaces/d2/geometry2d.py +18 -0
- mini_arcade_core/spaces/d2/kinematics2d.py +2 -8
- mini_arcade_core/spaces/d2/physics2d.py +9 -0
- mini_arcade_core/ui/__init__.py +0 -26
- mini_arcade_core/ui/menu.py +265 -84
- mini_arcade_core/utils/__init__.py +10 -0
- mini_arcade_core/utils/deprecated_decorator.py +45 -0
- mini_arcade_core/utils/logging.py +174 -0
- {mini_arcade_core-0.10.0.dist-info → mini_arcade_core-1.0.0.dist-info}/METADATA +1 -1
- mini_arcade_core-1.0.0.dist-info/RECORD +65 -0
- {mini_arcade_core-0.10.0.dist-info → mini_arcade_core-1.0.0.dist-info}/WHEEL +1 -1
- mini_arcade_core/commands.py +0 -84
- mini_arcade_core/entity.py +0 -72
- mini_arcade_core/game.py +0 -287
- mini_arcade_core/keymaps/__init__.py +0 -15
- mini_arcade_core/managers/base.py +0 -132
- mini_arcade_core/managers/entities.py +0 -38
- mini_arcade_core/managers/overlays.py +0 -53
- mini_arcade_core/managers/system.py +0 -26
- mini_arcade_core/scenes/model.py +0 -34
- mini_arcade_core/scenes/runtime.py +0 -29
- mini_arcade_core/scenes/scene.py +0 -109
- mini_arcade_core/scenes/system.py +0 -69
- mini_arcade_core/ui/overlays.py +0 -41
- mini_arcade_core-0.10.0.dist-info/RECORD +0 -40
- /mini_arcade_core/{keymaps → backend}/keys.py +0 -0
- {mini_arcade_core-0.10.0.dist-info → mini_arcade_core-1.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Game core module defining the Game class and configuration.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from time import perf_counter, sleep
|
|
9
|
+
from typing import Dict, Literal
|
|
10
|
+
|
|
11
|
+
from mini_arcade_core.backend import Backend
|
|
12
|
+
from mini_arcade_core.engine.commands import (
|
|
13
|
+
CommandContext,
|
|
14
|
+
CommandQueue,
|
|
15
|
+
QuitCommand,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# from mini_arcade_core.sim.runner import SimRunner, SimRunnerConfig
|
|
19
|
+
from mini_arcade_core.engine.render.packet import RenderPacket
|
|
20
|
+
from mini_arcade_core.engine.render.pipeline import RenderPipeline
|
|
21
|
+
from mini_arcade_core.managers.cheats import CheatManager
|
|
22
|
+
from mini_arcade_core.runtime.audio.audio_adapter import NullAudioAdapter
|
|
23
|
+
from mini_arcade_core.runtime.capture.capture_adapter import CaptureAdapter
|
|
24
|
+
from mini_arcade_core.runtime.file.file_adapter import LocalFilesAdapter
|
|
25
|
+
from mini_arcade_core.runtime.input.input_adapter import InputAdapter
|
|
26
|
+
from mini_arcade_core.runtime.input_frame import InputFrame
|
|
27
|
+
from mini_arcade_core.runtime.scene.scene_adapter import SceneAdapter
|
|
28
|
+
from mini_arcade_core.runtime.services import RuntimeServices
|
|
29
|
+
from mini_arcade_core.runtime.window.window_adapter import WindowAdapter
|
|
30
|
+
from mini_arcade_core.scenes.registry import SceneRegistry
|
|
31
|
+
from mini_arcade_core.utils import logger
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class WindowConfig:
|
|
36
|
+
"""
|
|
37
|
+
Configuration for a game window (not implemented).
|
|
38
|
+
|
|
39
|
+
:ivar width (int): Width of the window in pixels.
|
|
40
|
+
:ivar height (int): Height of the window in pixels.
|
|
41
|
+
:ivar background_color (tuple[int, int, int]): RGB background color.
|
|
42
|
+
:ivar title (str): Title of the window.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
width: int
|
|
46
|
+
height: int
|
|
47
|
+
background_color: tuple[int, int, int]
|
|
48
|
+
title: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class GameConfig:
|
|
53
|
+
"""
|
|
54
|
+
Configuration options for the Game.
|
|
55
|
+
|
|
56
|
+
:ivar window (WindowConfig | None): Optional window configuration.
|
|
57
|
+
:ivar fps (int): Target frames per second.
|
|
58
|
+
:ivar backend (Backend | None): Optional Backend instance to use for rendering and input.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
window: WindowConfig | None = None
|
|
62
|
+
fps: int = 60
|
|
63
|
+
backend: Backend | None = None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
Difficulty = Literal["easy", "normal", "hard", "insane"]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class GameSettings:
|
|
71
|
+
"""
|
|
72
|
+
Game settings that can be modified during gameplay.
|
|
73
|
+
|
|
74
|
+
:ivar difficulty (Difficulty): Current game difficulty level.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
difficulty: Difficulty = "normal"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _neutral_input(frame_index: int, dt: float) -> InputFrame:
|
|
81
|
+
"""Create a neutral InputFrame with no input events."""
|
|
82
|
+
return InputFrame(frame_index=frame_index, dt=dt)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass
|
|
86
|
+
class FrameTimer:
|
|
87
|
+
"""
|
|
88
|
+
Simple frame timer for marking and reporting time intervals.
|
|
89
|
+
|
|
90
|
+
:ivar enabled (bool): Whether timing is enabled.
|
|
91
|
+
:ivar marks (Dict[str, float]): Recorded time marks.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
enabled: bool = True
|
|
95
|
+
marks: Dict[str, float] = field(default_factory=dict)
|
|
96
|
+
|
|
97
|
+
def mark(self, name: str):
|
|
98
|
+
"""
|
|
99
|
+
Record a time mark with the given name.
|
|
100
|
+
|
|
101
|
+
:param name: Name of the mark.
|
|
102
|
+
:type name: str
|
|
103
|
+
"""
|
|
104
|
+
if not self.enabled:
|
|
105
|
+
return
|
|
106
|
+
self.marks[name] = perf_counter()
|
|
107
|
+
|
|
108
|
+
def diff_ms(self, start: str, end: str) -> float:
|
|
109
|
+
"""
|
|
110
|
+
Get the time difference in milliseconds between two marks.
|
|
111
|
+
|
|
112
|
+
:param start: Name of the start mark.
|
|
113
|
+
:type start: str
|
|
114
|
+
|
|
115
|
+
:param end: Name of the end mark.
|
|
116
|
+
:type end: str
|
|
117
|
+
|
|
118
|
+
:return: Time difference in milliseconds.
|
|
119
|
+
:rtype: float
|
|
120
|
+
"""
|
|
121
|
+
return (self.marks[end] - self.marks[start]) * 1000.0
|
|
122
|
+
|
|
123
|
+
def report_ms(self) -> Dict[str, float]:
|
|
124
|
+
"""
|
|
125
|
+
Returns diffs between consecutive marks in insertion order.
|
|
126
|
+
|
|
127
|
+
:return: Dictionary mapping "start->end" to time difference in milliseconds.
|
|
128
|
+
:rtype: Dict[str, float]
|
|
129
|
+
"""
|
|
130
|
+
if not self.enabled:
|
|
131
|
+
return {}
|
|
132
|
+
|
|
133
|
+
keys = list(self.marks.keys())
|
|
134
|
+
out: Dict[str, float] = {}
|
|
135
|
+
for a, b in zip(keys, keys[1:]):
|
|
136
|
+
out[f"{a}->{b}"] = self.diff_ms(a, b)
|
|
137
|
+
return out
|
|
138
|
+
|
|
139
|
+
def clear(self):
|
|
140
|
+
"""Clear all recorded marks."""
|
|
141
|
+
if not self.enabled:
|
|
142
|
+
return
|
|
143
|
+
self.marks.clear()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# TODO: Fix too-many-instance-attributes warning
|
|
147
|
+
# Justification: Core game class with many dependencies.
|
|
148
|
+
# pylint: disable=too-many-instance-attributes
|
|
149
|
+
class Game:
|
|
150
|
+
"""Core game object responsible for managing the main loop and active scene."""
|
|
151
|
+
|
|
152
|
+
def __init__(
|
|
153
|
+
self, config: GameConfig, registry: SceneRegistry | None = None
|
|
154
|
+
):
|
|
155
|
+
"""
|
|
156
|
+
:param config: Game configuration options.
|
|
157
|
+
:type config: GameConfig
|
|
158
|
+
|
|
159
|
+
:param registry: Optional SceneRegistry for scene management.
|
|
160
|
+
:type registry: SceneRegistry | None
|
|
161
|
+
|
|
162
|
+
:raises ValueError: If the provided config does not have a valid Backend.
|
|
163
|
+
"""
|
|
164
|
+
self.config = config
|
|
165
|
+
self._running: bool = False
|
|
166
|
+
|
|
167
|
+
if config.backend is None:
|
|
168
|
+
raise ValueError(
|
|
169
|
+
"GameConfig.backend must be set to a Backend instance"
|
|
170
|
+
)
|
|
171
|
+
if config.window is None:
|
|
172
|
+
raise ValueError("GameConfig.window must be set")
|
|
173
|
+
|
|
174
|
+
self.backend: Backend = config.backend
|
|
175
|
+
self.registry = registry or SceneRegistry(_factories={})
|
|
176
|
+
self.settings = GameSettings()
|
|
177
|
+
self.services = RuntimeServices(
|
|
178
|
+
window=WindowAdapter(
|
|
179
|
+
self.backend,
|
|
180
|
+
),
|
|
181
|
+
scenes=SceneAdapter(self.registry, self),
|
|
182
|
+
audio=NullAudioAdapter(),
|
|
183
|
+
files=LocalFilesAdapter(),
|
|
184
|
+
capture=CaptureAdapter(self.backend),
|
|
185
|
+
input=InputAdapter(),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
self.command_queue = CommandQueue()
|
|
189
|
+
self.cheat_manager = CheatManager()
|
|
190
|
+
|
|
191
|
+
def quit(self):
|
|
192
|
+
"""Request that the main loop stops."""
|
|
193
|
+
self._running = False
|
|
194
|
+
|
|
195
|
+
# TODO: Fix too-many-statements and too-many-locals warnings
|
|
196
|
+
# Justification: Main game loop with multiple responsibilities.
|
|
197
|
+
# pylint: disable=too-many-statements,too-many-locals
|
|
198
|
+
def run(self, initial_scene_id: str):
|
|
199
|
+
"""
|
|
200
|
+
Run the main loop starting with the given scene.
|
|
201
|
+
|
|
202
|
+
This is intentionally left abstract so you can plug pygame, pyglet,
|
|
203
|
+
or another backend.
|
|
204
|
+
|
|
205
|
+
:param initial_scene_id: The scene id to start the game with (must be registered).
|
|
206
|
+
:type initial_scene_id: str
|
|
207
|
+
"""
|
|
208
|
+
backend = self.backend
|
|
209
|
+
|
|
210
|
+
self._initialize_window()
|
|
211
|
+
|
|
212
|
+
self.services.scenes.change(initial_scene_id)
|
|
213
|
+
|
|
214
|
+
pipeline = RenderPipeline()
|
|
215
|
+
|
|
216
|
+
self._running = True
|
|
217
|
+
target_dt = 1.0 / self.config.fps if self.config.fps > 0 else 0.0
|
|
218
|
+
last_time = perf_counter()
|
|
219
|
+
frame_index = 0
|
|
220
|
+
|
|
221
|
+
# cache packets so blocked-update scenes still render their last frame
|
|
222
|
+
packet_cache: dict[int, RenderPacket] = {}
|
|
223
|
+
|
|
224
|
+
timer = FrameTimer(enabled=True)
|
|
225
|
+
report_every = 60 # print once per second at 60fps
|
|
226
|
+
|
|
227
|
+
# TODO: Integrate SimRunner for simulation stepping
|
|
228
|
+
# TODO: Fix assignment-from-no-return warning in self.services.input.build
|
|
229
|
+
# & self.services.scenes.input_entry
|
|
230
|
+
# Justification: These methods are expected to return values.
|
|
231
|
+
# pylint: disable=assignment-from-no-return
|
|
232
|
+
|
|
233
|
+
while self._running:
|
|
234
|
+
timer.clear()
|
|
235
|
+
timer.mark("frame_start")
|
|
236
|
+
|
|
237
|
+
now = perf_counter()
|
|
238
|
+
dt = now - last_time
|
|
239
|
+
last_time = now
|
|
240
|
+
|
|
241
|
+
events = list(backend.poll_events())
|
|
242
|
+
timer.mark("events_polled")
|
|
243
|
+
|
|
244
|
+
input_frame = self.services.input.build(events, frame_index, dt)
|
|
245
|
+
timer.mark("input_built")
|
|
246
|
+
|
|
247
|
+
# Window/OS quit (close button)
|
|
248
|
+
if input_frame.quit:
|
|
249
|
+
self.command_queue.push(QuitCommand())
|
|
250
|
+
|
|
251
|
+
# who gets input?
|
|
252
|
+
input_entry = self.services.scenes.input_entry()
|
|
253
|
+
if input_entry is None:
|
|
254
|
+
break
|
|
255
|
+
|
|
256
|
+
# tick policy-aware scenes
|
|
257
|
+
timer.mark("tick_start")
|
|
258
|
+
for entry in self.services.scenes.update_entries():
|
|
259
|
+
scene = entry.scene
|
|
260
|
+
effective_input = (
|
|
261
|
+
input_frame
|
|
262
|
+
if entry is input_entry
|
|
263
|
+
else _neutral_input(frame_index, dt)
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
packet = scene.tick(effective_input, dt)
|
|
267
|
+
packet_cache[id(scene)] = packet
|
|
268
|
+
timer.mark("tick_end")
|
|
269
|
+
|
|
270
|
+
timer.mark("command_ctx_start")
|
|
271
|
+
command_context = CommandContext(
|
|
272
|
+
services=self.services,
|
|
273
|
+
commands=self.command_queue,
|
|
274
|
+
settings=self.settings,
|
|
275
|
+
world=self._resolve_world(),
|
|
276
|
+
)
|
|
277
|
+
timer.mark("command_ctx_end")
|
|
278
|
+
|
|
279
|
+
timer.mark("cheats_start")
|
|
280
|
+
self.cheat_manager.process_frame(
|
|
281
|
+
input_frame,
|
|
282
|
+
context=command_context,
|
|
283
|
+
queue=self.command_queue,
|
|
284
|
+
)
|
|
285
|
+
timer.mark("cheats_end")
|
|
286
|
+
|
|
287
|
+
# Execute commands at the end of the frame (consistent write path)
|
|
288
|
+
timer.mark("cmd_exec_start")
|
|
289
|
+
for cmd in self.command_queue.drain():
|
|
290
|
+
cmd.execute(command_context)
|
|
291
|
+
timer.mark("cmd_exec_end")
|
|
292
|
+
|
|
293
|
+
timer.mark("render_start")
|
|
294
|
+
backend.begin_frame()
|
|
295
|
+
timer.mark("begin_frame_done")
|
|
296
|
+
|
|
297
|
+
for entry in self.services.scenes.visible_entries():
|
|
298
|
+
scene = entry.scene
|
|
299
|
+
packet = packet_cache.get(id(scene))
|
|
300
|
+
if packet is None:
|
|
301
|
+
# bootstrap (first frame visible but not updated)
|
|
302
|
+
packet = scene.tick(_neutral_input(frame_index, 0.0), 0.0)
|
|
303
|
+
packet_cache[id(scene)] = packet
|
|
304
|
+
|
|
305
|
+
pipeline.draw_packet(backend, packet)
|
|
306
|
+
|
|
307
|
+
timer.mark("draw_done")
|
|
308
|
+
backend.end_frame()
|
|
309
|
+
timer.mark("end_frame_done")
|
|
310
|
+
|
|
311
|
+
timer.mark("sleep_start")
|
|
312
|
+
if target_dt > 0 and dt < target_dt:
|
|
313
|
+
sleep(target_dt - dt)
|
|
314
|
+
timer.mark("sleep_end")
|
|
315
|
+
|
|
316
|
+
# --- report ---
|
|
317
|
+
if frame_index % report_every == 0 and frame_index > 0:
|
|
318
|
+
ms = timer.report_ms()
|
|
319
|
+
total = (perf_counter() - timer.marks["frame_start"]) * 1000.0
|
|
320
|
+
logger.debug(
|
|
321
|
+
f"[Frame {frame_index}] total={total:.2f}ms | {ms}"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
frame_index += 1
|
|
325
|
+
|
|
326
|
+
# pylint: enable=assignment-from-no-return
|
|
327
|
+
|
|
328
|
+
# exit remaining scenes
|
|
329
|
+
self.services.scenes.clean()
|
|
330
|
+
|
|
331
|
+
# pylint: enable=too-many-statements,too-many-locals
|
|
332
|
+
|
|
333
|
+
def _initialize_window(self):
|
|
334
|
+
"""Initialize the game window based on the configuration."""
|
|
335
|
+
self.services.window.set_window_size(
|
|
336
|
+
self.config.window.width, self.config.window.height
|
|
337
|
+
)
|
|
338
|
+
self.services.window.set_title(self.config.window.title)
|
|
339
|
+
|
|
340
|
+
br, bg, bb = self.config.window.background_color
|
|
341
|
+
self.services.window.set_clear_color(br, bg, bb)
|
|
342
|
+
|
|
343
|
+
def _resolve_world(self) -> object | None:
|
|
344
|
+
# Prefer gameplay world underneath overlays:
|
|
345
|
+
# scan from top to bottom and pick the first scene that has .world
|
|
346
|
+
for entry in reversed(self.services.scenes.visible_entries()):
|
|
347
|
+
scene = entry.scene
|
|
348
|
+
world = getattr(scene, "world", None)
|
|
349
|
+
if world is not None:
|
|
350
|
+
return world
|
|
351
|
+
return None
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
# pylint: enable=too-many-instance-attributes
|
|
File without changes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Render packet module.
|
|
3
|
+
Defines the RenderPacket class and related types."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Callable, Iterable, Protocol, runtime_checkable
|
|
9
|
+
|
|
10
|
+
from mini_arcade_core.backend import Backend
|
|
11
|
+
|
|
12
|
+
DrawOp = Callable[[Backend], None]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class RenderPacket:
|
|
17
|
+
"""
|
|
18
|
+
Minimal render packet for v1.
|
|
19
|
+
|
|
20
|
+
It is intentionally backend-agnostic: each op is a callable that knows
|
|
21
|
+
how to draw itself using the Backend instance.
|
|
22
|
+
|
|
23
|
+
Later you can replace DrawOp with typed primitives + passes.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
ops: tuple[DrawOp, ...] = ()
|
|
27
|
+
meta: dict[str, object] = field(default_factory=dict)
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def from_ops(ops: Iterable[DrawOp], **meta: object) -> "RenderPacket":
|
|
31
|
+
"""
|
|
32
|
+
Create a RenderPacket from an iterable of DrawOps and optional meta.
|
|
33
|
+
|
|
34
|
+
:param ops: Iterable of DrawOp callables.
|
|
35
|
+
:type ops: Iterable[DrawOp]
|
|
36
|
+
|
|
37
|
+
:return: RenderPacket instance.
|
|
38
|
+
:rtype: RenderPacket
|
|
39
|
+
"""
|
|
40
|
+
return RenderPacket(ops=tuple(ops), meta=dict(meta))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# TODO: Implement later
|
|
44
|
+
@runtime_checkable
|
|
45
|
+
class Renderable(Protocol):
|
|
46
|
+
"""
|
|
47
|
+
Optional convenience: any object that can produce a RenderPacket.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def render(self) -> RenderPacket:
|
|
51
|
+
"""
|
|
52
|
+
Produce a RenderPacket for this object.
|
|
53
|
+
|
|
54
|
+
:return: RenderPacket instance.
|
|
55
|
+
:rtype: RenderPacket
|
|
56
|
+
"""
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class RenderPipeline:
|
|
16
|
+
"""
|
|
17
|
+
Minimal pipeline for v1.
|
|
18
|
+
|
|
19
|
+
Later you can expand this into passes:
|
|
20
|
+
- build draw list
|
|
21
|
+
- cull
|
|
22
|
+
- sort
|
|
23
|
+
- backend draw pass
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def draw_packet(self, backend: Backend, packet: RenderPacket):
|
|
27
|
+
"""
|
|
28
|
+
Draw the given RenderPacket using the provided Backend.
|
|
29
|
+
|
|
30
|
+
:param backend: Backend to use for drawing.
|
|
31
|
+
:type backend: Backend
|
|
32
|
+
|
|
33
|
+
:param packet: RenderPacket to draw.
|
|
34
|
+
:type packet: RenderPacket
|
|
35
|
+
"""
|
|
36
|
+
if not packet:
|
|
37
|
+
return
|
|
38
|
+
for op in packet.ops:
|
|
39
|
+
op(backend)
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Managers module for Mini Arcade Core.
|
|
3
|
-
Provides various manager classes for handling game entities and resources.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from __future__ import annotations
|
|
7
|
-
|
|
8
|
-
from .cheats import BaseCheatCommand, CheatCode, CheatManager
|
|
9
|
-
from .entities import EntityManager
|
|
10
|
-
from .inputs import InputManager
|
|
11
|
-
from .overlays import OverlayManager
|
|
12
|
-
from .system import SystemManager
|
|
13
|
-
|
|
14
|
-
__all__ = [
|
|
15
|
-
"EntityManager",
|
|
16
|
-
"OverlayManager",
|
|
17
|
-
"CheatCode",
|
|
18
|
-
"CheatManager",
|
|
19
|
-
"BaseCheatCommand",
|
|
20
|
-
"InputManager",
|
|
21
|
-
"SystemManager",
|
|
22
|
-
]
|