You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs2/explanation/architecture.md
+7-4Lines changed: 7 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -129,11 +129,14 @@ graph TB
129
129
S -->|dispatch| TP3
130
130
```
131
131
132
-
The **PowerPlant** is the container for the entire system. It:
132
+
The hierarchy is straightforward:
133
133
134
-
- Holds all **Reactors** (your components)
135
-
- Reactors register **Reactions** (event handlers declared with `on<>().then()`)
136
-
- When data is emitted, the **Scheduler** creates tasks and dispatches them to **Thread Pools**
134
+
- A **PowerPlant** is the top-level container for the entire system
135
+
- A PowerPlant contains **Reactors** — your self-contained components
136
+
- Each Reactor declares **Reactions** — event handlers registered with `on<>().then()`
137
+
- When a Reaction runs, it can **emit messages** that trigger other Reactions
138
+
139
+
This creates a data-driven execution model: components don't call each other directly. Instead, they emit data, and the scheduler dispatches reactions in response.
@@ -190,9 +190,26 @@ The Fusion Engine walks the inheritance tree and collects all extension points f
190
190
191
191
See [Extension Points Reference](../reference/extensions/extension-points.md) and [Fusion Engine](../reference/extensions/fusion-engine.md) for full details.
192
192
193
+
## Thread Context
194
+
195
+
Different extension points run in different thread contexts. This is critical to understand when using `thread_local` storage or sharing state:
|`bind`| The thread that calls `on<>()`| Usually the main thread during reactor construction |
200
+
|`get`| The thread that **created** the task | Often different from the execution thread |
201
+
|`precondition`| The thread that **created** the task | Same thread as `get`|
202
+
|`pre_run`| The **execution** thread | Same thread as the callback |
203
+
|`post_run`| The **execution** thread | Same thread as the callback |
204
+
|`scope`| The **execution** thread | RAII object lives for callback duration |
205
+
206
+
!!! warning "thread_local in get vs pre_run/post_run"
207
+
Because `get` runs on the task-creation thread (not the execution thread), `thread_local` variables set in `get` will **not** be visible in `pre_run`, `post_run`, or the callback itself. If you need per-execution state, use `pre_run`/`post_run` or the `scope` extension point, which provides RAII objects that persist for the lifetime of the reaction execution.
208
+
193
209
## Tips
194
210
195
211
- Words are never instantiated — delete the constructor to make this clear.
196
-
- Use `thread_local` storage for per-execution state in `pre_run`/`post_run`.
212
+
- Use `thread_local` storage for per-execution state in `pre_run`/`post_run` only — not in `get`.
213
+
- Use the `scope` extension point if you need state that persists across the reaction execution with RAII semantics.
197
214
- Template parameters on your word become compile-time configuration (like `RateLimit<10, seconds>`).
198
215
- Test custom words the same way you test any reactor — single-threaded plant with assertions.
Copy file name to clipboardExpand all lines: docs2/how-to/logging.md
+94-1Lines changed: 94 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -135,4 +135,97 @@ public:
135
135
136
136
!!! note "Default log level"
137
137
138
-
Reactors default to `log_level = INFO`. Set it in your constructor to change the threshold.
138
+
Reactors default to `log_level = DEBUG`. Set it in your constructor to change the threshold.
139
+
140
+
## How the Log System Works
141
+
142
+
```mermaid
143
+
sequenceDiagram
144
+
participant R as Reactor
145
+
participant P as PowerPlant
146
+
participant H as Log Handler Reactions
147
+
148
+
R->>R: log<WARN>("message")
149
+
Note over R: Check: WARN >= reactor.log_level?
150
+
R->>P: Emit LogMessage
151
+
P->>H: Trigger all on<Trigger<LogMessage>> reactions
152
+
Note over H: Handler decides what to display/store
153
+
```
154
+
155
+
The log system is entirely message-driven:
156
+
157
+
1. A `log<Level>(...)` call in a reactor checks whether `Level` meets the reactor's `log_level` threshold
158
+
2. If it passes, a `LogMessage` is emitted into the system
159
+
3. Any reaction bound to `Trigger<LogMessage>` receives it
160
+
4.**Without a log handler installed, no output appears** — you must install a reactor that handles `LogMessage`
161
+
162
+
### Per-Reactor vs System Log Level
163
+
164
+
There are two filtering levels:
165
+
166
+
-**`log_level`** (per-reactor) — set in each reactor's constructor. Messages below this level are not emitted by that reactor.
167
+
-**`min_log_level`** (system-wide) — set in `NUClear::Configuration`. Messages below this level are discarded regardless of the reactor's setting.
168
+
169
+
A message is emitted only if its level meets **both** thresholds.
170
+
171
+
### The `display_level` Field
172
+
173
+
Each `LogMessage` carries a `display_level` field equal to the emitting reactor's `log_level`. This allows handlers to implement display filtering — a handler can choose to only display messages where `msg.level >= msg.display_level`, letting each reactor control its own verbosity without affecting other reactors.
174
+
175
+
## Writing Custom Log Handlers
176
+
177
+
Since log handling is just a reaction to `LogMessage`, you can write handlers that log to files, send to a network service, buffer output, or anything else:
If you need to log from code that isn't inside a reactor (e.g., a utility function called from `main()`), use the free function with full qualification:
|`name`|`string`| — | Unique name for this node on the network |
55
-
|`announce_address`|`string`| — |Multicast address for node discovery |
55
+
|`announce_address`|`string`| — |Address for node discovery announcements|
56
56
|`announce_port`|`uint16_t`| — | Port for announce messages |
57
57
|`bind_address`|`string`|`""` (all) | Local interface to bind to |
58
-
|`mtu`|`uint16_t`|`1500`| Maximum transmission unit (fragments if larger) |
58
+
|`mtu`|`uint16_t`|`1500`| Maximum transmission unit (fragments if larger) |
59
59
60
-
The default multicast address `239.226.152.162` with port `7447` is a conventional choice for LAN discovery. All nodes that share the same announce address and port will discover each other.
60
+
### Network Modes
61
+
62
+
NUClearNet supports several discovery modes depending on the `announce_address` you configure:
63
+
64
+
| Mode | Address Type | Example | Use Case |
65
+
|------|-------------|---------|----------|
66
+
|**Multicast IPv4**|`239.x.x.x`|`239.226.152.162`| LAN discovery, multiple nodes |
67
+
|**Multicast IPv6**|`ff02::x`|`ff02::1`| IPv6 LAN discovery |
68
+
|**Broadcast IPv4**|`x.x.x.255`|`192.168.1.255`| Simple LAN, all nodes on subnet |
69
+
|**Unicast IPv4/IPv6**| Specific IP |`192.168.1.50`| Point-to-point, two nodes |
70
+
71
+
#### Multicast (Default)
72
+
73
+
Multicast is the most common mode. All nodes join a multicast group and discover each other automatically:
The default multicast address `239.226.152.162` with port `7447` is a conventional choice. All nodes that share the same announce address and port will discover each other.
81
+
82
+
#### Broadcast
83
+
84
+
For simpler networks, use a broadcast address. All nodes on the subnet will receive announce messages:
@@ -144,3 +144,13 @@ Common period types: `std::chrono::milliseconds`, `std::chrono::seconds`, `std::
144
144
- The watchdog starts timing from the moment `bind` is called (when the `on<>` statement runs). Service it early if you need a grace period at startup.
145
145
- If the watchdog fires, the timer resets automatically — it will fire again after another timeout unless serviced.
146
146
- Use specific group types to avoid accidentally servicing the wrong watchdog.
147
+
- If a reactor only needs a single watchdog, you can use the reactor type itself as the group instead of creating a separate tag type:
Copy file name to clipboardExpand all lines: docs2/reference/dsl/always.md
+3Lines changed: 3 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -45,6 +45,9 @@ public:
45
45
!!! warning "CPU Spinning"
46
46
If the callback returns without blocking or sleeping, the reaction will busy-spin and consume 100% CPU on its dedicated thread. Always ensure the callback performs blocking work or includes an explicit sleep/wait.
47
47
48
+
!!! warning "Don't block forever"
49
+
The callback must periodically return or yield so that NUClear can shut down the system gracefully. If you block indefinitely (e.g., an infinite wait with no timeout), the PowerPlant cannot stop the thread when shutdown is requested. Use timeouts on blocking calls and check if the system is shutting down regularly.
50
+
48
51
- Always gets its own thread — it does not consume a thread from the default pool.
0 commit comments