android: Multi-program app switching
This commit is contained in:
parent
ce2eb6e8ee
commit
3f1290cee3
8 changed files with 117 additions and 34 deletions
|
@ -261,7 +261,7 @@ object NativeLibrary {
|
|||
/**
|
||||
* Begins emulation.
|
||||
*/
|
||||
external fun run(path: String?)
|
||||
external fun run(path: String?, programIndex: Int = 0)
|
||||
|
||||
// Surface Handling
|
||||
external fun surfaceChanged(surf: Surface?)
|
||||
|
@ -489,6 +489,12 @@ object NativeLibrary {
|
|||
sEmulationActivity.get()!!.onEmulationStopped(status)
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onProgramChanged(programIndex: Int) {
|
||||
sEmulationActivity.get()!!.onProgramChanged(programIndex)
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the Yuzu version, Android version and, CPU.
|
||||
*/
|
||||
|
|
|
@ -76,7 +76,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
|
||||
override fun onDestroy() {
|
||||
stopForegroundService(this)
|
||||
emulationViewModel.clear()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
@ -446,9 +445,14 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
|||
}
|
||||
|
||||
fun onEmulationStopped(status: Int) {
|
||||
if (status == 0) {
|
||||
if (status == 0 && emulationViewModel.programChanged.value == -1) {
|
||||
finish()
|
||||
}
|
||||
emulationViewModel.setEmulationStopped(true)
|
||||
}
|
||||
|
||||
fun onProgramChanged(programIndex: Int) {
|
||||
emulationViewModel.setProgramChanged(programIndex)
|
||||
}
|
||||
|
||||
private fun startMotionSensorListener() {
|
||||
|
|
|
@ -424,10 +424,38 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.programChanged.collect {
|
||||
if (it != 0) {
|
||||
emulationViewModel.setEmulationStarted(false)
|
||||
binding.drawerLayout.close()
|
||||
binding.drawerLayout
|
||||
.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
|
||||
ViewUtils.hideView(binding.surfaceInputOverlay)
|
||||
ViewUtils.showView(binding.loadingIndicator)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.emulationStopped.collect {
|
||||
if (it && emulationViewModel.programChanged.value != -1) {
|
||||
if (perfStatsUpdater != null) {
|
||||
perfStatsUpdateHandler.removeCallbacks(perfStatsUpdater!!)
|
||||
}
|
||||
emulationState.changeProgram(emulationViewModel.programChanged.value)
|
||||
emulationViewModel.setProgramChanged(-1)
|
||||
emulationViewModel.setEmulationStopped(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startEmulation() {
|
||||
private fun startEmulation(programIndex: Int = 0) {
|
||||
if (!NativeLibrary.isRunning() && !NativeLibrary.isPaused()) {
|
||||
if (!DirectoryInitialization.areDirectoriesReady) {
|
||||
DirectoryInitialization.start()
|
||||
|
@ -435,7 +463,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
|
||||
updateScreenLayout()
|
||||
|
||||
emulationState.run(emulationActivity!!.isActivityRecreated)
|
||||
emulationState.run(emulationActivity!!.isActivityRecreated, programIndex)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -833,6 +861,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
) {
|
||||
private var state: State
|
||||
private var surface: Surface? = null
|
||||
lateinit var emulationThread: Thread
|
||||
|
||||
init {
|
||||
// Starting state is stopped.
|
||||
|
@ -878,7 +907,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
|
||||
@Synchronized
|
||||
fun run(isActivityRecreated: Boolean) {
|
||||
fun run(isActivityRecreated: Boolean, programIndex: Int = 0) {
|
||||
if (isActivityRecreated) {
|
||||
if (NativeLibrary.isRunning()) {
|
||||
state = State.PAUSED
|
||||
|
@ -889,10 +918,20 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
|
||||
// If the surface is set, run now. Otherwise, wait for it to get set.
|
||||
if (surface != null) {
|
||||
runWithValidSurface()
|
||||
runWithValidSurface(programIndex)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun changeProgram(programIndex: Int) {
|
||||
emulationThread.join()
|
||||
emulationThread = Thread({
|
||||
Log.debug("[EmulationFragment] Starting emulation thread.")
|
||||
NativeLibrary.run(gamePath, programIndex)
|
||||
}, "NativeEmulation")
|
||||
emulationThread.start()
|
||||
}
|
||||
|
||||
// Surface callbacks
|
||||
@Synchronized
|
||||
fun newSurface(surface: Surface?) {
|
||||
|
@ -932,7 +971,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
}
|
||||
}
|
||||
|
||||
private fun runWithValidSurface() {
|
||||
private fun runWithValidSurface(programIndex: Int = 0) {
|
||||
NativeLibrary.surfaceChanged(surface)
|
||||
if (!emulationCanStart.invoke()) {
|
||||
return
|
||||
|
@ -940,9 +979,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||
|
||||
when (state) {
|
||||
State.STOPPED -> {
|
||||
val emulationThread = Thread({
|
||||
emulationThread = Thread({
|
||||
Log.debug("[EmulationFragment] Starting emulation thread.")
|
||||
NativeLibrary.run(gamePath)
|
||||
NativeLibrary.run(gamePath, programIndex)
|
||||
}, "NativeEmulation")
|
||||
emulationThread.start()
|
||||
}
|
||||
|
|
|
@ -15,6 +15,12 @@ class EmulationViewModel : ViewModel() {
|
|||
val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping
|
||||
private val _isEmulationStopping = MutableStateFlow(false)
|
||||
|
||||
private val _emulationStopped = MutableStateFlow(false)
|
||||
val emulationStopped = _emulationStopped.asStateFlow()
|
||||
|
||||
private val _programChanged = MutableStateFlow(-1)
|
||||
val programChanged = _programChanged.asStateFlow()
|
||||
|
||||
val shaderProgress: StateFlow<Int> get() = _shaderProgress
|
||||
private val _shaderProgress = MutableStateFlow(0)
|
||||
|
||||
|
@ -35,6 +41,17 @@ class EmulationViewModel : ViewModel() {
|
|||
_isEmulationStopping.value = value
|
||||
}
|
||||
|
||||
fun setEmulationStopped(value: Boolean) {
|
||||
if (value) {
|
||||
_emulationStarted.value = false
|
||||
}
|
||||
_emulationStopped.value = value
|
||||
}
|
||||
|
||||
fun setProgramChanged(programIndex: Int) {
|
||||
_programChanged.value = programIndex
|
||||
}
|
||||
|
||||
fun setShaderProgress(progress: Int) {
|
||||
_shaderProgress.value = progress
|
||||
}
|
||||
|
@ -56,20 +73,4 @@ class EmulationViewModel : ViewModel() {
|
|||
fun setDrawerOpen(value: Boolean) {
|
||||
_drawerOpen.value = value
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
setEmulationStarted(false)
|
||||
setIsEmulationStopping(false)
|
||||
setShaderProgress(0)
|
||||
setTotalShaders(0)
|
||||
setShaderMessage("")
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEY_EMULATION_STARTED = "EmulationStarted"
|
||||
const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting"
|
||||
const val KEY_SHADER_PROGRESS = "ShaderProgress"
|
||||
const val KEY_TOTAL_SHADERS = "TotalShaders"
|
||||
const val KEY_SHADER_MESSAGE = "ShaderMessage"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ static jmethodID s_exit_emulation_activity;
|
|||
static jmethodID s_disk_cache_load_progress;
|
||||
static jmethodID s_on_emulation_started;
|
||||
static jmethodID s_on_emulation_stopped;
|
||||
static jmethodID s_on_program_changed;
|
||||
|
||||
static jclass s_game_class;
|
||||
static jmethodID s_game_constructor;
|
||||
|
@ -123,6 +124,10 @@ jmethodID GetOnEmulationStopped() {
|
|||
return s_on_emulation_stopped;
|
||||
}
|
||||
|
||||
jmethodID GetOnProgramChanged() {
|
||||
return s_on_program_changed;
|
||||
}
|
||||
|
||||
jclass GetGameClass() {
|
||||
return s_game_class;
|
||||
}
|
||||
|
@ -306,6 +311,8 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
|||
env->GetStaticMethodID(s_native_library_class, "onEmulationStarted", "()V");
|
||||
s_on_emulation_stopped =
|
||||
env->GetStaticMethodID(s_native_library_class, "onEmulationStopped", "(I)V");
|
||||
s_on_program_changed =
|
||||
env->GetStaticMethodID(s_native_library_class, "onProgramChanged", "(I)V");
|
||||
|
||||
const jclass game_class = env->FindClass("org/yuzu/yuzu_emu/model/Game");
|
||||
s_game_class = reinterpret_cast<jclass>(env->NewGlobalRef(game_class));
|
||||
|
|
|
@ -19,6 +19,7 @@ jmethodID GetExitEmulationActivity();
|
|||
jmethodID GetDiskCacheLoadProgress();
|
||||
jmethodID GetOnEmulationStarted();
|
||||
jmethodID GetOnEmulationStopped();
|
||||
jmethodID GetOnProgramChanged();
|
||||
|
||||
jclass GetGameClass();
|
||||
jmethodID GetGameConstructor();
|
||||
|
|
|
@ -208,7 +208,8 @@ void EmulationSession::InitializeSystem(bool reload) {
|
|||
m_system.GetFileSystemController().CreateFactories(*m_vfs);
|
||||
}
|
||||
|
||||
Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath) {
|
||||
Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath,
|
||||
const std::size_t program_index) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
|
||||
// Create the render window.
|
||||
|
@ -238,7 +239,8 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
|
|||
ConfigureFilesystemProvider(filepath);
|
||||
|
||||
// Load the ROM.
|
||||
m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath);
|
||||
m_load_result =
|
||||
m_system.Load(EmulationSession::GetInstance().Window(), filepath, 0, program_index);
|
||||
if (m_load_result != Core::SystemResultStatus::Success) {
|
||||
return m_load_result;
|
||||
}
|
||||
|
@ -248,6 +250,12 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
|
|||
m_system.GetCpuManager().OnGpuReady();
|
||||
m_system.RegisterExitCallback([&] { HaltEmulation(); });
|
||||
|
||||
// Register an ExecuteProgram callback such that Core can execute a sub-program
|
||||
m_system.RegisterExecuteProgramCallback([&](std::size_t program_index_) {
|
||||
m_next_program_index = program_index_;
|
||||
EmulationSession::GetInstance().HaltEmulation();
|
||||
});
|
||||
|
||||
OnEmulationStarted();
|
||||
return Core::SystemResultStatus::Success;
|
||||
}
|
||||
|
@ -255,6 +263,11 @@ Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string
|
|||
void EmulationSession::ShutdownEmulation() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
|
||||
if (m_next_program_index != -1) {
|
||||
ChangeProgram(m_next_program_index);
|
||||
m_next_program_index = -1;
|
||||
}
|
||||
|
||||
m_is_running = false;
|
||||
|
||||
// Unload user input.
|
||||
|
@ -402,6 +415,12 @@ void EmulationSession::OnEmulationStopped(Core::SystemResultStatus result) {
|
|||
static_cast<jint>(result));
|
||||
}
|
||||
|
||||
void EmulationSession::ChangeProgram(std::size_t program_index) {
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnProgramChanged(),
|
||||
static_cast<jint>(program_index));
|
||||
}
|
||||
|
||||
u64 EmulationSession::GetProgramId(JNIEnv* env, jstring jprogramId) {
|
||||
auto program_id_string = GetJString(env, jprogramId);
|
||||
try {
|
||||
|
@ -411,7 +430,8 @@ u64 EmulationSession::GetProgramId(JNIEnv* env, jstring jprogramId) {
|
|||
}
|
||||
}
|
||||
|
||||
static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
|
||||
static Core::SystemResultStatus RunEmulation(const std::string& filepath,
|
||||
const size_t program_index = 0) {
|
||||
MicroProfileOnThreadCreate("EmuThread");
|
||||
SCOPE_EXIT({ MicroProfileShutdown(); });
|
||||
|
||||
|
@ -424,7 +444,7 @@ static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
|
|||
|
||||
SCOPE_EXIT({ EmulationSession::GetInstance().ShutdownEmulation(); });
|
||||
|
||||
jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath);
|
||||
jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath, program_index);
|
||||
if (result != Core::SystemResultStatus::Success) {
|
||||
return result;
|
||||
}
|
||||
|
@ -689,11 +709,11 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj
|
|||
Settings::LogSettings();
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_run__Ljava_lang_String_2(JNIEnv* env, jclass clazz,
|
||||
jstring j_path) {
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_run(JNIEnv* env, jobject jobj, jstring j_path,
|
||||
jint j_program_index) {
|
||||
const std::string path = GetJString(env, j_path);
|
||||
|
||||
const Core::SystemResultStatus result{RunEmulation(path)};
|
||||
const Core::SystemResultStatus result{RunEmulation(path, j_program_index)};
|
||||
if (result != Core::SystemResultStatus::Success) {
|
||||
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
|
||||
IDCache::GetExitEmulationActivity(), static_cast<int>(result));
|
||||
|
|
|
@ -45,7 +45,8 @@ public:
|
|||
const Core::PerfStatsResults& PerfStats();
|
||||
void ConfigureFilesystemProvider(const std::string& filepath);
|
||||
void InitializeSystem(bool reload);
|
||||
Core::SystemResultStatus InitializeEmulation(const std::string& filepath);
|
||||
Core::SystemResultStatus InitializeEmulation(const std::string& filepath,
|
||||
const std::size_t program_index = 0);
|
||||
|
||||
bool IsHandheldOnly();
|
||||
void SetDeviceType([[maybe_unused]] int index, int type);
|
||||
|
@ -60,6 +61,7 @@ public:
|
|||
private:
|
||||
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max);
|
||||
static void OnEmulationStopped(Core::SystemResultStatus result);
|
||||
static void ChangeProgram(std::size_t program_index);
|
||||
|
||||
private:
|
||||
// Window management
|
||||
|
@ -84,4 +86,7 @@ private:
|
|||
// Synchronization
|
||||
std::condition_variable_any m_cv;
|
||||
mutable std::mutex m_mutex;
|
||||
|
||||
// Program index for next boot
|
||||
std::atomic<s32> m_next_program_index = -1;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue