OSSIA
Open Scenario System for Interactive Application
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/joystick_protocol.hpp>
7 
8 #if __has_include(<SDL2/SDL.h>)
9 #include <SDL2/SDL.h>
10 #else
11 #include <SDL.h>
12 #endif
13 
14 #include <ossia/detail/fmt.hpp>
15 
16 #include <thread>
17 
18 namespace ossia::net
19 {
20 
21 struct sdl_joystick_context
22 {
23  sdl_joystick_context()
24  {
25  SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1");
26  // Prevent SDL from setting SIGINT handler on Posix Systems
27  SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
28 
29  if(int ret = SDL_Init(SDL_INIT_JOYSTICK); ret < 0)
30  throw std::runtime_error(fmt::format("SDL Init failure: {}", SDL_GetError()));
31 
32  SDL_JoystickEventState(SDL_ENABLE);
33  }
34 
35  static sdl_joystick_context& instance()
36  {
37  static sdl_joystick_context instance{};
38  return instance;
39  }
40 
41  ~sdl_joystick_context()
42  {
43  // To be sure to quit the event loop
44  SDL_Event ev;
45  ev.type = SDL_FIRSTEVENT;
46  SDL_PushEvent(&ev);
47 
48  SDL_Quit();
49  }
50 };
51 
52 class joystick_protocol_manager
53 {
54 public:
55  static joystick_protocol_manager& instance()
56  {
57  static joystick_protocol_manager instance{};
58  return instance;
59  }
60 
61  joystick_protocol_manager() { sdl_joystick_context::instance(); }
62 
63  ~joystick_protocol_manager() { SDL_Quit(); }
64 
65  bool joystick_is_registered(const SDL_JoystickID joystick_id)
66  {
67  return m_joystick_protocols.find(joystick_id) != m_joystick_protocols.end();
68  }
69 
70  void register_protocol(joystick_protocol& protocol)
71  {
72  const SDL_JoystickID joystick_id = protocol.m_joystick_id;
73 
74  if(joystick_is_registered(joystick_id))
75  throw std::runtime_error("A protocol is already registered for this joystick");
76 
77  {
78  std::lock_guard<std::mutex> _{m_joystick_protocols_mutex};
79  m_joystick_protocols[joystick_id] = &protocol;
80  }
81  }
82 
83  void unregister_protocol(joystick_protocol& protocol)
84  {
85  const SDL_JoystickID joystick_id = protocol.m_joystick_id;
86 
87  if(!joystick_is_registered(joystick_id))
88  throw std::runtime_error(
89  "Cannot unregister a protocol that haven't been registered");
90 
91  {
92  std::lock_guard<std::mutex> _{m_joystick_protocols_mutex};
93  m_joystick_protocols.erase(joystick_id);
94  }
95  }
96 
97  joystick_protocol* get_protocol_by_id(const SDL_JoystickID id)
98  {
99  joystick_protocol* ret = nullptr;
100 
101  std::lock_guard<std::mutex> _{m_joystick_protocols_mutex};
102  auto it = m_joystick_protocols.find(id);
103  if(it != m_joystick_protocols.end())
104  ret = it->second;
105 
106  return ret;
107  }
108 
109  ossia::hash_map<SDL_JoystickID, joystick_protocol*> m_joystick_protocols;
110  std::mutex m_joystick_protocols_mutex;
111 };
112 
113 // TODO refactor this so that each protocol gets callback only related to its
114 // own joystick instead
115 struct joystick_event_processor
116 {
117  struct timer_context
118  {
119  explicit timer_context(boost::asio::io_context& ctx)
120  : context{&ctx}
121  , timer{ctx}
122  , count{1}
123  {
124  }
125  timer_context(timer_context&&) = default;
126  timer_context& operator=(timer_context&&) = default;
127 
128  boost::asio::io_context* context{};
129  ossia::timer timer;
130  int count = 0;
131  };
132 
133  static inline std::atomic_int instance_count = 0;
134  joystick_event_processor(joystick_protocol_manager& manager)
135  : m_manager{manager}
136  {
137  }
138 
139  ~joystick_event_processor() = default;
140 
141  static joystick_event_processor& instance(joystick_protocol_manager& manager)
142  {
143  static joystick_event_processor instance{manager};
144  return instance;
145  }
146 
147  void register_context(boost::asio::io_context& ctx)
148  {
149  for(auto& tm : this->m_timers)
150  {
151  if(tm.context == &ctx)
152  {
153  tm.count++;
154  return;
155  }
156  }
157 
158  m_timers.emplace_back(ctx);
159 
160  if(instance_count > 0)
161  {
162  auto& tm = m_timers.back();
163  start(tm.timer);
164  }
165  }
166 
167  void start(ossia::timer& timer)
168  {
169  using namespace std::literals;
170  timer.set_delay(4ms);
171  timer.start([this] { this->process_events(); });
172  }
173 
174  void unregister_context(boost::asio::io_context& ctx)
175  {
176  for(auto it = m_timers.begin(); it != m_timers.end();)
177  {
178  auto& tm = *it;
179  if(tm.context == &ctx)
180  {
181  tm.count--;
182  if(tm.count == 0)
183  {
184  it = m_timers.erase(it);
185  continue;
186  }
187  }
188 
189  ++it;
190  }
191  }
192 
193  void start_event_loop()
194  {
195  if(instance_count++ > 0)
196  return;
197 
198  for(auto& tm : m_timers)
199  start(tm.timer);
200  }
201 
202  void stop_event_loop()
203  {
204  if(--instance_count > 0)
205  return;
206 
207  using namespace std::literals;
208  // To be sure to quit the event loop
209  SDL_Event ev;
210  ev.type = SDL_FIRSTEVENT;
211  SDL_PushEvent(&ev);
212 
213  for(auto& tm : m_timers)
214  tm.timer.stop();
215  }
216 
217  void
218  push(joystick_protocol* proto, ossia::net::parameter_base* param, ossia::value val)
219  {
220  proto->m_device->apply_incoming_message({*proto, 0}, *param, std::move(val));
221  }
222 
223  void push_axis(const SDL_JoyAxisEvent& ev)
224  {
225  if(auto p = m_manager.get_protocol_by_id(ev.which))
226  {
227  const float res = (ev.value + .5f) / (0x7FFF + .5f);
228  push(p, p->m_axis_parameters[ev.axis], res);
229  }
230  }
231 
232  void push_button(const SDL_JoyButtonEvent& ev)
233  {
234  if(auto p = m_manager.get_protocol_by_id(ev.which))
235  {
236  push(p, p->m_button_parameters[ev.button], bool(ev.state == SDL_PRESSED));
237  }
238  }
239 
240  void push_hat(const SDL_JoyHatEvent& ev)
241  {
242  if(auto p = m_manager.get_protocol_by_id(ev.which))
243  {
244  const uint8_t v = ev.value;
245  float x = 0.0f, y = 0.0f;
246 
247  if(v & SDL_HAT_LEFT)
248  x = -1.0;
249  else if(v & SDL_HAT_RIGHT)
250  x = 1.0;
251 
252  if(v & SDL_HAT_UP)
253  y = 1.0;
254  else if(v & SDL_HAT_DOWN)
255  y = -1;
256 
257  push(p, p->m_hat_parameters[ev.hat], std::array<float, 2>{x, y});
258  }
259  }
260 
261  void process_event(const SDL_Event& ev)
262  {
263  switch(ev.type)
264  {
265  case SDL_JOYAXISMOTION:
266  push_axis(ev.jaxis);
267  break;
268 
269  case SDL_JOYBUTTONDOWN:
270  case SDL_JOYBUTTONUP:
271  push_button(ev.jbutton);
272  break;
273 
274  case SDL_JOYHATMOTION:
275  push_hat(ev.jhat);
276  break;
277 
278  default:
279  break;
280  }
281  }
282 
283  void process_events()
284  {
285  SDL_Event ev;
286 
287  int max_event_count = 20;
288  while(SDL_PollEvent(&ev) && max_event_count-- > 0)
289  {
290  process_event(ev);
291  }
292  }
293 
294  joystick_protocol_manager& m_manager;
295  std::vector<timer_context> m_timers;
296 };
297 
298 }
The parameter_base class.
Definition: ossia/network/base/parameter.hpp:48
The value class.
Definition: value.hpp:173