diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index b0e2f70c..41204432 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -198,6 +198,7 @@ add_library(skyline SHARED ${source_DIR}/skyline/soc/gm20b/engines/maxwell_dma.cpp ${source_DIR}/skyline/soc/gm20b/engines/maxwell/initialization.cpp ${source_DIR}/skyline/soc/gm20b/engines/fermi_2d.cpp + ${source_DIR}/skyline/input.cpp ${source_DIR}/skyline/input/npad.cpp ${source_DIR}/skyline/input/npad_device.cpp ${source_DIR}/skyline/input/touch.cpp diff --git a/app/src/main/cpp/skyline/input.cpp b/app/src/main/cpp/skyline/input.cpp new file mode 100644 index 00000000..8466ded5 --- /dev/null +++ b/app/src/main/cpp/skyline/input.cpp @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright © 2022 Skyline Team and Contributors (https://github.com/skyline-emu/) + +#include +#include +#include +#include "input.h" + +namespace skyline::input { + Input::Input(const DeviceState &state) + : state{state}, + kHid{std::make_shared(state, sizeof(HidSharedMemory))}, + hid{reinterpret_cast(kHid->host.data())}, + npad{state, hid}, + touch{state, hid}, + updateThread{&Input::UpdateThread, this} {} + + void Input::UpdateThread() { + if (int result{pthread_setname_np(pthread_self(), "Sky-Input")}) + Logger::Warn("Failed to set the thread name: {}", strerror(result)); + + try { + signal::SetSignalHandler({SIGINT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV}, signal::ExceptionalSignalHandler); + + struct UpdateCallback { + std::chrono::milliseconds period; + std::chrono::steady_clock::time_point next; + std::function callback; + + UpdateCallback(std::chrono::milliseconds period, std::function callback) + : period{period}, next{std::chrono::steady_clock::now() + period}, callback{std::move(callback)} {} + + void operator()() { + callback(*this); + next += period; + } + }; + + constexpr std::chrono::milliseconds NPadUpdatePeriod{4}; //!< The period at which a Joy-Con is updated (250Hz) + constexpr std::chrono::milliseconds TouchUpdatePeriod{4}; //!< The period at which the touch screen is updated (250Hz) + + std::array updateCallbacks{ + UpdateCallback{NPadUpdatePeriod, [&](UpdateCallback &callback) { + for (auto &pad : npad.npads) + pad.UpdateSharedMemory(); + }}, + UpdateCallback{TouchUpdatePeriod, [&](UpdateCallback &callback) { + touch.UpdateSharedMemory(); + }}, + }; + + while (true) { + auto now{std::chrono::steady_clock::now()}, next{updateCallbacks[0].next}; + for (auto &callback : updateCallbacks) { + if (now >= callback.next) + callback(); + + if (callback.next < next) + next = callback.next; + } + std::this_thread::sleep_until(next); + } + } catch (const signal::SignalException &e) { + Logger::Error("{}\nStack Trace:{}", e.what(), state.loader->GetStackTrace(e.frames)); + if (state.process) + state.process->Kill(false); + else + std::rethrow_exception(std::current_exception()); + } catch (const std::exception &e) { + Logger::Error(e.what()); + if (state.process) + state.process->Kill(false); + else + std::rethrow_exception(std::current_exception()); + } + } +} diff --git a/app/src/main/cpp/skyline/input.h b/app/src/main/cpp/skyline/input.h index f753d777..2f6a8a5c 100644 --- a/app/src/main/cpp/skyline/input.h +++ b/app/src/main/cpp/skyline/input.h @@ -24,11 +24,14 @@ namespace skyline::input { NpadManager npad; TouchManager touch; - Input(const DeviceState &state) - : state(state), - kHid(std::make_shared(state, sizeof(HidSharedMemory))), - hid(reinterpret_cast(kHid->host.data())), - npad(state, hid), - touch(state, hid) {} + Input(const DeviceState &state); + + private: + std::thread updateThread; //!< A thread that handles delivering HID shared memory updates at a fixed rate + + /** + * @brief The entry point for the update thread, this handles timing and delegation to the shared memory managers + */ + void UpdateThread(); }; } diff --git a/app/src/main/cpp/skyline/input/npad_device.cpp b/app/src/main/cpp/skyline/input/npad_device.cpp index fa119282..3f5cb8c9 100644 --- a/app/src/main/cpp/skyline/input/npad_device.cpp +++ b/app/src/main/cpp/skyline/input/npad_device.cpp @@ -146,10 +146,7 @@ namespace skyline::input { type = newType; controllerInfo = &GetControllerInfo(); - GetNextEntry(*controllerInfo); - GetNextEntry(section.defaultController); - globalTimestamp++; - + UpdateSharedMemory(); updateEvent->Signal(); } @@ -186,7 +183,7 @@ namespace skyline::input { } } - NpadControllerState &NpadDevice::GetNextEntry(NpadControllerInfo &info) { + void NpadDevice::WriteNextEntry(NpadControllerInfo &info, NpadControllerState entry) { auto &lastEntry{info.state.at(info.header.currentEntry)}; info.header.timestamp = util::GetTimeTicks(); @@ -194,30 +191,33 @@ namespace skyline::input { info.header.maxEntry = info.header.entryCount; info.header.currentEntry = (info.header.currentEntry != constant::HidEntryCount - 1) ? info.header.currentEntry + 1 : 0; - auto &entry{info.state.at(info.header.currentEntry)}; + auto &nextEntry{info.state.at(info.header.currentEntry)}; - entry.globalTimestamp = globalTimestamp; - entry.localTimestamp = lastEntry.localTimestamp + 1; - entry.buttons = lastEntry.buttons; - entry.leftX = lastEntry.leftX; - entry.leftY = lastEntry.leftY; - entry.rightX = lastEntry.rightX; - entry.rightY = lastEntry.rightY; - entry.status.raw = connectionState.raw; - - return entry; + nextEntry.globalTimestamp = globalTimestamp; + nextEntry.localTimestamp = lastEntry.localTimestamp + 1; + nextEntry.buttons = entry.buttons; + nextEntry.leftX = entry.leftX; + nextEntry.leftY = entry.leftY; + nextEntry.rightX = entry.rightX; + nextEntry.rightY = entry.rightY; + nextEntry.status.raw = connectionState.raw; } - void NpadDevice::SetButtonState(NpadButton mask, bool pressed) { + void NpadDevice::UpdateSharedMemory() { if (!connectionState.connected) return; - auto &entry{GetNextEntry(*controllerInfo)}; + WriteNextEntry(*controllerInfo, controllerState); + WriteNextEntry(section.defaultController, defaultState); + globalTimestamp++; + } + + void NpadDevice::SetButtonState(NpadButton mask, bool pressed) { if (pressed) - entry.buttons.raw |= mask.raw; + controllerState.buttons.raw |= mask.raw; else - entry.buttons.raw &= ~mask.raw; + controllerState.buttons.raw &= ~mask.raw; if (manager.orientation == NpadJoyOrientation::Horizontal && (type == NpadControllerType::JoyconLeft || type == NpadControllerType::JoyconRight)) { NpadButton orientedMask{}; @@ -252,109 +252,98 @@ namespace skyline::input { mask = orientedMask; } - auto &defaultEntry{GetNextEntry(section.defaultController)}; if (pressed) - defaultEntry.buttons.raw |= mask.raw; + defaultState.buttons.raw |= mask.raw; else - defaultEntry.buttons.raw &= ~mask.raw; - - globalTimestamp++; + defaultState.buttons.raw &= ~mask.raw; } void NpadDevice::SetAxisValue(NpadAxisId axis, i32 value) { - if (!connectionState.connected) - return; - - auto &controllerEntry{GetNextEntry(*controllerInfo)}; - auto &defaultEntry{GetNextEntry(section.defaultController)}; - constexpr i16 threshold{std::numeric_limits::max() / 2}; // A 50% deadzone for the stick buttons if (manager.orientation == NpadJoyOrientation::Vertical || (type != NpadControllerType::JoyconLeft && type != NpadControllerType::JoyconRight)) { switch (axis) { case NpadAxisId::LX: - controllerEntry.leftX = value; - defaultEntry.leftX = value; + controllerState.leftX = value; + defaultState.leftX = value; - controllerEntry.buttons.leftStickLeft = controllerEntry.leftX <= -threshold; - defaultEntry.buttons.leftStickLeft = controllerEntry.buttons.leftStickLeft; + controllerState.buttons.leftStickLeft = controllerState.leftX <= -threshold; + defaultState.buttons.leftStickLeft = controllerState.buttons.leftStickLeft; - controllerEntry.buttons.leftStickRight = controllerEntry.leftX >= threshold; - defaultEntry.buttons.leftStickRight = controllerEntry.buttons.leftStickRight; + controllerState.buttons.leftStickRight = controllerState.leftX >= threshold; + defaultState.buttons.leftStickRight = controllerState.buttons.leftStickRight; break; case NpadAxisId::LY: - controllerEntry.leftY = value; - defaultEntry.leftY = value; + controllerState.leftY = value; + defaultState.leftY = value; - defaultEntry.buttons.leftStickUp = controllerEntry.buttons.leftStickUp; - controllerEntry.buttons.leftStickUp = controllerEntry.leftY >= threshold; + defaultState.buttons.leftStickUp = controllerState.buttons.leftStickUp; + controllerState.buttons.leftStickUp = controllerState.leftY >= threshold; - controllerEntry.buttons.leftStickDown = controllerEntry.leftY <= -threshold; - defaultEntry.buttons.leftStickDown = controllerEntry.buttons.leftStickDown; + controllerState.buttons.leftStickDown = controllerState.leftY <= -threshold; + defaultState.buttons.leftStickDown = controllerState.buttons.leftStickDown; break; case NpadAxisId::RX: - controllerEntry.rightX = value; - defaultEntry.rightX = value; + controllerState.rightX = value; + defaultState.rightX = value; - controllerEntry.buttons.rightStickLeft = controllerEntry.rightX <= -threshold; - defaultEntry.buttons.rightStickLeft = controllerEntry.buttons.rightStickLeft; + controllerState.buttons.rightStickLeft = controllerState.rightX <= -threshold; + defaultState.buttons.rightStickLeft = controllerState.buttons.rightStickLeft; - controllerEntry.buttons.rightStickRight = controllerEntry.rightX >= threshold; - defaultEntry.buttons.rightStickRight = controllerEntry.buttons.rightStickRight; + controllerState.buttons.rightStickRight = controllerState.rightX >= threshold; + defaultState.buttons.rightStickRight = controllerState.buttons.rightStickRight; break; case NpadAxisId::RY: - controllerEntry.rightY = value; - defaultEntry.rightY = value; + controllerState.rightY = value; + defaultState.rightY = value; - controllerEntry.buttons.rightStickUp = controllerEntry.rightY >= threshold; - defaultEntry.buttons.rightStickUp = controllerEntry.buttons.rightStickUp; + controllerState.buttons.rightStickUp = controllerState.rightY >= threshold; + defaultState.buttons.rightStickUp = controllerState.buttons.rightStickUp; - controllerEntry.buttons.rightStickDown = controllerEntry.rightY <= -threshold; - defaultEntry.buttons.rightStickDown = controllerEntry.buttons.rightStickDown; + controllerState.buttons.rightStickDown = controllerState.rightY <= -threshold; + defaultState.buttons.rightStickDown = controllerState.buttons.rightStickDown; break; } } else { switch (axis) { case NpadAxisId::LX: - controllerEntry.leftY = value; - controllerEntry.buttons.leftStickUp = controllerEntry.leftY >= threshold; - controllerEntry.buttons.leftStickDown = controllerEntry.leftY <= -threshold; + controllerState.leftY = value; + controllerState.buttons.leftStickUp = controllerState.leftY >= threshold; + controllerState.buttons.leftStickDown = controllerState.leftY <= -threshold; - defaultEntry.leftX = value; - defaultEntry.buttons.leftStickLeft = defaultEntry.leftX <= -threshold; - defaultEntry.buttons.leftStickRight = defaultEntry.leftX >= threshold; + defaultState.leftX = value; + defaultState.buttons.leftStickLeft = defaultState.leftX <= -threshold; + defaultState.buttons.leftStickRight = defaultState.leftX >= threshold; break; case NpadAxisId::LY: - controllerEntry.leftX = -value; - controllerEntry.buttons.leftStickLeft = controllerEntry.leftX <= -threshold; - controllerEntry.buttons.leftStickRight = controllerEntry.leftX >= threshold; + controllerState.leftX = -value; + controllerState.buttons.leftStickLeft = controllerState.leftX <= -threshold; + controllerState.buttons.leftStickRight = controllerState.leftX >= threshold; - defaultEntry.leftY = value; - defaultEntry.buttons.leftStickUp = defaultEntry.leftY >= threshold; - defaultEntry.buttons.leftStickDown = defaultEntry.leftY <= -threshold; + defaultState.leftY = value; + defaultState.buttons.leftStickUp = defaultState.leftY >= threshold; + defaultState.buttons.leftStickDown = defaultState.leftY <= -threshold; break; case NpadAxisId::RX: - controllerEntry.rightY = value; - controllerEntry.buttons.rightStickUp = controllerEntry.rightY >= threshold; - controllerEntry.buttons.rightStickDown = controllerEntry.rightY <= -threshold; + controllerState.rightY = value; + controllerState.buttons.rightStickUp = controllerState.rightY >= threshold; + controllerState.buttons.rightStickDown = controllerState.rightY <= -threshold; - defaultEntry.rightX = value; - defaultEntry.buttons.rightStickLeft = defaultEntry.rightX <= -threshold; - defaultEntry.buttons.rightStickRight = defaultEntry.rightX >= threshold; + defaultState.rightX = value; + defaultState.buttons.rightStickLeft = defaultState.rightX <= -threshold; + defaultState.buttons.rightStickRight = defaultState.rightX >= threshold; break; case NpadAxisId::RY: - controllerEntry.rightX = -value; - controllerEntry.buttons.rightStickLeft = controllerEntry.rightX <= -threshold; - controllerEntry.buttons.rightStickRight = controllerEntry.rightX >= threshold; + controllerState.rightX = -value; + controllerState.buttons.rightStickLeft = controllerState.rightX <= -threshold; + controllerState.buttons.rightStickRight = controllerState.rightX >= threshold; - defaultEntry.rightY = value; - defaultEntry.buttons.rightStickUp = defaultEntry.rightY >= threshold; - defaultEntry.buttons.rightStickDown = defaultEntry.rightY <= -threshold; + defaultState.rightY = value; + defaultState.buttons.rightStickUp = defaultState.rightY >= threshold; + defaultState.buttons.rightStickDown = defaultState.rightY <= -threshold; break; } } - - globalTimestamp++; } constexpr jlong MsInSecond{1000}; //!< The amount of milliseconds in a single second of time diff --git a/app/src/main/cpp/skyline/input/npad_device.h b/app/src/main/cpp/skyline/input/npad_device.h index b6447891..b4204d28 100644 --- a/app/src/main/cpp/skyline/input/npad_device.h +++ b/app/src/main/cpp/skyline/input/npad_device.h @@ -136,13 +136,14 @@ namespace skyline::input { NpadSection §ion; //!< The section in HID shared memory for this controller NpadControllerInfo *controllerInfo{}; //!< The NpadControllerInfo for this controller's type u64 globalTimestamp{}; //!< An incrementing timestamp that's common across all sections + NpadControllerState controllerState{}, defaultState{}; //!< The current state of the controller (normal and default) /** - * @brief Updates the headers and creates a new entry in HID Shared Memory + * @brief Updates the headers and writes a new entry in HID Shared Memory * @param info The controller info of the NPad that needs to be updated - * @return The next entry that has been created with values from the last entry + * @param entry An entry with the state of the controller */ - NpadControllerState &GetNextEntry(NpadControllerInfo &info); + void WriteNextEntry(NpadControllerInfo &info, NpadControllerState entry); /** * @return The NpadControllerInfo for this controller based on its type @@ -180,6 +181,11 @@ namespace skyline::input { */ void Disconnect(); + /** + * @brief Writes the current state of the controller to HID shared memory + */ + void UpdateSharedMemory(); + /** * @brief Changes the state of buttons to the specified state * @param mask A bit-field mask of all the buttons to change diff --git a/app/src/main/cpp/skyline/input/touch.cpp b/app/src/main/cpp/skyline/input/touch.cpp index 674d733b..79bb0d43 100644 --- a/app/src/main/cpp/skyline/input/touch.cpp +++ b/app/src/main/cpp/skyline/input/touch.cpp @@ -16,20 +16,12 @@ namespace skyline::input { } void TouchManager::SetState(span touchPoints) { - if (!activated) - return; - - const auto &lastEntry{section.entries[section.header.currentEntry]}; - auto entryIndex{(section.header.currentEntry != constant::HidEntryCount - 1) ? section.header.currentEntry + 1 : 0}; - auto &entry{section.entries[entryIndex]}; - touchPoints = touchPoints.first(std::min(touchPoints.size(), entry.data.size())); - entry.globalTimestamp = lastEntry.globalTimestamp + 1; - entry.localTimestamp = lastEntry.localTimestamp + 1; - entry.touchCount = touchPoints.size(); + touchPoints = touchPoints.first(std::min(touchPoints.size(), screenState.data.size())); + screenState.touchCount = touchPoints.size(); for (size_t i{}; i < touchPoints.size(); i++) { const auto &host{touchPoints[i]}; - auto &guest{entry.data[i]}; + auto &guest{screenState.data[i]}; guest.attribute.raw = static_cast(host.attribute); guest.index = static_cast(host.id); guest.positionX = static_cast(host.x); @@ -40,8 +32,20 @@ namespace skyline::input { } // Clear unused touch points - for (size_t i{touchPoints.size()}; i < entry.data.size(); i++) - entry.data[i] = {}; + for (size_t i{touchPoints.size()}; i < screenState.data.size(); i++) + screenState.data[i] = {}; + } + + void TouchManager::UpdateSharedMemory() { + if (!activated) + return; + + const auto &lastEntry{section.entries[section.header.currentEntry]}; + auto entryIndex{(section.header.currentEntry != constant::HidEntryCount - 1) ? section.header.currentEntry + 1 : 0}; + auto &entry{section.entries[entryIndex]}; + entry = screenState; + entry.globalTimestamp = lastEntry.globalTimestamp + 1; + entry.localTimestamp = lastEntry.localTimestamp + 1; section.header.timestamp = util::GetTimeTicks(); section.header.entryCount = std::min(static_cast(section.header.entryCount + 1), constant::HidEntryCount); diff --git a/app/src/main/cpp/skyline/input/touch.h b/app/src/main/cpp/skyline/input/touch.h index 5aaeb0fd..16ac2b0f 100644 --- a/app/src/main/cpp/skyline/input/touch.h +++ b/app/src/main/cpp/skyline/input/touch.h @@ -30,6 +30,7 @@ namespace skyline::input { const DeviceState &state; bool activated{}; TouchScreenSection §ion; + TouchScreenState screenState{}; //!< The current state of the touch screen public: /** @@ -40,5 +41,10 @@ namespace skyline::input { void Activate(); void SetState(span touchPoints); + + /** + * @brief Writes the current state of the touch screen to HID shared memory + */ + void UpdateSharedMemory(); }; }