1 /*
2  * This software is licensed under the terms of the MIT License.
3  * See COPYING for further information.
4  * ---
5  * Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
6  * Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
7  */
8 
9 #include "taisei.h"
10 
11 #include "util.h"
12 #include "hirestime.h"
13 
14 static bool use_hires;
15 static hrtime_t time_current;
16 static hrtime_t time_offset;
17 static uint64_t prev_hires_time;
18 static uint64_t prev_hires_freq;
19 static uint64_t fast_path_mul;
20 
set_freq(uint64_t freq)21 INLINE void set_freq(uint64_t freq) {
22 	prev_hires_freq = freq;
23 	lldiv_t d = lldiv(HRTIME_RESOLUTION, freq);
24 	fast_path_mul = d.quot * (d.rem == 0);
25 }
26 
time_update(void)27 static void time_update(void) {
28 	bool retry;
29 
30 	do {
31 		retry = false;
32 
33 		uint64_t freq = SDL_GetPerformanceFrequency();
34 		uint64_t cntr = SDL_GetPerformanceCounter();
35 
36 		if(freq != prev_hires_freq) {
37 			log_debug("High resolution timer frequency changed: was %"PRIu64", now %"PRIu64". Saved time offset: %"PRIuTIME"", prev_hires_freq, freq, time_offset);
38 			time_offset = time_current;
39 			set_freq(freq);
40 			prev_hires_time = SDL_GetPerformanceCounter();
41 			retry = true;
42 			continue;
43 		}
44 
45 		hrtime_t time_new;
46 
47 		if(fast_path_mul) {
48 			time_new = time_offset + (cntr - prev_hires_time) * fast_path_mul;
49 		} else {
50 			time_new = time_offset + umuldiv64(cntr - prev_hires_time, HRTIME_RESOLUTION, freq);
51 		}
52 
53 		if(time_new < time_current) {
54 			log_warn("BUG: time went backwards. Was %"PRIuTIME", now %"PRIuTIME". Possible cause: your OS sucks spherical objects. Attempting to correct this...", time_current, time_new);
55 			time_offset = time_current;
56 			time_current = 0;
57 			prev_hires_time = SDL_GetPerformanceCounter();
58 			retry = true;
59 		} else {
60 			time_current = time_new;
61 		}
62 	} while(retry);
63 }
64 
time_init(void)65 void time_init(void) {
66 	use_hires = env_get("TAISEI_HIRES_TIMER", 1);
67 
68 	if(use_hires) {
69 		log_info("Using the system high resolution timer");
70 		prev_hires_time = SDL_GetPerformanceCounter();
71 		set_freq(SDL_GetPerformanceFrequency());
72 	} else {
73 		log_info("Not using the system high resolution timer: disabled by environment");
74 		return;
75 	}
76 }
77 
time_shutdown(void)78 void time_shutdown(void) {
79 
80 }
81 
time_get(void)82 hrtime_t time_get(void) {
83 	if(use_hires) {
84 		assert(is_main_thread());
85 		time_update();
86 		return time_current;
87 	}
88 
89 	return SDL_GetTicks() * (HRTIME_RESOLUTION / 1000);
90 }
91