summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorslvr <slvr@unsafeio.com>2013-08-12 13:08:03 +0100
committerslvr <slvr@unsafeio.com>2013-08-12 13:08:03 +0100
commit1a39c397c57cfc2008d432cc38458a45b961aff0 (patch)
treec6351c0b684d6f330f7b20db5fdcd15eedec3571 /core
parent139d915482c82f2a4aa87b444008afffef728561 (diff)
Implemented Deadline Timer and timer_test
Diffstat (limited to 'core')
-rw-r--r--core/CMakeLists.txt3
-rw-r--r--core/timer.c273
-rw-r--r--core/timer.h104
3 files changed, 379 insertions, 1 deletions
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 55a41912..ad6eea01 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -10,7 +10,8 @@ set(core_sources
10 LAN_discovery.c 10 LAN_discovery.c
11 Messenger.c 11 Messenger.c
12 util.c 12 util.c
13 ping.c) 13 ping.c
14 timer.c)
14 15
15if(SHARED_TOXCORE) 16if(SHARED_TOXCORE)
16 add_library(toxcore SHARED ${core_sources}) 17 add_library(toxcore SHARED ${core_sources})
diff --git a/core/timer.c b/core/timer.c
new file mode 100644
index 00000000..74a2831d
--- /dev/null
+++ b/core/timer.c
@@ -0,0 +1,273 @@
1#include "timer.h"
2#include "network.h"
3#include <stdint.h>
4
5/*
6A nested linked list increases efficiency of insertions.
7Depending on the number of timers we have, we might need to have nested linked lists
8in order to improve insertion efficiency.
9The code below is preperation for that end, should it be necessary.
10
11typedef struct {
12 struct timer_package* _next;
13 union {
14 timer_packet* _inner;
15 timer* queue;
16 };
17 uint64_t pkgtime;
18} timer_package;
19
20timer_package* timer_package_pool;
21
22static timer_package* new_package()
23{
24 timer_package* ret;
25 if (timer_package_pool) {
26 ret = timer_package_pool;
27 timer_package_pool = timer_package_pool->_next;
28 } else {
29 ret = calloc(1, sizeof(struct timer_package));
30 }
31 return ret;
32}
33
34static void delete_package(timer_package* p)
35{
36 p->_next = timer_package_pool;
37 timer_package_pool = p;
38}
39*/
40
41enum timer_state {
42 STATE_INACTIVE = 0,
43 STATE_ACTIVE,
44 STATE_CALLBACK
45};
46
47struct timer
48{
49 enum timer_state state;
50 timer* _prev;
51 timer* _next;
52 timer_callback cb;
53 void* userdata;
54 uint64_t deadline;
55};
56
57static timer* timer_main_queue;
58static timer* timer_us_queue; /* hi-speed queue */
59
60inline static void timer_dequeue(timer* t, timer** queue)
61{
62 if (t->state == STATE_INACTIVE) return; /* not in a queue */
63
64 if (t->_prev) {
65 t->_prev->_next = t->_next;
66 } else {
67 *queue = t->_next;
68 }
69 if (t->_next) t->_next->_prev = t->_prev;
70 t->state = STATE_INACTIVE;
71}
72
73static void timer_enqueue(timer* t, timer** queue, timer* prev)
74{
75 t->state = STATE_ACTIVE;
76 while (true) {
77 if (!*queue) {
78 t->_next = 0;
79 t->_prev = prev;
80 *queue = t;
81 return;
82 }
83
84 if ((*queue)->deadline > t->deadline) {
85 (*queue)->_prev = t;
86 t->_next = *queue;
87 t->_prev = prev;
88 *queue = t;
89 return;
90 }
91
92 prev = *queue;
93 queue = &((*queue)->_next);
94 }
95}
96
97/*** interface ***/
98
99void timer_init()
100{
101 /* Nothing needs to be done... yet. */
102}
103
104/* Do not depend on fields being zeroed */
105static timer* timer_pool; /* timer_pool is SINGLY LINKED!! */
106
107timer* timer_new(void)
108{
109 timer* ret;
110 if (timer_pool) {
111 ret = timer_pool;
112 timer_pool = timer_pool->_next;
113 } else {
114 ret = calloc(1, sizeof(struct timer));
115 }
116 ret->state = STATE_INACTIVE;
117 return ret;
118}
119
120void timer_delete(timer* t)
121{
122 timer_dequeue(t, &timer_main_queue);
123 t->_next = timer_pool;
124 t->state = STATE_INACTIVE;
125 timer_pool = t;
126}
127
128void timer_setup(timer* t, timer_callback cb, void* userarg)
129{
130 t->cb = cb;
131 t->userdata = userarg;
132}
133
134void* timer_get_userdata(timer* t)
135{
136 return t->userdata;
137}
138
139static void timer_delay_us(timer* t, int us)
140{
141 t->deadline += us;
142 timer** queue = t->_prev ? &(t->_prev->_next) : &timer_main_queue;
143 timer_dequeue(t, &timer_main_queue);
144 timer_enqueue(t, queue, t->_prev);
145}
146
147/* Starts the timer so that it's called in sec seconds in the future.
148 * A non-positive value of sec results in the callback being called immediately.
149 * This function may be called again after a timer has been started to adjust
150 * the expiry time. */
151void timer_start(timer* t, int sec)
152{
153 uint64_t newdeadline = current_time() + sec * US_PER_SECOND;
154 if (timer_is_active(t)){
155 if (t->deadline < newdeadline) {
156 timer_delay_us(t, newdeadline - t->deadline);
157 return;
158 }
159 timer_dequeue(t, &timer_main_queue);
160 }
161 t->deadline = newdeadline;
162 timer_enqueue(t, &timer_main_queue, 0);
163}
164
165/* Stops the timer. Returns -1 if the timer was not active. */
166int timer_stop(timer* t)
167{
168 int ret = timer_is_active(t) ? -1 : 0;
169 timer_dequeue(t, &timer_main_queue);
170 return ret;
171}
172
173/* Adds additionalsec seconds to the timer.
174 * Returns -1 and does nothing if the timer was not active. */
175int timer_delay(timer* t, int additonalsec)
176{
177 if (!timer_is_active(t)) return -1;
178 timer_delay_us(t, additonalsec * US_PER_SECOND);
179 return 0;
180}
181
182static uint64_t timer_diff(timer* t, uint64_t time)
183{
184 if (t->deadline <= time) return 0;
185 return time - t->deadline;
186}
187
188/* Returns the time remaining on a timer in seconds.
189 * Returns -1 if the timer is not active.
190 * Returns 0 if the timer has expired and will be called upon the next call to timer_poll. */
191int timer_time_remaining(timer* t)
192{
193 if (!timer_is_active(t)) return -1;
194 return timer_diff(t, current_time()) / US_PER_SECOND;
195}
196
197bool timer_is_active(timer* t)
198{
199 return t->state != STATE_INACTIVE;
200}
201
202/* Single-use timer.
203 * Creates a new timer, preforms setup and starts it. */
204void timer_single(timer_callback cb, void* userarg, int sec)
205{
206 timer* t = timer_new();
207 timer_setup(t, cb, userarg);
208 timer_start(t, sec);
209}
210
211/* Single-use microsecond timer. */
212void timer_us(timer_callback cb, void* userarg, int us)
213{
214 timer* t = timer_new();
215 timer_setup(t, cb, userarg);
216 t->deadline = current_time() + us;
217 t->state = STATE_ACTIVE;
218 timer_enqueue(t, &timer_us_queue, 0);
219}
220
221uint64_t prevtime = 0;
222void timer_poll(void)
223{
224 uint64_t time = current_time();
225
226 /* Handle millisecond timers */
227 while (timer_us_queue) {
228 if (timer_diff(timer_us_queue, time) != 0) break;
229 timer* t = timer_us_queue;
230 timer_dequeue(t, &timer_us_queue);
231 t->cb(0, t->userdata);
232 timer_delete(t);
233 }
234
235 if (time - prevtime > US_PER_SECOND || prevtime == 0 || prevtime > time) {
236 /* time moving backwards is just a sanity check */
237 prevtime = time;
238
239 while (timer_main_queue) {
240 if (timer_diff(timer_main_queue, time) != 0) break;
241 timer* t = timer_main_queue;
242 t->state = STATE_CALLBACK;
243 int rv = t->cb(t, t->userdata);
244 if (rv != 0) {
245 timer_dequeue(t, &timer_main_queue);
246 timer_delete(t);
247 continue;
248 }
249 if (t->state != STATE_ACTIVE) {
250 timer_dequeue(t, &timer_main_queue);
251 }
252 }
253 }
254}
255
256/*** Internal Testing ***/
257
258/* I do not want to expose internals to the public,
259 * which is why internals testing is done this way. */
260void timer_internal_tests(bool (*assert)(bool, char*))
261{
262
263}
264
265void timer_debug_print()
266{
267 timer* t = timer_main_queue;
268 printf("Queue:\n");
269 while (t) {
270 printf("%lli (%lli) : %s\n", t->deadline, t->deadline/US_PER_SECOND, (char*)t->userdata);
271 t = t->_next;
272 }
273}
diff --git a/core/timer.h b/core/timer.h
new file mode 100644
index 00000000..fee66f29
--- /dev/null
+++ b/core/timer.h
@@ -0,0 +1,104 @@
1/* timer.h
2 *
3 * Timing subsystem. Provides deadline timers.
4 * All times are aliased to a second for efficiency.
5 *
6 * Timer Guarantees:
7 * - The callback will not be called before the timer expires.
8 * - The callback will be called sometime after the timer expires,
9 * on a best effort basis.
10 * - If timer_poll is called at least once a second, the callback
11 * will be called at most one second after it expires.
12 *
13 * Copyright (C) 2013 Tox project All Rights Reserved.
14 *
15 * This file is part of Tox.
16 *
17 * Tox is free software: you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation, either version 3 of the License, or
20 * (at your option) any later version.
21 *
22 * Tox is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * You should have received a copy of the GNU General Public License
28 * along with Tox. If not, see <http://www.gnu.org/licenses/>.
29 *
30 */
31
32#ifndef TIMER_H
33#define TIMER_H
34
35#include <stdint.h>
36#include <stdbool.h>
37
38#define US_PER_SECOND 1000000 /* 1 s = 10^6 us */
39
40struct timer;
41typedef struct timer timer;
42
43/* If time_callback returns a non-zero value, timer t is deleted.
44 * You may call any of the timer functions within the callback:
45 * For example, you may call timer_start to restart the timer from
46 * within a callback. */
47typedef int (*timer_callback)(timer* t, void* userarg);
48
49/* Initisalise timer subsystem */
50void timer_init(void);
51
52/* Poll. (I will eventually replace all polling in Tox with an async system.) */
53void timer_poll(void);
54
55/* Creates a new timer. Does not enqueue/start it. */
56timer* timer_new(void);
57
58/* Destroys a timer instance. */
59void timer_delete(timer* t);
60
61/* Sets up the timer callback. */
62void timer_setup(timer* t, timer_callback cb, void* userarg);
63
64/* Accessor Function. */
65void* timer_get_userdata(timer* t);
66
67/* Starts the timer so that it's called in sec seconds in the future from now.
68 * A non-positive value of sec results in the callback being called immediately.
69 * This function may be called again after a timer has been started to adjust
70 * the expiry time. */
71void timer_start(timer* t, int sec);
72
73/* Stops the timer. Returns -1 if the timer was not active. */
74int timer_stop(timer* t);
75
76/* Adds additionalsec seconds to the timer.
77 * Returns -1 and does nothing if the timer was not active. */
78int timer_delay(timer* t, int additonalsec);
79
80/* Returns the time remaining on a timer in seconds.
81 * Returns -1 if the timer is not active.
82 * Returns 0 if the timer has expired and the callback hasn't been called yet. */
83int timer_time_remaining(timer* t);
84
85/* Determines if timer is active. Returns TRUE if it is active */
86bool timer_is_active(timer* t);
87
88/* Single-use timer.
89 * Creates a new timer, preforms setup and starts it.
90 * Callback must return a non-zero value to prevent memory leak. */
91void timer_single(timer_callback cb, void* userarg, int sec);
92
93/* Single-use microsecond timer.
94 * Creates a new timer, preforms setup and starts it.
95 * Please do not use this when accuracy is not absolutely required.
96 * Use when one needs to time a period < 1 s.
97 * Use the more coarse timers above for periods > 5 s.
98 * WARNING: the callback will be called with NULL as the first argument */
99void timer_us(timer_callback cb, void* userarg, int us);
100
101/* Internal Testing */
102void timer_internal_tests(bool(*)(bool, char*));
103
104#endif