summaryrefslogtreecommitdiff
path: root/core/timer.c
diff options
context:
space:
mode:
Diffstat (limited to 'core/timer.c')
-rw-r--r--core/timer.c275
1 files changed, 275 insertions, 0 deletions
diff --git a/core/timer.c b/core/timer.c
new file mode 100644
index 00000000..06e25693
--- /dev/null
+++ b/core/timer.c
@@ -0,0 +1,275 @@
1#define __STDC_FORMAT_MACROS
2#include <inttypes.h>
3
4#include "timer.h"
5#include "network.h"
6
7/*
8A nested linked list increases efficiency of insertions.
9Depending on the number of timers we have, we might need to have nested linked lists
10in order to improve insertion efficiency.
11The code below is preperation for that end, should it be necessary.
12
13typedef struct {
14 struct timer_package* _next;
15 union {
16 timer_packet* _inner;
17 timer* queue;
18 };
19 uint64_t pkgtime;
20} timer_package;
21
22timer_package* timer_package_pool;
23
24static timer_package* new_package()
25{
26 timer_package* ret;
27 if (timer_package_pool) {
28 ret = timer_package_pool;
29 timer_package_pool = timer_package_pool->_next;
30 } else {
31 ret = calloc(1, sizeof(struct timer_package));
32 }
33 return ret;
34}
35
36static void delete_package(timer_package* p)
37{
38 p->_next = timer_package_pool;
39 timer_package_pool = p;
40}
41*/
42
43enum timer_state {
44 STATE_INACTIVE = 0,
45 STATE_ACTIVE,
46 STATE_CALLBACK
47};
48
49struct timer
50{
51 enum timer_state state;
52 timer* _prev;
53 timer* _next;
54 timer_callback cb;
55 void* userdata;
56 uint64_t deadline;
57};
58
59static timer* timer_main_queue;
60static timer* timer_us_queue; /* hi-speed queue */
61
62inline static void timer_dequeue(timer* t, timer** queue)
63{
64 if (t->state == STATE_INACTIVE) return; /* not in a queue */
65
66 if (t->_prev) {
67 t->_prev->_next = t->_next;
68 } else {
69 *queue = t->_next;
70 }
71 if (t->_next) t->_next->_prev = t->_prev;
72 t->state = STATE_INACTIVE;
73}
74
75static void timer_enqueue(timer* t, timer** queue, timer* prev)
76{
77 t->state = STATE_ACTIVE;
78 while (true) {
79 if (!*queue) {
80 t->_next = 0;
81 t->_prev = prev;
82 *queue = t;
83 return;
84 }
85
86 if ((*queue)->deadline > t->deadline) {
87 (*queue)->_prev = t;
88 t->_next = *queue;
89 t->_prev = prev;
90 *queue = t;
91 return;
92 }
93
94 prev = *queue;
95 queue = &((*queue)->_next);
96 }
97}
98
99/*** interface ***/
100
101void timer_init()
102{
103 /* Nothing needs to be done... yet. */
104}
105
106/* Do not depend on fields being zeroed */
107static timer* timer_pool; /* timer_pool is SINGLY LINKED!! */
108
109timer* new_timer(void)
110{
111 timer* ret;
112 if (timer_pool) {
113 ret = timer_pool;
114 timer_pool = timer_pool->_next;
115 } else {
116 ret = calloc(1, sizeof(struct timer));
117 }
118 ret->state = STATE_INACTIVE;
119 return ret;
120}
121
122void delete_timer(timer* t)
123{
124 timer_dequeue(t, &timer_main_queue);
125 t->_next = timer_pool;
126 t->state = STATE_INACTIVE;
127 timer_pool = t;
128}
129
130void timer_setup(timer* t, timer_callback cb, void* userarg)
131{
132 t->cb = cb;
133 t->userdata = userarg;
134}
135
136void* timer_get_userdata(timer* t)
137{
138 return t->userdata;
139}
140
141static void timer_delay_us(timer* t, int us)
142{
143 t->deadline += us;
144 timer** queue = t->_prev ? &(t->_prev->_next) : &timer_main_queue;
145 timer_dequeue(t, &timer_main_queue);
146 timer_enqueue(t, queue, t->_prev);
147}
148
149/* Starts the timer so that it's called in sec seconds in the future.
150 * A non-positive value of sec results in the callback being called immediately.
151 * This function may be called again after a timer has been started to adjust
152 * the expiry time. */
153void timer_start(timer* t, int sec)
154{
155 uint64_t newdeadline = current_time() + sec * US_PER_SECOND;
156 if (timer_is_active(t)){
157 if (t->deadline < newdeadline) {
158 timer_delay_us(t, newdeadline - t->deadline);
159 return;
160 }
161 timer_dequeue(t, &timer_main_queue);
162 }
163 t->deadline = newdeadline;
164 timer_enqueue(t, &timer_main_queue, 0);
165}
166
167/* Stops the timer. Returns -1 if the timer was not active. */
168int timer_stop(timer* t)
169{
170 int ret = timer_is_active(t) ? -1 : 0;
171 timer_dequeue(t, &timer_main_queue);
172 return ret;
173}
174
175/* Adds additionalsec seconds to the timer.
176 * Returns -1 and does nothing if the timer was not active. */
177int timer_delay(timer* t, int additonalsec)
178{
179 if (!timer_is_active(t)) return -1;
180 timer_delay_us(t, additonalsec * US_PER_SECOND);
181 return 0;
182}
183
184static uint64_t timer_diff(timer* t, uint64_t time)
185{
186 if (t->deadline <= time) return 0;
187 return time - t->deadline;
188}
189
190/* Returns the time remaining on a timer in seconds.
191 * Returns -1 if the timer is not active.
192 * Returns 0 if the timer has expired and will be called upon the next call to timer_poll. */
193int timer_time_remaining(timer* t)
194{
195 if (!timer_is_active(t)) return -1;
196 return timer_diff(t, current_time()) / US_PER_SECOND;
197}
198
199bool timer_is_active(timer* t)
200{
201 return t->state != STATE_INACTIVE;
202}
203
204/* Single-use timer.
205 * Creates a new timer, preforms setup and starts it. */
206void timer_single(timer_callback cb, void* userarg, int sec)
207{
208 timer* t = new_timer();
209 timer_setup(t, cb, userarg);
210 timer_start(t, sec);
211}
212
213/* Single-use microsecond timer. */
214void timer_us(timer_callback cb, void* userarg, int us)
215{
216 timer* t = new_timer();
217 timer_setup(t, cb, userarg);
218 t->deadline = current_time() + us;
219 t->state = STATE_ACTIVE;
220 timer_enqueue(t, &timer_us_queue, 0);
221}
222
223uint64_t prevtime = 0;
224void timer_poll(void)
225{
226 uint64_t time = current_time();
227
228 /* Handle millisecond timers */
229 while (timer_us_queue) {
230 if (timer_diff(timer_us_queue, time) != 0) break;
231 timer* t = timer_us_queue;
232 timer_dequeue(t, &timer_us_queue);
233 t->cb(0, t->userdata);
234 delete_timer(t);
235 }
236
237 if (time - prevtime > US_PER_SECOND || prevtime == 0 || prevtime > time) {
238 /* time moving backwards is just a sanity check */
239 prevtime = time;
240
241 while (timer_main_queue) {
242 if (timer_diff(timer_main_queue, time) != 0) break;
243 timer* t = timer_main_queue;
244 t->state = STATE_CALLBACK;
245 int rv = t->cb(t, t->userdata);
246 if (rv != 0) {
247 timer_dequeue(t, &timer_main_queue);
248 delete_timer(t);
249 continue;
250 }
251 if (t->state != STATE_ACTIVE) {
252 timer_dequeue(t, &timer_main_queue);
253 }
254 }
255 }
256}
257
258/*** Internal Testing ***/
259
260/* I do not want to expose internals to the public,
261 * which is why internals testing is done this way. */
262void timer_internal_tests(bool (*assert)(bool, char*))
263{
264
265}
266
267void timer_debug_print()
268{
269 timer* t = timer_main_queue;
270 printf("Queue:\n");
271 while (t) {
272 printf("%" PRIu64 " (%" PRIu64 ") : %s\n", t->deadline, t->deadline/US_PER_SECOND, (char*)t->userdata);
273 t = t->_next;
274 }
275}