From e4cc00d3a738a0ab704915877cebffd60f804c26 Mon Sep 17 00:00:00 2001 From: Klemen Plestenjak Date: Tue, 9 Jan 2024 20:53:21 +0100 Subject: [PATCH] Behaviour tree composites --- engine/breeze/ai/behaviour_tree.c | 248 +++++++++++++++++++++--------- engine/tests/btree_test.c | 15 +- 2 files changed, 184 insertions(+), 79 deletions(-) diff --git a/engine/breeze/ai/behaviour_tree.c b/engine/breeze/ai/behaviour_tree.c index 4929c27..46f0060 100644 --- a/engine/breeze/ai/behaviour_tree.c +++ b/engine/breeze/ai/behaviour_tree.c @@ -35,6 +35,9 @@ struct BzAIBTNodeState { BzAIBTNodeState *prev; union { + struct { + BzAIBTNode *running; + } composite; struct { i32 iter; } repeat; @@ -164,32 +167,14 @@ void bzAIBTDestroyState(BzAIBTState *state) { bzMemSet(state, 0, sizeof(*state)); } -void bzAIBTStatePush(BzAIBTState *state, BzAIBTNodeState *nodeState, - const BzAIBTNodeState *desc) { - BzAIBTNodeState *newState = bzObjectPool(state->nodeStatePool); - BZ_ASSERT(newState && desc); - *newState = *desc; - - newState->next = NULL; - newState->prev = NULL; - - if (nodeState == NULL) - nodeState = state->last; - - if (nodeState) { - BzAIBTNodeState *next = nodeState->next; - nodeState->next = newState; - newState->prev = nodeState; - newState->next = next; - if (next) - next->prev = newState; - if (state->last == nodeState) - state->last = newState; - } else { - newState->prev = state->last; - state->last = newState; - if (state->first == NULL) state->first = newState; - } +void bzAIBTStateAppend(BzAIBTState *state, BzAIBTNodeState *nodeState) { + nodeState->next = NULL; + nodeState->prev = state->last; + if (state->last) + state->last->next = nodeState; + else + state->first = nodeState; + state->last = nodeState; } void bzAIBTStatePop(BzAIBTState *state, BzAIBTNodeState *nodeState) { if (state->first == nodeState) state->first = nodeState->next; @@ -200,74 +185,170 @@ void bzAIBTStatePop(BzAIBTState *state, BzAIBTNodeState *nodeState) { nodeState->prev->next = next; if (nodeState->next) nodeState->next->prev = prev; + nodeState->next = NULL; + nodeState->prev = NULL; +} +void bzAIBTStateRenew(BzAIBTState *oldState, BzAIBTState *newState, BzAIBTNodeState *nodeState) { + // Pop nodeState and transfer it to the back + bzAIBTStatePop(oldState, nodeState); + bzAIBTStateAppend(newState, nodeState); +} +BzAIBTNodeState *bzAIBTStatePool(BzAIBTState *state, const BzAIBTNode *node) { + BzAIBTNodeState *nodeState = bzObjectPool(state->nodeStatePool); + nodeState->next = NULL; + nodeState->prev = NULL; + nodeState->node = node; + return nodeState; +} +void bzAIBTStateRelease(BzAIBTState *state, BzAIBTNodeState *nodeState) { bzObjectPoolRelease(state->nodeStatePool, nodeState); } +bool nodeMatchesState(const BzAIBTNode *node, const BzAIBTNodeState *state) { + return state && state->node == node; +} +BzAIBTNodeState *getNextNodeState(const BzAIBTNode *node, BzAIBTNodeState *nodeState) { + if (nodeState && nodeMatchesState(node, nodeState)) + return nodeState->next; + return nodeState; +} + static inline BzAIBTStatus bzAIBTExecuteNode(const BzAIBTNode *node, f32 dt, - BzAIBTState *state, BzAIBTNodeState *nodeState); -/* -static inline BzAIBTStatus bzAIBTExecuteComposite(const BzObjectPool *nodePool, const BzAIBTNode *node, - BzAIBTState *state, BzAIBTNodeState *nodeState) { + BzAIBTNodeState *nodeState, + BzAIBTState *oldState, BzAIBTState *newState); +static inline BzAIBTStatus bzAIBTExecuteComposite(const BzAIBTNode *node, f32 dt, + BzAIBTNodeState *nodeState, + BzAIBTState *oldState, BzAIBTState *newState) { + BzAIBTNodeState *nextState = getNextNodeState(node, nodeState); + BzAIBTNode *start = node->first; + bool isParallel = node->type == BZ_AIBT_COMP_PARALLEL_SEQUENCE || + node->type == BZ_AIBT_COMP_PARALLEL_SELECTOR; + + if (!isParallel && nodeMatchesState(node, nodeState)) + start = nodeState->as.composite.running; + + // Always push dummy state + if (nodeMatchesState(node, nodeState)) { + bzAIBTStateRenew(oldState, newState, nodeState); + } else { + nodeState = bzAIBTStatePool(oldState, node); + bzAIBTStateAppend(newState, nodeState); + } + i32 numRunning = 0; + i32 numSuccessful = 0; + i32 numFailed = 0; + i32 numChildren = 0; + BzAIBTStatus status = BZ_AIBT_ERROR; + BzAIBTNode *child = start; + for (;child; child = child->next) { + BzAIBTStatus childStatus = bzAIBTExecuteNode(child, dt, nextState, oldState, newState); + numChildren++; + switch (childStatus) { + case BZ_AIBT_RUNNING: + numRunning++; + break; + case BZ_AIBT_SUCCESS: + numSuccessful++; + break; + case BZ_AIBT_FAIL: + numFailed++; + break; + default: + break; + } + switch (node->type) { + case BZ_AIBT_COMP_SELECTOR: + case BZ_AIBT_COMP_PARALLEL_SELECTOR: + if (childStatus == BZ_AIBT_SUCCESS) + status = BZ_AIBT_SUCCESS; + break; + case BZ_AIBT_COMP_SEQUENCE: + case BZ_AIBT_COMP_PARALLEL_SEQUENCE: + if (childStatus == BZ_AIBT_FAIL) + status = BZ_AIBT_FAIL; + break; + default: + break; + } + if (status == BZ_AIBT_FAIL || status == BZ_AIBT_SUCCESS) + break; + if (numRunning > 0 && !isParallel) { + status = BZ_AIBT_RUNNING; + break; + } + } switch (node->type) { case BZ_AIBT_COMP_SELECTOR: - for (BzAIBTNode *child = node->first; child; child = child->next) { - BzAIBTStatus status = bzAIBTExecuteNode(bt, child); - if (status == BZ_AIBT_SUCCESS) return status; - if (status == BZ_AIBT_RUNNING) return status; - } - return BZ_AIBT_FAIL; + case BZ_AIBT_COMP_PARALLEL_SELECTOR: + if (numFailed == numChildren) + status = BZ_AIBT_FAIL; + break; case BZ_AIBT_COMP_SEQUENCE: - for (BzAIBTNode *child = node->first; child; child = child->next) { - BzAIBTStatus status = bzAIBTExecuteNode(bt, child); - if (status == BZ_AIBT_FAIL) return status; - if (status == BZ_AIBT_RUNNING) return status; - } - return BZ_AIBT_SUCCESS; + case BZ_AIBT_COMP_PARALLEL_SEQUENCE: + if (numSuccessful == numChildren) + status = BZ_AIBT_SUCCESS; + break; default: - assert(false); - return BZ_AIBT_ERROR; + break; } + + if (status == BZ_AIBT_ERROR) { + bzAIBTStatePop(newState, nodeState); + return BZ_AIBT_ERROR; + } + + bool finished = status == BZ_AIBT_SUCCESS || + status == BZ_AIBT_FAIL; + if (finished) { + // Dummy state is no longer needed + bzAIBTStatePop(newState, nodeState); + } else { + BZ_ASSERT(status == BZ_AIBT_RUNNING); + nodeState->as.composite.running = child; + } + return status; } - */ static inline BzAIBTStatus bzAIBTExecuteDecorator(const BzAIBTNode *node, f32 dt, - BzAIBTState *state, BzAIBTNodeState *nodeState) { - // Ensure decorator has only one child - BZ_ASSERT(node->first && node->first == node->last); - BzAIBTNodeState *first = nodeState; - if (nodeState && first->node == node) { - first = first->next; - } + BzAIBTNodeState *nodeState, + BzAIBTState *oldState, BzAIBTState *newState) { + // Ensure decorator has only one child, if any + BZ_ASSERT(!node->first || node->first == node->last); + BzAIBTNodeState *nextState = getNextNodeState(node, nodeState); switch (node->type) { case BZ_AIBT_DECOR_REPEAT: - if (!nodeState || nodeState->node != node) { - bzAIBTStatePush(state, nodeState, &(BzAIBTNodeState) { - .node = node, - .as.repeat.iter = 0 - }); + if (!nodeMatchesState(node, nodeState)) { + BzAIBTNodeState *newNodeState = bzAIBTStatePool(oldState, node); + newNodeState->as.repeat.iter = 0; + bzAIBTStateAppend(newState, newNodeState); + nodeState = newNodeState; + } else { + bzAIBTStateRenew(oldState, newState, nodeState); } break; case BZ_AIBT_DECOR_DELAY: - if (!nodeState || nodeState->node != node) { - bzAIBTStatePush(state, nodeState, &(BzAIBTNodeState) { - .node = node, - .as.delay = {0.2f} - }); + if (!nodeMatchesState(node, nodeState)) { + BzAIBTNodeState *newNodeState = bzAIBTStatePool(oldState, node); + newNodeState->as.delay.elapsed = dt; + bzAIBTStateAppend(newState, newNodeState); return BZ_AIBT_RUNNING; } - nodeState->as.delay.elapsed += 0.2f; + nodeState->as.delay.elapsed += dt; if (nodeState->as.delay.elapsed < node->as.delay.ms) { + bzAIBTStateRenew(oldState, newState, nodeState); return BZ_AIBT_RUNNING; } - bzAIBTStatePop(state, nodeState); break; default: break; } - BzAIBTStatus inStatus = bzAIBTExecuteNode(node->first, dt, state, first); + // Implicit success, if no children are present + BzAIBTStatus inStatus = BZ_AIBT_SUCCESS; + if (node->first) + inStatus = bzAIBTExecuteNode(node->first, dt, nextState, oldState, newState); - // ERROR, RUNNING are propagated up + // Propagate ERROR, RUNNING up if (inStatus == BZ_AIBT_ERROR) return BZ_AIBT_ERROR; if (inStatus == BZ_AIBT_RUNNING) @@ -286,8 +367,10 @@ static inline BzAIBTStatus bzAIBTExecuteDecorator(const BzAIBTNode *node, f32 dt status = BZ_AIBT_FAIL; break; case BZ_AIBT_DECOR_INVERT: - if (inStatus == BZ_AIBT_FAIL) status = BZ_AIBT_SUCCESS; - if (inStatus == BZ_AIBT_SUCCESS) status = BZ_AIBT_FAIL; + if (inStatus == BZ_AIBT_FAIL) + status = BZ_AIBT_SUCCESS; + if (inStatus == BZ_AIBT_SUCCESS) + status = BZ_AIBT_FAIL; break; case BZ_AIBT_DECOR_UNTIL_SUCCESS: if (inStatus == BZ_AIBT_SUCCESS) @@ -305,7 +388,7 @@ static inline BzAIBTStatus bzAIBTExecuteDecorator(const BzAIBTNode *node, f32 dt BZ_ASSERT(nodeState->node == node); nodeState->as.repeat.iter++; if (nodeState->as.repeat.iter >= node->as.repeat.n) { - bzAIBTStatePop(state, nodeState); + bzAIBTStatePop(newState, nodeState); status = inStatus; break; } @@ -317,14 +400,15 @@ static inline BzAIBTStatus bzAIBTExecuteDecorator(const BzAIBTNode *node, f32 dt return status; } static inline BzAIBTStatus bzAIBTExecuteNode(const BzAIBTNode *node, f32 dt, - BzAIBTState *state, BzAIBTNodeState *nodeState) { + BzAIBTNodeState *nodeState, + BzAIBTState *oldState, BzAIBTState *newState) { BzAIBTStatus status = BZ_AIBT_ERROR; switch (node->type) { case BZ_AIBT_COMP_SELECTOR: case BZ_AIBT_COMP_SEQUENCE: case BZ_AIBT_COMP_PARALLEL_SELECTOR: case BZ_AIBT_COMP_PARALLEL_SEQUENCE: - //status = bzAIBTExecuteComposite(bt, node, state, nodeState); + status = bzAIBTExecuteComposite(node, dt, nodeState, oldState, newState); break; case BZ_AIBT_DECOR_DUMMY: case BZ_AIBT_DECOR_SUCCESS: @@ -334,11 +418,11 @@ static inline BzAIBTStatus bzAIBTExecuteNode(const BzAIBTNode *node, f32 dt, case BZ_AIBT_DECOR_UNTIL_FAIL: case BZ_AIBT_DECOR_REPEAT: case BZ_AIBT_DECOR_DELAY: - status = bzAIBTExecuteDecorator(node, dt, state, nodeState); + status = bzAIBTExecuteDecorator(node, dt, nodeState, oldState, newState); break; case BZ_AIBT_ACTION: BZ_ASSERT(node->as.action.fn); - return node->as.action.fn(state->userData); + return node->as.action.fn(oldState->userData); } return status; } @@ -348,8 +432,24 @@ BzAIBTStatus bzAIBTExecute(BzAIBTState *state, f32 dt) { BZ_ASSERT(bzObjectPoolGetObjectSize(state->nodeStatePool) == bzAIBTGetNodeStateSize()); BZ_ASSERT(state); BZ_ASSERT(state->root); + + BzAIBTState newState = { + .first = NULL, + .last = NULL, + }; + BzAIBTNodeState *first = state->first; const BzAIBTNode *firstNode = first ? first->node : state->root; - BzAIBTStatus status = bzAIBTExecuteNode(firstNode, dt, state, first); + BzAIBTStatus status = bzAIBTExecuteNode(firstNode, dt, first, state, &newState); + + // Release leftover states + BzAIBTNodeState *pState = state->first; + while (pState) { + BzAIBTNodeState *next = pState->next; + bzAIBTStateRelease(state, pState); + pState = next; + } + state->first = newState.first; + state->last = newState.last; return status; } diff --git a/engine/tests/btree_test.c b/engine/tests/btree_test.c index a4caa28..c199e9e 100644 --- a/engine/tests/btree_test.c +++ b/engine/tests/btree_test.c @@ -8,7 +8,7 @@ BzAIBTNode *printBT = NULL; BzAIBTStatus printAction(void *data) { bzLogInfo("Hello, world!"); - return BZ_AIBT_FAIL; + return BZ_AIBT_SUCCESS; } bool init(int *game) { @@ -20,12 +20,16 @@ bool init(int *game) { }); // for 1..5: - // delay 1s - // print "Hello, world!" + // seq + // delay 1s + // print "Hello, world!" printBT = bzAIBTMakeRoot(nodePool); BzAIBTNode *node = bzAIBTDecorRepeat(nodePool, printBT, 5); - node = bzAIBTDecorDelay(nodePool, node, 1.0f); - bzAIBTAction(nodePool, node, printAction); + + BzAIBTNode *seq = bzAIBTCompSequence(nodePool, node, false); + + bzAIBTDecorDelay(nodePool, seq, 1.0f); + bzAIBTAction(nodePool, seq, printAction); BzAIBTState state = bzAIBTCreateState(&(BzAIBTStateDesc) { .root = printBT, @@ -39,6 +43,7 @@ bool init(int *game) { while (status == BZ_AIBT_RUNNING) { status = bzAIBTExecute(&state, 0.2f); count++; + assert(status != BZ_AIBT_ERROR); } bzLogInfo("Iter: %d", count);