1 /*
2 SuperCollider real time audio synthesis system
3 Copyright (c) 2002 James McCartney. All rights reserved.
4 http://www.audiosynth.com
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21
22 #include "PyrKernel.h"
23 #include "PyrSched.h"
24 #include "GC.h"
25 #include "PyrPrimitive.h"
26 #include "PyrSymbol.h"
27 #ifdef __APPLE__
28 # include <CoreAudio/HostTime.h>
29 #endif
30 #include <stdarg.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <math.h>
34 #include <limits>
35 #include <functional>
36
37 #if defined(__APPLE__) || defined(__linux__)
38 # include <pthread.h>
39 #endif
40
41 #include "SCBase.h"
42 #include "SC_Lock.h"
43 #include "SC_Clock.hpp"
44 #include "SC_LinkClock.hpp"
45
46 #include <boost/sync/semaphore.hpp>
47 #include <boost/sync/support/std_chrono.hpp>
48
49 // FIXME: These includes needs to be last on Windows, otherwise Ableton build breaks
50 // (Windows header include ordering dependencies)
51 #include "SC_Win32Utils.h"
52 #ifdef _MSC_VER
53 # include "wintime.h"
54 #else
55 # include <sys/time.h>
56 #endif
57
58 static const double dInfinity = std::numeric_limits<double>::infinity();
59
60 void runAwakeMessage(VMGlobals* g);
61
62
addheap(VMGlobals * g,PyrObject * heapArg,double schedtime,PyrSlot * task)63 bool addheap(VMGlobals* g, PyrObject* heapArg, double schedtime, PyrSlot* task) {
64 PyrHeap* heap = (PyrHeap*)heapArg;
65 #ifdef GC_SANITYCHECK
66 g->gc->SanityCheck();
67 #endif
68 if (heap->size >= ARRAYMAXINDEXSIZE(heap))
69 return false;
70 assert(heap->size);
71
72 // post("->addheap\n");
73 // dumpheap(heapArg);
74
75 /* parent and sibling in the heap, not in the task hierarchy */
76 int mom = heap->size - 1;
77 PyrSlot* pme = heap->slots + mom;
78 int stabilityCount = slotRawInt(&heap->count);
79 SetRaw(&heap->count, stabilityCount + 1);
80
81 for (; mom > 0;) { /* percolate up heap */
82 int newMom = ((mom - 3) / 2);
83 mom = newMom - newMom % 3; /// LATER: we could avoid the division by using 4 slots per element
84 PyrSlot* pmom = heap->slots + mom;
85 if (schedtime < slotRawFloat(pmom)) {
86 assert(slotRawInt(pmom + 2) < stabilityCount);
87 slotCopy(&pme[0], &pmom[0]);
88 slotCopy(&pme[1], &pmom[1]);
89 slotCopy(&pme[2], &pmom[2]);
90 pme = pmom;
91 } else
92 break;
93 }
94 SetFloat(&pme[0], schedtime);
95 slotCopy(&pme[1], task);
96 SetInt(&pme[2], stabilityCount);
97 g->gc->GCWrite(heap, task);
98 heap->size += 3;
99
100 #ifdef GC_SANITYCHECK
101 g->gc->SanityCheck();
102 #endif
103 // dumpheap(heapArg);
104 // post("<-addheap %g\n", schedtime);
105 return true;
106 }
107
108
lookheap(PyrObject * heap,double * schedtime,PyrSlot * task)109 bool lookheap(PyrObject* heap, double* schedtime, PyrSlot* task) {
110 if (heap->size > 1) {
111 *schedtime = slotRawFloat(&heap->slots[0]);
112 slotCopy(task, &heap->slots[1]);
113 return true;
114 } else
115 return false;
116 }
117
getheap(VMGlobals * g,PyrObject * heapArg,double * schedtime,PyrSlot * task)118 bool getheap(VMGlobals* g, PyrObject* heapArg, double* schedtime, PyrSlot* task) {
119 PyrHeap* heap = (PyrHeap*)heapArg;
120 PyrGC* gc = g->gc;
121 bool isPartialScanObj = gc->IsPartialScanObject(heapArg);
122 assert(heap->size);
123
124 // post("->getheap\n");
125 // dumpheap(heapArg);
126 if (heap->size > 1) {
127 *schedtime = slotRawFloat(&heap->slots[0]);
128 slotCopy(task, &heap->slots[1]);
129 heap->size -= 3;
130 int size = heap->size - 1;
131 slotCopy(&heap->slots[0], &heap->slots[size]);
132 slotCopy(&heap->slots[1], &heap->slots[size + 1]);
133 slotCopy(&heap->slots[2], &heap->slots[size + 2]);
134
135 /* parent and sibling in the heap, not in the task hierarchy */
136 int mom = 0;
137 int me = 3;
138 PyrSlot* pmom = heap->slots + mom;
139 PyrSlot* pme = heap->slots + me;
140 PyrSlot* pend = heap->slots + size;
141 double timetemp = slotRawFloat(&pmom[0]);
142 int stabilityCountTemp = slotRawInt(&pmom[2]);
143 PyrSlot tasktemp;
144 slotCopy(&tasktemp, &pmom[1]);
145 for (; pme < pend;) {
146 /* demote heap */
147 if (pme + 3 < pend
148 && ((slotRawFloat(&pme[0]) > slotRawFloat(&pme[3]))
149 || ((slotRawFloat(&pme[0]) == slotRawFloat(&pme[3]))
150 && (slotRawInt(&pme[2]) > slotRawInt(&pme[5]))))) {
151 me += 3;
152 pme += 3;
153 }
154 if (timetemp > slotRawFloat(&pme[0])
155 || (timetemp == slotRawFloat(&pme[0]) && stabilityCountTemp > slotRawInt(&pme[2]))) {
156 slotCopy(&pmom[0], &pme[0]);
157 slotCopy(&pmom[1], &pme[1]);
158 slotCopy(&pmom[2], &pme[2]);
159 if (isPartialScanObj) {
160 gc->GCWriteBlack(pmom + 1);
161 }
162 pmom = pme;
163 me = ((mom = me) * 2) + 3;
164 pme = heap->slots + me;
165 } else
166 break;
167 }
168 SetRaw(&pmom[0], timetemp);
169 slotCopy(&pmom[1], &tasktemp);
170 SetRaw(&pmom[2], stabilityCountTemp);
171 if (isPartialScanObj)
172 gc->GCWriteBlack(pmom + 1);
173
174 if (size == 0)
175 SetInt(&heap->count, 0);
176
177 // dumpheap(heapArg);
178 // post("<-getheap true\n");
179 return true;
180 } else {
181 // post("<-getheap false\n");
182 return false;
183 }
184 }
185
offsetheap(VMGlobals * g,PyrObject * heap,double offset)186 void offsetheap(VMGlobals* g, PyrObject* heap, double offset) {
187 long i;
188 for (i = 0; i < heap->size; i += 2) {
189 SetRaw(&heap->slots[i], slotRawFloat(&heap->slots[i]) + offset);
190 // post("%3d %9.2f %9.2f\n", i>>1, heap->slots[i].uf, offset);
191 }
192 }
193
dumpheap(PyrObject * heapArg)194 void dumpheap(PyrObject* heapArg) {
195 PyrHeap* heap = (PyrHeap*)heapArg;
196 double mintime = slotRawFloat(&heap->slots[0]);
197 int count = slotRawFloat(&heap->slots[2]);
198 int heapSize = heap->size - 1;
199 post("SCHED QUEUE (%d)\n", heapSize);
200 for (int i = 0; i < heapSize; i += 3) {
201 post("%3d(%3d) %9.2f %p %d\n", i / 3, i, slotRawFloat(&heap->slots[i]), slotRawObject(&heap->slots[i + 1]),
202 slotRawInt(&heap->slots[i + 2]));
203 if ((slotRawFloat(&heap->slots[i]) < mintime)
204 || (slotRawFloat(&heap->slots[i]) == mintime && slotRawInt(&heap->slots[i + 2]) < count))
205 post("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
206 }
207 }
208
209
210 bool gRunSched = false;
211 static std::thread gSchedThread;
212 static std::thread gResyncThread;
213 static boost::sync::semaphore gResyncThreadSemaphore;
214
215 std::condition_variable_any gSchedCond;
216 std::timed_mutex gLangMutex;
217
218 int64 gHostOSCoffset = 0;
219 int64 gHostStartNanos = 0;
220
221 int64 gElapsedOSCoffset = 0;
222
223 const int32 kSECONDS_FROM_1900_to_1970 = (int32)2208988800UL; /* 17 leap years */
224
225 static void syncOSCOffsetWithTimeOfDay();
226 void resyncThread();
227
228 // Use the highest resolution clock available for monotonic clock time
229 using monotonic_clock = std::conditional<std::chrono::high_resolution_clock::is_steady,
230 std::chrono::high_resolution_clock, std::chrono::steady_clock>::type;
231
232 static std::chrono::high_resolution_clock::time_point hrTimeOfInitialization;
233
schedInit()234 SCLANG_DLLEXPORT_C void schedInit() {
235 using namespace std::chrono;
236 hrTimeOfInitialization = high_resolution_clock::now();
237 #ifdef SC_ABLETON_LINK
238 LinkClock::Init();
239 #endif
240
241 syncOSCOffsetWithTimeOfDay();
242 gResyncThread = std::thread(resyncThread);
243
244 gHostStartNanos = duration_cast<nanoseconds>(hrTimeOfInitialization.time_since_epoch()).count();
245 gElapsedOSCoffset = (int64)(gHostStartNanos * kNanosToOSC) + gHostOSCoffset;
246 }
247
schedCleanup()248 SCLANG_DLLEXPORT_C void schedCleanup() {
249 gResyncThreadSemaphore.post();
250 gResyncThread.join();
251 }
252
elapsedTime()253 double elapsedTime() { return DurToFloat(std::chrono::high_resolution_clock::now() - hrTimeOfInitialization); }
254
monotonicClockTime()255 double monotonicClockTime() { return DurToFloat(monotonic_clock::now().time_since_epoch()); }
256
ElapsedTimeToOSC(double elapsed)257 int64 ElapsedTimeToOSC(double elapsed) { return (int64)(elapsed * kSecondsToOSC) + gElapsedOSCoffset; }
258
OSCToElapsedTime(int64 oscTime)259 double OSCToElapsedTime(int64 oscTime) { return (double)(oscTime - gElapsedOSCoffset) * kOSCtoSecs; }
260
ElapsedTimeToChrono(double elapsed,std::chrono::system_clock::time_point & out_time_point)261 void ElapsedTimeToChrono(double elapsed, std::chrono::system_clock::time_point& out_time_point) {
262 int64 oscTime = ElapsedTimeToOSC(elapsed);
263
264 int64 secs = ((oscTime >> 32) - kSECONDS_FROM_1900_to_1970);
265 int32 nano_secs = (int32)((oscTime & 0xFFFFFFFF) * kOSCtoNanos);
266
267
268 using namespace std::chrono;
269 #if 0 // TODO: check system_clock precision
270 system_clock::time_point time_point = system_clock::time_point(seconds(secs) + nanoseconds(nano_secs));
271 #else
272 int32 usecs = nano_secs / 1000;
273 system_clock::time_point time_point = system_clock::time_point(seconds(secs) + microseconds(usecs));
274 #endif
275 out_time_point = time_point;
276 }
277
OSCTime()278 int64 OSCTime() { return ElapsedTimeToOSC(elapsedTime()); }
279
syncOSCOffsetWithTimeOfDay()280 void syncOSCOffsetWithTimeOfDay() {
281 // generate a value gHostOSCoffset such that
282 // (gHostOSCoffset + systemTimeInOSCunits)
283 // is equal to gettimeofday time in OSCunits.
284 // Then if this machine is synced via NTP, we are synced with the world.
285 // more accurate way to do this??
286
287 using namespace std::chrono;
288 struct timeval tv;
289
290 nanoseconds systemTimeBefore, systemTimeAfter;
291 int64 diff, minDiff = 0x7fffFFFFffffFFFFLL;
292
293 // take best of several tries
294 const int numberOfTries = 8;
295 int64 newOffset = gHostOSCoffset;
296 for (int i = 0; i < numberOfTries; ++i) {
297 systemTimeBefore = high_resolution_clock::now().time_since_epoch();
298 gettimeofday(&tv, nullptr);
299 systemTimeAfter = high_resolution_clock::now().time_since_epoch();
300
301 diff = (systemTimeAfter - systemTimeBefore).count();
302 if (diff < minDiff) {
303 minDiff = diff;
304 // assume that gettimeofday happens halfway between high_resolution_clock::now() calls
305 int64 systemTimeBetween = systemTimeBefore.count() + diff / 2;
306 int64 systemTimeInOSCunits = (int64)((double)systemTimeBetween * kNanosToOSC);
307 int64 timeOfDayInOSCunits =
308 ((int64)(tv.tv_sec + kSECONDS_FROM_1900_to_1970) << 32) + (int64)(tv.tv_usec * kMicrosToOSC);
309 newOffset = timeOfDayInOSCunits - systemTimeInOSCunits;
310 }
311 }
312
313 gHostOSCoffset = newOffset;
314 // postfl("gHostOSCoffset %016llX\n", gHostOSCoffset);
315 }
316
317
318 void schedAdd(VMGlobals* g, PyrObject* inQueue, double inSeconds, PyrSlot* inTask);
schedAdd(VMGlobals * g,PyrObject * inQueue,double inSeconds,PyrSlot * inTask)319 void schedAdd(VMGlobals* g, PyrObject* inQueue, double inSeconds, PyrSlot* inTask) {
320 // gLangMutex must be locked
321 double prevTime = inQueue->size > 1 ? slotRawFloat(inQueue->slots + 1) : -1e10;
322 bool added = addheap(g, inQueue, inSeconds, inTask);
323 if (!added)
324 post("scheduler queue is full.\n");
325 else {
326 if (isKindOfSlot(inTask, class_thread)) {
327 SetFloat(&slotRawThread(inTask)->nextBeat, inSeconds);
328 }
329 if (slotRawFloat(inQueue->slots + 1) != prevTime) {
330 gSchedCond.notify_all();
331 }
332 }
333 }
334
schedStop()335 SCLANG_DLLEXPORT_C void schedStop() {
336 // printf("->schedStop\n");
337 gLangMutex.lock();
338 if (gRunSched) {
339 gRunSched = false;
340 gLangMutex.unlock();
341 gSchedCond.notify_all();
342 gSchedThread.join();
343 } else {
344 gLangMutex.unlock();
345 }
346 // printf("<-schedStop\n");
347 }
348
349 void schedClearUnsafe();
350
schedClear()351 SCLANG_DLLEXPORT_C void schedClear() {
352 gLangMutex.lock();
353 schedClearUnsafe();
354 gLangMutex.unlock();
355 }
356
schedClearUnsafe()357 void schedClearUnsafe() {
358 // postfl("->schedClear %d\n", gRunSched);
359 if (gRunSched) {
360 VMGlobals* g = gMainVMGlobals;
361 PyrObject* inQueue = slotRawObject(&g->process->sysSchedulerQueue);
362 inQueue->size = 1;
363 gSchedCond.notify_all();
364 }
365 // postfl("<-schedClear %d\n", gRunSched);
366 }
367
368 void post(const char* fmt, ...);
369
resyncThread()370 void resyncThread() {
371 while (true) {
372 if (gResyncThreadSemaphore.wait_for(std::chrono::seconds(20)))
373 return;
374
375 syncOSCOffsetWithTimeOfDay();
376 gElapsedOSCoffset = (int64)(gHostStartNanos * kNanosToOSC) + gHostOSCoffset;
377 }
378 }
379
380 extern bool gTraceInterpreter;
381
schedRunFunc()382 static void schedRunFunc() {
383 using namespace std::chrono;
384 unique_lock<timed_mutex> lock(gLangMutex);
385
386 VMGlobals* g = gMainVMGlobals;
387 PyrObject* inQueue = slotRawObject(&g->process->sysSchedulerQueue);
388 // dumpObject(inQueue);
389
390 gRunSched = true;
391 while (true) {
392 assert(inQueue->size);
393
394 // postfl("wait until there is something in scheduler\n");
395 // wait until there is something in scheduler
396 while (inQueue->size == 1) {
397 // postfl("wait until there is something in scheduler\n");
398 gSchedCond.wait(lock);
399 if (!gRunSched)
400 goto leave;
401 }
402 // postfl("wait until an event is ready\n");
403
404 // wait until an event is ready
405 high_resolution_clock::duration schedSecs;
406 high_resolution_clock::time_point now, schedPoint;
407 do {
408 now = high_resolution_clock::now();
409 schedSecs =
410 duration_cast<high_resolution_clock::duration>(duration<double>(slotRawFloat(inQueue->slots + 1)));
411 schedPoint = hrTimeOfInitialization + schedSecs;
412 if (now >= schedPoint)
413 break;
414 // postfl("wait until an event is ready\n");
415 gSchedCond.wait_until(lock, schedPoint);
416 if (!gRunSched)
417 goto leave;
418 // postfl("time diff %g\n", elapsedTime() - inQueue->slots->uf);
419 } while (inQueue->size > 1);
420
421 // postfl("perform all events that are ready %d %.9f\n", inQueue->size, elapsed);
422
423 // perform all events that are ready
424 // postfl("perform all events that are ready\n");
425 while ((inQueue->size > 1)
426 && now >= hrTimeOfInitialization
427 + duration_cast<high_resolution_clock::duration>(
428 duration<double>(slotRawFloat(inQueue->slots + 1)))) {
429 double schedtime, delta;
430 PyrSlot task;
431
432 // postfl("while %.6f >= %.6f\n", elapsed, inQueue->slots->uf);
433
434 getheap(g, inQueue, &schedtime, &task);
435
436 if (isKindOfSlot(&task, class_thread)) {
437 SetNil(&slotRawThread(&task)->nextBeat);
438 }
439
440 slotCopy((++g->sp), &task);
441 SetFloat(++g->sp, schedtime);
442 SetFloat(++g->sp, schedtime);
443 ++g->sp;
444 SetObject(g->sp, s_systemclock->u.classobj);
445
446 runAwakeMessage(g);
447 long err = slotDoubleVal(&g->result, &delta);
448 if (!err) {
449 // add delta time and reschedule
450 double time = schedtime + delta;
451
452 schedAdd(g, inQueue, time, &task);
453 }
454 }
455 // postfl("loop\n");
456 }
457 // postfl("exitloop\n");
458 leave:
459 return;
460 }
461
462 #ifdef __APPLE__
463 # include <mach/mach.h>
464 # include <mach/thread_policy.h>
465
466 // Polls a (non-realtime) thread to find out how it is scheduled
467 // Results are undefined of an error is returned. Otherwise, the
468 // priority is returned in the address pointed to by the priority
469 // parameter, and whether or not the thread uses timeshare scheduling
470 // is returned at the address pointed to by the isTimeShare parameter
GetStdThreadSchedule(mach_port_t machThread,int * priority,boolean_t * isTimeshare)471 kern_return_t GetStdThreadSchedule(mach_port_t machThread, int* priority, boolean_t* isTimeshare) {
472 kern_return_t result = 0;
473 thread_extended_policy_data_t timeShareData;
474 thread_precedence_policy_data_t precedenceData;
475 mach_msg_type_number_t structItemCount;
476 boolean_t fetchDefaults = false;
477
478 memset(&timeShareData, 0, sizeof(thread_extended_policy_data_t));
479 memset(&precedenceData, 0, sizeof(thread_precedence_policy_data_t));
480
481 if (0 == machThread)
482 machThread = mach_thread_self();
483
484 if (NULL != isTimeshare) {
485 structItemCount = THREAD_EXTENDED_POLICY_COUNT;
486 result = thread_policy_get(machThread, THREAD_EXTENDED_POLICY, (integer_t*)&timeShareData, &structItemCount,
487 &fetchDefaults);
488 *isTimeshare = timeShareData.timeshare;
489 if (0 != result)
490 return result;
491 }
492
493 if (NULL != priority) {
494 fetchDefaults = false;
495 structItemCount = THREAD_PRECEDENCE_POLICY_COUNT;
496 result = thread_policy_get(machThread, THREAD_PRECEDENCE_POLICY, (integer_t*)&precedenceData, &structItemCount,
497 &fetchDefaults);
498 *priority = precedenceData.importance;
499 }
500
501 return result;
502 }
503
504 // Reschedules the indicated thread according to new parameters:
505 //
506 // machThread The mach thread id. Pass 0 for the current thread.
507 // newPriority The desired priority.
508 // isTimeShare false for round robin (fixed) priority,
509 // true for timeshare (normal) priority
510 //
511 // A standard new thread usually has a priority of 0 and uses the
512 // timeshare scheduling scheme. Use pthread_mach_thread_np() to
513 // to convert a pthread id to a mach thread id
RescheduleStdThread(mach_port_t machThread,int newPriority,boolean_t isTimeshare)514 kern_return_t RescheduleStdThread(mach_port_t machThread, int newPriority, boolean_t isTimeshare) {
515 kern_return_t result = 0;
516 thread_extended_policy_data_t timeShareData;
517 thread_precedence_policy_data_t precedenceData;
518
519 // Set up some variables that we need for the task
520 precedenceData.importance = newPriority;
521 timeShareData.timeshare = isTimeshare;
522 if (0 == machThread)
523 machThread = mach_thread_self();
524
525 // Set the scheduling flavor. We want to do this first, since doing so
526 // can alter the priority
527 result =
528 thread_policy_set(machThread, THREAD_EXTENDED_POLICY, (integer_t*)&timeShareData, THREAD_EXTENDED_POLICY_COUNT);
529
530 if (0 != result)
531 return result;
532
533 // Now set the priority
534 return thread_policy_set(machThread, THREAD_PRECEDENCE_POLICY, (integer_t*)&precedenceData,
535 THREAD_PRECEDENCE_POLICY_COUNT);
536 }
537 #endif // __APPLE__
538
539 #ifdef __linux__
540 # include <string.h>
541
SC_LinuxSetRealtimePriority(pthread_t thread,int priority)542 static void SC_LinuxSetRealtimePriority(pthread_t thread, int priority) {
543 int policy;
544 struct sched_param param;
545
546 pthread_getschedparam(thread, &policy, ¶m);
547
548 policy = SCHED_FIFO;
549 const int minprio = sched_get_priority_min(policy);
550 const int maxprio = sched_get_priority_max(policy);
551 param.sched_priority = sc_clip(priority, minprio, maxprio);
552
553 int err = pthread_setschedparam(thread, policy, ¶m);
554 if (err != 0) {
555 post("Couldn't set realtime scheduling priority %d: %s\n", param.sched_priority, strerror(err));
556 }
557 }
558 #endif // __linux__
559
560
schedRun()561 SCLANG_DLLEXPORT_C void schedRun() {
562 SC_Thread thread(schedRunFunc);
563 gSchedThread = std::move(thread);
564
565 #ifdef __APPLE__
566 int policy;
567 struct sched_param param;
568
569 // pthread_t thread = pthread_self ();
570 pthread_getschedparam(gSchedThread.native_handle(), &policy, ¶m);
571 // post("param.sched_priority %d\n", param.sched_priority);
572
573 policy = SCHED_RR; // round-robin, AKA real-time scheduling
574
575 int machprio;
576 boolean_t timeshare;
577 GetStdThreadSchedule(pthread_mach_thread_np(gSchedThread.native_handle()), &machprio, ×hare);
578 // post("mach priority %d timeshare %d\n", machprio, timeshare);
579
580 // what priority should gSchedThread use?
581
582 RescheduleStdThread(pthread_mach_thread_np(gSchedThread.native_handle()), 62, false);
583
584 GetStdThreadSchedule(pthread_mach_thread_np(gSchedThread.native_handle()), &machprio, ×hare);
585 // post("mach priority %d timeshare %d\n", machprio, timeshare);
586
587 // param.sched_priority = 70; // you'll have to play with this to see what it does
588 // pthread_setschedparam (gSchedThread, policy, ¶m);
589
590 pthread_getschedparam(gSchedThread.native_handle(), &policy, ¶m);
591
592 // post("param.sched_priority %d\n", param.sched_priority);
593 #endif // __APPLE__
594
595 #ifdef __linux__
596 SC_LinuxSetRealtimePriority(gSchedThread.native_handle(), 1);
597 #endif // __linux__
598 }
599
600
601 /*
602
603 unscheduled events:
604 startup,
605 receive OSC,
606 mouse, keyboard, MIDI
607
608 all these happen in the main thread.
609
610 */
611
612 /*
613 new clock:
614 create
615 destroy
616 wake up at time x.
617 unschedule
618 awake
619 reschedules.
620 */
621
622
623 TempoClock* TempoClock::sAll = nullptr;
624
TempoClock_stopAll(void)625 void TempoClock_stopAll(void) {
626 // printf("->TempoClock_stopAll %p\n", TempoClock::sAll);
627 auto* clock = TempoClock::GetAll();
628 while (clock) {
629 auto* next = clock->GetNext();
630 clock->Stop();
631 // printf("delete\n");
632 delete clock;
633 clock = next;
634 }
635 // printf("<-TempoClock_stopAll %p\n", TempoClock::sAll);
636 TempoClock::InitAll();
637 }
638
TempoClock(VMGlobals * inVMGlobals,PyrObject * inTempoClockObj,double inTempo,double inBaseBeats,double inBaseSeconds)639 TempoClock::TempoClock(VMGlobals* inVMGlobals, PyrObject* inTempoClockObj, double inTempo, double inBaseBeats,
640 double inBaseSeconds):
641 g(inVMGlobals),
642 mTempoClockObj(inTempoClockObj),
643 mTempo(inTempo),
644 mBeatDur(1. / inTempo),
645 mBaseSeconds(inBaseSeconds),
646 mBaseBeats(inBaseBeats),
647 mRun(true),
648 mPrev(nullptr),
649 mNext(sAll) {
650 if (sAll)
651 sAll->mPrev = this;
652 sAll = this;
653
654 mQueue = (PyrHeap*)slotRawObject(&mTempoClockObj->slots[0]);
655 mQueue->size = 1;
656 SetInt(&mQueue->count, 0);
657
658 SC_Thread thread(std::bind(&TempoClock::Run, this));
659 mThread = std::move(thread);
660
661 #ifdef __APPLE__
662 int machprio;
663 boolean_t timeshare;
664 GetStdThreadSchedule(pthread_mach_thread_np(mThread.native_handle()), &machprio, ×hare);
665 // post("mach priority %d timeshare %d\n", machprio, timeshare);
666
667 // what priority should gSchedThread use?
668
669 RescheduleStdThread(pthread_mach_thread_np(mThread.native_handle()), 10, false);
670
671 GetStdThreadSchedule(pthread_mach_thread_np(mThread.native_handle()), &machprio, ×hare);
672 // post("mach priority %d timeshare %d\n", machprio, timeshare);
673
674 // param.sched_priority = 70; // you'll have to play with this to see what it does
675 // pthread_setschedparam (mThread, policy, ¶m);
676 #endif // __APPLE__
677
678 #ifdef __linux__
679 SC_LinuxSetRealtimePriority(mThread.native_handle(), 1);
680 #endif // __linux__
681 }
682
StopReq()683 void TempoClock::StopReq() {
684 // printf("->TempoClock::StopReq\n");
685 SC_Thread stopThread(std::bind(&TempoClock::StopAndDelete, this));
686 stopThread.detach();
687
688 // printf("<-TempoClock::StopReq\n");
689 }
690
Stop()691 void TempoClock::Stop() {
692 // printf("->TempoClock::Stop\n");
693 {
694 lock_guard<timed_mutex> lock(gLangMutex);
695
696 // printf("Stop mRun %d\n", mRun);
697 if (mRun) {
698 mRun = false;
699
700 // unlink
701 if (mPrev)
702 mPrev->mNext = mNext;
703 else
704 sAll = mNext;
705 if (mNext)
706 mNext->mPrev = mPrev;
707
708 mCondition.notify_all();
709 }
710 }
711 mThread.join();
712
713 // printf("<-TempoClock::Stop\n");
714 }
715
SetAll(double inTempo,double inBeats,double inSeconds)716 void TempoClock::SetAll(double inTempo, double inBeats, double inSeconds) {
717 mBaseSeconds = inSeconds;
718 mBaseBeats = inBeats;
719 mTempo = inTempo;
720 mBeatDur = 1. / mTempo;
721 mCondition.notify_one();
722 }
723
SetTempoAtBeat(double inTempo,double inBeats)724 void TempoClock::SetTempoAtBeat(double inTempo, double inBeats) {
725 mBaseSeconds = BeatsToSecs(inBeats);
726 mBaseBeats = inBeats;
727 mTempo = inTempo;
728 mBeatDur = 1. / mTempo;
729 mCondition.notify_one();
730 }
731
SetTempoAtTime(double inTempo,double inSeconds)732 void TempoClock::SetTempoAtTime(double inTempo, double inSeconds) {
733 mBaseBeats = SecsToBeats(inSeconds);
734 mBaseSeconds = inSeconds;
735 mTempo = inTempo;
736 mBeatDur = 1. / mTempo;
737 mCondition.notify_one();
738 }
739
ElapsedBeats()740 double TempoClock::ElapsedBeats() { return SecsToBeats(elapsedTime()); }
741
Run()742 void* TempoClock::Run() {
743 using namespace std::chrono;
744 // printf("->TempoClock::Run\n");
745 unique_lock<timed_mutex> lock(gLangMutex);
746
747 while (mRun) {
748 assert(mQueue->size);
749 // printf("tempo %g dur %g beats %g\n", mTempo, mBeatDur, mBeats);
750 // printf("wait until there is something in scheduler\n");
751 // wait until there is something in scheduler
752 while (mQueue->size == 1) {
753 // printf("wait until there is something in scheduler\n");
754 mCondition.wait(gLangMutex);
755 // printf("mRun a %d\n", mRun);
756 if (!mRun)
757 goto leave;
758 }
759 // printf("wait until an event is ready\n");
760
761 // wait until an event is ready
762 double elapsedBeats;
763 high_resolution_clock::duration schedSecs;
764 high_resolution_clock::time_point schedPoint;
765 do {
766 elapsedBeats = ElapsedBeats();
767 if (elapsedBeats >= slotRawFloat(mQueue->slots))
768 break;
769
770 // printf("event ready at %g . elapsed beats %g\n", mQueue->slots->uf, elapsedBeats);
771 double wakeTime = BeatsToSecs(slotRawFloat(mQueue->slots));
772
773 schedSecs = duration_cast<high_resolution_clock::duration>(duration<double>(wakeTime));
774 schedPoint = hrTimeOfInitialization + schedSecs;
775
776 // printf("wait until an event is ready. wake %g now %g\n", wakeTime, elapsedTime());
777 mCondition.wait_until(lock, schedPoint);
778 // printf("mRun b %d\n", mRun);
779 if (!mRun)
780 goto leave;
781 // printf("time diff %g\n", elapsedTime() - mQueue->slots->uf);
782 } while (mQueue->size > 1);
783 // printf("perform all events that are ready %d %.9f\n", mQueue->size, elapsedBeats);
784
785 // perform all events that are ready
786 // printf("perform all events that are ready\n");
787 while (mQueue->size > 1 && elapsedBeats >= slotRawFloat(mQueue->slots)) {
788 double delta;
789 PyrSlot task;
790
791 // printf("while %.6f >= %.6f\n", elapsedBeats, mQueue->slots->uf);
792
793 getheap(g, (PyrObject*)mQueue, &mBeats, &task);
794
795 if (isKindOfSlot(&task, class_thread)) {
796 SetNil(&slotRawThread(&task)->nextBeat);
797 }
798
799 slotCopy((++g->sp), &task);
800 SetFloat(++g->sp, mBeats);
801 SetFloat(++g->sp, BeatsToSecs(mBeats));
802 ++g->sp;
803 SetObject(g->sp, mTempoClockObj);
804
805 runAwakeMessage(g);
806 long err = slotDoubleVal(&g->result, &delta);
807 if (!err) {
808 // add delta time and reschedule
809 double beats = mBeats + delta;
810 Add(beats, &task);
811 }
812 }
813 }
814 leave:
815 // printf("<-TempoClock::Run\n");
816 return nullptr;
817 }
818
819 /*
820 void TempoClock::Flush()
821 {
822 while (mQueue->size && elapsedBeats >= mQueue->slots->uf) {
823 double delta;
824 PyrSlot task;
825
826 //printf("while %.6f >= %.6f\n", elapsedBeats, mQueue->slots->uf);
827
828 getheap(g, mQueue, &mBeats, &task);
829
830 slotCopy((++g->sp), &task);
831 (++g->sp)->uf = mBeats;
832 (++g->sp)->uf = BeatsToSecs(mBeats);
833 ++g->sp; SetObject(g->sp, mTempoClockObj);
834
835 runAwakeMessage(g);
836 long err = slotDoubleVal(&g->result, &delta);
837 if (!err) {
838 // add delta time and reschedule
839 double beats = mBeats + delta;
840 Add(beats, &task);
841 }
842 }
843 }
844 */
845
846
Add(double inBeats,PyrSlot * inTask)847 void TempoClock::Add(double inBeats, PyrSlot* inTask) {
848 double prevBeats = mQueue->size > 1 ? slotRawFloat(mQueue->slots) : -1e10;
849 bool added = addheap(g, (PyrObject*)mQueue, inBeats, inTask);
850 if (!added)
851 post("scheduler queue is full.\n");
852 else {
853 if (isKindOfSlot(inTask, class_thread)) {
854 SetFloat(&slotRawThread(inTask)->nextBeat, inBeats);
855 }
856 if (slotRawFloat(mQueue->slots) != prevBeats)
857 mCondition.notify_one();
858 }
859 }
860
Clear()861 void TempoClock::Clear() {
862 if (mRun) {
863 mQueue->size = 1;
864 mCondition.notify_one();
865 }
866 }
867
Dump()868 void TempoClock::Dump() {
869 post("mTempo %g\n", mTempo);
870 post("mBeatDur %g\n", mBeatDur);
871 post("mBeats %g\n", mBeats);
872 post("seconds %g\n", BeatsToSecs(mBeats));
873 post("mBaseSeconds %g\n", mBaseSeconds);
874 post("mBaseBeats %g\n", mBaseBeats);
875 }
876
877
878 int prTempoClock_Free(struct VMGlobals* g, int numArgsPushed);
prTempoClock_Free(struct VMGlobals * g,int numArgsPushed)879 int prTempoClock_Free(struct VMGlobals* g, int numArgsPushed) {
880 PyrSlot* a = g->sp;
881 TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
882 if (!clock)
883 return errNone; // not running
884
885 SetNil(slotRawObject(a)->slots + 1);
886 clock->StopReq();
887
888 return errNone;
889 }
890
891 int prTempoClock_Clear(struct VMGlobals* g, int numArgsPushed);
prTempoClock_Clear(struct VMGlobals * g,int numArgsPushed)892 int prTempoClock_Clear(struct VMGlobals* g, int numArgsPushed) {
893 PyrSlot* a = g->sp;
894 TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
895 if (clock)
896 clock->Clear();
897
898 return errNone;
899 }
900
901 int prTempoClock_Dump(struct VMGlobals* g, int numArgsPushed);
prTempoClock_Dump(struct VMGlobals * g,int numArgsPushed)902 int prTempoClock_Dump(struct VMGlobals* g, int numArgsPushed) {
903 PyrSlot* a = g->sp;
904 TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
905 if (clock)
906 clock->Dump();
907
908 return errNone;
909 }
910
911
912 int prTempoClock_Tempo(struct VMGlobals* g, int numArgsPushed);
prTempoClock_Tempo(struct VMGlobals * g,int numArgsPushed)913 int prTempoClock_Tempo(struct VMGlobals* g, int numArgsPushed) {
914 PyrSlot* a = g->sp;
915 TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
916 if (!clock) {
917 error("clock is not running.\n");
918 return errFailed;
919 }
920
921 SetFloat(a, clock->GetTempo());
922
923 return errNone;
924 }
925
926 int prTempoClock_BeatDur(struct VMGlobals* g, int numArgsPushed);
prTempoClock_BeatDur(struct VMGlobals * g,int numArgsPushed)927 int prTempoClock_BeatDur(struct VMGlobals* g, int numArgsPushed) {
928 PyrSlot* a = g->sp;
929 TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
930 if (!clock) {
931 error("clock is not running.\n");
932 return errFailed;
933 }
934
935 SetFloat(a, clock->GetBeatDur());
936
937 return errNone;
938 }
939
940 int prTempoClock_ElapsedBeats(struct VMGlobals* g, int numArgsPushed);
prTempoClock_ElapsedBeats(struct VMGlobals * g,int numArgsPushed)941 int prTempoClock_ElapsedBeats(struct VMGlobals* g, int numArgsPushed) {
942 PyrSlot* a = g->sp;
943 TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
944 if (!clock) {
945 error("clock is not running.\n");
946 return errFailed;
947 }
948
949 SetFloat(a, clock->ElapsedBeats());
950
951 return errNone;
952 }
953
954 int prTempoClock_Beats(struct VMGlobals* g, int numArgsPushed);
prTempoClock_Beats(struct VMGlobals * g,int numArgsPushed)955 int prTempoClock_Beats(struct VMGlobals* g, int numArgsPushed) {
956 PyrSlot* a = g->sp;
957 double beats, seconds;
958
959 if (SlotEq(&g->thread->clock, a)) {
960 int err = slotDoubleVal(&g->thread->beats, &beats);
961 if (err)
962 return errWrongType;
963 } else {
964 TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
965 if (!clock) {
966 error("clock is not running.\n");
967 return errFailed;
968 }
969
970 int err = slotDoubleVal(&g->thread->seconds, &seconds);
971 if (err)
972 return errWrongType;
973
974 beats = clock->SecsToBeats(seconds);
975 }
976 SetFloat(a, beats);
977 return errNone;
978 }
979
980 int prTempoClock_Sched(struct VMGlobals* g, int numArgsPushed);
prTempoClock_Sched(struct VMGlobals * g,int numArgsPushed)981 int prTempoClock_Sched(struct VMGlobals* g, int numArgsPushed) {
982 PyrSlot* a = g->sp - 2;
983 PyrSlot* b = g->sp - 1;
984 PyrSlot* c = g->sp;
985 double delta, beats;
986 int err;
987
988 TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
989 if (!clock) {
990 error("clock is not running.\n");
991 return errFailed;
992 }
993
994 if (SlotEq(&g->thread->clock, a)) {
995 err = slotDoubleVal(&g->thread->beats, &beats);
996 if (err)
997 return errNone; // return nil OK, just don't schedule
998 } else {
999 double seconds;
1000 err = slotDoubleVal(&g->thread->seconds, &seconds);
1001 if (err)
1002 return errNone;
1003 beats = clock->SecsToBeats(seconds);
1004 }
1005
1006 err = slotDoubleVal(b, &delta);
1007 if (err)
1008 return errNone; // return nil OK, just don't schedule
1009 beats += delta;
1010 if (beats == dInfinity)
1011 return errNone; // return nil OK, just don't schedule
1012
1013 clock->Add(beats, c);
1014
1015 return errNone;
1016 }
1017
1018 int prTempoClock_SchedAbs(struct VMGlobals* g, int numArgsPushed);
prTempoClock_SchedAbs(struct VMGlobals * g,int numArgsPushed)1019 int prTempoClock_SchedAbs(struct VMGlobals* g, int numArgsPushed) {
1020 PyrSlot* a = g->sp - 2;
1021 PyrSlot* b = g->sp - 1;
1022 PyrSlot* c = g->sp;
1023
1024 TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
1025 if (!clock) {
1026 error("clock is not running.\n");
1027 return errFailed;
1028 }
1029
1030 double beats;
1031 int err = slotDoubleVal(b, &beats) || (beats == dInfinity);
1032 if (err)
1033 return errNone; // return nil OK, just don't schedule
1034
1035 clock->Add(beats, c);
1036
1037 return errNone;
1038 }
1039
1040 int prTempoClock_BeatsToSecs(struct VMGlobals* g, int numArgsPushed);
prTempoClock_BeatsToSecs(struct VMGlobals * g,int numArgsPushed)1041 int prTempoClock_BeatsToSecs(struct VMGlobals* g, int numArgsPushed) {
1042 PyrSlot* a = g->sp - 1;
1043 PyrSlot* b = g->sp;
1044
1045 TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
1046 if (!clock) {
1047 error("clock is not running.\n");
1048 return errFailed;
1049 }
1050
1051 double beats;
1052 int err = slotDoubleVal(b, &beats);
1053 if (err)
1054 return errFailed;
1055
1056 SetFloat(a, clock->BeatsToSecs(beats));
1057
1058 return errNone;
1059 }
1060
1061 int prTempoClock_SecsToBeats(struct VMGlobals* g, int numArgsPushed);
prTempoClock_SecsToBeats(struct VMGlobals * g,int numArgsPushed)1062 int prTempoClock_SecsToBeats(struct VMGlobals* g, int numArgsPushed) {
1063 PyrSlot* a = g->sp - 1;
1064 PyrSlot* b = g->sp;
1065
1066 TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
1067 if (!clock) {
1068 error("clock is not running.\n");
1069 return errFailed;
1070 }
1071
1072 double secs;
1073 int err = slotDoubleVal(b, &secs);
1074 if (err)
1075 return errFailed;
1076
1077 SetFloat(a, clock->SecsToBeats(secs));
1078
1079 return errNone;
1080 }
1081
1082 int prSystemClock_Clear(struct VMGlobals* g, int numArgsPushed);
prSystemClock_Clear(struct VMGlobals * g,int numArgsPushed)1083 int prSystemClock_Clear(struct VMGlobals* g, int numArgsPushed) {
1084 // PyrSlot *a = g->sp;
1085
1086 schedClearUnsafe();
1087
1088 return errNone;
1089 }
1090
1091 int prSystemClock_Sched(struct VMGlobals* g, int numArgsPushed);
prSystemClock_Sched(struct VMGlobals * g,int numArgsPushed)1092 int prSystemClock_Sched(struct VMGlobals* g, int numArgsPushed) {
1093 // PyrSlot *a = g->sp - 2;
1094 PyrSlot* b = g->sp - 1;
1095 PyrSlot* c = g->sp;
1096
1097 double delta, seconds;
1098 int err = slotDoubleVal(b, &delta);
1099 if (err)
1100 return errNone; // return nil OK, just don't schedule
1101 err = slotDoubleVal(&g->thread->seconds, &seconds);
1102 if (err)
1103 return errNone; // return nil OK, just don't schedule
1104 seconds += delta;
1105 if (seconds == dInfinity)
1106 return errNone; // return nil OK, just don't schedule
1107 PyrObject* inQueue = slotRawObject(&g->process->sysSchedulerQueue);
1108
1109 schedAdd(g, inQueue, seconds, c);
1110
1111 return errNone;
1112 }
1113
1114 int prSystemClock_SchedAbs(struct VMGlobals* g, int numArgsPushed);
prSystemClock_SchedAbs(struct VMGlobals * g,int numArgsPushed)1115 int prSystemClock_SchedAbs(struct VMGlobals* g, int numArgsPushed) {
1116 // PyrSlot *a = g->sp - 2;
1117 PyrSlot* b = g->sp - 1;
1118 PyrSlot* c = g->sp;
1119
1120 double time;
1121 int err = slotDoubleVal(b, &time) || (time == dInfinity);
1122 if (err)
1123 return errNone; // return nil OK, just don't schedule
1124 PyrObject* inQueue = slotRawObject(&g->process->sysSchedulerQueue);
1125
1126 schedAdd(g, inQueue, time, c);
1127
1128 return errNone;
1129 }
1130
prElapsedTime(struct VMGlobals * g,int numArgsPushed)1131 int prElapsedTime(struct VMGlobals* g, int numArgsPushed) {
1132 SetFloat(g->sp, elapsedTime());
1133 return errNone;
1134 }
1135
prmonotonicClockTime(struct VMGlobals * g,int numArgsPushed)1136 int prmonotonicClockTime(struct VMGlobals* g, int numArgsPushed) {
1137 SetFloat(g->sp, monotonicClockTime());
1138 return errNone;
1139 }
1140
initSchedPrimitives()1141 void initSchedPrimitives() {
1142 int base, index = 0;
1143
1144 base = nextPrimitiveIndex();
1145
1146 definePrimitive(base, index++, "_TempoClock_New", prClock_New<TempoClock>, 4, 0);
1147 definePrimitive(base, index++, "_TempoClock_Free", prTempoClock_Free, 1, 0);
1148 definePrimitive(base, index++, "_TempoClock_Clear", prTempoClock_Clear, 1, 0);
1149 definePrimitive(base, index++, "_TempoClock_Dump", prTempoClock_Dump, 1, 0);
1150 definePrimitive(base, index++, "_TempoClock_Sched", prTempoClock_Sched, 3, 0);
1151 definePrimitive(base, index++, "_TempoClock_SchedAbs", prTempoClock_SchedAbs, 3, 0);
1152 definePrimitive(base, index++, "_TempoClock_Tempo", prTempoClock_Tempo, 1, 0);
1153 definePrimitive(base, index++, "_TempoClock_BeatDur", prTempoClock_BeatDur, 1, 0);
1154 definePrimitive(base, index++, "_TempoClock_ElapsedBeats", prTempoClock_ElapsedBeats, 1, 0);
1155 definePrimitive(base, index++, "_TempoClock_Beats", prTempoClock_Beats, 1, 0);
1156 definePrimitive(base, index++, "_TempoClock_SetBeats", prClock_SetBeats<TempoClock>, 2, 0);
1157 definePrimitive(base, index++, "_TempoClock_SetTempoAtBeat", prClock_SetTempoAtBeat<TempoClock>, 3, 0);
1158 definePrimitive(base, index++, "_TempoClock_SetTempoAtTime", prClock_SetTempoAtTime<TempoClock>, 3, 0);
1159 definePrimitive(base, index++, "_TempoClock_SetAll", prClock_SetAll<TempoClock>, 4, 0);
1160 definePrimitive(base, index++, "_TempoClock_BeatsToSecs", prTempoClock_BeatsToSecs, 2, 0);
1161 definePrimitive(base, index++, "_TempoClock_SecsToBeats", prTempoClock_SecsToBeats, 2, 0);
1162
1163 definePrimitive(base, index++, "_SystemClock_Clear", prSystemClock_Clear, 1, 0);
1164 definePrimitive(base, index++, "_SystemClock_Sched", prSystemClock_Sched, 3, 0);
1165 definePrimitive(base, index++, "_SystemClock_SchedAbs", prSystemClock_SchedAbs, 3, 0);
1166
1167 definePrimitive(base, index++, "_ElapsedTime", prElapsedTime, 1, 0);
1168 definePrimitive(base, index++, "_monotonicClockTime", prmonotonicClockTime, 1, 0);
1169 }
1170