mirror of
				https://git.tardis.systems/mirrors/yuzu
				synced 2025-10-31 02:34:11 +01:00 
			
		
		
		
	android: Multi-program app switching
This commit is contained in:
		
							parent
							
								
									ce2eb6e8ee
								
							
						
					
					
						commit
						3f1290cee3
					
				| @ -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 New Issue
	
	Block a user