forked from MeloNX/MeloNX
UIThread fix
This commit is contained in:
parent
877a3b3dd7
commit
4589e6da3b
@ -7,6 +7,8 @@ import skip.ui.*
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.hardware.Sensor
|
||||||
|
import android.util.Log
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@ -17,6 +19,7 @@ import androidx.compose.foundation.layout.Box
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.SideEffect
|
import androidx.compose.runtime.SideEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
|
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
|
import melo.nxmodel.Ryujinx
|
||||||
import org.libsdl.app.SDLActivity
|
import org.libsdl.app.SDLActivity
|
||||||
|
|
||||||
internal val logger: SkipLogger = SkipLogger(subsystem = "melonx.module", category = "melonx")
|
internal val logger: SkipLogger = SkipLogger(subsystem = "melonx.module", category = "melonx")
|
||||||
@ -56,9 +60,12 @@ open class MainActivity: SDLActivity {
|
|||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
|
val gameIsRunning = remember { GameState.shared._isGameRunning.projectedValue }
|
||||||
|
val startGameConfig = remember { GameState.shared._startGameConfig.projectedValue }
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
SDLComposeSurface()
|
SDLComposeSurface()
|
||||||
Box(Modifier.graphicsLayer(alpha = 0.5f)) {
|
if (gameIsRunning?.value != true) {
|
||||||
val saveableStateHolder = rememberSaveableStateHolder()
|
val saveableStateHolder = rememberSaveableStateHolder()
|
||||||
saveableStateHolder.SaveableStateProvider(true) {
|
saveableStateHolder.SaveableStateProvider(true) {
|
||||||
PresentationRootView(ComposeContext())
|
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.
|
// 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
|
@Composable
|
||||||
|
@ -24,6 +24,12 @@ extern "C" {
|
|||||||
SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER | SDL_INIT_SENSOR \
|
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 {
|
struct GameInfo {
|
||||||
long FileSize;
|
long FileSize;
|
||||||
char TitleName[512];
|
char TitleName[512];
|
||||||
|
@ -19,6 +19,7 @@ import Darwin
|
|||||||
@Observable public class Ryujinx {
|
@Observable public class Ryujinx {
|
||||||
public var games: [Game] = []
|
public var games: [Game] = []
|
||||||
public var firmwareversion = "0"
|
public var firmwareversion = "0"
|
||||||
|
public var controllers: [Controller] = []
|
||||||
public static let shared = Ryujinx()
|
public static let shared = Ryujinx()
|
||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
@ -27,11 +28,18 @@ import Darwin
|
|||||||
private var basePath: URL!
|
private var basePath: URL!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public struct Controller: Identifiable, Hashable {
|
||||||
|
public var id: String
|
||||||
|
public var name: String
|
||||||
|
}
|
||||||
|
|
||||||
public extension Ryujinx {
|
public extension Ryujinx {
|
||||||
func initialize(basePath: URL) {
|
func initialize(basePath: URL) {
|
||||||
self.basePath = basePath
|
self.basePath = basePath
|
||||||
SDLLib.shared.initSDL()
|
SDLLib.shared.initSDL()
|
||||||
RyujinxLib.shared.initialize(basePath: basePath.path())
|
RyujinxLib.shared.initialize(basePath: basePath.path())
|
||||||
|
refreshConnectedControllers()
|
||||||
loadGames()
|
loadGames()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +88,7 @@ public extension Ryujinx {
|
|||||||
|
|
||||||
games.append(game)
|
games.append(game)
|
||||||
} catch {
|
} catch {
|
||||||
print(error)
|
logger.error("\(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,6 +137,32 @@ public extension Ryujinx {
|
|||||||
func getCurrentFps() -> Int {
|
func getCurrentFps() -> Int {
|
||||||
RyujinxLib.shared.getCurrentFps()
|
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 {
|
private extension Ryujinx {
|
||||||
@ -242,6 +276,33 @@ private extension Ryujinx {
|
|||||||
return args
|
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 {
|
public extension Ryujinx {
|
||||||
|
@ -47,4 +47,44 @@ extension SDLLib {
|
|||||||
let f_SDL_LogSetAllPriority = unsafeBitCast(sym_SDL_LogSetAllPriority, to: SDL_LogSetAllPriority.self)
|
let f_SDL_LogSetAllPriority = unsafeBitCast(sym_SDL_LogSetAllPriority, to: SDL_LogSetAllPriority.self)
|
||||||
f_SDL_LogSetAllPriority(1)
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,12 @@ enum ContentTab: String, Hashable {
|
|||||||
case games, home, settings
|
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 {
|
struct ContentView: View {
|
||||||
@AppStorage("tab") var tab = ContentTab.games
|
@AppStorage("tab") var tab = ContentTab.games
|
||||||
@State var viewModel = ViewModel()
|
@State var viewModel = ViewModel()
|
||||||
@ -72,16 +78,19 @@ private extension ContentView {
|
|||||||
.onAppear {
|
.onAppear {
|
||||||
setupEmulation()
|
setupEmulation()
|
||||||
|
|
||||||
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
|
Task {
|
||||||
if Ryujinx.shared.getCurrentFps() != 0 {
|
try await Task.sleep(nanoseconds: 5_000_000_000)
|
||||||
withAnimation {
|
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
|
||||||
isLoading = false
|
if Ryujinx.shared.getCurrentFps() != 0 {
|
||||||
}
|
withAnimation {
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
|
||||||
// isAnimating = false
|
GameState.shared.isGameRunning = true
|
||||||
timer.invalidate()
|
// isAnimating = false
|
||||||
|
timer.invalidate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
logger.info("FPS: \(Ryujinx.shared.getCurrentFps())")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,16 +100,15 @@ private extension ContentView {
|
|||||||
// patchMakeKeyAndVisible()
|
// patchMakeKeyAndVisible()
|
||||||
// isVCA = (currentControllers.first(where: { $0 == onscreencontroller }) != nil)
|
// isVCA = (currentControllers.first(where: { $0 == onscreencontroller }) != nil)
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
start()
|
||||||
start()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
guard let game else { return }
|
guard let game else { return }
|
||||||
|
|
||||||
config.gamepath = game.fileURL.path
|
config.gamepath = game.fileURL.path
|
||||||
// config.inputids = Array(Set(currentControllers.map(\.id)))
|
Ryujinx.shared.refreshConnectedControllers()
|
||||||
|
config.inputids = Ryujinx.shared.controllers.map(\.id)
|
||||||
//
|
//
|
||||||
// if mVKPreFillBuffer {
|
// if mVKPreFillBuffer {
|
||||||
// let setting = MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "2")
|
// let setting = MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "2")
|
||||||
@ -116,10 +124,11 @@ private extension ContentView {
|
|||||||
config.inputids.append("0")
|
config.inputids.append("0")
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
// do {
|
||||||
try Ryujinx.shared.start(with: config)
|
GameState.shared.startGameConfig = config
|
||||||
} catch {
|
// try Ryujinx.shared.start(with: config)
|
||||||
logger.error("Error: \(error.localizedDescription)")
|
// } catch {
|
||||||
}
|
// logger.error("Error: \(error.localizedDescription)")
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user