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