| /* 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); |
| } |