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: MappingScheduler.cpp: implements a thread in which mapping actions are andrewm@0: performed. Each Mapping object implements a triggerReceived() method andrewm@0: which is called by the hardware I/O thread. This method should do a andrewm@0: minimal amount of work but pass the real work off to the performMapping() andrewm@0: method which is called by the MappingScheduler thread. The scheduler andrewm@0: also allows mapping calls to be performed in the absence of received data, andrewm@0: for example to cause a parameter to ramp down over time if no touch data andrewm@0: is received. andrewm@0: */ andrewm@0: andrewm@0: #include "MappingScheduler.h" andrewm@0: #include "Mapping.h" andrewm@0: andrewm@0: #undef DEBUG_MAPPING_SCHEDULER andrewm@0: andrewm@0: using std::cout; andrewm@0: andrewm@0: const timestamp_diff_type MappingScheduler::kAllowableAdvanceExecutionTime = milliseconds_to_timestamp(1.0); andrewm@0: andrewm@11: // Constructor andrewm@11: MappingScheduler::MappingScheduler(PianoKeyboard& keyboard, String threadName) andrewm@11: : Thread(threadName), keyboard_(keyboard), andrewm@11: waitableEvent_(true), isRunning_(false), counter_(0) andrewm@11: #ifdef DEBUG_MAPPING_SCHEDULER_STATISTICS andrewm@11: ,lastDebugStatisticsTimestamp_(0) andrewm@11: #endif andrewm@11: { andrewm@11: } andrewm@11: andrewm@0: // Destructor andrewm@0: MappingScheduler::~MappingScheduler() { andrewm@0: // Stop the thread andrewm@0: stop(); andrewm@0: andrewm@0: // Now go through and delete any mappings awaiting deletion andrewm@0: // so these objects don't leak andrewm@0: MappingAction nextAction; andrewm@0: andrewm@0: while(actionsNow_.Consume(nextAction)) { andrewm@0: if(nextAction.who != 0 && nextAction.action == kActionUnregisterAndDelete) { andrewm@0: #ifdef DEBUG_MAPPING_SCHEDULER andrewm@0: std::cout << "~MappingScheduler(): Deleting mapping " << who << " (actionsNow)\n"; andrewm@0: #endif andrewm@0: delete nextAction.who; andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: while(!actionsLater_.empty()) { andrewm@0: nextAction = actionsLater_.begin()->second; andrewm@0: andrewm@0: if(nextAction.who != 0 && nextAction.action == kActionUnregisterAndDelete) { andrewm@0: #ifdef DEBUG_MAPPING_SCHEDULER andrewm@0: std::cout << "~MappingScheduler(): Deleting mapping " << who << " (actionsLater)\n"; andrewm@0: #endif andrewm@0: delete nextAction.who; andrewm@0: } andrewm@0: actionsLater_.erase(actionsLater_.begin()); andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Start the thread handling the scheduling. andrewm@0: void MappingScheduler::start() { andrewm@0: if(isRunning_) andrewm@0: return; andrewm@0: startThread(); andrewm@0: } andrewm@0: andrewm@0: // Stop the scheduler thread if it is currently running. andrewm@0: void MappingScheduler::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: // Register a mapping to be called by the scheduler andrewm@0: void MappingScheduler::registerMapping(Mapping *who) { andrewm@0: // Lock the mutex for insertions to ensure that only a single andrewm@0: // thread can act as producer at any given time. andrewm@0: ScopedLock sl(actionsInsertionMutex_); andrewm@0: andrewm@0: actionsNow_.Produce(MappingAction(who, counter_, kActionRegister)); andrewm@0: andrewm@0: // Increment the counter so each insertion gets a unique label andrewm@0: counter_++; andrewm@0: andrewm@0: // Wake up the consumer thread andrewm@0: waitableEvent_.signal(); andrewm@0: } andrewm@0: andrewm@0: // Schedule a mapping action to happen as soon as possible andrewm@0: void MappingScheduler::scheduleNow(Mapping *who) { andrewm@0: // Lock the mutex for insertions to ensure that only a single andrewm@0: // thread can act as producer at any given time. andrewm@0: ScopedLock sl(actionsInsertionMutex_); andrewm@0: andrewm@0: actionsNow_.Produce(MappingAction(who, counter_, kActionPerformMapping)); andrewm@0: andrewm@0: // Increment the counter so each insertion gets a unique label andrewm@0: counter_++; andrewm@0: andrewm@0: // Wake up the consumer thread andrewm@0: waitableEvent_.signal(); andrewm@0: } andrewm@0: andrewm@0: // Schedule a mapping action to happen in the future at a specified timestamp andrewm@0: void MappingScheduler::scheduleLater(Mapping *who, timestamp_type timestamp) { andrewm@0: ScopedLock sl(actionsLaterMutex_); andrewm@0: ScopedLock sl2(actionsInsertionMutex_); andrewm@0: andrewm@0: bool newActionWillComeFirst = false; andrewm@0: if(actionsLater_.empty()) andrewm@0: newActionWillComeFirst = true; andrewm@0: else if(timestamp < actionsLater_.begin()->first) andrewm@0: newActionWillComeFirst = true; andrewm@0: andrewm@0: actionsLater_.insert(std::pair(timestamp, andrewm@0: MappingAction(who, andrewm@0: counter_, andrewm@0: kActionPerformMapping))); andrewm@0: andrewm@0: // Increment the counter so each insertion gets a unique label andrewm@0: counter_++; andrewm@0: andrewm@0: // Wake up the consumer thread if what we inserted is the next andrewm@0: // upcoming event andrewm@0: if(newActionWillComeFirst) andrewm@0: waitableEvent_.signal(); andrewm@0: } andrewm@0: andrewm@0: // Unschedule any further mappings from this object. Immediate mappings andrewm@0: // already in the queue may still be executed. andrewm@0: void MappingScheduler::unschedule(Mapping *who) { andrewm@0: // Unscheduling works by inserting an action in the "now" queue andrewm@0: // which preempts any further actions by this object. andrewm@0: ScopedLock sl(actionsInsertionMutex_); andrewm@0: andrewm@0: actionsNow_.Produce(MappingAction(who, counter_, kActionUnschedule)); andrewm@0: andrewm@0: // Increment the counter to indicate we're at another cycle andrewm@0: counter_++; andrewm@0: andrewm@0: // Wake up the consumer thread andrewm@0: waitableEvent_.signal(); andrewm@0: } andrewm@0: andrewm@0: // Unregister a mapping which prevents it from being called by future events andrewm@0: void MappingScheduler::unregisterMapping(Mapping *who) { andrewm@0: // Lock the mutex for insertions to ensure that only a single andrewm@0: // thread can act as producer at any given time. andrewm@0: ScopedLock sl(actionsInsertionMutex_); andrewm@0: andrewm@0: actionsNow_.Produce(MappingAction(who, counter_, kActionUnregister)); andrewm@0: andrewm@0: // Increment the counter so each insertion gets a unique label andrewm@0: counter_++; andrewm@0: andrewm@0: // Wake up the consumer thread andrewm@0: waitableEvent_.signal(); andrewm@0: } andrewm@0: andrewm@0: andrewm@0: // Unschedule any further mappings from this object. Once any currently andrewm@0: // scheduled "now" mappings have been executed, delete the object in question. andrewm@0: void MappingScheduler::unregisterAndDelete(Mapping *who) { andrewm@0: // Unscheduling works by inserting an action in the "now" queue andrewm@0: // which preempts any further actions by this object. Deletion andrewm@0: // will be handled by the consumer thread. andrewm@0: ScopedLock sl(actionsInsertionMutex_); andrewm@0: andrewm@0: actionsNow_.Produce(MappingAction(who, counter_, kActionUnregisterAndDelete)); andrewm@0: andrewm@0: // Increment the counter to indicate we're at another cycle andrewm@0: counter_++; andrewm@0: andrewm@0: // Wake up the consumer thread andrewm@0: waitableEvent_.signal(); andrewm@0: } andrewm@0: andrewm@0: // This function runs in its own thread (from the Juce::Thread parent class). Every time andrewm@0: // it is signaled, it executes all the Mapping actions in the actionsNow_ category and then andrewm@0: // looks for the next delayed action. andrewm@0: andrewm@0: void MappingScheduler::run() { andrewm@0: isRunning_ = true; andrewm@0: andrewm@0: // This will run until the thread is interrupted (in the stop() method) andrewm@0: while(!threadShouldExit()) { andrewm@0: MappingAction nextAction; andrewm@0: andrewm@0: // Go through the accumulated actions in the "now" queue andrewm@0: while(actionsNow_.Consume(nextAction)) { andrewm@0: if(nextAction.who != 0) { andrewm@0: #ifdef DEBUG_MAPPING_SCHEDULER andrewm@0: std::cout << "Performing immediate mapping\n"; andrewm@0: #endif andrewm@0: performAction(nextAction); andrewm@0: } andrewm@11: andrewm@11: #ifdef DEBUG_MAPPING_SCHEDULER_STATISTICS andrewm@11: printDebugStatistics(); andrewm@11: #endif andrewm@0: } andrewm@0: andrewm@0: // Next, grab the first upcoming action in the later category andrewm@0: bool foundAction = true; andrewm@0: timestamp_diff_type timeToNextAction = 0; andrewm@0: andrewm@11: while(foundAction && !threadShouldExit()) { andrewm@0: // Lock the future actions mutex to examine the contents andrewm@0: // of the future actions collection andrewm@0: actionsLaterMutex_.enter(); andrewm@0: foundAction = false; andrewm@0: andrewm@0: if(!actionsLater_.empty()) { andrewm@0: std::multimap::iterator it = actionsLater_.begin(); andrewm@0: timestamp_type t = it->first; andrewm@0: andrewm@0: timeToNextAction = t - keyboard_.schedulerCurrentTimestamp(); andrewm@0: if(timeToNextAction <= 0) { andrewm@0: // If we get here, we have a non-empty collection fo future actions, the first andrewm@0: // of which should happen by now. Copy the action, erase it from the collection andrewm@0: // and unlock the mutex before proceeding. andrewm@0: nextAction = it->second; andrewm@0: actionsLater_.erase(it); andrewm@0: foundAction = true; andrewm@0: } andrewm@0: } andrewm@0: else andrewm@0: timeToNextAction = 0; andrewm@0: andrewm@0: actionsLaterMutex_.exit(); andrewm@0: andrewm@0: if(foundAction) { andrewm@0: // If this is set, we found a future action which is supposed to happen by now. andrewm@0: // Execute it and check the next one. andrewm@0: #ifdef DEBUG_MAPPING_SCHEDULER andrewm@0: std::cout << "Performing delayed mapping\n"; andrewm@0: #endif andrewm@0: performAction(nextAction); andrewm@0: } andrewm@0: else { andrewm@0: #ifdef DEBUG_MAPPING_SCHEDULER andrewm@0: std::cout << "Found no further actions\n"; andrewm@0: #endif andrewm@0: } andrewm@11: #ifdef DEBUG_MAPPING_SCHEDULER_STATISTICS andrewm@11: printDebugStatistics(); andrewm@11: #endif andrewm@0: } andrewm@0: andrewm@0: if(timeToNextAction > 0) { andrewm@0: // If we complete the above loop with timeToNextAction set greater than 0, it means andrewm@0: // we found an action that's supposed to happen in the future, but isn't ready yet. andrewm@0: // The alternative is that there were no further actions, in which case the loop will andrewm@0: // terminate with timeToNextAction set to 0. andrewm@0: andrewm@0: #ifdef DEBUG_MAPPING_SCHEDULER andrewm@0: std::cout << "Waiting for next action in " << timestamp_to_milliseconds(timeToNextAction) << "ms\n"; andrewm@11: #elif defined(DEBUG_MAPPING_SCHEDULER_STATISTICS) andrewm@11: if(timestamp_to_milliseconds(timeToNextAction) > 100) andrewm@11: std::cout << "Waiting for next action in " << timestamp_to_milliseconds(timeToNextAction) << "ms\n"; andrewm@0: #endif andrewm@11: andrewm@0: // Wait for the next action to arrive (unless signaled) andrewm@0: waitableEvent_.wait(timestamp_to_milliseconds(timeToNextAction)); andrewm@0: } andrewm@0: else { andrewm@0: // No future actions found; wait for a signal andrewm@0: andrewm@11: #if defined(DEBUG_MAPPING_SCHEDULER) || defined(DEBUG_MAPPING_SCHEDULER_STATISTICS) andrewm@0: std::cout << "Waiting for next action\n"; andrewm@0: #endif andrewm@0: waitableEvent_.wait(); andrewm@0: } andrewm@0: andrewm@0: waitableEvent_.reset(); // Clear the signal andrewm@0: } andrewm@0: } andrewm@0: andrewm@0: // Perform a mapping action: either execute the mapping or unschedule it, andrewm@0: // depending on the contents of the MappingAction object. andrewm@0: void MappingScheduler::performAction(MappingAction const& mappingAction) { andrewm@0: Mapping *who = mappingAction.who; andrewm@0: bool skip = true; andrewm@0: andrewm@0: andrewm@0: // Check if this mapping action has been superseded by another andrewm@0: // one already executed which was scheduled at the same time or later. andrewm@0: // For example, if multiple actions have the same counter and the same andrewm@0: // object, only the first one will run. andrewm@0: if(countersForMappings_.count(who) != 0) { andrewm@0: if(countersForMappings_[who] < mappingAction.counter) { andrewm@0: #ifdef DEBUG_MAPPING_SCHEDULER andrewm@0: std::cout << "Found counter " << countersForMappings_[who] << " for mapping " << who << std::endl; andrewm@0: #endif andrewm@0: skip = false; andrewm@0: } andrewm@0: } andrewm@0: else if(mappingAction.action == kActionRegister) { andrewm@0: // Registration can happen if there is no previous andrewm@0: // counter for the object. This is in fact the expected case. andrewm@0: #ifdef DEBUG_MAPPING_SCHEDULER andrewm@0: std::cout << "No counter for mapping " << who << " but allowing for registration\n"; andrewm@0: #endif andrewm@0: skip = false; andrewm@0: } andrewm@0: andrewm@0: if(!skip) { andrewm@0: // Update the last counter for this object andrewm@0: countersForMappings_[who] = mappingAction.counter; andrewm@0: andrewm@0: if(mappingAction.action == kActionRegister) { andrewm@0: #ifdef DEBUG_MAPPING_SCHEDULER andrewm@0: std::cout << "Registering object " << mappingAction.who << " with counter " << mappingAction.counter << std::endl; andrewm@0: #endif andrewm@0: } andrewm@0: else if(mappingAction.action == kActionPerformMapping) { andrewm@0: #ifdef DEBUG_MAPPING_SCHEDULER andrewm@0: std::cout << "Performing mapping for object " << mappingAction.who << " with counter " << mappingAction.counter << std::endl; andrewm@0: #endif andrewm@0: timestamp_type nextTimestamp = who->performMapping(); andrewm@0: andrewm@0: // Reschedule for later if next timestamp isn't 0 andrewm@0: if(nextTimestamp != 0) { andrewm@0: #ifdef DEBUG_MAPPING_SCHEDULER andrewm@0: std::cout << "Rescheduling object " << mappingAction.who << " for timestamp " << nextTimestamp << std::endl; andrewm@0: #endif andrewm@0: scheduleLater(who, nextTimestamp); andrewm@0: } andrewm@0: } andrewm@0: else if(mappingAction.action == kActionUnschedule) { andrewm@0: #ifdef DEBUG_MAPPING_SCHEDULER andrewm@0: std::cout << "Unscheduling object " << who << " with counter " << mappingAction.counter << std::endl; andrewm@0: #endif andrewm@0: // Nothing to do in fact; updating the counter will cause all future actions on this andrewm@0: // object to be ignored. andrewm@0: } andrewm@0: else if(mappingAction.action == kActionUnregister) { andrewm@0: #ifdef DEBUG_MAPPING_SCHEDULER andrewm@0: std::cout << "Unregistering and deleting object " << who << " with counter " << mappingAction.counter << std::endl; andrewm@0: #endif andrewm@0: // Remove the object from the counter registry andrewm@0: countersForMappings_.erase(who); andrewm@0: } andrewm@0: else if(mappingAction.action == kActionUnregisterAndDelete) { andrewm@0: #ifdef DEBUG_MAPPING_SCHEDULER andrewm@0: std::cout << "Unregistering and deleting object " << who << " with counter " << mappingAction.counter << std::endl; andrewm@0: #endif andrewm@0: // Remove the object from the counter registry andrewm@0: countersForMappings_.erase(who); andrewm@0: andrewm@0: // Delete this object andrewm@0: delete mappingAction.who; andrewm@0: } andrewm@0: else { andrewm@0: // Shouldn't happen andrewm@0: #ifdef DEBUG_MAPPING_SCHEDULER andrewm@0: std::cout << "Unknown action " << mappingAction.action << " for object " << who << " with counter " << mappingAction.counter << std::endl; andrewm@0: #endif andrewm@0: } andrewm@0: } andrewm@0: else { andrewm@0: #ifdef DEBUG_MAPPING_SCHEDULER andrewm@0: std::cout << "Skipping action " << mappingAction.action << " for object " << who << " with counter " << mappingAction.counter << std::endl; andrewm@0: #endif andrewm@0: } andrewm@11: } andrewm@11: andrewm@11: #ifdef DEBUG_MAPPING_SCHEDULER_STATISTICS andrewm@11: void MappingScheduler::printDebugStatistics() { andrewm@11: timestamp_type currentTimestamp = keyboard_.schedulerCurrentTimestamp(); andrewm@11: if(currentTimestamp - lastDebugStatisticsTimestamp_ < milliseconds_to_timestamp(500)) andrewm@11: return; andrewm@11: lastDebugStatisticsTimestamp_ = currentTimestamp; andrewm@11: std::cout << "MappingScheduler: " << actionsNow_.size() << " now, " << actionsLater_.size() << " later"; andrewm@11: if(!actionsLater_.empty()) { andrewm@11: std::cout << ", time lag = " << timestamp_to_milliseconds(currentTimestamp - actionsLater_.begin()->first) << std::endl; andrewm@11: } andrewm@11: else andrewm@11: std::cout << std::endl; andrewm@11: } andrewm@11: #endif