Source code for mini_arcade_core.runtime.capture.replay_format
"""
Replay file format for mini-arcade.
"""
from __future__ import annotations
import json
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Iterator, Optional, TextIO
from mini_arcade_core.runtime.input_frame import InputFrame
REPLAY_MAGIC = "mini-arcade-replay"
REPLAY_VERSION = 1
[docs]
class ReplayWriter:
"""Replay file writer."""
def __init__(self, path: Path, header: ReplayHeader):
"""
:param path: Path to save the replay file.
:type path: Path
:param header: Header information for the replay.
:type header: ReplayHeader
"""
self.path = path
self.header = header
self._f: Optional[TextIO] = None
[docs]
def open(self) -> None:
"""Open the replay file for writing."""
self.path.parent.mkdir(parents=True, exist_ok=True)
self._f = self.path.open("w", encoding="utf-8")
self._f.write(json.dumps(asdict(self.header)) + "\n")
[docs]
def write_frame(self, frame: InputFrame) -> None:
"""
Write an input frame to the replay file.
:param frame: Input frame to write.
:type frame: InputFrame
"""
if not self._f:
raise RuntimeError("ReplayWriter is not open")
self._f.write(json.dumps(frame.to_dict()) + "\n")
[docs]
def close(self) -> None:
"""Close the replay file."""
if self._f:
self._f.close()
self._f = None
[docs]
class ReplayReader:
"""Replay file reader."""
def __init__(self, path: Path):
"""
:param path: Path to the replay file.
:type path: Path"""
self.path = path
self.header: Optional[ReplayHeader] = None
self._f: Optional[TextIO] = None
[docs]
def open(self) -> ReplayHeader:
"""
Open the replay file for reading.
:return: The replay header.
:rtype: ReplayHeader
"""
self._f = self.path.open("r", encoding="utf-8")
header_line = self._f.readline()
self.header = ReplayHeader(**json.loads(header_line))
if self.header.magic != REPLAY_MAGIC:
raise ValueError("Not a mini-arcade replay file")
return self.header
[docs]
def frames(self) -> Iterator[InputFrame]:
"""
Iterate over input frames in the replay file.
:return: Input frames from the replay.
:rtype: Iterator[InputFrame]
"""
if not self._f:
raise RuntimeError("ReplayReader is not open")
for line in self._f:
if not line.strip():
continue
yield InputFrame.from_dict(json.loads(line))
[docs]
def close(self) -> None:
"""Close the replay file."""
if self._f:
self._f.close()
self._f = None