Archived
1
0
forked from MeloNX/MeloNX

UIThread fix

This commit is contained in:
Daniil Vinogradov 2025-03-07 23:37:12 +01:00
parent 877a3b3dd7
commit 4589e6da3b
5 changed files with 172 additions and 19 deletions

View File

@ -7,6 +7,8 @@ import skip.ui.*
import android.Manifest
import android.app.Application
import android.hardware.Sensor
import android.util.Log
import androidx.activity.enableEdgeToEdge
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
@ -17,6 +19,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
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
@ -25,6 +28,7 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.app.ActivityCompat
import melo.nxmodel.Ryujinx
import org.libsdl.app.SDLActivity
internal val logger: SkipLogger = SkipLogger(subsystem = "melonx.module", category = "melonx")
@ -56,9 +60,12 @@ open class MainActivity: SDLActivity {
enableEdgeToEdge()
setContent {
val gameIsRunning = remember { GameState.shared._isGameRunning.projectedValue }
val startGameConfig = remember { GameState.shared._startGameConfig.projectedValue }
Box {
SDLComposeSurface()
Box(Modifier.graphicsLayer(alpha = 0.5f)) {
if (gameIsRunning?.value != true) {
val saveableStateHolder = rememberSaveableStateHolder()
saveableStateHolder.SaveableStateProvider(true) {
PresentationRootView(ComposeContext())
@ -66,6 +73,11 @@ open class MainActivity: SDLActivity {
}
}
}
if (startGameConfig?.value != null) {
runSimulator(GameState.shared.startGameConfig!!)
// GameState.shared.startGameConfig = null
}
}
// Example of requesting permissions on startup.
@ -139,6 +151,31 @@ open class MainActivity: SDLActivity {
}
)
}
fun runSimulator(config: Ryujinx.Configuration) {
class RyujinxMain : Runnable {
override fun run() {
try {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_DISPLAY)
} catch (e: Exception) {
Log.v("SDL", "modify thread properties failed $e")
}
try {
Ryujinx.shared.start(config)
} catch (e: Exception) {
Log.v("Ryujinx", "Emulation failed to start $e")
}
}
}
// 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

@ -24,6 +24,12 @@ extern "C" {
SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER | SDL_INIT_SENSOR \
)
typedef struct {
unsigned char data[16];
} SDL_GUID;
typedef SDL_GUID SDL_JoystickGUID;
struct GameInfo {
long FileSize;
char TitleName[512];

View File

@ -19,6 +19,7 @@ import Darwin
@Observable public class Ryujinx {
public var games: [Game] = []
public var firmwareversion = "0"
public var controllers: [Controller] = []
public static let shared = Ryujinx()
private init() {}
@ -27,11 +28,18 @@ import Darwin
private var basePath: URL!
}
public struct Controller: Identifiable, Hashable {
public var id: String
public var name: String
}
public extension Ryujinx {
func initialize(basePath: URL) {
self.basePath = basePath
SDLLib.shared.initSDL()
RyujinxLib.shared.initialize(basePath: basePath.path())
refreshConnectedControllers()
loadGames()
}
@ -80,7 +88,7 @@ public extension Ryujinx {
games.append(game)
} catch {
print(error)
logger.error("\(error)")
}
}
@ -129,6 +137,32 @@ public extension Ryujinx {
func getCurrentFps() -> Int {
RyujinxLib.shared.getCurrentFps()
}
func refreshConnectedControllers() {
var controllers: [Controller] = []
let numJoysticks = SDLLib.shared.SDL_NumJoysticks()
for i in 0..<numJoysticks {
if let controller = SDLLib.shared.SDL_GameControllerOpen(i) {
let guid = generateGamepadId(joystickIndex: i)
let name = String(cString: SDLLib.shared.SDL_GameControllerName(controller))
logger.info("Controller \(i): \(name), GUID: \(guid ?? "")")
guard let guid else {
SDLLib.shared.SDL_GameControllerClose(controller)
continue
}
controllers.append(Controller(id: guid, name: name))
SDLLib.shared.SDL_GameControllerClose(controller)
}
}
self.controllers = controllers
}
}
private extension Ryujinx {
@ -242,6 +276,33 @@ private extension Ryujinx {
return args
}
func generateGamepadId(joystickIndex: Int) -> String? {
let guid = SDLLib.shared.SDL_JoystickGetDeviceGUID(joystickIndex)
if guid.data.0 == 0 && guid.data.1 == 0 && guid.data.2 == 0 && guid.data.3 == 0 {
return nil
}
let reorderedGUID: [UInt8] = [
guid.data.3, guid.data.2, guid.data.1, guid.data.0,
guid.data.5, guid.data.4,
guid.data.7, guid.data.6,
guid.data.8, guid.data.9,
guid.data.10, guid.data.11, guid.data.12, guid.data.13, guid.data.14, guid.data.15
]
let guidString = reorderedGUID.map { String(format: "%02X", $0) }.joined().lowercased()
func substring(_ str: String, _ start: Int, _ end: Int) -> String {
let startIdx = str.index(str.startIndex, offsetBy: start)
let endIdx = str.index(str.startIndex, offsetBy: end)
return String(str[startIdx..<endIdx])
}
let formattedGUID = "\(substring(guidString, 0, 8))-\(substring(guidString, 8, 12))-\(substring(guidString, 12, 16))-\(substring(guidString, 16, 20))-\(substring(guidString, 20, 32))"
return "\(joystickIndex)-\(formattedGUID)"
}
}
public extension Ryujinx {

View File

@ -47,4 +47,44 @@ extension SDLLib {
let f_SDL_LogSetAllPriority = unsafeBitCast(sym_SDL_LogSetAllPriority, to: SDL_LogSetAllPriority.self)
f_SDL_LogSetAllPriority(1)
}
func SDL_NumJoysticks() -> Int {
let sym = dlsym(handle, "SDL_NumJoysticks")
typealias SDL_NumJoysticks = @convention(c) () -> (Int32)
let f = unsafeBitCast(sym, to: SDL_NumJoysticks.self)
return Int(f())
}
func SDL_GameControllerOpen(_ joystick_index: Int) -> OpaquePointer! {
let sym = dlsym(handle, "SDL_GameControllerOpen")
typealias SDL_GameControllerOpen = @convention(c) (Int32) -> (OpaquePointer?)
let f = unsafeBitCast(sym, to: SDL_GameControllerOpen.self)
return f(Int32(joystick_index))
}
func SDL_GameControllerName(_ gamecontroller: OpaquePointer!) -> UnsafePointer<CChar>! {
let sym = dlsym(handle, "SDL_GameControllerName")
typealias SDL_GameControllerName = @convention(c) (OpaquePointer?) -> (UnsafePointer<CChar>?)
let f = unsafeBitCast(sym, to: SDL_GameControllerName.self)
return f(gamecontroller)
}
func SDL_GameControllerClose(_ gamecontroller: OpaquePointer!) {
let sym = dlsym(handle, "SDL_GameControllerClose")
typealias SDL_GameControllerClose = @convention(c) (OpaquePointer?) -> ()
let f = unsafeBitCast(sym, to: SDL_GameControllerClose.self)
f(gamecontroller)
}
func SDL_JoystickGetDeviceGUID(_ device_index: Int) -> SDL_JoystickGUID {
let sym = dlsym(handle, "SDL_JoystickGetDeviceGUID")
typealias SDL_JoystickGetDeviceGUID = @convention(c) (Int32) -> (SDL_JoystickGUID)
let f = unsafeBitCast(sym, to: SDL_JoystickGetDeviceGUID.self)
return f(Int32(device_index))
}
}

View File

@ -6,6 +6,12 @@ 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()
@ -72,16 +78,19 @@ private extension ContentView {
.onAppear {
setupEmulation()
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
if Ryujinx.shared.getCurrentFps() != 0 {
withAnimation {
isLoading = false
}
Task {
try await Task.sleep(nanoseconds: 5_000_000_000)
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
if Ryujinx.shared.getCurrentFps() != 0 {
withAnimation {
isLoading = false
}
// isAnimating = false
timer.invalidate()
GameState.shared.isGameRunning = true
// isAnimating = false
timer.invalidate()
}
}
logger.info("FPS: \(Ryujinx.shared.getCurrentFps())")
}
}
@ -91,16 +100,15 @@ private extension ContentView {
// patchMakeKeyAndVisible()
// isVCA = (currentControllers.first(where: { $0 == onscreencontroller }) != nil)
DispatchQueue.main.async {
start()
}
start()
}
func start() {
guard let game else { return }
config.gamepath = game.fileURL.path
// config.inputids = Array(Set(currentControllers.map(\.id)))
Ryujinx.shared.refreshConnectedControllers()
config.inputids = Ryujinx.shared.controllers.map(\.id)
//
// if mVKPreFillBuffer {
// let setting = MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "2")
@ -116,10 +124,11 @@ private extension ContentView {
config.inputids.append("0")
}
do {
try Ryujinx.shared.start(with: config)
} catch {
logger.error("Error: \(error.localizedDescription)")
}
// do {
GameState.shared.startGameConfig = config
// try Ryujinx.shared.start(with: config)
// } catch {
// logger.error("Error: \(error.localizedDescription)")
// }
}
}