-
Notifications
You must be signed in to change notification settings - Fork 41
Expand file tree
/
Copy pathtimer.cpp
More file actions
234 lines (186 loc) · 7.79 KB
/
timer.cpp
File metadata and controls
234 lines (186 loc) · 7.79 KB
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#include <thread>
#include <variant>
#include <set>
#include "blockingconcurrentqueue.h"
#include "ultramodern/ultra64.h"
#include "ultramodern/ultramodern.hpp"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include "Windows.h"
#endif
// Start time for the program
static std::chrono::high_resolution_clock::time_point start_time = std::chrono::high_resolution_clock::now();
// Offset of the duration since program start used to calculate the value for osGetTime.
static int64_t ostime_offset = 0;
// Game speed multiplier (1 means no speedup)
constexpr uint32_t speed_multiplier = 1;
// N64 CPU counter ticks per millisecond
constexpr uint32_t counter_per_ms = 46'875 * speed_multiplier;
struct OSTimer {
PTR(OSTimer) unused1;
PTR(OSTimer) unused2;
OSTime interval;
OSTime timestamp;
PTR(OSMesgQueue) mq;
OSMesg msg;
};
struct AddTimerAction {
PTR(OSTimer) timer;
};
struct RemoveTimerAction {
PTR(OSTimer) timer;
};
using Action = std::variant<AddTimerAction, RemoveTimerAction>;
struct {
std::thread thread;
moodycamel::BlockingConcurrentQueue<Action> action_queue{};
} timer_context;
uint64_t duration_to_ticks(std::chrono::high_resolution_clock::duration duration) {
uint64_t delta_micros = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
// More accurate than using a floating point timer, will only overflow after running for 12.47 years
// Units: (micros * (counts/millis)) / (micros/millis) = counts
uint64_t total_count = (delta_micros * counter_per_ms) / 1000;
return total_count;
}
std::chrono::microseconds ticks_to_duration(uint64_t ticks) {
using namespace std::chrono_literals;
return ticks * 1000us / counter_per_ms;
}
std::chrono::high_resolution_clock::time_point ticks_to_timepoint(uint64_t ticks) {
return start_time + ticks_to_duration(ticks);
}
uint64_t time_now() {
return duration_to_ticks(std::chrono::high_resolution_clock::now() - start_time);
}
void timer_thread(RDRAM_ARG1) {
ultramodern::set_native_thread_name("Timer Thread");
ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::VeryHigh);
// Lambda comparator function to keep the set ordered
auto timer_sort = [PASS_RDRAM1](PTR(OSTimer) a_, PTR(OSTimer) b_) {
OSTimer* a = TO_PTR(OSTimer, a_);
OSTimer* b = TO_PTR(OSTimer, b_);
// Order by timestamp if the timers have different timestamps
if (a->timestamp != b->timestamp) {
return a->timestamp < b->timestamp;
}
// If they have the exact same timestamp then order by address instead
return a < b;
};
// Ordered set of timers that are currently active
std::set<PTR(OSTimer), decltype(timer_sort)> active_timers{timer_sort};
// Lambda to process a timer action to handle adding and removing timers
auto process_timer_action = [&](const Action& action) {
// Determine the action type and act on it
if (const auto* add_action = std::get_if<AddTimerAction>(&action)) {
active_timers.insert(add_action->timer);
} else if (const auto* remove_action = std::get_if<RemoveTimerAction>(&action)) {
active_timers.erase(remove_action->timer);
}
};
while (true) {
// Empty the action queue
Action cur_action;
while (timer_context.action_queue.try_dequeue(cur_action)) {
process_timer_action(cur_action);
}
// If there's no timer to act on, wait for one to come in from the action queue
while (active_timers.empty()) {
timer_context.action_queue.wait_dequeue(cur_action);
process_timer_action(cur_action);
}
// Get the timer that's closest to running out
PTR(OSTimer) cur_timer_ = *active_timers.begin();
OSTimer* cur_timer = TO_PTR(OSTimer, cur_timer_);
// Remove the timer from the queue (it may get readded if waiting is interrupted)
active_timers.erase(cur_timer_);
// Determine how long to wait to reach the timer's timestamp
auto wait_duration = ticks_to_timepoint(cur_timer->timestamp) - std::chrono::high_resolution_clock::now();
// Wait for either the duration to complete or a new action to come through
if (wait_duration.count() >= 0 && timer_context.action_queue.wait_dequeue_timed(cur_action, wait_duration)) {
// Timer was interrupted by a new action
// Add the current timer back to the queue (done first in case the action is to remove this timer)
active_timers.insert(cur_timer_);
// Process the new action
process_timer_action(cur_action);
}
else {
// Waiting for the timer completed, so send the timer's message to its message queue
ultramodern::enqueue_external_message(cur_timer->mq, cur_timer->msg, false, true);
// If the timer has a specified interval then reload it with that value
if (cur_timer->interval != 0) {
cur_timer->timestamp = cur_timer->interval + time_now();
active_timers.insert(cur_timer_);
}
}
}
}
void ultramodern::init_timers(RDRAM_ARG1) {
timer_context.thread = std::thread{ timer_thread, PASS_RDRAM1 };
timer_context.thread.detach();
}
uint32_t ultramodern::get_speed_multiplier() {
return speed_multiplier;
}
std::chrono::high_resolution_clock::time_point ultramodern::get_start() {
return start_time;
}
std::chrono::high_resolution_clock::duration ultramodern::time_since_start() {
return std::chrono::high_resolution_clock::now() - start_time;
}
extern "C" u32 osGetCount() {
uint64_t total_count = time_now();
// Allow for overflows, which is how osGetCount behaves
return (uint32_t)total_count;
}
extern "C" void osSetCount(u32 count) {
assert(false);
}
extern "C" OSTime osGetTime() {
uint64_t total_count = time_now() - ostime_offset;
return total_count;
}
extern "C" void osSetTime(OSTime t) {
ostime_offset = time_now() - t;
}
extern "C" int osSetTimer(RDRAM_ARG PTR(OSTimer) t_, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg) {
OSTimer* t = TO_PTR(OSTimer, t_);
// Determine the time when this timer will trigger off
if (countdown == 0) {
// Set the timestamp based on the interval
t->timestamp = interval + time_now();
} else {
t->timestamp = countdown + time_now();
}
t->interval = interval;
t->mq = mq;
t->msg = msg;
timer_context.action_queue.enqueue(AddTimerAction{ t_ });
return 0;
}
extern "C" int osStopTimer(RDRAM_ARG PTR(OSTimer) t_) {
timer_context.action_queue.enqueue(RemoveTimerAction{ t_ });
// TODO don't blindly return 0 here; requires some response from the timer thread to know what the returned value was
return 0;
}
#ifdef _WIN32
// The implementations of std::chrono::sleep_until and sleep_for were affected by changing the system clock backwards in older versions
// of Microsoft's STL. This was fixed as of Visual Studio 2022 17.9, but to be safe ultramodern uses Win32 Sleep directly.
void ultramodern::sleep_milliseconds(uint32_t millis) {
Sleep(millis);
}
void ultramodern::sleep_until(const std::chrono::high_resolution_clock::time_point& time_point) {
auto time_now = std::chrono::high_resolution_clock::now();
if (time_point > time_now) {
long long delta_ms = std::chrono::ceil<std::chrono::milliseconds>(time_point - time_now).count();
// printf("Sleeping %lld %d ms\n", delta_ms, (uint32_t)delta_ms);
Sleep(delta_ms);
}
}
#else
void ultramodern::sleep_milliseconds(uint32_t millis) {
std::this_thread::sleep_for(std::chrono::milliseconds{millis});
}
void ultramodern::sleep_until(const std::chrono::high_resolution_clock::time_point& time_point) {
std::this_thread::sleep_until(time_point);
}
#endif