view Source/Utility/Scheduler.cpp @ 20:dfff66c07936

Lots of minor changes to support building on Visual Studio. A few MSVC-specific #ifdefs to eliminate things Visual Studio doesn't like. This version now compiles on Windows (provided liblo, Juce and pthread are present) but the TouchKeys device support is not yet enabled. Also, the code now needs to be re-checked on Mac and Linux.
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Sun, 09 Feb 2014 18:40:51 +0000
parents 3580ffe87dc8
children
line wrap: on
line source
/*
  TouchKeys: multi-touch musical keyboard control software
  Copyright (c) 2013 Andrew McPherson

  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 3 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, see <http://www.gnu.org/licenses/>.
 
  =====================================================================

  Scheduler.cpp: allows actions to be scheduled at future times. Runs a
  thread in which these actions are executed.
*/

#include "Scheduler.h"
#undef DEBUG_SCHEDULER

using std::cout;

const timestamp_diff_type Scheduler::kAllowableAdvanceExecutionTime = milliseconds_to_timestamp(1.0);

// Start the thread handling the scheduling.  Pass it an initial timestamp.
void Scheduler::start(timestamp_type where) {
	if(isRunning_)
		return;
    startingTimestamp_ = where;
    startThread();
}

// Stop the scheduler thread if it is currently running.  Events will remain
// in the queue unless explicitly cleared.
void Scheduler::stop() {
	if(!isRunning_)
		return;
    
    // Tell the thread to quit and signal the event it waits on
    signalThreadShouldExit();
    waitableEvent_.signal();
    stopThread(-1);
    
	isRunning_ = false;
}

// Return the current timestamp, relative to this class's start time.
timestamp_type Scheduler::currentTimestamp() {
	if(!isRunning_)
		return 0;
    return milliseconds_to_timestamp(Time::getMillisecondCounterHiRes() - startTimeMilliseconds_);
	//return ptime_to_timestamp(microsec_clock::universal_time() - startTime_);
}

// Schedule a new event
void Scheduler::schedule(void *who, action func, timestamp_type timestamp) {
    ScopedLock sl(eventMutex_);
    
#ifdef DEBUG_SCHEDULER
    std::cerr << "Scheduler::schedule: " << who << ", " << timestamp << " (" << timestamp - currentTimestamp() << " from now)\n";
#endif
    
    // Check if this timestamp will become the next thing in the queue
    bool newActionWillComeFirst = false;
    if(events_.empty())
        newActionWillComeFirst = true;
    else if(timestamp < events_.begin()->first)
        newActionWillComeFirst = true;
    events_.insert(std::pair<timestamp_type,std::pair<void*, action> >
					(timestamp, std::pair<void*, action>(who, func)));

	// Tell the thread to wake up and recheck its status if the
    // time of the next event has changed
    if(newActionWillComeFirst)
        waitableEvent_.signal();
}

// Remove an existing event
void Scheduler::unschedule(void *who, timestamp_type timestamp) {
#ifdef DEBUG_SCHEDULER
    std::cerr << "Scheduler::unschedule: " << who << ", " << timestamp << std::endl;
#endif
    ScopedLock sl(eventMutex_);
    
	// Find all events with this timestamp, and remove only the ones matching the given source
	std::multimap<timestamp_type, std::pair<void*, action> >::iterator it;
    
    if(timestamp == 0) {
        // Remove all events from this source
        it = events_.begin();
        while(it != events_.end()) {
#ifdef DEBUG_SCHEDULER
            std::cerr << "| (" << it->first << ", " << it->second.first << ")\n";
#endif
            if(it->second.first == who) {
#ifdef DEBUG_SCHEDULER
                std::cerr << "--> erased " << it->first << ", " << it->second.first << ")\n";
#endif
                events_.erase(it++);
            }
            else
                it++;
        }
    }
    else {
        // Remove only a specific event from this source with the given timestmap
        it = events_.find(timestamp);
        while(it != events_.end()) {
            if(it->second.first == who) {
#ifdef DEBUG_SCHEDULER
                std::cerr << "--> erased " << it->first << ", " << it->second.first << ")\n";
#endif
                events_.erase(it++);
            }
            else
                it++;
        }
    }

#ifdef DEBUG_SCHEDULER
    std::cerr << "Scheduler::unschedule: done\n";
#endif
	// No need to wake up the thread...
}

// Clear all events from the queue
void Scheduler::clear() {
    ScopedLock sl(eventMutex_);
    
	events_.clear();
	
	// No need to signal the condition variable.  If the thread is waiting, it can keep waiting.
}

// This function runs in its own thread (from the Juce parent class).  It looks for the next event
// in the queue.  When its time arrives, the event is executed and removed from the queue.
// When the queue is empty, or the next event has not arrived yet, the thread sleeps.

void Scheduler::run() {
    // Start with the mutex locked.  The wait() methods will unlock it.
    eventMutex_.enter();
    
	// Find the start time, against which our offsets will be measured.
	//startTime_ = microsec_clock::universal_time();
    startTimeMilliseconds_ = Time::getMillisecondCounterHiRes();
	isRunning_ = true;
	
    // This will run until the thread is interrupted (in the stop() method)
    // events_ is ordered by increasing timestamp, so the next event to execute is always the first item.
    while(!threadShouldExit()) {
        if(events_.empty())	{					// If there are no events in the queue, wait until we're signaled
            eventMutex_.exit();                 // that a new one comes in.  Unlock the mutex and wait.
            waitableEvent_.wait();
            eventMutex_.enter();
        }
        else {
            timestamp_type t = events_.begin()->first;				// Find the timestamp of the first event
            double targetTimeMilliseconds = startTimeMilliseconds_ + timestamp_to_milliseconds(t);
            
            // Wait until that time arrives, provided it hasn't already
            int timeDifferenceMilliseconds = (int)floor(targetTimeMilliseconds - Time::getMillisecondCounterHiRes() + 0.5);
#ifdef DEBUG_SCHEDULER
            std::cerr << "Scheduler::run: waiting for " << timeDifferenceMilliseconds << "ms\n";
#endif
            if(timeDifferenceMilliseconds > 0) {
                eventMutex_.exit();                                    
                waitableEvent_.wait(timeDifferenceMilliseconds);
                eventMutex_.enter();
            }
        }
        
        waitableEvent_.reset();             // Clear the signal
        
        if(threadShouldExit())
            break;
        
        // At this point, the mutex is locked.  We can change the contents of events_ without worrying about disrupting anything.
        
        if(events_.empty())				// Double check that we actually have an event to execute
            continue;
        if(currentTimestamp() + kAllowableAdvanceExecutionTime < events_.begin()->first) {
#ifdef DEBUG_SCHEDULER
            std::cerr << "Scheduler::run: next event hasn't arrived (currently " << currentTimestamp() << ", waiting for " << events_.begin()->first << "\n";
#endif
            continue;
        }
        
        // Run the function that's stored, which takes no arguments and returns a timestamp
        // of the next time this particular function should run.
        std::multimap<timestamp_type, std::pair<void*, action> >::iterator it = events_.begin();
        action actionFunction = (it->second).second;
        //timestamp_type testingTimestamp = it->first;
        void *who = it->second.first;
        
#ifdef DEBUG_SCHEDULER
        std::cerr << "Scheduler::run: " << who << ", " << it->first << std::endl;
#endif
        
        timestamp_type timeOfNextEvent = actionFunction();
        
        // Remove the last event from the queue
        events_.erase(it);
        
        if(timeOfNextEvent > 0) {
            // Reschedule the same event for some (hopefully) future time.
            events_.insert(std::pair<timestamp_type,std::pair<void*, action> >
                           (timeOfNextEvent,
                            std::pair<void*, action>(who, actionFunction)));
        }
    }
    
    eventMutex_.exit();
}