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, &param);
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, &param);
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, &param);
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, &timeshare);
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, &timeshare);
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, &param);
589 
590     pthread_getschedparam(gSchedThread.native_handle(), &policy, &param);
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, &timeshare);
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, &timeshare);
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, &param);
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