mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-11-12 14:57:20 +00:00
[client/idd] input: reduce network latency
Route input through a dedicated high-priority stream, send pointer motion as cumulative datagrams, and recover suspected loss reliably. Reduce downstream input queuing with age-aware coalescing and input thread scheduling.
This commit is contained in:
@@ -601,7 +601,7 @@ static LG_TransportStatus networkAudioSendDatagramNL(NetworkAudio * audio,
|
||||
{
|
||||
return networkConnectionSendDatagram(audio->connection,
|
||||
LG_NET_SERVICE_AUDIO, messageType, audio->serviceVersion, 0,
|
||||
audio->subscriptionEpoch, requestID, payload, payloadLength, false);
|
||||
audio->subscriptionEpoch, requestID, payload, payloadLength, 0, false);
|
||||
}
|
||||
|
||||
static bool networkAudioSendSubscribeNL(NetworkAudio * audio)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -51,6 +51,8 @@ typedef void (*NetworkConnectionStateFn)(void * opaque, bool connected,
|
||||
LG_TransportStatus reason);
|
||||
typedef void (*NetworkSendReadyFn)(void * opaque, LGNetService service,
|
||||
NetworkPacketDelivery delivery);
|
||||
typedef void (*NetworkDatagramLostFn)(void * opaque, LGNetService service,
|
||||
uint64_t componentEpoch, uint64_t applicationSequence);
|
||||
|
||||
typedef struct NetworkConnectionParams
|
||||
{
|
||||
@@ -63,6 +65,7 @@ typedef struct NetworkConnectionParams
|
||||
NetworkPacketFn packet;
|
||||
NetworkConnectionStateFn state;
|
||||
NetworkSendReadyFn sendReady;
|
||||
NetworkDatagramLostFn datagramLost;
|
||||
const LGNetCapability * capabilities;
|
||||
uint32_t capabilityCount;
|
||||
void * opaque;
|
||||
@@ -92,7 +95,8 @@ LG_TransportStatus networkConnectionSendDatagram(
|
||||
NetworkConnection * connection, LGNetService service,
|
||||
uint16_t messageType, uint16_t serviceVersion,
|
||||
LGNetEnvelopeFlags flags, uint64_t componentEpoch, uint64_t requestID,
|
||||
const void * payload, uint32_t payloadLength, bool discardIfBlocked);
|
||||
const void * payload, uint32_t payloadLength,
|
||||
uint64_t applicationSequence, bool discardIfBlocked);
|
||||
LG_TransportStatus networkConnectionSendBulk(NetworkConnection * connection,
|
||||
LGNetService service, uint16_t messageType, uint16_t serviceVersion,
|
||||
LGNetEnvelopeFlags flags, uint64_t componentEpoch, uint64_t requestID,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -37,5 +37,9 @@ void networkInputDestroy(NetworkInput ** input);
|
||||
const LG_InputOps * networkInputGetOps(void);
|
||||
|
||||
void networkInputConnectionState(NetworkInput * input, bool connected);
|
||||
void networkInputSendReady(NetworkInput * input,
|
||||
NetworkPacketDelivery delivery);
|
||||
void networkInputDatagramLost(NetworkInput * input,
|
||||
uint64_t claimEpoch, uint64_t pointerSequence);
|
||||
bool networkInputPacket(NetworkInput * input,
|
||||
const LGNetPacketView * packet, bool datagram);
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
#define NETWORK_FILE_MAX_PAYLOAD \
|
||||
(LG_NET_FILE_CHUNK_HEADER_WIRE_SIZE + NETWORK_TRANSFER_CHUNK)
|
||||
#define NETWORK_VIDEO_MAX_PAYLOAD LG_NET_MAX_PAYLOAD_LENGTH
|
||||
#define NETWORK_INPUT_MAX_PAYLOAD LG_NET_INPUT_ABSOLUTE_WIRE_SIZE
|
||||
|
||||
static const LGNetCapability NETWORK_CAPABILITIES[] =
|
||||
{
|
||||
@@ -91,9 +92,9 @@ static const LGNetCapability NETWORK_CAPABILITIES[] =
|
||||
.flags = LG_NET_CAPABILITY_RELIABLE |
|
||||
LG_NET_CAPABILITY_DATAGRAM,
|
||||
.features = 0,
|
||||
.maxPayloadLength = NETWORK_MAX_PAYLOAD,
|
||||
.maxDatagramPayload = NETWORK_MAX_DATAGRAM,
|
||||
.maxBidirectionalStreams = 0,
|
||||
.maxPayloadLength = NETWORK_INPUT_MAX_PAYLOAD,
|
||||
.maxDatagramPayload = NETWORK_INPUT_MAX_PAYLOAD,
|
||||
.maxBidirectionalStreams = 1,
|
||||
.maxUnidirectionalStreams = 0,
|
||||
},
|
||||
{
|
||||
@@ -473,6 +474,8 @@ static void networkSendReady(void * opaque, LGNetService service,
|
||||
LG_Transport * this = opaque;
|
||||
const bool datagram =
|
||||
delivery == NETWORK_PACKET_DELIVERY_DATAGRAM;
|
||||
if (service == LG_NET_SERVICE_INPUT)
|
||||
networkInputSendReady(this->input, delivery);
|
||||
if (service == LG_NET_SERVICE_AUDIO &&
|
||||
delivery != NETWORK_PACKET_DELIVERY_BULK)
|
||||
networkAudioSendReady(this->audio, datagram);
|
||||
@@ -484,6 +487,15 @@ static void networkSendReady(void * opaque, LGNetService service,
|
||||
networkVideoSendReady(this->video, datagram);
|
||||
}
|
||||
|
||||
static void networkDatagramLost(void * opaque, LGNetService service,
|
||||
uint64_t componentEpoch, uint64_t applicationSequence)
|
||||
{
|
||||
LG_Transport * this = opaque;
|
||||
if (service == LG_NET_SERVICE_INPUT)
|
||||
networkInputDatagramLost(
|
||||
this->input, componentEpoch, applicationSequence);
|
||||
}
|
||||
|
||||
static bool networkCreate(LG_Transport ** result)
|
||||
{
|
||||
if (!result)
|
||||
@@ -552,6 +564,7 @@ static bool networkCreate(LG_Transport ** result)
|
||||
.packet = networkPacket,
|
||||
.state = networkConnectionState,
|
||||
.sendReady = networkSendReady,
|
||||
.datagramLost = networkDatagramLost,
|
||||
.capabilities = NETWORK_CAPABILITIES,
|
||||
.capabilityCount = sizeof(NETWORK_CAPABILITIES) /
|
||||
sizeof(NETWORK_CAPABILITIES[0]),
|
||||
|
||||
@@ -592,7 +592,7 @@ static LG_TransportStatus networkVideoSendDatagramNL(NetworkVideo * video,
|
||||
{
|
||||
return networkConnectionSendDatagram(video->connection,
|
||||
LG_NET_SERVICE_VIDEO, messageType, video->serviceVersion, 0,
|
||||
componentEpoch, 0, payload, payloadLength, true);
|
||||
componentEpoch, 0, payload, payloadLength, 0, true);
|
||||
}
|
||||
|
||||
static void networkVideoSendSubscribeNL(NetworkVideo * video, uint64_t now)
|
||||
|
||||
@@ -27,9 +27,12 @@
|
||||
#include "CDebug.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <avrt.h>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
#pragma comment(lib, "avrt.lib")
|
||||
|
||||
const DWORD CPipeEndpoint::CLIENT_RETRY_INITIAL_MS = 100;
|
||||
const DWORD CPipeEndpoint::CLIENT_RETRY_MAX_MS = 2000;
|
||||
const DWORD CPipeEndpoint::SERVER_RETRY_MS = 1000;
|
||||
@@ -195,22 +198,24 @@ bool CPipeEndpoint::Start(
|
||||
size_t messageSize,
|
||||
const SECURITY_ATTRIBUTES * serverSecurity,
|
||||
DWORD serverOpenMode,
|
||||
DWORD serverPipeMode)
|
||||
DWORD serverPipeMode,
|
||||
ThreadScheduling threadScheduling)
|
||||
{
|
||||
Stop();
|
||||
|
||||
if (!pipeName || !*pipeName || !messageSize || messageSize > MAXDWORD)
|
||||
return false;
|
||||
|
||||
m_pipeName = pipeName;
|
||||
m_mode = mode;
|
||||
m_messageSize = messageSize;
|
||||
m_hasServerSecurity = serverSecurity != nullptr;
|
||||
m_serverSecurity = serverSecurity ? *serverSecurity :
|
||||
m_pipeName = pipeName;
|
||||
m_mode = mode;
|
||||
m_messageSize = messageSize;
|
||||
m_threadScheduling = threadScheduling;
|
||||
m_hasServerSecurity = serverSecurity != nullptr;
|
||||
m_serverSecurity = serverSecurity ? *serverSecurity :
|
||||
SECURITY_ATTRIBUTES {};
|
||||
m_serverOpenMode = serverOpenMode;
|
||||
m_serverPipeMode = serverPipeMode;
|
||||
m_stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
m_serverOpenMode = serverOpenMode;
|
||||
m_serverPipeMode = serverPipeMode;
|
||||
m_stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
if (!m_stopEvent)
|
||||
{
|
||||
const DWORD error = GetLastError();
|
||||
@@ -362,11 +367,30 @@ DWORD WINAPI CPipeEndpoint::ThreadProc(void * context)
|
||||
|
||||
void CPipeEndpoint::Thread()
|
||||
{
|
||||
HANDLE avTaskHandle = nullptr;
|
||||
if (m_threadScheduling == ThreadScheduling::Input)
|
||||
{
|
||||
DWORD avTask = 0;
|
||||
bool prioritySet = false;
|
||||
|
||||
avTaskHandle = AvSetMmThreadCharacteristicsW(L"Distribution", &avTask);
|
||||
if (avTaskHandle)
|
||||
prioritySet =
|
||||
AvSetMmThreadPriority(avTaskHandle, AVRT_PRIORITY_HIGH) != FALSE;
|
||||
if (!prioritySet && !SetThreadPriority(
|
||||
GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL))
|
||||
DEBUG_WARN_HR(GetLastError(),
|
||||
"Failed to raise input pipe thread priority");
|
||||
}
|
||||
|
||||
if (m_mode == Mode::Server)
|
||||
RunServer();
|
||||
else
|
||||
RunClient();
|
||||
|
||||
if (avTaskHandle)
|
||||
AvRevertMmThreadCharacteristics(avTaskHandle);
|
||||
|
||||
Atomic::Store(m_running, false);
|
||||
Atomic::Store(m_connected, false);
|
||||
}
|
||||
|
||||
@@ -80,6 +80,12 @@ public:
|
||||
Client,
|
||||
};
|
||||
|
||||
enum class ThreadScheduling
|
||||
{
|
||||
Normal,
|
||||
Input,
|
||||
};
|
||||
|
||||
CPipeEndpoint() = default;
|
||||
~CPipeEndpoint();
|
||||
|
||||
@@ -92,7 +98,8 @@ public:
|
||||
_In_ size_t messageSize,
|
||||
_In_opt_ const SECURITY_ATTRIBUTES * serverSecurity = nullptr,
|
||||
_In_ DWORD serverOpenMode = 0,
|
||||
_In_ DWORD serverPipeMode = 0);
|
||||
_In_ DWORD serverPipeMode = 0,
|
||||
_In_ ThreadScheduling threadScheduling = ThreadScheduling::Normal);
|
||||
void Stop();
|
||||
void DisconnectClient();
|
||||
|
||||
@@ -158,13 +165,14 @@ private:
|
||||
void ClosePipe(_In_ HANDLE pipe);
|
||||
|
||||
std::wstring m_pipeName;
|
||||
size_t m_messageSize = 0;
|
||||
Mode m_mode = Mode::Client;
|
||||
IPipeEndpointHandler * m_handler = nullptr;
|
||||
SECURITY_ATTRIBUTES m_serverSecurity = {};
|
||||
bool m_hasServerSecurity = false;
|
||||
DWORD m_serverOpenMode = 0;
|
||||
DWORD m_serverPipeMode = 0;
|
||||
size_t m_messageSize = 0;
|
||||
Mode m_mode = Mode::Client;
|
||||
ThreadScheduling m_threadScheduling = ThreadScheduling::Normal;
|
||||
IPipeEndpointHandler * m_handler = nullptr;
|
||||
SECURITY_ATTRIBUTES m_serverSecurity = {};
|
||||
bool m_hasServerSecurity = false;
|
||||
DWORD m_serverOpenMode = 0;
|
||||
DWORD m_serverPipeMode = 0;
|
||||
|
||||
std::atomic<bool> m_running { false };
|
||||
std::atomic<bool> m_connected { false };
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "CSRWLock.h"
|
||||
#include "InputPipeProtocol.h"
|
||||
|
||||
#include <avrt.h>
|
||||
#include <string.h>
|
||||
|
||||
CInputPipeServer g_inputPipeServer;
|
||||
@@ -43,9 +44,18 @@ bool CInputPipeServer::Init()
|
||||
m_performanceFrequency.QuadPart = 0;
|
||||
if (!QueryPerformanceFrequency(&m_performanceFrequency))
|
||||
m_performanceFrequency.QuadPart = 0;
|
||||
m_lastStatistics = GetTickCount64();
|
||||
m_stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
m_queueEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||
static_assert(MOTION_AGE_BUDGET_MS &&
|
||||
1000 % MOTION_AGE_BUDGET_MS == 0,
|
||||
"motion age budget must evenly divide one second");
|
||||
const int64_t budgetDivisor = 1000 / MOTION_AGE_BUDGET_MS;
|
||||
m_motionBudgetTicks =
|
||||
m_performanceFrequency.QuadPart / budgetDivisor;
|
||||
if (m_performanceFrequency.QuadPart % budgetDivisor)
|
||||
++m_motionBudgetTicks;
|
||||
|
||||
m_lastStatistics = GetTickCount64();
|
||||
m_stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
m_queueEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr);
|
||||
if (!m_stopEvent || !m_queueEvent)
|
||||
{
|
||||
DEBUG_ERROR_HR(GetLastError(),
|
||||
@@ -119,6 +129,89 @@ void CInputPipeServer::DeInit()
|
||||
m_statSlowWrites = 0;
|
||||
m_statWriteTicks = 0;
|
||||
m_statMaxWriteTicks = 0;
|
||||
|
||||
m_motionBudgetTicks = 0;
|
||||
m_performanceFrequency.QuadPart = 0;
|
||||
}
|
||||
|
||||
bool CInputPipeServer::MotionBudgetExceededLocked() const
|
||||
{
|
||||
if (m_queueCount >= MOTION_EMERGENCY_DEPTH)
|
||||
return true;
|
||||
if (!m_queueCount || !m_motionBudgetTicks)
|
||||
return false;
|
||||
|
||||
LARGE_INTEGER now = {};
|
||||
if (!QueryPerformanceCounter(&now))
|
||||
return false;
|
||||
|
||||
const int64_t oldest = m_queue[m_queueHead].enqueueTicks;
|
||||
return oldest > 0 && now.QuadPart >= oldest &&
|
||||
now.QuadPart - oldest >= m_motionBudgetTicks;
|
||||
}
|
||||
|
||||
CInputPipeServer::QueueItem& CInputPipeServer::GetQueueItemLocked(
|
||||
size_t position)
|
||||
{
|
||||
return m_queue[(m_queueHead + position) % QUEUE_LENGTH];
|
||||
}
|
||||
|
||||
void CInputPipeServer::RemoveQueueItemLocked(size_t position)
|
||||
{
|
||||
for (size_t i = position; i + 1 < m_queueCount; ++i)
|
||||
GetQueueItemLocked(i) = GetQueueItemLocked(i + 1);
|
||||
--m_queueCount;
|
||||
}
|
||||
|
||||
bool CInputPipeServer::CompactQueuedMotionLocked()
|
||||
{
|
||||
for (size_t i = 0; i + 1 < m_queueCount; ++i)
|
||||
{
|
||||
QueueItem& first = GetQueueItemLocked(i);
|
||||
QueueItem& second = GetQueueItemLocked(i + 1);
|
||||
if (!first.pureMotion || !second.pureMotion ||
|
||||
first.type != second.type || first.state != second.state)
|
||||
continue;
|
||||
|
||||
if (first.type == LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE)
|
||||
{
|
||||
if (first.payload.mouseRelative.buttons !=
|
||||
second.payload.mouseRelative.buttons ||
|
||||
first.payload.mouseRelative.wheel ||
|
||||
second.payload.mouseRelative.wheel)
|
||||
continue;
|
||||
|
||||
const int64_t x = static_cast<int64_t>(
|
||||
first.payload.mouseRelative.deltaX) +
|
||||
second.payload.mouseRelative.deltaX;
|
||||
const int64_t y = static_cast<int64_t>(
|
||||
first.payload.mouseRelative.deltaY) +
|
||||
second.payload.mouseRelative.deltaY;
|
||||
if (x < LG_INPUT_MOUSE_DELTA_MIN || x > LG_INPUT_MOUSE_DELTA_MAX ||
|
||||
y < LG_INPUT_MOUSE_DELTA_MIN || y > LG_INPUT_MOUSE_DELTA_MAX)
|
||||
continue;
|
||||
|
||||
first.payload.mouseRelative.deltaX = static_cast<int32_t>(x);
|
||||
first.payload.mouseRelative.deltaY = static_cast<int32_t>(y);
|
||||
RemoveQueueItemLocked(i + 1);
|
||||
++m_statRelativeCoalesced;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (first.type != LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE ||
|
||||
first.payload.mouseAbsolute.buttons !=
|
||||
second.payload.mouseAbsolute.buttons ||
|
||||
first.payload.mouseAbsolute.wheel ||
|
||||
second.payload.mouseAbsolute.wheel)
|
||||
continue;
|
||||
|
||||
first.payload.mouseAbsolute.x = second.payload.mouseAbsolute.x;
|
||||
first.payload.mouseAbsolute.y = second.payload.mouseAbsolute.y;
|
||||
RemoveQueueItemLocked(i + 1);
|
||||
++m_statAbsoluteCoalesced;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CInputPipeServer::QueueLocked(
|
||||
@@ -126,20 +219,26 @@ bool CInputPipeServer::QueueLocked(
|
||||
const KVMFRInputPayload& payload,
|
||||
bool pureMotion)
|
||||
{
|
||||
const bool motionBudgetExceeded = MotionBudgetExceededLocked();
|
||||
if (motionBudgetExceeded)
|
||||
while (CompactQueuedMotionLocked())
|
||||
{
|
||||
}
|
||||
|
||||
if (m_queueCount)
|
||||
{
|
||||
const size_t tailIndex =
|
||||
(m_queueHead + m_queueCount - 1) % QUEUE_LENGTH;
|
||||
QueueItem& tail = m_queue[tailIndex];
|
||||
QueueItem& tail = m_queue[tailIndex];
|
||||
if (tail.type == type)
|
||||
{
|
||||
if (type == LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE &&
|
||||
m_queueCount >= MOTION_COALESCE_THRESHOLD &&
|
||||
tail.pureMotion && pureMotion &&
|
||||
tail.payload.mouseRelative.wheel == 0 &&
|
||||
payload.mouseRelative.wheel == 0 &&
|
||||
tail.payload.mouseRelative.buttons ==
|
||||
payload.mouseRelative.buttons)
|
||||
payload.mouseRelative.buttons &&
|
||||
motionBudgetExceeded)
|
||||
{
|
||||
const int64_t x = static_cast<int64_t>(
|
||||
tail.payload.mouseRelative.deltaX) +
|
||||
@@ -181,16 +280,23 @@ bool CInputPipeServer::QueueRawLocked(
|
||||
const KVMFRInputPayload& payload,
|
||||
bool pureMotion)
|
||||
{
|
||||
if (MotionBudgetExceededLocked())
|
||||
while (CompactQueuedMotionLocked())
|
||||
{
|
||||
}
|
||||
if (m_queueCount == QUEUE_LENGTH)
|
||||
return false;
|
||||
|
||||
const size_t index =
|
||||
const size_t index =
|
||||
(m_queueHead + m_queueCount) % QUEUE_LENGTH;
|
||||
m_queue[index].type = type;
|
||||
m_queue[index].state =
|
||||
LARGE_INTEGER enqueueTime = {};
|
||||
QueryPerformanceCounter(&enqueueTime);
|
||||
m_queue[index].type = type;
|
||||
m_queue[index].state =
|
||||
Atomic::Load(m_state, std::memory_order_relaxed);
|
||||
m_queue[index].payload = payload;
|
||||
m_queue[index].pureMotion = pureMotion;
|
||||
m_queue[index].enqueueTicks = enqueueTime.QuadPart;
|
||||
m_queue[index].payload = payload;
|
||||
m_queue[index].pureMotion = pureMotion;
|
||||
++m_queueCount;
|
||||
++m_statEnqueued;
|
||||
if (m_queueCount > m_statQueueHighWater)
|
||||
@@ -564,6 +670,19 @@ DWORD WINAPI CInputPipeServer::ThreadProc(void * context)
|
||||
|
||||
void CInputPipeServer::Thread()
|
||||
{
|
||||
DWORD avTask = 0;
|
||||
HANDLE avTaskHandle = nullptr;
|
||||
bool prioritySet = false;
|
||||
|
||||
avTaskHandle = AvSetMmThreadCharacteristicsW(L"Distribution", &avTask);
|
||||
if (avTaskHandle)
|
||||
prioritySet =
|
||||
AvSetMmThreadPriority(avTaskHandle, AVRT_PRIORITY_HIGH) != FALSE;
|
||||
if (!prioritySet && !SetThreadPriority(
|
||||
GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL))
|
||||
DEBUG_WARN_HR(GetLastError(),
|
||||
"Failed to raise LGInput sender thread priority");
|
||||
|
||||
const HANDLE handles[] = { m_stopEvent, m_queueEvent };
|
||||
for (;;)
|
||||
{
|
||||
@@ -588,6 +707,8 @@ void CInputPipeServer::Thread()
|
||||
}
|
||||
|
||||
Invalidate(0, false);
|
||||
if (avTaskHandle)
|
||||
AvRevertMmThreadCharacteristics(avTaskHandle);
|
||||
}
|
||||
|
||||
void CInputPipeServer::OnPipeConnected()
|
||||
|
||||
@@ -32,14 +32,16 @@
|
||||
class CInputPipeServer : public IInputSink, private IPipeEndpointHandler
|
||||
{
|
||||
private:
|
||||
static constexpr size_t QUEUE_LENGTH = 128;
|
||||
static constexpr size_t MOTION_COALESCE_THRESHOLD = QUEUE_LENGTH / 2;
|
||||
static constexpr DWORD STATISTICS_INTERVAL_MS = 5000;
|
||||
static constexpr size_t QUEUE_LENGTH = 128;
|
||||
static constexpr size_t MOTION_EMERGENCY_DEPTH = 8;
|
||||
static constexpr DWORD MOTION_AGE_BUDGET_MS = 1;
|
||||
static constexpr DWORD STATISTICS_INTERVAL_MS = 5000;
|
||||
|
||||
struct QueueItem
|
||||
{
|
||||
KVMFRInputPayload payload;
|
||||
uint64_t state;
|
||||
int64_t enqueueTicks;
|
||||
LGInputPipeMessageType type;
|
||||
bool pureMotion;
|
||||
};
|
||||
@@ -84,15 +86,20 @@ private:
|
||||
uint64_t m_statResyncDiscarded = 0;
|
||||
size_t m_statQueueHighWater = 0;
|
||||
|
||||
uint64_t m_statSent = 0;
|
||||
uint64_t m_statStale = 0;
|
||||
uint64_t m_statWriteFailed = 0;
|
||||
uint64_t m_statSlowWrites = 0;
|
||||
int64_t m_statWriteTicks = 0;
|
||||
int64_t m_statMaxWriteTicks = 0;
|
||||
ULONGLONG m_lastStatistics = 0;
|
||||
uint64_t m_statSent = 0;
|
||||
uint64_t m_statStale = 0;
|
||||
uint64_t m_statWriteFailed = 0;
|
||||
uint64_t m_statSlowWrites = 0;
|
||||
int64_t m_statWriteTicks = 0;
|
||||
int64_t m_statMaxWriteTicks = 0;
|
||||
int64_t m_motionBudgetTicks = 0;
|
||||
ULONGLONG m_lastStatistics = 0;
|
||||
LARGE_INTEGER m_performanceFrequency = {};
|
||||
|
||||
bool MotionBudgetExceededLocked() const;
|
||||
QueueItem& GetQueueItemLocked(size_t position);
|
||||
void RemoveQueueItemLocked(size_t position);
|
||||
bool CompactQueuedMotionLocked();
|
||||
bool QueueLocked(LGInputPipeMessageType type,
|
||||
const KVMFRInputPayload& payload, bool pureMotion);
|
||||
bool QueueRawLocked(LGInputPipeMessageType type,
|
||||
|
||||
@@ -207,29 +207,31 @@ bool CInputHub::Bind(
|
||||
void CInputHub::Unbind(BackendId backend, uint32_t epoch)
|
||||
{
|
||||
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
|
||||
Source * selected = nullptr;
|
||||
IInputSource * input = nullptr;
|
||||
bool stop = false;
|
||||
Source * selected = nullptr;
|
||||
IInputSource * input = nullptr;
|
||||
bool stop = false;
|
||||
{
|
||||
CSRWExclusiveLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
for (Source& source : m_sources)
|
||||
if ((source.active || source.failed || source.reserved) &&
|
||||
source.backend == backend && source.epoch == epoch)
|
||||
{
|
||||
source.active = false;
|
||||
source.failed = false;
|
||||
source.active = false;
|
||||
source.failed = false;
|
||||
source.failurePending = false;
|
||||
source.reserved = true;
|
||||
input = source.endpoint;
|
||||
stop = source.running;
|
||||
source.running = false;
|
||||
selected = &source;
|
||||
source.reserved = true;
|
||||
input = source.endpoint;
|
||||
stop = source.running;
|
||||
source.running = false;
|
||||
selected = &source;
|
||||
if (m_owner.backend == backend && m_owner.epoch == epoch)
|
||||
{
|
||||
if (m_sink)
|
||||
m_sink->Reset();
|
||||
m_owner = {};
|
||||
m_owner = {};
|
||||
m_ownerDeadline = 0;
|
||||
AdvanceAuthorityGeneration();
|
||||
}
|
||||
if (m_interactionOwner.backend == backend &&
|
||||
m_interactionOwner.epoch == epoch)
|
||||
@@ -246,16 +248,17 @@ void CInputHub::Unbind(BackendId backend, uint32_t epoch)
|
||||
if (input && stop)
|
||||
input->Stop();
|
||||
|
||||
CSRWExclusiveLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (selected->reserved && selected->backend == backend &&
|
||||
selected->epoch == epoch)
|
||||
{
|
||||
selected->backend = 0;
|
||||
selected->epoch = 0;
|
||||
selected->endpoint = nullptr;
|
||||
selected->running = false;
|
||||
selected->reserved = false;
|
||||
selected->failed = false;
|
||||
selected->backend = 0;
|
||||
selected->epoch = 0;
|
||||
selected->endpoint = nullptr;
|
||||
selected->running = false;
|
||||
selected->reserved = false;
|
||||
selected->failed = false;
|
||||
selected->failurePending = false;
|
||||
}
|
||||
}
|
||||
@@ -267,6 +270,7 @@ InteractionResult CInputHub::CheckInteraction(
|
||||
if (!source.backend || !source.epoch || !source.client)
|
||||
return InteractionResult::STALE;
|
||||
|
||||
CSRWExclusiveLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_started && m_sink)
|
||||
CheckState();
|
||||
@@ -313,6 +317,7 @@ void CInputHub::CommitInteraction(
|
||||
source != permit.source)
|
||||
return;
|
||||
|
||||
CSRWExclusiveLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_started && m_sink)
|
||||
CheckState();
|
||||
@@ -367,9 +372,10 @@ bool CInputHub::TakeFailure(SourceKey& source)
|
||||
bool CInputHub::Start(IInputSink& sink)
|
||||
{
|
||||
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
|
||||
Source * sources[MAX_SOURCES] = {};
|
||||
unsigned count = 0;
|
||||
Source * sources[MAX_SOURCES] = {};
|
||||
unsigned count = 0;
|
||||
{
|
||||
CSRWExclusiveLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_started)
|
||||
return m_sink == &sink;
|
||||
@@ -387,10 +393,11 @@ bool CInputHub::Start(IInputSink& sink)
|
||||
|
||||
for (unsigned i = 0; i < count; ++i)
|
||||
{
|
||||
Source& source = *sources[i];
|
||||
Source& source = *sources[i];
|
||||
const bool started = source.endpoint->Start(source);
|
||||
bool stop = !started;
|
||||
bool stop = !started;
|
||||
{
|
||||
CSRWExclusiveLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!started || source.failed || !source.active || !source.reserved)
|
||||
{
|
||||
@@ -399,8 +406,9 @@ bool CInputHub::Start(IInputSink& sink)
|
||||
{
|
||||
if (m_sink)
|
||||
m_sink->Reset();
|
||||
m_owner = {};
|
||||
m_owner = {};
|
||||
m_ownerDeadline = 0;
|
||||
AdvanceAuthorityGeneration();
|
||||
}
|
||||
if (m_interactionOwner.backend == source.backend &&
|
||||
m_interactionOwner.epoch == source.epoch)
|
||||
@@ -432,11 +440,12 @@ bool CInputHub::Start(IInputSink& sink)
|
||||
void CInputHub::Stop()
|
||||
{
|
||||
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
|
||||
Source * sources[MAX_SOURCES] = {};
|
||||
unsigned count = 0;
|
||||
IInputSink * sink = nullptr;
|
||||
bool reset = false;
|
||||
Source * sources[MAX_SOURCES] = {};
|
||||
unsigned count = 0;
|
||||
IInputSink * sink = nullptr;
|
||||
bool reset = false;
|
||||
{
|
||||
CSRWExclusiveLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!m_started)
|
||||
{
|
||||
@@ -450,10 +459,12 @@ void CInputHub::Stop()
|
||||
source.running = false;
|
||||
sources[count++] = &source;
|
||||
}
|
||||
sink = m_sink;
|
||||
reset = m_owner.backend != 0;
|
||||
sink = m_sink;
|
||||
reset = m_owner.backend != 0;
|
||||
m_owner = {};
|
||||
m_ownerDeadline = 0;
|
||||
if (reset)
|
||||
AdvanceAuthorityGeneration();
|
||||
InvalidateInteraction();
|
||||
}
|
||||
|
||||
@@ -462,6 +473,7 @@ void CInputHub::Stop()
|
||||
if (reset && sink)
|
||||
sink->Reset();
|
||||
|
||||
CSRWExclusiveLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
m_sink = nullptr;
|
||||
m_sinkState = 0;
|
||||
@@ -520,6 +532,41 @@ void CInputHub::AdvanceInteractionSerial()
|
||||
Seq::Inc(m_interactionSerial);
|
||||
}
|
||||
|
||||
void CInputHub::AdvanceAuthorityGeneration()
|
||||
{
|
||||
Seq::Inc(m_authorityGeneration);
|
||||
AdvanceInputGeneration();
|
||||
}
|
||||
|
||||
void CInputHub::AdvanceInputGeneration()
|
||||
{
|
||||
Seq::Inc(m_inputGeneration);
|
||||
}
|
||||
|
||||
bool CInputHub::StateAvailable() const
|
||||
{
|
||||
if (!m_started || !m_sink)
|
||||
return false;
|
||||
|
||||
const uint64_t state = m_sink->GetState();
|
||||
if (state != m_sinkState || !(state & 1))
|
||||
return false;
|
||||
return !m_owner.backend || GetTickCount64() < m_ownerDeadline;
|
||||
}
|
||||
|
||||
InputResult CInputHub::ReconcileAuthorityState(const SourceKey& source)
|
||||
{
|
||||
CSRWExclusiveLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!SourceValid(source))
|
||||
return InputResult::STALE;
|
||||
if (!CheckState())
|
||||
return InputResult::UNAVAILABLE;
|
||||
if (!OwnerValid(source))
|
||||
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
|
||||
return InputResult::ACCEPTED;
|
||||
}
|
||||
|
||||
bool CInputHub::CheckState()
|
||||
{
|
||||
if (!m_started || !m_sink)
|
||||
@@ -529,7 +576,10 @@ bool CInputHub::CheckState()
|
||||
if (state != m_sinkState)
|
||||
{
|
||||
if (m_owner.backend)
|
||||
{
|
||||
m_sink->Reset();
|
||||
AdvanceAuthorityGeneration();
|
||||
}
|
||||
m_owner = {};
|
||||
m_ownerDeadline = 0;
|
||||
InvalidateInteraction();
|
||||
@@ -540,13 +590,31 @@ bool CInputHub::CheckState()
|
||||
m_sink->Reset();
|
||||
m_owner = {};
|
||||
m_ownerDeadline = 0;
|
||||
AdvanceAuthorityGeneration();
|
||||
InvalidateInteraction();
|
||||
}
|
||||
return (state & 1) != 0;
|
||||
}
|
||||
|
||||
void CInputHub::HandleSinkFailure(
|
||||
const SourceKey& source, bool reset, uint64_t inputGeneration)
|
||||
{
|
||||
CSRWExclusiveLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (inputGeneration != m_inputGeneration || !OwnerValid(source))
|
||||
return;
|
||||
|
||||
if (reset && m_sink)
|
||||
m_sink->Reset();
|
||||
m_owner = {};
|
||||
m_ownerDeadline = 0;
|
||||
AdvanceAuthorityGeneration();
|
||||
InvalidateInteraction();
|
||||
}
|
||||
|
||||
InputTargetState CInputHub::GetState(const SourceKey& source)
|
||||
{
|
||||
CSRWExclusiveLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
InputTargetState result;
|
||||
if (!BindingPresent(source))
|
||||
@@ -563,8 +631,8 @@ InputTargetState CInputHub::GetState(const SourceKey& source)
|
||||
result.available = true;
|
||||
else if (source.client && source.generation && m_owner == source)
|
||||
{
|
||||
result.available = true;
|
||||
result.owned = true;
|
||||
result.available = true;
|
||||
result.owned = true;
|
||||
result.owner.client = m_owner.client;
|
||||
result.owner.generation = m_owner.generation;
|
||||
}
|
||||
@@ -577,16 +645,43 @@ bool CInputHub::WithAuthority(const SourceKey& source,
|
||||
if (!action)
|
||||
return false;
|
||||
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!SourceValid(source) || !CheckState() || !OwnerValid(source))
|
||||
return false;
|
||||
for (;;)
|
||||
{
|
||||
// Freeze ownership while allowing the input fast path to continue under
|
||||
// the shared authority guard. The action must not hold the state lock.
|
||||
CSRWSharedLock authorityLock(m_authorityLock);
|
||||
uint64_t generation = 0;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!SourceValid(source))
|
||||
return false;
|
||||
if (StateAvailable())
|
||||
{
|
||||
if (!OwnerValid(source))
|
||||
return false;
|
||||
generation = m_authorityGeneration;
|
||||
}
|
||||
}
|
||||
|
||||
action(opaque);
|
||||
return true;
|
||||
if (!generation)
|
||||
{
|
||||
authorityLock.Unlock();
|
||||
if (ReconcileAuthorityState(source) != InputResult::ACCEPTED)
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
action(opaque);
|
||||
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
return generation == m_authorityGeneration &&
|
||||
SourceValid(source) && OwnerValid(source);
|
||||
}
|
||||
}
|
||||
|
||||
void CInputHub::Failed(Source& source)
|
||||
{
|
||||
CSRWExclusiveLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!source.active)
|
||||
return;
|
||||
@@ -594,8 +689,9 @@ void CInputHub::Failed(Source& source)
|
||||
{
|
||||
if (m_sink)
|
||||
m_sink->Reset();
|
||||
m_owner = {};
|
||||
m_owner = {};
|
||||
m_ownerDeadline = 0;
|
||||
AdvanceAuthorityGeneration();
|
||||
}
|
||||
if (m_interactionOwner.backend == source.backend &&
|
||||
m_interactionOwner.epoch == source.epoch)
|
||||
@@ -610,6 +706,7 @@ void CInputHub::Failed(Source& source)
|
||||
|
||||
InputResult CInputHub::Claim(const SourceKey& source)
|
||||
{
|
||||
CSRWExclusiveLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!SourceValid(source))
|
||||
return InputResult::STALE;
|
||||
@@ -627,25 +724,38 @@ InputResult CInputHub::Claim(const SourceKey& source)
|
||||
}
|
||||
m_owner = source;
|
||||
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
|
||||
AdvanceAuthorityGeneration();
|
||||
InvalidateInteraction();
|
||||
return InputResult::ACCEPTED;
|
||||
}
|
||||
|
||||
InputResult CInputHub::Touch(const SourceKey& source)
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!SourceValid(source))
|
||||
return InputResult::STALE;
|
||||
if (!CheckState())
|
||||
return InputResult::UNAVAILABLE;
|
||||
if (!OwnerValid(source))
|
||||
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
|
||||
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
|
||||
return InputResult::ACCEPTED;
|
||||
for (;;)
|
||||
{
|
||||
{
|
||||
CSRWSharedLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!SourceValid(source))
|
||||
return InputResult::STALE;
|
||||
if (StateAvailable())
|
||||
{
|
||||
if (!OwnerValid(source))
|
||||
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
|
||||
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
|
||||
return InputResult::ACCEPTED;
|
||||
}
|
||||
}
|
||||
|
||||
const InputResult result = ReconcileAuthorityState(source);
|
||||
if (result != InputResult::ACCEPTED)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
InputResult CInputHub::Release(const SourceKey& source, bool reset)
|
||||
{
|
||||
CSRWExclusiveLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!SourceValid(source))
|
||||
return InputResult::STALE;
|
||||
@@ -656,6 +766,7 @@ InputResult CInputHub::Release(const SourceKey& source, bool reset)
|
||||
accepted = m_sink->Reset();
|
||||
m_owner = {};
|
||||
m_ownerDeadline = 0;
|
||||
AdvanceAuthorityGeneration();
|
||||
InvalidateInteraction();
|
||||
return accepted ? InputResult::ACCEPTED : InputResult::UNAVAILABLE;
|
||||
}
|
||||
@@ -663,85 +774,134 @@ InputResult CInputHub::Release(const SourceKey& source, bool reset)
|
||||
InputResult CInputHub::SendMouseRelative(const SourceKey& source,
|
||||
int32_t deltaX, int32_t deltaY, int32_t wheel, uint32_t buttons)
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!SourceValid(source))
|
||||
return InputResult::STALE;
|
||||
if (!CheckState())
|
||||
return InputResult::UNAVAILABLE;
|
||||
if (!OwnerValid(source))
|
||||
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
|
||||
if (!m_sink->SendMouseRelative(deltaX, deltaY, wheel, buttons))
|
||||
for (;;)
|
||||
{
|
||||
m_sink->Reset();
|
||||
m_owner = {};
|
||||
m_ownerDeadline = 0;
|
||||
InvalidateInteraction();
|
||||
return InputResult::UNAVAILABLE;
|
||||
{
|
||||
CSRWSharedLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!SourceValid(source))
|
||||
return InputResult::STALE;
|
||||
if (StateAvailable())
|
||||
{
|
||||
if (!OwnerValid(source))
|
||||
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
|
||||
const uint64_t inputGeneration = m_inputGeneration;
|
||||
if (!m_sink->SendMouseRelative(deltaX, deltaY, wheel, buttons))
|
||||
{
|
||||
lock.Unlock();
|
||||
authorityLock.Unlock();
|
||||
HandleSinkFailure(source, true, inputGeneration);
|
||||
return InputResult::UNAVAILABLE;
|
||||
}
|
||||
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
|
||||
AdvanceInputGeneration();
|
||||
return InputResult::ACCEPTED;
|
||||
}
|
||||
}
|
||||
|
||||
const InputResult result = ReconcileAuthorityState(source);
|
||||
if (result != InputResult::ACCEPTED)
|
||||
return result;
|
||||
}
|
||||
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
|
||||
return InputResult::ACCEPTED;
|
||||
}
|
||||
|
||||
InputResult CInputHub::SendMouseAbsolute(const SourceKey& source,
|
||||
uint16_t x, uint16_t y, int32_t wheel, uint32_t buttons)
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!SourceValid(source))
|
||||
return InputResult::STALE;
|
||||
if (!CheckState())
|
||||
return InputResult::UNAVAILABLE;
|
||||
if (!OwnerValid(source))
|
||||
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
|
||||
if (!m_sink->SendMouseAbsolute(x, y, wheel, buttons))
|
||||
for (;;)
|
||||
{
|
||||
m_sink->Reset();
|
||||
m_owner = {};
|
||||
m_ownerDeadline = 0;
|
||||
InvalidateInteraction();
|
||||
return InputResult::UNAVAILABLE;
|
||||
{
|
||||
CSRWSharedLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!SourceValid(source))
|
||||
return InputResult::STALE;
|
||||
if (StateAvailable())
|
||||
{
|
||||
if (!OwnerValid(source))
|
||||
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
|
||||
const uint64_t inputGeneration = m_inputGeneration;
|
||||
if (!m_sink->SendMouseAbsolute(x, y, wheel, buttons))
|
||||
{
|
||||
lock.Unlock();
|
||||
authorityLock.Unlock();
|
||||
HandleSinkFailure(source, true, inputGeneration);
|
||||
return InputResult::UNAVAILABLE;
|
||||
}
|
||||
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
|
||||
AdvanceInputGeneration();
|
||||
return InputResult::ACCEPTED;
|
||||
}
|
||||
}
|
||||
|
||||
const InputResult result = ReconcileAuthorityState(source);
|
||||
if (result != InputResult::ACCEPTED)
|
||||
return result;
|
||||
}
|
||||
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
|
||||
return InputResult::ACCEPTED;
|
||||
}
|
||||
|
||||
InputResult CInputHub::SendKeyboard(const SourceKey& source,
|
||||
uint8_t modifiers, const uint8_t * keys)
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!SourceValid(source))
|
||||
return InputResult::STALE;
|
||||
if (!CheckState())
|
||||
return InputResult::UNAVAILABLE;
|
||||
if (!OwnerValid(source))
|
||||
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
|
||||
if (!m_sink->SendKeyboard(modifiers, keys))
|
||||
for (;;)
|
||||
{
|
||||
m_sink->Reset();
|
||||
m_owner = {};
|
||||
m_ownerDeadline = 0;
|
||||
InvalidateInteraction();
|
||||
return InputResult::UNAVAILABLE;
|
||||
{
|
||||
CSRWSharedLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!SourceValid(source))
|
||||
return InputResult::STALE;
|
||||
if (StateAvailable())
|
||||
{
|
||||
if (!OwnerValid(source))
|
||||
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
|
||||
const uint64_t inputGeneration = m_inputGeneration;
|
||||
if (!m_sink->SendKeyboard(modifiers, keys))
|
||||
{
|
||||
lock.Unlock();
|
||||
authorityLock.Unlock();
|
||||
HandleSinkFailure(source, true, inputGeneration);
|
||||
return InputResult::UNAVAILABLE;
|
||||
}
|
||||
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
|
||||
AdvanceInputGeneration();
|
||||
return InputResult::ACCEPTED;
|
||||
}
|
||||
}
|
||||
|
||||
const InputResult result = ReconcileAuthorityState(source);
|
||||
if (result != InputResult::ACCEPTED)
|
||||
return result;
|
||||
}
|
||||
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
|
||||
return InputResult::ACCEPTED;
|
||||
}
|
||||
|
||||
InputResult CInputHub::Reset(const SourceKey& source)
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!SourceValid(source))
|
||||
return InputResult::STALE;
|
||||
if (!CheckState())
|
||||
return InputResult::UNAVAILABLE;
|
||||
if (!OwnerValid(source))
|
||||
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
|
||||
if (!m_sink->Reset())
|
||||
for (;;)
|
||||
{
|
||||
m_owner = {};
|
||||
m_ownerDeadline = 0;
|
||||
InvalidateInteraction();
|
||||
return InputResult::UNAVAILABLE;
|
||||
{
|
||||
CSRWSharedLock authorityLock(m_authorityLock);
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!SourceValid(source))
|
||||
return InputResult::STALE;
|
||||
if (StateAvailable())
|
||||
{
|
||||
if (!OwnerValid(source))
|
||||
return m_owner.backend ? InputResult::BUSY : InputResult::STALE;
|
||||
const uint64_t inputGeneration = m_inputGeneration;
|
||||
if (!m_sink->Reset())
|
||||
{
|
||||
lock.Unlock();
|
||||
authorityLock.Unlock();
|
||||
HandleSinkFailure(source, false, inputGeneration);
|
||||
return InputResult::UNAVAILABLE;
|
||||
}
|
||||
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
|
||||
AdvanceInputGeneration();
|
||||
return InputResult::ACCEPTED;
|
||||
}
|
||||
}
|
||||
|
||||
const InputResult result = ReconcileAuthorityState(source);
|
||||
if (result != InputResult::ACCEPTED)
|
||||
return result;
|
||||
}
|
||||
m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS;
|
||||
return InputResult::ACCEPTED;
|
||||
}
|
||||
|
||||
@@ -77,15 +77,18 @@ private:
|
||||
|
||||
mutable CSRWLock m_lock;
|
||||
CSRWLock m_lifecycleLock;
|
||||
CSRWLock m_authorityLock;
|
||||
Source m_sources[MAX_SOURCES];
|
||||
IInputSink * m_sink = nullptr;
|
||||
uint64_t m_sinkState = 0;
|
||||
IInputSink * m_sink = nullptr;
|
||||
uint64_t m_sinkState = 0;
|
||||
SourceKey m_owner;
|
||||
uint64_t m_ownerDeadline = 0;
|
||||
uint64_t m_ownerDeadline = 0;
|
||||
uint64_t m_authorityGeneration = 1;
|
||||
uint64_t m_inputGeneration = 1;
|
||||
SourceKey m_interactionOwner;
|
||||
uint64_t m_interactionDeadline = 0;
|
||||
uint64_t m_interactionSerial = 1;
|
||||
bool m_started = false;
|
||||
uint64_t m_interactionDeadline = 0;
|
||||
uint64_t m_interactionSerial = 1;
|
||||
bool m_started = false;
|
||||
|
||||
bool SourceValid(const SourceKey& source) const;
|
||||
bool BindingPresent(const SourceKey& source) const;
|
||||
@@ -94,7 +97,13 @@ private:
|
||||
void ClearInteraction();
|
||||
void InvalidateInteraction();
|
||||
void AdvanceInteractionSerial();
|
||||
void AdvanceAuthorityGeneration();
|
||||
void AdvanceInputGeneration();
|
||||
bool StateAvailable() const;
|
||||
InputResult ReconcileAuthorityState(const SourceKey& source);
|
||||
bool CheckState();
|
||||
void HandleSinkFailure(const SourceKey& source, bool reset,
|
||||
uint64_t inputGeneration);
|
||||
InputTargetState GetState(const SourceKey& source);
|
||||
bool WithAuthority(const SourceKey& source,
|
||||
InputAuthorityAction action, void * opaque);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,14 +35,15 @@ static constexpr USHORT LG_INPUT_VENDOR_ID = 0x0000;
|
||||
static constexpr USHORT LG_INPUT_PRODUCT_ID = 0x0000;
|
||||
static constexpr USHORT LG_INPUT_VERSION = 0x0001;
|
||||
static constexpr size_t REPORT_QUEUE_LENGTH = 64;
|
||||
static constexpr size_t MOTION_COALESCE_THRESHOLD =
|
||||
REPORT_QUEUE_LENGTH / 2;
|
||||
static constexpr size_t MOTION_EMERGENCY_DEPTH = 8;
|
||||
static constexpr DWORD MOTION_AGE_BUDGET_MS = 1;
|
||||
|
||||
struct HIDQueuedReport
|
||||
{
|
||||
UCHAR data[HID_MAX_INPUT_REPORT_SIZE];
|
||||
bool pureMotion;
|
||||
size_t size;
|
||||
UCHAR data[HID_MAX_INPUT_REPORT_SIZE];
|
||||
int64_t enqueueTicks;
|
||||
size_t size;
|
||||
bool pureMotion;
|
||||
};
|
||||
|
||||
struct HIDStatistics
|
||||
@@ -77,6 +78,9 @@ struct HIDDeviceContext
|
||||
uint16_t consumerUsage;
|
||||
uint16_t absoluteX;
|
||||
uint16_t absoluteY;
|
||||
int64_t motionBudgetTicks;
|
||||
HIDKeyboardReport keyboard;
|
||||
bool keyboardValid;
|
||||
HIDStatistics statistics;
|
||||
HIDQueuedReport reports[REPORT_QUEUE_LENGTH];
|
||||
CInputPipeClient * inputPipe;
|
||||
@@ -200,28 +204,71 @@ static void RemoveQueuedReport(
|
||||
--context->reportCount;
|
||||
}
|
||||
|
||||
static bool CompactStaleMotion(
|
||||
_Inout_ HIDDeviceContext * context,
|
||||
_In_ UCHAR incomingReportId)
|
||||
static bool MotionBudgetExceeded(_In_ HIDDeviceContext * context)
|
||||
{
|
||||
for (size_t i = 0; i < context->reportCount; ++i)
|
||||
if (context->reportCount >= MOTION_EMERGENCY_DEPTH)
|
||||
return true;
|
||||
if (!context->reportCount || !context->motionBudgetTicks)
|
||||
return false;
|
||||
|
||||
LARGE_INTEGER now = {};
|
||||
if (!QueryPerformanceCounter(&now))
|
||||
return false;
|
||||
|
||||
const int64_t oldest = GetQueuedReport(context, 0).enqueueTicks;
|
||||
return oldest > 0 && now.QuadPart >= oldest &&
|
||||
now.QuadPart - oldest >= context->motionBudgetTicks;
|
||||
}
|
||||
|
||||
static bool CompactQueuedMotion(_Inout_ HIDDeviceContext * context)
|
||||
{
|
||||
for (size_t i = 0; i + 1 < context->reportCount; ++i)
|
||||
{
|
||||
HIDQueuedReport& report = GetQueuedReport(context, i);
|
||||
if (!report.pureMotion ||
|
||||
report.data[0] != HID_REPORT_ID_MOUSE_ABSOLUTE)
|
||||
HIDQueuedReport& first = GetQueuedReport(context, i);
|
||||
HIDQueuedReport& second = GetQueuedReport(context, i + 1);
|
||||
if (!first.pureMotion || !second.pureMotion ||
|
||||
first.data[0] != second.data[0])
|
||||
continue;
|
||||
|
||||
bool superseded = incomingReportId == HID_REPORT_ID_MOUSE_ABSOLUTE;
|
||||
for (size_t j = i + 1; !superseded && j < context->reportCount; ++j)
|
||||
superseded = GetQueuedReport(context, j).data[0] ==
|
||||
HID_REPORT_ID_MOUSE_ABSOLUTE;
|
||||
|
||||
if (superseded)
|
||||
if (first.data[0] == HID_REPORT_ID_MOUSE_RELATIVE)
|
||||
{
|
||||
RemoveQueuedReport(context, i);
|
||||
++context->statistics.staleAbsoluteCompacted;
|
||||
HIDMouseRelativeReport * previous =
|
||||
reinterpret_cast<HIDMouseRelativeReport *>(first.data);
|
||||
const HIDMouseRelativeReport * current =
|
||||
reinterpret_cast<const HIDMouseRelativeReport *>(second.data);
|
||||
const int32_t x =
|
||||
static_cast<int32_t>(previous->x) + current->x;
|
||||
const int32_t y =
|
||||
static_cast<int32_t>(previous->y) + current->y;
|
||||
if (x < INT16_MIN || x > INT16_MAX ||
|
||||
y < INT16_MIN || y > INT16_MAX ||
|
||||
previous->buttons != current->buttons ||
|
||||
previous->wheel || current->wheel)
|
||||
continue;
|
||||
|
||||
previous->x = static_cast<int16_t>(x);
|
||||
previous->y = static_cast<int16_t>(y);
|
||||
RemoveQueuedReport(context, i + 1);
|
||||
++context->statistics.relativeCoalesced;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (first.data[0] != HID_REPORT_ID_MOUSE_ABSOLUTE)
|
||||
continue;
|
||||
|
||||
const HIDMouseAbsoluteReport * previous =
|
||||
reinterpret_cast<const HIDMouseAbsoluteReport *>(first.data);
|
||||
const HIDMouseAbsoluteReport * current =
|
||||
reinterpret_cast<const HIDMouseAbsoluteReport *>(second.data);
|
||||
if (previous->buttons != current->buttons ||
|
||||
previous->wheel || current->wheel)
|
||||
continue;
|
||||
|
||||
CopyMemory(first.data, second.data, second.size);
|
||||
first.size = second.size;
|
||||
RemoveQueuedReport(context, i + 1);
|
||||
++context->statistics.staleAbsoluteCompacted;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -232,7 +279,13 @@ static NTSTATUS QueueReport(
|
||||
_In_ size_t size,
|
||||
_In_ bool pureMotion)
|
||||
{
|
||||
const UCHAR reportId = *static_cast<const UCHAR *>(data);
|
||||
const UCHAR reportId = *static_cast<const UCHAR *>(data);
|
||||
const bool motionBudgetExceeded = MotionBudgetExceeded(context);
|
||||
if (motionBudgetExceeded)
|
||||
while (CompactQueuedMotion(context))
|
||||
{
|
||||
}
|
||||
|
||||
if (context->reportCount)
|
||||
{
|
||||
HIDQueuedReport& tail =
|
||||
@@ -240,20 +293,21 @@ static NTSTATUS QueueReport(
|
||||
if (tail.data[0] == reportId)
|
||||
{
|
||||
if (reportId == HID_REPORT_ID_MOUSE_RELATIVE &&
|
||||
context->reportCount >= MOTION_COALESCE_THRESHOLD &&
|
||||
tail.pureMotion && pureMotion)
|
||||
tail.pureMotion && pureMotion &&
|
||||
motionBudgetExceeded)
|
||||
{
|
||||
HIDMouseRelativeReport * previous =
|
||||
HIDMouseRelativeReport * previous =
|
||||
reinterpret_cast<HIDMouseRelativeReport *>(tail.data);
|
||||
const HIDMouseRelativeReport * current =
|
||||
const HIDMouseRelativeReport * current =
|
||||
static_cast<const HIDMouseRelativeReport *>(data);
|
||||
const int32_t x = static_cast<int32_t>(previous->x) +
|
||||
current->x;
|
||||
const int32_t y = static_cast<int32_t>(previous->y) +
|
||||
current->y;
|
||||
const int32_t x =
|
||||
static_cast<int32_t>(previous->x) + current->x;
|
||||
const int32_t y =
|
||||
static_cast<int32_t>(previous->y) + current->y;
|
||||
if (x >= INT16_MIN && x <= INT16_MAX &&
|
||||
y >= INT16_MIN && y <= INT16_MAX &&
|
||||
previous->buttons == current->buttons)
|
||||
previous->buttons == current->buttons &&
|
||||
!previous->wheel && !current->wheel)
|
||||
{
|
||||
previous->x = static_cast<int16_t>(x);
|
||||
previous->y = static_cast<int16_t>(y);
|
||||
@@ -266,9 +320,10 @@ static NTSTATUS QueueReport(
|
||||
{
|
||||
const HIDMouseAbsoluteReport * previous =
|
||||
reinterpret_cast<const HIDMouseAbsoluteReport *>(tail.data);
|
||||
const HIDMouseAbsoluteReport * current =
|
||||
const HIDMouseAbsoluteReport * current =
|
||||
static_cast<const HIDMouseAbsoluteReport *>(data);
|
||||
if (previous->buttons == current->buttons)
|
||||
if (previous->buttons == current->buttons &&
|
||||
!previous->wheel && !current->wheel)
|
||||
{
|
||||
CopyMemory(tail.data, data, size);
|
||||
tail.size = size;
|
||||
@@ -276,30 +331,24 @@ static NTSTATUS QueueReport(
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
else if (reportId == HID_REPORT_ID_KEYBOARD &&
|
||||
tail.size == size && memcmp(tail.data, data, size) == 0)
|
||||
{
|
||||
++context->statistics.keyboardDuplicates;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (context->reportCount == REPORT_QUEUE_LENGTH)
|
||||
{
|
||||
if (!CompactStaleMotion(context, reportId))
|
||||
{
|
||||
++context->statistics.overflows;
|
||||
return STATUS_BUFFER_OVERFLOW;
|
||||
}
|
||||
++context->statistics.overflows;
|
||||
return STATUS_BUFFER_OVERFLOW;
|
||||
}
|
||||
|
||||
const size_t index =
|
||||
const size_t index =
|
||||
(context->reportHead + context->reportCount) % REPORT_QUEUE_LENGTH;
|
||||
HIDQueuedReport * report = &context->reports[index];
|
||||
HIDQueuedReport * report = &context->reports[index];
|
||||
LARGE_INTEGER enqueueTime = {};
|
||||
QueryPerformanceCounter(&enqueueTime);
|
||||
CopyMemory(report->data, data, size);
|
||||
report->size = size;
|
||||
report->pureMotion = pureMotion;
|
||||
report->enqueueTicks = enqueueTime.QuadPart;
|
||||
report->size = size;
|
||||
report->pureMotion = pureMotion;
|
||||
++context->reportCount;
|
||||
++context->statistics.queued;
|
||||
if (context->reportCount > context->statistics.queueHighWater)
|
||||
@@ -365,10 +414,11 @@ static NTSTATUS DeactivateDevice(
|
||||
if (context->deactivateRequest)
|
||||
return STATUS_DEVICE_BUSY;
|
||||
|
||||
context->active = false;
|
||||
context->reportHead = 0;
|
||||
context->reportCount = 0;
|
||||
context->consumerUsage = UINT16_MAX;
|
||||
context->active = false;
|
||||
context->reportHead = 0;
|
||||
context->reportCount = 0;
|
||||
context->consumerUsage = UINT16_MAX;
|
||||
context->keyboardValid = false;
|
||||
}
|
||||
|
||||
context->deactivateRequest = request;
|
||||
@@ -432,10 +482,23 @@ NTSTATUS CHIDDevice::Create(_Inout_ PWDFDEVICE_INIT deviceInit)
|
||||
if (!NT_SUCCESS(status))
|
||||
return status;
|
||||
|
||||
HIDDeviceContext * context = HIDGetDeviceContext(device);
|
||||
HIDDeviceContext * context = HIDGetDeviceContext(device);
|
||||
LARGE_INTEGER performanceFrequency = {};
|
||||
new (context) HIDDeviceContext {};
|
||||
context->active = true;
|
||||
context->inputPipe = new (std::nothrow) CInputPipeClient;
|
||||
if (QueryPerformanceFrequency(&performanceFrequency))
|
||||
{
|
||||
static_assert(MOTION_AGE_BUDGET_MS &&
|
||||
1000 % MOTION_AGE_BUDGET_MS == 0,
|
||||
"motion age budget must evenly divide one second");
|
||||
const int64_t budgetDivisor = 1000 / MOTION_AGE_BUDGET_MS;
|
||||
context->motionBudgetTicks =
|
||||
performanceFrequency.QuadPart / budgetDivisor;
|
||||
if (performanceFrequency.QuadPart % budgetDivisor)
|
||||
++context->motionBudgetTicks;
|
||||
}
|
||||
context->active = true;
|
||||
context->consumerUsage = UINT16_MAX;
|
||||
context->inputPipe = new (std::nothrow) CInputPipeClient;
|
||||
if (!context->inputPipe)
|
||||
return STATUS_INSUFFICIENT_RESOURCES;
|
||||
|
||||
@@ -503,6 +566,13 @@ NTSTATUS CHIDDevice::SubmitReport(
|
||||
++context->statistics.consumerDuplicates;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
if (reportId == HID_REPORT_ID_KEYBOARD &&
|
||||
context->keyboardValid &&
|
||||
memcmp(&context->keyboard, report, size) == 0)
|
||||
{
|
||||
++context->statistics.keyboardDuplicates;
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
bool pureMotion = false;
|
||||
switch (reportId)
|
||||
@@ -573,6 +643,11 @@ NTSTATUS CHIDDevice::SubmitReport(
|
||||
static_cast<const HIDConsumerReport *>(report)->usage;
|
||||
break;
|
||||
|
||||
case HID_REPORT_ID_KEYBOARD:
|
||||
CopyMemory(&context->keyboard, report, size);
|
||||
context->keyboardValid = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -608,12 +683,13 @@ NTSTATUS CHIDDevice::ResetReports()
|
||||
absoluteX = context->absoluteX;
|
||||
absoluteY = context->absoluteY;
|
||||
context->statistics.resetDiscarded += context->reportCount;
|
||||
context->reportHead = 0;
|
||||
context->reportCount = 0;
|
||||
context->mouseMode = 0;
|
||||
context->relativeButtons = 0;
|
||||
context->absoluteButtons = 0;
|
||||
context->consumerUsage = UINT16_MAX;
|
||||
context->reportHead = 0;
|
||||
context->reportCount = 0;
|
||||
context->mouseMode = 0;
|
||||
context->relativeButtons = 0;
|
||||
context->absoluteButtons = 0;
|
||||
context->consumerUsage = UINT16_MAX;
|
||||
context->keyboardValid = false;
|
||||
}
|
||||
|
||||
const HIDKeyboardReport keyboard = {
|
||||
@@ -804,9 +880,10 @@ VOID HIDEvtReportQueueCleanup(_In_ WDFOBJECT object)
|
||||
CSRWExclusiveLock lifecycleLock(context->lifecycleLock);
|
||||
{
|
||||
CSRWExclusiveLock reportLock(context->reportLock);
|
||||
context->stopping = true;
|
||||
context->reportHead = 0;
|
||||
context->reportCount = 0;
|
||||
context->stopping = true;
|
||||
context->reportHead = 0;
|
||||
context->reportCount = 0;
|
||||
context->keyboardValid = false;
|
||||
}
|
||||
deactivateRequest = context->deactivateRequest;
|
||||
context->deactivateRequest = nullptr;
|
||||
|
||||
@@ -57,7 +57,11 @@ bool CInputPipeClient::Start()
|
||||
return m_endpoint.Start(
|
||||
LG_INPUT_PIPE_NAME,
|
||||
CPipeEndpoint::Mode::Client,
|
||||
sizeof(LGInputPipeMessage));
|
||||
sizeof(LGInputPipeMessage),
|
||||
nullptr,
|
||||
0,
|
||||
0,
|
||||
CPipeEndpoint::ThreadScheduling::Input);
|
||||
}
|
||||
|
||||
void CInputPipeClient::Stop()
|
||||
|
||||
Submodule repos/LGProtocol updated: 7913a08aba...189c9bc5b6
Reference in New Issue
Block a user