OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
joystick_manager.hpp
1#pragma once
2#include <ossia/detail/hash_map.hpp>
4#include <ossia/detail/timer.hpp>
5#include <ossia/network/context.hpp>
6#include <ossia/protocols/joystick/game_controller_protocol.hpp>
7#include <ossia/protocols/joystick/joystick_protocol.hpp>
8
9#if __has_include(<SDL2/SDL.h>)
10#include <SDL2/SDL.h>
11#else
12#include <SDL.h>
13#endif
14
15#include <ossia/detail/fmt.hpp>
16
17namespace ossia::net
18{
19
20struct sdl_joystick_context
21{
22 sdl_joystick_context()
23 {
24 SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1");
25 // Prevent SDL from setting SIGINT handler on Posix Systems
26 SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
27
28 if(int ret = SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); ret < 0)
29 throw std::runtime_error(fmt::format("SDL Init failure: {}", SDL_GetError()));
30
31 // Optional: absent from some SDL builds (the Emscripten port has no haptic
32 // support at all), and no joystick API here needs them to be up. Sensors
33 // are read through SDL_GameControllerHasSensor, which simply reports none.
34 SDL_InitSubSystem(SDL_INIT_HAPTIC);
35 SDL_InitSubSystem(SDL_INIT_SENSOR);
36
37 SDL_JoystickEventState(SDL_ENABLE);
38 SDL_GameControllerEventState(SDL_ENABLE);
39 }
40
41 static sdl_joystick_context& instance()
42 {
43 static sdl_joystick_context instance{};
44 return instance;
45 }
46
47 ~sdl_joystick_context()
48 {
49 // To be sure to quit the event loop
50 SDL_Event ev;
51 ev.type = SDL_FIRSTEVENT;
52 SDL_PushEvent(&ev);
53
54 SDL_Quit();
55 }
56};
57
58class joystick_protocol_manager
59{
60public:
61 static joystick_protocol_manager& instance()
62 {
63 static joystick_protocol_manager instance{};
64 return instance;
65 }
66
67 joystick_protocol_manager() { sdl_joystick_context::instance(); }
68
69 ~joystick_protocol_manager() { SDL_Quit(); }
70
71 bool joystick_is_registered(const SDL_JoystickID joystick_id)
72 {
73 return m_joystick_protocols.find(joystick_id) != m_joystick_protocols.end();
74 }
75
76 void register_protocol(auto& protocol)
77 {
78 const SDL_JoystickID joystick_id = protocol.m_joystick_id;
79
80 if(joystick_is_registered(joystick_id))
81 throw std::runtime_error("A protocol is already registered for this joystick");
82
83 {
84 std::lock_guard<std::mutex> _{m_joystick_protocols_mutex};
85 m_joystick_protocols[joystick_id] = &protocol;
86 }
87 }
88
89 void unregister_protocol(auto& protocol)
90 {
91 const SDL_JoystickID joystick_id = protocol.m_joystick_id;
92
93 if(!joystick_is_registered(joystick_id))
94 throw std::runtime_error(
95 "Cannot unregister a protocol that haven't been registered");
96
97 {
98 std::lock_guard<std::mutex> _{m_joystick_protocols_mutex};
99 m_joystick_protocols.erase(joystick_id);
100 }
101 }
102
103 template <typename T>
104 T* get_protocol_by_id(const SDL_JoystickID id)
105 {
106 std::lock_guard<std::mutex> _{m_joystick_protocols_mutex};
107 auto it = m_joystick_protocols.find(id);
108 if(it != m_joystick_protocols.end())
109 {
110 proto& p = it->second;
111 if(auto res = ossia::get_if<T*>(&p); res && *res)
112 return *res;
113 }
114 return nullptr;
115 }
116
117 using proto = ossia::variant<joystick_protocol*, game_controller_protocol*>;
118 ossia::hash_map<SDL_JoystickID, proto> m_joystick_protocols;
119 std::mutex m_joystick_protocols_mutex;
120};
121
122// TODO refactor this so that each protocol gets callback only related to its
123// own joystick instead
124struct joystick_event_processor
125{
126 struct timer_context
127 {
128 explicit timer_context(boost::asio::io_context& ctx)
129 : context{&ctx}
130 , timer{ctx}
131 , count{1}
132 {
133 }
134 timer_context(timer_context&&) = default;
135 timer_context& operator=(timer_context&&) = default;
136
137 boost::asio::io_context* context{};
138 ossia::timer timer;
139 int count = 0;
140 };
141
142 static inline std::atomic_int instance_count = 0;
143 joystick_event_processor(joystick_protocol_manager& manager)
144 : m_manager{manager}
145 {
146 }
147
148 ~joystick_event_processor() = default;
149
150 static joystick_event_processor& instance(joystick_protocol_manager& manager)
151 {
152 static joystick_event_processor instance{manager};
153 return instance;
154 }
155
156 void register_context(boost::asio::io_context& ctx)
157 {
158 for(auto& tm : this->m_timers)
159 {
160 if(tm.context == &ctx)
161 {
162 tm.count++;
163 return;
164 }
165 }
166
167 m_timers.emplace_back(ctx);
168
169 if(instance_count > 0)
170 {
171 auto& tm = m_timers.back();
172 start(tm.timer);
173 }
174 }
175
176 void start(ossia::timer& timer)
177 {
178 using namespace std::literals;
179 timer.set_delay(4ms);
180 timer.start([this] { this->process_events(); });
181 }
182
183 void unregister_context(boost::asio::io_context& ctx)
184 {
185 for(auto it = m_timers.begin(); it != m_timers.end();)
186 {
187 auto& tm = *it;
188 if(tm.context == &ctx)
189 {
190 tm.count--;
191 if(tm.count == 0)
192 {
193 it = m_timers.erase(it);
194 continue;
195 }
196 }
197
198 ++it;
199 }
200 }
201
202 void start_event_loop()
203 {
204 if(instance_count++ > 0)
205 return;
206
207 for(auto& tm : m_timers)
208 start(tm.timer);
209 }
210
211 void stop_event_loop()
212 {
213 if(--instance_count > 0)
214 return;
215
216 using namespace std::literals;
217 // To be sure to quit the event loop
218 SDL_Event ev;
219 ev.type = SDL_FIRSTEVENT;
220 SDL_PushEvent(&ev);
221
222 for(auto& tm : m_timers)
223 tm.timer.stop();
224 }
225
226 void push(auto* proto, ossia::net::parameter_base* param, ossia::value val)
227 {
228 if(proto && param)
229 proto->m_device->apply_incoming_message({*proto, 0}, *param, std::move(val));
230 }
231
232 void push_axis(const SDL_JoyAxisEvent& ev)
233 {
234 if(auto p = m_manager.get_protocol_by_id<joystick_protocol>(ev.which))
235 {
236 const float res = (ev.value + .5f) / (0x7FFF + .5f);
237 push(p, p->m_axis_parameters[ev.axis], res);
238 }
239 }
240
241 void push_axis(const SDL_ControllerAxisEvent& ev)
242 {
243 if(auto p = m_manager.get_protocol_by_id<game_controller_protocol>(ev.which))
244 {
245 const float res = (ev.value + .5f) / (0x7FFF + .5f);
246 push(p, p->m_axis_parameters[ev.axis], res);
247 }
248 }
249
250 void push_button(const SDL_JoyButtonEvent& ev)
251 {
252 if(auto p = m_manager.get_protocol_by_id<joystick_protocol>(ev.which))
253 {
254 push(p, p->m_button_parameters[ev.button], bool(ev.state == SDL_PRESSED));
255 }
256 }
257
258 void push_button(const SDL_ControllerButtonEvent& ev)
259 {
260 if(auto p = m_manager.get_protocol_by_id<game_controller_protocol>(ev.which))
261 {
262 push(p, p->m_button_parameters[ev.button], bool(ev.state == SDL_PRESSED));
263 }
264 }
265
266 void push_sensor(const SDL_ControllerSensorEvent& ev)
267 {
268 if(auto p = m_manager.get_protocol_by_id<game_controller_protocol>(ev.which))
269 {
270 push(
271 p, p->m_sensor_parameters[ev.sensor],
272 ossia::vec3f{ev.data[0], ev.data[1], ev.data[2]});
273 }
274 }
275
276 void push_touchpad(const SDL_ControllerTouchpadEvent& ev)
277 {
278 if(auto p = m_manager.get_protocol_by_id<game_controller_protocol>(ev.which))
279 {
280 auto it = p->m_touchpads.find(ev.touchpad);
281 if(it == p->m_touchpads.end())
282 return;
283
284 auto& touchpad = it->second;
285 if(ev.finger >= 0 && ev.finger < touchpad.fingers.size())
286 {
287 auto& finger = touchpad.fingers[ev.finger];
288 push(p, finger.x, ev.x);
289 push(p, finger.y, ev.y);
290 push(p, finger.pressure, ev.pressure);
291 }
292 }
293 }
294
295 void push_hat(const SDL_JoyHatEvent& ev)
296 {
297 if(auto p = m_manager.get_protocol_by_id<joystick_protocol>(ev.which))
298 {
299 const uint8_t v = ev.value;
300 float x = 0.0f, y = 0.0f;
301
302 if(v & SDL_HAT_LEFT)
303 x = -1.0;
304 else if(v & SDL_HAT_RIGHT)
305 x = 1.0;
306
307 if(v & SDL_HAT_UP)
308 y = 1.0;
309 else if(v & SDL_HAT_DOWN)
310 y = -1;
311
312 push(p, p->m_hat_parameters[ev.hat], std::array<float, 2>{x, y});
313 }
314 }
315
316 void process_event(const SDL_Event& ev)
317 {
318 switch(ev.type)
319 {
320 case SDL_JOYAXISMOTION:
321 push_axis(ev.jaxis);
322 break;
323
324 case SDL_JOYBUTTONDOWN:
325 case SDL_JOYBUTTONUP:
326 push_button(ev.jbutton);
327 break;
328
329 case SDL_JOYHATMOTION:
330 push_hat(ev.jhat);
331 break;
332
333 case SDL_CONTROLLERAXISMOTION:
334 push_axis(ev.caxis);
335 break;
336 case SDL_CONTROLLERBUTTONDOWN:
337 case SDL_CONTROLLERBUTTONUP:
338 push_button(ev.cbutton);
339 break;
340 case SDL_CONTROLLERTOUCHPADDOWN:
341 case SDL_CONTROLLERTOUCHPADMOTION:
342 case SDL_CONTROLLERTOUCHPADUP:
343 push_touchpad(ev.ctouchpad);
344 break;
345 case SDL_CONTROLLERSENSORUPDATE:
346 push_sensor(ev.csensor);
347 break;
348 // case SDL_CONTROLLERUPDATECOMPLETE_RESERVED_FOR_SDL3:
349 // case SDL_CONTROLLERSTEAMHANDLEUPDATED:
350
351 default:
352 break;
353 }
354 }
355
356 void process_events()
357 {
358 SDL_Event ev;
359
360 int max_event_count = 20;
361 while(SDL_PollEvent(&ev) && max_event_count-- > 0)
362 {
363 process_event(ev);
364 }
365 }
366
367 joystick_protocol_manager& m_manager;
368 std::vector<timer_context> m_timers;
369};
370
371}
The parameter_base class.
Definition ossia/network/base/parameter.hpp:48
The value class.
Definition value.hpp:173