diff options
| author | Charles Lombardo <clombardo169@gmail.com> | 2023-03-08 10:41:29 -0500 | 
|---|---|---|
| committer | bunnei <bunneidev@gmail.com> | 2023-06-03 00:05:38 -0700 | 
| commit | 66079923aee1b75bfd14c8a0600a2e5b90a62895 (patch) | |
| tree | 03077e5456e808c31684067da20f330682e8b5b1 /src/android/app | |
| parent | 0e4256651a1f082216b9fb8e4ecd6ebd2d4931f2 (diff) | |
android: Convert EmulationFragment to Kotlin
Diffstat (limited to 'src/android/app')
| -rw-r--r-- | src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.java | 375 | ||||
| -rw-r--r-- | src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt | 348 | 
2 files changed, 348 insertions, 375 deletions
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<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 +        } +    } +}  | 
