[gnome-games] retro-runner: Limit the number of automatic savestates



commit cd309d15d4ed807784192caf04c233fb877030c3
Author: Yetizone <andreii lisita gmail com>
Date:   Wed Jul 3 21:17:44 2019 +0300

    retro-runner: Limit the number of automatic savestates

 src/command/command-runner.vala |  10 +-
 src/core/runner.vala            |   4 +-
 src/dummy/dummy-runner.vala     |  10 +-
 src/retro/retro-runner.vala     | 237 ++++++++++++++++++++++------------------
 src/ui/display-view.vala        |  23 ++--
 5 files changed, 165 insertions(+), 119 deletions(-)
---
diff --git a/src/command/command-runner.vala b/src/command/command-runner.vala
index 43dcca95..c20664ce 100644
--- a/src/command/command-runner.vala
+++ b/src/command/command-runner.vala
@@ -82,7 +82,15 @@ public class Games.CommandRunner : Object, Runner {
        public void stop () {
        }
 
-       public void attempt_create_savestate () {
+       public bool try_create_savestate (bool is_automatic) {
+               return false;
+       }
+
+       public void load_savestate (Savestate savestate) {
+       }
+
+       public Savestate[] get_savestates () {
+               return {};
        }
 
        public InputMode[] get_available_input_modes () {
diff --git a/src/core/runner.vala b/src/core/runner.vala
index f4b9bd68..f87828f8 100644
--- a/src/core/runner.vala
+++ b/src/core/runner.vala
@@ -16,7 +16,9 @@ public interface Games.Runner : Object {
        public abstract void resume () throws Error;
        public abstract void pause ();
        public abstract void stop ();
-       public abstract void attempt_create_savestate () throws Error;
+       public abstract bool try_create_savestate (bool is_automatic);
+       public abstract void load_savestate (Savestate savestate) throws Error;
+       public abstract Savestate[] get_savestates ();
 
        public abstract InputMode[] get_available_input_modes ();
        public abstract bool key_press_event (Gdk.EventKey event);
diff --git a/src/dummy/dummy-runner.vala b/src/dummy/dummy-runner.vala
index 595f3583..8434e2ae 100644
--- a/src/dummy/dummy-runner.vala
+++ b/src/dummy/dummy-runner.vala
@@ -48,7 +48,15 @@ private class Games.DummyRunner : Object, Runner {
        public void stop () {
        }
 
-       public void attempt_create_savestate () {
+       public bool try_create_savestate (bool is_automatic) {
+               return false;
+       }
+
+       public void load_savestate (Savestate savestate) {
+       }
+
+       public Savestate[] get_savestates () {
+               return {};
        }
 
        public InputMode[] get_available_input_modes () {
diff --git a/src/retro/retro-runner.vala b/src/retro/retro-runner.vala
index ad5bc925..4ddba679 100644
--- a/src/retro/retro-runner.vala
+++ b/src/retro/retro-runner.vala
@@ -94,12 +94,9 @@ public class Games.RetroRunner : Object, Runner {
        // It is called by the DisplayView to check if a runner can be used
        // This method must be called before other methods/properties
        public bool try_init_phase_one (out string error_message) {
-               // Step 1) Check for the two RetroErrors -------------------------------
-               // FIXME: Write this properly using RetroCoreManager
-               /*
                try {
-                       media_set.selected_media_number = 0;
-                       //init ();
+                       init_phase_one ();
+               // TODO: Check for the two RetroErrors using RetroCoreManager
                }
                catch (RetroError.MODULE_NOT_FOUND e) {
                        debug (e.message);
@@ -113,50 +110,40 @@ public class Games.RetroRunner : Object, Runner {
 
                        return false;
                }
-               */
-
-               // Step 2) Load the game's savestates ----------------------------------
-               try {
-                       core_source.get_module_path (); // FIXME: Hack needed to get core_id
-                       string core_id = null;
-
-                       if (core_descriptor != null) {
-                               core_id = core_descriptor.get_id ();
-                       }
-                       else {
-                               core_id = core_source.get_core_id ();
-                       }
-
-                       game_savestates = Savestate.get_game_savestates (uid, core_id);
-                       if (game_savestates.length != 0)
-                               latest_savestate = game_savestates[0];
-               }
                catch (Error e) {
+                       debug (e.message);
                        error_message = e.message;
+
                        return false;
                }
 
-               // Step 3) Init the CoreView -------------------------------------------
+               // Nothing went wrong
+               error_message = "";
+               return true;
+       }
+
+       private string get_core_id () throws Error {
+               if (core_descriptor != null)
+                       return core_descriptor.get_id ();
+               else
+                       return core_source.get_core_id ();
+       }
+
+       private void init_phase_one () throws Error {
+               // Step 1) Load the game's savestates ----------------------------------
+               game_savestates = Savestate.get_game_savestates (uid, get_core_id ());
+               if (game_savestates.length != 0)
+                       latest_savestate = game_savestates[0];
+
+               // Step 2) Init the CoreView -------------------------------------------
                // This is done here such that get_display() won't return null
                view = new Retro.CoreView ();
                settings.changed["video-filter"].connect (on_video_filter_changed);
                on_video_filter_changed ();
 
-               // Step 4) Display the screenshot of the latest_savestate --------------
-               // FIXME: This does not work currently, but perhaps we can fix it
-               // somehow in retro-gtk ? We should be able to render a picture on the
-               // screen without having to boot the core itself
-               try {
-                       load_screenshot ();
-               }
-               catch (Error e) {
-                       error_message = e.message;
-                       return false;
-               }
-
-               // Nothing went wrong
-               error_message = "";
-               return true;
+               // Step 3) Display the screenshot of the latest_savestate --------------
+               // FIXME: This does not work currently
+               load_screenshot ();
        }
 
        public Gtk.Widget get_display () {
@@ -167,6 +154,33 @@ public class Games.RetroRunner : Object, Runner {
                return null;
        }
 
+       public void load_savestate (Savestate savestate) throws Error {
+               stop ();
+
+               tmp_live_savestate = savestate.clone_in_tmp ();
+               instantiate_core (tmp_live_savestate.get_save_directory_path ());
+
+               core.save_directory = tmp_live_savestate.get_save_directory_path ();
+               load_save_ram (savestate.get_save_ram_path ());
+               core.set_state (savestate.get_snapshot_data ());
+
+               if (savestate.has_media_data ())
+                       media_set.selected_media_number = savestate.get_media_data ();
+
+               loop.start ();
+
+               is_ready = true;
+               running = true;
+       }
+
+       public Savestate[] get_savestates () {
+               if (game_savestates == null) {
+                       critical ("RetroRunner hasn't loaded savestates. Call try_init_phase_one()");
+               }
+
+               return game_savestates;
+       }
+
        public void start () throws Error {
                if (latest_savestate != null && latest_savestate.has_media_data ())
                        media_set.selected_media_number = latest_savestate.get_media_data ();
@@ -177,7 +191,7 @@ public class Games.RetroRunner : Object, Runner {
                        else
                                tmp_live_savestate = Savestate.create_empty_in_tmp ();
 
-                       init_phase_two (tmp_live_savestate.get_save_directory_path ());
+                       instantiate_core (tmp_live_savestate.get_save_directory_path ());
                }
 
                if (!is_ready) {
@@ -192,34 +206,20 @@ public class Games.RetroRunner : Object, Runner {
                running = true;
        }
 
-       public void resume () throws Error {
-               if (is_ready) {
-                       // In this case, the effect is to simply "unpause" an already running game
-                       loop.start ();
-               }
-               else {
-                       // In this case, the effect is to load the data from a savestate and
-                       // run the game
-                       if (!is_initialized) {
-                               tmp_live_savestate = latest_savestate.clone_in_tmp ();
-                               init_phase_two (tmp_live_savestate.get_save_directory_path ());
-                       }
-
-                       loop.stop ();
-
-                       // TODO: This will become "load_data_from_arbitrary_savestate ()"
-                       load_data_from_latest_savestate ();
-
-                       loop.start ();
+       public void resume () {
+               if (!is_ready) {
+                       critical ("RetroRunner.resume() cannot be called if the game isn't playing");
+                       return;
                }
 
-               // In both cases the game is now running
+               // Unpause an already running game
+               loop.start ();
                running = true;
        }
 
-       // init_phase_two is used to setup the core, which needs to have the savestate
+       // instantiate_core is used to setup the core, which needs to have a savestate
        // in /tmp created and ready
-       private void init_phase_two (string core_save_directory_path) throws Error {
+       private void instantiate_core (string core_save_directory_path) throws Error {
                prepare_core (core_save_directory_path);
 
                var present_analog_sticks = input_capabilities == null || 
input_capabilities.get_allow_analog_gamepads ();
@@ -237,19 +237,6 @@ public class Games.RetroRunner : Object, Runner {
                is_initialized = true;
        }
 
-       private void load_data_from_latest_savestate () throws Error {
-               // TODO: This method assumes that there exists at least a savestate
-               // [Yeti]: Perhaps we should bug-proof this using an Assert ?
-               load_save_ram (latest_savestate.get_save_ram_path ());
-               core.reset ();
-               core.set_state (latest_savestate.get_snapshot_data ());
-
-               if (latest_savestate.has_media_data ())
-                       media_set.selected_media_number = latest_savestate.get_media_data ();
-
-               is_ready = true;
-       }
-
        private void deinit () {
                if (!is_initialized)
                        return;
@@ -340,16 +327,7 @@ public class Games.RetroRunner : Object, Runner {
                        return;
 
                pause ();
-
-               try {
-                       attempt_create_savestate ();
-               }
-               catch (Error e) {
-                       warning (e.message);
-               }
-
                deinit ();
-
                stopped ();
        }
 
@@ -415,44 +393,72 @@ public class Games.RetroRunner : Object, Runner {
                var data_dir_path = Application.get_data_dir ();
                var savestates_dir_path = Path.build_filename (data_dir_path, "savestates");
                var uid = uid.get_uid ();
+               var core_id = get_core_id ();
+               var core_id_prefix = core_id.replace (".libretro", "");
 
-               string core_id = null;
+               return Path.build_filename (savestates_dir_path, uid + "-" + core_id_prefix);
+       }
 
-               if (core_descriptor != null) {
-                       core_id = core_descriptor.get_id ();
-               }
-               else {
-                       core_id = core_source.get_core_id ();
+       // Returns true/false to let the caller know if the savestate was created successfully
+       // Currently the caller is the DisplayView
+       // In the future we might want to throw Errors from here in case there is
+       // something that can be done, but right now there's nothing we can do if
+       // savestate creation fails except warn the user of unsaved progress via the
+       // QuitDialog in the DisplayView
+       public bool try_create_savestate (bool is_automatic) {
+               if (!core.get_can_access_state ()) // Check if the core can support savestates
+                       return false;
+
+               try {
+                       create_savestate (is_automatic);
                }
+               catch (Error e) {
+                       critical ("RetroRunner failed to create savestate: %s", e.message);
 
-               var core_id_prefix = core_id.replace (".libretro", "");
+                       return false;
+               }
 
-               return Path.build_filename (savestates_dir_path, uid + "-" + core_id_prefix);
+               return true; // Savestate created successfully
        }
 
-       public void attempt_create_savestate () throws Error {
-               if (!core.get_can_access_state ()) // Check if the core can support savestates
-                       return;
+       private void create_savestate (bool is_automatic) throws Error {
+               // Decide if there are too many automatic savestates and delete the
+               // first one if so
+               var nr_automatic_savestates = count_automatic_savestates ();
+               if (is_automatic) {
+                       var max_nr_automatic_savestates = 5;
 
-               if (!should_save)
-                       return;
+                       if (nr_automatic_savestates >= max_nr_automatic_savestates)
+                               delete_first_automatic_savestate ();
+               }
 
+               // Populate the savestate in tmp with data from the current state of the game
                store_save_ram_in_tmp ();
-               // TODO: Also save the media data in tmp_savestate
+
+               if (media_set.get_size () > 1)
+                       tmp_live_savestate.set_media_data (media_set);
+
                tmp_live_savestate.set_snapshot_data (core.get_state ());
                save_screenshot_in_tmp ();
 
+               // Populate the metadata file
+               var now_time = new DateTime.now ();
+               var platform_prefix = platform.get_uid_prefix ();
+               if (is_automatic)
+                       tmp_live_savestate.set_metadata_automatic (now_time, platform_prefix, get_core_id ());
+               else {
+                       var nr_manual_savestates = game_savestates.length - nr_automatic_savestates;
+                       var savestate_name = _("New savestate %d").printf (nr_manual_savestates + 1);
+
+                       tmp_live_savestate.set_metadata_manual (savestate_name, now_time, platform_prefix, 
get_core_id ());
+               }
+
                // Save the tmp_live_savestate into the game savestates directory
                var game_savestates_dir_path = get_game_savestates_dir_path ();
                tmp_live_savestate.save_in (game_savestates_dir_path);
                should_save = false;
 
-               /*
-               store_save_ram (new_savestate_dir);
-
-               if (media_set.get_size () > 1)
-                       save_media_data (new_savestate_dir);
-               */
+               // FIXME: The game_savestates array should be updated somehow here
        }
 
        private string get_options_path () throws Error {
@@ -556,5 +562,28 @@ public class Games.RetroRunner : Object, Runner {
        public Retro.Core get_core () {
                return core;
        }
+
+       private int count_automatic_savestates () {
+               int counter = 0;
+
+               foreach (var savestate in game_savestates) {
+                       if (savestate.is_automatic ())
+                               counter++;
+               }
+
+               return counter;
+       }
+
+       private void delete_first_automatic_savestate () {
+               // Delete the first automatic savestate (assume they are sorted
+               // by creation date for now)
+
+               foreach (var savestate in game_savestates) {
+                       if (savestate.is_automatic ()) {
+                               savestate.delete_from_disk ();
+                               break;
+                       }
+               }
+       }
 }
 
diff --git a/src/ui/display-view.vala b/src/ui/display-view.vala
index 65812275..9d2ca269 100644
--- a/src/ui/display-view.vala
+++ b/src/ui/display-view.vala
@@ -269,10 +269,14 @@ private class Games.DisplayView : Object, UiView {
                return (Gtk.ResponseType) response;
        }
 
-       private bool try_run_with_cancellable (Runner runner, bool resume, Cancellable cancellable) {
+       private bool try_run_with_cancellable (Runner runner, bool use_latest_savestate, Cancellable 
cancellable) {
                try {
-                       if (resume)
-                               box.runner.resume ();
+                       if (use_latest_savestate) {
+                               var savestates = box.runner.get_savestates ();
+                               var latest_savestate = savestates[0];
+
+                               box.runner.load_savestate (latest_savestate);
+                       }
                        else
                                runner.start ();
 
@@ -344,19 +348,14 @@ private class Games.DisplayView : Object, UiView {
 
                box.runner.pause ();
 
-               try {
-                       box.runner.attempt_create_savestate ();
-               }
-               catch (Error e) {
-                       warning (e.message);
-               }
-
-               if (box.runner.can_quit_safely) {
+               if (box.runner.try_create_savestate (true)) {
+                       // Progress saved => can quit game safely
                        box.runner.stop ();
-
                        return true;
                }
 
+               // Failed to save progress => warn the user of unsaved progress
+               // via the QuitDialog
                if (quit_dialog != null)
                        return false;
 


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