Skip to content

Commit f19c59f

Browse files
committedApr 3, 2024·
Refactor Session to use the new EnvironmentDriver
1 parent aa47b9a commit f19c59f

File tree

4 files changed

+608
-945
lines changed

4 files changed

+608
-945
lines changed
 

‎src/libretro/api/content/driver.py

+237
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
import os.path
2+
from abc import abstractmethod
3+
from collections.abc import Sequence, Mapping, Iterator, Generator, Iterable
4+
from contextlib import contextmanager, ExitStack, AbstractContextManager
5+
from enum import Enum, auto
6+
from os import PathLike
7+
from typing import Protocol, runtime_checkable, AnyStr, TypeAlias, NamedTuple, BinaryIO
8+
9+
from .defs import *
10+
11+
SetContentArgs: TypeAlias = Content | SubsystemContent | None
12+
SetSubsystemsArgs = Sequence[retro_subsystem_info] | None
13+
14+
ParsedContent: TypeAlias = retro_game_info | Sequence[retro_game_info] | None
15+
16+
17+
class ContentInfo(NamedTuple):
18+
need_fullpath: bool
19+
persistent_data: bool
20+
21+
22+
class MappedContent:
23+
def __init__(self, content: Content):
24+
self._content: retro_game_info = content
25+
26+
def __del__(self):
27+
self.release()
28+
29+
def release(self) -> None:
30+
# TODO: Clear the data
31+
pass
32+
33+
@property
34+
def content(self) -> retro_game_info:
35+
return self._content
36+
37+
38+
class ContentAttributes(NamedTuple):
39+
block_extract: bool
40+
persistent_data: bool
41+
need_fullpath: bool
42+
required: bool
43+
44+
45+
class LoadedContentFile:
46+
def __init__(self, info: retro_game_info, info_ext: retro_game_info_ext | None = None, persistent: bool = False, io: BinaryIO | None = None):
47+
if not isinstance(info, retro_game_info):
48+
raise TypeError(f"Expected retro_game_info, got {type(info).__name__}")
49+
50+
if info_ext is not None and not isinstance(info_ext, retro_game_info_ext):
51+
raise TypeError(f"Expected retro_game_info_ext or None, got {type(info_ext).__name__}")
52+
53+
if io is not None and not isinstance(io, BinaryIO):
54+
raise TypeError(f"Expected BinaryIO or None, got {type(io).__name__}")
55+
56+
self._info = info
57+
self._info_ext = info_ext
58+
self._io = io
59+
self._persistent = bool(persistent)
60+
61+
def __del__(self):
62+
self.close()
63+
64+
def close(self):
65+
# Gotta clean up the pointers first,
66+
# since they may be holding on to references to the mapped buffer
67+
# (and freeing a mapped memory buffer before the pointers are cleared
68+
# may result in an exception)
69+
if self._info.data:
70+
self._info.data = None
71+
72+
if self._info_ext:
73+
self._info_ext.data = None
74+
75+
if self._io:
76+
self._io.close()
77+
78+
del self._io
79+
80+
@property
81+
def info(self) -> retro_game_info:
82+
return self._info
83+
84+
@property
85+
def info_ext(self) -> retro_game_info_ext | None:
86+
return self._info_ext
87+
88+
@property
89+
def persistent_data(self) -> bool:
90+
return self._persistent
91+
92+
93+
LoadedContent = tuple[retro_subsystem_info | None, Sequence[LoadedContentFile] | None]
94+
95+
96+
@runtime_checkable
97+
class ContentDriver(Protocol):
98+
@contextmanager
99+
def load(self, content: Content | SubsystemContent | None) -> AbstractContextManager[LoadedContent]:
100+
if not self.system_info:
101+
raise RuntimeError("System info not set")
102+
103+
with ExitStack() as stack:
104+
loaded_content: Sequence[LoadedContentFile] | None
105+
subsystem: retro_subsystem_info | None = None
106+
match content:
107+
case SubsystemContent(game_type=game_type, info=info):
108+
subsystem = self.__get_subsystem(game_type)
109+
if len(info) != len(subsystem):
110+
raise ValueError(f"Subsystem {subsystem.ident!r} needs {len(subsystem)} ROMs, got {len(info)}")
111+
112+
i: int
113+
c: Content
114+
loaded_content = [stack.enter_context(self._load(i, subsystem, subsystem[i])) for (i, c) in enumerate(info)]
115+
case str() | PathLike() | bytes() | bytearray() | memoryview() | retro_game_info():
116+
loaded_content = [stack.enter_context(self._load(content))]
117+
case None if self.support_no_game:
118+
loaded_content = []
119+
case None:
120+
raise ValueError("No content provided and core did not register support for no-content mode")
121+
case _:
122+
raise TypeError(f"Expected a content path, data buffer, SubsystemContent, or retro_game_info; got {type(content).__name__}")
123+
124+
yield subsystem, loaded_content
125+
126+
@abstractmethod
127+
def _load(
128+
self,
129+
content: Content,
130+
subsystem: retro_subsystem_info | None = None,
131+
subsysrom: retro_subsystem_rom_info | None = None
132+
) -> AbstractContextManager[LoadedContentFile]:
133+
"""
134+
TODO load stuff
135+
136+
:param content: The content to load.
137+
:param subsystem: The subsystem that content is being loaded for, defaults to ``None``.
138+
TODO write more
139+
:param subsysrom: The subsystem ROM that content is being loaded for, defaults to ``None``.
140+
"""
141+
...
142+
143+
@property
144+
@abstractmethod
145+
def enable_extended_info(self) -> bool: ...
146+
147+
@abstractmethod
148+
def set_system_info(self, info: retro_system_info | None) -> None: ...
149+
150+
@abstractmethod
151+
def get_system_info(self) -> retro_system_info | None: ...
152+
153+
@property
154+
def system_info(self) -> retro_system_info | None:
155+
return self.get_system_info()
156+
157+
@system_info.setter
158+
def system_info(self, info: retro_system_info) -> None:
159+
self.set_system_info(info)
160+
161+
@system_info.deleter
162+
def system_info(self) -> None:
163+
self.set_system_info(None)
164+
165+
@abstractmethod
166+
def set_overrides(self, overrides: Sequence[retro_system_content_info_override] | None) -> None: ...
167+
168+
@abstractmethod
169+
def get_overrides(self) -> ContentInfoOverrides | None: ...
170+
171+
@property
172+
def overrides(self) -> Sequence[retro_system_content_info_override] | None:
173+
return self.get_overrides()
174+
175+
@overrides.setter
176+
def overrides(self, overrides: Sequence[retro_system_content_info_override]) -> None:
177+
self.set_overrides(overrides)
178+
179+
@abstractmethod
180+
def set_subsystem_info(self, subsystems: Sequence[retro_subsystem_info] | None) -> None: ...
181+
182+
@abstractmethod
183+
def get_subsystem_info(self) -> Subsystems | None: ...
184+
185+
@property
186+
def subsystem_info(self) -> Subsystems | None:
187+
return self.get_subsystem_info()
188+
189+
@subsystem_info.setter
190+
def subsystem_info(self, subsystems: Sequence[retro_subsystem_info]) -> None:
191+
self.set_subsystem_info(subsystems)
192+
193+
@abstractmethod
194+
def set_support_no_game(self, support: bool) -> None: ...
195+
196+
@abstractmethod
197+
def get_support_no_game(self) -> bool | None: ...
198+
199+
@property
200+
def support_no_game(self) -> bool | None:
201+
return self.get_support_no_game()
202+
203+
@support_no_game.setter
204+
def support_no_game(self, support: bool) -> None:
205+
self.set_support_no_game(support)
206+
207+
def __get_subsystem(self, content: Content | SubsystemContent | None) -> retro_subsystem_info | None:
208+
if not isinstance(content, SubsystemContent):
209+
return None
210+
211+
if not self.subsystem_info:
212+
raise RuntimeError("Subsystem content was provided, but core did not register subsystems")
213+
214+
match content.game_type:
215+
case int(id):
216+
for s in self.subsystem_info:
217+
if s.id == id:
218+
return s
219+
220+
raise ValueError(f"Core did not register a subsystem with a numeric ID of {id}")
221+
222+
case str(ident) | bytes(ident):
223+
return self.subsystem_info[ident]
224+
225+
case e:
226+
raise TypeError(f"Expected a subsystem identifier of types int, str, or bytes; got {type(e).__name__}")
227+
228+
229+
__all__ = [
230+
"ParsedContent",
231+
"ContentDriver",
232+
"ContentInfo",
233+
"SetContentArgs",
234+
"SetSubsystemsArgs",
235+
"LoadedContentFile",
236+
"LoadedContent"
237+
]

0 commit comments

Comments
 (0)
Please sign in to comment.