Source code for mini_arcade_core.scenes.systems.builtins.timers
"""
Reusable timed-state helpers for temporary gameplay states.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Callable, Generic, TypeVar
from mini_arcade_core.scenes.systems.phases import SystemPhase
# pylint: disable=invalid-name
TCtx = TypeVar("TCtx")
# pylint: enable=invalid-name
def _default_enabled_when(_ctx: object) -> bool:
return True
[docs]
@dataclass
class TimedState:
"""
Mutable state for one temporary status window.
"""
active: bool = False
remaining_seconds: float = 0.0
tag: str = ""
payload: Any = None
[docs]
def activate_timed_state(
state: TimedState,
*,
duration_seconds: float,
tag: str = "",
payload: Any = None,
) -> None:
"""
Activate a timed state and reset its countdown.
"""
state.active = True
state.remaining_seconds = max(0.0, float(duration_seconds))
state.tag = str(tag)
state.payload = payload
[docs]
def clear_timed_state(state: TimedState) -> None:
"""
Clear a timed state immediately.
"""
state.active = False
state.remaining_seconds = 0.0
state.tag = ""
state.payload = None
[docs]
@dataclass(frozen=True)
class TimedStateBinding(Generic[TCtx]):
"""
Declarative timed-state decay rule.
"""
state_getter: Callable[[TCtx], TimedState]
on_expired: Callable[[TCtx, TimedState], None] | None = None
[docs]
@dataclass
class TimedStateSystem(Generic[TCtx]):
"""
Decay active timed states using frame dt.
"""
name: str = "common_timed_state"
phase: int = SystemPhase.SIMULATION
order: int = 16
enabled_when: Callable[[TCtx], bool] = _default_enabled_when
bindings: tuple[TimedStateBinding[TCtx], ...] = ()
[docs]
def step(self, ctx: TCtx) -> None:
"""Advance active timed states by the current frame delta."""
if not self.enabled_when(ctx):
return
dt = max(0.0, float(getattr(ctx, "dt", 0.0)))
if dt <= 0.0:
return
for binding in self.bindings:
state = binding.state_getter(ctx)
if not state.active:
continue
state.remaining_seconds = max(
0.0,
float(state.remaining_seconds) - dt,
)
if state.remaining_seconds > 0.0:
continue
if binding.on_expired is not None:
binding.on_expired(ctx, state)
clear_timed_state(state)
__all__ = [
"TimedState",
"TimedStateBinding",
"TimedStateSystem",
"activate_timed_state",
"clear_timed_state",
]