diff --git a/19-action-tree-buildin-actions.md b/19-action-tree-buildin-actions.md index 823a4157709f15ba7c29c0823641539964fa54cd..6ab592ec23c2166fa8e2a9a4fc7a65056ba82bc3 100644 --- a/19-action-tree-buildin-actions.md +++ b/19-action-tree-buildin-actions.md @@ -1,31 +1,525 @@ -# ActionTree自建动作 +# ActionTree内建动作 -## 类图 +## 动作类图 ![动作树类图](images/067-action-classes.png) ## 1. 基类动作 -### Action -### EventAction -### AssembleAction -### CompositeAction - -## 2. 组合动作(枝干) -### SequenceAction -### ParalleAction -### IfElseAction -### IfThenAction -### LoopAction -### LoopIfAction -### RepeatAction -### WrapperAction -### SwitchAction - -## 3. 执行动作(子叶) -### FunctionAction -### SleepAction -### SuccAction -### FailAction -### ExecuteCmdAction -### ExecuteInThreadAction -### DummyAction +### Action(动作基类) +它是所有动作的基类。 + +它有以以5种状态: + +- `Idle`,未启动; +- `Running`,执行中; +- `Pause`,暂停中; +- `Finished`,已结束(自发的); +- `Stoped`,已被停止(被动的); + +其状态可以通过`state()`方法获得; + +如果`Action`是自发地结束,那么它会有2个结果:成功、失败; +可通过`result()`方法获取结果; + +此外,它还对外主要提供以下几个操作方法: + +- `start()`,启动动作; +- `stop()`,停止动作; +- `pause()`,暂停动作; +- `resume()`,恢复动作; +- `reset()`,重置动作; + +以及两个超时相关的方法: + +- `setTimeout()`,设置动作的超时时长; +- `resetTimeout()`,清除动作的超时时长; + +对子类提供了两个方法: + +- `finish()`,结束动作; +- `block()`,暂停动作,并抛出异常; + +定义了以下虚函数,由子类根据业务需要进行实现: + +- `onStart()`,动作启动时要执行的动作; +- `onStop()`,动作被停止时要执行的动作; +- `onPause()`,动作被暂停时要执行的动作; +- `onResume()`,动作被恢复时要执行的动作; +- `onReset()`,动作被重置时要执行的动作; +- `onFinished()`,动作结束时要执行的动作; +- `onFinal()`,动作在终止或被停止时要执行的动作; +- `onBlock()`,动作阻塞时要执行的动作。默认向上传递; +- `onTimeout()`,动作在超时时要执行的动作。默认finish(false); + +需要说明的是: + +- `onStart()`与`onStop()`通常是必须要重写的,其它则可以根据需要决定; +- 除了`onFinal()`与`onTimeout()`在重写时不需要调Action被重写的函数外,其它都需要。否则会报警告且工作异常; + +如: +``` +void MyAction::onStart() { + Action::onStart(); //! 注意:放在前面 + ... +} + +void MyAction::onStop() { + ... + Action::onStop(); //! 注意:放在后面 +} + +void MyAction::onPause() { + ... + Action::onPause(); //! 注意:放在后面 +} + +void MyAction::onResume() { + Action::onResume(); //! 注意:放在前面 + ... +} + +void MyAction::onReset() { + ... + Action::onReset(); //! 注意:放在后面 +} + +void MyAction::onBlock(const Reason &r, const Trace &t) { + ... + Action::onBlock(r, t); //! 放后面 +} + +void MyAction::onFinished(bool is_succ, const Reason &r, const Trace &t) { + ... + Action::onFinished(is_succ, r, t); //! 放后面 +} +``` +不仅是`Action`有这个要求,其它被派生的动作类,如:`EventAction`,`CompositeAction`,`AssembleAction` 等也是如此。 + +还有其它辅助方法: + +- `set_label()`,设置动作标签,以便在日志以及GraphViz中可以展示功能; +- `toJson()`,导出内部数据到JSON对象; +- `isReady()`,检查参数以及子对象是否准备好; +- `setParent()`,建立动作之间的父子关系; +- `vars()`,获取白板对象,用于多动作之间传递中间数据; + +具体如何通过继承`Action`来创建一个自己的动作类,可以参考`SleepAction`与`FunctionAction`的实现。 + +### EventAction(事件动作基类) +它继承于`Action`与`EventSubscriber`,与`EventPublisher`配合使用,是一种能够监听由`EventPublisher`发布的外部事件的动作。 +在它处于活动状态下,就能够接受`EventPublisher`发布的事件。具体表现为,当它被 start 或 resume 的时候,它就开始订阅`EventPublisher`发布的事件。当它被 stop, pause, stop 或自行 finish, block 时,就取消事件的订阅。 + +它不能独自使用,必须被派生。 +其派生类要实现`onEvent()`方法,以指明接收到事件的时候该如何处理。 +例如: + +``` +bool MyEventAction::onEvent(tbox::flow::Event e) { + if (e.id == 400012) { + ... + return true; + } + + return false; +} +``` +这里要注意返回值。返回true,表示该事件被本Action处理,不再进行传递。返回false,表示该事件被本Action忽略,继续传递给下一个EventAction处理。 + +### AssembleAction(组装动作基类) +它是组装动作的基类。 + +它定义了组装子动作的几个接口: + +- `setChild()`,设置子动作。常用于只能设置一个子动作的情况,比如:`LoopAction`,`Composite`,`WrapperAction`; +- `setChildAs()`,设置子动作为指定的角色。用于设置多个子动作,每个子动作担当不同的角色,比如:`IfElseAction`,`LoopIfAction`; +- `addChild()`,追加子动作。常用于添加多次添加同种角色的多个子动作,比如:`SequenceAction`; +- `addChildAs()`,追加子动作为指定的角色。用于设置多个子动作,但多个子动作可能会担当同一个角色,比如:`IfThenAction`; + +还提供了设置最终动作完成时要执行的动作的方法:`setFinalCallback()`。 +这个方法非常有用,常用于在整个组合动作完成(无论是正常还是异常,主动还是被动结束的)之后进行清理的动作; + +它有一个常见的派生类:`SerialAssembleAction`序列型组装动作。它还有另一个派生类:`ParallelAction`并列动作。后面详述; + +### SerialAssembleAction(序列型组装动作基类) +它继承于`AssembleAction`,是一种用于同一时刻只会有一个子动作处于执行状态的组合动作。 +下面的组合动作,除了`ParalleAction`,均是继承于它。 + +它为子类提供了四个常用方法,供子类使用: + +- `startThisAction()`,启动指定的子动作; +- `stopCurrAction()`,停止当前的子动作; +- `handleChildFinishEvent()`,处理子动作完成的事件; +- `onLastChildFinshed()`,处理最后的子动作完成的事件; + +具体使用方法,可以参考`IfElseAction`的实现。 + +### CompositeAction(组合动作基类) +它用于对一个动作树进行封装。 + +## 2. 执行动作(子叶) +### FunctionAction(函数动作) +执行指定函数,并将返回值作为结果的动作。 +该动作常用于: + +- 作为判定的条件; +- 作为执行的动作; + +它通常不独立使用,通常作为子动作添加到组合动作中。 + +使用方法: +``` +auto &loop = *ctx().loop(); + +//! 构造中指定函数 +auto func_1_action = new tbox::flow::FunctionAction(loop, [] { return true; }); + +//! 构造中不指定函数,在setFunc()中指定 +auto func_2_action = new tbox::flow::FunctionAction(loop); +func_2_action->setFunc([] { LogTag(); return true; }); + +//! 带Reason的函数 +auto func_3_action = new tbox::flow::FunctionAction(loop); +func_3_action->setFunc([] (tbox::flow::Reason &r) { r.code = 1000; return false; }); +``` + +由于`FunctionAction`自身无法体现它的功能,在导出GraphViz的时候看不出它的功能。建议在创建了`FunctionAction`对象之后,通过其`set_label()`方法设置其标签。 + +### SleepAction(延时动作) +顾明思义,延时指定时长。 + +使用示例: +``` +auto &loop = *ctx().loop(); + +//! 固定延迟时长 +auto const_sleep_action = new tbox::flow::SleepAction(loop, std::chrono::seconds(5)); + +//! 动态延迟时长,可以运时决定 +auto get_time_func = [] { return std::chrono::seconds(3); }; +auto dynamic_sleep_action = new tbox::flow::SleepAction(loop, get_time_func); +``` + +### SuccAction(固定成功动作) +直接返回成功的动作。类拟于: +``` +return true; +``` + +### FailAction(固定失败动作) +直接返回失败的动作。类拟于: +``` +return false; +``` + +### ExecuteCmdAction(执行System指令动作) +委托子线程执行system命令的动作。 +该动作常用于执行类似于 curl, wget, zip, unzip 或者执行指定脚本等过程的场景。 + +使用示例: +``` +auto &loop = *ctx().loop(); +auto &thread_executor = *ctx().thread_pool(); + +//! 在构造函数中指定命令 +auto unzip_cmd_action = new tbox::flow::ExecuteCmdAction(loop, thread_executor, "unzip /tmp/fw-20150923.zip"); + +//! 不在构造函数中指定命令,稍后通过setCmd()进行指定 +auto curl_cmd_action = new tbox::flow::ExecuteCmdAction(loop, thread_executor); +curl_cmd_action->setCmd("curl -o /home/user/downloads/file.zip https://example.com/file.zip"); +``` + +### ExecuteInThreadAction(在子线程中执行函数的动作) +委托子线程执行指定函数的动作。 +该动作常用于包装执行保存数据、进行CPU密集运算、执行第三方阻塞性函数的操作; + +使用示例: +``` +auto &loop = *ctx().loop(); +auto &thread_executor = *ctx().thread_pool(); + +auto save_data_action = new tbox::flow::ExecuteInThreadAction(loop, thread_executor); +save_data_action->setFunc( + [filepath, content] { + return tbox::util::fs::WriteStringToTextFile(filepath, content); + } +); +``` + +### DummyAction(桩动作) +该动作有点类似于调试断点,使用场景较少。 + +## 3. 组合动作(枝干) +### SequenceAction(顺序组合动作) +它有三种模式: + +- AllFinish(默认),依次执行,直至所有结束(无论成功或失败) +- AnyFail,任一失败则结束,常用于下一个动作依赖上一个动作成功的顺序动作 +- AnySucc,任一成功则结束,常用于备选方案,上一个动作不成功,则尝试后面的动作 + +它实现类似以下的动作: +``` +bool is_succ = true; +for (child_action : child_action_vec) { + is_succ = child_action(); + if ((mode == AnySucc && is_succ) || + (mode == AnyFail && !is_succ)) + break; +} +return is_succ; +``` + +使用示例: +``` +auto &loop = *ctx().loop(); + +auto seq_action = new tbox::flow::SequenceAction(loop); +auto step_1_action = new tbox::flow::FunctionAction(loop, []{ LogDbg("Hello"); return true; }); +auto step_2_action = new tbox::flow::SleepAction(loop, std::chrono::seconds(2)); +auto step_3_action = new tbox::flow::FunctionAction(loop, []{ LogDbg("cpp-tbox"); return true; }); + +seq_action->addChild(step_1_action); +seq_action->addChild(step_2_action); +seq_action->addChild(step_3_action); + +seq_action->start(); +``` +执行的结果:先输出“Hello”,5秒之后后输出“cpp-tbox”; + +### ParalleAction(并行组合动作) +与`SequenceAction`相对,它是并行动作。它同时启动其下的所有子动作。 +它有三种模式: + +- AllFinish,所有的子动作都结束了才结束; +- AnyFail,只要其中有一个失败了,就结束; +- AnySucc,只要其中有一个成功了,就结束; + +使用方式与`SequenceAction`几乎一样。 + +### IfElseAction(IfElse条件分支组合动作) +实现if-else逻辑的动作。 + +``` +if (cond_action()) { + return then_action(); +} else { + return else_action(); +} +``` +或 +``` +if (cond_action()) { + return then_action(); +} else { + return true; +} +``` +或 +``` +if (cond_action()) { + return true; +} else { + return else_action(); +} +``` + +使用示例: +``` +auto &loop = *ctx().loop(); + +auto if_else_action = new tbox::flow::IfElseAction(loop); +auto cond_action = new tbox::flow::FunctionAction(loop, []{ return true; }); +auto then_action = new tbox::flow::FunctionAction(loop, []{ LogDbg("Then"); return true; }); +auto else_action = new tbox::flow::FunctionAction(loop, []{ LogDbg("Else"); return true; }); + +if_else_action->setChildAs(cond_action, "if"); +if_else_action->setChildAs(then_action, "then"); +if_else_action->setChildAs(else_action, "else"); + +if_else_action->start(); +``` +执行的结果:输出“Then”。因为`cond_action`返回的是true。 + +### IfThenAction(IfThen条件分支组合动作) +它是`IfElseAction`的扩展,可实现连续的条件判断。 + +类似于: +``` +if (cond_1_action()) { + return then_1_action; +} else if (cond_2_action) { + return then_2_action; +} else if (cond_xx_cond) { + ... +} else { + return false; +} +``` + +使用示例: +``` +auto &loop = *ctx().loop(); + +auto if_then_action = new tbox::flow::IfThenAction(loop); +auto cond_1_action = new tbox::flow::FunctionAction(loop, []{ return false; }); +auto then_1_action = new tbox::flow::FunctionAction(loop, []{ LogDbg("Then_1"); return true; }); +auto cond_2_action = new tbox::flow::FunctionAction(loop, []{ return false; }); +auto then_2_action = new tbox::flow::FunctionAction(loop, []{ LogDbg("Then_2"); return true; }); +auto else_action = new tbox::flow::FunctionAction(loop, []{ LogDbg("Else"); return true; }); + +if_then_action->addChildAs(cond_1_action, "if"); +if_then_action->addChildAs(then_1_action, "then"); +if_then_action->addChildAs(cond_2_action, "if"); +if_then_action->addChildAs(then_2_action, "then"); +if_then_action->addChildAs(new tbox::flow::SuccAction(loop), "if"); +if_then_action->addChildAs(else_action, "true"); + +if_then_action->start(); +``` +执行的结果:输出“Else”。因为`cond_1_action`与`cond_2_action`返回的都是false。所以`then_1_action`与`then_2_action`都不会执行。 + +### LoopAction(循环组合动作) +它反复执行其子动作。 +它有三种模式: + +- Forever,永远不会结束; +- UntilFail,直到失败了才结束; +- UntilSucc,直到成功了才结束; + +类似于以下的功能: +``` +while (true) { + bool is_succ = child_action(); + if ((mode == UntilSucc && is_succ) || + (mode == UntilFail && !is_succ)) + return is_succ; +} +``` + +使用示例: +``` +int count = 3; + +auto &loop = *ctx().loop(); + +auto loop_action = new tbox::flow::LoopAction(loop, tbox::flow::LoopAction::Mode::kUntilFail); +auto exec_action = new tbox::flow::FunctionAction(loop, [&]{ LogDbg("count:%d", --count); return count > 0; }); + +loop_action->setChild(exec_action); + +loop_action->start(); +``` + +### LoopIfAction(条件循环组合动作) +该动用是对`LoopAction`的补充,是否继续循环由单独的Action来判定。 +它实现类似于: +``` +while (cond_action()) { + exec_action(); +} +``` + +使用示例: +``` +int count = 10; + +auto &loop = *ctx().loop(); + +auto loop_if_action = new tbox::flow::LoopAction(loop, tbox::flow::LoopAction::Mode::kUntilFail); +auto cond_action = new tbox::flow::FunctionAction(loop, [&]{ return --count > 0; }); +auto exec_action = new tbox::flow::FunctionAction(loop, [&]{ LogDbg("count:%d", count); return true; }); + +loop_if_action->setChildAs(cond_action, "if"); +loop_if_action->setChildAs(exec_action, "exec"); + +loop_if_action->start(); +``` + +### RepeatAction(重复组合动作) +实现重复指定次数的动作,是对`LoopAction`的扩展。 + +它有三种模式: + +- NoBreak,不提前结束; +- BreakFail,如果失败,则提前结束; +- BreakSucc,如果成功,则提前结束。常用于动作重试,并指定了尝试次数; + +实现类似以下功能: +``` +for (int i = 0; i < times; ++i) { + bool is_succ = child_action(); + if ((mode == BreakSucc && is_succ) || + (mode == BreakFail && !is_succ)) + return is_succ; +} +return true; +``` + +使用示例: +``` +auto &loop = *ctx().loop(); + +auto repeat_action = new tbox::flow::LoopAction(loop, 3, tbox::flow::LoopAction::Mode::kBreakSucc); +auto exec_action = new tbox::flow::FunctionAction(loop, [&]{ LogDbg("Repeat"); return false; }); + +repeat_action->setChild(exec_action); + +repeat_action->start(); +``` +执行结果:打印3次"Repeat"。 + +### WrapperAction(结果包装组合动作) +如果不希望某个动作的结果影响到流程执行,可使用`WrapperAction`对它进行包装。 +它有四种模式: + +- Normal,透传。即子动作返回什么,它就返回什么; +- Invert,取反,即子动作返回成功,它就返回失败。子动作返回失败,它就返回成功; +- AlwaySucc,固定为成功,不管子动作返回什么; +- AlwayFail,固定为失败,不管子动作返回什么; + +使用示例: +``` +auto &loop = *ctx().loop(); + +auto your_action = new YourAction(loop); + +//! 对your_action的结果取反 +auto invert_wrapper_action = new tbox::flow::WrapperAction(loop, your_action, tbox::flow::WrapperAction::Mode::kInvert); +``` + +### SwitchAction(Switch分支组合动作) +用于实现switch-case逻辑。 +类似于: +``` +switch (cond_action()) { + case xxx: return xxx_action(); + case yyy: return yyy_action(); + default: return default_action(); +} +return false; +``` + +使用示例: +``` +auto &loop = *ctx().loop(); + +auto switch_action = new tbox::flow::SwitchAction(loop); + +auto cond_action = new tbox::flow::FunctionAction( + [](tbox::flow::Action::Reason &r) { + r.message = "case:B"; + return true; + } +); + +auto a_action = new tbox::flow::FunctionAction(loop, []{ LogDbg("A"); return true; }); +auto b_action = new tbox::flow::FunctionAction(loop, []{ LogDbg("B"); return true; }); +auto default_action = new tbox::flow::FunctionAction(loop, []{ LogDbg("Default"); return true; }); + +switch_action->setChildAs(cond_action, "switch"); +switch_action->setChildAs(a_action, "case:A"); +switch_action->setChildAs(b_action, "case:B"); +switch_action->setChildAs(default_action, "default"); + +switch_action->start(); +``` +执行结果:打印"B",因为`cond_action`的`FunctionAction`函数中将r.message设置成了"case:B",与`b_action`匹配。