Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 6fac087

Browse files
authoredJun 28, 2024··
Merge pull request #69 from samouwow/halt-running-nodes
Add halt for flow, decorator and sync action nodes.
2 parents 8b59d5c + f20c573 commit 6fac087

File tree

19 files changed

+707
-205
lines changed

19 files changed

+707
-205
lines changed
 

‎Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ description = "Workflow framework based on the behavior trees"
44
authors = ["BorisZhguchev <zhguchev@hotmail.com>"]
55
homepage = "https://github.com/besok/forester"
66
repository = "https://github.com/besok/forester"
7-
version = "0.3.2"
7+
version = "0.4.0"
88
edition = "2021"
99
license-file = "LICENSE"
1010

@@ -35,4 +35,3 @@ url = "2.4.1"
3535
[dev-dependencies]
3636
wiremock = "0.6.0"
3737
forester-http = "0.1.0"
38-

‎docs/src/falls.md

+12-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Fallback
1+
# Fallback
22

33
A Fallback ticks children sequentially until someone returns a `Success`.
44
Otherwise, if all children return `Failure`, the node returns `Failure`.
@@ -11,8 +11,8 @@ impl take_from_others()
1111
1212
root main {
1313
fallback {
14-
any_tasks() // goes farther if the first actions is failure
15-
do_it()
14+
any_tasks() // goes farther if the first actions is failure
15+
do_it()
1616
}
1717
}
1818
```
@@ -21,13 +21,13 @@ root main {
2121
- When it gets the first `tick` it switches to state `running`
2222
- When a child returns `success` it stops the execution and returns `success`
2323
- If a child returns `running`, the node returns `running` as well
24-
- If a child returns `failure`, the node proceeds to the next child
24+
- If a child returns `failure`, the node proceeds to the next child
2525
- if this is a final child, it returns `failure`
26-
- When a node is restarted, the process starts from the beginning
26+
- When a node is restarted or halted the process starts from the beginning
2727

2828
## Intention
2929
Often, it is used for making conditions.
30-
The script below emulates a simple condition that needs to do before
30+
The script below emulates a simple condition that needs to do before
3131
```f-tree
3232
cond can_take(sub:object)
3333
impl move_to(sub:object)
@@ -68,10 +68,14 @@ root main {
6868
r_fallback {
6969
needs_to_charge() // returns failure
7070
action() // returns running
71-
fin_and_save()
71+
fin_and_save()
7272
}
7373
}
7474
```
7575

7676
The node `action` returns `running` and the whole sequence returns `running`
77-
but on the next tick it starts from the node `needs_to_charge` again.
77+
but on the next tick it starts from the node `needs_to_charge` again.
78+
79+
`r_fallback` will halt the `running` child to allow a graceful shutdown if a prior child changes from `failure` to `success`. In the above example, if `needs_to_change` returned `success` on the second tick then `action` would be halted before `r_fallback` returned `success` itself.
80+
81+
Halting must be performed as quickly as possible. Note that currently only build-in flow, built-in decorator and sync action nodes are halted, async and remote actions are not.

‎docs/src/r_actions.md

+24-6
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,41 @@ There are three types of actions available at that moment:
1414

1515
## Traits
1616

17+
The action trait implements two functions, `tick()` and `halt()`.
18+
19+
The `tick()` function is the main entry point of the action and will be called whenever the node is executed.
20+
21+
The `halt()` function is used to notify a `running` action that a reactive flow node (e.g. `r_sequnce`) has changed the control flow. This means the previously `running` action won't be called again, or won't be called for a while, and so should gracefully clean up. The `halt()` function has a default no-op implementation that can be used if no clean up is necessary.
22+
23+
Actions must halt as quickly as possible, and the call to `halt()` should not block the execution.
24+
1725
### `Impl` for sync actions
1826

27+
Sync actions are the only actions that currently implement the `halt()` function.
28+
1929
```rust
2030
pub trait Impl {
2131
fn tick(&self, args: RtArgs, ctx: TreeContextRef) -> Tick;
32+
33+
fn halt(&self, args: RtArgs, ctx: TreeContextRef) -> RtOk {
34+
// Default halt is a no-op function.
35+
let _ = args;
36+
let _ = ctx;
37+
Ok(())
38+
}
2239
}
40+
2341
```
2442

25-
#### `ImplAsync` for async actions
43+
### `ImplAsync` for async actions
2644
```rust
2745
pub trait ImplAsync: Sync + Send {
2846
fn tick(&self, args: RtArgs, ctx: TreeContextRef) -> Tick;
2947
}
3048
```
3149

32-
Where `args` are the given arguments from the tree definition and invocation and `ctx`
33-
is a reference of the invocation context with `bb` and `tracer`
50+
Where `args` are the given arguments from the tree definition and invocation and `ctx`
51+
is a reference of the invocation context with `bb` and `tracer`.
3452

3553
## Mutability
3654
The actions are intentionally stateless thus they can't mutate.
@@ -43,7 +61,7 @@ fn simple_delay() {
4361
let mut forester_builder = fb("decorators/simple_delay");
4462

4563
forester_builder.register_sync_action("store", StoreData);
46-
64+
4765
}
4866
```
4967

@@ -52,7 +70,7 @@ fn simple_delay() {
5270
The async actions are executed in the multithreading environment and return the `running` tick result instantly.
5371
It does not block the execution of the tree and can be used in parallel nodes, etc.
5472

55-
On the other hand, every time when the tree is reloaded, the tick number is increased that can exceed the limit on ticks
73+
On the other hand, every time when the tree is reloaded, the tick number is increased that can exceed the limit on ticks
5674
if the system has it. Therefore, it needs to take into account (when forester runs with the limit of ticks.)
5775

5876

@@ -107,7 +125,7 @@ How to implement the client side, please see [remote action lib](./rem_action.md
107125

108126
## Default actions
109127

110-
By default, there are several implementations for http and interactions with bb are available in
128+
By default, there are several implementations for http and interactions with bb are available in
111129

112130
```rust
113131
use forester_rs::runtime::action::builtin::*;

‎docs/src/seq.md

+14-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Sequence
22

33
A Sequence node ticks all underlying nodes as long as they return `Success`.
4-
Otherwise, (when a child returns `failure` a sequence aborted)
4+
Otherwise, when a child returns `failure` the sequence is aborted.
55

66
In the language, the tree definitions and lambda invocations of this element are marked with the key word `sequence`.
77

@@ -10,9 +10,9 @@ impl store(key:string, value:string); // store a string value to a key in blackb
1010
1111
root main {
1212
sequence {
13-
store("a","1") // first tick and proceed if succeded
14-
store("b","2") // sec tick and proceed if succeded
15-
store("c","3") // thrd tick and finish if succeded
13+
store("a","1") // first tick and proceed if succeeded
14+
store("b","2") // sec tick and proceed if succeeded
15+
store("c","3") // thrd tick and finish if succeeded
1616
}
1717
}
1818
```
@@ -40,7 +40,7 @@ main ",shape=rect,color=black]
4040
- if this is a final child, it returns `success`
4141
- If a child returns `running`, the node returns `running` as well
4242
- If a child returns `failure`, the node returns `failure` as well
43-
- When a node is restarted, the process starts from the beginning
43+
- When a node is restarted or halted, the process starts from the beginning (see memory sequence for an exception)
4444

4545
## Intention
4646
Often, it is used as a straight chain of instructions
@@ -57,12 +57,12 @@ root main sequence {
5757

5858
# Subtypes
5959

60-
There are 2 subtypes that bring a few subtleties to the common process
60+
There are 2 subtypes that bring a few subtleties to the common process.
6161

6262
## Memory Sequence
6363

6464
This sequence defines in the language with the keyword `m_sequence` and has the following peculiarity:
65-
The sequence memorizes the children that are succeeded and skips them next time:
65+
The sequence memorizes the children that have succeeded and skips them next time.
6666

6767
```f-tree
6868
root main {
@@ -77,17 +77,17 @@ root main {
7777
The node `perform_action` returns `failure` and the decorator `retry` restarts `sequence`.
7878
The main difference with a sequence is an execution starts from the node `perform_action` skipping the node `store`.
7979

80-
The memory will be reset once the final action has returned `success`.
80+
This memory persists even if the `m_sequence` is halted by a reactive flow node. The memory will only be reset once the final action has returned `success`.
8181
That is, if `finish_and_save` returns `success`, the next iteration will start with `store` again.
8282

8383
## Reactive Sequence
8484

8585
This sequence defines in the language with the keyword `r_sequence` and has the following peculiarity:
86-
The sequence restarts all children if they return either failure or running:
86+
The sequence restarts all children if they return either failure or running.
8787

8888
```f-tree
8989
root main {
90-
m_sequence {
90+
r_sequence {
9191
store("key",1) // returns success
9292
perform_action() // returns running
9393
finish_and_save()
@@ -97,3 +97,7 @@ root main {
9797

9898
The node `perform_action` returns `running` and the whole sequence returns `running`
9999
but on the next tick it starts from the node `store` again.
100+
101+
`r_sequence` will halt the `running` child to allow a graceful shutdown if a prior child changes from `success` to `failure`. In the above example, if `store` returned `failure` on the second tick then `perform_action` would be halted before `r_sequence` returned `failure` itself.
102+
103+
Halting must be performed as quickly as possible. Note that currently only build-in flow, built-in decorator and sync action nodes are halted, async and remote actions are not.

‎src/runtime/action.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ use crate::runtime::context::{TreeContextRef, TreeRemoteContextRef};
66
use crate::runtime::{RtResult, RuntimeError, TickResult};
77
use std::sync::Arc;
88

9+
pub use crate::runtime::RtOk;
10+
911
pub type ActionName = String;
1012
pub type Tick = RtResult<TickResult>;
1113

1214
/// Recovers the tick depending on the result.
1315
pub fn recover(tick: Tick) -> Tick {
1416
match tick {
15-
Err(RuntimeError::RecoveryToFailure(r)) => Ok(TickResult::Failure(format!("{:?}",r))),
17+
Err(RuntimeError::RecoveryToFailure(r)) => Ok(TickResult::Failure(format!("{:?}", r))),
1618
Err(RuntimeError::BlackBoardError(r)) => Ok(TickResult::Failure(r)),
1719
other => other,
1820
}
@@ -121,6 +123,13 @@ impl Action {
121123
/// ```
122124
pub trait Impl: Sync + Send {
123125
fn tick(&self, args: RtArgs, ctx: TreeContextRef) -> Tick;
126+
127+
fn halt(&self, args: RtArgs, ctx: TreeContextRef) -> RtOk {
128+
// Default halt is a no-op function.
129+
let _ = args;
130+
let _ = ctx;
131+
Ok(())
132+
}
124133
}
125134

126135
pub trait ImplAsync: Sync + Send {

‎src/runtime/action/keeper.rs

+28-7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use crate::runtime::{RtResult, RuntimeError, TickResult};
99
use std::collections::{HashMap, HashSet};
1010
use std::sync::{Arc, Mutex};
1111

12+
use super::RtOk;
13+
1214
/// Just an action map to register and execute the actions.
1315
/// The actions are registered by the `ActionName` and the `Action` impl.
1416
pub struct ActionKeeper {
@@ -54,8 +56,8 @@ impl ActionKeeper {
5456
// the default action impl for the set = all_actions - impl_actions
5557
default: T,
5658
) -> RtResult<Self>
57-
where
58-
T: Fn() -> ActionImpl,
59+
where
60+
T: Fn() -> ActionImpl,
5961
{
6062
let mut impl_actions = impl_actions;
6163
let mut actions = HashMap::new();
@@ -114,11 +116,9 @@ impl ActionKeeper {
114116
// just to start it in the separate thread(supposedly)
115117
TaskState::Absent => {
116118
let action = action.to_owned();
117-
let tick_handle = env.runtime.spawn_blocking(move || action.tick(args, ctx));
118-
env.tasks.insert(
119-
name.to_string(),
120-
tick_handle,
121-
);
119+
let tick_handle =
120+
env.runtime.spawn_blocking(move || action.tick(args, ctx));
121+
env.tasks.insert(name.to_string(), tick_handle);
122122
Ok(TickResult::running())
123123
}
124124
TaskState::Started(handle) => {
@@ -131,6 +131,27 @@ impl ActionKeeper {
131131
}
132132
}
133133
}
134+
135+
pub fn halt(
136+
&mut self,
137+
_env: Arc<Mutex<RtEnv>>,
138+
name: &ActionName,
139+
args: RtArgs,
140+
ctx: TreeContextRef,
141+
_http_serv: &Option<ServInfo>,
142+
) -> RtOk {
143+
match self.get_mut(name)? {
144+
Action::Sync(action) => action.halt(args, ctx),
145+
Action::Remote(..) => {
146+
// Halting is not implemented for remote actions.
147+
Ok(())
148+
}
149+
Action::Async(..) => {
150+
// Halting is not implemented for async actions.
151+
Ok(())
152+
}
153+
}
154+
}
134155
}
135156

136157
fn get_port(http_serv: &Option<ServInfo>) -> Result<u16, RuntimeError> {

‎src/runtime/context.rs

+21-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::runtime::action::Tick;
22
use crate::runtime::args::{RtArgs, RtValue};
33
use crate::runtime::blackboard::{BBRef, BlackBoard};
4-
use crate::runtime::env::{RtEnvRef};
4+
use crate::runtime::env::RtEnvRef;
55
use crate::runtime::forester::flow::REASON;
66
use crate::runtime::rtree::rnode::RNodeId;
77
use crate::runtime::trimmer::{TrimmingQueue, TrimmingQueueRef};
@@ -13,6 +13,8 @@ use std::fmt::{Display, Formatter};
1313
use std::sync::Arc;
1414
use std::sync::Mutex;
1515

16+
use super::rtree::rnode::RNode;
17+
1618
pub type Timestamp = usize;
1719
pub type TracerRef = Arc<Mutex<Tracer>>;
1820

@@ -212,6 +214,17 @@ impl TreeContext {
212214
}
213215
}
214216

217+
pub(crate) fn force_to_halting_state(&mut self, id: RNodeId) -> RtResult<Option<RNodeState>> {
218+
self.ts_map.insert(id, self.curr_ts);
219+
let new_state = RNodeState::Halting(self.state_last_set(&id).args());
220+
221+
// Trace the state change with an extra indent
222+
self.tracer.lock()?.right();
223+
self.trace(NewState(id, new_state.clone()))?;
224+
self.tracer.lock()?.left();
225+
226+
Ok(self.state.insert(id, new_state))
227+
}
215228
pub(crate) fn new_state(
216229
&mut self,
217230
id: RNodeId,
@@ -245,6 +258,7 @@ pub enum RNodeState {
245258
Running(RtArgs),
246259
Success(RtArgs),
247260
Failure(RtArgs),
261+
Halting(RtArgs),
248262
}
249263

250264
impl Display for RNodeState {
@@ -262,6 +276,9 @@ impl Display for RNodeState {
262276
RNodeState::Failure(args) => {
263277
f.write_str(format!("Failure({})", args).as_str())?;
264278
}
279+
RNodeState::Halting(args) => {
280+
f.write_str(format!("Halting({})", args).as_str())?;
281+
}
265282
}
266283
Ok(())
267284
}
@@ -280,7 +297,7 @@ impl RNodeState {
280297
RNodeState::Ready(_) => Err(RuntimeError::uex(
281298
"the ready is the unexpected state for ".to_string(),
282299
)),
283-
RNodeState::Running(_) => Ok(TickResult::running()),
300+
RNodeState::Running(_) | RNodeState::Halting(_) => Ok(TickResult::running()),
284301
RNodeState::Success(_) => Ok(TickResult::success()),
285302
RNodeState::Failure(args) => {
286303
let reason = args
@@ -308,7 +325,8 @@ impl RNodeState {
308325
RNodeState::Ready(tick_args)
309326
| RNodeState::Running(tick_args)
310327
| RNodeState::Failure(tick_args)
311-
| RNodeState::Success(tick_args) => tick_args.clone(),
328+
| RNodeState::Success(tick_args)
329+
| RNodeState::Halting(tick_args) => tick_args.clone(),
312330
}
313331
}
314332
}
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Please sign in to comment.