SDKC++ SDK

Runtime modes

Current thread, background thread and manual update.

Current version · 1.4.0

Runtime modes

This chapter applies only to applications that need to continuously advance background work such as Retargeting or automatic reconnection. When only reading the raw pose/IMU, there is no need to choose a runtime: after a successful start() call, data is published with valid device frames.

When scheduling is needed, choose one of three mutually exclusive run modes: the current thread, a background thread, or manual update. next() and C++20 coroutines are ways to wait for a single data item, not a fourth run mode.

Running continuously on the current thread

run_forever() runs on the current thread and suits command-line programs with a simple lifecycle:

glove.start();
glove.run_forever();

run_forever() suits programs that keep running on the current thread. The public command-line examples end the process with Ctrl+C; when the application needs to stop it actively, use the background thread mode below.

Running on a background thread

Use this when the main thread must continue running the UI, the application event loop, or safe-stop coordination:

glove.run_background();
// The main thread handles other work.
glove.request_stop();
glove.join();

join() waits for the background run to finish and reports runtime errors. run_background() does not implicitly connect or start devices.

Manual update

When the application owns its own scheduling loop, call update() repeatedly to perform one SDK task scheduling pass:

const auto update = glove.update();
do_application_work();
if (update.has_next_task()) {
    const auto result = update.sleep();
}

UpdateResult provides tick(), ran_task_count(), and has_next_task(). sleep() waits for subsequent work based on the current time and returns SleepResult::ready, stopped, or no_tasks. Each update result's wait token can be used only once. Raw pose/IMU is published with device frames and does not depend on update().

Blocking wait and cancellation

next() returns a move-only NextOperation<T> that waits for the next data item after registration succeeds:

auto pending = device.pose().next();
auto sample = pending.get();

next() can be used after start(); for continuous data processing, subscribe() registered before starting is still preferred. IMU is awaited in the same way as pose.

get() blocks until data arrives or the operation terminates, and completes internal subscription cleanup before returning. Raw pose/IMU can wake the wait directly after start(); when waiting for retargeting output, the SDK scheduling must be advanced by background mode or another thread. Use cancel() to cancel synchronously:

auto pending = device.pose().next();
pending.cancel();

try {
    pending.get();
} catch (const std::system_error& error) {
    if (error.code() != std::errc::operation_canceled) {
        throw;
    }
}

NextOperation<T> can be consumed only once. Calling it again on a moved-from or already consumed operation throws LifecycleError. For the overview of read methods, see Data channels & read methods.

C++20 coroutines

After additionally linking qnbot::core_coro and including <qnbot/coro.hpp>, you can change an existing single wait to co_await qnbot::coro::next(...):

qnbot::Sample<qnbot::GlovePose> sample =
    co_await qnbot::coro::next(
        device.pose().next(), executor, stop_token);

executor is a std::shared_ptr<qnbot::coro::DeferredExecutor>. Its post() must satisfy the following contract:

  • It receives each task once and eventually executes it once.
  • It returns quickly and does not wait for the task execution to finish.
  • It does not execute the task inline on the callback thread that calls post().
  • post() does not throw.

The NextOperation<T> passed in can still be consumed only once. A std::stop_token that has already requested stop results in std::errc::operation_canceled.

The application must ensure that the relevant executor, stop token, and task objects stay valid while the coroutine has not yet completed or been cancelled; do not concurrently destroy a coroutine while an executor callback is still running. For complete, directly usable handling of the real glove lifecycle, see async_runtime.cpp.