#include #include #include #include #include #include #include #include #include #include namespace smo { namespace stim_buff { void StimulusBuffer::stop() { shouldContinue.store(false); // Set up a timeout bridge using the io_service boost::asio::deadline_timer delayTimer(ioService); AsynchronousBridge bridge(ioService); // Set up the delay to let in-flight operation finish delayTimer.expires_from_now( boost::posix_time::milliseconds(getStopDelayMs())); delayTimer.async_wait( [&bridge](const boost::system::error_code& error) { (void)error; // Always signal complete, whether timeout expired or was cancelled bridge.setAsyncOperationComplete(); }); bridge.waitForAsyncOperationCompleteOrIoServiceStopped(); std::cout << __func__ << ": Stopped stimulus buffer for device " << deviceAttachmentSpec.deviceSelector << std::endl; // After delay, cancel timer and perform cleanup timer.cancel(); } void StimulusBuffer::scheduleNextTimeout(int delayMs) { if (!shouldContinue.load()) { return; } // Schedule the next timeout using the provided delay timer.expires_from_now( boost::posix_time::milliseconds(delayMs)); timer.async_wait( std::bind( &StimulusBuffer::onTimeout, this, std::placeholders::_1)); } void StimulusBuffer::onTimeout(const boost::system::error_code& error) { // Timer was cancelled, which is expected when stopping if (error == boost::asio::error::operation_aborted) { return; } if (error) { std::cerr << "StimulusBuffer: Timer error: " << error.message() << std::endl; return; } if (!shouldContinue.load()) { return; } /** EXPLANATION: * We need to ensure that there's only ever one stimframe being produced * during any CONFIG_STIMBUFF_FRAME_PERIOD_MS period. To guarantee this, we * use a spinlock. * * When a new frame is to be produced, the async producer will first acquire * the frameAssemblyLimiter spinlock. This way, when the next timeout is * fired it can check whether its predecessor stimframe has finished being * produced. If the preceding stimframe is still being produced, then we'll * sleep for CONFIG_STIMBUFF_FRAME_RETRY_DELAY_MS ms before trying again. */ int nextWakeupDelayMs; if (frameAssemblyRateLimiter.tryAcquire()) { nextWakeupDelayMs = CONFIG_STIMBUFF_FRAME_PERIOD_MS; } else { nextWakeupDelayMs = CONFIG_STIMBUFF_FRAME_RETRY_DELAY_MS; } // Call the derived class's frame production handler stimFrameProductionTimesliceInd(); // Note: The lock should be released when frame production completes // Schedule next timeout with the pre-determined duration scheduleNextTimeout(nextWakeupDelayMs); } } // namespace stim_buff } // namespace smo