All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.wjybxx.btree.Task Maven / Gradle / Ivy

/*
 * Copyright 2024 wjybxx([email protected])
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.wjybxx.btree;

import cn.wjybxx.base.MathCommon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;

/**
 * 

取消

* 1.取消默认是协作式的,依赖于任务自身检查;如果期望更及时的响应取消信号,则需要注册注册监听器。 * 2.通常在执行外部代码后都应该检测. * 3.一般而言,不管理上下文的节点在子节点取消时都应该取消自己(因为是同一个CancelToken) * 4.Task类默认只在心跳方法中检测取消信号,任何的回调和事件方法中都由用户自身检测。 * 5.Task在运行期间,最多只应该添加一次监听。 * *

心跳+事件驱动

* 1.心跳为主,事件为辅。 * 2.心跳不是事件!心跳自顶向下驱动,事件则无规律。 * * @param 黑板的类型 * @author wjybxx * date - 2023/11/25 */ public abstract class Task implements ICancelTokenListener { public static final Logger logger = LoggerFactory.getLogger(Task.class); /** 低5位记录Task重写了哪些方法 */ private static final int MASK_OVERRIDES = 31; /** 低[6~10]位记录前一次的运行结果,范围 [0, 31] */ private static final int MASK_PREV_STATUS = 31 << 5; /** 前一次运行结果的存储偏移量 */ private static final int OFFSET_PREV_STATUS = 5; private static final int MASK_INHERITED_BLACKBOARD = 1 << 10; private static final int MASK_INHERITED_PROPS = 1 << 11; private static final int MASK_INHERITED_CANCEL_TOKEN = 1 << 12; private static final int MASK_ENTER_EXECUTE = 1 << 13; private static final int MASK_EXECUTING = 1 << 14; private static final int MASK_STOP_EXIT = 1 << 15; private static final int MASK_STILLBORN = 1 << 16; static final int MASK_DISABLE_NOTIFY = 1 << 17; static final int MASK_CHECKING_GUARD = 1 << 18; private static final int MASK_NOT_ACTIVE_SELF = 1 << 19; private static final int MASK_NOT_ACTIVE_IN_HIERARCHY = 1 << 20; private static final int MASK_REGISTERED_LISTENER = 1 << 21; public static final int MASK_SLOW_START = 1 << 24; public static final int MASK_AUTO_RESET_CHILDREN = 1 << 25; public static final int MASK_MANUAL_CHECK_CANCEL = 1 << 26; public static final int MASK_AUTO_LISTEN_CANCEL = 1 << 27; public static final int MASK_CANCEL_TOKEN_PER_CHILD = 1 << 28; public static final int MASK_BLACKBOARD_PER_CHILD = 1 << 29; public static final int MASK_INVERTED_GUARD = 1 << 30; public static final int MASK_BREAK_INLINE = 1 << 31; /** 高8位为流程控制特征值(对外开放) */ public static final int MASK_CONTROL_FLOW_OPTIONS = (-1) << 24; /** 条件节点的基础选项 */ private static final int MASK_GUARD_BASE_OPTIONS = MASK_CHECKING_GUARD | MASK_MANUAL_CHECK_CANCEL; /** enter前相关options */ private static final int MASK_BEFORE_ENTER_OPTIONS = MASK_AUTO_LISTEN_CANCEL | MASK_AUTO_RESET_CHILDREN; /** 任务树的入口(缓存以避免递归查找) */ transient TaskEntry taskEntry; /** 任务的控制节点,通常是Task的Parent节点 */ transient Task control; /** * 任务运行时依赖的黑板(主要上下文) * 1.每个任务可有独立的黑板(数据); * 2.运行时不能为null; * 3.如果是自动继承的,exit后自动删除;如果是Control赋值的,则由control删除。 */ protected transient T blackboard; /** * 共享属性(配置上下文) * 1.用于解决【数据和行为分离】架构下的配置需求,主要解决策划的配置问题,减少维护工作量。 * 2.共享属性应该在运行前赋值,不应该也不能被序列化。 * 3.共享属性应该是只读的、可共享的,因为它是配置。 * 4.如果是自动继承的,exit后自动删除;如果是Control赋值的,则由control删除。 *

* 举个例子:部分项目的角色技能是有等级的,且数值不是根据等级计算的,而是一级一级配置的, * 那么每一级的数值就是props,技能流程就是task。 */ protected transient Object sharedProps; /** * 取消令牌(取消上下文) * 1.每个任务可有独立的取消信号; * 2.运行时不能为null; * 3.如果是自动继承的,exit后自动删除;如果是Control赋值的,则由control删除。 */ protected transient CancelToken cancelToken; /** * Control为管理子节点存储在子节点上的数据 * 1.避免额外映射,提高性能和易用性 * 2.entry的逻辑control是用户,因此也可以存储用户的数据 * 3.该属性不自动继承,不属于运行上下文。 */ private transient Object controlData; /** 任务的状态 -- {@link TaskStatus},使用int以支持用户返回更详细的错误码 */ private transient int status; /** 任务运行时的控制信息(bits) -- 每次运行时会重置为0 */ private transient int ctl; /** 启动时的帧号 -- 每次运行时会重置,仅保留override信息 */ private transient int enterFrame; /** 结束时的帧号 -- 每次运行时重置为0 */ private transient int exitFrame; /** 重入Id,只增不减 -- 用于事件驱动下检测冲突(递归);reset时不重置,甚至也增加 */ private transient short reentryId; /** * 任务绑定的前置条件(precondition太长...) * 编程经验告诉我们:前置条件和行为是由控制节点组合的,而不是行为的属性。 * 但由于Task只能有一个Control,因此将前置条件存储在Task可避免额外的映射,从而可提高查询性能和易用性; * 另外,将前置条件存储在Task上,可以让行为树的结构更加清晰。 */ private Task guard; /** * 任务的自定义标识 * 1.对任务进行标记是一个常见的需求,我们将其定义在顶层以简化使用 * 2.在运行期间不应该变动 * 3.高8位为流程控制特征值,会在任务运行前拷贝到ctl -- 以支持在编辑器导中指定Task的运行特征。 * 4.用户可以通过flags来识别child,以判断是否处理child的结果 */ protected int flags; public Task() { ctl = TaskOverrides.maskOfTask(getClass()); } // region getter/setter public final TaskEntry getTaskEntry() { return taskEntry; } public final Task getControl() { return control; } public final T getBlackboard() { return blackboard; } public final void setBlackboard(T blackboard) { this.blackboard = blackboard; } public final CancelToken getCancelToken() { return cancelToken; } public final void setCancelToken(CancelToken cancelToken) { this.cancelToken = cancelToken; } public final Object getControlData() { return controlData; } public final void setControlData(Object controlData) { this.controlData = controlData; } public final Object getSharedProps() { return sharedProps; } public final void setSharedProps(Object sharedProps) { this.sharedProps = sharedProps; } public final int getEnterFrame() { return enterFrame; } public final int getExitFrame() { return exitFrame; } /** 慎重调用 */ public final void setEnterFrame(int enterFrame) { this.enterFrame = enterFrame; } /** 慎重调用 */ public final void setExitFrame(int exitFrame) { this.exitFrame = exitFrame; } // endregion // region status /** 获取原始的状态码 */ public final int getStatus() { return status; } /** 获取归一化后的状态码,所有的失败码都转换为{@link TaskStatus#ERROR} */ public final int getNormalizedStatus() { return Math.min(status, TaskStatus.ERROR); } /** 任务是否正在运行 */ public final boolean isRunning() { return status == TaskStatus.RUNNING; } /** 任务是否已完成(成功、失败、取消) */ public final boolean isCompleted() { return status >= TaskStatus.SUCCESS; } /** 任务是否已成功 */ public final boolean isSucceeded() { return status == TaskStatus.SUCCESS; } /** 任务是否已被取消 */ public final boolean isCancelled() { return status == TaskStatus.CANCELLED; } /** 任务是否已失败 */ public final boolean isFailed() { return status > TaskStatus.CANCELLED; } /** 任务是否已失败或被取消 */ public final boolean isFailedOrCancelled() { return status >= TaskStatus.CANCELLED; } // endregion // region context /** 获取行为树绑定的实体 -- 最好让Entity也在黑板中 */ public Object getEntity() { if (taskEntry == null) { throw new IllegalStateException("This task has never run"); } return taskEntry.getEntity(); } /** * 运行的帧数 * 1.任务如果在首次{@link #execute()}的时候就进入完成状态,那么运行帧数0 * 2.运行帧数是非常重要的统计属性,值得我们定义在顶层. */ public final int getRunFrames() { if (status == TaskStatus.RUNNING) { return taskEntry.getCurFrame() - enterFrame; } // 不测试taskEntry,是因为child可能在运行后被删除 return exitFrame - enterFrame; } /** * 获取任务前一次的执行结果 * 1.取值范围[0,63] -- 其实只要能区分成功失败就够; * 2.这并不是一个运行时必须的属性,而是为Debug和UI视图服务的; */ public final int getPrevStatus() { return (ctl & MASK_PREV_STATUS) >> OFFSET_PREV_STATUS; } public final void setPrevStatus(int prevStatus) { prevStatus = MathCommon.clamp(prevStatus, 0, TaskStatus.MAX_PREV_STATUS); ctl |= (prevStatus << OFFSET_PREV_STATUS); } // endregion // region core /** * 该方法用于初始化对象。 * 1.不命名为init,是因为init通常让人觉得只调用一次。 * 2.该方法不可以使自身进入完成状态。 */ protected void beforeEnter() { } /** * 该方法在Task进入运行状态时执行 * 1.数据初始化需要放在{@link #beforeEnter()}中,避免执行逻辑时对象未初始化完成。 * 2.如果要初始化子节点,也放到{@link #beforeEnter()}方法; * 3.允许更新自己为完成状态 * * @param reentryId 用于判断父类是否使任务进入了完成状态;虽然也可先捕获再调用超类方法,但传入会方便许多。 */ protected void enter(int reentryId) { } /** * Task的心跳方法,在Task进入完成状态之前会反复执行。 * 1.运行中可通过{@link #setSuccess()}、{@link #setFailed(int)}、{@link #setCancelled()}将自己更新为完成状态。 * 2.如果不想和{@link #enter(int)}同步执行,可通过{@link #setSlowStart(boolean)}实现。 * 3.不建议直接调用该方法,而是通过模板方法{@link #template_execute(boolean)}运行。 */ protected abstract void execute(); /** * 该方法在Task进入完成状态时执行 * 1.该方法与{@link #enter(int)}对应,通常用于停止{@link #enter(int)}中启动的逻辑。 * 2.该方法也用于清理部分运行时产生的临时数据。 * 3.一定记得取消注册的各种监听器。 */ protected void exit() { } /** 设置为运行成功 */ public final void setSuccess() { assert this.status == TaskStatus.RUNNING; this.status = TaskStatus.SUCCESS; template_exit(); if ((ctl & MASK_DISABLE_NOTIFY) == 0 && control != null) { control.onChildCompleted(this); } } /** 设置为取消 */ public final void setCancelled() { setCompleted(TaskStatus.CANCELLED, false); } /** 设置为执行失败 */ public final void setFailed(int status) { if (status < TaskStatus.ERROR) { throw new IllegalArgumentException("status " + status); } setCompleted(status, false); } /** * 设置为前置条件测试失败 * 1.该方法仅适用于control测试child的guard失败,令child在未运行的情况下直接失败的情况。 * 2.对于运行中的child,如果发现child的guard失败,不能继续运行,应当取消子节点的执行(stop)。 * * @param control 由于task未运行,其control可能尚未赋值,因此要传入 * @param isHook 是否是钩子任务,钩子任务则禁用回调 */ public final void setGuardFailed(Task control, boolean isHook) { assert status != TaskStatus.RUNNING; setCtlBit(MASK_DISABLE_NOTIFY, isHook); // 需要覆盖旧值 setControl(control); setCompleted(TaskStatus.GUARD_FAILED, false); } /** 设置为完成 -- 通常用于通过子节点的结果设置自己 */ public final void setCompleted(int status, boolean fromChild) { if (status < TaskStatus.SUCCESS) throw new IllegalArgumentException(); if (status == TaskStatus.GUARD_FAILED && fromChild) { status = TaskStatus.ERROR; // GUARD_FAILED 不能向上传播 } final int prevStatus = this.status; if (prevStatus == TaskStatus.RUNNING) { if (status == TaskStatus.GUARD_FAILED) { throw new IllegalArgumentException("Running task cant fail with 'GUARD_FAILED'"); } this.status = status; template_exit(); } else { // 未调用Enter和Exit,需要补偿 -- 保留当前的ctl会更好 setPrevStatus(prevStatus); this.enterFrame = exitFrame; this.reentryId++; ctl |= MASK_STILLBORN; this.status = status; } if ((ctl & MASK_DISABLE_NOTIFY) == 0 && control != null) { control.onChildCompleted(this); } } /** * 子节点还需要继续运行 * 1.该方法在运行期间可能被多次调用(非启动时调用表示修复内联) * 2.该方法不应该触发状态迁移,即不应该使自己进入完成状态 * * @param starting 是否是启动时调用,即首次调用 */ protected abstract void onChildRunning(Task child, boolean starting); /** * 子节点进入完成状态 * 1.避免方法数太多,实现类测试task的status即可 * 2.{@link #getNormalizedStatus()}有助于switch测试 * 3.task可能是取消状态,甚至可能没运行过直接失败(前置条件失败) * 4.钩子任务和guard不会调用该方法 * 5.同一子节点连续通知的情况下,completed的逻辑应当覆盖{@link #onChildRunning(Task, boolean)}的影响。 * 6.任何的回调和事件方法中都由用户自身检测取消信号 */ protected abstract void onChildCompleted(Task child); /** * Task收到外部事件 * * @see #onEventImpl(Object) */ public final void onEvent(@Nonnull Object event) { if ((ctl & TaskOverrides.MASK_CAN_HANDLE_EVENT) == 0) { if (status == TaskStatus.RUNNING) { onEventImpl(event); } } else if (canHandleEvent(event)) { onEventImpl(event); } } /** * 该方法用于测试自己的状态和事件数据 * (任何事件处理中,都用用户自身检测取消信号) *

* 如果通过条件Task来实现事件过滤,那么通常的写法如下: *

     *     blackboard.set("event", event); // task通过黑板获取事件对象
     *     try {
     *         return template_checkGuard(eventFilter);
     *     } finally {
     *         blackboard.remove("event");
     *     }
     * 
* ps: 如果想支持编辑器中测试事件属性,event通常需要实现为KV结构。 */ public boolean canHandleEvent(@Nonnull Object event) { return status == TaskStatus.RUNNING; } /** * 对于控制节点,通常将事件派发给约定的子节点或钩子节点。 * 对于叶子节点,通常自身处理事件。 * 注意: * 1.转发事件时应该调用子节点的{@link #onEvent(Object)}方法 * 2.在AI这样的领域中,建议将事件转化为信息存储在Task或黑板中,而不是尝试立即做出反应。 */ protected abstract void onEventImpl(@Nonnull Object event); /** * 取消令牌的回调方法 * 注意:如果未启动自动监听,手动监听时也建议绑定到该方法 */ @Override public void onCancelRequested(CancelToken cancelToken) { if (isRunning()) setCompleted(TaskStatus.CANCELLED, false); } /** 强制停止任务 */ public final void stop() { stop(TaskStatus.CANCELLED); } /** * 强制停止任务 * 1.只应该由Control调用,因此不会通知Control * 2.未完成的任务默认会进入Cancelled状态 * 3.不命名为cancel,否则容易误用;我们设计的cancel是协作式的,可通过{@link #cancelToken}发出请求请求。 * * @param result 取消任务时的分配的结果,0默认为取消 -- 更好的支持FSM */ public final void stop(int result) { if (result == 0) { result = TaskStatus.CANCELLED; } // 被显式调用stop的task不能通知父节点,只要任务执行过就需要标记 if (status == TaskStatus.RUNNING) { ctl |= MASK_DISABLE_NOTIFY; setCompleted(result, false); } } /** * 停止所有运行中的子节点 * 1.该方法在自身的exit之前调用 * 2.如果有特殊的子节点(钩子任务),也需要在这里停止 * 3.该方法不采用visitor实现,是因为停止任务可能有特殊的时序。 * 4.如果子节点不是按原始序启动的,务必要重写该方法。 */ protected void stopRunningChildren() { // 停止child时默认逆序停止;一般而言都是后面的子节点依赖前面的子节点 for (int idx = getChildCount() - 1; idx >= 0; idx--) { final Task child = getChild(idx); if (child.status == TaskStatus.RUNNING) { child.stop(TaskStatus.CANCELLED); } } } /** * 重置任务以便重新启动(清理运行产生的所有临时数据) *

* 1. 和exit一样,清理的是运行时产生的临时数据,而不是所有数据;不过该方法是比exit更彻底的清理。 * 2. 钩子任务也应当被重置。 * 3. 与{@link #beforeEnter()}相同,重写方法时,应先执行父类逻辑,再重置自身属性。 * 4. 有临时数据的Task都应该重写该方法,行为树通常是需要反复执行的。 */ public void resetForRestart() { if (status == TaskStatus.NEW) { return; } if (status == TaskStatus.RUNNING) { stop(TaskStatus.CANCELLED); } resetChildrenForRestart(); if (guard != null) { guard.resetForRestart(); } if (this != taskEntry) { unsetControl(); } status = 0; ctl &= MASK_OVERRIDES; // 保留Overrides信息 enterFrame = 0; exitFrame = 0; reentryId++; // 上下文变动,和之前的执行分开 } /** 重置所有的子节点 */ public final void resetChildrenForRestart() { // 由于所有的子节点都已停止,因此重置顺序无影响 visitChildren(TaskVisitors.resetForRestart(), null); } /** 当前节点自身是否为active状态 */ public final boolean isActiveSelf() { return (ctl & MASK_NOT_ACTIVE_SELF) == 0; } /** 当前节点及其所有父节点是否都为active状态 */ public final boolean isActiveInHierarchy() { return (ctl & MASK_NOT_ACTIVE_IN_HIERARCHY) == 0; } /** * 修改节点的active状态 * 注意: * 1.active为false表示可以不执行心跳逻辑{@link #execute()} * 2.只有停止Execute而不影响逻辑的场景,才可能需要该特性。比如:等待事件发生。 * 3.如果等待条件或事件的过程中需要响应超时,通常需要通过定时任务唤醒。 * 4.如果Task处于非运行状态,该属性在运行时被重置。 * 5.该属性对条件检查无效。 * 6.为控制复杂度,暂不打算支持activeChanged事件。 */ public final void setActive(boolean value) { if (isActiveSelf() == value) { return; } setCtlBit(MASK_NOT_ACTIVE_SELF, !value); // 取反 refreshActiveInHierarchy(); } /** * 当节点在层次结构中的Active状态发生变化时调用 * 1.该方法只在Task处于运行状态下调用。 * 2.该方法不应该产生状态迁移,即不应该使Task进入完成状态。 * 3.该方法主要用于暂停关联的外部逻辑,如停止外部计时器。 * 4.重写该方法通常应该重写enter方法,在enter方法中处理未激活的情况。 */ protected void onActiveInHierarchyChanged() { } /** * 刷新Task在层次结构中的active状态 */ public final void refreshActiveInHierarchy() { boolean newState = isActiveSelf() && (control == null || control.isActiveInHierarchy()); if (newState == isActiveInHierarchy()) { return; } setCtlBit(MASK_NOT_ACTIVE_IN_HIERARCHY, !newState); // 取反 if (status == TaskStatus.RUNNING) { onActiveInHierarchyChanged(); visitChildren(TaskVisitors.refreshActive(), null); } } // endregion // region execute-util /** 注册取消信号监听器,任务在退出时将自动触发删除 */ public final void registerCancelListener() { cancelToken.addListener(this); ctl |= MASK_REGISTERED_LISTENER; } /** * 检查取消 * * @param rid 重入id;方法保存的局部变量 * @return 任务是否已进入完成状态;如果返回true,调用者应立即退出 * @see #isExited(int) */ public final boolean checkCancel(int rid) { if (rid != this.reentryId) { // exit return true; } if (cancelToken.isCancelRequested()) { // 这里是手动检查 setCompleted(TaskStatus.CANCELLED, false); return true; } return false; } /** * 获取重入id * 1.重入id用于解决事件(或外部逻辑)可能使当前Task进入完成状态的问题。 * 2.如果执行的外部逻辑可能触发状态切换,在执行外部逻辑前最好捕获重入id,再执行外部逻辑后以检查是否可进行运行。 */ public final int getReentryId() { return reentryId; // 勿修改返回值类型,以便以后扩展 } /** * 重入id对应的任务是否已退出,即:是否已执行{@link #exit()}方法。 * 1.如果已退出,当前逻辑应该立即退出。 * 2.通常在执行外部代码后都应该检测,eg:运行子节点,派发事件,执行用户钩子... * 3.通常循环体中的代码应该调用{@link #checkCancel(int)} * 4.也可以用于检测是否已重新启动 * * @param rid 重入id;方法保存的局部变量 * @return 重入id对应的任务是否已退出 */ public final boolean isExited(int rid) { return rid != this.reentryId; } /** * 任务是否未启动就失败了。常见原因: * 1. 前置条件失败 * 2. 任务开始前检测到取消 */ public final boolean isStillborn() { return (ctl & MASK_STILLBORN) != 0; } /** 当前是否是条件检查上下文 */ public final boolean isCheckingGuard() { return (ctl & MASK_CHECKING_GUARD) != 0; } /** 是否可以延迟启动 */ private static boolean checkSlowStart(int ctl) { // 条件节点不可延迟启动;其它情况下只有用户请求延迟启动的Task才可延迟启动 return (ctl & (MASK_CHECKING_GUARD | MASK_SLOW_START)) == MASK_SLOW_START; } // endregion // region options /** * 告知模板方法否将{@link #enter(int)}和{@link #execute()}方法分开执行。 * 1.默认值由{@link #flags}中的信息指定,默认不分开执行 * 2.要覆盖默认值应当在{@link #beforeEnter()}和{@link #enter(int)}方法中调用 * 3.该属性运行期间不应该调整,调整也无效 */ public final void setSlowStart(boolean value) { setCtlBit(MASK_SLOW_START, value); } public final boolean isSlowStart() { return (ctl & MASK_SLOW_START) != 0; } /** * 告知模板方法是否在{@link #enter(int)}前自动调用{@link #resetChildrenForRestart()} * 1.默认值由{@link #flags}中的信息指定,默认不启用 * 2.要覆盖默认值应当在{@link #beforeEnter()}方法中调用 * 3.部分任务可能在调用{@link #resetForRestart()}之前不会再次运行,因此需要该特性 */ public final void setAutoResetChildren(boolean value) { setCtlBit(MASK_AUTO_RESET_CHILDREN, value); } public final boolean isAutoResetChildren() { return (ctl & MASK_AUTO_RESET_CHILDREN) != 0; } /** * 告知模板方法是否手动检测取消 * 1.默认值由{@link #flags}中的信息指定,默认不禁用 * 2.是否检测取消信号是一个动态的属性,可随时更改 -- 因此不要轻易缓存。 */ public final void setManualCheckCancel(boolean value) { setCtlBit(MASK_MANUAL_CHECK_CANCEL, value); } public final boolean isManualCheckCancel() { return (ctl & MASK_MANUAL_CHECK_CANCEL) != 0; } private boolean isAutoCheckCancel() { return (ctl & MASK_MANUAL_CHECK_CANCEL) == 0; // 执行频率很高,不调用封装方法 } /** * 告知模板方法是否自动监听取消事件 * 1.默认值由{@link #flags}中的信息指定,默认不自动监听!自动监听有较大的开销,绝大多数业务只需要在Entry监听。 * 2.要覆盖默认值应当在{@link #beforeEnter()}方法中调用 */ public final void setAutoListenCancel(boolean value) { setCtlBit(MASK_AUTO_LISTEN_CANCEL, value); } public final boolean isAutoListenCancel() { return (ctl & MASK_AUTO_LISTEN_CANCEL) != 0; } /** * 是否每个child一个独立的取消令牌 * 1.默认值由{@link #flags}中的信息指定,默认false * 2.要覆盖默认值应当在{@link #beforeEnter()}方法中调用 * 3.该值是否生效取决于控制节点的实现,这里只是提供配置接口。 */ public final void setCancelTokenPerChild(boolean value) { setCtlBit(MASK_CANCEL_TOKEN_PER_CHILD, value); } public final boolean isCancelTokenPerChild() { return (ctl & MASK_CANCEL_TOKEN_PER_CHILD) != 0; } /** * 是否每个child一个独立的黑板(常见于栈式黑板) * 1.默认值由{@link #flags}中的信息指定,默认false * 2.该值是否生效取决于控制节点的实现,这里只是提供配置接口。 */ public final void setBlackboardPerChild(boolean value) { setCtlBit(MASK_BLACKBOARD_PER_CHILD, value); } public final boolean isBlackboardPerChild() { return (ctl & MASK_BLACKBOARD_PER_CHILD) != 0; } /** * 当task作为guard节点时,是否取反(减少栈深度) -- 避免套用{@link cn.wjybxx.btree.decorator.Inverter}节点 * 1.默认值由{@link #flags}中的信息指定,默认不禁用(即默认延迟通知) * 2.要覆盖默认值应当在{@link #beforeEnter()}方法中调用 */ public final void setInvertedGuard(boolean value) { setCtlBit(MASK_INVERTED_GUARD, value); } public final boolean isInvertedGuard() { return (ctl & MASK_INVERTED_GUARD) != 0; } /** * 当Task可以被内联时是否打破内联 * 1.默认值由{@link #flags}中的信息指定,默认不禁用(即默认延迟通知) * 2.要覆盖默认值应当在{@link #beforeEnter()}方法中调用 * 3.它的作用是避免被内联子节点进入完成状态时产生【过长的恢复路径】 */ public final void setBreakInline(boolean value) { setCtlBit(MASK_BREAK_INLINE, value); } public final boolean isBreakInline() { return (ctl & MASK_BREAK_INLINE) != 0; } // endregion // region 模板方法 /** start方法不暴露,否则以后难以改动 */ final void template_start(final Task control, int initMask) { assert status != TaskStatus.RUNNING; // 初始化基础上下文后才可以检测取消 if (control != null) { initMask |= captureContext(control); initMask |= (control.ctl & MASK_NOT_ACTIVE_IN_HIERARCHY); } initMask |= (ctl & MASK_OVERRIDES); // 方法实现bits initMask |= (flags & MASK_CONTROL_FLOW_OPTIONS); // 控制流bits ctl = initMask; final CancelToken cancelToken = this.cancelToken; if (cancelToken.isCancelRequested()) { // 胎死腹中 releaseContext(); setCompleted(TaskStatus.CANCELLED, false); return; } final int prevStatus = Math.min(TaskStatus.MAX_PREV_STATUS, this.status); initMask |= (prevStatus << OFFSET_PREV_STATUS); ctl = initMask; status = TaskStatus.RUNNING; // 先更新为running状态,以避免执行过程中外部查询task的状态时仍处于上一次的结束status enterFrame = exitFrame = taskEntry.getCurFrame(); final int reentryId = ++this.reentryId; // 和上次执行的exit分开 // beforeEnter if ((initMask & TaskOverrides.MASK_BEFORE_ENTER) != 0) { beforeEnter(); // 这里用户可能修改控制流标记 } if ((ctl & MASK_BEFORE_ENTER_OPTIONS) != 0) { if (isAutoResetChildren()) { resetChildrenForRestart(); } if (isAutoListenCancel()) { cancelToken.addListener(this); ctl |= MASK_REGISTERED_LISTENER; } } // enter if ((initMask & TaskOverrides.MASK_ENTER) != 0) { enter(reentryId); // enter可能导致结束和取消信号,也可能修改ctl if (reentryId != this.reentryId) { return; } if (cancelToken.isCancelRequested() && isAutoCheckCancel()) { setCompleted(TaskStatus.CANCELLED, false); return; } } if (checkSlowStart(ctl)) { // 需要使用最新的ctl if ((initMask & MASK_DISABLE_NOTIFY) == 0 && control != null) { control.onChildRunning(this, true); } return; } // execute execute(); if (reentryId != this.reentryId) { return; } if (cancelToken.isCancelRequested() && isAutoCheckCancel()) { setCompleted(TaskStatus.CANCELLED, false); return; } if ((initMask & MASK_DISABLE_NOTIFY) == 0 && control != null) { control.onChildRunning(this, true); } } /** * execute模板方法 * (通过参数的方式,有助于我们统一代码,也简化子类实现;同时避免遗漏) * * @param fromControl 是否由父节点调用(判断是否是心跳) */ public final void template_execute(boolean fromControl) { assert status == TaskStatus.RUNNING; // 事件驱动下无法精确判断是否是心跳走到这里,因此只要处于非激活状态就拒绝父节点请求(装死) if (fromControl && (ctl & MASK_NOT_ACTIVE_IN_HIERARCHY) != 0) { return; } if (cancelToken.isCancelRequested() && isAutoCheckCancel()) { setCompleted(TaskStatus.CANCELLED, false); return; } final int reentryId = this.reentryId; execute(); if (reentryId != this.reentryId) { return; } if (cancelToken.isCancelRequested() && isAutoCheckCancel()) { setCompleted(TaskStatus.CANCELLED, false); return; } // 如果可以被内联的子节点没有被内联执行,则尝试通知父节点修复内联 if (fromControl && isInlinable() && (ctl & MASK_DISABLE_NOTIFY) == 0) { control.onChildRunning(this, false); } } /** * 被内联子节点的execute模板方法 * * @param helper 存储被内联子节点的对象 * @param source 被内联前的Task */ public final void template_executeInlined(TaskInlineHelper helper, Task source) { assert status == TaskStatus.RUNNING; if ((ctl & MASK_NOT_ACTIVE_IN_HIERARCHY) != 0) { return; } // 理论上这里可以先检查一下source的取消令牌,但如果source收到取消信号,则被内联的节点的子节点也一定收到取消信号 final int sourceReentryId = source.reentryId; final int reentryId = this.reentryId; // 内联template_execute逻辑 outer: { if (cancelToken.isCancelRequested() && isAutoCheckCancel()) { setCompleted(TaskStatus.CANCELLED, false); break outer; } execute(); if (reentryId != this.reentryId) { break outer; } if (cancelToken.isCancelRequested() && isAutoCheckCancel()) { setCompleted(TaskStatus.CANCELLED, false); } } // 如果被内联子节点退出,而源任务未退出,则重新内联 if (reentryId != this.reentryId && sourceReentryId == source.reentryId) { helper.inlineChild(source); } } private void template_exit() { if ((ctl & MASK_REGISTERED_LISTENER) != 0) { cancelToken.remListener(this); } exitFrame = taskEntry.getCurFrame(); stopRunningChildren(); // 停止有特殊顺序,不能使用访问器 if ((ctl & TaskOverrides.MASK_EXIT) != 0) { exit(); } releaseContext(); reentryId++; } /** * 启动子节点 * * @param child 普通子节点,或需要接收通知的钩子任务 * @param checkGuard 是否检查子节点的前置条件 */ public final void template_startChild(Task child, boolean checkGuard) { if (!checkGuard || child.guard == null || template_checkGuard(child.guard)) { int initMask = (ctl & MASK_CHECKING_GUARD) == 0 ? 0 : MASK_GUARD_BASE_OPTIONS; child.template_start(this, initMask); } else { child.setGuardFailed(this, false); } } /** * 启动钩子节点 * 1.钩子任务不会触发{@link #onChildRunning(Task, boolean)}和{@link #onChildCompleted(Task)} * 2.前置条件其实是特殊的钩子任务 * 3.条件分支通常不应该有钩子任务 * * @param hook 钩子任务,或不需要接收事件通知的子节点 * @param checkGuard 是否检查子节点的前置条件 */ public final void template_startHook(Task hook, boolean checkGuard) { if (!checkGuard || hook.guard == null || template_checkGuard(hook.guard)) { int initMask = (ctl & MASK_CHECKING_GUARD) == 0 ? MASK_DISABLE_NOTIFY : MASK_DISABLE_NOTIFY | MASK_GUARD_BASE_OPTIONS; hook.template_start(this, initMask); } else { hook.setGuardFailed(this, true); } } /** * 检查前置条件 * 1.如果未显式设置guard的上下文,会默认捕获当前Task的上下文 * 2.guard的上下文在运行结束后会被清理 * 3.guard只应该依赖共享上下文(黑板和props),不应当对父节点做任何的假设。 * 4.guard永远是检查当前Task的上下文,子节点的guard也不例外。 * 5.guard通常不应该修改数据 * 6.guard默认不检查取消信号,用户可实现取消信号检测节点。 * 7.如果guard开启了inverter内联或包含Inverter节点,Status将不能保留原始的错误码 -- 只有Sequence能精确返回错误码。 * * @param guard 前置条件;可以是子节点的guard属性,也可以是条件子节点,也可以是外部的条件节点 */ public final boolean template_checkGuard(@Nullable Task guard) { if (guard == null) { return true; } // 注意:此时需要从flags读取反转标记,因为尚未运行(且guard.guard失败的情况下不会运行) boolean inverted = (guard.flags & MASK_INVERTED_GUARD) != 0; try { // 极少情况下会有前置的前置,更推荐组合节点,更清晰;guard的guard也是检测当前上下文 if (guard.guard != null && !template_checkGuard(guard.guard)) { guard.ctl |= MASK_DISABLE_NOTIFY; guard.setCompleted(inverted ? TaskStatus.SUCCESS : TaskStatus.GUARD_FAILED, false); return inverted; } guard.template_start(this, MASK_DISABLE_NOTIFY | MASK_GUARD_BASE_OPTIONS); if (guard.isSucceeded()) { if (inverted) { guard.status = TaskStatus.ERROR; return false; } return true; } if (guard.isFailed()) { if (inverted) { guard.status = TaskStatus.SUCCESS; return true; } return false; } throw new IllegalStateException("Illegal guard status '%d'. Guards must either succeed or fail in one step." .formatted(guard.getStatus())); } finally { guard.unsetControl(); // 条件类节点总是及时清理 } } /** @return 内部使用的mask */ private int captureContext(Task control) { this.taskEntry = control.taskEntry; this.control = control; // 如果黑板不为null,则认为是control预设置的;其它属性同理 int r = 0; if (this.blackboard == null) { this.blackboard = Objects.requireNonNull(control.blackboard); r |= MASK_INHERITED_BLACKBOARD; } if (this.sharedProps == null && control.sharedProps != null) { this.sharedProps = control.sharedProps; r |= MASK_INHERITED_PROPS; } if (this.cancelToken == null) { this.cancelToken = Objects.requireNonNull(control.cancelToken); r |= MASK_INHERITED_CANCEL_TOKEN; } return r; } /** 释放自动捕获的上下文 -- 如果保留上次的上下文,下次执行就会出错(guard是典型) */ private void releaseContext() { final int ctl = this.ctl; if ((ctl & MASK_INHERITED_BLACKBOARD) != 0) { blackboard = null; } if ((ctl & MASK_INHERITED_PROPS) != 0) { sharedProps = null; } if ((ctl & MASK_INHERITED_CANCEL_TOKEN) != 0) { cancelToken = null; } } /** * 设置任务的控制节点 */ public final void setControl(Task control) { assert control != this; if (this == taskEntry) { throw new Error(); } this.taskEntry = Objects.requireNonNull(control.taskEntry); this.control = control; } /** * 删除任务的控制节点(用于清理) * 该方法在任务结束时并不会自动调用,因为Task上的数据可能是有用的,不能立即删除,只有用户知道是否可以清理。 */ public final void unsetControl() { if (this == taskEntry) { throw new Error(); } this.taskEntry = null; this.control = null; this.blackboard = null; this.sharedProps = null; this.cancelToken = null; this.controlData = null; } // endregion // region child维护 /** * 1.尽量不要在运行时增删子节点(危险操作) * 2.不建议将Task从一棵树转移到另一棵树,可能产生内存泄漏(引用未删除干净) * * @param task 要添加的子节点 * @return child index */ public final int addChild(final Task task) { checkAddChild(task); return addChildImpl(task); } /** * 替换指定索引位置的child * (该方法可避免Task的结构发生变化,也可以减少事件数) * * @return index对应的旧节点 */ public final Task setChild(int index, Task newTask) { checkAddChild(newTask); return setChildImpl(index, newTask); } private void checkAddChild(Task child) { if (child == null) throw new NullPointerException("child"); if (child == this) throw new IllegalArgumentException("add self to children"); if (child.isRunning()) throw new IllegalArgumentException("child is running"); if (child.control != this) { // 必须先从旧的父节点上删除,但有可能是自己之前放在一边的子节点 if (child.taskEntry != null || child.control != null) { throw new IllegalArgumentException("child.control is not null"); } } } /** 删除指定child */ public final boolean removeChild(final Task task) { if (null == task) { throw new NullPointerException("task"); } // child未启动的情况下,control可能尚未赋值,因此不能检查control来判别 int index = indexChild(task); if (index > 0) { removeChildImpl(index); task.unsetControl(); return true; } return false; } /** 删除指定索引的child */ public final Task removeChild(int index) { Task child = removeChildImpl(index); child.unsetControl(); return child; } /** 删除所有的child -- 不是个常用方法 */ public final void removeAllChild() { for (int idx = getChildCount() - 1; idx >= 0; idx--) { removeChildImpl(idx).unsetControl(); } } /** @return index or -1 */ public int indexChild(Task task) { for (int idx = getChildCount() - 1; idx >= 0; idx--) { if (getChild(idx) == task) { return idx; } } return -1; } /** 访问所有的子节点(含hook节点) */ public abstract void visitChildren(TaskVisitor visitor, Object param); /** 子节点的数量(仅包括普通意义上的child,不包括钩子任务) */ public abstract int getChildCount(); /** 获取指定索引的child */ public abstract Task getChild(int index); /** @return 为child分配的index */ protected abstract int addChildImpl(Task task); /** @return 索引位置旧的child */ protected abstract Task setChildImpl(int index, final Task task); /** @return index对应的child */ protected abstract Task removeChildImpl(int index); // endregion // region util /** task是否支持内联 */ public final boolean isInlinable() { return (ctl & (TaskOverrides.MASK_INLINABLE | MASK_BREAK_INLINE)) == TaskOverrides.MASK_INLINABLE; } /** 获取任务的控制流标记位 */ public final int getControlFlowOptions() { return ctl & MASK_CONTROL_FLOW_OPTIONS; } /** 将task上的临时控制标记写回到flags中 */ public final void exportControlFlowOptions() { int controlFlowOptions = ctl & MASK_CONTROL_FLOW_OPTIONS; flags &= ~MASK_CONTROL_FLOW_OPTIONS; flags |= controlFlowOptions; } /** 设置子节点的取消令牌 -- 会自动传播取消信号 */ public final void setChildCancelToken(Task child, CancelToken childCancelToken) { if (childCancelToken != null && childCancelToken != cancelToken) { cancelToken.addListener(childCancelToken); } child.cancelToken = childCancelToken; } /** 删除子节点的取消令牌 */ public final void unsetChildCancelToken(Task child) { CancelToken childCancelToken = child.cancelToken; if (childCancelToken != null && childCancelToken != cancelToken) { cancelToken.remListener(childCancelToken); childCancelToken.reset(); } child.cancelToken = null; } /** 停止目标任务 */ public static void stop(@Nullable Task task) { if (task != null && task.status == TaskStatus.RUNNING) { task.stop(TaskStatus.CANCELLED); } } /** 重置目标任务 */ public static void resetForRestart(@Nullable Task task) { if (task != null && task.status != TaskStatus.NEW) { task.resetForRestart(); } } private void setCtlBit(int mask, boolean enable) { if (enable) { ctl |= mask; } else { ctl &= ~mask; } } // endregion // region 序列化 public final Task getGuard() { return guard; } public final Task setGuard(Task guard) { this.guard = guard; return this; } public final int getFlags() { return flags; } public final Task setFlags(int flags) { this.flags = flags; return this; } // endregion }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy