1// tControllerSystem.cpp 
2// 
3// This file implements the main API for the input system. It manages all attached controllers. 
4// 
5// Copyright (c) 2025 Tristan Grimmer. 
6// Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby 
7// granted, provided that the above copyright notice and this permission notice appear in all copies. 
8// 
9// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL 
10// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
11// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 
12// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
13// PERFORMANCE OF THIS SOFTWARE. 
14 
15#include <chrono> 
16#ifdef PLATFORM_WINDOWS 
17#include <windows.h> 
18#include <xinput.h> 
19#endif 
20#include <Foundation/tPlatform.h> 
21#include <Foundation/tName.h> 
22#include <System/tPrint.h> 
23#include "Input/tControllerSystem.h" 
24#include "Input/tControllerDefinitions.h" 
25 
26 
27#ifdef PLATFORM_WINDOWS 
28// Missing from xinput.h. 
29_XInputGetCapabilitiesEx XInputGetCapabilitiesEx; 
30#endif 
31 
32 
33namespace tInput 
34
35 
36 
37tControllerSystem::tControllerSystem(int pollingPeriod_ns, int detectionPeriod_ms) : 
38 PollingPeriod_ns(pollingPeriod_ns
39
40 Gamepads.reserve(n: int(tGamepadID::NumGamepads)); 
41 Gamepads.emplace_back(args: "Gamepad1", args: tGamepadID::GP0); 
42 Gamepads.emplace_back(args: "Gamepad2", args: tGamepadID::GP1); 
43 Gamepads.emplace_back(args: "Gamepad3", args: tGamepadID::GP2); 
44 Gamepads.emplace_back(args: "Gamepad4", args: tGamepadID::GP3); 
45 
46 DetectPeriod_ms = (detectionPeriod_ms > 0) ? detectionPeriod_ms : 1000
47 
48 #ifdef PLATFORM_WINDOWS 
49 // This is better than LoadLibrary(TEXT("XInput1_4.dll") in two ways: 
50 // 1) The module is already loaded using the import library. 
51 // 2) We use the xinput.h header define in case xinput is ever updated. 
52 HMODULE moduleHandle = GetModuleHandle(XINPUT_DLL); 
53 XInputGetCapabilitiesEx = (_XInputGetCapabilitiesEx)GetProcAddress(moduleHandle, (char*)108); 
54 #endif 
55 
56 DetectThread = std::thread(&tControllerSystem::Detect, this); 
57
58 
59 
60tControllerSystem::~tControllerSystem() 
61
62
63 std::lock_guard<std::mutex> lock(Mutex); 
64 DetectExitRequested = true
65
66 // Notify that we want to cooperatively stop the polling thread. Notify one (thread) should be sufficient. 
67 // Notify_all would also work but is overkill since only one (polling) thread is waiting. By using a condition 
68 // variable we've made it so we don't have to wait for the current polling cycle sleep to complete. 
69 DetectExitCondition.notify_one(); 
70 
71 // The join just blocks until the polling thread has finished -- and has responded to the notify above. 
72 DetectThread.join(); 
73
74 
75 
76void tControllerSystem::Detect() 
77
78 while (1
79
80 // Detect controllers connected and disconnected. 
81 #ifdef PLATFORM_WINDOWS 
82 
83 for (int g = 0; g < int(tGamepadID::NumGamepads); g++) 
84
85 // XInputGetState is generally faster for detecting device connectedness. 
86 WinXInputState state; 
87 tStd::tMemclr(&state, sizeof(WinXInputState)); 
88 WinDWord result = XInputGetState(g, &state); 
89 
90 if (result == WinErrorSuccess) 
91
92 // If we're already polling then move on. 
93 if (Gamepads[g].IsPolling()) 
94 continue
95 
96 // Controller connected. Now we need to start polling the controller and queue a message that a 
97 // controller has been connected for the main Update to pick up. If polling period is set to 0 the 
98 // gamepad will do a hrdware lookup to determine the polling period. 
99 Gamepads[g].StartPolling(PollingPeriod_ns); 
100
101 else 
102
103 // Either WinErrorDeviceNotConnected or some other error. Either way treat controller as disconnected. 
104 if (Gamepads[g].IsPolling()) 
105
106 // Now we need to stop polling and queue a disconnect message to for the main update to pick up. 
107 // Since the gamepad is now disconnected 
108 Gamepads[g].StopPolling(); 
109
110
111
112 #endif 
113 
114 static int detectNum = 0
115 tPrintf(f: "Detect: %d\n", detectNum++); 
116 
117 // This unique_lock is just a more powerful version of lock_guard. Supports subsequent unlocking/locking which 
118 // is presumably needed by wait_for. In any case, wait_for needs this type of lock. 
119 std::unique_lock<std::mutex> lock(Mutex); 
120 bool exitRequested = DetectExitCondition.wait_for(lock&: lock, rtime: std::chrono::milliseconds(DetectPeriod_ms), p: [this]{ return DetectExitRequested; }); 
121 if (exitRequested
122 break
123
124
125 
126 
127void tControllerSystem::Update() 
128
129 for (int g = 0; g < int(tGamepadID::NumGamepads); g++) 
130
131 Gamepads[g].Update(); 
132
133
134 
135 
136
137