diff --git a/src/MeloNX-Skip/melonx-native/Android/app/src/main/jniLibs/arm64-v8a/libRyujinx.Headless.SDL2.so b/src/MeloNX-Skip/melonx-native/Android/app/src/main/jniLibs/arm64-v8a/libRyujinx.Headless.SDL2.so index 2dbafcf59..9c10b06a7 100644 Binary files a/src/MeloNX-Skip/melonx-native/Android/app/src/main/jniLibs/arm64-v8a/libRyujinx.Headless.SDL2.so and b/src/MeloNX-Skip/melonx-native/Android/app/src/main/jniLibs/arm64-v8a/libRyujinx.Headless.SDL2.so differ diff --git a/src/MeloNX-Skip/melonx-native/Android/app/src/main/kotlin/Main.kt b/src/MeloNX-Skip/melonx-native/Android/app/src/main/kotlin/Main.kt index 896008bea..c060ee4ad 100644 --- a/src/MeloNX-Skip/melonx-native/Android/app/src/main/kotlin/Main.kt +++ b/src/MeloNX-Skip/melonx-native/Android/app/src/main/kotlin/Main.kt @@ -1,31 +1,31 @@ package melonx.module -import skip.lib.* -import skip.model.* -import skip.foundation.* -import skip.ui.* - -import android.Manifest import android.app.Application -import androidx.activity.enableEdgeToEdge +import android.hardware.Sensor +import android.os.Process +import android.util.Log import androidx.activity.compose.setContent -import androidx.appcompat.app.AppCompatActivity -import androidx.compose.foundation.background +import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveableStateHolder import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView -import androidx.core.app.ActivityCompat +import com.lenta.presentation.utils.Rebugger +import kotlinx.coroutines.flow.MutableStateFlow +import melo.nxmodel.Ryujinx import org.libsdl.app.SDLActivity +import skip.foundation.ProcessInfo +import skip.foundation.SkipLogger +import skip.ui.ColorScheme +import skip.ui.ComposeContext +import skip.ui.PresentationRoot +import skip.ui.UIApplication internal val logger: SkipLogger = SkipLogger(subsystem = "melonx.module", category = "melonx") @@ -49,6 +49,11 @@ open class MainActivity: SDLActivity { constructor() { } + companion object { + } + + private val startGameConfigMutableStateFlow = MutableStateFlow(null) + override fun onCreate(savedInstanceState: android.os.Bundle?) { super.onCreate(savedInstanceState) logger.info("starting activity") @@ -56,16 +61,15 @@ open class MainActivity: SDLActivity { enableEdgeToEdge() setContent { - Box { - SDLComposeSurface() - Box(Modifier.graphicsLayer(alpha = 0.5f)) { - val saveableStateHolder = rememberSaveableStateHolder() - saveableStateHolder.SaveableStateProvider(true) { - PresentationRootView(ComposeContext()) - SideEffect { saveableStateHolder.removeState(true) } - } + + val startGameConfig = remember { GameState.shared._startGameConfig.projectedValue } + if (startGameConfig?.value != null) { + runSimulator(startGameConfig.value!!) + GameState.shared.startGameConfig = null } - } + + val gameIsRunning = remember { GameState.shared._isGameRunning.projectedValue } + RootScreen(gameIsRunning?.value) } // Example of requesting permissions on startup. @@ -128,7 +132,27 @@ open class MainActivity: SDLActivity { logger.info("onRequestPermissionsResult: ${requestCode}") } - companion object { + @Composable + fun RootScreen(gameIsRunning: Boolean?) { + Rebugger( + trackMap = mapOf( + "gameIsRunning" to gameIsRunning + ) + ) + + Box { + + SDLComposeSurface() + if (gameIsRunning != true) { +// Box(Modifier.graphicsLayer(alpha = 0.2f)) { + val saveableStateHolder = rememberSaveableStateHolder() + saveableStateHolder.SaveableStateProvider(true) { + PresentationRootView(ComposeContext()) + SideEffect { saveableStateHolder.removeState(true) } + } +// } + } + } } @Composable @@ -139,6 +163,27 @@ open class MainActivity: SDLActivity { } ) } + + fun runSimulator(config: Ryujinx.Configuration) { + class RyujinxMain : Runnable { + override fun run() { + try { + Process.setThreadPriority(Process.THREAD_PRIORITY_DISPLAY) + } catch (e: Exception) { + Log.v("SDL", "modify thread properties failed $e") + } + + Ryujinx.shared.start(config) + } + } + + // This is the entry point to the C app. + // Start up the C app thread and enable sensor input for the first time + // FIXME: Why aren't we enabling sensor input at start? + mSDLThread = Thread(RyujinxMain(), "SDLThread") + mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true) + mSDLThread.start() + } } @Composable diff --git a/src/MeloNX-Skip/melonx-native/Android/app/src/main/kotlin/Rebugger.kt b/src/MeloNX-Skip/melonx-native/Android/app/src/main/kotlin/Rebugger.kt new file mode 100644 index 000000000..051b3888f --- /dev/null +++ b/src/MeloNX-Skip/melonx-native/Android/app/src/main/kotlin/Rebugger.kt @@ -0,0 +1,84 @@ +package com.lenta.presentation.utils + +import android.util.Log +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue + + +private const val TAG = "Rebugger" + +private class Ref(var value: T) + +/** + * Fork of Rebugger library, which help catch UI recomposition for composable func + * Used an android Log system instead the project Log, because we would't send on + * + * Example of usage + * ```kotlin + * @Composable + * fun VehicleUi( + * car: Car, + * bike: Bike, + * ) { + * var human by remember { mutableStateOf(Human("John")) } + * // Call Rebugger and pass the states you want to track. + * // It could be a function arg or a state + * Rebugger( + * trackMap = mapOf( + * "car" to car, + * "bike" to bike, + * "human" to human + * ), + * ) + * //... + * ``` + * + * @see + * Rebugger gitHub + * + */ +@Suppress("unused") +@Composable +fun Rebugger( + trackMap: Map, + composableName: String = Thread.currentThread().stackTrace[3].methodName, +) { + + LaunchedEffect(Unit) { + Log.i(TAG, "🐞 Rebugger activated on `$composableName`") + } + + val count = remember { Ref(0) } + val flag = remember { Ref(false) } + SideEffect { + count.value++ + } + + val changeLog = StringBuilder() + for ((key, newArg) in trackMap) { + var recompositionTrigger by remember { mutableStateOf(false) } + val oldArg = remember(recompositionTrigger) { newArg } + + if (oldArg != newArg) { + changeLog.append("\n\t `$key` changed from `$oldArg` to `$newArg`, ") + flag.value = true + + recompositionTrigger = !recompositionTrigger + } + } + + if (changeLog.isNotEmpty()) { + Log.i(TAG, "🐞$composableName recomposed because $changeLog") + } else { + if (count.value >= 1 && !flag.value) { + Log.i(TAG, "🐞$composableName recomposed not because of param change") + } else { + flag.value = false + } + } +} \ No newline at end of file diff --git a/src/MeloNX-Skip/melonx-native/Sources/MeloNXModel/Ryujinx.swift b/src/MeloNX-Skip/melonx-native/Sources/MeloNXModel/Ryujinx.swift index aef639c4e..78d4d8dde 100644 --- a/src/MeloNX-Skip/melonx-native/Sources/MeloNXModel/Ryujinx.swift +++ b/src/MeloNX-Skip/melonx-native/Sources/MeloNXModel/Ryujinx.swift @@ -214,7 +214,7 @@ private extension Ryujinx { args.append("--enable-debug-logs") } - args.append("--disable-info-logs") +// args.append("--disable-info-logs") if config.tracelogs { args.append("--enable-trace-logs") diff --git a/src/MeloNX-Skip/melonx-native/Sources/melonx/ContentView.swift b/src/MeloNX-Skip/melonx-native/Sources/melonx/ContentView.swift index 09b6973d8..43ef6f010 100644 --- a/src/MeloNX-Skip/melonx-native/Sources/melonx/ContentView.swift +++ b/src/MeloNX-Skip/melonx-native/Sources/melonx/ContentView.swift @@ -6,6 +6,13 @@ enum ContentTab: String, Hashable { case games, home, settings } +@Observable public class GameState { + public static var shared = GameState() + public var isGameRunning: Bool = false + + public var startGameConfig: Ryujinx.Configuration? +} + struct ContentView: View { @AppStorage("tab") var tab = ContentTab.games @State var viewModel = ViewModel() @@ -14,6 +21,7 @@ struct ContentView: View { @State private var config: Ryujinx.Configuration @State private var game: Game? @State private var isLoading = true + @State private var startSDL = false init() { // let defaultConfig = loadSettings() ?? Ryujinx.Configuration(gamepath: "") @@ -33,9 +41,7 @@ struct ContentView: View { } else { TabView(selection: $tab) { NavigationStack { - NavigationStack { - GamesView(startemu: $game) - } + GamesView(startemu: $game) } .tabItem { Label("Games", systemImage: "house.fill") } .tag(ContentTab.games) @@ -70,7 +76,11 @@ private extension ContentView { var emulationView: some View { Text("Loading...") .onAppear { + GameState.shared.isGameRunning = true + startSDL = true + setupEmulation() + // SKIP INSERT: // MainActivity.enableSDLLayer(true) Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in if Ryujinx.shared.getCurrentFps() != 0 { @@ -91,9 +101,9 @@ private extension ContentView { // patchMakeKeyAndVisible() // isVCA = (currentControllers.first(where: { $0 == onscreencontroller }) != nil) - DispatchQueue.main.async { +// DispatchQueue.main.async { start() - } +// } } func start() { @@ -117,7 +127,8 @@ private extension ContentView { } do { - try Ryujinx.shared.start(with: config) + GameState.shared.startGameConfig = config +// try Ryujinx.shared.start(with: config) } catch { logger.error("Error: \(error.localizedDescription)") } diff --git a/src/Ryujinx.Headless.SDL2/Program.cs b/src/Ryujinx.Headless.SDL2/Program.cs index d02e884cf..7a3056edf 100644 --- a/src/Ryujinx.Headless.SDL2/Program.cs +++ b/src/Ryujinx.Headless.SDL2/Program.cs @@ -315,6 +315,9 @@ namespace Ryujinx.Headless.SDL2 static void Main(string[] args) { + Logger.Info?.Print(LogClass.Application, $"Start Emu on Thread name: {Thread.CurrentThread.Name}"); + Logger.Info?.Print(LogClass.Application, $"Start Emu on Thread id: {Thread.CurrentThread.ManagedThreadId}"); + // Make process DPI aware for proper window sizing on high-res screens. ForceDpiAware.Windows(); diff --git a/src/Ryujinx.SDL2.Common/SDL2Driver.cs b/src/Ryujinx.SDL2.Common/SDL2Driver.cs index 49e7dd147..cac9536d8 100644 --- a/src/Ryujinx.SDL2.Common/SDL2Driver.cs +++ b/src/Ryujinx.SDL2.Common/SDL2Driver.cs @@ -159,6 +159,8 @@ namespace Ryujinx.SDL2.Common using ManualResetEventSlim waitHandle = new(false); + Logger.Info?.Print(LogClass.Application, $"Start Emu on Thread name 2: {Thread.CurrentThread.Name}"); + Logger.Info?.Print(LogClass.Application, $"Start Emu on Thread id 2: {Thread.CurrentThread.ManagedThreadId}"); while (_isRunning) { MainThreadDispatcher?.Invoke(() => diff --git a/src/RyujinxAndroid/.idea/workspace.xml b/src/RyujinxAndroid/.idea/workspace.xml new file mode 100644 index 000000000..34cfb76af --- /dev/null +++ b/src/RyujinxAndroid/.idea/workspace.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1740865571599 + + + + + + + + + + + + + + \ No newline at end of file