Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for deferred executors. #14859

Merged
merged 14 commits into from
Nov 15, 2021
Prev Previous commit
Next Next commit
Review comments.
  • Loading branch information
tzarc committed Oct 18, 2021
commit 881bd981d5a7651f39964cc35c23f86095f43b0a
8 changes: 6 additions & 2 deletions docs/custom_quantum_functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,17 +415,21 @@ QMK has the ability to execute a callback after a specified period of time, rath
All _deferred executor callbacks_ have a common function signature and look like:

```c
uint32_t my_callback(void *cb_arg) {
uint32_t my_callback(uint32_t trigger_time, void *cb_arg) {
/* do something */
bool repeat = my_deferred_functionality();
return repeat ? 500 : 0;
}
```

The argument `cb_arg` is the same argument passed into the registration function.
The first argument `trigger_time` is the intended time of execution. If other delays prevent executing at the exact trigger time, this allows for "catch-up" or even skipping intervals, depending on the required behaviour.

The second argument `cb_arg` is the same argument passed into `defer_exec()` below, and can be used to access state information from the original call context.

The return value is the number of milliseconds to use if the function should be repeated -- if the callback returns `0` then it's automatically unregistered. In the example above, a hypothetical `my_deferred_functionality()` is invoked to determine if the callback needs to be repeated -- if it does, it reschedules for a `500` millisecond delay, otherwise it informs the deferred execution background task that it's done, by returning `0`.
tzarc marked this conversation as resolved.
Show resolved Hide resolved

?> Note that the returned delay will be applied to the intended trigger time, not the time of callback invocation. This allows for generally consistent timing even in the face of occasional late execution.

#### Deferred executor registration

Once a callback has been defined, it can be scheduled using the following API:
Expand Down
23 changes: 18 additions & 5 deletions quantum/deferred_exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ static inline bool token_can_be_used(deferred_token token) {
return true;
}

static inline deferred_token allocate_token(void) {
deferred_token first = ++current_token;
while (!token_can_be_used(current_token)) {
++current_token;
if (current_token == first) {
// If we've looped back around to the first, everything is already allocated (yikes!). Need to exit with a failure.
return INVALID_DEFERRED_TOKEN;
}
}
return current_token;
}

deferred_token defer_exec(uint32_t delay_ms, deferred_exec_callback callback, void *cb_arg) {
// Ignore queueing if it's a zero-time delay, or invalid callback
if (delay_ms == 0 || !callback) {
Expand All @@ -42,10 +54,11 @@ deferred_token defer_exec(uint32_t delay_ms, deferred_exec_callback callback, vo
for (int i = 0; i < MAX_DEFERRED_EXECUTORS; ++i) {
deferred_executor_t *entry = &executors[i];
if (entry->token == INVALID_DEFERRED_TOKEN) {
// Work out the new token value
do {
++current_token;
} while (!token_can_be_used(current_token)); // Skip invalid or busy values
// Work out the new token value, dropping out if none were available
deferred_token token = allocate_token();
if(token == INVALID_DEFERRED_TOKEN) {
return false;
}

// Set up the executor table entry
entry->token = current_token;
Expand Down Expand Up @@ -117,7 +130,7 @@ void deferred_exec_task(void) {
// Check if we're supposed to execute this entry
if (entry->token != INVALID_DEFERRED_TOKEN && ((int32_t)TIMER_DIFF_32(entry->trigger_time, now)) <= 0) {
// Invoke the callback and work work out if we should be requeued
uint32_t delay_ms = entry->callback(entry->cb_arg);
uint32_t delay_ms = entry->callback(entry->trigger_time, entry->cb_arg);

// Update the trigger time if we have to repeat, otherwise clear it out
if (delay_ms > 0) {
Expand Down
5 changes: 3 additions & 2 deletions quantum/deferred_exec.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ typedef uint8_t deferred_token;
#define INVALID_DEFERRED_TOKEN 0

// Callback to execute.
// -- Parameter cb_arg: the callback argument specified when enqueueing the deferred executor
// -- Parameter trigger_time: the intended trigger time to execute the callback -- equivalent time-space as timer_read32()
// cb_arg: the callback argument specified when enqueueing the deferred executor
// -- Return value: Non-zero re-queues the callback to execute after the returned number of milliseconds. Zero cancels repeated execution.
typedef uint32_t (*deferred_exec_callback)(void *cb_arg);
typedef uint32_t (*deferred_exec_callback)(uint32_t trigger_time, void *cb_arg);

// Configures the supplied deferred executor to be executed after the required number of milliseconds.
// -- Parameter delay_ms: the number of milliseconds before executing the callback
Expand Down