mirror of
https://git.suyu.dev/suyu/suyu.git
synced 2024-11-15 22:54:00 +00:00
android: Convert EmulationFragment to Kotlin
This commit is contained in:
parent
0e4256651a
commit
66079923ae
2 changed files with 348 additions and 375 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue