From 2f2974fb77c79d9320af15365595d6b59ee7ac2c Mon Sep 17 00:00:00 2001 From: Klemen Plestenjak Date: Thu, 11 Jan 2024 07:35:58 +0100 Subject: [PATCH] Refactor behaviour_tree (more consistent naming), fix BT visualization --- engine/breeze/ai/behaviour_tree.c | 36 +++---- engine/breeze/ai/behaviour_tree.h | 4 +- engine/tests/btree_test.c | 161 +++++++++++++++++------------- game/components.c | 31 ++++-- 4 files changed, 135 insertions(+), 97 deletions(-) diff --git a/engine/breeze/ai/behaviour_tree.c b/engine/breeze/ai/behaviour_tree.c index 9c26448..0da8d6f 100644 --- a/engine/breeze/ai/behaviour_tree.c +++ b/engine/breeze/ai/behaviour_tree.c @@ -61,9 +61,9 @@ const char *bzBTNodeTypeToStr(BzBTNodeType type) { return "SELECTOR"; case BZ_BT_COMP_SEQUENCE: return "SEQUENCE"; - case BZ_BT_COMP_PARALLEL_SELECTOR: + case BZ_BT_COMP_P_SELECTOR: return "P_SELECTOR"; - case BZ_BT_COMP_PARALLEL_SEQUENCE: + case BZ_BT_COMP_P_SEQUENCE: return "P_SEQUENCE"; case BZ_BT_DECOR_DUMMY: return "DUMMY"; @@ -126,13 +126,13 @@ void bzBTDestroyRoot(BzObjectPool *nodePool, BzBTNode *node) { BzBTNode *bzBTCompSelector(BzObjectPool *nodePool, BzBTNode *parent, bool parallel) { BzBTNodeType type = parallel ? - BZ_BT_COMP_PARALLEL_SELECTOR : + BZ_BT_COMP_P_SELECTOR : BZ_BT_COMP_SELECTOR; return bzBTNodeMake(nodePool, parent, type); } BzBTNode *bzBTCompSequence(BzObjectPool *nodePool, BzBTNode *parent, bool parallel) { BzBTNodeType type = parallel ? - BZ_BT_COMP_PARALLEL_SEQUENCE : + BZ_BT_COMP_P_SEQUENCE : BZ_BT_COMP_SEQUENCE; return bzBTNodeMake(nodePool, parent, type); } @@ -215,9 +215,9 @@ BzBTNode *bzBTCompStateGetRunningChild(const BzBTNodeState *state) { BZ_ASSERT(state->node); BzBTNodeType type = state->node->type; bool isComposite = type == BZ_BT_COMP_SELECTOR || - type == BZ_BT_COMP_PARALLEL_SELECTOR || + type == BZ_BT_COMP_P_SELECTOR || type == BZ_BT_COMP_SEQUENCE || - type == BZ_BT_COMP_PARALLEL_SEQUENCE; + type == BZ_BT_COMP_P_SEQUENCE; BZ_ASSERT(isComposite); return state->as.composite.running; } @@ -366,8 +366,8 @@ static inline BzBTExecReturn bzBTExecuteComposite(const BzBTNode *node, f32 dt, BzBTExecState *state, BzObjectPool *statePool) { BzBTNodeState *nodeState = getNodeState(node, state); - bool isParallel = node->type == BZ_BT_COMP_PARALLEL_SEQUENCE || - node->type == BZ_BT_COMP_PARALLEL_SELECTOR; + bool isParallel = node->type == BZ_BT_COMP_P_SEQUENCE || + node->type == BZ_BT_COMP_P_SELECTOR; BzBTNode *start = node->first; if (!isParallel && nodeState) @@ -384,7 +384,7 @@ static inline BzBTExecReturn bzBTExecuteComposite(const BzBTNode *node, f32 dt, if (childReturn.status == BZ_BT_RUNNING) { execStateMerge(&execReturn.state, &childReturn.state); } - BZ_ASSERT(childReturn.state.first == NULL); + BZ_ASSERT(childReturn.state.first == NULL && childReturn.state.last == NULL); numChildren++; switch (childReturn.status) { case BZ_BT_RUNNING: @@ -401,12 +401,12 @@ static inline BzBTExecReturn bzBTExecuteComposite(const BzBTNode *node, f32 dt, } switch (node->type) { case BZ_BT_COMP_SELECTOR: - case BZ_BT_COMP_PARALLEL_SELECTOR: + case BZ_BT_COMP_P_SELECTOR: if (childReturn.status == BZ_BT_SUCCESS) execReturn.status = BZ_BT_SUCCESS; break; case BZ_BT_COMP_SEQUENCE: - case BZ_BT_COMP_PARALLEL_SEQUENCE: + case BZ_BT_COMP_P_SEQUENCE: if (childReturn.status == BZ_BT_FAIL) execReturn.status = BZ_BT_FAIL; break; @@ -415,19 +415,20 @@ static inline BzBTExecReturn bzBTExecuteComposite(const BzBTNode *node, f32 dt, } if (execReturn.status == BZ_BT_FAIL || execReturn.status == BZ_BT_SUCCESS) break; - if (numRunning > 0 && !isParallel) { + if (numRunning > 0) { execReturn.status = BZ_BT_RUNNING; - break; + if (!isParallel) + break; } } switch (node->type) { case BZ_BT_COMP_SELECTOR: - case BZ_BT_COMP_PARALLEL_SELECTOR: + case BZ_BT_COMP_P_SELECTOR: if (numFailed == numChildren) execReturn.status = BZ_BT_FAIL; break; case BZ_BT_COMP_SEQUENCE: - case BZ_BT_COMP_PARALLEL_SEQUENCE: + case BZ_BT_COMP_P_SEQUENCE: if (numSuccessful == numChildren) execReturn.status = BZ_BT_SUCCESS; break; @@ -450,6 +451,7 @@ static inline BzBTExecReturn bzBTExecuteComposite(const BzBTNode *node, f32 dt, return execReturn; } BZ_ASSERT(execReturn.status == BZ_BT_RUNNING); + // Parallels don't utilize state but still require it for correct tree traversal. nodeState = ensureValidNodeState(node, statePool, nodeState); nodeState->as.composite.running = child; execStatePushFront(&execReturn.state, nodeState); @@ -557,8 +559,8 @@ static inline BzBTExecReturn bzBTExecuteNode(const BzBTNode *node, f32 dt, switch (node->type) { case BZ_BT_COMP_SELECTOR: case BZ_BT_COMP_SEQUENCE: - case BZ_BT_COMP_PARALLEL_SELECTOR: - case BZ_BT_COMP_PARALLEL_SEQUENCE: + case BZ_BT_COMP_P_SELECTOR: + case BZ_BT_COMP_P_SEQUENCE: execReturn = bzBTExecuteComposite(node, dt, nodeState, statePool); break; case BZ_BT_DECOR_DUMMY: diff --git a/engine/breeze/ai/behaviour_tree.h b/engine/breeze/ai/behaviour_tree.h index 9b43666..4ac09e1 100644 --- a/engine/breeze/ai/behaviour_tree.h +++ b/engine/breeze/ai/behaviour_tree.h @@ -18,8 +18,8 @@ typedef enum BzBTNodeType { // Composite BZ_BT_COMP_SELECTOR, BZ_BT_COMP_SEQUENCE, - BZ_BT_COMP_PARALLEL_SELECTOR, - BZ_BT_COMP_PARALLEL_SEQUENCE, + BZ_BT_COMP_P_SELECTOR, + BZ_BT_COMP_P_SEQUENCE, // Decorator BZ_BT_DECOR_DUMMY, BZ_BT_DECOR_SUCCESS, diff --git a/engine/tests/btree_test.c b/engine/tests/btree_test.c index 2604110..a200f7d 100644 --- a/engine/tests/btree_test.c +++ b/engine/tests/btree_test.c @@ -26,7 +26,10 @@ bool init(int *game) { // delay 1s // print "Hello, world!" printBT = bzBTMakeRoot(nodePool); - BzBTNode *node = bzBTDecorRepeat(nodePool, printBT, 5); + BzBTNode *pseq = bzBTCompSequence(nodePool, printBT, true); + bzBTDecorDelay(nodePool, pseq, 2.0f); + + BzBTNode *node = bzBTDecorRepeat(nodePool, pseq, 5); BzBTNode *seq = bzBTCompSequence(nodePool, node, false); @@ -48,78 +51,14 @@ void deinit(int *game) { bzObjectPoolDestroy(nodeStatePool); } -void igVisualizeBTState(const BzBTNode *node, const BzBTNodeState *state, bool sameLine, i32 depth) { - const BzBTNode *child = bzBTNodeChild(node); - BzBTNodeType type = bzBTGetNodeType(node); - char extraInfo[128]; - extraInfo[0] = '\0'; - - bool hasState = bzBTNodeMatchesState(node, state); - - switch (type) { - case BZ_BT_DECOR_REPEAT: - if (hasState) { - snprintf(extraInfo, sizeof(extraInfo), " (%d < %d)", - bzBTRepeatStateGetIter(state), - bzBTDecorGetRepeat(node)); - } else { - snprintf(extraInfo, sizeof(extraInfo), " (%d)", bzBTDecorGetRepeat(node)); - } - break; - case BZ_BT_DECOR_DELAY: - if (hasState) { - snprintf(extraInfo, sizeof(extraInfo), " (%.2f < %.2fms)", - bzBTDelayStateGetElapsed(state), - bzBTDecorGetDelay(node)); - } else { - snprintf(extraInfo, sizeof(extraInfo), " (%.2fms)", bzBTDecorGetDelay(node)); - } - break; - case BZ_BT_ACTION: - snprintf(extraInfo, sizeof(extraInfo), " (%s:%p)", - bzBTNodeGetName(node) ? bzBTNodeGetName(node) : "?", - bzBTActionGetFn(node)); - break; - default: - break; - } - if (hasState) - state = bzBTNodeStateNext(state); - - ImVec4 color = {1.0f, 1.0f, 1.0f, 1.0f}; - if (hasState) - color = (ImVec4) {1.0f, 1.0f, 0.5f, 1.0f}; - - bool hasSingleChild = false; - if (child && bzBTNodeNext(child) == NULL) hasSingleChild = true; - - const char *suffix = hasSingleChild ? " > " : ": "; - - if (sameLine) { - igTextColored(color, "%s%s%s", bzBTNodeTypeToStr(type), - extraInfo, suffix); - } else { - igTextColored(color, "%*s%s %s", - depth * 2, "", - bzBTNodeTypeToStr(type), extraInfo, suffix); - depth++; - } - - - - while (child) { - if (hasSingleChild) igSameLine(0, 0); - igVisualizeBTState(child, state, hasSingleChild, depth); - child = bzBTNodeNext(child); - } -} - +void igVisualizeBTState(const BzBTNode *node, const BzBTNodeState *state, + bool isActive, bool sameLine, i32 depth); void igRenderBT(BzBTState *state) { const BzBTNode *root = state->root; if (igBegin("BehaviourTree", NULL, 0)) { igText("NodeState pool: %d", bzObjectPoolGetNumFree(nodeStatePool)); igSeparatorText(""); - igVisualizeBTState(root, state->_first, false, 0); + igVisualizeBTState(root, state->_first, true, false, 0); } igEnd(); } @@ -143,3 +82,89 @@ bool bzMain(BzAppDesc *appDesc, int argc, const char **argv) { return true; } +static const BzBTNodeState *findNodeState(const BzBTNode *node, const BzBTNodeState *state) { + const BzBTNodeState *pState = state; + + // Although it's painfully slow, it serves as a debug tool, + // so speed is not a critical concern. + while (pState) { + const BzBTNodeState *next = bzBTNodeStateNext(pState); + if (bzBTNodeMatchesState(node, pState)) + return pState; + pState = next; + } + return NULL; +} +void igVisualizeBTState(const BzBTNode *node, const BzBTNodeState *state, + bool isActive, bool sameLine, i32 depth) { + const BzBTNode *child = bzBTNodeChild(node); + BzBTNodeType type = bzBTGetNodeType(node); + char extraInfo[128]; + extraInfo[0] = '\0'; + + const BzBTNodeState *nodeState = findNodeState(node, state); + bool hasState = nodeState != NULL; + isActive |= hasState; + + switch (type) { + case BZ_BT_DECOR_REPEAT: + if (hasState) { + snprintf(extraInfo, sizeof(extraInfo), " (%d < %d)", + bzBTRepeatStateGetIter(nodeState), + bzBTDecorGetRepeat(node)); + } else { + snprintf(extraInfo, sizeof(extraInfo), " (%d)", bzBTDecorGetRepeat(node)); + } + break; + case BZ_BT_DECOR_DELAY: + if (hasState) { + snprintf(extraInfo, sizeof(extraInfo), " (%.2f < %.2fms)", + bzBTDelayStateGetElapsed(nodeState), + bzBTDecorGetDelay(node)); + } else { + snprintf(extraInfo, sizeof(extraInfo), " (%.2fms)", bzBTDecorGetDelay(node)); + } + break; + case BZ_BT_ACTION: + snprintf(extraInfo, sizeof(extraInfo), " (%s:%p)", + bzBTNodeGetName(node) ? bzBTNodeGetName(node) : "?", + bzBTActionGetFn(node)); + break; + default: + break; + } + + + ImVec4 color = {1.0f, 1.0f, 1.0f, 1.0f}; + if (isActive) + color = (ImVec4) {1.0f, 1.0f, 0.5f, 1.0f}; + + bool hasSingleChild = true; + if (child && bzBTNodeNext(child)) hasSingleChild = false; + + const char *suffix = hasSingleChild ? " > " : ": "; + + if (sameLine) { + igTextColored(color, "%s%s%s", bzBTNodeTypeToStr(type), + extraInfo, suffix); + } else { + igTextColored(color, "%*s%s %s", + depth * 2, "", + bzBTNodeTypeToStr(type), extraInfo, suffix); + depth++; + } + + bool isComposite = type == BZ_BT_COMP_SELECTOR || + type == BZ_BT_COMP_P_SELECTOR || + type == BZ_BT_COMP_SEQUENCE || + type == BZ_BT_COMP_P_SEQUENCE; + + while (child) { + if (hasSingleChild) igSameLine(0, 0); + bool childActive = isActive && hasSingleChild; + if (hasState && isComposite && !childActive) + childActive = bzBTCompStateGetRunningChild(state) == child; + igVisualizeBTState(child, state, childActive, hasSingleChild, depth); + child = bzBTNodeNext(child); + } +} diff --git a/game/components.c b/game/components.c index 6cd45f8..93cc3cc 100644 --- a/game/components.c +++ b/game/components.c @@ -214,6 +214,19 @@ void igArm(ecs_world_t *ecs, } +static const BzBTNodeState *findNodeState(const BzBTNode *node, const BzBTNodeState *state) { + const BzBTNodeState *pState = state; + + // Although it's painfully slow, it serves as a debug tool, + // so speed is not a critical concern. + while (pState) { + const BzBTNodeState *next = bzBTNodeStateNext(pState); + if (bzBTNodeMatchesState(node, pState)) + return pState; + pState = next; + } + return NULL; +} void igVisualizeBTState(const BzBTNode *node, const BzBTNodeState *state, bool isActive, bool sameLine, i32 depth) { const BzBTNode *child = bzBTNodeChild(node); @@ -221,14 +234,15 @@ void igVisualizeBTState(const BzBTNode *node, const BzBTNodeState *state, char extraInfo[128]; extraInfo[0] = '\0'; - bool hasState = bzBTNodeMatchesState(node, state); + const BzBTNodeState *nodeState = findNodeState(node, state); + bool hasState = nodeState != NULL; isActive |= hasState; switch (type) { case BZ_BT_DECOR_REPEAT: if (hasState) { snprintf(extraInfo, sizeof(extraInfo), " (%d < %d)", - bzBTRepeatStateGetIter(state), + bzBTRepeatStateGetIter(nodeState), bzBTDecorGetRepeat(node)); } else { snprintf(extraInfo, sizeof(extraInfo), " (%d)", bzBTDecorGetRepeat(node)); @@ -237,7 +251,7 @@ void igVisualizeBTState(const BzBTNode *node, const BzBTNodeState *state, case BZ_BT_DECOR_DELAY: if (hasState) { snprintf(extraInfo, sizeof(extraInfo), " (%.2f < %.2fms)", - bzBTDelayStateGetElapsed(state), + bzBTDelayStateGetElapsed(nodeState), bzBTDecorGetDelay(node)); } else { snprintf(extraInfo, sizeof(extraInfo), " (%.2fms)", bzBTDecorGetDelay(node)); @@ -273,19 +287,16 @@ void igVisualizeBTState(const BzBTNode *node, const BzBTNodeState *state, } bool isComposite = type == BZ_BT_COMP_SELECTOR || - type == BZ_BT_COMP_PARALLEL_SELECTOR || - type == BZ_BT_COMP_SEQUENCE || - type == BZ_BT_COMP_PARALLEL_SEQUENCE; + type == BZ_BT_COMP_P_SELECTOR || + type == BZ_BT_COMP_SEQUENCE || + type == BZ_BT_COMP_P_SEQUENCE; while (child) { if (hasSingleChild) igSameLine(0, 0); bool childActive = isActive && hasSingleChild; if (hasState && isComposite && !childActive) childActive = bzBTCompStateGetRunningChild(state) == child; - const BzBTNodeState *childState = state; - if (hasState) - childState = bzBTNodeStateNext(state); - igVisualizeBTState(child, childState, childActive, hasSingleChild, depth); + igVisualizeBTState(child, state, childActive, hasSingleChild, depth); child = bzBTNodeNext(child); } }