committed by
bunnei
2 changed files with 348 additions and 375 deletions
-
375src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.java
-
348src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@ -1,375 +0,0 @@ |
|||
package org.yuzu.yuzu_emu.fragments; |
|||
|
|||
import android.content.Context; |
|||
import android.content.IntentFilter; |
|||
import android.content.SharedPreferences; |
|||
import android.graphics.Color; |
|||
import android.os.Bundle; |
|||
import android.os.Handler; |
|||
import android.preference.PreferenceManager; |
|||
import android.view.Choreographer; |
|||
import android.view.LayoutInflater; |
|||
import android.view.Surface; |
|||
import android.view.SurfaceHolder; |
|||
import android.view.SurfaceView; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.Button; |
|||
import android.widget.TextView; |
|||
import android.widget.Toast; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.fragment.app.Fragment; |
|||
import androidx.localbroadcastmanager.content.LocalBroadcastManager; |
|||
|
|||
import org.yuzu.yuzu_emu.NativeLibrary; |
|||
import org.yuzu.yuzu_emu.R; |
|||
import org.yuzu.yuzu_emu.activities.EmulationActivity; |
|||
import org.yuzu.yuzu_emu.overlay.InputOverlay; |
|||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization; |
|||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization.DirectoryInitializationState; |
|||
import org.yuzu.yuzu_emu.utils.DirectoryStateReceiver; |
|||
import org.yuzu.yuzu_emu.utils.Log; |
|||
|
|||
public final class EmulationFragment extends Fragment implements SurfaceHolder.Callback, Choreographer.FrameCallback { |
|||
private static final String KEY_GAMEPATH = "gamepath"; |
|||
|
|||
private static final Handler perfStatsUpdateHandler = new Handler(); |
|||
|
|||
private SharedPreferences mPreferences; |
|||
|
|||
private InputOverlay mInputOverlay; |
|||
|
|||
private EmulationState mEmulationState; |
|||
|
|||
private DirectoryStateReceiver directoryStateReceiver; |
|||
|
|||
private EmulationActivity activity; |
|||
|
|||
private TextView mPerfStats; |
|||
|
|||
private Runnable perfStatsUpdater; |
|||
|
|||
public static EmulationFragment newInstance(String gamePath) { |
|||
Bundle args = new Bundle(); |
|||
args.putString(KEY_GAMEPATH, gamePath); |
|||
|
|||
EmulationFragment fragment = new EmulationFragment(); |
|||
fragment.setArguments(args); |
|||
return fragment; |
|||
} |
|||
|
|||
@Override |
|||
public void onAttach(@NonNull Context context) { |
|||
super.onAttach(context); |
|||
|
|||
if (context instanceof EmulationActivity) { |
|||
activity = (EmulationActivity) context; |
|||
NativeLibrary.setEmulationActivity((EmulationActivity) context); |
|||
} else { |
|||
throw new IllegalStateException("EmulationFragment must have EmulationActivity parent"); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Initialize anything that doesn't depend on the layout / views in here. |
|||
*/ |
|||
@Override |
|||
public void onCreate(Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
|
|||
// So this fragment doesn't restart on configuration changes; i.e. rotation. |
|||
setRetainInstance(true); |
|||
|
|||
mPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity()); |
|||
|
|||
String gamePath = getArguments().getString(KEY_GAMEPATH); |
|||
mEmulationState = new EmulationState(gamePath); |
|||
} |
|||
|
|||
/** |
|||
* Initialize the UI and start emulation in here. |
|||
*/ |
|||
@Override |
|||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { |
|||
View contents = inflater.inflate(R.layout.fragment_emulation, container, false); |
|||
|
|||
SurfaceView surfaceView = contents.findViewById(R.id.surface_emulation); |
|||
surfaceView.getHolder().addCallback(this); |
|||
|
|||
mInputOverlay = contents.findViewById(R.id.surface_input_overlay); |
|||
mPerfStats = contents.findViewById(R.id.show_fps_text); |
|||
mPerfStats.setTextColor(Color.YELLOW); |
|||
|
|||
Button doneButton = contents.findViewById(R.id.done_control_config); |
|||
if (doneButton != null) { |
|||
doneButton.setOnClickListener(v -> stopConfiguringControls()); |
|||
} |
|||
|
|||
// Setup overlay. |
|||
resetInputOverlay(); |
|||
updateShowFpsOverlay(); |
|||
|
|||
// The new Surface created here will get passed to the native code via onSurfaceChanged. |
|||
return contents; |
|||
} |
|||
|
|||
@Override |
|||
public void onResume() { |
|||
super.onResume(); |
|||
Choreographer.getInstance().postFrameCallback(this); |
|||
if (DirectoryInitialization.areDirectoriesReady()) { |
|||
mEmulationState.run(activity.isActivityRecreated()); |
|||
} else { |
|||
setupDirectoriesThenStartEmulation(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onPause() { |
|||
if (directoryStateReceiver != null) { |
|||
LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(directoryStateReceiver); |
|||
directoryStateReceiver = null; |
|||
} |
|||
|
|||
if (mEmulationState.isRunning()) { |
|||
mEmulationState.pause(); |
|||
} |
|||
|
|||
Choreographer.getInstance().removeFrameCallback(this); |
|||
super.onPause(); |
|||
} |
|||
|
|||
@Override |
|||
public void onDetach() { |
|||
NativeLibrary.clearEmulationActivity(); |
|||
super.onDetach(); |
|||
} |
|||
|
|||
private void setupDirectoriesThenStartEmulation() { |
|||
IntentFilter statusIntentFilter = new IntentFilter( |
|||
DirectoryInitialization.BROADCAST_ACTION); |
|||
|
|||
directoryStateReceiver = |
|||
new DirectoryStateReceiver(directoryInitializationState -> |
|||
{ |
|||
if (directoryInitializationState == |
|||
DirectoryInitializationState.YUZU_DIRECTORIES_INITIALIZED) { |
|||
mEmulationState.run(activity.isActivityRecreated()); |
|||
} else if (directoryInitializationState == |
|||
DirectoryInitializationState.CANT_FIND_EXTERNAL_STORAGE) { |
|||
Toast.makeText(getContext(), R.string.external_storage_not_mounted, |
|||
Toast.LENGTH_SHORT) |
|||
.show(); |
|||
} |
|||
}); |
|||
|
|||
// Registers the DirectoryStateReceiver and its intent filters |
|||
LocalBroadcastManager.getInstance(getActivity()).registerReceiver( |
|||
directoryStateReceiver, |
|||
statusIntentFilter); |
|||
DirectoryInitialization.start(getActivity()); |
|||
} |
|||
|
|||
public void refreshInputOverlay() { |
|||
mInputOverlay.refreshControls(); |
|||
} |
|||
|
|||
public void resetInputOverlay() { |
|||
// Reset button scale |
|||
SharedPreferences.Editor editor = mPreferences.edit(); |
|||
editor.putInt("controlScale", 50); |
|||
editor.apply(); |
|||
|
|||
mInputOverlay.resetButtonPlacement(); |
|||
} |
|||
|
|||
public void updateShowFpsOverlay() { |
|||
if (true) { |
|||
final int SYSTEM_FPS = 0; |
|||
final int FPS = 1; |
|||
final int FRAMETIME = 2; |
|||
final int SPEED = 3; |
|||
|
|||
perfStatsUpdater = () -> |
|||
{ |
|||
final double[] perfStats = NativeLibrary.GetPerfStats(); |
|||
if (perfStats[FPS] > 0) { |
|||
mPerfStats.setText(String.format("FPS: %.1f", perfStats[FPS])); |
|||
} |
|||
|
|||
perfStatsUpdateHandler.postDelayed(perfStatsUpdater, 100); |
|||
}; |
|||
perfStatsUpdateHandler.post(perfStatsUpdater); |
|||
|
|||
mPerfStats.setVisibility(View.VISIBLE); |
|||
} else { |
|||
if (perfStatsUpdater != null) { |
|||
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater); |
|||
} |
|||
|
|||
mPerfStats.setVisibility(View.GONE); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void surfaceCreated(SurfaceHolder holder) { |
|||
// We purposely don't do anything here. |
|||
// All work is done in surfaceChanged, which we are guaranteed to get even for surface creation. |
|||
} |
|||
|
|||
@Override |
|||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { |
|||
Log.debug("[EmulationFragment] Surface changed. Resolution: " + width + "x" + height); |
|||
mEmulationState.newSurface(holder.getSurface()); |
|||
} |
|||
|
|||
@Override |
|||
public void surfaceDestroyed(SurfaceHolder holder) { |
|||
mEmulationState.clearSurface(); |
|||
} |
|||
|
|||
@Override |
|||
public void doFrame(long frameTimeNanos) { |
|||
Choreographer.getInstance().postFrameCallback(this); |
|||
NativeLibrary.DoFrame(); |
|||
} |
|||
|
|||
public void stopEmulation() { |
|||
mEmulationState.stop(); |
|||
} |
|||
|
|||
public void startConfiguringControls() { |
|||
getView().findViewById(R.id.done_control_config).setVisibility(View.VISIBLE); |
|||
mInputOverlay.setIsInEditMode(true); |
|||
} |
|||
|
|||
public void stopConfiguringControls() { |
|||
getView().findViewById(R.id.done_control_config).setVisibility(View.GONE); |
|||
mInputOverlay.setIsInEditMode(false); |
|||
} |
|||
|
|||
public boolean isConfiguringControls() { |
|||
return mInputOverlay.isInEditMode(); |
|||
} |
|||
|
|||
private static class EmulationState { |
|||
private final String mGamePath; |
|||
private State state; |
|||
private Surface mSurface; |
|||
private boolean mRunWhenSurfaceIsValid; |
|||
|
|||
EmulationState(String gamePath) { |
|||
mGamePath = gamePath; |
|||
// Starting state is stopped. |
|||
state = State.STOPPED; |
|||
} |
|||
|
|||
public synchronized boolean isStopped() { |
|||
return state == State.STOPPED; |
|||
} |
|||
|
|||
// Getters for the current state |
|||
|
|||
public synchronized boolean isPaused() { |
|||
return state == State.PAUSED; |
|||
} |
|||
|
|||
public synchronized boolean isRunning() { |
|||
return state == State.RUNNING; |
|||
} |
|||
|
|||
public synchronized void stop() { |
|||
if (state != State.STOPPED) { |
|||
Log.debug("[EmulationFragment] Stopping emulation."); |
|||
state = State.STOPPED; |
|||
NativeLibrary.StopEmulation(); |
|||
} else { |
|||
Log.warning("[EmulationFragment] Stop called while already stopped."); |
|||
} |
|||
} |
|||
|
|||
// State changing methods |
|||
|
|||
public synchronized void pause() { |
|||
if (state != State.PAUSED) { |
|||
state = State.PAUSED; |
|||
Log.debug("[EmulationFragment] Pausing emulation."); |
|||
|
|||
// Release the surface before pausing, since emulation has to be running for that. |
|||
NativeLibrary.SurfaceDestroyed(); |
|||
NativeLibrary.PauseEmulation(); |
|||
} else { |
|||
Log.warning("[EmulationFragment] Pause called while already paused."); |
|||
} |
|||
} |
|||
|
|||
public synchronized void run(boolean isActivityRecreated) { |
|||
if (isActivityRecreated) { |
|||
if (NativeLibrary.IsRunning()) { |
|||
state = State.PAUSED; |
|||
} |
|||
} else { |
|||
Log.debug("[EmulationFragment] activity resumed or fresh start"); |
|||
} |
|||
|
|||
// If the surface is set, run now. Otherwise, wait for it to get set. |
|||
if (mSurface != null) { |
|||
runWithValidSurface(); |
|||
} else { |
|||
mRunWhenSurfaceIsValid = true; |
|||
} |
|||
} |
|||
|
|||
// Surface callbacks |
|||
public synchronized void newSurface(Surface surface) { |
|||
mSurface = surface; |
|||
if (mRunWhenSurfaceIsValid) { |
|||
runWithValidSurface(); |
|||
} |
|||
} |
|||
|
|||
public synchronized void clearSurface() { |
|||
if (mSurface == null) { |
|||
Log.warning("[EmulationFragment] clearSurface called, but surface already null."); |
|||
} else { |
|||
mSurface = null; |
|||
Log.debug("[EmulationFragment] Surface destroyed."); |
|||
|
|||
if (state == State.RUNNING) { |
|||
NativeLibrary.SurfaceDestroyed(); |
|||
state = State.PAUSED; |
|||
} else if (state == State.PAUSED) { |
|||
Log.warning("[EmulationFragment] Surface cleared while emulation paused."); |
|||
} else { |
|||
Log.warning("[EmulationFragment] Surface cleared while emulation stopped."); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void runWithValidSurface() { |
|||
mRunWhenSurfaceIsValid = false; |
|||
if (state == State.STOPPED) { |
|||
NativeLibrary.SurfaceChanged(mSurface); |
|||
Thread mEmulationThread = new Thread(() -> |
|||
{ |
|||
Log.debug("[EmulationFragment] Starting emulation thread."); |
|||
NativeLibrary.Run(mGamePath); |
|||
}, "NativeEmulation"); |
|||
mEmulationThread.start(); |
|||
|
|||
} else if (state == State.PAUSED) { |
|||
Log.debug("[EmulationFragment] Resuming emulation."); |
|||
NativeLibrary.SurfaceChanged(mSurface); |
|||
NativeLibrary.UnPauseEmulation(); |
|||
} else { |
|||
Log.debug("[EmulationFragment] Bug, run called while already running."); |
|||
} |
|||
state = State.RUNNING; |
|||
} |
|||
|
|||
private enum State { |
|||
STOPPED, RUNNING, PAUSED |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,348 @@ |
|||
package org.yuzu.yuzu_emu.fragments |
|||
|
|||
import android.content.Context |
|||
import android.content.IntentFilter |
|||
import android.content.SharedPreferences |
|||
import android.graphics.Color |
|||
import android.os.Bundle |
|||
import android.os.Handler |
|||
import android.view.* |
|||
import android.widget.Button |
|||
import android.widget.TextView |
|||
import android.widget.Toast |
|||
import androidx.fragment.app.Fragment |
|||
import androidx.localbroadcastmanager.content.LocalBroadcastManager |
|||
import androidx.preference.PreferenceManager |
|||
import org.yuzu.yuzu_emu.NativeLibrary |
|||
import org.yuzu.yuzu_emu.R |
|||
import org.yuzu.yuzu_emu.YuzuApplication |
|||
import org.yuzu.yuzu_emu.activities.EmulationActivity |
|||
import org.yuzu.yuzu_emu.features.settings.model.Settings |
|||
import org.yuzu.yuzu_emu.overlay.InputOverlay |
|||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization |
|||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization.DirectoryInitializationState |
|||
import org.yuzu.yuzu_emu.utils.DirectoryStateReceiver |
|||
import org.yuzu.yuzu_emu.utils.Log |
|||
|
|||
class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.FrameCallback { |
|||
private lateinit var preferences: SharedPreferences |
|||
private var inputOverlay: InputOverlay? = null |
|||
private lateinit var emulationState: EmulationState |
|||
private var directoryStateReceiver: DirectoryStateReceiver? = null |
|||
private var emulationActivity: EmulationActivity? = null |
|||
private lateinit var perfStats: TextView |
|||
private var perfStatsUpdater: (() -> Unit)? = null |
|||
|
|||
override fun onAttach(context: Context) { |
|||
super.onAttach(context) |
|||
if (context is EmulationActivity) { |
|||
emulationActivity = context |
|||
NativeLibrary.setEmulationActivity(context) |
|||
} else { |
|||
throw IllegalStateException("EmulationFragment must have EmulationActivity parent") |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Initialize anything that doesn't depend on the layout / views in here. |
|||
*/ |
|||
override fun onCreate(savedInstanceState: Bundle?) { |
|||
super.onCreate(savedInstanceState) |
|||
|
|||
// So this fragment doesn't restart on configuration changes; i.e. rotation. |
|||
retainInstance = true |
|||
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) |
|||
val gamePath = requireArguments().getString(KEY_GAMEPATH) |
|||
emulationState = EmulationState(gamePath) |
|||
} |
|||
|
|||
/** |
|||
* Initialize the UI and start emulation in here. |
|||
*/ |
|||
override fun onCreateView( |
|||
inflater: LayoutInflater, |
|||
container: ViewGroup?, |
|||
savedInstanceState: Bundle? |
|||
): View? { |
|||
val contents = inflater.inflate(R.layout.fragment_emulation, container, false) |
|||
val surfaceView = contents.findViewById<SurfaceView>(R.id.surface_emulation) |
|||
surfaceView.holder.addCallback(this) |
|||
inputOverlay = contents.findViewById(R.id.surface_input_overlay) |
|||
perfStats = contents.findViewById(R.id.show_fps_text) |
|||
perfStats.setTextColor(Color.YELLOW) |
|||
val doneButton = contents.findViewById<Button>(R.id.done_control_config) |
|||
doneButton?.setOnClickListener { stopConfiguringControls() } |
|||
|
|||
// Setup overlay. |
|||
resetInputOverlay() |
|||
updateShowFpsOverlay() |
|||
|
|||
// The new Surface created here will get passed to the native code via onSurfaceChanged. |
|||
return contents |
|||
} |
|||
|
|||
override fun onResume() { |
|||
super.onResume() |
|||
Choreographer.getInstance().postFrameCallback(this) |
|||
if (DirectoryInitialization.areDirectoriesReady()) { |
|||
emulationState.run(emulationActivity!!.isActivityRecreated) |
|||
} else { |
|||
setupDirectoriesThenStartEmulation() |
|||
} |
|||
} |
|||
|
|||
override fun onPause() { |
|||
if (directoryStateReceiver != null) { |
|||
LocalBroadcastManager.getInstance(requireActivity()).unregisterReceiver( |
|||
directoryStateReceiver!! |
|||
) |
|||
directoryStateReceiver = null |
|||
} |
|||
if (emulationState.isRunning) { |
|||
emulationState.pause() |
|||
} |
|||
Choreographer.getInstance().removeFrameCallback(this) |
|||
super.onPause() |
|||
} |
|||
|
|||
override fun onDetach() { |
|||
NativeLibrary.clearEmulationActivity() |
|||
super.onDetach() |
|||
} |
|||
|
|||
private fun setupDirectoriesThenStartEmulation() { |
|||
val statusIntentFilter = IntentFilter( |
|||
DirectoryInitialization.BROADCAST_ACTION |
|||
) |
|||
directoryStateReceiver = |
|||
DirectoryStateReceiver { directoryInitializationState: DirectoryInitializationState -> |
|||
if (directoryInitializationState == |
|||
DirectoryInitializationState.YUZU_DIRECTORIES_INITIALIZED |
|||
) { |
|||
emulationState.run(emulationActivity!!.isActivityRecreated) |
|||
} else if (directoryInitializationState == |
|||
DirectoryInitializationState.CANT_FIND_EXTERNAL_STORAGE |
|||
) { |
|||
Toast.makeText( |
|||
context, |
|||
R.string.external_storage_not_mounted, |
|||
Toast.LENGTH_SHORT |
|||
) |
|||
.show() |
|||
} |
|||
} |
|||
|
|||
// Registers the DirectoryStateReceiver and its intent filters |
|||
LocalBroadcastManager.getInstance(requireActivity()).registerReceiver( |
|||
directoryStateReceiver!!, |
|||
statusIntentFilter |
|||
) |
|||
DirectoryInitialization.start(requireContext()) |
|||
} |
|||
|
|||
fun refreshInputOverlay() { |
|||
inputOverlay!!.refreshControls() |
|||
} |
|||
|
|||
fun resetInputOverlay() { |
|||
// Reset button scale |
|||
preferences.edit() |
|||
.putInt(Settings.PREF_CONTROL_SCALE, 50) |
|||
.apply() |
|||
inputOverlay!!.resetButtonPlacement() |
|||
} |
|||
|
|||
private fun updateShowFpsOverlay() { |
|||
// TODO: Create a setting so that this actually works... |
|||
if (true) { |
|||
val SYSTEM_FPS = 0 |
|||
val FPS = 1 |
|||
val FRAMETIME = 2 |
|||
val SPEED = 3 |
|||
perfStatsUpdater = { |
|||
val perfStats = NativeLibrary.GetPerfStats() |
|||
if (perfStats[FPS] > 0) { |
|||
this.perfStats.text = String.format("FPS: %.1f", perfStats[FPS]) |
|||
} |
|||
perfStatsUpdateHandler.postDelayed(perfStatsUpdater!!, 100) |
|||
} |
|||
perfStatsUpdateHandler.post(perfStatsUpdater!!) |
|||
perfStats.visibility = View.VISIBLE |
|||
} else { |
|||
if (perfStatsUpdater != null) { |
|||
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!) |
|||
} |
|||
perfStats.visibility = View.GONE |
|||
} |
|||
} |
|||
|
|||
override fun surfaceCreated(holder: SurfaceHolder) { |
|||
// We purposely don't do anything here. |
|||
// All work is done in surfaceChanged, which we are guaranteed to get even for surface creation. |
|||
} |
|||
|
|||
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) { |
|||
Log.debug("[EmulationFragment] Surface changed. Resolution: " + width + "x" + height) |
|||
emulationState.newSurface(holder.surface) |
|||
} |
|||
|
|||
override fun surfaceDestroyed(holder: SurfaceHolder) { |
|||
emulationState.clearSurface() |
|||
} |
|||
|
|||
override fun doFrame(frameTimeNanos: Long) { |
|||
Choreographer.getInstance().postFrameCallback(this) |
|||
NativeLibrary.DoFrame() |
|||
} |
|||
|
|||
fun stopEmulation() { |
|||
emulationState.stop() |
|||
} |
|||
|
|||
fun startConfiguringControls() { |
|||
requireView().findViewById<View>(R.id.done_control_config).visibility = |
|||
View.VISIBLE |
|||
inputOverlay!!.setIsInEditMode(true) |
|||
} |
|||
|
|||
fun stopConfiguringControls() { |
|||
requireView().findViewById<View>(R.id.done_control_config).visibility = View.GONE |
|||
inputOverlay!!.setIsInEditMode(false) |
|||
} |
|||
|
|||
val isConfiguringControls: Boolean |
|||
get() = inputOverlay!!.isInEditMode |
|||
|
|||
private class EmulationState(private val mGamePath: String?) { |
|||
private var state: State |
|||
private var surface: Surface? = null |
|||
private var runWhenSurfaceIsValid = false |
|||
|
|||
init { |
|||
// Starting state is stopped. |
|||
state = State.STOPPED |
|||
} |
|||
|
|||
@get:Synchronized |
|||
val isStopped: Boolean |
|||
get() = state == State.STOPPED |
|||
|
|||
// Getters for the current state |
|||
@get:Synchronized |
|||
val isPaused: Boolean |
|||
get() = state == State.PAUSED |
|||
|
|||
@get:Synchronized |
|||
val isRunning: Boolean |
|||
get() = state == State.RUNNING |
|||
|
|||
@Synchronized |
|||
fun stop() { |
|||
if (state != State.STOPPED) { |
|||
Log.debug("[EmulationFragment] Stopping emulation.") |
|||
state = State.STOPPED |
|||
NativeLibrary.StopEmulation() |
|||
} else { |
|||
Log.warning("[EmulationFragment] Stop called while already stopped.") |
|||
} |
|||
} |
|||
|
|||
// State changing methods |
|||
@Synchronized |
|||
fun pause() { |
|||
if (state != State.PAUSED) { |
|||
state = State.PAUSED |
|||
Log.debug("[EmulationFragment] Pausing emulation.") |
|||
|
|||
// Release the surface before pausing, since emulation has to be running for that. |
|||
NativeLibrary.SurfaceDestroyed() |
|||
NativeLibrary.PauseEmulation() |
|||
} else { |
|||
Log.warning("[EmulationFragment] Pause called while already paused.") |
|||
} |
|||
} |
|||
|
|||
@Synchronized |
|||
fun run(isActivityRecreated: Boolean) { |
|||
if (isActivityRecreated) { |
|||
if (NativeLibrary.IsRunning()) { |
|||
state = State.PAUSED |
|||
} |
|||
} else { |
|||
Log.debug("[EmulationFragment] activity resumed or fresh start") |
|||
} |
|||
|
|||
// If the surface is set, run now. Otherwise, wait for it to get set. |
|||
if (surface != null) { |
|||
runWithValidSurface() |
|||
} else { |
|||
runWhenSurfaceIsValid = true |
|||
} |
|||
} |
|||
|
|||
// Surface callbacks |
|||
@Synchronized |
|||
fun newSurface(surface: Surface?) { |
|||
this.surface = surface |
|||
if (runWhenSurfaceIsValid) { |
|||
runWithValidSurface() |
|||
} |
|||
} |
|||
|
|||
@Synchronized |
|||
fun clearSurface() { |
|||
if (surface == null) { |
|||
Log.warning("[EmulationFragment] clearSurface called, but surface already null.") |
|||
} else { |
|||
surface = null |
|||
Log.debug("[EmulationFragment] Surface destroyed.") |
|||
when (state) { |
|||
State.RUNNING -> { |
|||
NativeLibrary.SurfaceDestroyed() |
|||
state = State.PAUSED |
|||
} |
|||
State.PAUSED -> Log.warning("[EmulationFragment] Surface cleared while emulation paused.") |
|||
else -> Log.warning("[EmulationFragment] Surface cleared while emulation stopped.") |
|||
} |
|||
} |
|||
} |
|||
|
|||
private fun runWithValidSurface() { |
|||
runWhenSurfaceIsValid = false |
|||
when (state) { |
|||
State.STOPPED -> { |
|||
NativeLibrary.SurfaceChanged(surface) |
|||
val mEmulationThread = Thread({ |
|||
Log.debug("[EmulationFragment] Starting emulation thread.") |
|||
NativeLibrary.Run(mGamePath) |
|||
}, "NativeEmulation") |
|||
mEmulationThread.start() |
|||
} |
|||
State.PAUSED -> { |
|||
Log.debug("[EmulationFragment] Resuming emulation.") |
|||
NativeLibrary.SurfaceChanged(surface) |
|||
NativeLibrary.UnPauseEmulation() |
|||
} |
|||
else -> Log.debug("[EmulationFragment] Bug, run called while already running.") |
|||
} |
|||
state = State.RUNNING |
|||
} |
|||
|
|||
private enum class State { |
|||
STOPPED, RUNNING, PAUSED |
|||
} |
|||
} |
|||
|
|||
companion object { |
|||
private const val KEY_GAMEPATH = "gamepath" |
|||
private val perfStatsUpdateHandler = Handler() |
|||
|
|||
fun newInstance(gamePath: String?): EmulationFragment { |
|||
val args = Bundle() |
|||
args.putString(KEY_GAMEPATH, gamePath) |
|||
val fragment = EmulationFragment() |
|||
fragment.arguments = args |
|||
return fragment |
|||
} |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue