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.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
|
||||
|
@ -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];
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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)")
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user