andrewm@0: /* andrewm@0: TouchKeys: multi-touch musical keyboard control software andrewm@0: Copyright (c) 2013 Andrew McPherson andrewm@0: andrewm@0: This program is free software: you can redistribute it and/or modify andrewm@0: it under the terms of the GNU General Public License as published by andrewm@0: the Free Software Foundation, either version 3 of the License, or andrewm@0: (at your option) any later version. andrewm@0: andrewm@0: This program is distributed in the hope that it will be useful, andrewm@0: but WITHOUT ANY WARRANTY; without even the implied warranty of andrewm@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the andrewm@0: GNU General Public License for more details. andrewm@0: andrewm@0: You should have received a copy of the GNU General Public License andrewm@0: along with this program. If not, see . andrewm@0: andrewm@0: ===================================================================== andrewm@0: andrewm@0: Scheduler.cpp: allows actions to be scheduled at future times. Runs a andrewm@0: thread in which these actions are executed. andrewm@0: */ andrewm@0: andrewm@0: #include "Scheduler.h" andrewm@0: #undef DEBUG_SCHEDULER andrewm@0: andrewm@0: using std::cout; andrewm@0: andrewm@0: const timestamp_diff_type Scheduler::kAllowableAdvanceExecutionTime = milliseconds_to_timestamp(1.0); andrewm@0: andrewm@0: // Start the thread handling the scheduling. Pass it an initial timestamp. andrewm@0: void Scheduler::start(timestamp_type where) { andrewm@0: if(isRunning_) andrewm@0: return; andrewm@0: startingTimestamp_ = where; andrewm@0: startThread(); andrewm@0: } andrewm@0: andrewm@0: // Stop the scheduler thread if it is currently running. Events will remain andrewm@0: // in the queue unless explicitly cleared. andrewm@0: void Scheduler::stop() { andrewm@0: if(!isRunning_) andrewm@0: return; andrewm@0: andrewm@0: // Tell the thread to quit and signal the event it waits on andrewm@0: signalThreadShouldExit(); andrewm@0: waitableEvent_.signal(); andrewm@0: stopThread(-1); andrewm@0: andrewm@0: isRunning_ = false; andrewm@0: } andrewm@0: andrewm@0: // Return the current timestamp, relative to this class's start time. andrewm@0: timestamp_type Scheduler::currentTimestamp() { andrewm@0: if(!isRunning_) andrewm@0: return 0; andrewm@0: return milliseconds_to_timestamp(Time::getMillisecondCounterHiRes() - startTimeMilliseconds_); andrewm@0: //return ptime_to_timestamp(microsec_clock::universal_time() - startTime_); andrewm@0: } andrewm@0: andrewm@0: // Schedule a new event andrewm@0: void Scheduler::schedule(void *who, action func, timestamp_type timestamp) { andrewm@0: ScopedLock sl(eventMutex_); andrewm@0: andrewm@0: #ifdef DEBUG_SCHEDULER andrewm@0: std::cerr << "Scheduler::schedule: " << who << ", " << timestamp << " (" << timestamp - currentTimestamp() << " from now)\n"; andrewm@0: #endif andrewm@0: andrewm@0: // Check if this timestamp will become the next thing in the queue andrewm@0: bool newActionWillComeFirst = false; andrewm@0: if(events_.empty()) andrewm@0: newActionWillComeFirst = true; andrewm@0: else if(timestamp < events_.begin()->first) andrewm@0: newActionWillComeFirst = true; andrewm@0: events_.insert(std::pair > andrewm@0: (timestamp, std::pair(who, func))); andrewm@0: andrewm@0: // Tell the thread to wake up and recheck its status if the andrewm@0: // time of the next event has changed andrewm@0: if(newActionWillComeFirst) andrewm@0: waitableEvent_.signal(); andrewm@0: } andrewm@0: andrewm@0: // Remove an existing event andrewm@0: void Scheduler::unschedule(void *who, timestamp_type timestamp) { andrewm@0: #ifdef DEBUG_SCHEDULER andrewm@0: std::cerr << "Scheduler::unschedule: " << who << ", " << timestamp << std::endl; andrewm@0: #endif andrewm@0: ScopedLock sl(eventMutex_); andrewm@0: andrewm@0: // Find all events with this timestamp, and remove only the ones matching the given source andrewm@0: std::multimap >::iterator it; andrewm@0: andrewm@0: if(timestamp == 0) { andrewm@0: // Remove all events from this source andrewm@0: it = events_.begin(); andrewm@0: while(it != events_.end()) { andrewm@0: #ifdef DEBUG_SCHEDULER andrewm@0: std::cerr << "| (" << it->first << ", " << it->second.first << ")\n"; andrewm@0: #endif andrewm@0: if(it->second.first == who) { andrewm@0: #ifdef DEBUG_SCHEDULER andrewm@0: std::cerr << "--> erased " << it->first << ", " << it->second.first << ")\n"; andrewm@0: #endif andrewm@0: events_.erase(it++); andrewm@0: } andrewm@0: else andrewm@0: it++; andrewm@0: } andrewm@0: } andrewm@0: else { andrewm@0: // Remove only a specific event from this source with the given timestmap andrewm@0: it = events_.find(timestamp); andrewm@0: while(it != events_.end()) { andrewm@0: if(it->second.first == who) { andrewm@0: #ifdef DEBUG_SCHEDULER andrewm@0: std::cerr << "--> erased " << it->first << ", " << it->second.first << ")\n"; andrewm@0: #endif andrewm@0: events_.erase(it++); andrewm@0: } andrewm@0: else andrewm@0: it++; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: #ifdef DEBUG_SCHEDULER andrewm@0: std::cerr << "Scheduler::unschedule: done\n"; andrewm@0: #endif andrewm@0: // No need to wake up the thread... andrewm@0: } andrewm@0: andrewm@0: // Clear all events from the queue andrewm@0: void Scheduler::clear() { andrewm@0: ScopedLock sl(eventMutex_); andrewm@0: andrewm@0: events_.clear(); andrewm@0: andrewm@0: // No need to signal the condition variable. If the thread is waiting, it can keep waiting. andrewm@0: } andrewm@0: andrewm@0: // This function runs in its own thread (from the Juce parent class). It looks for the next event andrewm@0: // in the queue. When its time arrives, the event is executed and removed from the queue. andrewm@0: // When the queue is empty, or the next event has not arrived yet, the thread sleeps. andrewm@0: andrewm@0: void Scheduler::run() { andrewm@0: // Start with the mutex locked. The wait() methods will unlock it. andrewm@0: eventMutex_.enter(); andrewm@0: andrewm@0: // Find the start time, against which our offsets will be measured. andrewm@0: //startTime_ = microsec_clock::universal_time(); andrewm@0: startTimeMilliseconds_ = Time::getMillisecondCounterHiRes(); andrewm@0: isRunning_ = true; andrewm@0: andrewm@0: // This will run until the thread is interrupted (in the stop() method) andrewm@0: // events_ is ordered by increasing timestamp, so the next event to execute is always the first item. andrewm@0: while(!threadShouldExit()) { andrewm@0: if(events_.empty()) { // If there are no events in the queue, wait until we're signaled andrewm@0: eventMutex_.exit(); // that a new one comes in. Unlock the mutex and wait. andrewm@0: waitableEvent_.wait(); andrewm@0: eventMutex_.enter(); andrewm@0: } andrewm@0: else { andrewm@0: timestamp_type t = events_.begin()->first; // Find the timestamp of the first event andrewm@0: double targetTimeMilliseconds = startTimeMilliseconds_ + timestamp_to_milliseconds(t); andrewm@0: andrewm@0: // Wait until that time arrives, provided it hasn't already andrewm@20: int timeDifferenceMilliseconds = (int)floor(targetTimeMilliseconds - Time::getMillisecondCounterHiRes() + 0.5); andrewm@0: #ifdef DEBUG_SCHEDULER andrewm@0: std::cerr << "Scheduler::run: waiting for " << timeDifferenceMilliseconds << "ms\n"; andrewm@0: #endif andrewm@0: if(timeDifferenceMilliseconds > 0) { andrewm@0: eventMutex_.exit(); andrewm@0: waitableEvent_.wait(timeDifferenceMilliseconds); andrewm@0: eventMutex_.enter(); andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: waitableEvent_.reset(); // Clear the signal andrewm@0: andrewm@0: if(threadShouldExit()) andrewm@0: break; andrewm@0: andrewm@0: // At this point, the mutex is locked. We can change the contents of events_ without worrying about disrupting anything. andrewm@0: andrewm@0: if(events_.empty()) // Double check that we actually have an event to execute andrewm@0: continue; andrewm@0: if(currentTimestamp() + kAllowableAdvanceExecutionTime < events_.begin()->first) { andrewm@0: #ifdef DEBUG_SCHEDULER andrewm@0: std::cerr << "Scheduler::run: next event hasn't arrived (currently " << currentTimestamp() << ", waiting for " << events_.begin()->first << "\n"; andrewm@0: #endif andrewm@0: continue; andrewm@0: } andrewm@0: andrewm@0: // Run the function that's stored, which takes no arguments and returns a timestamp andrewm@0: // of the next time this particular function should run. andrewm@0: std::multimap >::iterator it = events_.begin(); andrewm@0: action actionFunction = (it->second).second; andrewm@0: //timestamp_type testingTimestamp = it->first; andrewm@0: void *who = it->second.first; andrewm@0: andrewm@0: #ifdef DEBUG_SCHEDULER andrewm@0: std::cerr << "Scheduler::run: " << who << ", " << it->first << std::endl; andrewm@0: #endif andrewm@0: andrewm@0: timestamp_type timeOfNextEvent = actionFunction(); andrewm@0: andrewm@0: // Remove the last event from the queue andrewm@0: events_.erase(it); andrewm@0: andrewm@0: if(timeOfNextEvent > 0) { andrewm@0: // Reschedule the same event for some (hopefully) future time. andrewm@0: events_.insert(std::pair > andrewm@0: (timeOfNextEvent, andrewm@0: std::pair(who, actionFunction))); andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: eventMutex_.exit(); andrewm@0: }