blob: 2956ddc17c814cd65c9adaf721f6f85cb0f410ff [file] [log] [blame]
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2017, 2018 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
* Copyright 2017 (c) Stefan Profanter, fortiss GmbH
*/
#include "ua_util_internal.h"
#include "ua_timer.h"
struct UA_TimerEntry {
ZIP_ENTRY(UA_TimerEntry) zipfields;
UA_DateTime nextTime; /* The next time when the callback
* is to be executed */
UA_UInt64 interval; /* Interval in 100ns resolution */
UA_Boolean repeated; /* Repeated callback? */
UA_ApplicationCallback callback;
void *application;
void *data;
ZIP_ENTRY(UA_TimerEntry) idZipfields;
UA_UInt64 id; /* Id of the entry */
};
/* There may be several entries with the same nextTime in the tree. We give them
* an absolute order by considering the memory address to break ties. Because of
* this, the nextTime property cannot be used to lookup specific entries. */
static enum ZIP_CMP
cmpDateTime(const UA_DateTime *a, const UA_DateTime *b) {
if(*a < *b)
return ZIP_CMP_LESS;
if(*a > *b)
return ZIP_CMP_MORE;
if(a == b)
return ZIP_CMP_EQ;
if(a < b)
return ZIP_CMP_LESS;
return ZIP_CMP_MORE;
}
ZIP_PROTTYPE(UA_TimerZip, UA_TimerEntry, UA_DateTime)
ZIP_IMPL(UA_TimerZip, UA_TimerEntry, zipfields, UA_DateTime, nextTime, cmpDateTime)
/* The identifiers of entries are unique */
static enum ZIP_CMP
cmpId(const UA_UInt64 *a, const UA_UInt64 *b) {
if(*a < *b)
return ZIP_CMP_LESS;
if(*a == *b)
return ZIP_CMP_EQ;
return ZIP_CMP_MORE;
}
ZIP_PROTTYPE(UA_TimerIdZip, UA_TimerEntry, UA_UInt64)
ZIP_IMPL(UA_TimerIdZip, UA_TimerEntry, idZipfields, UA_UInt64, id, cmpId)
void
UA_Timer_init(UA_Timer *t) {
memset(t, 0, sizeof(UA_Timer));
}
static UA_StatusCode
addCallback(UA_Timer *t, UA_ApplicationCallback callback, void *application, void *data,
UA_DateTime nextTime, UA_UInt64 interval, UA_Boolean repeated,
UA_UInt64 *callbackId) {
/* A callback method needs to be present */
if(!callback)
return UA_STATUSCODE_BADINTERNALERROR;
/* Allocate the repeated callback structure */
UA_TimerEntry *te = (UA_TimerEntry*)UA_malloc(sizeof(UA_TimerEntry));
if(!te)
return UA_STATUSCODE_BADOUTOFMEMORY;
/* Set the repeated callback */
te->interval = (UA_UInt64)interval;
te->id = ++t->idCounter;
te->callback = callback;
te->application = application;
te->data = data;
te->repeated = repeated;
te->nextTime = nextTime;
/* Set the output identifier */
if(callbackId)
*callbackId = te->id;
ZIP_INSERT(UA_TimerZip, &t->root, te, ZIP_FFS32(UA_UInt32_random()));
ZIP_INSERT(UA_TimerIdZip, &t->idRoot, te, ZIP_RANK(te, zipfields));
return UA_STATUSCODE_GOOD;
}
UA_StatusCode
UA_Timer_addTimedCallback(UA_Timer *t, UA_ApplicationCallback callback,
void *application, void *data, UA_DateTime date,
UA_UInt64 *callbackId) {
return addCallback(t, callback, application, data, date, 0, false, callbackId);
}
/* Adding repeated callbacks: Add an entry with the "nextTime" timestamp in the
* future. This will be picked up in the next iteration and inserted at the
* correct place. So that the next execution takes place ät "nextTime". */
UA_StatusCode
UA_Timer_addRepeatedCallback(UA_Timer *t, UA_ApplicationCallback callback,
void *application, void *data, UA_Double interval_ms,
UA_UInt64 *callbackId) {
/* The interval needs to be positive */
if(interval_ms <= 0.0)
return UA_STATUSCODE_BADINTERNALERROR;
UA_UInt64 interval = (UA_UInt64)(interval_ms * UA_DATETIME_MSEC);
UA_DateTime nextTime = UA_DateTime_nowMonotonic() + (UA_DateTime)interval;
return addCallback(t, callback, application, data, nextTime,
interval, true, callbackId);
}
UA_StatusCode
UA_Timer_changeRepeatedCallbackInterval(UA_Timer *t, UA_UInt64 callbackId,
UA_Double interval_ms) {
/* The interval needs to be positive */
if(interval_ms <= 0.0)
return UA_STATUSCODE_BADINTERNALERROR;
/* Remove from the sorted list */
UA_TimerEntry *te = ZIP_FIND(UA_TimerIdZip, &t->idRoot, &callbackId);
if(!te)
return UA_STATUSCODE_BADNOTFOUND;
/* Set the repeated callback */
ZIP_REMOVE(UA_TimerZip, &t->root, te);
te->interval = (UA_UInt64)(interval_ms * UA_DATETIME_MSEC); /* in 100ns resolution */
te->nextTime = UA_DateTime_nowMonotonic() + (UA_DateTime)te->interval;
ZIP_INSERT(UA_TimerZip, &t->root, te, ZIP_RANK(te, zipfields));
return UA_STATUSCODE_GOOD;
}
void
UA_Timer_removeCallback(UA_Timer *t, UA_UInt64 callbackId) {
UA_TimerEntry *te = ZIP_FIND(UA_TimerIdZip, &t->idRoot, &callbackId);
if(!te)
return;
ZIP_REMOVE(UA_TimerZip, &t->root, te);
ZIP_REMOVE(UA_TimerIdZip, &t->idRoot, te);
UA_free(te);
}
UA_DateTime
UA_Timer_process(UA_Timer *t, UA_DateTime nowMonotonic,
UA_TimerExecutionCallback executionCallback,
void *executionApplication) {
UA_TimerEntry *first;
while((first = ZIP_MIN(UA_TimerZip, &t->root)) &&
first->nextTime <= nowMonotonic) {
ZIP_REMOVE(UA_TimerZip, &t->root, first);
/* Reinsert / remove to their new position first. Because the callback
* can interact with the zip tree and expects the same entries in the
* root and idRoot trees. */
if(!first->repeated) {
ZIP_REMOVE(UA_TimerIdZip, &t->idRoot, first);
executionCallback(executionApplication, first->callback,
first->application, first->data);
UA_free(first);
continue;
}
/* Set the time for the next execution. Prevent an infinite loop by
* forcing the next processing into the next iteration. */
first->nextTime += (UA_Int64)first->interval;
if(first->nextTime < nowMonotonic)
first->nextTime = nowMonotonic + 1;
ZIP_INSERT(UA_TimerZip, &t->root, first, ZIP_RANK(first, zipfields));
executionCallback(executionApplication, first->callback,
first->application, first->data);
}
/* Return the timestamp of the earliest next callback */
first = ZIP_MIN(UA_TimerZip, &t->root);
return (first) ? first->nextTime : UA_INT64_MAX;
}
static void
freeEntry(UA_TimerEntry *te, void *data) {
UA_free(te);
}
void
UA_Timer_deleteMembers(UA_Timer *t) {
/* Free all nodes and reset the root */
ZIP_ITER(UA_TimerZip, &t->root, freeEntry, NULL);
ZIP_INIT(&t->root);
}