Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] Support restore emulator state #6270

Merged
merged 6 commits into from
Jan 5, 2018

Conversation

mahdihijazi
Copy link
Contributor

@mahdihijazi mahdihijazi commented Dec 25, 2017

Support restore emulator state after emulation screen is killed

This is more tricky that what I expected and I need help from the emulator core devs. Really sorry for the huge text below, putting it here easier to go over all of these on the chat :)

Problem I am trying to solve
if you leave the app by opening another app or pressing the home button, the emulator screen might be killed and we need to restore the state if you come back to Dolphin again.

The proposed solution
While leaving the app we are going to save the state on special slot (8) for this situation then when you come back we will decide if we need to load the temporary state we saved or not.

Problems I need help with
1. in Android there is no way to tell if the screen got killed because you switched the app rotation or because of memory limitation while your app in the background and this makes the situation harder because during rotation we can theoretically pause/resume the emulation but in the other case since the OS decided to kill the screen we might not have the emulation state to resume the emulation so we have to load the temporary saved sate.

But, in order to do that I have to stop the emulation during onDestroy phase because running the emulation while previous emulation was not stopped caused an error "broken pipe". Stoping the emulation caused the pause/resume ability during rotation changed to stop working and now we are loading the temp saved state even in the case of rotating change.

If the native library can inform the app that it still has the previous emulation state and can continue, the app will be able to distinguish between the two cases and use the saved state only when needed.

Or
I wonder if the app rotation is important for Dolphin, can't we just look the emulation to landscape on mobile devices? I think PPSSPP is doing that.

2. Loading the state should only happen after the game start running, there is no way the app can tell if the game start running, a hack is done inside surfaceChanged() that load the state after 2 seconds of running the game.

3. Keeping those temporary states might not be a good thing to do, if we can have a way to delete states that would make it better.

In this PR we have a working solution that can restart the state during screen rotation or after screen been killed. The downside is the during the rotation now you will loose the EFP copies since we are loading the state and it will stutter because of the 2 seconds delay hack.

EDIT: All cases are testing and hopefully are working fine.

The final status for each case:

  1. Device rotation: working as expected using pause/resume emulation.
  2. Leaving the emulation screen but the screen kept a live: Working fine using pause/resume emulation.
  3. Leaving the emulation screen and the screen being killed but the native code still has the emulation data and can resume emulation again: Working fine using pause/resume emulation.
  4. Leaving the emulation screen and the screen being killed but the native code doesn't have the emulation data and can't resume the emulation: Working using temporary saved states to resume emulation.

@mahdihijazi
Copy link
Contributor Author

To test the screen been killed case, go to developer options then check the option Don't keep activities

Then start any game, press home, press recent apps and open Dolphin again

@JosJuice
Copy link
Member

Here are a few (untested) native methods you can use: master...JosJuice:restore-state-helpers

IsRunning should be fairly self-explanatory. If it returns true, there's no need to load a savestate.

I suggest that you use SaveStateAs and LoadStateAs with a fixed path in the app-local storage, like getFilesDir() + File.separator + "suspend.sav". This avoids conflicts with any savestates the user might want to make manually in slot 8, and you'll be able to delete the file with standard Java methods.


A hack that hardcodes the duration 2 seconds isn't mergeable in my opinion. I can vaguely recall that someone started working on making the core be able to load a savestate on its own when booting... I don't remember the details about that (or even if it's true that someone started working on it), but we would need something like that.

@JosJuice
Copy link
Member

Huh, apparently the core already has a way to load savestates at boot (Core::SetStateFileName)! But the way it's designed right now makes it impossible to use for anything other than the Movie subsystem. I'll look into improving that.

@mahdihijazi
Copy link
Contributor Author

@JosJuice I am a bit confused about this isRunning() method, will it return true after I stop the emulation? because otherwise it will not be helpful since I have to stop the emulation as soon as the screen is being killed for any reason, but I need to know if the native code still has the ability to resume emulation after the distraction of the screen, can that method help?

@JosJuice
Copy link
Member

If you call StopEmulation, IsRunning will become false and it will be impossible for the native code to resume emulation. But if you call PauseEmulation, IsRunning will remain true and the native code will be able to resume emulation.

@JosJuice
Copy link
Member

JosJuice commented Dec 25, 2017

I don't see why we would have to stop emulation when the screen is killed. In master, it's possible to rotate the screen without stopping emulation.

@mahdihijazi
Copy link
Contributor Author

because of the other case, if the OS decided to kill the activity I will have to run the emulation form the start, but I managed to find a way to distinguish between those 2 cases at last :)

for now I think I don't need IsRunning()

btw thanks a lot for your quick help :)

@mahdihijazi mahdihijazi force-pushed the suppport_restore_state branch from 54ad664 to 7c0f560 Compare December 25, 2017 20:20
@JosJuice
Copy link
Member

JosJuice commented Dec 25, 2017

Yes, you'll have to run the emulation from the start if the system kills the Dolphin process, but that doesn't mean that you have to stop emulation manually in advance. If the Dolphin process is killed, the emulation will automatically be in the "stopped" state the next time the process starts up, and IsRunning will return false.

Or do you mean that something goes wrong when the emulation was "automatically stopped" like that?

@mahdihijazi
Copy link
Contributor Author

I am afraid there is a situation where the screen is killed but the native code still there and actually has the content and can resume emulation, because I tried to start the emulation after screen being killed, it doesn't work unless I stop the emulation manually, can you explain why?

@JosJuice
Copy link
Member

JosJuice commented Dec 25, 2017

I suppose that Android is killing the activity without killing the whole process. (Maybe that's how the "Don't keep activities" option works?) The native code isn't bound to any activity, so killing activities has no effect on it.

IsRunning will reliably tell you whether the native code can resume emulation in situations like these, so I suggest that you use it instead of the current solution.

@mahdihijazi mahdihijazi force-pushed the suppport_restore_state branch from 7c0f560 to 96afa52 Compare December 25, 2017 20:52
@mahdihijazi
Copy link
Contributor Author

@JosJuice I cherry-picked the helper methods commit from your repo and ended up in a mess here, I will fix that later

@JosJuice
Copy link
Member

JosJuice commented Dec 25, 2017

Please don't store the savestate in the cache directory, otherwise it might get deleted automatically even though we haven't finished using it yet. The cache directory is intended for data that can be reobtained if needed.

If you want Dolphin to use as little storage as possible, my suggestion is to make EmulationActivity's onCreate always delete the savestate file (but not before the the call to restoreState, of course).

EDIT: No wait, restoreState isn't the method that loads the savestate... :/ So ignore my suggestion about where to put the code. But I do think that the right way to go is for Dolphin to delete savestate files when it knows that they aren't needed anymore.

@mahdihijazi mahdihijazi force-pushed the suppport_restore_state branch from 96afa52 to cbd469e Compare December 26, 2017 00:52
@leoetlino
Copy link
Member

FYI, #6271 has now been merged :)

@JosJuice
Copy link
Member

JosJuice commented Dec 26, 2017

I've pushed two new commits to the same branch as before. The first one adds the ability to load a savestate at boot, as made possible by PR #6271. The second one adds the ability to make SaveState/SaveStateAs wait with returning until the savestate has been written to disk. It's important that you use that ability in this PR so that Android won't have a chance to kill the process while it's working on writing a savestate to disk. (In master, SaveState/SaveStateAs returns after it's safe to resume emulation but before the savestate gets compressed and written to disk.)

Like before, this is untested. I've only confirmed that it compiles.

@mahdihijazi mahdihijazi force-pushed the suppport_restore_state branch from cbd469e to f41c782 Compare December 27, 2017 20:50
@mahdihijazi mahdihijazi force-pushed the suppport_restore_state branch from f41c782 to bc7cbb0 Compare December 27, 2017 21:02
Copy link
Member

@JosJuice JosJuice left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This round of pointing out things to fix is probably the last round unless I've missed something :)


private String getTemporaryStateFilePath()
{
return getContext().getCacheDir() + File.separator + "temp.sav";

This comment was marked as off-topic.

This comment was marked as off-topic.

}
else
{
loadPreviousTemporaryState = true;

This comment was marked as off-topic.

This comment was marked as off-topic.

This comment was marked as off-topic.

This comment was marked as off-topic.

if (!isManualExit)
{
mEmulationFragment.saveTemporaryState();
}

This comment was marked as off-topic.

This comment was marked as off-topic.

Log.debug("[EmulationFragment] activity resumed or fresh start");
// activity resumed without being killed or this is the first run
deleteFile(temporaryStatePath);
}

This comment was marked as off-topic.

JosJuice and others added 2 commits December 28, 2017 23:15
…s killed

1) Most of the times the native heap is kept around even after the activity
is killed, we can ask the native code if it is still running and resume
the emulation if that is the case.

2) In case the native heap is freed and the emulation can't resume we used
a temporary state to load on the game boot.

I couldn't find a way to test this, if you want to test this schnario,
add this block to EmulationFragment.

public void onDestroy()
{
	stopEmulation();
	super.onDestroy();
}

onDestroy is only called if the acivity killed by the OS and not be rotation
change whihch in this case will make sure to kill the emulation and start
again when the activiy is re-created.
@mahdihijazi mahdihijazi force-pushed the suppport_restore_state branch from bc7cbb0 to 136e835 Compare December 28, 2017 22:51
@mahdihijazi mahdihijazi changed the title WIP: [Android] Support restore emulator state [Android] Support restore emulator state Dec 28, 2017
@mahdihijazi
Copy link
Contributor Author

@JosJuice ready for the final review and hopefully to be merged. Thanks a lot for your help!

@degasus degasus merged commit e705d43 into dolphin-emu:master Jan 5, 2018
@degasus
Copy link
Member

degasus commented Jan 5, 2018

Device rotation: working as expected using pause/resume emulation.

Do we need to pause/resume here at all?

@mahdihijazi
Copy link
Contributor Author

mahdihijazi commented Jan 5, 2018

@degasus during the onPause I can't tell if this is orientation change or you are leaving the emulation to another app, not pausing means the emulation will keep running for the later case

@JosJuice
Copy link
Member

JosJuice commented Jan 5, 2018

Can't you use isChangingConfigurations() for that?

@mahdihijazi
Copy link
Contributor Author

I didn't know that method exist!

theoretically I can do that but I have to hack again emulationState.run() to pick another flow if this is an orientation change, what is the benefit of not pausing the emulation? I am not sure if it worth all the checks/flag that need to be added to support this.

@JosJuice
Copy link
Member

JosJuice commented Jan 6, 2018

I don't know how worth not pausing is, but it would be nice if we could skip creating a savestate if we know that the activity is just changing configuration.

@mahdihijazi
Copy link
Contributor Author

@JosJuice you are right, I will make this enhancement soon

@mahdihijazi
Copy link
Contributor Author

@JosJuice just checked the code again, we are creating the temporary saves state inside onSaveInstance which is not called when the configuration is changed, so nothing to improve here!

@JosJuice
Copy link
Member

Why wouldn't onSaveInstanceState be called when changing configuration? If it isn't called, I don't see how "regular" apps wouldn't lose data such as form contents when rotating the screen.

I would've taken a look at this myself to verify what actually happens, but my only working Android device stopped working yesterday :(

@mahdihijazi
Copy link
Contributor Author

I don't know what I was thinking when I write that comment. I am glad you are following with me on the Android changes, otherwise it would be a mess!

@mahdihijazi mahdihijazi deleted the suppport_restore_state branch July 20, 2018 20:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants