Skip to content
Permalink
master
Go to file
 
 
Cannot retrieve contributors at this time
1154 lines (936 sloc) 35.8 KB
/*
SuperCollider real time audio synthesis system
Copyright (c) 2002 James McCartney. All rights reserved.
http://www.audiosynth.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "PyrKernel.h"
#include "PyrSched.h"
#include "GC.h"
#include "PyrPrimitive.h"
#include "PyrSymbol.h"
#ifdef __APPLE__
# include <CoreAudio/HostTime.h>
#endif
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <limits>
#include <functional>
#if defined(__APPLE__) || defined(__linux__)
# include <pthread.h>
#endif
#include "SCBase.h"
#include "SC_Lock.h"
#include "SC_Clock.hpp"
#include "SC_LinkClock.hpp"
#include <boost/sync/semaphore.hpp>
#include <boost/sync/support/std_chrono.hpp>
// FIXME: These includes needs to be last on Windows, otherwise Ableton build breaks
// (Windows header include ordering dependencies)
#include "SC_Win32Utils.h"
#ifdef _MSC_VER
# include "wintime.h"
#else
# include <sys/time.h>
#endif
static const double dInfinity = std::numeric_limits<double>::infinity();
void runAwakeMessage(VMGlobals* g);
bool addheap(VMGlobals* g, PyrObject* heapArg, double schedtime, PyrSlot* task) {
PyrHeap* heap = (PyrHeap*)heapArg;
#ifdef GC_SANITYCHECK
g->gc->SanityCheck();
#endif
if (heap->size >= ARRAYMAXINDEXSIZE(heap))
return false;
assert(heap->size);
// post("->addheap\n");
// dumpheap(heapArg);
/* parent and sibling in the heap, not in the task hierarchy */
int mom = heap->size - 1;
PyrSlot* pme = heap->slots + mom;
int stabilityCount = slotRawInt(&heap->count);
SetRaw(&heap->count, stabilityCount + 1);
for (; mom > 0;) { /* percolate up heap */
int newMom = ((mom - 3) / 2);
mom = newMom - newMom % 3; /// LATER: we could avoid the division by using 4 slots per element
PyrSlot* pmom = heap->slots + mom;
if (schedtime < slotRawFloat(pmom)) {
assert(slotRawInt(pmom + 2) < stabilityCount);
slotCopy(&pme[0], &pmom[0]);
slotCopy(&pme[1], &pmom[1]);
slotCopy(&pme[2], &pmom[2]);
pme = pmom;
} else
break;
}
SetFloat(&pme[0], schedtime);
slotCopy(&pme[1], task);
SetInt(&pme[2], stabilityCount);
g->gc->GCWrite(heap, task);
heap->size += 3;
#ifdef GC_SANITYCHECK
g->gc->SanityCheck();
#endif
// dumpheap(heapArg);
// post("<-addheap %g\n", schedtime);
return true;
}
bool lookheap(PyrObject* heap, double* schedtime, PyrSlot* task) {
if (heap->size > 1) {
*schedtime = slotRawFloat(&heap->slots[0]);
slotCopy(task, &heap->slots[1]);
return true;
} else
return false;
}
bool getheap(VMGlobals* g, PyrObject* heapArg, double* schedtime, PyrSlot* task) {
PyrHeap* heap = (PyrHeap*)heapArg;
PyrGC* gc = g->gc;
bool isPartialScanObj = gc->IsPartialScanObject(heapArg);
assert(heap->size);
// post("->getheap\n");
// dumpheap(heapArg);
if (heap->size > 1) {
*schedtime = slotRawFloat(&heap->slots[0]);
slotCopy(task, &heap->slots[1]);
heap->size -= 3;
int size = heap->size - 1;
slotCopy(&heap->slots[0], &heap->slots[size]);
slotCopy(&heap->slots[1], &heap->slots[size + 1]);
slotCopy(&heap->slots[2], &heap->slots[size + 2]);
/* parent and sibling in the heap, not in the task hierarchy */
int mom = 0;
int me = 3;
PyrSlot* pmom = heap->slots + mom;
PyrSlot* pme = heap->slots + me;
PyrSlot* pend = heap->slots + size;
double timetemp = slotRawFloat(&pmom[0]);
int stabilityCountTemp = slotRawInt(&pmom[2]);
PyrSlot tasktemp;
slotCopy(&tasktemp, &pmom[1]);
for (; pme < pend;) {
/* demote heap */
if (pme + 3 < pend
&& ((slotRawFloat(&pme[0]) > slotRawFloat(&pme[3]))
|| ((slotRawFloat(&pme[0]) == slotRawFloat(&pme[3]))
&& (slotRawInt(&pme[2]) > slotRawInt(&pme[5]))))) {
me += 3;
pme += 3;
}
if (timetemp > slotRawFloat(&pme[0])
|| (timetemp == slotRawFloat(&pme[0]) && stabilityCountTemp > slotRawInt(&pme[2]))) {
slotCopy(&pmom[0], &pme[0]);
slotCopy(&pmom[1], &pme[1]);
slotCopy(&pmom[2], &pme[2]);
if (isPartialScanObj) {
gc->GCWriteBlack(pmom + 1);
}
pmom = pme;
me = ((mom = me) * 2) + 3;
pme = heap->slots + me;
} else
break;
}
SetRaw(&pmom[0], timetemp);
slotCopy(&pmom[1], &tasktemp);
SetRaw(&pmom[2], stabilityCountTemp);
if (isPartialScanObj)
gc->GCWriteBlack(pmom + 1);
if (size == 0)
SetInt(&heap->count, 0);
// dumpheap(heapArg);
// post("<-getheap true\n");
return true;
} else {
// post("<-getheap false\n");
return false;
}
}
void offsetheap(VMGlobals* g, PyrObject* heap, double offset) {
long i;
for (i = 0; i < heap->size; i += 2) {
SetRaw(&heap->slots[i], slotRawFloat(&heap->slots[i]) + offset);
// post("%3d %9.2f %9.2f\n", i>>1, heap->slots[i].uf, offset);
}
}
void dumpheap(PyrObject* heapArg) {
PyrHeap* heap = (PyrHeap*)heapArg;
double mintime = slotRawFloat(&heap->slots[0]);
int count = slotRawFloat(&heap->slots[2]);
int heapSize = heap->size - 1;
post("SCHED QUEUE (%d)\n", heapSize);
for (int i = 0; i < heapSize; i += 3) {
post("%3d(%3d) %9.2f %p %d\n", i / 3, i, slotRawFloat(&heap->slots[i]), slotRawObject(&heap->slots[i + 1]),
slotRawInt(&heap->slots[i + 2]));
if ((slotRawFloat(&heap->slots[i]) < mintime)
|| (slotRawFloat(&heap->slots[i]) == mintime && slotRawInt(&heap->slots[i + 2]) < count))
post("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n");
}
}
bool gRunSched = false;
static std::thread gSchedThread;
static std::thread gResyncThread;
static boost::sync::semaphore gResyncThreadSemaphore;
std::condition_variable_any gSchedCond;
std::timed_mutex gLangMutex;
int64 gHostOSCoffset = 0;
int64 gHostStartNanos = 0;
int64 gElapsedOSCoffset = 0;
const int32 kSECONDS_FROM_1900_to_1970 = (int32)2208988800UL; /* 17 leap years */
static void syncOSCOffsetWithTimeOfDay();
void resyncThread();
// Use the highest resolution clock available for monotonic clock time
using monotonic_clock = std::conditional<std::chrono::high_resolution_clock::is_steady,
std::chrono::high_resolution_clock, std::chrono::steady_clock>::type;
static std::chrono::high_resolution_clock::time_point hrTimeOfInitialization;
SCLANG_DLLEXPORT_C void schedInit() {
using namespace std::chrono;
hrTimeOfInitialization = high_resolution_clock::now();
#ifdef SC_ABLETON_LINK
LinkClock::Init();
#endif
syncOSCOffsetWithTimeOfDay();
gResyncThread = std::thread(resyncThread);
gHostStartNanos = duration_cast<nanoseconds>(hrTimeOfInitialization.time_since_epoch()).count();
gElapsedOSCoffset = (int64)(gHostStartNanos * kNanosToOSC) + gHostOSCoffset;
}
SCLANG_DLLEXPORT_C void schedCleanup() {
gResyncThreadSemaphore.post();
gResyncThread.join();
}
double elapsedTime() { return DurToFloat(std::chrono::high_resolution_clock::now() - hrTimeOfInitialization); }
double monotonicClockTime() { return DurToFloat(monotonic_clock::now().time_since_epoch()); }
int64 ElapsedTimeToOSC(double elapsed) { return (int64)(elapsed * kSecondsToOSC) + gElapsedOSCoffset; }
double OSCToElapsedTime(int64 oscTime) { return (double)(oscTime - gElapsedOSCoffset) * kOSCtoSecs; }
void ElapsedTimeToChrono(double elapsed, std::chrono::system_clock::time_point& out_time_point) {
int64 oscTime = ElapsedTimeToOSC(elapsed);
int64 secs = ((oscTime >> 32) - kSECONDS_FROM_1900_to_1970);
int32 nano_secs = (int32)((oscTime & 0xFFFFFFFF) * kOSCtoNanos);
using namespace std::chrono;
#if 0 // TODO: check system_clock precision
system_clock::time_point time_point = system_clock::time_point(seconds(secs) + nanoseconds(nano_secs));
#else
int32 usecs = nano_secs / 1000;
system_clock::time_point time_point = system_clock::time_point(seconds(secs) + microseconds(usecs));
#endif
out_time_point = time_point;
}
int64 OSCTime() { return ElapsedTimeToOSC(elapsedTime()); }
void syncOSCOffsetWithTimeOfDay() {
// generate a value gHostOSCoffset such that
// (gHostOSCoffset + systemTimeInOSCunits)
// is equal to gettimeofday time in OSCunits.
// Then if this machine is synced via NTP, we are synced with the world.
// more accurate way to do this??
using namespace std::chrono;
struct timeval tv;
nanoseconds systemTimeBefore, systemTimeAfter;
int64 diff, minDiff = 0x7fffFFFFffffFFFFLL;
// take best of several tries
const int numberOfTries = 8;
int64 newOffset = gHostOSCoffset;
for (int i = 0; i < numberOfTries; ++i) {
systemTimeBefore = high_resolution_clock::now().time_since_epoch();
gettimeofday(&tv, nullptr);
systemTimeAfter = high_resolution_clock::now().time_since_epoch();
diff = (systemTimeAfter - systemTimeBefore).count();
if (diff < minDiff) {
minDiff = diff;
// assume that gettimeofday happens halfway between high_resolution_clock::now() calls
int64 systemTimeBetween = systemTimeBefore.count() + diff / 2;
int64 systemTimeInOSCunits = (int64)((double)systemTimeBetween * kNanosToOSC);
int64 timeOfDayInOSCunits =
((int64)(tv.tv_sec + kSECONDS_FROM_1900_to_1970) << 32) + (int64)(tv.tv_usec * kMicrosToOSC);
newOffset = timeOfDayInOSCunits - systemTimeInOSCunits;
}
}
gHostOSCoffset = newOffset;
// postfl("gHostOSCoffset %016llX\n", gHostOSCoffset);
}
void schedAdd(VMGlobals* g, PyrObject* inQueue, double inSeconds, PyrSlot* inTask) {
// gLangMutex must be locked
double prevTime = inQueue->size > 1 ? slotRawFloat(inQueue->slots + 1) : -1e10;
bool added = addheap(g, inQueue, inSeconds, inTask);
if (!added)
post("scheduler queue is full.\n");
else {
if (isKindOfSlot(inTask, class_thread)) {
SetFloat(&slotRawThread(inTask)->nextBeat, inSeconds);
}
if (slotRawFloat(inQueue->slots + 1) != prevTime) {
gSchedCond.notify_all();
}
}
}
SCLANG_DLLEXPORT_C void schedStop() {
// printf("->schedStop\n");
gLangMutex.lock();
if (gRunSched) {
gRunSched = false;
gLangMutex.unlock();
gSchedCond.notify_all();
gSchedThread.join();
} else {
gLangMutex.unlock();
}
// printf("<-schedStop\n");
}
void schedClearUnsafe();
SCLANG_DLLEXPORT_C void schedClear() {
gLangMutex.lock();
schedClearUnsafe();
gLangMutex.unlock();
}
void schedClearUnsafe() {
// postfl("->schedClear %d\n", gRunSched);
if (gRunSched) {
VMGlobals* g = gMainVMGlobals;
PyrObject* inQueue = slotRawObject(&g->process->sysSchedulerQueue);
inQueue->size = 1;
gSchedCond.notify_all();
}
// postfl("<-schedClear %d\n", gRunSched);
}
void post(const char* fmt, ...);
void resyncThread() {
while (true) {
if (gResyncThreadSemaphore.wait_for(std::chrono::seconds(20)))
return;
syncOSCOffsetWithTimeOfDay();
gElapsedOSCoffset = (int64)(gHostStartNanos * kNanosToOSC) + gHostOSCoffset;
}
}
extern bool gTraceInterpreter;
static void schedRunFunc() {
using namespace std::chrono;
unique_lock<timed_mutex> lock(gLangMutex);
VMGlobals* g = gMainVMGlobals;
PyrObject* inQueue = slotRawObject(&g->process->sysSchedulerQueue);
// dumpObject(inQueue);
gRunSched = true;
while (true) {
assert(inQueue->size);
// postfl("wait until there is something in scheduler\n");
// wait until there is something in scheduler
while (inQueue->size == 1) {
// postfl("wait until there is something in scheduler\n");
gSchedCond.wait(lock);
if (!gRunSched)
goto leave;
}
// postfl("wait until an event is ready\n");
// wait until an event is ready
high_resolution_clock::duration schedSecs;
high_resolution_clock::time_point now, schedPoint;
do {
now = high_resolution_clock::now();
schedSecs =
duration_cast<high_resolution_clock::duration>(duration<double>(slotRawFloat(inQueue->slots + 1)));
schedPoint = hrTimeOfInitialization + schedSecs;
if (now >= schedPoint)
break;
// postfl("wait until an event is ready\n");
gSchedCond.wait_until(lock, schedPoint);
if (!gRunSched)
goto leave;
// postfl("time diff %g\n", elapsedTime() - inQueue->slots->uf);
} while (inQueue->size > 1);
// postfl("perform all events that are ready %d %.9f\n", inQueue->size, elapsed);
// perform all events that are ready
// postfl("perform all events that are ready\n");
while ((inQueue->size > 1)
&& now >= hrTimeOfInitialization
+ duration_cast<high_resolution_clock::duration>(
duration<double>(slotRawFloat(inQueue->slots + 1)))) {
double schedtime, delta;
PyrSlot task;
// postfl("while %.6f >= %.6f\n", elapsed, inQueue->slots->uf);
getheap(g, inQueue, &schedtime, &task);
if (isKindOfSlot(&task, class_thread)) {
SetNil(&slotRawThread(&task)->nextBeat);
}
slotCopy((++g->sp), &task);
SetFloat(++g->sp, schedtime);
SetFloat(++g->sp, schedtime);
++g->sp;
SetObject(g->sp, s_systemclock->u.classobj);
runAwakeMessage(g);
long err = slotDoubleVal(&g->result, &delta);
if (!err) {
// add delta time and reschedule
double time = schedtime + delta;
schedAdd(g, inQueue, time, &task);
}
}
// postfl("loop\n");
}
// postfl("exitloop\n");
leave:
return;
}
#ifdef __APPLE__
# include <mach/mach.h>
# include <mach/thread_policy.h>
// Polls a (non-realtime) thread to find out how it is scheduled
// Results are undefined of an error is returned. Otherwise, the
// priority is returned in the address pointed to by the priority
// parameter, and whether or not the thread uses timeshare scheduling
// is returned at the address pointed to by the isTimeShare parameter
kern_return_t GetStdThreadSchedule(mach_port_t machThread, int* priority, boolean_t* isTimeshare) {
kern_return_t result = 0;
thread_extended_policy_data_t timeShareData;
thread_precedence_policy_data_t precedenceData;
mach_msg_type_number_t structItemCount;
boolean_t fetchDefaults = false;
memset(&timeShareData, 0, sizeof(thread_extended_policy_data_t));
memset(&precedenceData, 0, sizeof(thread_precedence_policy_data_t));
if (0 == machThread)
machThread = mach_thread_self();
if (NULL != isTimeshare) {
structItemCount = THREAD_EXTENDED_POLICY_COUNT;
result = thread_policy_get(machThread, THREAD_EXTENDED_POLICY, (integer_t*)&timeShareData, &structItemCount,
&fetchDefaults);
*isTimeshare = timeShareData.timeshare;
if (0 != result)
return result;
}
if (NULL != priority) {
fetchDefaults = false;
structItemCount = THREAD_PRECEDENCE_POLICY_COUNT;
result = thread_policy_get(machThread, THREAD_PRECEDENCE_POLICY, (integer_t*)&precedenceData, &structItemCount,
&fetchDefaults);
*priority = precedenceData.importance;
}
return result;
}
// Reschedules the indicated thread according to new parameters:
//
// machThread The mach thread id. Pass 0 for the current thread.
// newPriority The desired priority.
// isTimeShare false for round robin (fixed) priority,
// true for timeshare (normal) priority
//
// A standard new thread usually has a priority of 0 and uses the
// timeshare scheduling scheme. Use pthread_mach_thread_np() to
// to convert a pthread id to a mach thread id
kern_return_t RescheduleStdThread(mach_port_t machThread, int newPriority, boolean_t isTimeshare) {
kern_return_t result = 0;
thread_extended_policy_data_t timeShareData;
thread_precedence_policy_data_t precedenceData;
// Set up some variables that we need for the task
precedenceData.importance = newPriority;
timeShareData.timeshare = isTimeshare;
if (0 == machThread)
machThread = mach_thread_self();
// Set the scheduling flavor. We want to do this first, since doing so
// can alter the priority
result =
thread_policy_set(machThread, THREAD_EXTENDED_POLICY, (integer_t*)&timeShareData, THREAD_EXTENDED_POLICY_COUNT);
if (0 != result)
return result;
// Now set the priority
return thread_policy_set(machThread, THREAD_PRECEDENCE_POLICY, (integer_t*)&precedenceData,
THREAD_PRECEDENCE_POLICY_COUNT);
}
#endif // __APPLE__
#ifdef __linux__
# include <string.h>
static void SC_LinuxSetRealtimePriority(pthread_t thread, int priority) {
int policy;
struct sched_param param;
pthread_getschedparam(thread, &policy, &param);
policy = SCHED_FIFO;
const int minprio = sched_get_priority_min(policy);
const int maxprio = sched_get_priority_max(policy);
param.sched_priority = sc_clip(priority, minprio, maxprio);
int err = pthread_setschedparam(thread, policy, &param);
if (err != 0) {
post("Couldn't set realtime scheduling priority %d: %s\n", param.sched_priority, strerror(err));
}
}
#endif // __linux__
SCLANG_DLLEXPORT_C void schedRun() {
SC_Thread thread(schedRunFunc);
gSchedThread = std::move(thread);
#ifdef __APPLE__
int policy;
struct sched_param param;
// pthread_t thread = pthread_self ();
pthread_getschedparam(gSchedThread.native_handle(), &policy, &param);
// post("param.sched_priority %d\n", param.sched_priority);
policy = SCHED_RR; // round-robin, AKA real-time scheduling
int machprio;
boolean_t timeshare;
GetStdThreadSchedule(pthread_mach_thread_np(gSchedThread.native_handle()), &machprio, &timeshare);
// post("mach priority %d timeshare %d\n", machprio, timeshare);
// what priority should gSchedThread use?
RescheduleStdThread(pthread_mach_thread_np(gSchedThread.native_handle()), 62, false);
GetStdThreadSchedule(pthread_mach_thread_np(gSchedThread.native_handle()), &machprio, &timeshare);
// post("mach priority %d timeshare %d\n", machprio, timeshare);
// param.sched_priority = 70; // you'll have to play with this to see what it does
// pthread_setschedparam (gSchedThread, policy, &param);
pthread_getschedparam(gSchedThread.native_handle(), &policy, &param);
// post("param.sched_priority %d\n", param.sched_priority);
#endif // __APPLE__
#ifdef __linux__
SC_LinuxSetRealtimePriority(gSchedThread.native_handle(), 1);
#endif // __linux__
}
/*
unscheduled events:
startup,
receive OSC,
mouse, keyboard, MIDI
all these happen in the main thread.
*/
/*
new clock:
create
destroy
wake up at time x.
unschedule
awake
reschedules.
*/
TempoClock* TempoClock::sAll = nullptr;
void TempoClock_stopAll(void) {
// printf("->TempoClock_stopAll %p\n", TempoClock::sAll);
auto* clock = TempoClock::GetAll();
while (clock) {
auto* next = clock->GetNext();
clock->Stop();
// printf("delete\n");
delete clock;
clock = next;
}
// printf("<-TempoClock_stopAll %p\n", TempoClock::sAll);
TempoClock::InitAll();
}
TempoClock::TempoClock(VMGlobals* inVMGlobals, PyrObject* inTempoClockObj, double inTempo, double inBaseBeats,
double inBaseSeconds):
g(inVMGlobals),
mTempoClockObj(inTempoClockObj),
mTempo(inTempo),
mBeatDur(1. / inTempo),
mBaseSeconds(inBaseSeconds),
mBaseBeats(inBaseBeats),
mRun(true),
mPrev(nullptr),
mNext(sAll) {
if (sAll)
sAll->mPrev = this;
sAll = this;
mQueue = (PyrHeap*)slotRawObject(&mTempoClockObj->slots[0]);
mQueue->size = 1;
SetInt(&mQueue->count, 0);
SC_Thread thread(std::bind(&TempoClock::Run, this));
mThread = std::move(thread);
#ifdef __APPLE__
int machprio;
boolean_t timeshare;
GetStdThreadSchedule(pthread_mach_thread_np(mThread.native_handle()), &machprio, &timeshare);
// post("mach priority %d timeshare %d\n", machprio, timeshare);
// what priority should gSchedThread use?
RescheduleStdThread(pthread_mach_thread_np(mThread.native_handle()), 10, false);
GetStdThreadSchedule(pthread_mach_thread_np(mThread.native_handle()), &machprio, &timeshare);
// post("mach priority %d timeshare %d\n", machprio, timeshare);
// param.sched_priority = 70; // you'll have to play with this to see what it does
// pthread_setschedparam (mThread, policy, &param);
#endif // __APPLE__
#ifdef __linux__
SC_LinuxSetRealtimePriority(mThread.native_handle(), 1);
#endif // __linux__
}
void TempoClock::StopReq() {
// printf("->TempoClock::StopReq\n");
SC_Thread stopThread(std::bind(&TempoClock::StopAndDelete, this));
stopThread.detach();
// printf("<-TempoClock::StopReq\n");
}
void TempoClock::Stop() {
// printf("->TempoClock::Stop\n");
{
lock_guard<timed_mutex> lock(gLangMutex);
// printf("Stop mRun %d\n", mRun);
if (mRun) {
mRun = false;
// unlink
if (mPrev)
mPrev->mNext = mNext;
else
sAll = mNext;
if (mNext)
mNext->mPrev = mPrev;
mCondition.notify_all();
}
}
mThread.join();
// printf("<-TempoClock::Stop\n");
}
void TempoClock::SetAll(double inTempo, double inBeats, double inSeconds) {
mBaseSeconds = inSeconds;
mBaseBeats = inBeats;
mTempo = inTempo;
mBeatDur = 1. / mTempo;
mCondition.notify_one();
}
void TempoClock::SetTempoAtBeat(double inTempo, double inBeats) {
mBaseSeconds = BeatsToSecs(inBeats);
mBaseBeats = inBeats;
mTempo = inTempo;
mBeatDur = 1. / mTempo;
mCondition.notify_one();
}
void TempoClock::SetTempoAtTime(double inTempo, double inSeconds) {
mBaseBeats = SecsToBeats(inSeconds);
mBaseSeconds = inSeconds;
mTempo = inTempo;
mBeatDur = 1. / mTempo;
mCondition.notify_one();
}
double TempoClock::ElapsedBeats() { return SecsToBeats(elapsedTime()); }
void* TempoClock::Run() {
using namespace std::chrono;
// printf("->TempoClock::Run\n");
unique_lock<timed_mutex> lock(gLangMutex);
while (mRun) {
assert(mQueue->size);
// printf("tempo %g dur %g beats %g\n", mTempo, mBeatDur, mBeats);
// printf("wait until there is something in scheduler\n");
// wait until there is something in scheduler
while (mQueue->size == 1) {
// printf("wait until there is something in scheduler\n");
mCondition.wait(gLangMutex);
// printf("mRun a %d\n", mRun);
if (!mRun)
goto leave;
}
// printf("wait until an event is ready\n");
// wait until an event is ready
double elapsedBeats;
high_resolution_clock::duration schedSecs;
high_resolution_clock::time_point schedPoint;
do {
elapsedBeats = ElapsedBeats();
if (elapsedBeats >= slotRawFloat(mQueue->slots))
break;
// printf("event ready at %g . elapsed beats %g\n", mQueue->slots->uf, elapsedBeats);
double wakeTime = BeatsToSecs(slotRawFloat(mQueue->slots));
schedSecs = duration_cast<high_resolution_clock::duration>(duration<double>(wakeTime));
schedPoint = hrTimeOfInitialization + schedSecs;
// printf("wait until an event is ready. wake %g now %g\n", wakeTime, elapsedTime());
mCondition.wait_until(lock, schedPoint);
// printf("mRun b %d\n", mRun);
if (!mRun)
goto leave;
// printf("time diff %g\n", elapsedTime() - mQueue->slots->uf);
} while (mQueue->size > 1);
// printf("perform all events that are ready %d %.9f\n", mQueue->size, elapsedBeats);
// perform all events that are ready
// printf("perform all events that are ready\n");
while (mQueue->size > 1 && elapsedBeats >= slotRawFloat(mQueue->slots)) {
double delta;
PyrSlot task;
// printf("while %.6f >= %.6f\n", elapsedBeats, mQueue->slots->uf);
getheap(g, (PyrObject*)mQueue, &mBeats, &task);
if (isKindOfSlot(&task, class_thread)) {
SetNil(&slotRawThread(&task)->nextBeat);
}
slotCopy((++g->sp), &task);
SetFloat(++g->sp, mBeats);
SetFloat(++g->sp, BeatsToSecs(mBeats));
++g->sp;
SetObject(g->sp, mTempoClockObj);
runAwakeMessage(g);
long err = slotDoubleVal(&g->result, &delta);
if (!err) {
// add delta time and reschedule
double beats = mBeats + delta;
Add(beats, &task);
}
}
}
leave:
// printf("<-TempoClock::Run\n");
return nullptr;
}
/*
void TempoClock::Flush()
{
while (mQueue->size && elapsedBeats >= mQueue->slots->uf) {
double delta;
PyrSlot task;
//printf("while %.6f >= %.6f\n", elapsedBeats, mQueue->slots->uf);
getheap(g, mQueue, &mBeats, &task);
slotCopy((++g->sp), &task);
(++g->sp)->uf = mBeats;
(++g->sp)->uf = BeatsToSecs(mBeats);
++g->sp; SetObject(g->sp, mTempoClockObj);
runAwakeMessage(g);
long err = slotDoubleVal(&g->result, &delta);
if (!err) {
// add delta time and reschedule
double beats = mBeats + delta;
Add(beats, &task);
}
}
}
*/
void TempoClock::Add(double inBeats, PyrSlot* inTask) {
double prevBeats = mQueue->size > 1 ? slotRawFloat(mQueue->slots) : -1e10;
bool added = addheap(g, (PyrObject*)mQueue, inBeats, inTask);
if (!added)
post("scheduler queue is full.\n");
else {
if (isKindOfSlot(inTask, class_thread)) {
SetFloat(&slotRawThread(inTask)->nextBeat, inBeats);
}
if (slotRawFloat(mQueue->slots) != prevBeats)
mCondition.notify_one();
}
}
void TempoClock::Clear() {
if (mRun) {
mQueue->size = 1;
mCondition.notify_one();
}
}
void TempoClock::Dump() {
post("mTempo %g\n", mTempo);
post("mBeatDur %g\n", mBeatDur);
post("mBeats %g\n", mBeats);
post("seconds %g\n", BeatsToSecs(mBeats));
post("mBaseSeconds %g\n", mBaseSeconds);
post("mBaseBeats %g\n", mBaseBeats);
}
int prTempoClock_Free(struct VMGlobals* g, int numArgsPushed) {
PyrSlot* a = g->sp;
TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
if (!clock)
return errNone; // not running
SetNil(slotRawObject(a)->slots + 1);
clock->StopReq();
return errNone;
}
int prTempoClock_Clear(struct VMGlobals* g, int numArgsPushed) {
PyrSlot* a = g->sp;
TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
if (clock)
clock->Clear();
return errNone;
}
int prTempoClock_Dump(struct VMGlobals* g, int numArgsPushed) {
PyrSlot* a = g->sp;
TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
if (clock)
clock->Dump();
return errNone;
}
int prTempoClock_Tempo(struct VMGlobals* g, int numArgsPushed) {
PyrSlot* a = g->sp;
TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
if (!clock) {
error("clock is not running.\n");
return errFailed;
}
SetFloat(a, clock->GetTempo());
return errNone;
}
int prTempoClock_BeatDur(struct VMGlobals* g, int numArgsPushed) {
PyrSlot* a = g->sp;
TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
if (!clock) {
error("clock is not running.\n");
return errFailed;
}
SetFloat(a, clock->GetBeatDur());
return errNone;
}
int prTempoClock_ElapsedBeats(struct VMGlobals* g, int numArgsPushed) {
PyrSlot* a = g->sp;
TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
if (!clock) {
error("clock is not running.\n");
return errFailed;
}
SetFloat(a, clock->ElapsedBeats());
return errNone;
}
int prTempoClock_Beats(struct VMGlobals* g, int numArgsPushed) {
PyrSlot* a = g->sp;
double beats, seconds;
if (SlotEq(&g->thread->clock, a)) {
int err = slotDoubleVal(&g->thread->beats, &beats);
if (err)
return errWrongType;
} else {
TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
if (!clock) {
error("clock is not running.\n");
return errFailed;
}
int err = slotDoubleVal(&g->thread->seconds, &seconds);
if (err)
return errWrongType;
beats = clock->SecsToBeats(seconds);
}
SetFloat(a, beats);
return errNone;
}
int prTempoClock_Sched(struct VMGlobals* g, int numArgsPushed) {
PyrSlot* a = g->sp - 2;
PyrSlot* b = g->sp - 1;
PyrSlot* c = g->sp;
double delta, beats;
int err;
TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
if (!clock) {
error("clock is not running.\n");
return errFailed;
}
if (SlotEq(&g->thread->clock, a)) {
err = slotDoubleVal(&g->thread->beats, &beats);
if (err)
return errNone; // return nil OK, just don't schedule
} else {
double seconds;
err = slotDoubleVal(&g->thread->seconds, &seconds);
if (err)
return errNone;
beats = clock->SecsToBeats(seconds);
}
err = slotDoubleVal(b, &delta);
if (err)
return errNone; // return nil OK, just don't schedule
beats += delta;
if (beats == dInfinity)
return errNone; // return nil OK, just don't schedule
clock->Add(beats, c);
return errNone;
}
int prTempoClock_SchedAbs(struct VMGlobals* g, int numArgsPushed) {
PyrSlot* a = g->sp - 2;
PyrSlot* b = g->sp - 1;
PyrSlot* c = g->sp;
TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
if (!clock) {
error("clock is not running.\n");
return errFailed;
}
double beats;
int err = slotDoubleVal(b, &beats) || (beats == dInfinity);
if (err)
return errNone; // return nil OK, just don't schedule
clock->Add(beats, c);
return errNone;
}
int prTempoClock_BeatsToSecs(struct VMGlobals* g, int numArgsPushed) {
PyrSlot* a = g->sp - 1;
PyrSlot* b = g->sp;
TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
if (!clock) {
error("clock is not running.\n");
return errFailed;
}
double beats;
int err = slotDoubleVal(b, &beats);
if (err)
return errFailed;
SetFloat(a, clock->BeatsToSecs(beats));
return errNone;
}
int prTempoClock_SecsToBeats(struct VMGlobals* g, int numArgsPushed) {
PyrSlot* a = g->sp - 1;
PyrSlot* b = g->sp;
TempoClock* clock = (TempoClock*)slotRawPtr(&slotRawObject(a)->slots[1]);
if (!clock) {
error("clock is not running.\n");
return errFailed;
}
double secs;
int err = slotDoubleVal(b, &secs);
if (err)
return errFailed;
SetFloat(a, clock->SecsToBeats(secs));
return errNone;
}
int prSystemClock_Clear(struct VMGlobals* g, int numArgsPushed) {
// PyrSlot *a = g->sp;
schedClearUnsafe();
return errNone;
}
int prSystemClock_Sched(struct VMGlobals* g, int numArgsPushed) {
// PyrSlot *a = g->sp - 2;
PyrSlot* b = g->sp - 1;
PyrSlot* c = g->sp;
double delta, seconds;
int err = slotDoubleVal(b, &delta);
if (err)
return errNone; // return nil OK, just don't schedule
err = slotDoubleVal(&g->thread->seconds, &seconds);
if (err)
return errNone; // return nil OK, just don't schedule
seconds += delta;
if (seconds == dInfinity)
return errNone; // return nil OK, just don't schedule
PyrObject* inQueue = slotRawObject(&g->process->sysSchedulerQueue);
schedAdd(g, inQueue, seconds, c);
return errNone;
}
int prSystemClock_SchedAbs(struct VMGlobals* g, int numArgsPushed) {
// PyrSlot *a = g->sp - 2;
PyrSlot* b = g->sp - 1;
PyrSlot* c = g->sp;
double time;
int err = slotDoubleVal(b, &time) || (time == dInfinity);
if (err)
return errNone; // return nil OK, just don't schedule
PyrObject* inQueue = slotRawObject(&g->process->sysSchedulerQueue);
schedAdd(g, inQueue, time, c);
return errNone;
}
int prElapsedTime(struct VMGlobals* g, int numArgsPushed) {
SetFloat(g->sp, elapsedTime());
return errNone;
}
int prmonotonicClockTime(struct VMGlobals* g, int numArgsPushed) {
SetFloat(g->sp, monotonicClockTime());
return errNone;
}
void initSchedPrimitives() {
int base, index = 0;
base = nextPrimitiveIndex();
definePrimitive(base, index++, "_TempoClock_New", prClock_New<TempoClock>, 4, 0);
definePrimitive(base, index++, "_TempoClock_Free", prTempoClock_Free, 1, 0);
definePrimitive(base, index++, "_TempoClock_Clear", prTempoClock_Clear, 1, 0);
definePrimitive(base, index++, "_TempoClock_Dump", prTempoClock_Dump, 1, 0);
definePrimitive(base, index++, "_TempoClock_Sched", prTempoClock_Sched, 3, 0);
definePrimitive(base, index++, "_TempoClock_SchedAbs", prTempoClock_SchedAbs, 3, 0);
definePrimitive(base, index++, "_TempoClock_Tempo", prTempoClock_Tempo, 1, 0);
definePrimitive(base, index++, "_TempoClock_BeatDur", prTempoClock_BeatDur, 1, 0);
definePrimitive(base, index++, "_TempoClock_ElapsedBeats", prTempoClock_ElapsedBeats, 1, 0);
definePrimitive(base, index++, "_TempoClock_Beats", prTempoClock_Beats, 1, 0);
definePrimitive(base, index++, "_TempoClock_SetBeats", prClock_SetBeats<TempoClock>, 2, 0);
definePrimitive(base, index++, "_TempoClock_SetTempoAtBeat", prClock_SetTempoAtBeat<TempoClock>, 3, 0);
definePrimitive(base, index++, "_TempoClock_SetTempoAtTime", prClock_SetTempoAtTime<TempoClock>, 3, 0);
definePrimitive(base, index++, "_TempoClock_SetAll", prClock_SetAll<TempoClock>, 4, 0);
definePrimitive(base, index++, "_TempoClock_BeatsToSecs", prTempoClock_BeatsToSecs, 2, 0);
definePrimitive(base, index++, "_TempoClock_SecsToBeats", prTempoClock_SecsToBeats, 2, 0);
definePrimitive(base, index++, "_SystemClock_Clear", prSystemClock_Clear, 1, 0);
definePrimitive(base, index++, "_SystemClock_Sched", prSystemClock_Sched, 3, 0);
definePrimitive(base, index++, "_SystemClock_SchedAbs", prSystemClock_SchedAbs, 3, 0);
definePrimitive(base, index++, "_ElapsedTime", prElapsedTime, 1, 0);
definePrimitive(base, index++, "_monotonicClockTime", prmonotonicClockTime, 1, 0);
}