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 LAN_discovery.c Messenger.c util.c - ping.c) + ping.c + timer.c) if(SHARED_TOXCORE) 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 @@ +#include "timer.h" +#include "network.h" +#include + +/* +A nested linked list increases efficiency of insertions. +Depending on the number of timers we have, we might need to have nested linked lists +in order to improve insertion efficiency. +The code below is preperation for that end, should it be necessary. + +typedef struct { + struct timer_package* _next; + union { + timer_packet* _inner; + timer* queue; + }; + uint64_t pkgtime; +} timer_package; + +timer_package* timer_package_pool; + +static timer_package* new_package() +{ + timer_package* ret; + if (timer_package_pool) { + ret = timer_package_pool; + timer_package_pool = timer_package_pool->_next; + } else { + ret = calloc(1, sizeof(struct timer_package)); + } + return ret; +} + +static void delete_package(timer_package* p) +{ + p->_next = timer_package_pool; + timer_package_pool = p; +} +*/ + +enum timer_state { + STATE_INACTIVE = 0, + STATE_ACTIVE, + STATE_CALLBACK +}; + +struct timer +{ + enum timer_state state; + timer* _prev; + timer* _next; + timer_callback cb; + void* userdata; + uint64_t deadline; +}; + +static timer* timer_main_queue; +static timer* timer_us_queue; /* hi-speed queue */ + +inline static void timer_dequeue(timer* t, timer** queue) +{ + if (t->state == STATE_INACTIVE) return; /* not in a queue */ + + if (t->_prev) { + t->_prev->_next = t->_next; + } else { + *queue = t->_next; + } + if (t->_next) t->_next->_prev = t->_prev; + t->state = STATE_INACTIVE; +} + +static void timer_enqueue(timer* t, timer** queue, timer* prev) +{ + t->state = STATE_ACTIVE; + while (true) { + if (!*queue) { + t->_next = 0; + t->_prev = prev; + *queue = t; + return; + } + + if ((*queue)->deadline > t->deadline) { + (*queue)->_prev = t; + t->_next = *queue; + t->_prev = prev; + *queue = t; + return; + } + + prev = *queue; + queue = &((*queue)->_next); + } +} + +/*** interface ***/ + +void timer_init() +{ + /* Nothing needs to be done... yet. */ +} + +/* Do not depend on fields being zeroed */ +static timer* timer_pool; /* timer_pool is SINGLY LINKED!! */ + +timer* timer_new(void) +{ + timer* ret; + if (timer_pool) { + ret = timer_pool; + timer_pool = timer_pool->_next; + } else { + ret = calloc(1, sizeof(struct timer)); + } + ret->state = STATE_INACTIVE; + return ret; +} + +void timer_delete(timer* t) +{ + timer_dequeue(t, &timer_main_queue); + t->_next = timer_pool; + t->state = STATE_INACTIVE; + timer_pool = t; +} + +void timer_setup(timer* t, timer_callback cb, void* userarg) +{ + t->cb = cb; + t->userdata = userarg; +} + +void* timer_get_userdata(timer* t) +{ + return t->userdata; +} + +static void timer_delay_us(timer* t, int us) +{ + t->deadline += us; + timer** queue = t->_prev ? &(t->_prev->_next) : &timer_main_queue; + timer_dequeue(t, &timer_main_queue); + timer_enqueue(t, queue, t->_prev); +} + +/* Starts the timer so that it's called in sec seconds in the future. + * A non-positive value of sec results in the callback being called immediately. + * This function may be called again after a timer has been started to adjust + * the expiry time. */ +void timer_start(timer* t, int sec) +{ + uint64_t newdeadline = current_time() + sec * US_PER_SECOND; + if (timer_is_active(t)){ + if (t->deadline < newdeadline) { + timer_delay_us(t, newdeadline - t->deadline); + return; + } + timer_dequeue(t, &timer_main_queue); + } + t->deadline = newdeadline; + timer_enqueue(t, &timer_main_queue, 0); +} + +/* Stops the timer. Returns -1 if the timer was not active. */ +int timer_stop(timer* t) +{ + int ret = timer_is_active(t) ? -1 : 0; + timer_dequeue(t, &timer_main_queue); + return ret; +} + +/* Adds additionalsec seconds to the timer. + * Returns -1 and does nothing if the timer was not active. */ +int timer_delay(timer* t, int additonalsec) +{ + if (!timer_is_active(t)) return -1; + timer_delay_us(t, additonalsec * US_PER_SECOND); + return 0; +} + +static uint64_t timer_diff(timer* t, uint64_t time) +{ + if (t->deadline <= time) return 0; + return time - t->deadline; +} + +/* Returns the time remaining on a timer in seconds. + * Returns -1 if the timer is not active. + * Returns 0 if the timer has expired and will be called upon the next call to timer_poll. */ +int timer_time_remaining(timer* t) +{ + if (!timer_is_active(t)) return -1; + return timer_diff(t, current_time()) / US_PER_SECOND; +} + +bool timer_is_active(timer* t) +{ + return t->state != STATE_INACTIVE; +} + +/* Single-use timer. + * Creates a new timer, preforms setup and starts it. */ +void timer_single(timer_callback cb, void* userarg, int sec) +{ + timer* t = timer_new(); + timer_setup(t, cb, userarg); + timer_start(t, sec); +} + +/* Single-use microsecond timer. */ +void timer_us(timer_callback cb, void* userarg, int us) +{ + timer* t = timer_new(); + timer_setup(t, cb, userarg); + t->deadline = current_time() + us; + t->state = STATE_ACTIVE; + timer_enqueue(t, &timer_us_queue, 0); +} + +uint64_t prevtime = 0; +void timer_poll(void) +{ + uint64_t time = current_time(); + + /* Handle millisecond timers */ + while (timer_us_queue) { + if (timer_diff(timer_us_queue, time) != 0) break; + timer* t = timer_us_queue; + timer_dequeue(t, &timer_us_queue); + t->cb(0, t->userdata); + timer_delete(t); + } + + if (time - prevtime > US_PER_SECOND || prevtime == 0 || prevtime > time) { + /* time moving backwards is just a sanity check */ + prevtime = time; + + while (timer_main_queue) { + if (timer_diff(timer_main_queue, time) != 0) break; + timer* t = timer_main_queue; + t->state = STATE_CALLBACK; + int rv = t->cb(t, t->userdata); + if (rv != 0) { + timer_dequeue(t, &timer_main_queue); + timer_delete(t); + continue; + } + if (t->state != STATE_ACTIVE) { + timer_dequeue(t, &timer_main_queue); + } + } + } +} + +/*** Internal Testing ***/ + +/* I do not want to expose internals to the public, + * which is why internals testing is done this way. */ +void timer_internal_tests(bool (*assert)(bool, char*)) +{ + +} + +void timer_debug_print() +{ + timer* t = timer_main_queue; + printf("Queue:\n"); + while (t) { + printf("%lli (%lli) : %s\n", t->deadline, t->deadline/US_PER_SECOND, (char*)t->userdata); + t = t->_next; + } +} 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 @@ +/* timer.h + * + * Timing subsystem. Provides deadline timers. + * All times are aliased to a second for efficiency. + * + * Timer Guarantees: + * - The callback will not be called before the timer expires. + * - The callback will be called sometime after the timer expires, + * on a best effort basis. + * - If timer_poll is called at least once a second, the callback + * will be called at most one second after it expires. + * + * Copyright (C) 2013 Tox project All Rights Reserved. + * + * This file is part of Tox. + * + * Tox is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tox is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tox. If not, see . + * + */ + +#ifndef TIMER_H +#define TIMER_H + +#include +#include + +#define US_PER_SECOND 1000000 /* 1 s = 10^6 us */ + +struct timer; +typedef struct timer timer; + +/* If time_callback returns a non-zero value, timer t is deleted. + * You may call any of the timer functions within the callback: + * For example, you may call timer_start to restart the timer from + * within a callback. */ +typedef int (*timer_callback)(timer* t, void* userarg); + +/* Initisalise timer subsystem */ +void timer_init(void); + +/* Poll. (I will eventually replace all polling in Tox with an async system.) */ +void timer_poll(void); + +/* Creates a new timer. Does not enqueue/start it. */ +timer* timer_new(void); + +/* Destroys a timer instance. */ +void timer_delete(timer* t); + +/* Sets up the timer callback. */ +void timer_setup(timer* t, timer_callback cb, void* userarg); + +/* Accessor Function. */ +void* timer_get_userdata(timer* t); + +/* Starts the timer so that it's called in sec seconds in the future from now. + * A non-positive value of sec results in the callback being called immediately. + * This function may be called again after a timer has been started to adjust + * the expiry time. */ +void timer_start(timer* t, int sec); + +/* Stops the timer. Returns -1 if the timer was not active. */ +int timer_stop(timer* t); + +/* Adds additionalsec seconds to the timer. + * Returns -1 and does nothing if the timer was not active. */ +int timer_delay(timer* t, int additonalsec); + +/* Returns the time remaining on a timer in seconds. + * Returns -1 if the timer is not active. + * Returns 0 if the timer has expired and the callback hasn't been called yet. */ +int timer_time_remaining(timer* t); + +/* Determines if timer is active. Returns TRUE if it is active */ +bool timer_is_active(timer* t); + +/* Single-use timer. + * Creates a new timer, preforms setup and starts it. + * Callback must return a non-zero value to prevent memory leak. */ +void timer_single(timer_callback cb, void* userarg, int sec); + +/* Single-use microsecond timer. + * Creates a new timer, preforms setup and starts it. + * Please do not use this when accuracy is not absolutely required. + * Use when one needs to time a period < 1 s. + * Use the more coarse timers above for periods > 5 s. + * WARNING: the callback will be called with NULL as the first argument */ +void timer_us(timer_callback cb, void* userarg, int us); + +/* Internal Testing */ +void timer_internal_tests(bool(*)(bool, char*)); + +#endif diff --git a/testing/CMakeLists.txt b/testing/CMakeLists.txt index 7322509a..65ba35c0 100644 --- a/testing/CMakeLists.txt +++ b/testing/CMakeLists.txt @@ -9,6 +9,7 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Lossless_UDP_testclient.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Lossless_UDP_testserver.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Messenger_test.cmake) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/crypto_speed_test.cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/timer_test.cmake) if(WIN32) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/nTox_win32.cmake) diff --git a/testing/cmake/timer_test.cmake b/testing/cmake/timer_test.cmake new file mode 100644 index 00000000..a5f8c5ec --- /dev/null +++ b/testing/cmake/timer_test.cmake @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 2.6.0) +project(timer_test C) + +set(exe_name timer_test) + +add_executable(${exe_name} + timer_test.c) + +linkCoreLibraries(${exe_name}) diff --git a/testing/timer_test.c b/testing/timer_test.c new file mode 100644 index 00000000..50c4cda7 --- /dev/null +++ b/testing/timer_test.c @@ -0,0 +1,66 @@ +#include "../core/timer.h" +#include + +#ifdef WINDOWS +#include +#else +#include +#endif + +void mssleep(int ms) +{ +#ifdef WINDOWS + Sleep(ms); +#else + usleep(ms * 1000); +#endif +} + +int callback(timer* t, void* arg){ + printf("%s\n", (char*)arg); + return 1; +} + +int repeating(timer* t, void *arg) { + printf("%s\n", (char*)arg); + timer_start(t, 3); + return 0; +} + +extern void timer_debug_print(); + +int main(int argc, char** argv) +{ + timer_init(); + timer_debug_print(); + + timer* t = timer_new(); + timer_setup(t, &callback, "Long setup method, 4 seconds"); + timer_start(t, 4); + timer_debug_print(); + + timer_single(&repeating, (void*)"This repeats every 3 seconds", 3); + timer_debug_print(); + + timer_single(&callback, "Short method, 4 seconds", 4); + timer_debug_print(); + + timer_single(&callback, "1 second", 1); + timer_debug_print(); + + timer_single(&callback, "15 seconds", 15); + timer_debug_print(); + + timer_single(&callback, "10 seconds", 10); + timer_debug_print(); + + timer_us(&callback, "100000us", 100000); + timer_us(&callback, "13s", 13 * US_PER_SECOND); + + while (true) { + timer_poll(); + mssleep(10); + } + + return 0; +}