Class: Gemba::SaveStateManager
- Inherits:
-
Object
- Object
- Gemba::SaveStateManager
- Includes:
- Locale::Translatable
- Defined in:
- lib/gemba/save_state_manager.rb
Overview
Manages save state persistence: save, load, screenshot capture, debounce, and backup rotation.
All dependencies are injected via the constructor so the class can be tested with lightweight mocks (no real mGBA Core or Tk interpreter).
Instance Attribute Summary collapse
-
#backup ⇒ Boolean
Whether to create .bak files.
-
#core ⇒ Core
The mGBA core (swappable for reset/ROM change).
-
#quick_save_slot ⇒ Integer
Quick save/load slot.
-
#state_dir ⇒ String?
Current state directory.
Instance Method Summary collapse
-
#initialize(core:, config:, app:, platform:) ⇒ SaveStateManager
constructor
A new instance of SaveStateManager.
-
#load_state(slot) ⇒ Array(Boolean, String)
Load the emulator state from the given slot.
-
#quick_load ⇒ Array(Boolean, String)
Load from the quick save slot.
-
#quick_save ⇒ Array(Boolean, String)
Save to the quick save slot.
-
#save_screenshot(path) ⇒ Object
Save a PNG screenshot of the current frame via Tk photo image.
-
#save_state(slot) ⇒ Array(Boolean, String)
Save the emulator state to the given slot.
-
#screenshot_path(slot) ⇒ String
Path to the screenshot PNG for this slot.
-
#state_dir_for_rom(core) ⇒ String
Build per-ROM state directory path using game code + CRC32.
-
#state_path(slot) ⇒ String
Path to the save state file for this slot.
Constructor Details
#initialize(core:, config:, app:, platform:) ⇒ SaveStateManager
Returns a new instance of SaveStateManager.
24 25 26 27 28 29 30 31 32 33 |
# File 'lib/gemba/save_state_manager.rb', line 24 def initialize(core:, config:, app:, platform:) @core = core @config = config @app = app @platform = platform @last_save_time = 0 @state_dir = nil @quick_save_slot = config.quick_save_slot @backup = config.save_state_backup? end |
Instance Attribute Details
#backup ⇒ Boolean
Returns whether to create .bak files.
39 40 41 |
# File 'lib/gemba/save_state_manager.rb', line 39 def backup @backup end |
#core ⇒ Core
Returns the mGBA core (swappable for reset/ROM change).
42 43 44 |
# File 'lib/gemba/save_state_manager.rb', line 42 def core @core end |
#quick_save_slot ⇒ Integer
Returns quick save/load slot.
36 37 38 |
# File 'lib/gemba/save_state_manager.rb', line 36 def quick_save_slot @quick_save_slot end |
#state_dir ⇒ String?
Returns current state directory.
59 60 61 |
# File 'lib/gemba/save_state_manager.rb', line 59 def state_dir @state_dir end |
Instance Method Details
#load_state(slot) ⇒ Array(Boolean, String)
Load the emulator state from the given slot.
106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/gemba/save_state_manager.rb', line 106 def load_state(slot) return [false, nil] unless @core && !@core.destroyed? ss = state_path(slot) unless File.exist?(ss) return [false, translate('toast.no_state', slot: slot)] end if @core.load_state_from_file(ss) [true, translate('toast.state_loaded', slot: slot)] else [false, translate('toast.load_failed')] end end |
#quick_load ⇒ Array(Boolean, String)
Load from the quick save slot.
129 130 131 |
# File 'lib/gemba/save_state_manager.rb', line 129 def quick_load load_state(@quick_save_slot) end |
#quick_save ⇒ Array(Boolean, String)
Save to the quick save slot.
123 124 125 |
# File 'lib/gemba/save_state_manager.rb', line 123 def quick_save save_state(@quick_save_slot) end |
#save_screenshot(path) ⇒ Object
Save a PNG screenshot of the current frame via Tk photo image. Uses @app.command() to drive Tk’s image subsystem.
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/gemba/save_state_manager.rb', line 136 def save_screenshot(path) return unless @core && !@core.destroyed? pixels = @core.video_buffer_argb photo_name = "__gemba_ss_#{object_id}" @app.command(:image, :create, :photo, photo_name, width: @platform.width, height: @platform.height) @app.interp.photo_put_block(photo_name, pixels, @platform.width, @platform.height, format: :argb) @app.command(photo_name, :write, path, format: :png) @app.command(:image, :delete, photo_name) rescue StandardError => e warn "gemba: screenshot failed for #{path}: #{e.} (#{e.class})" @app.command(:image, :delete, photo_name) rescue nil end |
#save_state(slot) ⇒ Array(Boolean, String)
Save the emulator state to the given slot.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/gemba/save_state_manager.rb', line 76 def save_state(slot) return [false, nil] unless @core && !@core.destroyed? now = Process.clock_gettime(Process::CLOCK_MONOTONIC) if now - @last_save_time < @config.save_state_debounce return [false, translate('toast.save_blocked')] end FileUtils.mkdir_p(@state_dir) unless File.directory?(@state_dir) # Backup rotation: rename existing files → .bak ss = state_path(slot) png = screenshot_path(slot) if @backup File.rename(ss, "#{ss}.bak") if File.exist?(ss) File.rename(png, "#{png}.bak") if File.exist?(png) end if @core.save_state_to_file(ss) @last_save_time = now save_screenshot(png) [true, translate('toast.state_saved', slot: slot)] else [false, translate('toast.save_failed')] end end |
#screenshot_path(slot) ⇒ String
Returns path to the screenshot PNG for this slot.
69 70 71 |
# File 'lib/gemba/save_state_manager.rb', line 69 def screenshot_path(slot) File.join(@state_dir, "state#{slot}.png") end |
#state_dir_for_rom(core) ⇒ String
Build per-ROM state directory path using game code + CRC32. e.g. states/AGB-BTKE-A1B2C3D4/
48 49 50 51 52 |
# File 'lib/gemba/save_state_manager.rb', line 48 def state_dir_for_rom(core) code = core.game_code.gsub(/[^a-zA-Z0-9_.-]/, '_') crc = format('%08X', core.checksum) File.join(@config.states_dir, "#{code}-#{crc}") end |
#state_path(slot) ⇒ String
Returns path to the save state file for this slot.
63 64 65 |
# File 'lib/gemba/save_state_manager.rb', line 63 def state_path(slot) File.join(@state_dir, "state#{slot}.ss") end |