mirror of
https://github.com/irungentoo/toxcore.git
synced 2024-03-22 13:30:51 +08:00
Implemented Deadline Timer and timer_test
This commit is contained in:
parent
139d915482
commit
1a39c397c5
|
@ -10,7 +10,8 @@ set(core_sources
|
||||||
LAN_discovery.c
|
LAN_discovery.c
|
||||||
Messenger.c
|
Messenger.c
|
||||||
util.c
|
util.c
|
||||||
ping.c)
|
ping.c
|
||||||
|
timer.c)
|
||||||
|
|
||||||
if(SHARED_TOXCORE)
|
if(SHARED_TOXCORE)
|
||||||
add_library(toxcore SHARED ${core_sources})
|
add_library(toxcore SHARED ${core_sources})
|
||||||
|
|
273
core/timer.c
Normal file
273
core/timer.c
Normal file
|
@ -0,0 +1,273 @@
|
||||||
|
#include "timer.h"
|
||||||
|
#include "network.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
104
core/timer.h
Normal file
104
core/timer.h
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TIMER_H
|
||||||
|
#define TIMER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#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
|
|
@ -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/Lossless_UDP_testserver.cmake)
|
||||||
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Messenger_test.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/crypto_speed_test.cmake)
|
||||||
|
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/timer_test.cmake)
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/nTox_win32.cmake)
|
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/nTox_win32.cmake)
|
||||||
|
|
9
testing/cmake/timer_test.cmake
Normal file
9
testing/cmake/timer_test.cmake
Normal file
|
@ -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})
|
66
testing/timer_test.c
Normal file
66
testing/timer_test.c
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#include "../core/timer.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#ifdef WINDOWS
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user