Modifies WaitEvent and WaitEventTimeout to actually wait instead of polling

When possible use native os functions to make a blocking call waiting for
an incoming event. Previous behavior was to continuously poll the event
queue with a small delay between each poll.

The blocking call uses a new optional video driver event,
WaitEventTimeout, if available. It is called only if an window
already shown is available. If present the window is designated
using the variable wakeup_window to receive a wakeup event if
needed.

The WaitEventTimeout function accept a timeout parameter. If
positive the call will wait for an event or return if the timeout
expired without any event. If the timeout is zero it will
implement a polling behavior. If the timeout is negative the
function will block indefinetely waiting for an event.

To let the main thread sees events sent form a different thread
a "wake-up" signal is sent to the main thread if the main thread
is in a blocking state. The wake-up event is sent to the designated
wakeup_window if present.

The wake-up event is sent only if the PushEvent call is coming
from a different thread. Before sending the wake-up event
the ID of the thread making the blocking call is saved using the
variable blocking_thread_id and it is compared to the current
thread's id to decide if the wake-up event should be sent.

Two new optional video device methods are introduced:

WaitEventTimeout
SendWakeupEvent

in addition the mutex

wakeup_lock

which is defined and initialized but only for the drivers supporting the
methods above.

If the methods are not present the system behaves as previously
performing a periodic polling of the events queue.

The blocking call is disabled if a joystick or sensor is detected
and falls back to previous behavior.
This commit is contained in:
Francesco Abbate
2021-03-12 21:58:20 +01:00
committed by Sam Lantinga
parent 40e5ce7fe5
commit 0dd7024d55
16 changed files with 716 additions and 132 deletions

View File

@@ -759,6 +759,80 @@ SDL_PollEvent(SDL_Event * event)
return SDL_WaitEventTimeout(event, 0);
}
static int
SDL_WaitEventTimeout_Device(_THIS, SDL_Window *wakeup_window, SDL_Event * event, int timeout)
{
/* Release any keys held down from last frame */
SDL_ReleaseAutoReleaseKeys();
for (;;) {
if (!_this->wakeup_lock || SDL_LockMutex(_this->wakeup_lock) == 0) {
int status = SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
/* If status == 0 we are going to block so wakeup will be needed. */
if (status == 0) {
_this->wakeup_window = wakeup_window;
_this->blocking_thread_id = SDL_ThreadID();
} else {
_this->wakeup_window = NULL;
_this->blocking_thread_id = 0;
}
if (_this->wakeup_lock) {
SDL_UnlockMutex(_this->wakeup_lock);
}
if (status < 0) {
/* Got an error: return */
break;
}
if (status > 0) {
/* There is an event, we can return. */
SDL_SendPendingSignalEvents(); /* in case we had a signal handler fire, etc. */
return 1;
}
/* No events found in the queue, call WaitEventTimeout to wait for an event. */
status = _this->WaitEventTimeout(_this, timeout);
/* Set wakeup_window to NULL without holding the lock. */
_this->wakeup_window = NULL;
if (status <= 0) {
/* There is either an error or the timeout is elapsed: return */
return 0;
}
/* An event was found and pumped into the SDL events queue. Continue the loop
to let SDL_PeepEvents pick it up .*/
}
}
return 0;
}
static int
SDL_events_need_polling() {
SDL_bool need_polling = SDL_FALSE;
#if !SDL_JOYSTICK_DISABLED
need_polling = \
(!SDL_disabled_events[SDL_JOYAXISMOTION >> 8] || SDL_JoystickEventState(SDL_QUERY)) \
&& (SDL_NumJoysticks() > 0);
#endif
#if !SDL_SENSOR_DISABLED
need_polling = need_polling || (!SDL_disabled_events[SDL_SENSORUPDATE >> 8] && \
(SDL_NumSensors() > 0));
#endif
return need_polling;
}
static SDL_Window *
SDL_find_active_window(SDL_VideoDevice * _this)
{
SDL_Window *window;
for (window = _this->windows; window; window = window->next) {
if (!window->is_destroying) {
return window;
}
}
return NULL;
}
int
SDL_WaitEvent(SDL_Event * event)
{
@@ -768,11 +842,24 @@ SDL_WaitEvent(SDL_Event * event)
int
SDL_WaitEventTimeout(SDL_Event * event, int timeout)
{
SDL_VideoDevice *_this = SDL_GetVideoDevice();
SDL_bool need_polling = SDL_events_need_polling();
SDL_Window *wakeup_window = NULL;
Uint32 expiration = 0;
if (timeout > 0)
expiration = SDL_GetTicks() + timeout;
if (!need_polling && _this) {
/* Look if a shown window is available to send the wakeup event. */
wakeup_window = SDL_find_active_window(_this);
need_polling = (wakeup_window == NULL);
}
if (!need_polling && _this && _this->WaitEventTimeout && _this->SendWakeupEvent) {
return SDL_WaitEventTimeout_Device(_this, wakeup_window, event, timeout);
}
for (;;) {
SDL_PumpEvents();
switch (SDL_PeepEvents(event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)) {
@@ -796,6 +883,24 @@ SDL_WaitEventTimeout(SDL_Event * event, int timeout)
}
}
static int
SDL_SendWakeupEvent()
{
SDL_VideoDevice *_this = SDL_GetVideoDevice();
if (!_this || !_this->SendWakeupEvent) {
return 0;
}
if (!_this->wakeup_lock || SDL_LockMutex(_this->wakeup_lock) == 0) {
if (_this->wakeup_window && _this->blocking_thread_id != 0 && _this->blocking_thread_id != SDL_ThreadID()) {
_this->SendWakeupEvent(_this, _this->wakeup_window);
}
if (_this->wakeup_lock) {
SDL_UnlockMutex(_this->wakeup_lock);
}
}
return 0;
}
int
SDL_PushEvent(SDL_Event * event)
{
@@ -845,6 +950,7 @@ SDL_PushEvent(SDL_Event * event)
return -1;
}
SDL_SendWakeupEvent();
SDL_GestureProcessEvent(event);
return 1;