[gnome-games/wip/exalm/rebrand: 41/124] retro-runner: Merge into Runner




commit ff663687468538bdcaaa14712f0447f311dac480
Author: Alexander Mikhaylenko <alexm gnome org>
Date:   Mon Mar 29 21:36:52 2021 +0500

    retro-runner: Merge into Runner

 plugins/game-cube/src/game-cube-runner.vala       |   2 +-
 plugins/ms-dos/src/ms-dos-plugin.vala             |   2 +-
 plugins/nintendo-3ds/src/nintendo-3ds-runner.vala |   2 +-
 plugins/nintendo-64/src/nintendo-64-runner.vala   |   2 +-
 plugins/nintendo-ds/src/nintendo-ds-runner.vala   |   2 +-
 src/core/runner.vala                              | 586 +++++++++++++++++++++-
 src/meson.build                                   |   1 -
 src/retro/retro-runner-factory.vala               |   2 +-
 src/retro/retro-runner.vala                       | 564 ---------------------
 9 files changed, 566 insertions(+), 597 deletions(-)
---
diff --git a/plugins/game-cube/src/game-cube-runner.vala b/plugins/game-cube/src/game-cube-runner.vala
index fad78db7..fddb667a 100644
--- a/plugins/game-cube/src/game-cube-runner.vala
+++ b/plugins/game-cube/src/game-cube-runner.vala
@@ -1,6 +1,6 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
-private class Games.GameCubeRunner : RetroRunner {
+private class Games.GameCubeRunner : Runner {
        public GameCubeRunner (Game game, RetroCoreSource source) {
                base (game, source);
        }
diff --git a/plugins/ms-dos/src/ms-dos-plugin.vala b/plugins/ms-dos/src/ms-dos-plugin.vala
index dbfa14e2..fb2ed78a 100644
--- a/plugins/ms-dos/src/ms-dos-plugin.vala
+++ b/plugins/ms-dos/src/ms-dos-plugin.vala
@@ -47,7 +47,7 @@ private class Games.MsDosPlugin : Object, Plugin {
 
        private static Runner? create_runner (Game game) throws Error {
                var core_source = new RetroCoreSource (platform);
-               var runner = new RetroRunner (game, core_source);
+               var runner = new Runner (game, core_source);
 
                runner.input_capabilities = new GenericInputCapabilities (true, false);
 
diff --git a/plugins/nintendo-3ds/src/nintendo-3ds-runner.vala 
b/plugins/nintendo-3ds/src/nintendo-3ds-runner.vala
index 34283287..423477a5 100644
--- a/plugins/nintendo-3ds/src/nintendo-3ds-runner.vala
+++ b/plugins/nintendo-3ds/src/nintendo-3ds-runner.vala
@@ -1,6 +1,6 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
-private class Games.Nintendo3DsRunner : RetroRunner {
+private class Games.Nintendo3DsRunner : Runner {
        // Map the 1,2,3,4 key values to the 4 screen layouts of the Nintendo 3DS
        private static HashTable<uint, ScreenLayout?> layouts;
 
diff --git a/plugins/nintendo-64/src/nintendo-64-runner.vala b/plugins/nintendo-64/src/nintendo-64-runner.vala
index d45acdde..1074578e 100644
--- a/plugins/nintendo-64/src/nintendo-64-runner.vala
+++ b/plugins/nintendo-64/src/nintendo-64-runner.vala
@@ -1,6 +1,6 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
-private class Games.Nintendo64Runner : RetroRunner {
+private class Games.Nintendo64Runner : Runner {
        private const string MUPEN64PLUS_PAK_OPTION = "mupen64plus-pak%u";
        private const string PARALLEL_N64_PAK_OPTION = "parallel-n64-pak%u";
 
diff --git a/plugins/nintendo-ds/src/nintendo-ds-runner.vala b/plugins/nintendo-ds/src/nintendo-ds-runner.vala
index 9dbb58c2..756b7df2 100644
--- a/plugins/nintendo-ds/src/nintendo-ds-runner.vala
+++ b/plugins/nintendo-ds/src/nintendo-ds-runner.vala
@@ -1,6 +1,6 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
-private class Games.NintendoDsRunner : RetroRunner {
+private class Games.NintendoDsRunner : Runner {
        // Map the 1,2,3,4 key values to the 4 screen layouts of the Nintendo DS
        private static HashTable<uint, ScreenLayout?> layouts;
        private static HashTable<string, string> gap_overrides;
diff --git a/src/core/runner.vala b/src/core/runner.vala
index 504dbd3f..3d4389dd 100644
--- a/src/core/runner.vala
+++ b/src/core/runner.vala
@@ -1,33 +1,567 @@
 // This file is part of GNOME Games. License: GPL-3.0+.
 
-public interface Games.Runner : Object {
+public class Games.Runner : Object {
        public signal void snapshot_created ();
        public signal void stopped ();
        public signal void crash (string message);
+       public signal void controllers_changed ();
 
-       public abstract bool can_fullscreen { get; }
-       public abstract bool can_resume { get; }
-       public abstract bool supports_snapshots { get; }
-       public abstract MediaSet? media_set { get; }
-       public abstract InputMode input_mode { get; set; }
-
-       public abstract Gtk.Widget get_display ();
-       public abstract HeaderBarWidget? get_extra_widget ();
-
-       public abstract void prepare () throws RunnerError;
-       public abstract void start () throws Error;
-       public abstract void resume ();
-       public abstract void pause ();
-       public abstract void stop ();
-
-       public abstract Snapshot? try_create_snapshot (bool is_automatic);
-       public abstract void delete_snapshot (Snapshot snapshot);
-       public abstract void preview_snapshot (Snapshot snapshot);
-       public abstract void preview_current_state ();
-       public abstract void load_previewed_snapshot () throws Error;
-       public abstract Snapshot[] get_snapshots ();
-
-       public abstract InputMode[] get_available_input_modes ();
-       public abstract bool key_press_event (uint keyval, Gdk.ModifierType state);
-       public abstract bool gamepad_button_press_event (uint16 button);
+       public bool can_fullscreen {
+               get { return true; }
+       }
+
+       public bool can_resume {
+               get {
+                       if (snapshot_manager == null)
+                               return false;
+
+                       return snapshot_manager.has_snapshots ();
+               }
+       }
+
+       public bool supports_snapshots {
+               get { return started && core.get_can_access_state (); }
+       }
+
+       private MediaSet _media_set;
+       public MediaSet? media_set {
+               get { return _media_set; }
+       }
+
+       public InputCapabilities input_capabilities { get; set; }
+
+       private Retro.Core core;
+       private Retro.CoreView view;
+       private RetroInputManager input_manager;
+       private InputMode _input_mode;
+       public InputMode input_mode {
+               get { return _input_mode; }
+               set {
+                       _input_mode = value;
+                       input_manager.input_mode = value;
+               }
+       }
+
+       private RetroCoreSource core_source;
+       private Settings settings;
+       private Game game;
+       private SnapshotManager snapshot_manager;
+
+       private Snapshot previewed_snapshot;
+
+       private string tmp_save_dir;
+
+       private Gdk.Pixbuf current_state_pixbuf;
+
+       private bool _running;
+       private bool running {
+               get { return _running; }
+               set {
+                       _running = value;
+                       view.sensitive = running;
+               }
+       }
+
+       private bool started;
+       private bool core_loaded;
+       private bool is_error;
+
+       public Runner (Game game, RetroCoreSource source) {
+               this.game = game;
+
+               core_source = source;
+
+               _media_set = game.media_set;
+               if (media_set == null && game.uri != null) {
+                       var media = new Media ();
+                       media.add_uri (game.uri);
+
+                       _media_set = new MediaSet ();
+                       _media_set.add_media (media);
+               }
+
+               _media_set.notify["selected-media-number"].connect (on_media_number_changed);
+       }
+
+       construct {
+               settings = new Settings ("org.gnome.Games");
+               view = new Retro.CoreView ();
+
+               settings.changed["video-filter"].connect (on_video_filter_changed);
+               on_video_filter_changed ();
+       }
+
+       private void on_video_filter_changed () {
+               var filter_name = settings.get_string ("video-filter");
+               var filter = Retro.VideoFilter.from_string (filter_name);
+               view.set_filter (filter);
+       }
+
+       ~Runner () {
+               deinit ();
+       }
+
+       public Gtk.Widget get_display () {
+               return view;
+       }
+
+       public virtual HeaderBarWidget? get_extra_widget () {
+               return null;
+       }
+
+       private string get_unsupported_system_message () {
+               var platform_name = game.platform.get_name ();
+               if (platform_name != null)
+                       return _("The system “%s” isn’t supported yet, but full support is planned.").printf 
(platform_name);
+
+               return _("The system isn’t supported yet, but full support is planned.");
+       }
+
+       private string get_core_id () throws Error {
+               return core_source.get_core_id ();
+       }
+
+       private string create_tmp_save_dir () throws Error {
+               return DirUtils.make_tmp ("games_save_dir_XXXXXX");
+       }
+
+       private string get_options_path () throws Error {
+               assert (core != null);
+
+               var core_filename = core.get_filename ();
+               var file = File.new_for_path (core_filename);
+               var basename = file.get_basename ();
+               var options_name = basename.split (".")[0];
+               options_name = options_name.replace ("_libretro", "");
+
+               return @"$(Config.OPTIONS_DIR)/$options_name.options";
+       }
+
+       private void prepare_core () throws Error {
+               var module_path = core_source.get_module_path ();
+
+               core = new Retro.Core (module_path);
+
+               var options_path = get_options_path ();
+               if (FileUtils.test (options_path, FileTest.EXISTS))
+                       try {
+                               var options = new RetroOptions (options_path);
+                               options.apply (core);
+                       } catch (Error e) {
+                               critical (e.message);
+                       }
+
+               var platforms_dir = Application.get_platforms_dir ();
+               var platform_id = game.platform.get_id ();
+
+               var user_system_dir = @"$platforms_dir/$platform_id/system";
+               var system_dir = @"$(Config.SYSTEM_DIR)/$platform_id";
+
+               /* Prefer the user system directory if both exist */
+               if (FileUtils.test (system_dir, FileTest.EXISTS) &&
+                   !FileUtils.test (user_system_dir, FileTest.EXISTS))
+                       core.system_directory = system_dir;
+               else
+                       core.system_directory = user_system_dir;
+
+               core.save_directory = tmp_save_dir;
+
+               core.log.connect (Retro.g_log);
+               core.shutdown.connect (stop);
+               core.crashed.connect ((core, error) => {
+                       is_error = true;
+                       game.update_last_played ();
+                       crash (error);
+               });
+
+               view.set_core (core);
+
+               string[] medias_uris = {};
+               media_set.foreach_media ((media) => {
+                       var uris = media.get_uris ();
+                       medias_uris += (uris.length == 0) ? "" : uris[0].to_string ();
+               });
+
+               core.set_medias (medias_uris);
+               core.boot ();
+
+               if (medias_uris.length > 0)
+                       core.set_current_media (media_set.selected_media_number);
+
+               input_manager = new RetroInputManager (core, view);
+               input_manager.controllers_changed.connect (() => {
+                       controllers_changed ();
+               });
+
+               // Keep the internal values of input_mode in sync between RetroRunner and RetroInputManager
+               input_mode = get_available_input_modes ()[0];
+
+               core_loaded = true;
+       }
+
+       public void prepare () throws RunnerError {
+               try {
+                       snapshot_manager = new SnapshotManager (game, get_core_id ());
+
+                       var snapshot = snapshot_manager.get_latest_snapshot ();
+
+                       tmp_save_dir = create_tmp_save_dir ();
+
+                       var tmp_dir = File.new_for_path (tmp_save_dir);
+                       if (tmp_dir.query_exists ())
+                               FileOperations.delete_files (tmp_dir);
+                       tmp_dir.make_directory ();
+
+                       if (snapshot != null) {
+                               snapshot.copy_save_dir_to (tmp_save_dir);
+                       } else {
+                               var path = get_fallback_save_directory_path ();
+                               var save_dir_path = Path.build_filename (path, "save-dir");
+
+                               if (FileUtils.test (save_dir_path, FileTest.EXISTS)) {
+                                       var save_dir = File.new_for_path (save_dir_path);
+
+                                       FileOperations.copy_contents (save_dir, File.new_for_path 
(tmp_save_dir));
+                               }
+                       }
+
+                       prepare_core ();
+
+                       if (snapshot != null) {
+                               reset_with_snapshot (snapshot);
+
+                               preview_snapshot (snapshot);
+                       } else {
+                               var path = get_fallback_save_directory_path ();
+                               var save_ram_path = Path.build_filename (path, "save");
+
+                               if (FileUtils.test (save_ram_path, FileTest.EXISTS) &&
+                                   core.get_memory_size (Retro.MemoryType.SAVE_RAM) > 0)
+                                       core.load_memory (Retro.MemoryType.SAVE_RAM, save_ram_path);
+                       }
+
+                       try {
+                               prepare_save_dir (tmp_save_dir);
+                       } catch (Error e) {
+                               critical ("Couldn't prepare save directory: %s", e.message);
+                       }
+               }
+               catch (RetroError.MODULE_NOT_FOUND e) {
+                       debug ("%s\n", e.message);
+                       throw new RunnerError.UNSUPPORTED_SYSTEM (get_unsupported_system_message ());
+               }
+               catch (FirmwareError.FIRMWARE_NOT_FOUND e) {
+                       debug ("%s\n", e.message);
+                       throw new RunnerError.UNSUPPORTED_SYSTEM (get_unsupported_system_message ());
+               }
+               catch (Error e) {
+                       throw new RunnerError.OTHER (e.message);
+               }
+       }
+
+       public void start () throws Error {
+               assert (core_loaded);
+
+               resume ();
+       }
+
+       public void resume () {
+               if (!core_loaded)
+                       return;
+
+               // Unpause an already running game
+               core.run ();
+               running = true;
+               started = true;
+
+               notify_property ("supports-snapshots");
+       }
+
+       public void pause () {
+               if (!core_loaded)
+                       return;
+
+               if (!running)
+                       return;
+
+               if (!is_error) {
+                       current_state_pixbuf = view.get_pixbuf ();
+                       core.stop ();
+               }
+
+               running = false;
+       }
+
+       private string get_fallback_save_directory_path () throws Error {
+               var uid = game.uid;
+               var core_id_prefix = get_core_id ().replace (".libretro", "");
+
+               return Path.build_filename (Application.get_data_dir (),
+                                           "savestates",
+                                           @"$uid-$core_id_prefix",
+                                           "global");
+       }
+
+       public void stop () {
+               if (!core_loaded)
+                       return;
+
+               if (!is_error && !supports_snapshots) {
+                       try {
+                               var path = get_fallback_save_directory_path ();
+
+                               if (core.get_memory_size (Retro.MemoryType.SAVE_RAM) > 0)
+                                       core.save_memory (Retro.MemoryType.SAVE_RAM,
+                                                         Path.build_filename (path, "save"));
+
+                               var tmp_dir = File.new_for_path (tmp_save_dir);
+                               var dest_dir = File.new_for_path (Path.build_filename (path, "save-dir"));
+                               if (dest_dir.query_exists ())
+                                       FileOperations.delete_files (dest_dir);
+                               FileOperations.copy_contents (tmp_dir, dest_dir);
+                       }
+                       catch (Error e) {
+                               critical ("Failed to create snapshot: %s", e.message);
+                       }
+               }
+
+               game.update_last_played ();
+               deinit ();
+               stopped ();
+       }
+
+       private void deinit () {
+               if (!core_loaded)
+                       return;
+
+               settings.changed["video-filter"].disconnect (on_video_filter_changed);
+
+               input_manager = null;
+
+               if (!is_error && core.is_initiated)
+                       core.stop ();
+
+               core = null;
+
+               if (view != null) {
+                       view.set_core (null);
+                       view = null;
+               }
+
+               _running = false;
+               started = false;
+               core_loaded = false;
+
+               notify_property ("supports-snapshots");
+       }
+
+       public Snapshot? try_create_snapshot (bool is_automatic) {
+               if (!supports_snapshots)
+                       return null;
+
+               if (!is_automatic)
+                       snapshot_created ();
+
+               try {
+                       return snapshot_manager.create_snapshot (is_automatic, save_to_snapshot);
+               }
+               catch (Error e) {
+                       critical ("Failed to create snapshot: %s", e.message);
+
+                       return null;
+               }
+       }
+
+       public void delete_snapshot (Snapshot snapshot) {
+               snapshot_manager.delete_snapshot (snapshot);
+       }
+
+       public void preview_snapshot (Snapshot snapshot) {
+               previewed_snapshot = snapshot;
+
+               var screenshot_path = snapshot.get_screenshot_path ();
+               Gdk.Pixbuf pixbuf = null;
+
+               // Treat errors locally because loading the snapshot screenshot is not
+               // a critical operation
+               try {
+                       pixbuf = new Gdk.Pixbuf.from_file (screenshot_path);
+
+                       var aspect_ratio = snapshot.screenshot_aspect_ratio;
+
+                       if (aspect_ratio != 0)
+                               Retro.pixbuf_set_aspect_ratio (pixbuf, (float) aspect_ratio);
+               }
+               catch (Error e) {
+                       warning ("Couldn't load %s: %s", screenshot_path, e.message);
+               }
+
+               view.set_pixbuf (pixbuf);
+       }
+
+       public void preview_current_state () {
+               view.set_pixbuf (current_state_pixbuf);
+       }
+
+       public void load_previewed_snapshot () throws Error {
+               load_from_snapshot (previewed_snapshot);
+       }
+
+       public Snapshot[] get_snapshots () {
+               if (snapshot_manager == null)
+                       return {};
+
+               return snapshot_manager.get_snapshots ();
+       }
+
+       public InputMode[] get_available_input_modes () {
+               if (input_capabilities == null)
+                       return { InputMode.GAMEPAD };
+
+               InputMode[] modes = {};
+
+               if (input_capabilities.get_allow_gamepad_mode ())
+                       modes += InputMode.GAMEPAD;
+
+               if (input_capabilities.get_allow_keyboard_mode ())
+                       modes += InputMode.KEYBOARD;
+
+               return modes;
+       }
+
+       public virtual bool key_press_event (uint keyval, Gdk.ModifierType state) {
+               return false;
+       }
+
+       public virtual bool gamepad_button_press_event (uint16 button) {
+               return false;
+       }
+
+       private void on_media_number_changed () {
+               if (!core_loaded)
+                       return;
+
+               try {
+                       core.set_current_media (media_set.selected_media_number);
+               }
+               catch (Error e) {
+                       debug (e.message);
+
+                       return;
+               }
+
+               var media_number = media_set.selected_media_number;
+
+               Media media = null;
+               try {
+                       media = media_set.get_selected_media (media_number);
+               }
+               catch (Error e) {
+                       warning (e.message);
+
+                       return;
+               }
+
+               var uris = media.get_uris ();
+               if (uris.length == 0)
+                       return;
+
+               try {
+                       core.set_current_media (media_set.selected_media_number);
+               }
+               catch (Error e) {
+                       debug (e.message);
+
+                       return;
+               }
+       }
+
+       public Retro.Core get_core () {
+               return core;
+       }
+
+       public Game get_game () {
+               return game;
+       }
+
+       private void save_screenshot (string path) throws Error {
+               var pixbuf = current_state_pixbuf;
+               if (pixbuf == null)
+                       return;
+
+               var now = new GLib.DateTime.now_local ();
+               var creation_time = now.to_string ();
+               var game_title = game.name;
+               var platform = game.platform;
+               var platform_name = platform.get_name ();
+               var platform_id = platform.get_id ();
+               if (platform_name == null) {
+                       critical ("Unknown name for platform %s", platform_id);
+                       platform_name = _("Unknown platform");
+               }
+
+               // See http://www.libpng.org/pub/png/spec/iso/index-object.html#11textinfo
+               // for description of used keys. "Game Title" and "Platform" are
+               // non-standard fields as allowed by PNG specification.
+               pixbuf.save (path, "png",
+                            "tEXt::Software", "GNOME Games",
+                            "tEXt::Title", @"Screenshot of $game_title on $platform_name",
+                            "tEXt::Creation Time", creation_time.to_string (),
+                            "tEXt::Game Title", game_title,
+                            "tEXt::Platform", platform_name,
+                            null);
+       }
+
+       private void load_save_ram (string save_ram_path) throws Error {
+               if (!FileUtils.test (save_ram_path, FileTest.EXISTS))
+                       return;
+
+               if (core.get_memory_size (Retro.MemoryType.SAVE_RAM) == 0)
+                       return;
+
+               core.load_memory (Retro.MemoryType.SAVE_RAM, save_ram_path);
+       }
+
+       protected virtual void save_to_snapshot (Snapshot snapshot) throws Error {
+               if (core.get_memory_size (Retro.MemoryType.SAVE_RAM) > 0)
+                       core.save_memory (Retro.MemoryType.SAVE_RAM,
+                                         snapshot.get_save_ram_path ());
+
+               var tmp_dir = File.new_for_path (tmp_save_dir);
+               var dest_dir = File.new_for_path (snapshot.get_save_directory_path ());
+               FileOperations.copy_contents (tmp_dir, dest_dir);
+
+               if (media_set.get_size () > 1)
+                       snapshot.set_media_data (media_set);
+
+               core.save_state (snapshot.get_snapshot_path ());
+               save_screenshot (snapshot.get_screenshot_path ());
+               snapshot.screenshot_aspect_ratio = Retro.pixbuf_get_aspect_ratio (current_state_pixbuf);
+       }
+
+       protected virtual void load_from_snapshot (Snapshot snapshot) throws Error {
+               File tmp_dir = File.new_for_path (tmp_save_dir);
+
+               if (tmp_dir.query_exists ())
+                       FileOperations.delete_files (tmp_dir);
+               tmp_dir.make_directory ();
+
+               snapshot.copy_save_dir_to (tmp_save_dir);
+
+               load_save_ram (snapshot.get_save_ram_path ());
+               core.load_state (snapshot.get_snapshot_path ());
+
+               if (snapshot.has_media_data ())
+                       media_set.selected_media_number = snapshot.get_media_data ();
+       }
+
+       protected virtual void reset_with_snapshot (Snapshot last_snapshot) throws Error {
+               load_save_ram (last_snapshot.get_save_ram_path ());
+
+               if (last_snapshot.has_media_data ())
+                       media_set.selected_media_number = last_snapshot.get_media_data ();
+       }
+
+       protected virtual void prepare_save_dir (string path) throws Error {
+       }
 }
diff --git a/src/meson.build b/src/meson.build
index 0d56fd5a..e833d5bd 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -104,7 +104,6 @@ vala_sources = [
   'retro/retro-gamepad.vala',
   'retro/retro-input-manager.vala',
   'retro/retro-options.vala',
-  'retro/retro-runner.vala',
   'retro/retro-runner-factory.vala',
   'retro/retro-simple-game-uri-adapter.vala',
   'retro/retro-simple-type.vala',
diff --git a/src/retro/retro-runner-factory.vala b/src/retro/retro-runner-factory.vala
index 296ddc5f..6e0fae6b 100644
--- a/src/retro/retro-runner-factory.vala
+++ b/src/retro/retro-runner-factory.vala
@@ -14,6 +14,6 @@ public class Games.RetroRunnerFactory : Object, RunnerFactory {
        public Runner? create_runner (Game game) throws Error {
                var core_source = new RetroCoreSource (platform);
 
-               return new RetroRunner (game, core_source);
+               return new Runner (game, core_source);
        }
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]