Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/gcc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ jobs:
continue-on-error: true

# Use the container for this specific version of gcc
container: ${{ matrix.toolchain.container }}
container:
image: ${{ matrix.toolchain.container }}
options: --privileged

steps:
- name: Checkout Code
Expand All @@ -49,7 +51,7 @@ jobs:
# Update for all the actions that need to install stuff
- run: |
apt-get update
apt-get install -y software-properties-common unzip
apt-get install -y software-properties-common unzip sudo

- name: Install GCC
run: |
Expand Down Expand Up @@ -90,6 +92,9 @@ jobs:
- name: CCache Stats
run: ccache --show-stats

- name: Add capabilities for scheduler
run: find build/tests -type f -executable -exec sudo setcap cap_sys_nice=eip {} \; || true

- name: Test
timeout-minutes: 2
working-directory: build
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/sonarcloud.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ jobs:
timeout-minutes: 30
run: cmake --build build/ --config Release

- name: Add capabilities for scheduler
run: find build/tests -type f -executable -exec sudo setcap cap_sys_nice=eip {} \; || true

- name: Run tests to generate coverage statistics
timeout-minutes: 10
working-directory: build
Expand Down
22 changes: 11 additions & 11 deletions docs/how-to/extending-dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,17 +178,17 @@ The Fusion Engine walks the inheritance tree and collects all extension points f

## Extension Point Summary

| Point | Purpose | Returns | Fusion Strategy |
| -------------- | ------------------------------------ | ---------- | ------------------- |
| `bind` | Register reaction at creation time | `void` | All called |
| `get` | Retrieve data for callback arguments | Any type | Tuple concatenation |
| `precondition` | Gate whether the task should run | `bool` | Logical AND |
| `pre_run` | Hook before callback execution | `void` | All called |
| `post_run` | Hook after callback execution | `void` | All called |
| `scope` | RAII lock held during execution | RAII type | All held |
| `priority` | Task scheduling priority | `int` | Maximum wins |
| `group` | Concurrency group membership | Set | Union |
| `pool` | Which thread pool to run on | Descriptor | (single value) |
| Point | Purpose | Returns | Fusion Strategy |
| -------------- | ------------------------------------ | --------------- | ------------------- |
| `bind` | Register reaction at creation time | `void` | All called |
| `get` | Retrieve data for callback arguments | Any type | Tuple concatenation |
| `precondition` | Gate whether the task should run | `bool` | Logical AND |
| `pre_run` | Hook before callback execution | `void` | All called |
| `post_run` | Hook after callback execution | `void` | All called |
| `scope` | RAII lock held during execution | RAII type | All held |
| `priority` | Task scheduling priority | `PriorityLevel` | Maximum wins |
| `group` | Concurrency group membership | Set | Union |
| `pool` | Which thread pool to run on | Descriptor | (single value) |

See [Extension Points Reference](../reference/extensions/extension-points.md) and [Fusion Engine](../reference/extensions/fusion-engine.md) for full details.

Expand Down
27 changes: 18 additions & 9 deletions docs/reference/dsl/priority.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,42 @@ Sets the scheduling priority for a reaction's tasks in the thread pool.

```cpp
on<Trigger<T>, Priority::REALTIME>()
on<Trigger<T>, Priority::HIGHEST>()
on<Trigger<T>, Priority::HIGH>()
on<Trigger<T>, Priority::NORMAL>()
on<Trigger<T>, Priority::LOW>()
on<Trigger<T>, Priority::LOWEST>()
on<Trigger<T>, Priority::IDLE>()
```

## Levels

| Level | Value | Use case |
| -------- | ----- | ------------------------------- |
| REALTIME | 1000 | Safety-critical, hard deadlines |
| HIGH | 750 | Important, time-sensitive |
| NORMAL | 500 | Default for all reactions |
| LOW | 250 | Background processing |
| IDLE | 0 | Only when nothing else to do |
| Level | Use case |
| -------- | ------------------------------- |
| REALTIME | Safety-critical, hard deadlines |
| HIGHEST | Highest non-realtime priority |
| HIGH | Important, time-sensitive |
| NORMAL | Default for all reactions |
| LOW | Background processing |
| LOWEST | Lowest non-idle priority |
| IDLE | Only when nothing else to do |

## Behavior

Higher priority tasks are dequeued before lower priority tasks when a thread becomes available.
Priority affects only queuing order — it does not preempt tasks that are already running.
Scheduling is cooperative.

If no priority is specified, `NORMAL` (500) is used.
If no priority is specified, `NORMAL` is used.

The scheduler maps the seven DSL levels onto five internal buckets.
`LOWEST` and `HIGHEST` share adjacent buckets with `LOW` and `HIGH` respectively.

Priority implements the `priority` extension point.
If multiple DSL words in the same binding set a priority, the maximum value wins.

When a task executes, the worker thread's OS priority is set via `ThreadPriority` RAII for the duration of the callback.

## Example

```cpp
Expand All @@ -49,7 +58,7 @@ on<Trigger<Background>, Priority::LOW>().then([](const Background& b) {

- Priority does **not** preempt running tasks.
A low-priority task already executing will not be interrupted by a high-priority task entering the queue.
- Default priority is `NORMAL` (500) when unspecified.
- Default priority is `NORMAL` when unspecified.
- Priority applies per-reaction, not per-reactor.

## See Also
Expand Down
24 changes: 12 additions & 12 deletions docs/reference/extensions/extension-points.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,15 @@ Each extension point is documented in detail on its own page:

## Quick Reference

| Point | Signature | Returns | Fusion Strategy |
| -------------- | ------------------------------ | ----------- | --------------------------------- |
| `bind` | `bind<DSL>(reaction, args...)` | void | Arg-distributing (FunctionFusion) |
| `get` | `get<DSL>(task)` | data | Tuple concatenation |
| `precondition` | `precondition<DSL>(task)` | bool | AND (short-circuit) |
| `priority` | `priority<DSL>(task)` | int | Maximum |
| `pool` | `pool<DSL>(task)` | descriptor | Exactly one |
| `group` | `group<DSL>(task)` | set | Set union |
| `run_inline` | `run_inline<DSL>(task)` | Inline enum | Must agree |
| `scope` | `scope<DSL>(task)` | RAII lock | All held |
| `pre_run` | `pre_run<DSL>(task)` | void | Sequential |
| `post_run` | `post_run<DSL>(task)` | void | Sequential |
| Point | Signature | Returns | Fusion Strategy |
| -------------- | ------------------------------ | ------------- | --------------------------------- |
| `bind` | `bind<DSL>(reaction, args...)` | void | Arg-distributing (FunctionFusion) |
| `get` | `get<DSL>(task)` | data | Tuple concatenation |
| `precondition` | `precondition<DSL>(task)` | bool | AND (short-circuit) |
| `priority` | `priority<DSL>(task)` | PriorityLevel | Maximum |
| `pool` | `pool<DSL>(task)` | descriptor | Exactly one |
| `group` | `group<DSL>(task)` | set | Set union |
| `run_inline` | `run_inline<DSL>(task)` | Inline enum | Must agree |
| `scope` | `scope<DSL>(task)` | RAII lock | All held |
| `pre_run` | `pre_run<DSL>(task)` | void | Sequential |
| `post_run` | `post_run<DSL>(task)` | void | Sequential |
24 changes: 15 additions & 9 deletions docs/reference/extensions/points/priority.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Returns the priority level for this task in the scheduling queue.

```cpp
template <typename DSL>
static int priority(const threading::ReactionTask& task)
static PriorityLevel priority(const threading::ReactionTask& task)
```

## Details
Expand All @@ -15,7 +15,7 @@ static int priority(const threading::ReactionTask& task)
| ----------- | ---------------------------------------------------------------------------- |
| **When** | Task creation (determines queue ordering) |
| **Thread** | The emitter's thread (the thread that called `emit()`) |
| **Returns** | `int` — higher values mean higher priority |
| **Returns** | `PriorityLevel` — higher levels are scheduled before lower ones |
| **Fusion** | Maximum value wins. If multiple words provide priority, the highest is used. |

## Context & Arguments
Expand All @@ -28,22 +28,28 @@ The returned value determines where the task sits in the scheduler's priority qu
Higher priority tasks are dequeued and executed before lower priority ones.

Priority does **not** preempt running tasks — it only affects queue ordering.
When a task runs, the OS thread priority is set via `ThreadPriority` RAII for the duration of the callback.

## Example

```cpp
struct HighPriority {
template <typename DSL>
static int priority(const threading::ReactionTask& /*task*/) {
return 750; // Same as Priority::HIGH
static PriorityLevel priority(const threading::ReactionTask& /*task*/) {
return PriorityLevel::HIGH;
}
};
```

## Built-in Words Using priority

- `Priority::REALTIME` — value 1000
- `Priority::HIGH` — value 750
- `Priority::NORMAL` — value 500 (default)
- `Priority::LOW` — value 250
- `Priority::IDLE` — value 0
- `Priority::REALTIME`
- `Priority::HIGHEST`
- `Priority::HIGH`
- `Priority::NORMAL` — default
- `Priority::LOW`
- `Priority::LOWEST`
- `Priority::IDLE`

The scheduler maps these DSL levels onto five internal buckets (`REALTIME`, `HIGH`, `NORMAL`, `LOW`, `IDLE`).
`LOWEST` shares the `LOW` bucket; `HIGHEST` shares the `HIGH` bucket.
6 changes: 3 additions & 3 deletions src/LogLevel.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ class LogLevel {
*
* @param value The value to construct the LogLevel from
*/
constexpr LogLevel(const Value& value = Value::UNKNOWN) : value(value) {};
constexpr LogLevel(const Value& value = Value::UNKNOWN) noexcept : value(value) {}

/**
* Construct a LogLevel from a string
Expand Down Expand Up @@ -168,7 +168,7 @@ class LogLevel {
*
* @return The ostream that was passed in
*/
friend std::ostream& operator<<(std::ostream& os, LogLevel level) {
friend std::ostream& operator<<(std::ostream& os, const LogLevel& level) {
return os << static_cast<std::string>(level);
}

Expand Down Expand Up @@ -208,11 +208,11 @@ class LogLevel {
friend bool operator!=(const std::string& lhs, const LogLevel& rhs) { return lhs != static_cast<std::string>(rhs); }
// clang-format on


private:
/// The stored enum value
Value value;
};

} // namespace NUClear

#endif // NUCLEAR_LOGLEVEL_HPP
157 changes: 157 additions & 0 deletions src/PriorityLevel.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
* MIT License
*
* Copyright (c) 2024 NUClear Contributors
*
* This file is part of the NUClear codebase.
* See https://github.com/Fastcode/NUClear for further info.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#ifndef NUCLEAR_PRIORITY_LEVEL_HPP
#define NUCLEAR_PRIORITY_LEVEL_HPP

#include <cstdint>
#include <ostream>
#include <string>

namespace NUClear {

class PriorityLevel {
public:
enum Value : uint8_t {
UNKNOWN = 0,
IDLE = 1,
LOWEST = 2,
LOW = 3,
NORMAL = 4,
HIGH = 5,
HIGHEST = 6,
REALTIME = 7
};

/**
* Construct a PriorityLevel from a Value
*
* @param value The value to construct the PriorityLevel from
*/
constexpr PriorityLevel(const Value& value = Value::NORMAL) noexcept : value(value) {}

/**
* Construct a PriorityLevel from a string
*
* @param level The string to construct the PriorityLevel from
*/
PriorityLevel(const std::string& level)
: value(level == "IDLE" ? Value::IDLE
: level == "LOWEST" ? Value::LOWEST
: level == "LOW" ? Value::LOW
: level == "NORMAL" ? Value::NORMAL
: level == "HIGH" ? Value::HIGH
: level == "HIGHEST" ? Value::HIGHEST
: level == "REALTIME" ? Value::REALTIME
: Value::NORMAL) {}

/**
* A call operator which will return the value of the PriorityLevel
* This can be useful in situations where the implicit conversion operators are ambiguous.
*
* @return The value of the PriorityLevel
*/
constexpr Value operator()() const {
return value;
}

/**
* A conversion operator which will return the value of the PriorityLevel
*
* @return The value of the PriorityLevel
*/
constexpr operator Value() const {
return value;
}

/**
* A conversion operator which will return the string representation of the PriorityLevel
*
* @return The string representation of the PriorityLevel
*/
operator std::string() const {
return value == Value::IDLE ? "IDLE"
: value == Value::LOWEST ? "LOWEST"
: value == Value::LOW ? "LOW"
: value == Value::NORMAL ? "NORMAL"
: value == Value::HIGH ? "HIGH"
: value == Value::HIGHEST ? "HIGHEST"
: value == Value::REALTIME ? "REALTIME"
: "UNKNOWN";
}

/**
* Stream the PriorityLevel to an ostream, it will output the string representation of the PriorityLevel
*
* @param os The ostream to output to
* @param level The PriorityLevel to output
*
* @return The ostream that was passed in
*/
friend std::ostream& operator<<(std::ostream& os, const PriorityLevel& level) {
return os << static_cast<std::string>(level);
}

// Operators to compare PriorityLevel values and PriorityLevel to Value
// clang-format off
friend constexpr bool operator<(const PriorityLevel& lhs, const PriorityLevel& rhs) { return lhs.value < rhs.value; }
friend constexpr bool operator>(const PriorityLevel& lhs, const PriorityLevel& rhs) { return lhs.value > rhs.value; }
friend constexpr bool operator<=(const PriorityLevel& lhs, const PriorityLevel& rhs) { return lhs.value <= rhs.value; }
friend constexpr bool operator>=(const PriorityLevel& lhs, const PriorityLevel& rhs) { return lhs.value >= rhs.value; }
friend constexpr bool operator==(const PriorityLevel& lhs, const PriorityLevel& rhs) { return lhs.value == rhs.value; }
friend constexpr bool operator!=(const PriorityLevel& lhs, const PriorityLevel& rhs) { return lhs.value != rhs.value; }

friend constexpr bool operator<(const PriorityLevel& lhs, const Value& rhs) { return lhs.value < rhs; }
friend constexpr bool operator>(const PriorityLevel& lhs, const Value& rhs) { return lhs.value > rhs; }
friend constexpr bool operator<=(const PriorityLevel& lhs, const Value& rhs) { return lhs.value <= rhs; }
friend constexpr bool operator>=(const PriorityLevel& lhs, const Value& rhs) { return lhs.value >= rhs; }
friend constexpr bool operator==(const PriorityLevel& lhs, const Value& rhs) { return lhs.value == rhs; }
friend constexpr bool operator!=(const PriorityLevel& lhs, const Value& rhs) { return lhs.value != rhs; }
friend constexpr bool operator<(const Value& lhs, const PriorityLevel& rhs) { return lhs < rhs.value; }
friend constexpr bool operator>(const Value& lhs, const PriorityLevel& rhs) { return lhs > rhs.value; }
friend constexpr bool operator<=(const Value& lhs, const PriorityLevel& rhs) { return lhs <= rhs.value; }
friend constexpr bool operator>=(const Value& lhs, const PriorityLevel& rhs) { return lhs >= rhs.value; }
friend constexpr bool operator==(const Value& lhs, const PriorityLevel& rhs) { return lhs == rhs.value; }
friend constexpr bool operator!=(const Value& lhs, const PriorityLevel& rhs) { return lhs != rhs.value; }

friend bool operator<(const PriorityLevel& lhs, const std::string& rhs) { return static_cast<std::string>(lhs) < rhs; }
friend bool operator>(const PriorityLevel& lhs, const std::string& rhs) { return static_cast<std::string>(lhs) > rhs; }
friend bool operator<=(const PriorityLevel& lhs, const std::string& rhs) { return static_cast<std::string>(lhs) <= rhs; }
friend bool operator>=(const PriorityLevel& lhs, const std::string& rhs) { return static_cast<std::string>(lhs) >= rhs; }
friend bool operator==(const PriorityLevel& lhs, const std::string& rhs) { return static_cast<std::string>(lhs) == rhs; }
friend bool operator!=(const PriorityLevel& lhs, const std::string& rhs) { return static_cast<std::string>(lhs) != rhs; }
friend bool operator<(const std::string& lhs, const PriorityLevel& rhs) { return lhs < static_cast<std::string>(rhs); }
friend bool operator>(const std::string& lhs, const PriorityLevel& rhs) { return lhs > static_cast<std::string>(rhs); }
friend bool operator<=(const std::string& lhs, const PriorityLevel& rhs) { return lhs <= static_cast<std::string>(rhs); }
friend bool operator>=(const std::string& lhs, const PriorityLevel& rhs) { return lhs >= static_cast<std::string>(rhs); }
friend bool operator==(const std::string& lhs, const PriorityLevel& rhs) { return lhs == static_cast<std::string>(rhs); }
friend bool operator!=(const std::string& lhs, const PriorityLevel& rhs) { return lhs != static_cast<std::string>(rhs); }
Comment thread
CMurtagh-LGTM marked this conversation as resolved.
// clang-format on

private:
/// The stored enum value
Value value;
};

} // namespace NUClear

#endif // NUCLEAR_PRIORITY_LEVEL_HPP
Loading
Loading