android: Convert EmulationFragment to Kotlin
This commit is contained in:
		@@ -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
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user