2025-11-02 19:08:47 -04:00
|
|
|
#include <config.h>
|
2025-11-13 01:09:30 -04:00
|
|
|
#include <iostream>
|
|
|
|
|
#include <chrono>
|
2025-11-20 01:25:46 -04:00
|
|
|
#include <algorithm>
|
2026-05-30 11:59:42 -04:00
|
|
|
#include <boost/asio/io_context.hpp>
|
2025-11-13 01:09:30 -04:00
|
|
|
#include <opts.h>
|
|
|
|
|
#include <componentThread.h>
|
2026-06-09 11:19:42 -04:00
|
|
|
#include <adapters/boostAsio/deadlineTimerAReq.h>
|
2025-11-14 19:50:51 -04:00
|
|
|
#include <user/stimulusProducer.h>
|
2025-11-14 23:19:32 -04:00
|
|
|
#include <user/stimulusBuffer.h>
|
2025-11-02 19:08:47 -04:00
|
|
|
|
|
|
|
|
namespace smo {
|
|
|
|
|
namespace stim_buff {
|
|
|
|
|
|
2026-06-09 19:47:44 -04:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
long computeTimesliceResidueMs(
|
|
|
|
|
long productionDurationMs, long periodMs)
|
|
|
|
|
{
|
|
|
|
|
if (productionDurationMs >= periodMs) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return periodMs - productionDurationMs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void logProductionOverrunIfNeeded(
|
|
|
|
|
const char *daemonName,
|
|
|
|
|
long productionDurationMs, long periodMs,
|
|
|
|
|
size_t &nTimesliceOverruns)
|
|
|
|
|
{
|
|
|
|
|
if (productionDurationMs <= periodMs) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
++nTimesliceOverruns;
|
|
|
|
|
const long overrunByMs = productionDurationMs - periodMs;
|
|
|
|
|
std::cerr << daemonName << ": production overrun: actual="
|
|
|
|
|
<< productionDurationMs << "ms budget=" << periodMs
|
|
|
|
|
<< "ms overrunBy=" << overrunByMs << "ms nOverruns="
|
|
|
|
|
<< nTimesliceOverruns << std::endl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
long durationMsSince(
|
|
|
|
|
const std::chrono::high_resolution_clock::time_point &startStamp,
|
|
|
|
|
const std::chrono::high_resolution_clock::time_point &endStamp)
|
|
|
|
|
{
|
|
|
|
|
const auto duration = endStamp - startStamp;
|
|
|
|
|
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
|
|
|
duration).count();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void logDaemonDurationsIfVerbose(
|
|
|
|
|
const char *daemonName,
|
|
|
|
|
long productionDurationMs,
|
|
|
|
|
long timesliceDurationMs,
|
|
|
|
|
long periodMs)
|
|
|
|
|
{
|
2026-06-10 04:12:32 -04:00
|
|
|
if (!OptionParser::getOptions().verbose) {
|
2026-06-09 19:47:44 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::cerr << daemonName << ": daemon durations: production="
|
|
|
|
|
<< productionDurationMs << "ms timeslice="
|
|
|
|
|
<< timesliceDurationMs << "ms period=" << periodMs
|
|
|
|
|
<< "ms" << std::endl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
2025-11-14 23:19:32 -04:00
|
|
|
std::shared_ptr<StimulusBuffer> StimulusProducer::getAttachedStimulusBuffer(
|
|
|
|
|
const std::shared_ptr<device::DeviceAttachmentSpec>& spec) const
|
|
|
|
|
{
|
|
|
|
|
for (const auto& buffer : attachedStimulusBuffers)
|
|
|
|
|
{
|
|
|
|
|
if (buffer && buffer->deviceAttachmentSpec &&
|
|
|
|
|
*buffer->deviceAttachmentSpec == *spec)
|
|
|
|
|
{
|
|
|
|
|
return buffer;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-14 11:02:18 -04:00
|
|
|
std::shared_ptr<StimulusBuffer> StimulusProducer::getAttachedStimulusBufferByAttachIdentity(
|
|
|
|
|
const std::string& deviceIdentifier,
|
|
|
|
|
const std::string& qualeIfaceApi) const
|
|
|
|
|
{
|
|
|
|
|
for (const auto& buffer : attachedStimulusBuffers)
|
|
|
|
|
{
|
|
|
|
|
if (!buffer || !buffer->deviceAttachmentSpec)
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error(
|
|
|
|
|
"StimulusProducer::getAttachedStimulusBufferByAttachIdentity: "
|
|
|
|
|
"encountered null buffer or null deviceAttachmentSpec in "
|
|
|
|
|
"attachedStimulusBuffers (should never happen)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (buffer->deviceAttachmentSpec->deviceIdentifier != deviceIdentifier)
|
|
|
|
|
{ continue; }
|
|
|
|
|
|
|
|
|
|
if (buffer->deviceAttachmentSpec->qualeIfaceApi != qualeIfaceApi)
|
|
|
|
|
{ continue; }
|
|
|
|
|
|
|
|
|
|
return buffer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-16 02:23:53 -04:00
|
|
|
bool StimulusProducer::hasBufferWithQualeIfaceApi(
|
|
|
|
|
const std::string& qualeIfaceApi) const
|
|
|
|
|
{
|
|
|
|
|
for (const auto& buffer : attachedStimulusBuffers)
|
|
|
|
|
{
|
|
|
|
|
if (!buffer || !buffer->deviceAttachmentSpec)
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error(
|
|
|
|
|
"StimulusProducer::hasBufferWithQualeIfaceApi: encountered "
|
|
|
|
|
"null buffer or null deviceAttachmentSpec in "
|
|
|
|
|
"attachedStimulusBuffers (should never happen)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (buffer->deviceAttachmentSpec->qualeIfaceApi != qualeIfaceApi)
|
|
|
|
|
{ continue; }
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-14 11:02:18 -04:00
|
|
|
void StimulusProducer::ensureNoDuplicateQualeIface(
|
|
|
|
|
const std::string& qualeIfaceApi) const
|
|
|
|
|
{
|
|
|
|
|
if (!hasBufferWithQualeIfaceApi(qualeIfaceApi)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw std::runtime_error(
|
|
|
|
|
"duplicate qualeIface '" + qualeIfaceApi
|
|
|
|
|
+ "' for this producer session");
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-23 23:10:20 -04:00
|
|
|
bool StimulusProducer::addAttachedStimulusBufferIfNotExists(
|
|
|
|
|
const std::shared_ptr<StimulusBuffer>& buffer)
|
|
|
|
|
{
|
|
|
|
|
if (!buffer) { return false; }
|
|
|
|
|
|
|
|
|
|
auto it = std::find_if(
|
|
|
|
|
attachedStimulusBuffers.begin(),
|
|
|
|
|
attachedStimulusBuffers.end(),
|
|
|
|
|
[&](const std::shared_ptr<StimulusBuffer>& buf) {
|
|
|
|
|
return buf && buffer &&
|
|
|
|
|
buf->deviceAttachmentSpec && buffer->deviceAttachmentSpec &&
|
|
|
|
|
*(buf->deviceAttachmentSpec) == *(buffer->deviceAttachmentSpec);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (it != attachedStimulusBuffers.end()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
attachedStimulusBuffers.push_back(buffer);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-20 01:25:46 -04:00
|
|
|
void StimulusProducer::destroyAttachedStimulusBuffer(
|
|
|
|
|
const std::shared_ptr<StimulusBuffer>& buffer)
|
|
|
|
|
{
|
|
|
|
|
if (!buffer) { return; }
|
|
|
|
|
|
|
|
|
|
auto it = std::find(
|
|
|
|
|
attachedStimulusBuffers.begin(),
|
|
|
|
|
attachedStimulusBuffers.end(),
|
|
|
|
|
buffer);
|
|
|
|
|
|
|
|
|
|
if (it != attachedStimulusBuffers.end()) {
|
|
|
|
|
attachedStimulusBuffers.erase(it);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-09 11:19:42 -04:00
|
|
|
sscl::co::NonViralNonPostingInvoker StimulusProducer::productionCDaemon(
|
|
|
|
|
std::exception_ptr &, std::function<void()>,
|
|
|
|
|
sscl::SyncCancelerForAsyncWork &canceler)
|
2025-11-02 19:08:47 -04:00
|
|
|
{
|
2026-06-09 19:47:44 -04:00
|
|
|
const long framePeriodMs = CONFIG_STIMBUFF_FRAME_PERIOD_MS;
|
2025-11-02 19:08:47 -04:00
|
|
|
|
2026-06-09 11:19:42 -04:00
|
|
|
do
|
2025-11-12 12:31:37 -04:00
|
|
|
{
|
2026-06-09 19:47:44 -04:00
|
|
|
if (canceler.isCancellationRequested()) {
|
|
|
|
|
break;
|
2026-06-09 11:19:42 -04:00
|
|
|
}
|
2026-05-30 07:18:29 -04:00
|
|
|
|
2026-06-09 19:47:44 -04:00
|
|
|
const auto timesliceStartStamp =
|
|
|
|
|
std::chrono::high_resolution_clock::now();
|
|
|
|
|
|
|
|
|
|
const auto productionStartStamp =
|
|
|
|
|
std::chrono::high_resolution_clock::now();
|
|
|
|
|
|
|
|
|
|
co_await stimFrameProductionTimesliceCInd(canceler);
|
|
|
|
|
|
|
|
|
|
const auto productionEndStamp =
|
|
|
|
|
std::chrono::high_resolution_clock::now();
|
|
|
|
|
const long productionDurationMs = durationMsSince(
|
|
|
|
|
productionStartStamp, productionEndStamp);
|
|
|
|
|
|
|
|
|
|
logProductionOverrunIfNeeded(
|
|
|
|
|
"productionCDaemon",
|
|
|
|
|
productionDurationMs, framePeriodMs, nTimesliceOverruns);
|
|
|
|
|
|
|
|
|
|
const long residueMs = computeTimesliceResidueMs(
|
|
|
|
|
productionDurationMs, framePeriodMs);
|
|
|
|
|
|
|
|
|
|
// Schedule the next timeout based on timeslice remaining time.
|
2026-06-09 11:19:42 -04:00
|
|
|
const bool expiredNormally = co_await
|
|
|
|
|
adapters::boostAsio::getDeadlineTimerAReqAwaiter(
|
|
|
|
|
ioContext,
|
|
|
|
|
daemonTimer,
|
2026-06-09 19:47:44 -04:00
|
|
|
boost::posix_time::milliseconds(residueMs));
|
2026-05-30 07:18:29 -04:00
|
|
|
|
2026-06-09 11:19:42 -04:00
|
|
|
if (!expiredNormally) {
|
|
|
|
|
// Timer was cancelled, which is expected when stopping
|
|
|
|
|
break;
|
2025-11-13 01:09:30 -04:00
|
|
|
}
|
|
|
|
|
|
2026-06-09 19:47:44 -04:00
|
|
|
const auto timesliceEndStamp =
|
|
|
|
|
std::chrono::high_resolution_clock::now();
|
|
|
|
|
const long timesliceDurationMs = durationMsSince(
|
|
|
|
|
timesliceStartStamp, timesliceEndStamp);
|
|
|
|
|
|
|
|
|
|
logDaemonDurationsIfVerbose(
|
|
|
|
|
"productionCDaemon",
|
|
|
|
|
productionDurationMs, timesliceDurationMs,
|
|
|
|
|
framePeriodMs);
|
2025-11-13 01:09:30 -04:00
|
|
|
|
2026-06-09 11:19:42 -04:00
|
|
|
} while (!canceler.isCancellationRequested());
|
2025-11-02 19:08:47 -04:00
|
|
|
|
2026-06-09 11:19:42 -04:00
|
|
|
co_return;
|
|
|
|
|
}
|
2025-11-12 15:08:44 -04:00
|
|
|
|
2026-06-09 11:19:42 -04:00
|
|
|
void StimulusProducer::start()
|
|
|
|
|
{
|
|
|
|
|
std::cout << __func__ << ": Starting stimulus producer for device "
|
|
|
|
|
<< deviceAttachmentSpec->deviceSelector << std::endl;
|
2025-11-13 01:09:30 -04:00
|
|
|
|
2026-06-09 19:47:44 -04:00
|
|
|
nTimesliceOverruns = 0;
|
2026-06-09 11:19:42 -04:00
|
|
|
taskNursery.openAdmission();
|
|
|
|
|
taskNursery.launch(
|
|
|
|
|
[this](sscl::co::NonViralTaskNursery::Slot::Lease &lease)
|
|
|
|
|
{
|
|
|
|
|
return productionCDaemon(
|
|
|
|
|
lease.getExceptionStorage(),
|
|
|
|
|
lease.getCallerLambda(),
|
|
|
|
|
lease.getSyncCanceler());
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void StimulusProducer::stop()
|
|
|
|
|
{
|
|
|
|
|
// Cancel timer immediately
|
|
|
|
|
daemonTimer.cancel();
|
|
|
|
|
taskNursery.requestCancelOnAll();
|
|
|
|
|
taskNursery.closeAdmission();
|
|
|
|
|
taskNursery.syncAwaitAllSettlements(
|
|
|
|
|
sscl::ComponentThread::getSelf()->getIoContext());
|
|
|
|
|
|
|
|
|
|
std::cout << __func__ << ": Stopped stimulus producer for device "
|
|
|
|
|
<< deviceAttachmentSpec->deviceSelector << std::endl;
|
2025-11-02 19:08:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace stim_buff
|
|
|
|
|
} // namespace smo
|