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