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: }