mirror of
https://github.com/LizardByte/Sunshine.git
synced 2025-01-30 12:32:43 +00:00
Implement input batching for reduced latency
This commit is contained in:
parent
99225c0256
commit
62606a62f9
360
src/input.cpp
360
src/input.cpp
@ -11,6 +11,7 @@ extern "C" {
|
||||
|
||||
#include <bitset>
|
||||
#include <chrono>
|
||||
#include <list>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
@ -145,6 +146,9 @@ namespace input {
|
||||
safe::mail_raw_t::event_t<input::touch_port_t> touch_port_event;
|
||||
platf::rumble_queue_t rumble_queue;
|
||||
|
||||
std::list<std::vector<uint8_t>> input_queue;
|
||||
std::mutex input_queue_lock;
|
||||
|
||||
thread_pool_util::ThreadPool::task_id_t mouse_left_button_timeout;
|
||||
|
||||
input::touch_port_t touch_port;
|
||||
@ -760,12 +764,347 @@ namespace input {
|
||||
gamepad.gamepad_state = gamepad_state;
|
||||
}
|
||||
|
||||
void
|
||||
passthrough_helper(std::shared_ptr<input_t> input, std::vector<std::uint8_t> &&input_data) {
|
||||
void *payload = input_data.data();
|
||||
auto header = (PNV_INPUT_HEADER) payload;
|
||||
enum class batch_result_e {
|
||||
batched, // This entry was batched with the source entry
|
||||
not_batchable, // Not eligible to batch but continue attempts to batch
|
||||
terminate_batch, // Stop trying to batch with this entry
|
||||
};
|
||||
|
||||
switch (util::endian::little(header->magic)) {
|
||||
/**
|
||||
* @brief Batch two relative mouse messages.
|
||||
* @param dest The original packet to batch into.
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return `batch_result_e` : The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PNV_REL_MOUSE_MOVE_PACKET dest, PNV_REL_MOUSE_MOVE_PACKET src) {
|
||||
short deltaX, deltaY;
|
||||
|
||||
// Batching is safe as long as the result doesn't overflow a 16-bit integer
|
||||
if (!__builtin_add_overflow(util::endian::big(dest->deltaX), util::endian::big(src->deltaX), &deltaX)) {
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
if (!__builtin_add_overflow(util::endian::big(dest->deltaY), util::endian::big(src->deltaY), &deltaY)) {
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
|
||||
// Take the sum of deltas
|
||||
dest->deltaX = util::endian::big(deltaX);
|
||||
dest->deltaY = util::endian::big(deltaY);
|
||||
return batch_result_e::batched;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Batch two absolute mouse messages.
|
||||
* @param dest The original packet to batch into.
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return `batch_result_e` : The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PNV_ABS_MOUSE_MOVE_PACKET dest, PNV_ABS_MOUSE_MOVE_PACKET src) {
|
||||
// Batching must only happen if the reference width and height don't change
|
||||
if (dest->width != src->width || dest->height != src->height) {
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
|
||||
// Take the latest absolute position
|
||||
*dest = *src;
|
||||
return batch_result_e::batched;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Batch two vertical scroll messages.
|
||||
* @param dest The original packet to batch into.
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return `batch_result_e` : The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PNV_SCROLL_PACKET dest, PNV_SCROLL_PACKET src) {
|
||||
short scrollAmt;
|
||||
|
||||
// Batching is safe as long as the result doesn't overflow a 16-bit integer
|
||||
if (!__builtin_add_overflow(util::endian::big(dest->scrollAmt1), util::endian::big(src->scrollAmt1), &scrollAmt)) {
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
|
||||
// Take the sum of delta
|
||||
dest->scrollAmt1 = util::endian::big(scrollAmt);
|
||||
dest->scrollAmt2 = util::endian::big(scrollAmt);
|
||||
return batch_result_e::batched;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Batch two horizontal scroll messages.
|
||||
* @param dest The original packet to batch into.
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return `batch_result_e` : The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PSS_HSCROLL_PACKET dest, PSS_HSCROLL_PACKET src) {
|
||||
short scrollAmt;
|
||||
|
||||
// Batching is safe as long as the result doesn't overflow a 16-bit integer
|
||||
if (!__builtin_add_overflow(util::endian::big(dest->scrollAmount), util::endian::big(src->scrollAmount), &scrollAmt)) {
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
|
||||
// Take the sum of delta
|
||||
dest->scrollAmount = util::endian::big(scrollAmt);
|
||||
return batch_result_e::batched;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Batch two controller state messages.
|
||||
* @param dest The original packet to batch into.
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return `batch_result_e` : The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PNV_MULTI_CONTROLLER_PACKET dest, PNV_MULTI_CONTROLLER_PACKET src) {
|
||||
// Do not allow batching if the active controllers change
|
||||
if (dest->activeGamepadMask != src->activeGamepadMask) {
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
|
||||
// We can only batch entries for the same controller, but allow batching attempts to continue
|
||||
// in case we have more packets for this controller later in the queue.
|
||||
if (dest->controllerNumber != src->controllerNumber) {
|
||||
return batch_result_e::not_batchable;
|
||||
}
|
||||
|
||||
// Do not allow batching if the button state changes on this controller
|
||||
if (dest->buttonFlags != src->buttonFlags || dest->buttonFlags2 != src->buttonFlags2) {
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
|
||||
// Take the latest state
|
||||
*dest = *src;
|
||||
return batch_result_e::batched;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Batch two touch messages.
|
||||
* @param dest The original packet to batch into.
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return `batch_result_e` : The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PSS_TOUCH_PACKET dest, PSS_TOUCH_PACKET src) {
|
||||
// Only batch hover or move events
|
||||
if (dest->eventType != LI_TOUCH_EVENT_MOVE &&
|
||||
dest->eventType != LI_TOUCH_EVENT_HOVER) {
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
|
||||
// Don't batch beyond state changing events
|
||||
if (src->eventType != LI_TOUCH_EVENT_MOVE &&
|
||||
src->eventType != LI_TOUCH_EVENT_HOVER) {
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
|
||||
// Batched events must be the same pointer ID
|
||||
if (dest->pointerId != src->pointerId) {
|
||||
return batch_result_e::not_batchable;
|
||||
}
|
||||
|
||||
// The pointer must be in the same state
|
||||
if (dest->eventType != src->eventType) {
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
|
||||
// Take the latest state
|
||||
*dest = *src;
|
||||
return batch_result_e::batched;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Batch two pen messages.
|
||||
* @param dest The original packet to batch into.
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return `batch_result_e` : The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PSS_PEN_PACKET dest, PSS_PEN_PACKET src) {
|
||||
// Only batch hover or move events
|
||||
if (dest->eventType != LI_TOUCH_EVENT_MOVE &&
|
||||
dest->eventType != LI_TOUCH_EVENT_HOVER) {
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
|
||||
// Batched events must be the same type
|
||||
if (dest->eventType != src->eventType) {
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
|
||||
// Do not allow batching if the button state changes
|
||||
if (dest->penButtons != src->penButtons) {
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
|
||||
// Do not batch beyond tool changes
|
||||
if (dest->toolType != src->toolType) {
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
|
||||
// Take the latest state
|
||||
*dest = *src;
|
||||
return batch_result_e::batched;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Batch two controller touch messages.
|
||||
* @param dest The original packet to batch into.
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return `batch_result_e` : The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PSS_CONTROLLER_TOUCH_PACKET dest, PSS_CONTROLLER_TOUCH_PACKET src) {
|
||||
// Only batch hover or move events
|
||||
if (dest->eventType != LI_TOUCH_EVENT_MOVE &&
|
||||
dest->eventType != LI_TOUCH_EVENT_HOVER) {
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
|
||||
// We can only batch entries for the same controller, but allow batching attempts to continue
|
||||
// in case we have more packets for this controller later in the queue.
|
||||
if (dest->controllerNumber != src->controllerNumber) {
|
||||
return batch_result_e::not_batchable;
|
||||
}
|
||||
|
||||
// Don't batch beyond state changing events
|
||||
if (src->eventType != LI_TOUCH_EVENT_MOVE &&
|
||||
src->eventType != LI_TOUCH_EVENT_HOVER) {
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
|
||||
// Batched events must be the same pointer ID
|
||||
if (dest->pointerId != src->pointerId) {
|
||||
return batch_result_e::not_batchable;
|
||||
}
|
||||
|
||||
// The pointer must be in the same state
|
||||
if (dest->eventType != src->eventType) {
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
|
||||
// Take the latest state
|
||||
*dest = *src;
|
||||
return batch_result_e::batched;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Batch two controller motion messages.
|
||||
* @param dest The original packet to batch into.
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return `batch_result_e` : The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PSS_CONTROLLER_MOTION_PACKET dest, PSS_CONTROLLER_MOTION_PACKET src) {
|
||||
// We can only batch entries for the same controller, but allow batching attempts to continue
|
||||
// in case we have more packets for this controller later in the queue.
|
||||
if (dest->controllerNumber != src->controllerNumber) {
|
||||
return batch_result_e::not_batchable;
|
||||
}
|
||||
|
||||
// Batched events must be the same sensor
|
||||
if (dest->motionType != src->motionType) {
|
||||
return batch_result_e::not_batchable;
|
||||
}
|
||||
|
||||
// Take the latest state
|
||||
*dest = *src;
|
||||
return batch_result_e::batched;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Batch two input messages.
|
||||
* @param dest The original packet to batch into.
|
||||
* @param src A later packet to attempt to batch.
|
||||
* @return `batch_result_e` : The status of the batching operation.
|
||||
*/
|
||||
batch_result_e
|
||||
batch(PNV_INPUT_HEADER dest, PNV_INPUT_HEADER src) {
|
||||
// We can only batch if the packet types are the same
|
||||
if (dest->magic != src->magic) {
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
|
||||
// We can only batch certain message types
|
||||
switch (util::endian::little(dest->magic)) {
|
||||
case MOUSE_MOVE_REL_MAGIC_GEN5:
|
||||
return batch((PNV_REL_MOUSE_MOVE_PACKET) dest, (PNV_REL_MOUSE_MOVE_PACKET) src);
|
||||
case MOUSE_MOVE_ABS_MAGIC:
|
||||
return batch((PNV_ABS_MOUSE_MOVE_PACKET) dest, (PNV_ABS_MOUSE_MOVE_PACKET) src);
|
||||
case SCROLL_MAGIC_GEN5:
|
||||
return batch((PNV_SCROLL_PACKET) dest, (PNV_SCROLL_PACKET) src);
|
||||
case SS_HSCROLL_MAGIC:
|
||||
return batch((PSS_HSCROLL_PACKET) dest, (PSS_HSCROLL_PACKET) src);
|
||||
case MULTI_CONTROLLER_MAGIC_GEN5:
|
||||
return batch((PNV_MULTI_CONTROLLER_PACKET) dest, (PNV_MULTI_CONTROLLER_PACKET) src);
|
||||
case SS_TOUCH_MAGIC:
|
||||
return batch((PSS_TOUCH_PACKET) dest, (PSS_TOUCH_PACKET) src);
|
||||
case SS_PEN_MAGIC:
|
||||
return batch((PSS_PEN_PACKET) dest, (PSS_PEN_PACKET) src);
|
||||
case SS_CONTROLLER_TOUCH_MAGIC:
|
||||
return batch((PSS_CONTROLLER_TOUCH_PACKET) dest, (PSS_CONTROLLER_TOUCH_PACKET) src);
|
||||
case SS_CONTROLLER_MOTION_MAGIC:
|
||||
return batch((PSS_CONTROLLER_MOTION_PACKET) dest, (PSS_CONTROLLER_MOTION_PACKET) src);
|
||||
default:
|
||||
// Not a batchable message type
|
||||
return batch_result_e::terminate_batch;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called on a thread pool thread to process an input message.
|
||||
* @param input The input context pointer.
|
||||
*/
|
||||
void
|
||||
passthrough_next_message(std::shared_ptr<input_t> input) {
|
||||
// 'entry' backs the 'payload' pointer, so they must remain in scope together
|
||||
std::vector<uint8_t> entry;
|
||||
PNV_INPUT_HEADER payload;
|
||||
|
||||
// Lock the input queue while batching, but release it before sending
|
||||
// the input to the OS. This avoids potentially lengthy lock contention
|
||||
// in the control stream thread while input is being processed by the OS.
|
||||
{
|
||||
std::lock_guard<std::mutex> lg(input->input_queue_lock);
|
||||
|
||||
// If all entries have already been processed, nothing to do
|
||||
if (input->input_queue.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pop off the first entry, which we will send
|
||||
entry = input->input_queue.front();
|
||||
payload = (PNV_INPUT_HEADER) entry.data();
|
||||
input->input_queue.pop_front();
|
||||
|
||||
// Try to batch with remaining items on the queue
|
||||
auto i = input->input_queue.begin();
|
||||
while (i != input->input_queue.end()) {
|
||||
auto batchable_entry = *i;
|
||||
auto batchable_payload = (PNV_INPUT_HEADER) batchable_entry.data();
|
||||
|
||||
auto batch_result = batch(payload, batchable_payload);
|
||||
if (batch_result == batch_result_e::terminate_batch) {
|
||||
// Stop batching
|
||||
break;
|
||||
}
|
||||
else if (batch_result == batch_result_e::batched) {
|
||||
// Erase this entry since it was batched
|
||||
i = input->input_queue.erase(i);
|
||||
}
|
||||
else {
|
||||
// We couldn't batch this entry, but try to batch later entries.
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send the batched input to the OS
|
||||
switch (util::endian::little(payload->magic)) {
|
||||
case MOUSE_MOVE_REL_MAGIC_GEN5:
|
||||
passthrough(input, (PNV_REL_MOUSE_MOVE_PACKET) payload);
|
||||
break;
|
||||
@ -795,9 +1134,18 @@ namespace input {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called on the control stream thread to queue an input message.
|
||||
* @param input The input context pointer.
|
||||
* @param input_data The input message.
|
||||
*/
|
||||
void
|
||||
passthrough(std::shared_ptr<input_t> &input, std::vector<std::uint8_t> &&input_data) {
|
||||
task_pool.push(passthrough_helper, input, move_by_copy_util::cmove(input_data));
|
||||
{
|
||||
std::lock_guard<std::mutex> lg(input->input_queue_lock);
|
||||
input->input_queue.push_back(std::move(input_data));
|
||||
}
|
||||
task_pool.push(passthrough_next_message, input);
|
||||
}
|
||||
|
||||
void
|
||||
|
Loading…
x
Reference in New Issue
Block a user