blob: 075ca21bb7d1132c80ec07eb19fbfaa2530530b2 [file] [log] [blame]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <string.h>
#include "log.h"
#include "async_alarm.h"
#include "async_internal.h"
#include "timing.h"
typedef struct {
TimeValue time;
int interval;
AsyncAlarmCallback *callback;
void *data;
unsigned active:1;
unsigned cancel:1;
unsigned reschedule:1;
} AlarmEntry;
struct AsyncAlarmDataStruct {
Queue *alarmQueue;
};
void
asyncDeallocateAlarmData (AsyncAlarmData *ad) {
if (ad) {
if (ad->alarmQueue) deallocateQueue(ad->alarmQueue);
free(ad);
}
}
static AsyncAlarmData *
getAlarmData (void) {
AsyncThreadSpecificData *tsd = asyncGetThreadSpecificData();
if (!tsd) return NULL;
if (!tsd->alarmData) {
AsyncAlarmData *ad;
if (!(ad = malloc(sizeof(*ad)))) {
logMallocError();
return NULL;
}
memset(ad, 0, sizeof(*ad));
ad->alarmQueue = NULL;
tsd->alarmData = ad;
}
return tsd->alarmData;
}
static void
cancelAlarm (Element *element) {
AlarmEntry *alarm = getElementItem(element);
if (alarm->active) {
alarm->cancel = 1;
} else {
deleteElement(element);
}
}
static void
deallocateAlarmEntry (void *item, void *data) {
AlarmEntry *alarm = item;
free(alarm);
}
static int
compareAlarmEntries (const void *newItem, const void *existingItem, void *queueData) {
const AlarmEntry *newAlarm = newItem;
const AlarmEntry *existingAlarm = existingItem;
return compareTimeValues(&newAlarm->time, &existingAlarm->time) < 0;
}
static Queue *
getAlarmQueue (int create) {
AsyncAlarmData *ad = getAlarmData();
if (!ad) return NULL;
if (!ad->alarmQueue && create) {
if ((ad->alarmQueue = newQueue(deallocateAlarmEntry, compareAlarmEntries))) {
static AsyncQueueMethods methods = {
.cancelRequest = cancelAlarm
};
setQueueData(ad->alarmQueue, &methods);
}
}
return ad->alarmQueue;
}
typedef struct {
const TimeValue *time;
AsyncAlarmCallback *callback;
void *data;
} AlarmElementParameters;
static Element *
newAlarmElement (const void *parameters) {
const AlarmElementParameters *aep = parameters;
Queue *alarms = getAlarmQueue(1);
if (alarms) {
AlarmEntry *alarm;
if ((alarm = malloc(sizeof(*alarm)))) {
memset(alarm, 0, sizeof(*alarm));
alarm->time = *aep->time;
alarm->callback = aep->callback;
alarm->data = aep->data;
alarm->active = 0;
alarm->cancel = 0;
alarm->reschedule = 0;
{
Element *element = enqueueItem(alarms, alarm);
if (element) {
logSymbol(LOG_CATEGORY(ASYNC_EVENTS), aep->callback, "alarm added");
return element;
}
}
free(alarm);
} else {
logMallocError();
}
}
return NULL;
}
int
asyncNewAbsoluteAlarm (
AsyncHandle *handle,
const TimeValue *time,
AsyncAlarmCallback *callback,
void *data
) {
const AlarmElementParameters aep = {
.time = time,
.callback = callback,
.data = data
};
return asyncMakeHandle(handle, newAlarmElement, &aep);
}
int
asyncNewRelativeAlarm (
AsyncHandle *handle,
int milliseconds,
AsyncAlarmCallback *callback,
void *data
) {
TimeValue time;
getMonotonicTime(&time);
adjustTimeValue(&time, milliseconds);
return asyncNewAbsoluteAlarm(handle, &time, callback, data);
}
static Element *
getAlarmElement (AsyncHandle handle) {
return asyncGetHandleElement(handle, getAlarmQueue(0));
}
int
asyncResetAlarmTo (AsyncHandle handle, const TimeValue *time) {
Element *element = getAlarmElement(handle);
if (element) {
AlarmEntry *alarm = getElementItem(element);
alarm->time = *time;
requeueElement(element);
return 1;
}
return 0;
}
int
asyncResetAlarmIn (AsyncHandle handle, int milliseconds) {
TimeValue time;
getMonotonicTime(&time);
adjustTimeValue(&time, milliseconds);
return asyncResetAlarmTo(handle, &time);
}
int
asyncResetAlarmInterval (AsyncHandle handle, int milliseconds) {
Element *element = getAlarmElement(handle);
if (element) {
AlarmEntry *alarm = getElementItem(element);
alarm->interval = milliseconds;
alarm->reschedule = milliseconds > 0;
return 1;
}
return 0;
}
static int
testInactiveAlarm (void *item, void *data) {
const AlarmEntry *alarm = item;
return !alarm->active;
}
int
asyncExecuteAlarmCallback (AsyncAlarmData *ad, long int *timeout) {
if (ad) {
Queue *alarms = ad->alarmQueue;
if (alarms) {
Element *element = processQueue(alarms, testInactiveAlarm, NULL);
if (element) {
AlarmEntry *alarm = getElementItem(element);
TimeValue now;
long int milliseconds;
getMonotonicTime(&now);
milliseconds = millisecondsBetween(&now, &alarm->time);
if (milliseconds <= 0) {
AsyncAlarmCallback *callback = alarm->callback;
const AsyncAlarmCallbackParameters parameters = {
.now = &now,
.data = alarm->data
};
logSymbol(LOG_CATEGORY(ASYNC_EVENTS), callback, "alarm starting");
alarm->active = 1;
if (callback) callback(&parameters);
alarm->active = 0;
if (alarm->reschedule) {
adjustTimeValue(&alarm->time, alarm->interval);
getMonotonicTime(&now);
if (compareTimeValues(&alarm->time, &now) < 0) alarm->time = now;
requeueElement(element);
} else {
alarm->cancel = 1;
}
if (alarm->cancel) deleteElement(element);
return 1;
}
if (milliseconds < *timeout) {
*timeout = milliseconds;
logSymbol(LOG_CATEGORY(ASYNC_EVENTS), alarm->callback, "next alarm: %ld", *timeout);
}
}
}
}
return 0;
}