Skip to content

Commit 235bb2e

Browse files
authored
Merge pull request #16 from nefarius/wgi-caps-fix
Lifted 4 device hard limit for WGI backend
2 parents e0f274a + 16dee30 commit 235bb2e

1 file changed

Lines changed: 77 additions & 65 deletions

File tree

src/wgi_backend.cpp

Lines changed: 77 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,19 @@
22
#include <winrt/Windows.Foundation.h>
33
#include <winrt/Windows.Foundation.Collections.h>
44
#include <winrt/Windows.Gaming.Input.h>
5-
#include <array>
65
#include <cstdint>
76
#include <mutex>
87
#include <optional>
98
#include <string>
109
#include <utility>
10+
#include <vector>
1111

1212
namespace
1313
{
1414
using namespace winrt;
1515
using namespace Windows::Gaming::Input;
1616
using namespace Windows::Foundation;
1717

18-
constexpr int kMaxSlots = 4;
19-
2018
/**
2119
* @brief Converts a WinRT GamepadButtons bitmask into the internal 16-bit button mask.
2220
*
@@ -109,59 +107,64 @@ namespace
109107
struct WgiBackend::Impl
110108
{
111109
mutable std::mutex mutex;
112-
std::array<std::optional<Gamepad>, kMaxSlots> slotGamepads{};
113-
GamepadState states[kMaxSlots]{};
114-
std::array<std::string, kMaxSlots> slotDisplayNames;
115-
std::array<uint16_t, kMaxSlots> slotVendorIds{};
116-
std::array<uint16_t, kMaxSlots> slotProductIds{};
110+
std::vector<std::optional<Gamepad>> slotGamepads;
111+
std::vector<GamepadState> states;
112+
std::vector<std::string> slotDisplayNames;
113+
std::vector<uint16_t> slotVendorIds;
114+
std::vector<uint16_t> slotProductIds;
117115

118116
event_token addedToken;
119117
event_token removedToken;
120118

121119
/**
122-
* @brief Assigns a newly connected gamepad to the first available backend slot and records its metadata.
123-
*
124-
* If a free slot exists, stores the provided gamepad handle in that slot and populates the slot's
125-
* display name and vendor/product IDs. If all slots are occupied, the added gamepad is ignored.
120+
* @brief Re-enumerate the system gamepad list and rebuild the slot arrays.
126121
*
127-
* @param pad The gamepad that was added.
122+
* Must be called with mutex held.
128123
*/
129-
void OnGamepadAdded(const IInspectable&, const Gamepad& pad)
124+
void ResyncFromSystemLocked()
130125
{
131-
std::lock_guard lock(mutex);
132-
for (int i = 0; i < kMaxSlots; ++i)
126+
auto gamepads = Gamepad::Gamepads();
127+
const size_t count = static_cast<size_t>(gamepads.Size());
128+
129+
slotGamepads.assign(count, std::nullopt);
130+
states.assign(count, GamepadState{});
131+
slotDisplayNames.assign(count, std::string{});
132+
slotVendorIds.assign(count, 0);
133+
slotProductIds.assign(count, 0);
134+
135+
for (size_t i = 0; i < count; ++i)
133136
{
134-
if (!slotGamepads[i])
137+
try
135138
{
139+
auto pad = gamepads.GetAt(static_cast<uint32_t>(i));
136140
slotGamepads[i] = pad;
137-
slotDisplayNames[i] = GetDisplayNameForGamepad(pad, i);
141+
slotDisplayNames[i] = GetDisplayNameForGamepad(pad, static_cast<int>(i));
138142
GetDeviceIdsForGamepad(pad, &slotVendorIds[i], &slotProductIds[i]);
143+
}
144+
catch (...)
145+
{
146+
// If enumeration fails mid-way, leave remaining entries as empty/default.
139147
break;
140148
}
141149
}
142150
}
143151

144152
/**
145-
* @brief Handles a gamepad removal event by clearing the corresponding slot.
146-
*
147-
* Searches the tracked slots for the given gamepad handle; when a match is found,
148-
* removes the stored handle and resets the slot's vendor and product IDs to zero.
149-
*
150-
* @param pad The gamepad that was removed.
153+
* @brief Handles a gamepad add event by re-syncing the system list.
151154
*/
152-
void OnGamepadRemoved(const IInspectable&, const Gamepad& pad)
155+
void OnGamepadAdded(const IInspectable&, const Gamepad&)
153156
{
154157
std::lock_guard lock(mutex);
155-
for (int i = 0; i < kMaxSlots; ++i)
156-
{
157-
if (slotGamepads[i] && slotGamepads[i] == pad)
158-
{
159-
slotGamepads[i].reset();
160-
slotVendorIds[i] = 0;
161-
slotProductIds[i] = 0;
162-
break;
163-
}
164-
}
158+
ResyncFromSystemLocked();
159+
}
160+
161+
/**
162+
* @brief Handles a gamepad remove event by re-syncing the system list.
163+
*/
164+
void OnGamepadRemoved(const IInspectable&, const Gamepad&)
165+
{
166+
std::lock_guard lock(mutex);
167+
ResyncFromSystemLocked();
165168
}
166169
};
167170

@@ -181,7 +184,7 @@ WgiBackend::~WgiBackend()
181184
/**
182185
* @brief Initialize the WinRT gamepad backend, enumerate currently connected gamepads, and subscribe to add/remove events.
183186
*
184-
* Initializes the WinRT apartment once per thread, acquires the internal mutex, populates up to kMaxSlots with already-connected gamepads (storing each slot's handle, display name, and vendor/product IDs), and registers handlers for future GamepadAdded and GamepadRemoved events, saving their subscription tokens.
187+
* Initializes the WinRT apartment once per thread, acquires the internal mutex, populates slots with already-connected gamepads (storing each slot's handle, display name, and vendor/product IDs), and registers handlers for future GamepadAdded and GamepadRemoved events, saving their subscription tokens.
185188
*
186189
* @param hwnd Window handle provided for initialization context; currently accepted for API compatibility and not otherwise used.
187190
*/
@@ -195,22 +198,7 @@ void WgiBackend::Init(HWND)
195198
}
196199

197200
std::lock_guard lock(impl_->mutex);
198-
// Enumerate already-connected gamepads (GetAt in loop avoids IVectorView::Size() auto return type issue with MSVC)
199-
auto gamepads = Gamepad::Gamepads();
200-
for (int i = 0; i < kMaxSlots; ++i)
201-
{
202-
try
203-
{
204-
auto pad = gamepads.GetAt(static_cast<uint32_t>(i));
205-
impl_->slotGamepads[i] = pad;
206-
impl_->slotDisplayNames[i] = GetDisplayNameForGamepad(pad, i);
207-
GetDeviceIdsForGamepad(pad, &impl_->slotVendorIds[i], &impl_->slotProductIds[i]);
208-
}
209-
catch (const hresult_out_of_bounds&)
210-
{
211-
break;
212-
}
213-
}
201+
impl_->ResyncFromSystemLocked();
214202
impl_->addedToken = Gamepad::GamepadAdded([this](const IInspectable& s, const Gamepad& p)
215203
{
216204
impl_->OnGamepadAdded(s, p);
@@ -224,7 +212,8 @@ void WgiBackend::Init(HWND)
224212
void WgiBackend::Poll()
225213
{
226214
std::lock_guard lock(impl_->mutex);
227-
for (int i = 0; i < kMaxSlots; ++i)
215+
const size_t count = impl_->states.size();
216+
for (size_t i = 0; i < count; ++i)
228217
{
229218
auto& gs = impl_->states[i];
230219
if (!impl_->slotGamepads[i])
@@ -251,14 +240,28 @@ void WgiBackend::Poll()
251240
}
252241
}
253242

254-
int WgiBackend::GetMaxSlots() const { return kMaxSlots; }
243+
int WgiBackend::GetMaxSlots() const
244+
{
245+
std::lock_guard lock(impl_->mutex);
246+
return static_cast<int>(impl_->states.size());
247+
}
255248

256249
const GamepadState& WgiBackend::GetState(int slot) const
257250
{
258251
static constexpr GamepadState empty{};
259-
if (slot < 0 || slot >= kMaxSlots)
252+
if (slot < 0)
260253
return empty;
261-
return impl_->states[slot];
254+
255+
std::lock_guard lock(impl_->mutex);
256+
if (static_cast<size_t>(slot) >= impl_->states.size())
257+
return empty;
258+
259+
// Return a stable reference even if slots resize on another thread.
260+
thread_local std::vector<GamepadState> slotResultStates;
261+
if (slotResultStates.size() < impl_->states.size())
262+
slotResultStates.resize(impl_->states.size());
263+
slotResultStates[static_cast<size_t>(slot)] = impl_->states[static_cast<size_t>(slot)];
264+
return slotResultStates[static_cast<size_t>(slot)];
262265
}
263266

264267
const char* WgiBackend::GetName() const { return Name; }
@@ -271,34 +274,43 @@ const char* WgiBackend::GetName() const { return Name; }
271274
*/
272275
const char* WgiBackend::GetSlotDisplayName(int slot) const
273276
{
274-
if (slot < 0 || slot >= kMaxSlots) return nullptr;
277+
if (slot < 0) return nullptr;
275278
std::lock_guard lock(impl_->mutex);
276-
if (!impl_->slotGamepads[slot]) return nullptr;
277-
thread_local std::array<std::string, kMaxSlots> slotResultBuffers;
278-
slotResultBuffers[slot] = impl_->slotDisplayNames[slot];
279-
return slotResultBuffers[slot].c_str();
279+
if (static_cast<size_t>(slot) >= impl_->slotGamepads.size()) return nullptr;
280+
if (!impl_->slotGamepads[static_cast<size_t>(slot)]) return nullptr;
281+
thread_local std::vector<std::string> slotResultBuffers;
282+
if (slotResultBuffers.size() < impl_->slotDisplayNames.size())
283+
slotResultBuffers.resize(impl_->slotDisplayNames.size());
284+
slotResultBuffers[static_cast<size_t>(slot)] = impl_->slotDisplayNames[static_cast<size_t>(slot)];
285+
return slotResultBuffers[static_cast<size_t>(slot)].c_str();
280286
}
281287

282288
/**
283289
* @brief Retrieve the vendor and product identifiers for a slot's gamepad.
284290
*
285291
* Writes the 16-bit vendor and product IDs for the gamepad in the given slot into the provided output pointers. If the slot index is out of range or no gamepad is present in that slot, `0` is written for each ID. Passing a `nullptr` for either output pointer suppresses writing that value.
286292
*
287-
* @param slot Slot index (0 .. kMaxSlots - 1) to query.
293+
* @param slot Slot index to query.
288294
* @param vendorId Pointer that receives the vendor ID, or `nullptr` to ignore.
289295
* @param productId Pointer that receives the product ID, or `nullptr` to ignore.
290296
*/
291297
void WgiBackend::GetSlotDeviceIds(int slot, uint16_t* vendorId, uint16_t* productId) const
292298
{
293-
if (slot < 0 || slot >= kMaxSlots)
299+
if (slot < 0)
294300
{
295301
if (vendorId) *vendorId = 0;
296302
if (productId) *productId = 0;
297303
return;
298304
}
299305
std::lock_guard lock(impl_->mutex);
300-
uint16_t vid = impl_->slotGamepads[slot] ? impl_->slotVendorIds[slot] : 0;
301-
uint16_t pid = impl_->slotGamepads[slot] ? impl_->slotProductIds[slot] : 0;
306+
if (static_cast<size_t>(slot) >= impl_->slotGamepads.size())
307+
{
308+
if (vendorId) *vendorId = 0;
309+
if (productId) *productId = 0;
310+
return;
311+
}
312+
uint16_t vid = impl_->slotGamepads[static_cast<size_t>(slot)] ? impl_->slotVendorIds[static_cast<size_t>(slot)] : 0;
313+
uint16_t pid = impl_->slotGamepads[static_cast<size_t>(slot)] ? impl_->slotProductIds[static_cast<size_t>(slot)] : 0;
302314
if (vendorId) *vendorId = vid;
303315
if (productId) *productId = pid;
304316
}

0 commit comments

Comments
 (0)