diff --git a/app/src/main/cpp/emu_jni.cpp b/app/src/main/cpp/emu_jni.cpp index 234b2363..336dbf4f 100644 --- a/app/src/main/cpp/emu_jni.cpp +++ b/app/src/main/cpp/emu_jni.cpp @@ -53,6 +53,7 @@ extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication( try { skyline::kernel::OS os(jvmManager, logger, settings, std::string(appFilesPath)); inputWeak = os.state.input; + jvmManager->InitializeControllers(); env->ReleaseStringUTFChars(appFilesPathJstring, appFilesPath); auto romUri = env->GetStringUTFChars(romUriJstring, nullptr); @@ -100,27 +101,22 @@ extern "C" JNIEXPORT jfloat Java_emu_skyline_EmulationActivity_getFrametime(JNIE } extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setController(JNIEnv *, jobject, jint index, jint type, jint partnerIndex) { - while (inputWeak.expired()); // If this isn't called then the guest won't know that the following host controller exists auto input = inputWeak.lock(); std::lock_guard guard(input->npad.mutex); input->npad.controllers[index] = skyline::input::GuestController{static_cast(type), static_cast(partnerIndex)}; } extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_updateControllers(JNIEnv *, jobject) { - while (inputWeak.expired()); // If this isn't called then the mappings will not update unless the guest initiates an update itself - auto input = inputWeak.lock(); - std::unique_lock lock(input->npad.mutex); - input->npad.Update(lock, true); + inputWeak.lock()->npad.Update(); } -extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setButtonState(JNIEnv *, jobject, jint index, jlong mask, jint state) { +extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setButtonState(JNIEnv *, jobject, jint index, jlong mask, jboolean pressed) { try { auto input = inputWeak.lock(); auto device = input->npad.controllers[index].device; - skyline::input::NpadButton button{.raw = static_cast(mask)}; if (device) - device->SetButtonState(button, state); - } catch (const std::bad_weak_ptr) { + device->SetButtonState(skyline::input::NpadButton{.raw = static_cast(mask)}, pressed); + } catch (const std::bad_weak_ptr&) { // We don't mind if we miss button updates while input hasn't been initialized } } @@ -131,7 +127,7 @@ extern "C" JNIEXPORT void JNICALL Java_emu_skyline_EmulationActivity_setAxisValu auto device = input->npad.controllers[index].device; if (device) device->SetAxisValue(static_cast(axis), value); - } catch (const std::bad_weak_ptr) { + } catch (const std::bad_weak_ptr&) { // We don't mind if we miss axis updates while input hasn't been initialized } } diff --git a/app/src/main/cpp/skyline/input/npad.cpp b/app/src/main/cpp/skyline/input/npad.cpp index 7493a01b..65020dfd 100644 --- a/app/src/main/cpp/skyline/input/npad.cpp +++ b/app/src/main/cpp/skyline/input/npad.cpp @@ -13,15 +13,8 @@ namespace skyline::input { {*this, hid->npad[8], NpadId::Unknown}, {*this, hid->npad[9], NpadId::Handheld}, } {} - void NpadManager::Update(std::unique_lock &lock, bool host) { - if (host) { - updated = true; - } else if (!updated) { - lock.unlock(); - while (!updated); - lock.lock(); - return; - } + void NpadManager::Update() { + std::lock_guard guard(mutex); if (!activated) return; @@ -85,17 +78,17 @@ namespace skyline::input { } void NpadManager::Activate() { - std::unique_lock lock(mutex); + std::lock_guard guard(mutex); supportedIds = {NpadId::Handheld, NpadId::Player1, NpadId::Player2, NpadId::Player3, NpadId::Player4, NpadId::Player5, NpadId::Player6, NpadId::Player7, NpadId::Player8}; styles = {.proController = true, .joyconHandheld = true, .joyconDual = true, .joyconLeft = true, .joyconRight = true}; activated = true; - Update(lock); + Update(); } void NpadManager::Deactivate() { - std::unique_lock lock(mutex); + std::lock_guard guard(mutex); supportedIds = {}; styles = {}; diff --git a/app/src/main/cpp/skyline/input/npad.h b/app/src/main/cpp/skyline/input/npad.h index dd292f42..c1eda5e5 100644 --- a/app/src/main/cpp/skyline/input/npad.h +++ b/app/src/main/cpp/skyline/input/npad.h @@ -22,7 +22,6 @@ namespace skyline::input { private: const DeviceState &state; bool activated{false}; //!< If this NpadManager is activated or not - std::atomic updated{false}; //!< If this NpadManager has been updated by the guest friend NpadDevice; @@ -43,7 +42,7 @@ namespace skyline::input { } public: - std::mutex mutex; //!< This mutex must be locked before any modifications to class members + std::recursive_mutex mutex; //!< This mutex must be locked before any modifications to class members std::array npads; std::array controllers; std::vector supportedIds; //!< The NpadId(s) that are supported by the application @@ -71,10 +70,9 @@ namespace skyline::input { /** * @brief This deduces all the mappings from guest controllers -> players based on the configuration supplied by HID services and available controllers - * @param lock A unique_lock which locks the mutex in the class, it should be locked before modifications to any members and must not be passed in an unlocked state - * @param host If the update is host-initiated rather than the guest + * @note If any class members were edited, the mutex shouldn't be released till this is called */ - void Update(std::unique_lock &lock, bool host = false); + void Update(); /** * @brief This activates the mapping between guest controllers -> players, a call to this is required for function diff --git a/app/src/main/cpp/skyline/jvm.cpp b/app/src/main/cpp/skyline/jvm.cpp index 48862bf9..0e915cb5 100644 --- a/app/src/main/cpp/skyline/jvm.cpp +++ b/app/src/main/cpp/skyline/jvm.cpp @@ -6,7 +6,7 @@ thread_local JNIEnv *env; namespace skyline { - JvmManager::JvmManager(JNIEnv *environ, jobject instance) : instance(instance), instanceClass(reinterpret_cast(environ->NewGlobalRef(environ->GetObjectClass(instance)))) { + JvmManager::JvmManager(JNIEnv *environ, jobject instance) : instance(instance), instanceClass(reinterpret_cast(environ->NewGlobalRef(environ->GetObjectClass(instance)))), initializeControllersId(environ->GetMethodID(instanceClass, "initializeControllers", "()V")) { env = environ; if (env->GetJavaVM(&vm) < 0) throw exception("Cannot get JavaVM from environment"); @@ -37,4 +37,8 @@ namespace skyline { bool JvmManager::CheckNull(jobject &object) { return env->IsSameObject(object, nullptr); } + + void JvmManager::InitializeControllers() { + env->CallVoidMethod(instance, initializeControllersId); + } } diff --git a/app/src/main/cpp/skyline/jvm.h b/app/src/main/cpp/skyline/jvm.h index 52dcaae1..2bdc8ff4 100644 --- a/app/src/main/cpp/skyline/jvm.h +++ b/app/src/main/cpp/skyline/jvm.h @@ -35,7 +35,7 @@ namespace skyline { /** * @brief Returns a pointer to the JNI environment for the current thread */ - JNIEnv *GetEnv(); + static JNIEnv *GetEnv(); /** * @brief Retrieves a specific field of the given type from the activity @@ -85,6 +85,14 @@ namespace skyline { * @param object The jobject to check * @return If the object is null or not */ - bool CheckNull(jobject &object); + static bool CheckNull(jobject &object); + + /** + * @brief A call to EmulationActivity.initializeControllers in Kotlin + */ + void InitializeControllers(); + + private: + jmethodID initializeControllersId; }; } diff --git a/app/src/main/cpp/skyline/services/hid/IHidServer.cpp b/app/src/main/cpp/skyline/services/hid/IHidServer.cpp index 94006115..e93fd164 100644 --- a/app/src/main/cpp/skyline/services/hid/IHidServer.cpp +++ b/app/src/main/cpp/skyline/services/hid/IHidServer.cpp @@ -29,9 +29,9 @@ namespace skyline::service::hid { void IHidServer::SetSupportedNpadStyleSet(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { auto styleSet = request.Pop(); - std::unique_lock lock(state.input->npad.mutex); + std::lock_guard lock(state.input->npad.mutex); state.input->npad.styles = styleSet; - state.input->npad.Update(lock); + state.input->npad.Update(); state.logger->Debug("Controller Support:\nPro-Controller: {}\nJoy-Con: Handheld: {}, Dual: {}, L: {}, R: {}\nGameCube: {}\nPokeBall: {}\nNES: {}, NES Handheld: {}, SNES: {}", static_cast(styleSet.proController), static_cast(styleSet.joyconHandheld), static_cast(styleSet.joyconDual), static_cast(styleSet.joyconLeft), static_cast (styleSet.joyconRight), static_cast(styleSet.gamecube), static_cast(styleSet.palma), static_cast(styleSet.nes), static_cast(styleSet.nesHandheld), static_cast(styleSet.snes)); @@ -45,16 +45,16 @@ namespace skyline::service::hid { const auto &buffer = request.inputBuf.at(0); u64 address = buffer.address; size_t size = buffer.size / sizeof(NpadId); - std::vector supportedIds; + std::vector supportedIds(size); for (size_t i = 0; i < size; i++) { - supportedIds.push_back(state.process->GetObject(address)); + supportedIds[i] = state.process->GetObject(address); address += sizeof(NpadId); } - std::unique_lock lock(state.input->npad.mutex); + std::lock_guard lock(state.input->npad.mutex); state.input->npad.supportedIds = supportedIds; - state.input->npad.Update(lock); + state.input->npad.Update(); } void IHidServer::ActivateNpad(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { @@ -75,10 +75,10 @@ namespace skyline::service::hid { } void IHidServer::SetNpadJoyHoldType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - std::unique_lock lock(state.input->npad.mutex); + std::lock_guard lock(state.input->npad.mutex); request.Skip(); state.input->npad.orientation = request.Pop(); - state.input->npad.Update(lock); + state.input->npad.Update(); } void IHidServer::GetNpadJoyHoldType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { @@ -87,22 +87,22 @@ namespace skyline::service::hid { void IHidServer::SetNpadJoyAssignmentModeSingleByDefault(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { auto id = request.Pop(); - std::unique_lock lock(state.input->npad.mutex); + std::lock_guard lock(state.input->npad.mutex); state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Single); - state.input->npad.Update(lock); + state.input->npad.Update(); } void IHidServer::SetNpadJoyAssignmentModeSingle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { auto id = request.Pop(); - std::unique_lock lock(state.input->npad.mutex); + std::lock_guard lock(state.input->npad.mutex); state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Single); - state.input->npad.Update(lock); + state.input->npad.Update(); } void IHidServer::SetNpadJoyAssignmentModeDual(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { auto id = request.Pop(); - std::unique_lock lock(state.input->npad.mutex); + std::lock_guard lock(state.input->npad.mutex); state.input->npad.at(id).SetAssignment(NpadJoyAssignment::Dual); - state.input->npad.Update(lock); + state.input->npad.Update(); } } diff --git a/app/src/main/cpp/skyline/services/settings/ISystemSettingsServer.cpp b/app/src/main/cpp/skyline/services/settings/ISystemSettingsServer.cpp index 9276b501..fe694c52 100644 --- a/app/src/main/cpp/skyline/services/settings/ISystemSettingsServer.cpp +++ b/app/src/main/cpp/skyline/services/settings/ISystemSettingsServer.cpp @@ -9,7 +9,7 @@ namespace skyline::service::settings { {0x3, SFUNC(ISystemSettingsServer::GetFirmwareVersion)}}) {} void ISystemSettingsServer::GetFirmwareVersion(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - SysVerTitle title{.major=5, .minor=0, .micro=0, .revMajor=4, .revMinor=0, .platform="NX", .verHash="4de65c071fd0869695b7629f75eb97b2551dbf2f", .dispVer="9.0.0", .dispTitle="NintendoSDK Firmware for NX 9.0.0-4.0"}; + SysVerTitle title{.major=9, .minor=0, .micro=0, .revMajor=4, .revMinor=0, .platform="NX", .verHash="4de65c071fd0869695b7629f75eb97b2551dbf2f", .dispVer="9.0.0", .dispTitle="NintendoSDK Firmware for NX 9.0.0-4.0"}; state.process->WriteMemory(title, request.outputBuf.at(0).address); } } diff --git a/app/src/main/java/emu/skyline/EmulationActivity.kt b/app/src/main/java/emu/skyline/EmulationActivity.kt index bb05fb99..1a69eb46 100644 --- a/app/src/main/java/emu/skyline/EmulationActivity.kt +++ b/app/src/main/java/emu/skyline/EmulationActivity.kt @@ -10,6 +10,7 @@ import android.content.Intent import android.net.Uri import android.os.Build import android.os.Bundle +import android.os.ConditionVariable import android.os.ParcelFileDescriptor import android.util.Log import android.view.* @@ -52,6 +53,11 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback { @Volatile private var surface : Surface? = null + /** + * A condition variable keeping track of if the surface is ready or not + */ + private var surfaceReady = ConditionVariable() + /** * A boolean flag denoting if the emulation thread should call finish() or not */ @@ -120,9 +126,9 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback { * * @param index The index of the controller this is directed to * @param mask The mask of the button that are being set - * @param state The state to set the button to + * @param pressed If the buttons are being pressed or released */ - private external fun setButtonState(index : Int, mask : Long, state : Int) + private external fun setButtonState(index : Int, mask : Long, pressed : Boolean) /** * This sets the value of a specific axis on a specific controller @@ -141,13 +147,13 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback { val controller = entry.value if (controller.type != ControllerType.None) { - val type : Int = when (controller.type) { + val type = when (controller.type) { ControllerType.None -> throw IllegalArgumentException() ControllerType.HandheldProController -> if (operationMode) ControllerType.ProController.id else ControllerType.HandheldProController.id ControllerType.ProController, ControllerType.JoyConLeft, ControllerType.JoyConRight -> controller.type.id } - val partnerIndex : Int? = when (controller) { + val partnerIndex = when (controller) { is JoyConLeftController -> controller.partnerId is JoyConRightController -> controller.partnerId else -> null @@ -170,10 +176,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback { romFd = contentResolver.openFileDescriptor(rom, "r")!! emulationThread = Thread { - while (surface == null) - Thread.yield() - - runOnUiThread { initializeControllers() } + surfaceReady.block() executeApplication(rom.toString(), romType, romFd.fd, preferenceFd.fd, applicationContext.filesDir.canonicalPath + "/") @@ -216,14 +219,12 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback { val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) if (sharedPreferences.getBoolean("perf_stats", false)) { - val perfRunnable = object : Runnable { + perf_stats.postDelayed(object : Runnable { override fun run() { perf_stats.text = "${getFps()} FPS\n${getFrametime()}ms" perf_stats.postDelayed(this, 250) } - } - - perf_stats.postDelayed(perfRunnable, 250) + }, 250) } operationMode = sharedPreferences.getBoolean("operation_mode", operationMode) @@ -274,6 +275,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback { Log.d("surfaceCreated", "Holder: $holder") surface = holder.surface setSurface(surface) + surfaceReady.open() } /** @@ -288,6 +290,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback { */ override fun surfaceDestroyed(holder : SurfaceHolder) { Log.d("surfaceDestroyed", "Holder: $holder") + surfaceReady.close() surface = null setSurface(surface) } @@ -299,7 +302,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback { if (event.repeatCount != 0) return super.dispatchKeyEvent(event) - val action : ButtonState = when (event.action) { + val action = when (event.action) { KeyEvent.ACTION_DOWN -> ButtonState.Pressed KeyEvent.ACTION_UP -> ButtonState.Released else -> return super.dispatchKeyEvent(event) @@ -308,7 +311,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback { return when (val guestEvent = input.eventMap[KeyHostEvent(event.device.descriptor, event.keyCode)]) { is ButtonGuestEvent -> { if (guestEvent.button != ButtonId.Menu) - setButtonState(guestEvent.id, guestEvent.button.value(), action.ordinal) + setButtonState(guestEvent.id, guestEvent.button.value(), action.state) true } @@ -356,7 +359,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback { when (guestEvent) { is ButtonGuestEvent -> { if (guestEvent.button != ButtonId.Menu) - setButtonState(guestEvent.id, guestEvent.button.value(), if (abs(value) >= guestEvent.threshold) ButtonState.Pressed.ordinal else ButtonState.Released.ordinal) + setButtonState(guestEvent.id, guestEvent.button.value(), if (abs(value) >= guestEvent.threshold) ButtonState.Pressed.state else ButtonState.Released.state) } is AxisGuestEvent -> { diff --git a/app/src/main/java/emu/skyline/MainActivity.kt b/app/src/main/java/emu/skyline/MainActivity.kt index 15b1d983..eeddb046 100644 --- a/app/src/main/java/emu/skyline/MainActivity.kt +++ b/app/src/main/java/emu/skyline/MainActivity.kt @@ -179,7 +179,7 @@ class MainActivity : AppCompatActivity(), View.OnClickListener { setupAppList() app_list.addOnScrollListener(object : RecyclerView.OnScrollListener() { - var y : Int = 0 + var y = 0 override fun onScrolled(recyclerView : RecyclerView, dx : Int, dy : Int) { y += dy diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index efe527c7..fa72b5cb 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -65,12 +65,14 @@ android:key="category_input" android:title="@string/input" app:initialExpandedChildrenCount="4"> +