From 66079923aee1b75bfd14c8a0600a2e5b90a62895 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Wed, 8 Mar 2023 10:41:29 -0500 Subject: android: Convert EmulationFragment to Kotlin --- .../yuzu/yuzu_emu/fragments/EmulationFragment.java | 375 --------------------- .../yuzu/yuzu_emu/fragments/EmulationFragment.kt | 348 +++++++++++++++++++ 2 files changed, 348 insertions(+), 375 deletions(-) delete mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.java create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.java b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.java deleted file mode 100644 index 2a2b0f68b..000000000 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.java +++ /dev/null @@ -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 - } - } -} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt new file mode 100644 index 000000000..77964b88c --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -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(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