annotate Source/Mappings/MappingScheduler.cpp @ 56:b4a2d2ae43cf tip

merge
author Andrew McPherson <andrewm@eecs.qmul.ac.uk>
date Fri, 23 Nov 2018 15:48:14 +0000
parents c6f30c1e2bda
children
rev   line source
andrewm@0 1 /*
andrewm@0 2 TouchKeys: multi-touch musical keyboard control software
andrewm@0 3 Copyright (c) 2013 Andrew McPherson
andrewm@0 4
andrewm@0 5 This program is free software: you can redistribute it and/or modify
andrewm@0 6 it under the terms of the GNU General Public License as published by
andrewm@0 7 the Free Software Foundation, either version 3 of the License, or
andrewm@0 8 (at your option) any later version.
andrewm@0 9
andrewm@0 10 This program is distributed in the hope that it will be useful,
andrewm@0 11 but WITHOUT ANY WARRANTY; without even the implied warranty of
andrewm@0 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
andrewm@0 13 GNU General Public License for more details.
andrewm@0 14
andrewm@0 15 You should have received a copy of the GNU General Public License
andrewm@0 16 along with this program. If not, see <http://www.gnu.org/licenses/>.
andrewm@0 17
andrewm@0 18 =====================================================================
andrewm@0 19
andrewm@0 20 MappingScheduler.cpp: implements a thread in which mapping actions are
andrewm@0 21 performed. Each Mapping object implements a triggerReceived() method
andrewm@0 22 which is called by the hardware I/O thread. This method should do a
andrewm@0 23 minimal amount of work but pass the real work off to the performMapping()
andrewm@0 24 method which is called by the MappingScheduler thread. The scheduler
andrewm@0 25 also allows mapping calls to be performed in the absence of received data,
andrewm@0 26 for example to cause a parameter to ramp down over time if no touch data
andrewm@0 27 is received.
andrewm@0 28 */
andrewm@0 29
andrewm@0 30 #include "MappingScheduler.h"
andrewm@0 31 #include "Mapping.h"
andrewm@0 32
andrewm@0 33 #undef DEBUG_MAPPING_SCHEDULER
andrewm@0 34
andrewm@0 35 using std::cout;
andrewm@0 36
andrewm@0 37 const timestamp_diff_type MappingScheduler::kAllowableAdvanceExecutionTime = milliseconds_to_timestamp(1.0);
andrewm@0 38
andrewm@11 39 // Constructor
andrewm@11 40 MappingScheduler::MappingScheduler(PianoKeyboard& keyboard, String threadName)
andrewm@11 41 : Thread(threadName), keyboard_(keyboard),
andrewm@11 42 waitableEvent_(true), isRunning_(false), counter_(0)
andrewm@11 43 #ifdef DEBUG_MAPPING_SCHEDULER_STATISTICS
andrewm@11 44 ,lastDebugStatisticsTimestamp_(0)
andrewm@11 45 #endif
andrewm@11 46 {
andrewm@11 47 }
andrewm@11 48
andrewm@0 49 // Destructor
andrewm@0 50 MappingScheduler::~MappingScheduler() {
andrewm@0 51 // Stop the thread
andrewm@0 52 stop();
andrewm@0 53
andrewm@0 54 // Now go through and delete any mappings awaiting deletion
andrewm@0 55 // so these objects don't leak
andrewm@0 56 MappingAction nextAction;
andrewm@0 57
andrewm@0 58 while(actionsNow_.Consume(nextAction)) {
andrewm@0 59 if(nextAction.who != 0 && nextAction.action == kActionUnregisterAndDelete) {
andrewm@0 60 #ifdef DEBUG_MAPPING_SCHEDULER
andrewm@0 61 std::cout << "~MappingScheduler(): Deleting mapping " << who << " (actionsNow)\n";
andrewm@0 62 #endif
andrewm@0 63 delete nextAction.who;
andrewm@0 64 }
andrewm@0 65 }
andrewm@0 66
andrewm@0 67 while(!actionsLater_.empty()) {
andrewm@0 68 nextAction = actionsLater_.begin()->second;
andrewm@0 69
andrewm@0 70 if(nextAction.who != 0 && nextAction.action == kActionUnregisterAndDelete) {
andrewm@0 71 #ifdef DEBUG_MAPPING_SCHEDULER
andrewm@0 72 std::cout << "~MappingScheduler(): Deleting mapping " << who << " (actionsLater)\n";
andrewm@0 73 #endif
andrewm@0 74 delete nextAction.who;
andrewm@0 75 }
andrewm@0 76 actionsLater_.erase(actionsLater_.begin());
andrewm@0 77 }
andrewm@0 78 }
andrewm@0 79
andrewm@0 80 // Start the thread handling the scheduling.
andrewm@0 81 void MappingScheduler::start() {
andrewm@0 82 if(isRunning_)
andrewm@0 83 return;
andrewm@0 84 startThread();
andrewm@0 85 }
andrewm@0 86
andrewm@0 87 // Stop the scheduler thread if it is currently running.
andrewm@0 88 void MappingScheduler::stop() {
andrewm@0 89 if(!isRunning_)
andrewm@0 90 return;
andrewm@0 91
andrewm@0 92 // Tell the thread to quit and signal the event it waits on
andrewm@0 93 signalThreadShouldExit();
andrewm@0 94 waitableEvent_.signal();
andrewm@0 95 stopThread(-1);
andrewm@0 96
andrewm@0 97 isRunning_ = false;
andrewm@0 98 }
andrewm@0 99
andrewm@0 100 // Register a mapping to be called by the scheduler
andrewm@0 101 void MappingScheduler::registerMapping(Mapping *who) {
andrewm@0 102 // Lock the mutex for insertions to ensure that only a single
andrewm@0 103 // thread can act as producer at any given time.
andrewm@0 104 ScopedLock sl(actionsInsertionMutex_);
andrewm@0 105
andrewm@0 106 actionsNow_.Produce(MappingAction(who, counter_, kActionRegister));
andrewm@0 107
andrewm@0 108 // Increment the counter so each insertion gets a unique label
andrewm@0 109 counter_++;
andrewm@0 110
andrewm@0 111 // Wake up the consumer thread
andrewm@0 112 waitableEvent_.signal();
andrewm@0 113 }
andrewm@0 114
andrewm@0 115 // Schedule a mapping action to happen as soon as possible
andrewm@0 116 void MappingScheduler::scheduleNow(Mapping *who) {
andrewm@0 117 // Lock the mutex for insertions to ensure that only a single
andrewm@0 118 // thread can act as producer at any given time.
andrewm@0 119 ScopedLock sl(actionsInsertionMutex_);
andrewm@0 120
andrewm@0 121 actionsNow_.Produce(MappingAction(who, counter_, kActionPerformMapping));
andrewm@0 122
andrewm@0 123 // Increment the counter so each insertion gets a unique label
andrewm@0 124 counter_++;
andrewm@0 125
andrewm@0 126 // Wake up the consumer thread
andrewm@0 127 waitableEvent_.signal();
andrewm@0 128 }
andrewm@0 129
andrewm@0 130 // Schedule a mapping action to happen in the future at a specified timestamp
andrewm@0 131 void MappingScheduler::scheduleLater(Mapping *who, timestamp_type timestamp) {
andrewm@0 132 ScopedLock sl(actionsLaterMutex_);
andrewm@0 133 ScopedLock sl2(actionsInsertionMutex_);
andrewm@0 134
andrewm@0 135 bool newActionWillComeFirst = false;
andrewm@0 136 if(actionsLater_.empty())
andrewm@0 137 newActionWillComeFirst = true;
andrewm@0 138 else if(timestamp < actionsLater_.begin()->first)
andrewm@0 139 newActionWillComeFirst = true;
andrewm@0 140
andrewm@0 141 actionsLater_.insert(std::pair<timestamp_type, MappingAction>(timestamp,
andrewm@0 142 MappingAction(who,
andrewm@0 143 counter_,
andrewm@0 144 kActionPerformMapping)));
andrewm@0 145
andrewm@0 146 // Increment the counter so each insertion gets a unique label
andrewm@0 147 counter_++;
andrewm@0 148
andrewm@0 149 // Wake up the consumer thread if what we inserted is the next
andrewm@0 150 // upcoming event
andrewm@0 151 if(newActionWillComeFirst)
andrewm@0 152 waitableEvent_.signal();
andrewm@0 153 }
andrewm@0 154
andrewm@0 155 // Unschedule any further mappings from this object. Immediate mappings
andrewm@0 156 // already in the queue may still be executed.
andrewm@0 157 void MappingScheduler::unschedule(Mapping *who) {
andrewm@0 158 // Unscheduling works by inserting an action in the "now" queue
andrewm@0 159 // which preempts any further actions by this object.
andrewm@0 160 ScopedLock sl(actionsInsertionMutex_);
andrewm@0 161
andrewm@0 162 actionsNow_.Produce(MappingAction(who, counter_, kActionUnschedule));
andrewm@0 163
andrewm@0 164 // Increment the counter to indicate we're at another cycle
andrewm@0 165 counter_++;
andrewm@0 166
andrewm@0 167 // Wake up the consumer thread
andrewm@0 168 waitableEvent_.signal();
andrewm@0 169 }
andrewm@0 170
andrewm@0 171 // Unregister a mapping which prevents it from being called by future events
andrewm@0 172 void MappingScheduler::unregisterMapping(Mapping *who) {
andrewm@0 173 // Lock the mutex for insertions to ensure that only a single
andrewm@0 174 // thread can act as producer at any given time.
andrewm@0 175 ScopedLock sl(actionsInsertionMutex_);
andrewm@0 176
andrewm@0 177 actionsNow_.Produce(MappingAction(who, counter_, kActionUnregister));
andrewm@0 178
andrewm@0 179 // Increment the counter so each insertion gets a unique label
andrewm@0 180 counter_++;
andrewm@0 181
andrewm@0 182 // Wake up the consumer thread
andrewm@0 183 waitableEvent_.signal();
andrewm@0 184 }
andrewm@0 185
andrewm@0 186
andrewm@0 187 // Unschedule any further mappings from this object. Once any currently
andrewm@0 188 // scheduled "now" mappings have been executed, delete the object in question.
andrewm@0 189 void MappingScheduler::unregisterAndDelete(Mapping *who) {
andrewm@0 190 // Unscheduling works by inserting an action in the "now" queue
andrewm@0 191 // which preempts any further actions by this object. Deletion
andrewm@0 192 // will be handled by the consumer thread.
andrewm@0 193 ScopedLock sl(actionsInsertionMutex_);
andrewm@0 194
andrewm@0 195 actionsNow_.Produce(MappingAction(who, counter_, kActionUnregisterAndDelete));
andrewm@0 196
andrewm@0 197 // Increment the counter to indicate we're at another cycle
andrewm@0 198 counter_++;
andrewm@0 199
andrewm@0 200 // Wake up the consumer thread
andrewm@0 201 waitableEvent_.signal();
andrewm@0 202 }
andrewm@0 203
andrewm@0 204 // This function runs in its own thread (from the Juce::Thread parent class). Every time
andrewm@0 205 // it is signaled, it executes all the Mapping actions in the actionsNow_ category and then
andrewm@0 206 // looks for the next delayed action.
andrewm@0 207
andrewm@0 208 void MappingScheduler::run() {
andrewm@0 209 isRunning_ = true;
andrewm@0 210
andrewm@0 211 // This will run until the thread is interrupted (in the stop() method)
andrewm@0 212 while(!threadShouldExit()) {
andrewm@0 213 MappingAction nextAction;
andrewm@0 214
andrewm@0 215 // Go through the accumulated actions in the "now" queue
andrewm@0 216 while(actionsNow_.Consume(nextAction)) {
andrewm@0 217 if(nextAction.who != 0) {
andrewm@0 218 #ifdef DEBUG_MAPPING_SCHEDULER
andrewm@0 219 std::cout << "Performing immediate mapping\n";
andrewm@0 220 #endif
andrewm@0 221 performAction(nextAction);
andrewm@0 222 }
andrewm@11 223
andrewm@11 224 #ifdef DEBUG_MAPPING_SCHEDULER_STATISTICS
andrewm@11 225 printDebugStatistics();
andrewm@11 226 #endif
andrewm@0 227 }
andrewm@0 228
andrewm@0 229 // Next, grab the first upcoming action in the later category
andrewm@0 230 bool foundAction = true;
andrewm@0 231 timestamp_diff_type timeToNextAction = 0;
andrewm@0 232
andrewm@11 233 while(foundAction && !threadShouldExit()) {
andrewm@0 234 // Lock the future actions mutex to examine the contents
andrewm@0 235 // of the future actions collection
andrewm@0 236 actionsLaterMutex_.enter();
andrewm@0 237 foundAction = false;
andrewm@0 238
andrewm@0 239 if(!actionsLater_.empty()) {
andrewm@0 240 std::multimap<timestamp_type, MappingAction>::iterator it = actionsLater_.begin();
andrewm@0 241 timestamp_type t = it->first;
andrewm@0 242
andrewm@0 243 timeToNextAction = t - keyboard_.schedulerCurrentTimestamp();
andrewm@0 244 if(timeToNextAction <= 0) {
andrewm@0 245 // If we get here, we have a non-empty collection fo future actions, the first
andrewm@0 246 // of which should happen by now. Copy the action, erase it from the collection
andrewm@0 247 // and unlock the mutex before proceeding.
andrewm@0 248 nextAction = it->second;
andrewm@0 249 actionsLater_.erase(it);
andrewm@0 250 foundAction = true;
andrewm@0 251 }
andrewm@0 252 }
andrewm@0 253 else
andrewm@0 254 timeToNextAction = 0;
andrewm@0 255
andrewm@0 256 actionsLaterMutex_.exit();
andrewm@0 257
andrewm@0 258 if(foundAction) {
andrewm@0 259 // If this is set, we found a future action which is supposed to happen by now.
andrewm@0 260 // Execute it and check the next one.
andrewm@0 261 #ifdef DEBUG_MAPPING_SCHEDULER
andrewm@0 262 std::cout << "Performing delayed mapping\n";
andrewm@0 263 #endif
andrewm@0 264 performAction(nextAction);
andrewm@0 265 }
andrewm@0 266 else {
andrewm@0 267 #ifdef DEBUG_MAPPING_SCHEDULER
andrewm@0 268 std::cout << "Found no further actions\n";
andrewm@0 269 #endif
andrewm@0 270 }
andrewm@11 271 #ifdef DEBUG_MAPPING_SCHEDULER_STATISTICS
andrewm@11 272 printDebugStatistics();
andrewm@11 273 #endif
andrewm@0 274 }
andrewm@0 275
andrewm@0 276 if(timeToNextAction > 0) {
andrewm@0 277 // If we complete the above loop with timeToNextAction set greater than 0, it means
andrewm@0 278 // we found an action that's supposed to happen in the future, but isn't ready yet.
andrewm@0 279 // The alternative is that there were no further actions, in which case the loop will
andrewm@0 280 // terminate with timeToNextAction set to 0.
andrewm@0 281
andrewm@0 282 #ifdef DEBUG_MAPPING_SCHEDULER
andrewm@0 283 std::cout << "Waiting for next action in " << timestamp_to_milliseconds(timeToNextAction) << "ms\n";
andrewm@11 284 #elif defined(DEBUG_MAPPING_SCHEDULER_STATISTICS)
andrewm@11 285 if(timestamp_to_milliseconds(timeToNextAction) > 100)
andrewm@11 286 std::cout << "Waiting for next action in " << timestamp_to_milliseconds(timeToNextAction) << "ms\n";
andrewm@0 287 #endif
andrewm@11 288
andrewm@0 289 // Wait for the next action to arrive (unless signaled)
andrewm@0 290 waitableEvent_.wait(timestamp_to_milliseconds(timeToNextAction));
andrewm@0 291 }
andrewm@0 292 else {
andrewm@0 293 // No future actions found; wait for a signal
andrewm@0 294
andrewm@11 295 #if defined(DEBUG_MAPPING_SCHEDULER) || defined(DEBUG_MAPPING_SCHEDULER_STATISTICS)
andrewm@0 296 std::cout << "Waiting for next action\n";
andrewm@0 297 #endif
andrewm@0 298 waitableEvent_.wait();
andrewm@0 299 }
andrewm@0 300
andrewm@0 301 waitableEvent_.reset(); // Clear the signal
andrewm@0 302 }
andrewm@0 303 }
andrewm@0 304
andrewm@0 305 // Perform a mapping action: either execute the mapping or unschedule it,
andrewm@0 306 // depending on the contents of the MappingAction object.
andrewm@0 307 void MappingScheduler::performAction(MappingAction const& mappingAction) {
andrewm@0 308 Mapping *who = mappingAction.who;
andrewm@0 309 bool skip = true;
andrewm@0 310
andrewm@0 311
andrewm@0 312 // Check if this mapping action has been superseded by another
andrewm@0 313 // one already executed which was scheduled at the same time or later.
andrewm@0 314 // For example, if multiple actions have the same counter and the same
andrewm@0 315 // object, only the first one will run.
andrewm@0 316 if(countersForMappings_.count(who) != 0) {
andrewm@0 317 if(countersForMappings_[who] < mappingAction.counter) {
andrewm@0 318 #ifdef DEBUG_MAPPING_SCHEDULER
andrewm@0 319 std::cout << "Found counter " << countersForMappings_[who] << " for mapping " << who << std::endl;
andrewm@0 320 #endif
andrewm@0 321 skip = false;
andrewm@0 322 }
andrewm@0 323 }
andrewm@0 324 else if(mappingAction.action == kActionRegister) {
andrewm@0 325 // Registration can happen if there is no previous
andrewm@0 326 // counter for the object. This is in fact the expected case.
andrewm@0 327 #ifdef DEBUG_MAPPING_SCHEDULER
andrewm@0 328 std::cout << "No counter for mapping " << who << " but allowing for registration\n";
andrewm@0 329 #endif
andrewm@0 330 skip = false;
andrewm@0 331 }
andrewm@0 332
andrewm@0 333 if(!skip) {
andrewm@0 334 // Update the last counter for this object
andrewm@0 335 countersForMappings_[who] = mappingAction.counter;
andrewm@0 336
andrewm@0 337 if(mappingAction.action == kActionRegister) {
andrewm@0 338 #ifdef DEBUG_MAPPING_SCHEDULER
andrewm@0 339 std::cout << "Registering object " << mappingAction.who << " with counter " << mappingAction.counter << std::endl;
andrewm@0 340 #endif
andrewm@0 341 }
andrewm@0 342 else if(mappingAction.action == kActionPerformMapping) {
andrewm@0 343 #ifdef DEBUG_MAPPING_SCHEDULER
andrewm@0 344 std::cout << "Performing mapping for object " << mappingAction.who << " with counter " << mappingAction.counter << std::endl;
andrewm@0 345 #endif
andrewm@0 346 timestamp_type nextTimestamp = who->performMapping();
andrewm@0 347
andrewm@0 348 // Reschedule for later if next timestamp isn't 0
andrewm@0 349 if(nextTimestamp != 0) {
andrewm@0 350 #ifdef DEBUG_MAPPING_SCHEDULER
andrewm@0 351 std::cout << "Rescheduling object " << mappingAction.who << " for timestamp " << nextTimestamp << std::endl;
andrewm@0 352 #endif
andrewm@0 353 scheduleLater(who, nextTimestamp);
andrewm@0 354 }
andrewm@0 355 }
andrewm@0 356 else if(mappingAction.action == kActionUnschedule) {
andrewm@0 357 #ifdef DEBUG_MAPPING_SCHEDULER
andrewm@0 358 std::cout << "Unscheduling object " << who << " with counter " << mappingAction.counter << std::endl;
andrewm@0 359 #endif
andrewm@0 360 // Nothing to do in fact; updating the counter will cause all future actions on this
andrewm@0 361 // object to be ignored.
andrewm@0 362 }
andrewm@0 363 else if(mappingAction.action == kActionUnregister) {
andrewm@0 364 #ifdef DEBUG_MAPPING_SCHEDULER
andrewm@0 365 std::cout << "Unregistering and deleting object " << who << " with counter " << mappingAction.counter << std::endl;
andrewm@0 366 #endif
andrewm@0 367 // Remove the object from the counter registry
andrewm@0 368 countersForMappings_.erase(who);
andrewm@0 369 }
andrewm@0 370 else if(mappingAction.action == kActionUnregisterAndDelete) {
andrewm@0 371 #ifdef DEBUG_MAPPING_SCHEDULER
andrewm@0 372 std::cout << "Unregistering and deleting object " << who << " with counter " << mappingAction.counter << std::endl;
andrewm@0 373 #endif
andrewm@0 374 // Remove the object from the counter registry
andrewm@0 375 countersForMappings_.erase(who);
andrewm@0 376
andrewm@0 377 // Delete this object
andrewm@0 378 delete mappingAction.who;
andrewm@0 379 }
andrewm@0 380 else {
andrewm@0 381 // Shouldn't happen
andrewm@0 382 #ifdef DEBUG_MAPPING_SCHEDULER
andrewm@0 383 std::cout << "Unknown action " << mappingAction.action << " for object " << who << " with counter " << mappingAction.counter << std::endl;
andrewm@0 384 #endif
andrewm@0 385 }
andrewm@0 386 }
andrewm@0 387 else {
andrewm@0 388 #ifdef DEBUG_MAPPING_SCHEDULER
andrewm@0 389 std::cout << "Skipping action " << mappingAction.action << " for object " << who << " with counter " << mappingAction.counter << std::endl;
andrewm@0 390 #endif
andrewm@0 391 }
andrewm@11 392 }
andrewm@11 393
andrewm@11 394 #ifdef DEBUG_MAPPING_SCHEDULER_STATISTICS
andrewm@11 395 void MappingScheduler::printDebugStatistics() {
andrewm@11 396 timestamp_type currentTimestamp = keyboard_.schedulerCurrentTimestamp();
andrewm@11 397 if(currentTimestamp - lastDebugStatisticsTimestamp_ < milliseconds_to_timestamp(500))
andrewm@11 398 return;
andrewm@11 399 lastDebugStatisticsTimestamp_ = currentTimestamp;
andrewm@11 400 std::cout << "MappingScheduler: " << actionsNow_.size() << " now, " << actionsLater_.size() << " later";
andrewm@11 401 if(!actionsLater_.empty()) {
andrewm@11 402 std::cout << ", time lag = " << timestamp_to_milliseconds(currentTimestamp - actionsLater_.begin()->first) << std::endl;
andrewm@11 403 }
andrewm@11 404 else
andrewm@11 405 std::cout << std::endl;
andrewm@11 406 }
andrewm@11 407 #endif