summaryrefslogtreecommitdiff
path: root/src/common/timer.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/timer.c')
-rw-r--r--src/common/timer.c432
1 files changed, 432 insertions, 0 deletions
diff --git a/src/common/timer.c b/src/common/timer.c
new file mode 100644
index 000000000..c239a9d70
--- /dev/null
+++ b/src/common/timer.c
@@ -0,0 +1,432 @@
+// Copyright (c) Athena Dev Teams - Licensed under GNU GPL
+// For more information, see LICENCE in the main folder
+
+#include "../common/cbasetypes.h"
+#include "../common/db.h"
+#include "../common/malloc.h"
+#include "../common/showmsg.h"
+#include "../common/utils.h"
+#include "timer.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#ifdef WIN32
+#include "../common/winapi.h" // GetTickCount()
+#else
+#include <unistd.h>
+#include <sys/time.h> // struct timeval, gettimeofday()
+#endif
+
+// If the server can't handle processing thousands of monsters
+// or many connected clients, please increase TIMER_MIN_INTERVAL.
+#define TIMER_MIN_INTERVAL 50
+#define TIMER_MAX_INTERVAL 1000
+
+// timers (array)
+static struct TimerData* timer_data = NULL;
+static int timer_data_max = 0;
+static int timer_data_num = 0;
+
+// free timers (array)
+static int* free_timer_list = NULL;
+static int free_timer_list_max = 0;
+static int free_timer_list_pos = 0;
+
+
+/// Comparator for the timer heap. (minimum tick at top)
+/// Returns negative if tid1's tick is smaller, positive if tid2's tick is smaller, 0 if equal.
+///
+/// @param tid1 First timer
+/// @param tid2 Second timer
+/// @return negative if tid1 is top, positive if tid2 is top, 0 if equal
+#define DIFFTICK_MINTOPCMP(tid1,tid2) DIFF_TICK(timer_data[tid1].tick,timer_data[tid2].tick)
+
+// timer heap (binary heap of tid's)
+static BHEAP_VAR(int, timer_heap);
+
+
+// server startup time
+time_t start_time;
+
+
+/*----------------------------
+ * Timer debugging
+ *----------------------------*/
+struct timer_func_list {
+ struct timer_func_list* next;
+ TimerFunc func;
+ char* name;
+} *tfl_root = NULL;
+
+/// Sets the name of a timer function.
+int add_timer_func_list(TimerFunc func, char* name)
+{
+ struct timer_func_list* tfl;
+
+ if (name) {
+ for( tfl=tfl_root; tfl != NULL; tfl=tfl->next )
+ {// check suspicious cases
+ if( func == tfl->func )
+ ShowWarning("add_timer_func_list: duplicating function %p(%s) as %s.\n",tfl->func,tfl->name,name);
+ else if( strcmp(name,tfl->name) == 0 )
+ ShowWarning("add_timer_func_list: function %p has the same name as %p(%s)\n",func,tfl->func,tfl->name);
+ }
+ CREATE(tfl,struct timer_func_list,1);
+ tfl->next = tfl_root;
+ tfl->func = func;
+ tfl->name = aStrdup(name);
+ tfl_root = tfl;
+ }
+ return 0;
+}
+
+/// Returns the name of the timer function.
+char* search_timer_func_list(TimerFunc func)
+{
+ struct timer_func_list* tfl;
+
+ for( tfl=tfl_root; tfl != NULL; tfl=tfl->next )
+ if (func == tfl->func)
+ return tfl->name;
+
+ return "unknown timer function";
+}
+
+/*----------------------------
+ * Get tick time
+ *----------------------------*/
+
+#if defined(ENABLE_RDTSC)
+static uint64 RDTSC_BEGINTICK = 0, RDTSC_CLOCK = 0;
+
+static __inline uint64 _rdtsc(){
+ register union{
+ uint64 qw;
+ uint32 dw[2];
+ } t;
+
+ asm volatile("rdtsc":"=a"(t.dw[0]), "=d"(t.dw[1]) );
+
+ return t.qw;
+}
+
+static void rdtsc_calibrate(){
+ uint64 t1, t2;
+ int32 i;
+
+ ShowStatus("Calibrating Timer Source, please wait... ");
+
+ RDTSC_CLOCK = 0;
+
+ for(i = 0; i < 5; i++){
+ t1 = _rdtsc();
+ usleep(1000000); //1000 MS
+ t2 = _rdtsc();
+ RDTSC_CLOCK += (t2 - t1) / 1000;
+ }
+ RDTSC_CLOCK /= 5;
+
+ RDTSC_BEGINTICK = _rdtsc();
+
+ ShowMessage(" done. (Frequency: %u Mhz)\n", (uint32)(RDTSC_CLOCK/1000) );
+}
+
+#endif
+
+/// platform-abstracted tick retrieval
+static unsigned int tick(void)
+{
+#if defined(WIN32)
+ return GetTickCount();
+#elif defined(ENABLE_RDTSC)
+ //
+ return (unsigned int)((_rdtsc() - RDTSC_BEGINTICK) / RDTSC_CLOCK);
+ //
+#elif defined(HAVE_MONOTONIC_CLOCK)
+ struct timespec tval;
+ clock_gettime(CLOCK_MONOTONIC, &tval);
+ return tval.tv_sec * 1000 + tval.tv_nsec / 1000000;
+#else
+ struct timeval tval;
+ gettimeofday(&tval, NULL);
+ return tval.tv_sec * 1000 + tval.tv_usec / 1000;
+#endif
+}
+
+//////////////////////////////////////////////////////////////////////////
+#if defined(TICK_CACHE) && TICK_CACHE > 1
+//////////////////////////////////////////////////////////////////////////
+// tick is cached for TICK_CACHE calls
+static unsigned int gettick_cache;
+static int gettick_count = 1;
+
+unsigned int gettick_nocache(void)
+{
+ gettick_count = TICK_CACHE;
+ gettick_cache = tick();
+ return gettick_cache;
+}
+
+unsigned int gettick(void)
+{
+ return ( --gettick_count == 0 ) ? gettick_nocache() : gettick_cache;
+}
+//////////////////////////////
+#else
+//////////////////////////////
+// tick doesn't get cached
+unsigned int gettick_nocache(void)
+{
+ return tick();
+}
+
+unsigned int gettick(void)
+{
+ return tick();
+}
+//////////////////////////////////////////////////////////////////////////
+#endif
+//////////////////////////////////////////////////////////////////////////
+
+/*======================================
+ * CORE : Timer Heap
+ *--------------------------------------*/
+
+/// Adds a timer to the timer_heap
+static void push_timer_heap(int tid)
+{
+ BHEAP_ENSURE(timer_heap, 1, 256);
+ BHEAP_PUSH(timer_heap, tid, DIFFTICK_MINTOPCMP);
+}
+
+/*==========================
+ * Timer Management
+ *--------------------------*/
+
+/// Returns a free timer id.
+static int acquire_timer(void)
+{
+ int tid;
+
+ // select a free timer
+ if (free_timer_list_pos) {
+ do {
+ tid = free_timer_list[--free_timer_list_pos];
+ } while(tid >= timer_data_num && free_timer_list_pos > 0);
+ } else
+ tid = timer_data_num;
+
+ // check available space
+ if( tid >= timer_data_num )
+ for (tid = timer_data_num; tid < timer_data_max && timer_data[tid].type; tid++);
+ if (tid >= timer_data_num && tid >= timer_data_max)
+ {// expand timer array
+ timer_data_max += 256;
+ if( timer_data )
+ RECREATE(timer_data, struct TimerData, timer_data_max);
+ else
+ CREATE(timer_data, struct TimerData, timer_data_max);
+ memset(timer_data + (timer_data_max - 256), 0, sizeof(struct TimerData)*256);
+ }
+
+ if( tid >= timer_data_num )
+ timer_data_num = tid + 1;
+
+ return tid;
+}
+
+/// Starts a new timer that is deleted once it expires (single-use).
+/// Returns the timer's id.
+int add_timer(unsigned int tick, TimerFunc func, int id, intptr_t data)
+{
+ int tid;
+
+ tid = acquire_timer();
+ timer_data[tid].tick = tick;
+ timer_data[tid].func = func;
+ timer_data[tid].id = id;
+ timer_data[tid].data = data;
+ timer_data[tid].type = TIMER_ONCE_AUTODEL;
+ timer_data[tid].interval = 1000;
+ push_timer_heap(tid);
+
+ return tid;
+}
+
+/// Starts a new timer that automatically restarts itself (infinite loop until manually removed).
+/// Returns the timer's id, or INVALID_TIMER if it fails.
+int add_timer_interval(unsigned int tick, TimerFunc func, int id, intptr_t data, int interval)
+{
+ int tid;
+
+ if( interval < 1 )
+ {
+ ShowError("add_timer_interval: invalid interval (tick=%u %p[%s] id=%d data=%d diff_tick=%d)\n", tick, func, search_timer_func_list(func), id, data, DIFF_TICK(tick, gettick()));
+ return INVALID_TIMER;
+ }
+
+ tid = acquire_timer();
+ timer_data[tid].tick = tick;
+ timer_data[tid].func = func;
+ timer_data[tid].id = id;
+ timer_data[tid].data = data;
+ timer_data[tid].type = TIMER_INTERVAL;
+ timer_data[tid].interval = interval;
+ push_timer_heap(tid);
+
+ return tid;
+}
+
+/// Retrieves internal timer data
+const struct TimerData* get_timer(int tid)
+{
+ return ( tid >= 0 && tid < timer_data_num ) ? &timer_data[tid] : NULL;
+}
+
+/// Marks a timer specified by 'id' for immediate deletion once it expires.
+/// Param 'func' is used for debug/verification purposes.
+/// Returns 0 on success, < 0 on failure.
+int delete_timer(int tid, TimerFunc func)
+{
+ if( tid < 0 || tid >= timer_data_num )
+ {
+ ShowError("delete_timer error : no such timer %d (%p(%s))\n", tid, func, search_timer_func_list(func));
+ return -1;
+ }
+ if( timer_data[tid].func != func )
+ {
+ ShowError("delete_timer error : function mismatch %p(%s) != %p(%s)\n", timer_data[tid].func, search_timer_func_list(timer_data[tid].func), func, search_timer_func_list(func));
+ return -2;
+ }
+
+ timer_data[tid].func = NULL;
+ timer_data[tid].type = TIMER_ONCE_AUTODEL;
+
+ return 0;
+}
+
+/// Adjusts a timer's expiration time.
+/// Returns the new tick value, or -1 if it fails.
+int addtick_timer(int tid, unsigned int tick)
+{
+ return settick_timer(tid, timer_data[tid].tick+tick);
+}
+
+/// Modifies a timer's expiration time (an alternative to deleting a timer and starting a new one).
+/// Returns the new tick value, or -1 if it fails.
+int settick_timer(int tid, unsigned int tick)
+{
+ size_t i;
+
+ // search timer position
+ ARR_FIND(0, BHEAP_LENGTH(timer_heap), i, BHEAP_DATA(timer_heap)[i] == tid);
+ if( i == BHEAP_LENGTH(timer_heap) )
+ {
+ ShowError("settick_timer: no such timer %d (%p(%s))\n", tid, timer_data[tid].func, search_timer_func_list(timer_data[tid].func));
+ return -1;
+ }
+
+ if( (int)tick == -1 )
+ tick = 0;// add 1ms to avoid the error value -1
+
+ if( timer_data[tid].tick == tick )
+ return (int)tick;// nothing to do, already in propper position
+
+ // pop and push adjusted timer
+ BHEAP_POPINDEX(timer_heap, i, DIFFTICK_MINTOPCMP);
+ timer_data[tid].tick = tick;
+ BHEAP_PUSH(timer_heap, tid, DIFFTICK_MINTOPCMP);
+ return (int)tick;
+}
+
+/// Executes all expired timers.
+/// Returns the value of the smallest non-expired timer (or 1 second if there aren't any).
+int do_timer(unsigned int tick)
+{
+ int diff = TIMER_MAX_INTERVAL; // return value
+
+ // process all timers one by one
+ while( BHEAP_LENGTH(timer_heap) )
+ {
+ int tid = BHEAP_PEEK(timer_heap);// top element in heap (smallest tick)
+
+ diff = DIFF_TICK(timer_data[tid].tick, tick);
+ if( diff > 0 )
+ break; // no more expired timers to process
+
+ // remove timer
+ BHEAP_POP(timer_heap, DIFFTICK_MINTOPCMP);
+ timer_data[tid].type |= TIMER_REMOVE_HEAP;
+
+ if( timer_data[tid].func )
+ {
+ if( diff < -1000 )
+ // timer was delayed for more than 1 second, use current tick instead
+ timer_data[tid].func(tid, tick, timer_data[tid].id, timer_data[tid].data);
+ else
+ timer_data[tid].func(tid, timer_data[tid].tick, timer_data[tid].id, timer_data[tid].data);
+ }
+
+ // in the case the function didn't change anything...
+ if( timer_data[tid].type & TIMER_REMOVE_HEAP )
+ {
+ timer_data[tid].type &= ~TIMER_REMOVE_HEAP;
+
+ switch( timer_data[tid].type )
+ {
+ default:
+ case TIMER_ONCE_AUTODEL:
+ timer_data[tid].type = 0;
+ if (free_timer_list_pos >= free_timer_list_max) {
+ free_timer_list_max += 256;
+ RECREATE(free_timer_list,int,free_timer_list_max);
+ memset(free_timer_list + (free_timer_list_max - 256), 0, 256 * sizeof(int));
+ }
+ free_timer_list[free_timer_list_pos++] = tid;
+ break;
+ case TIMER_INTERVAL:
+ if( DIFF_TICK(timer_data[tid].tick, tick) < -1000 )
+ timer_data[tid].tick = tick + timer_data[tid].interval;
+ else
+ timer_data[tid].tick += timer_data[tid].interval;
+ push_timer_heap(tid);
+ break;
+ }
+ }
+ }
+
+ return cap_value(diff, TIMER_MIN_INTERVAL, TIMER_MAX_INTERVAL);
+}
+
+unsigned long get_uptime(void)
+{
+ return (unsigned long)difftime(time(NULL), start_time);
+}
+
+void timer_init(void)
+{
+#if defined(ENABLE_RDTSC)
+ rdtsc_calibrate();
+#endif
+
+ time(&start_time);
+}
+
+void timer_final(void)
+{
+ struct timer_func_list *tfl;
+ struct timer_func_list *next;
+
+ for( tfl=tfl_root; tfl != NULL; tfl = next ) {
+ next = tfl->next; // copy next pointer
+ aFree(tfl->name); // free structures
+ aFree(tfl);
+ }
+
+ if (timer_data) aFree(timer_data);
+ BHEAP_CLEAR(timer_heap);
+ if (free_timer_list) aFree(free_timer_list);
+}