libretro.py 0.2.0__tar.gz → 0.3.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.
Files changed (154) hide show
  1. {libretro_py-0.2.0 → libretro_py-0.3.0}/CHANGELOG.md +26 -0
  2. {libretro_py-0.2.0/src/libretro.py.egg-info → libretro_py-0.3.0}/PKG-INFO +1 -1
  3. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/video/context.py +1 -0
  4. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/builder.py +207 -65
  5. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/content/standard.py +5 -3
  6. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/path/__init__.py +2 -1
  7. libretro_py-0.3.0/src/libretro/drivers/path/driver.py +64 -0
  8. libretro_py-0.3.0/src/libretro/drivers/path/explicit.py +164 -0
  9. libretro_py-0.3.0/src/libretro/drivers/path/temp.py +124 -0
  10. libretro_py-0.3.0/src/libretro/drivers/video/driver.py +471 -0
  11. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/video/multi.py +84 -15
  12. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/video/opengl/moderngl.py +314 -210
  13. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/video/software/base.py +1 -1
  14. libretro_py-0.3.0/src/libretro/py/test/_common.py +173 -0
  15. libretro_py-0.3.0/src/libretro/py/test/loads_content.py +41 -0
  16. libretro_py-0.3.0/src/libretro/py/test/runs.py +102 -0
  17. {libretro_py-0.2.0 → libretro_py-0.3.0/src/libretro.py.egg-info}/PKG-INFO +1 -1
  18. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro.py.egg-info/SOURCES.txt +4 -1
  19. libretro_py-0.2.0/src/libretro/drivers/path/default.py +0 -98
  20. libretro_py-0.2.0/src/libretro/drivers/path/driver.py +0 -38
  21. libretro_py-0.2.0/src/libretro/drivers/video/driver.py +0 -271
  22. libretro_py-0.2.0/src/libretro/py/test/_common.py +0 -35
  23. {libretro_py-0.2.0 → libretro_py-0.3.0}/LICENSE +0 -0
  24. {libretro_py-0.2.0 → libretro_py-0.3.0}/MANIFEST.in +0 -0
  25. {libretro_py-0.2.0 → libretro_py-0.3.0}/README.md +0 -0
  26. {libretro_py-0.2.0 → libretro_py-0.3.0}/pyproject.toml +0 -0
  27. {libretro_py-0.2.0 → libretro_py-0.3.0}/setup.cfg +0 -0
  28. {libretro_py-0.2.0 → libretro_py-0.3.0}/setup.py +0 -0
  29. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/__init__.py +0 -0
  30. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/_typing.py +0 -0
  31. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/_utils.py +0 -0
  32. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/__init__.py +0 -0
  33. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/_utils.py +0 -0
  34. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/audio.py +0 -0
  35. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/av.py +0 -0
  36. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/camera.py +0 -0
  37. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/content.py +0 -0
  38. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/disk.py +0 -0
  39. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/environment.py +0 -0
  40. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/input/__init__.py +0 -0
  41. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/input/analog.py +0 -0
  42. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/input/device.py +0 -0
  43. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/input/joypad.py +0 -0
  44. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/input/keyboard.py +0 -0
  45. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/input/lightgun.py +0 -0
  46. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/input/mouse.py +0 -0
  47. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/input/pointer.py +0 -0
  48. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/led.py +0 -0
  49. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/location.py +0 -0
  50. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/log.py +0 -0
  51. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/memory.py +0 -0
  52. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/message.py +0 -0
  53. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/microphone.py +0 -0
  54. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/midi.py +0 -0
  55. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/netpacket.py +0 -0
  56. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/options.py +0 -0
  57. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/perf.py +0 -0
  58. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/power.py +0 -0
  59. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/proc.py +0 -0
  60. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/rumble.py +0 -0
  61. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/savestate.py +0 -0
  62. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/sensor.py +0 -0
  63. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/timing.py +0 -0
  64. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/user.py +0 -0
  65. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/vfs.py +0 -0
  66. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/video/__init__.py +0 -0
  67. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/video/frame.py +0 -0
  68. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/video/negotiate.py +0 -0
  69. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/api/video/render.py +0 -0
  70. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/core.py +0 -0
  71. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/__init__.py +0 -0
  72. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/audio/__init__.py +0 -0
  73. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/audio/array.py +0 -0
  74. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/audio/driver.py +0 -0
  75. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/audio/wave.py +0 -0
  76. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/camera/__init__.py +0 -0
  77. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/camera/driver.py +0 -0
  78. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/camera/generator.py +0 -0
  79. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/content/__init__.py +0 -0
  80. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/content/driver.py +0 -0
  81. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/disk/__init__.py +0 -0
  82. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/disk/driver.py +0 -0
  83. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/environment/__init__.py +0 -0
  84. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/environment/composite.py +0 -0
  85. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/environment/default.py +0 -0
  86. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/environment/dict.py +0 -0
  87. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/environment/driver.py +0 -0
  88. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/input/__init__.py +0 -0
  89. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/input/driver.py +0 -0
  90. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/input/generator.py +0 -0
  91. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/led/__init__.py +0 -0
  92. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/led/dict.py +0 -0
  93. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/led/driver.py +0 -0
  94. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/location/__init__.py +0 -0
  95. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/location/driver.py +0 -0
  96. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/location/generator.py +0 -0
  97. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/log/__init__.py +0 -0
  98. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/log/driver.py +0 -0
  99. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/log/unformatted.py +0 -0
  100. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/message/__init__.py +0 -0
  101. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/message/driver.py +0 -0
  102. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/message/logger.py +0 -0
  103. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/microphone/__init__.py +0 -0
  104. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/microphone/driver.py +0 -0
  105. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/microphone/generator.py +0 -0
  106. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/midi/__init__.py +0 -0
  107. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/midi/driver.py +0 -0
  108. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/midi/generator.py +0 -0
  109. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/netpacket/__init__.py +0 -0
  110. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/netpacket/driver.py +0 -0
  111. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/netpacket/socket.py +0 -0
  112. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/options/__init__.py +0 -0
  113. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/options/dict.py +0 -0
  114. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/options/driver.py +0 -0
  115. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/perf/__init__.py +0 -0
  116. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/perf/default.py +0 -0
  117. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/perf/driver.py +0 -0
  118. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/power/__init__.py +0 -0
  119. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/power/driver.py +0 -0
  120. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/rumble/__init__.py +0 -0
  121. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/rumble/default.py +0 -0
  122. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/rumble/interface.py +0 -0
  123. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/sensor/__init__.py +0 -0
  124. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/sensor/generator.py +0 -0
  125. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/sensor/interface.py +0 -0
  126. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/timing/__init__.py +0 -0
  127. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/timing/default.py +0 -0
  128. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/timing/driver.py +0 -0
  129. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/user/__init__.py +0 -0
  130. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/user/default.py +0 -0
  131. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/user/driver.py +0 -0
  132. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/vfs/__init__.py +0 -0
  133. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/vfs/default.py +0 -0
  134. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/vfs/history.py +0 -0
  135. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/vfs/interface.py +0 -0
  136. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/video/__init__.py +0 -0
  137. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/video/opengl/__init__.py +0 -0
  138. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/video/opengl/moderngl_frag.glsl +0 -0
  139. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/video/opengl/moderngl_vertex.glsl +0 -0
  140. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/video/software/__init__.py +0 -0
  141. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/drivers/video/software/array.py +0 -0
  142. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/error.py +0 -0
  143. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/h.py +0 -0
  144. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/py/__init__.py +0 -0
  145. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/py/test/__init__.py +0 -0
  146. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/py/test/api_version.py +0 -0
  147. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/py/test/inits.py +0 -0
  148. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/py/test/loads.py +0 -0
  149. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/py/test/sets_callbacks.py +0 -0
  150. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/py/test/system_info.py +0 -0
  151. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro/session.py +0 -0
  152. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro.py.egg-info/dependency_links.txt +0 -0
  153. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro.py.egg-info/requires.txt +0 -0
  154. {libretro_py-0.2.0 → libretro_py-0.3.0}/src/libretro.py.egg-info/top_level.txt +0 -0
@@ -10,6 +10,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
10
10
  > breaking changes may be introduced
11
11
  > at any time without warning.
12
12
 
13
+ ## [0.3.0] - 2024-09-25
14
+
15
+ ### Added
16
+
17
+ - Add `TempDirPathDriver`.
18
+ - Added various runnable scripts for test purposes.
19
+ - Add a new guide for taking a capture with RenderDoc.
20
+ - Add labels to OpenGL objects created by `ModernGlVideoDriver`.
21
+ - Add debug groups to important methods within `ModernGlVideoDriver`.
22
+
23
+ ### Changed
24
+
25
+ - **BREAKING:** Rename `DefaultPathDriver` to `ExplicitPathDriver`.
26
+ - Make `TempDirPathDriver` the default path driver used by `SessionBuilder`.
27
+
28
+ ### Fixed
29
+
30
+ - Improved documentation for parts of `SessionBuilder` and `VideoDriver`
31
+ - Removed a `glClear` call in `ModernGlVideoDriver` that was left in by accident.
32
+ - Clear the `glGetError` queue at various places in `ModernGlVideoDriver`
33
+ to prevent PyOpenGL from misreporting errors that came from moderngl or the loaded core.
34
+
35
+ ### Removed
36
+
37
+ - **BREAKING:** Remove `VideoDriverInitArgs`.
38
+
13
39
  ## [0.2.0] - 2024-09-12
14
40
 
15
41
  Thanks to @JSensebe for his contributions!
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: libretro.py
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: A libretro frontend for Python intended for testing cores.
5
5
  Author-email: Jesse Talavera <jesse@jesse.tg>
6
6
  Maintainer-email: Jesse Talavera <jesse@jesse.tg>
@@ -36,6 +36,7 @@ class HardwareContext(IntEnum):
36
36
  D3D11 = RETRO_HW_CONTEXT_D3D11
37
37
  D3D10 = RETRO_HW_CONTEXT_D3D10
38
38
  D3D12 = RETRO_HW_CONTEXT_D3D12
39
+ D3D9 = RETRO_HW_CONTEXT_D3D9
39
40
 
40
41
  def __init__(self, value):
41
42
  self._type_ = "I"
@@ -16,23 +16,21 @@ from libretro.api import (
16
16
  ThrottleMode,
17
17
  retro_device_power,
18
18
  retro_game_info,
19
- retro_hw_render_callback,
20
19
  retro_throttle_state,
21
20
  )
22
21
  from libretro.core import Core
23
22
  from libretro.drivers import (
24
23
  ArrayAudioDriver,
25
- ArrayVideoDriver,
26
24
  AudioDriver,
27
25
  CompositeEnvironmentDriver,
28
26
  ConstantPowerDriver,
29
27
  ContentDriver,
30
- DefaultPathDriver,
31
28
  DefaultPerfDriver,
32
29
  DefaultTimingDriver,
33
30
  DefaultUserDriver,
34
31
  DictLedDriver,
35
32
  DictOptionDriver,
33
+ DriverMap,
36
34
  FileSystemInterface,
37
35
  GeneratorInputDriver,
38
36
  GeneratorLocationDriver,
@@ -57,31 +55,30 @@ from libretro.drivers import (
57
55
  PowerDriver,
58
56
  StandardContentDriver,
59
57
  StandardFileSystemInterface,
58
+ TempDirPathDriver,
60
59
  TimingDriver,
61
60
  UnformattedLogDriver,
62
61
  UserDriver,
63
62
  VideoDriver,
64
- VideoDriverInitArgs,
65
63
  )
66
64
  from libretro.session import Session
67
65
 
68
- try:
69
- from libretro.drivers.video import ModernGlVideoDriver
70
- except ImportError:
71
- ModernGlVideoDriver = None
72
-
73
66
 
74
67
  class _DefaultType(Enum):
75
68
  DEFAULT = auto()
76
69
 
77
70
 
78
71
  DEFAULT = _DefaultType.DEFAULT
79
- Default = Literal[_DefaultType.DEFAULT]
80
72
  """
81
- A placeholder that indicates the default value for a SessionBuilder argument.
82
- When passed to a SessionBuilder method, the method will use the default value for that argument.
73
+ A placeholder that indicates the default value for one of :obj:`SessionBuilder`'s arguments.
74
+ When passed to one of :obj:`.SessionBuilder`'s ``with_`` methods,
75
+ it will use the default driver or argument configuration
76
+ unless otherwise noted.
83
77
  """
84
78
 
79
+ Default: TypeAlias = Literal[_DefaultType.DEFAULT]
80
+
81
+
85
82
  T = TypeVar("T")
86
83
 
87
84
  _RequiredFactory: TypeAlias = Callable[[], T]
@@ -100,7 +97,7 @@ InputDriverArg: TypeAlias = (
100
97
  | InputStateIterator
101
98
  | Default
102
99
  )
103
- VideoDriverArg: TypeAlias = _RequiredArg[VideoDriver] | Default
100
+ VideoDriverArg: TypeAlias = _RequiredArg[VideoDriver] | DriverMap | Default
104
101
  ContentArg: TypeAlias = (
105
102
  Content | SubsystemContent | _OptionalFactory[Content | SubsystemContent] | None
106
103
  )
@@ -110,7 +107,7 @@ MessageDriverArg: TypeAlias = _OptionalArg[MessageInterface] | Logger
110
107
  OptionDriverArg: TypeAlias = (
111
108
  _OptionalArg[OptionDriver] | Mapping[AnyStr, AnyStr] | Literal[0, 1, 2]
112
109
  )
113
- PathDriverArg: TypeAlias = _OptionalArg[PathDriver] | str | PathLike
110
+ PathDriverArg: TypeAlias = PathDriver | Callable[[Core], PathDriver | None] | Default | None
114
111
  LogDriverArg: TypeAlias = _OptionalArg[LogDriver] | Logger
115
112
  PerfDriverArg: TypeAlias = _OptionalArg[PerfDriver]
116
113
  LocationDriverArg: TypeAlias = _OptionalArg[LocationDriver] | LocationInputGenerator
@@ -146,7 +143,7 @@ class _SessionBuilderArgs(TypedDict):
146
143
  overscan: _OptionalFactory[bool] # TODO: Replace with some driver (not sure what yet)
147
144
  message: _OptionalFactory[MessageInterface]
148
145
  options: _OptionalFactory[OptionDriver]
149
- path: _OptionalFactory[PathDriver]
146
+ path: Callable[[Core], PathDriver | None]
150
147
  log: _OptionalFactory[LogDriver]
151
148
  perf: _OptionalFactory[PerfDriver]
152
149
  location: _OptionalFactory[LocationDriver]
@@ -168,13 +165,23 @@ class _SessionBuilderArgs(TypedDict):
168
165
 
169
166
  class SessionBuilder:
170
167
  """
171
- A builder class for constructing Session objects.
168
+ A builder class for constructing a :py:class:`.Session`.
172
169
 
173
- A Session requires a Core, an AudioDriver, an InputDriver, and a VideoDriver;
174
- each ``with_`` method sets an argument (mostly drivers) for the Session.
170
+ At minimum, a :py:class:`.Session` requires a :py:class:`.Core`,
171
+ an :py:class:`.AudioDriver`,
172
+ an :py:class:`.InputDriver`,
173
+ and a :py:class:`.VideoDriver`;
174
+ each ``with_`` method sets an argument (mostly drivers) for the :py:class:`.Session`.
175
175
  """
176
176
 
177
177
  def __init__(self):
178
+ """
179
+ Initializes a new :py:class:`SessionBuilder` with no arguments,
180
+ not even the required ones.
181
+
182
+ Calling :py:meth:`build` before setting any of the required arguments
183
+ will raise a :py:class:`RequiredError`.
184
+ """
178
185
  self._args = _SessionBuilderArgs(
179
186
  core=lambda: _raise_required_error("A Core is required"),
180
187
  audio=lambda: _raise_required_error("An AudioDriver is required"),
@@ -185,7 +192,7 @@ class SessionBuilder:
185
192
  overscan=_nothing,
186
193
  message=_nothing,
187
194
  options=_nothing,
188
- path=_nothing,
195
+ path=lambda _: None,
189
196
  log=_nothing,
190
197
  perf=_nothing,
191
198
  location=_nothing,
@@ -203,23 +210,33 @@ class SessionBuilder:
203
210
  power=_nothing,
204
211
  )
205
212
 
213
+ @classmethod
214
+ def defaults(cls, core: CoreArg) -> Self:
215
+ """
216
+ Alias to :py:func:`defaults`.
217
+ """
218
+ return defaults(core)
219
+
206
220
  def with_core(self, core: CoreArg) -> Self:
207
221
  """
208
222
  Sets the core to use for the session.
209
223
 
210
- ``core`` may be one of the following:
224
+ :param core: The core to use for the session. May be one of the following:
225
+
226
+ :class:`.Core`
227
+ Will be used as-is.
228
+
229
+ :class:`str`, :class:`~os.PathLike`
230
+ Will load a :class:`.Core` from this path in :meth:`build`.
211
231
 
212
- - A :class:`Core` that will be used as-is.
213
- - A :class:`str` or :class:`PathLike` that represents a path to the core.
214
- It will be loaded as a :class:`Core` when the session is built.
215
- - A :class:`CDLL` object that represents a loaded binary.
216
- It will be loaded into a :class:`Core` when the session is built.
217
- - A :class:`Callable` that returns one of the above types.
218
- It will be evaluated when the session is built.
232
+ :class:`~ctypes.CDLL`
233
+ Will load a :class:`.Core` from this library in :meth:`build`.
219
234
 
220
- :param core: The core to use for the session.
221
- :return: This builder object.
222
- :raises ValueError: If ``core`` is :py:const:`Default`.
235
+ :class:`~collections.abc.Callable` () -> :class:`.Core`
236
+ Zero-argument function that returns a :class:`.Core`.
237
+ Will be called in :meth:`build`.
238
+
239
+ :return: This :class:`SessionBuilder` object.
223
240
  :raises TypeError: If ``core`` is not one of the permitted types.
224
241
  """
225
242
  match core:
@@ -229,11 +246,9 @@ class SessionBuilder:
229
246
  self._args["core"] = lambda: core
230
247
  case str() | PathLike() | CDLL():
231
248
  self._args["core"] = lambda: Core(core)
232
- case _DefaultType.DEFAULT:
233
- raise ValueError("Core does not have a default value")
234
249
  case _:
235
250
  raise TypeError(
236
- f"Expected Core, str, PathLike, a CDLL, or a callable that returns one of them; got {type(core).__name__}"
251
+ f"Expected Core, str, PathLike, a CDLL, or a callable that returns a Core; got {type(core).__name__}"
237
252
  )
238
253
 
239
254
  return self
@@ -241,10 +256,38 @@ class SessionBuilder:
241
256
  def with_content(self, content: ContentArg) -> Self:
242
257
  """
243
258
  Sets the content to use for this session.
259
+ Will be loaded and managed by this builder's assigned :class:`.ContentDriver`.
260
+
261
+ :param content: The content to use for this session. May be one of the following:
244
262
 
245
- ``content`` may be one of the following:
263
+ :class:`str`, :class:`~os.PathLike`
264
+ Will load a single content file without enabling a subsystem.
246
265
 
247
- TODO
266
+ :class:`zipfile.Path`
267
+ Will load a single content file within a ZIP archive without enabling a subsystem.
268
+
269
+ :class:`bytes`, :class:`bytearray`, :class:`memoryview`, :class:`~collections.abc.Buffer`
270
+ Will expose a single unnamed content buffer without enabling a subsystem.
271
+
272
+ :class:`.SubsystemContent`
273
+ Will enable a subsystem and load multiple associated content files.
274
+
275
+ :class:`.retro_game_info`
276
+ Will be passed to the core as-is without enabling a subsystem.
277
+
278
+ :obj:`None`
279
+ Will load the core without using any content or enabling a subsystem;
280
+ if not supported by the core, this will raise an error in :meth:`build`.
281
+ Note that ``retro_load_game`` **will** be called.
282
+
283
+ :class:`~collections.abc.Callable` () -> :data:`.Content` | :class:`.SubsystemContent` | :obj:`None`
284
+ Zero-argument function that returns one of the above.
285
+ Will be called in :meth:`build`.
286
+
287
+ :return: This :class:`.SessionBuilder` object.
288
+ :raises TypeError: If ``content`` is not one of the permitted types.
289
+
290
+ :see: :meth:`.ContentDriver.load` for details on how loaded content is exposed to the core.
248
291
  """
249
292
  match content:
250
293
  case Callable() as func:
@@ -326,20 +369,26 @@ class SessionBuilder:
326
369
  """
327
370
  Sets the video driver for this session.
328
371
 
329
- The default video driver is a ``MultiVideoDriver`` that supports the following contexts:
372
+ :param video: The video driver to use for this session. May be one of the following:
330
373
 
331
- - ``HardwareContext.NONE``: An ``ArrayVideoDriver``.
332
- - ``HardwareContext.OPENGL``: A ``ModernGlVideoDriver`` if the ``moderngl`` package is installed,
333
- absent if not.
374
+ :class:`.VideoDriver`
375
+ Used by the built :class:`.Session` as-is.
334
376
 
335
- :param video: One of the following:
377
+ :const:`DEFAULT`
378
+ Uses a :class:`.MultiVideoDriver` with its default configuration.
379
+ See its documentation for more details.
336
380
 
337
- - A ``VideoDriver`` that will be used as-is.
338
- - A ``Callable`` that returns a ``VideoDriver``.
339
- - A :class:`Callable` that returns a :class:`VideoDriver`.
340
- - :py:const:`DEFAULT` to use the default video driver.
341
- - :py:const:`None` to raise an error.
342
- :return: This builder object.
381
+ :class:`~collections.abc.Mapping` [:class:`.HardwareContext`, :class:`~collections.abc.Callable` () -> :class:`.VideoDriver`]
382
+ Uses a :class:`.MultiVideoDriver` with the provided driver map.
383
+
384
+ :class:`~collections.abc.Callable` () -> :class:`.VideoDriver`
385
+ Zero-argument function that returns a :class:`.VideoDriver`.
386
+ Called in :meth:`build`.
387
+
388
+
389
+ :return: This :class:`SessionBuilder` object.
390
+ :raises TypeError: If ``video`` is not one of the aforementioned types.
391
+ :raises ValueError: If ``video`` does not contain a mapping for :attr:`.HardwareContext.NONE`.
343
392
  """
344
393
 
345
394
  match video:
@@ -348,20 +397,28 @@ class SessionBuilder:
348
397
  case VideoDriver():
349
398
  self._args["video"] = lambda: video
350
399
  case _DefaultType.DEFAULT:
351
- drivers: dict[HardwareContext, Callable[[], VideoDriver]] = dict()
352
- drivers[HardwareContext.NONE] = ArrayVideoDriver
400
+ self._args["video"] = MultiVideoDriver
401
+ case Mapping():
402
+ if HardwareContext.NONE not in video:
403
+ raise ValueError("A driver for HardwareContext.NONE is required")
404
+
405
+ if not all(isinstance(k, HardwareContext) for k in video.keys()):
406
+ raise TypeError(
407
+ "Each key in the provided driver map must be a HardwareContext"
408
+ )
353
409
 
354
- if ModernGlVideoDriver:
355
- # If moderngl is installed...
356
- drivers[HardwareContext.OPENGL] = ModernGlVideoDriver
357
- drivers[HardwareContext.OPENGL_CORE] = ModernGlVideoDriver
410
+ if not all(callable(v) for v in video.values()):
411
+ raise TypeError(
412
+ "Each value in the provided driver map must be a callable that returns a VideoDriver"
413
+ )
414
+
415
+ self._args["video"] = lambda: MultiVideoDriver(video)
358
416
 
359
- self._args["video"] = lambda: MultiVideoDriver(drivers)
360
417
  case None:
361
418
  raise ValueError("A video driver is required")
362
419
  case _:
363
420
  raise TypeError(
364
- f"Expected a VideoDriver, a callable that returns one, or DEFAULT; got {type(video).__name__}"
421
+ f"Expected a VideoDriver, a callable that returns one, a map of HardwareContexts to Callables, or DEFAULT; got {type(video).__name__}"
365
422
  )
366
423
 
367
424
  return self
@@ -420,6 +477,41 @@ class SessionBuilder:
420
477
  return self
421
478
 
422
479
  def with_options(self, options: OptionDriverArg) -> Self:
480
+ """
481
+ Configures the options driver for this session.
482
+
483
+ :param options: May be one of the following:
484
+
485
+ :class:`.OptionDriver`
486
+ Will be used by the built :class:`.Session` as-is.
487
+
488
+ :class:`~collections.abc.Mapping` [:obj:`~typing.AnyStr`, :obj:`~typing.AnyStr`]
489
+ Will be used to initialize a :class:`.DictOptionDriver` with the provided options
490
+ and with API version 2.
491
+ All keys and values must be either :class:`str` or :class:`bytes`;
492
+ mixing the two is not allowed.
493
+
494
+ ``0``, ``1``, or ``2``
495
+ Will be used to initialize a :class:`.DictOptionDriver` with no initial options
496
+ using the provided API version.
497
+
498
+ :data:`.DEFAULT`
499
+ Will use a :class:`.DictOptionDriver` with API version 2
500
+ and no initial options.
501
+
502
+ :obj:`None`
503
+ All environment calls that :class:`.OptionDriver` normally implements
504
+ will be unavailable to the loaded :class:`.Core`.
505
+
506
+ :class:`~collections.abc.Callable` () -> :class:`.OptionDriver` | :obj:`None`
507
+ Zero-argument function that returns an :class:`.OptionDriver` or :obj:`None`.
508
+ Will be called in :meth:`build`.
509
+
510
+ :return: This :class:`.SessionBuilder` object.
511
+ :raises TypeError: If ``options`` is not one of the aforementioned types.
512
+ :raises ValueError: If ``options`` is a :class:`~collections.abc.Mapping` whose keys and values
513
+ are not all :class:`str` or :class:`bytes`.
514
+ """
423
515
  _types = (str, bytes)
424
516
  match options:
425
517
  case Callable() as func:
@@ -427,11 +519,17 @@ class SessionBuilder:
427
519
  case OptionDriver() as driver:
428
520
  driver: OptionDriver
429
521
  self._args["options"] = lambda: driver
430
- case dict(vars) if all(
431
- isinstance(k, _types) and isinstance(v, _types) for k, v in vars.items()
432
- ):
433
- vars: Mapping[AnyStr, AnyStr]
434
- self._args["options"] = lambda: DictOptionDriver(2, True, vars)
522
+ case Mapping() as optionvars:
523
+ optionvars: Mapping[AnyStr, AnyStr]
524
+ all_str = all(
525
+ isinstance(k, str) and isinstance(v, str) for k, v in optionvars.items()
526
+ )
527
+ all_bytes = all(
528
+ isinstance(k, bytes) and isinstance(v, bytes) for k, v in optionvars.items()
529
+ )
530
+ if not (all_str or all_bytes):
531
+ raise ValueError("All keys and values must be either str or bytes")
532
+ self._args["options"] = lambda: DictOptionDriver(2, True, optionvars)
435
533
  case 0 | 1 | 2 as version:
436
534
  self._args["options"] = lambda: DictOptionDriver(version)
437
535
  case _DefaultType.DEFAULT:
@@ -440,21 +538,46 @@ class SessionBuilder:
440
538
  self._args["options"] = _nothing
441
539
  case _:
442
540
  raise TypeError(
443
- f"Expected OptionDriver, a dict, a callable that returns one, DEFAULT, or None; got {type(options).__name__}"
541
+ f"Expected an OptionDriver, a Mapping, DEFAULT, an API version, or a Callable that returns an OptionDriver, or None; got {type(options).__name__}"
444
542
  )
445
543
 
446
544
  return self
447
545
 
448
546
  def with_paths(self, path: PathDriverArg) -> Self:
547
+ """
548
+ Configures the path driver for this session.
549
+
550
+ :param path: May be one of the following:
551
+
552
+ :class:`.PathDriver`
553
+ Will be used by the built :class:`.Session` as-is.
554
+
555
+ :data:`.DEFAULT`
556
+ Will use a :class:`.TempDirPathDriver`
557
+ configured with an unspecified temporary directory
558
+ and the provided :class:`.Core`'s path.
559
+
560
+ :class:`~collections.abc.Callable` (:class:`.Core`) -> :class:`.PathDriver` | :obj:`None`
561
+ One-argument function that accepts a :class:`.Core`
562
+ and returns a :class:`.PathDriver` or :obj:`None`.
563
+ Will be called in :meth:`build` with the configured :class:`.Core` as the argument.
564
+
565
+ :obj:`None`
566
+ All environment calls that :class:`.PathDriver` normally implements
567
+ will be unavailable to the loaded :class:`.Core`.
568
+
569
+ :return: This :class:`.SessionBuilder` object.
570
+ :raises TypeError: If ``path`` is not one of the aforementioned types.
571
+ """
449
572
  match path:
450
573
  case Callable() as func:
451
574
  self._args["path"] = func
452
575
  case PathDriver():
453
- self._args["path"] = lambda: path
576
+ self._args["path"] = lambda _: path
454
577
  case _DefaultType.DEFAULT:
455
- self._args["path"] = DefaultPathDriver # TODO: How to pass core path?
578
+ self._args["path"] = lambda core: TempDirPathDriver(core)
456
579
  case None:
457
- self._args["path"] = _nothing
580
+ self._args["path"] = lambda _: None
458
581
  case _:
459
582
  raise TypeError(
460
583
  f"Expected PathDriver, a callable that returns one, DEFAULT, or None; got {type(path).__name__}"
@@ -742,7 +865,12 @@ class SessionBuilder:
742
865
 
743
866
  def build(self) -> Session:
744
867
  """
745
- Constructs a Session object with the provided arguments.
868
+ Constructs a :py:class:`.Session` with the provided arguments.
869
+
870
+ :raises RequiredError: If a :py:class:`.Core`, :py:class:`.AudioDriver`, :py:class:`.InputDriver`, or :py:class:`.VideoDriver` is not set.
871
+ :raises Exception: Any exception raised by a registered driver factory or initializer.
872
+
873
+ :return: A :py:class:`.Session` object.
746
874
  """
747
875
  core = self._args["core"]()
748
876
  content = self._args["content"]()
@@ -754,7 +882,7 @@ class SessionBuilder:
754
882
  overscan=self._args["overscan"](),
755
883
  message=self._args["message"](),
756
884
  options=self._args["options"](),
757
- path=self._args["path"](),
885
+ path=self._args["path"](core),
758
886
  log=self._args["log"](),
759
887
  perf=self._args["perf"](),
760
888
  location=self._args["location"](),
@@ -777,6 +905,19 @@ class SessionBuilder:
777
905
 
778
906
 
779
907
  def defaults(core: CoreArg) -> SessionBuilder:
908
+ """
909
+ Constructs a :py:class:`SessionBuilder` with the recommended drivers and their default values.
910
+ Does not build the session, so these defaults may still be overridden.
911
+
912
+ :param core: The core to use for the session.
913
+
914
+ Examples::
915
+
916
+ builder = SessionBuilder.defaults(core)
917
+ with builder.with_log(None).build() as session:
918
+ pass
919
+
920
+ """
780
921
  return (
781
922
  SessionBuilder()
782
923
  .with_core(core)
@@ -810,4 +951,5 @@ __all__ = [
810
951
  "SessionBuilder",
811
952
  "DEFAULT",
812
953
  "defaults",
954
+ "RequiredError",
813
955
  ]
@@ -92,6 +92,8 @@ class StandardContentDriver(ContentDriver):
92
92
  raise RuntimeError("System info not set")
93
93
 
94
94
  with ExitStack() as stack:
95
+ # We may be loading several files, each of which needs its own context manager
96
+ # So we use ExitStack to manage all of their lives at once
95
97
  loaded_content: Sequence[LoadedContentFile | None] | None
96
98
  subsystem: retro_subsystem_info | None = None
97
99
  match content:
@@ -273,12 +275,12 @@ class StandardContentDriver(ContentDriver):
273
275
  yield LoadedContentFile(loaded_info, loaded_info_ext)
274
276
 
275
277
  # For test cases that provide content by path
276
- case str(path) | PathLike(path), ContentAttributes(need_fullpath=True):
278
+ case (str() | PathLike()) as path, ContentAttributes(need_fullpath=True):
277
279
  loaded_info = retro_game_info(os.fsencode(path), None, 0, None)
278
280
  loaded_info_ext = _make_game_info_ext(loaded_info)
279
281
  yield LoadedContentFile(loaded_info, loaded_info_ext)
280
282
  # There's no data to persist, so no cleanup needed
281
- case str(path) | PathLike(path), ContentAttributes(persistent_data=False):
283
+ case (str() | PathLike()) as path, ContentAttributes(persistent_data=False):
282
284
  with mmap_file(path) as view:
283
285
  loaded_info = retro_game_info(
284
286
  os.fsencode(path), addressof_buffer(view), len(view), None
@@ -286,7 +288,7 @@ class StandardContentDriver(ContentDriver):
286
288
  loaded_info_ext = _make_game_info_ext(loaded_info)
287
289
  yield LoadedContentFile(loaded_info, loaded_info_ext)
288
290
  # Content is not persistent, so just let the with statement clean up the view
289
- case str(path) | PathLike(path), ContentAttributes(persistent_data=True):
291
+ case (str() | PathLike()) as path, ContentAttributes(persistent_data=True):
290
292
  context = mmap_file(path)
291
293
  view = context.__enter__()
292
294
  loaded_info = retro_game_info(
@@ -2,5 +2,6 @@
2
2
  Drivers that determine the paths and directories associated with the core.
3
3
  """
4
4
 
5
- from .default import *
6
5
  from .driver import *
6
+ from .explicit import *
7
+ from .temp import *
@@ -0,0 +1,64 @@
1
+ from abc import abstractmethod
2
+ from typing import Protocol, runtime_checkable
3
+
4
+
5
+ @runtime_checkable
6
+ class PathDriver(Protocol):
7
+ """
8
+ Interface for a driver that defines various paths exposed to libretro cores.
9
+ """
10
+
11
+ @property
12
+ @abstractmethod
13
+ def system_dir(self) -> bytes | None:
14
+ """
15
+ Corresponds to :py:attr:`.EnvironmentCall.GET_SYSTEM_DIRECTORY`.
16
+
17
+ If :py:obj:`None`, a core's call to ``RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY`` should return ``false``.
18
+ """
19
+ ...
20
+
21
+ @property
22
+ @abstractmethod
23
+ def libretro_path(self) -> bytes | None:
24
+ """
25
+ Corresponds to :py:attr:`.EnvironmentCall.GET_LIBRETRO_PATH`.
26
+
27
+ If :py:obj:`None`, a core's call to ``RETRO_ENVIRONMENT_GET_LIBRETRO_PATH`` should return ``false``.
28
+ """
29
+ ...
30
+
31
+ @property
32
+ @abstractmethod
33
+ def core_assets_dir(self) -> bytes | None:
34
+ """
35
+ Corresponds to :py:attr:`.EnvironmentCall.GET_CORE_ASSETS_DIRECTORY`.
36
+
37
+ If :py:obj:`None`, a core's call to ``RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY`` should return ``false``.
38
+ """
39
+ ...
40
+
41
+ @property
42
+ @abstractmethod
43
+ def save_dir(self) -> bytes | None:
44
+ """
45
+ Corresponds to :py:attr:`.EnvironmentCall.GET_SAVE_DIRECTORY`.
46
+
47
+ If :py:obj:`None`, a core's call to ``RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY`` should return ``false``.
48
+ """
49
+ ...
50
+
51
+ @property
52
+ @abstractmethod
53
+ def playlist_dir(self) -> bytes | None:
54
+ """
55
+ Corresponds to :py:attr:`.EnvironmentCall.GET_PLAYLIST_DIRECTORY`.
56
+
57
+ If :py:obj:`None`, a core's call to ``RETRO_ENVIRONMENT_GET_PLAYLIST_DIRECTORY`` should return ``false``.
58
+ """
59
+ ...
60
+
61
+
62
+ __all__ = [
63
+ "PathDriver",
64
+ ]