summaryrefslogtreecommitdiff
path: root/timer.c
blob: 96eb4537c30e8529a0dad1425b7d98929076ef6b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149


#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdint.h>
#include <stdbool.h>

struct timer_handle {
	HANDLE htimer;
	uint64_t interval_ns;
	uint64_t qpc_frequency;
	uint64_t next_deadline;
	bool started;
};

static inline uint64_t qpc_now_ns(uint64_t freq) {
	LARGE_INTEGER qpc;
	QueryPerformanceCounter(&qpc);
	return (uint64_t)((qpc.QuadPart * 1000000000ULL) / freq);
}

struct timer_handle *timer_new(uint64_t interval_ns) {
	struct timer_handle *t = (struct timer_handle *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct timer_handle));
	if(!t) return 0;

	t->htimer = CreateWaitableTimerW(0, TRUE, 0);
	if(!t->htimer) {
		HeapFree(GetProcessHeap(), 0, t);
		return 0;
	}

	LARGE_INTEGER freq;
	QueryPerformanceFrequency(&freq);
	t->qpc_frequency = freq.QuadPart;
	t->interval_ns = interval_ns;
	t->started = false;

	return t;
}

bool timer_start(struct timer_handle *t) {
	t->next_deadline = qpc_now_ns(t->qpc_frequency) + t->interval_ns;
	t->started = true;
	return true;
}

bool timer_wait(struct timer_handle *t) {
	if(!t->started) return false;

	uint64_t now = qpc_now_ns(t->qpc_frequency);
	if(t->next_deadline <= now) {
		t->next_deadline += t->interval_ns;
		return true;
	}

	uint64_t sleep_ns = t->next_deadline - now;
	if(sleep_ns > 500000) { // > 0.5ms
		LARGE_INTEGER due;
		due.QuadPart = -(int64_t)((sleep_ns - 500000) / 100); // 100ns units, negative = relative
		SetWaitableTimer(t->htimer, &due, 0, 0, 0, 0);
		WaitForSingleObject(t->htimer, INFINITE);
	}

	while(qpc_now_ns(t->qpc_frequency) < t->next_deadline) {
		YieldProcessor(); // pause instruction
	}

	t->next_deadline += t->interval_ns;
	return true;
}

void timer_destroy(struct timer_handle *t) {
	if(t) {
		if(t->htimer) CloseHandle(t->htimer);
		HeapFree(GetProcessHeap(), 0, t);
	}
}


#else // Linux
#include <sys/timerfd.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>

struct timer_handle {
	int tfd;
	int epfd;
	struct itimerspec spec;
	int started;
};

static struct timer_handle *timer_new(uint64_t interval_ns) {
	struct timer_handle *t = (struct timer_handle *)malloc(sizeof(struct timer_handle));
	if(!t) return 0;

	t->tfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
	if(t->tfd < 0) { free(t); return 0; }

	t->epfd = epoll_create1(EPOLL_CLOEXEC);
	if(t->epfd < 0) {
		close(t->tfd); free(t); return 0;
	}

	struct epoll_event ev = { .events = EPOLLIN, .data = { .fd = t->tfd } };
	epoll_ctl(t->epfd, EPOLL_CTL_ADD, t->tfd, &ev);

	t->spec.it_interval.tv_sec = interval_ns / 1000000000ULL;
	t->spec.it_interval.tv_nsec = interval_ns % 1000000000ULL;
	t->spec.it_value.tv_sec = 0;
	t->spec.it_value.tv_nsec = 0;
	t->started = 0;

	return t;
}

static bool timer_start(struct timer_handle *t) {
	if(t->started) return true;

	t->spec.it_value = t->spec.it_interval;
	if(timerfd_settime(t->tfd, 0, &t->spec, 0)) return false;

	t->started = 1;
	return true;
}

static bool timer_wait(struct timer_handle *t) {
	if(!t->started) return false;

	struct epoll_event ev;
	int r = epoll_wait(t->epfd, &ev, 1, -1);
	if(r < 0) return false;
	uint64_t expirations;
	read(t->tfd, &expirations, sizeof(expirations));
	return true;
}

static void timer_destroy(struct timer_handle *t) {
	if(t) {
		if(t->tfd >= 0) close(t->tfd);
		if(t->epfd >= 0) close(t->epfd);
		free(t);
	}
}

#endif