1
0
forked from MeloNX/MeloNX

UIThread working

This commit is contained in:
Daniil Vinogradov 2025-03-07 15:46:04 +01:00
parent 877a3b3dd7
commit 552069c8ce
8 changed files with 319 additions and 33 deletions

View File

@ -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<Ryujinx.Configuration?>(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

View File

@ -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<T>(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 <a href="https://github.com/theapache64/rebugger">
* Rebugger gitHub
* </a>
*/
@Suppress("unused")
@Composable
fun Rebugger(
trackMap: Map<String, Any?>,
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
}
}
}

View File

@ -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")

View File

@ -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)")
}

View File

@ -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();

View File

@ -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(() =>

141
src/RyujinxAndroid/.idea/workspace.xml generated Normal file
View File

@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AutoImportSettings">
<option name="autoReloadType" value="NONE" />
</component>
<component name="ChangeListManager">
<list default="true" id="b70a156d-88fb-4b09-9b57-c7698aab2d2a" name="Changes" comment="" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ClangdSettings">
<option name="formatViaClangd" value="false" />
</component>
<component name="ExecutionTargetManager" SELECTED_TARGET="device_and_snapshot_combo_box_target[DeviceId(pluginId=LocalEmulator, isTemplate=false, identifier=path=/Users/daniilvinogradov/.android/avd/Pixel_7_Pro_API_34.avd)]" />
<component name="ExternalProjectsData">
<projectState path="$PROJECT_DIR$">
<ProjectState />
</projectState>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/../.." />
</component>
<component name="ProjectColorInfo"><![CDATA[{
"associatedIndex": 4
}]]></component>
<component name="ProjectId" id="2tjVfEZg0XN8pbpoF5pzqoZcHSH" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"Android App.app.executor": "Debug",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.cidr.known.project.marker": "true",
"RunOnceActivity.readMode.enableVisualFormatting": "true",
"cf.first.check.clang-format": "false",
"cidr.known.project.marker": "true",
"git-widget-placeholder": "Message-Bridge",
"kotlin-language-version-configured": "true",
"last_opened_file_path": "/Users/daniilvinogradov/Documents/Dev/MeloNX/src/RyujinxAndroid",
"settings.editor.selected.configurable": "AndroidSdkUpdater"
}
}]]></component>
<component name="RunManager">
<configuration name="app" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
<module name="RyujinxAndroid.app.main" />
<option name="DEPLOY" value="true" />
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
<option name="DEPLOY_AS_INSTANT" value="false" />
<option name="ARTIFACT_NAME" value="" />
<option name="PM_INSTALL_OPTIONS" value="" />
<option name="ALL_USERS" value="false" />
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
<option name="CLEAR_APP_STORAGE" value="false" />
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
<option name="MODE" value="default_activity" />
<option name="RESTORE_ENABLED" value="false" />
<option name="RESTORE_FILE" value="" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid>
<Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Java>
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<option name="DEEP_LINK" value="" />
<option name="ACTIVITY_CLASS" value="" />
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="b70a156d-88fb-4b09-9b57-c7698aab2d2a" name="Changes" comment="" />
<created>1740865571599</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1740865571599</updated>
</task>
<servers />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<breakpoints>
<breakpoint enabled="true" type="OCSymbolicBreakpointType">
<properties>
<option name="moduleName" value="libart.so" />
<option name="symbolPattern" value="art_sigbus_fault" />
</properties>
<option name="timeStamp" value="2" />
</breakpoint>
</breakpoints>
</breakpoint-manager>
</component>
</project>