From d7feba04dbb0c3587a890933b137e9f6c53cce6f Mon Sep 17 00:00:00 2001 From: Klemen Plestenjak Date: Tue, 9 Jan 2024 17:10:00 +0100 Subject: [PATCH] Behaviour tree decorator implementation --- engine/CMakeLists.txt | 4 + engine/breeze.h | 2 + engine/breeze/ai/behaviour_tree.c | 355 ++++++++++++++++++++++++++++++ engine/breeze/ai/behaviour_tree.h | 82 +++++++ engine/breeze/util/object_pool.c | 7 +- engine/breeze/util/object_pool.h | 2 + engine/tests/CMakeLists.txt | 3 + engine/tests/btree_test.c | 63 ++++++ 8 files changed, 517 insertions(+), 1 deletion(-) create mode 100644 engine/breeze/ai/behaviour_tree.c create mode 100644 engine/breeze/ai/behaviour_tree.h create mode 100644 engine/tests/btree_test.c diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 9189e27..ea4b243 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -35,6 +35,8 @@ set(libraryDirs set(BreezeSources + breeze/ai/behaviour_tree.c + breeze/core/logger.c breeze/core/module_system.c @@ -54,6 +56,8 @@ set(BreezeSources ) set(BreezeHeaders + breeze/ai/behaviour_tree.h + breeze/core/logger.h breeze/math/vec2i.h diff --git a/engine/breeze.h b/engine/breeze.h index a0e3dd3..1dc7ca8 100644 --- a/engine/breeze.h +++ b/engine/breeze.h @@ -1,6 +1,8 @@ #ifndef BREEZE_H #define BREEZE_H +#include "breeze/ai/behaviour_tree.h" + #include "breeze/core/logger.h" #include "breeze/math/vec2i.h" diff --git a/engine/breeze/ai/behaviour_tree.c b/engine/breeze/ai/behaviour_tree.c new file mode 100644 index 0000000..4929c27 --- /dev/null +++ b/engine/breeze/ai/behaviour_tree.c @@ -0,0 +1,355 @@ +#include "behaviour_tree.h" + +#include "../memory/memory.h" +#include "../util/object_pool.h" + +//#define GET_NODE(idx) ((BzAIBTNode *) bzObjectPoolGetObject(bt->nodePool, idx)) + +struct BzAIBTNode { + BzAIBTNode *parent; + // Children + BzAIBTNode *first; + BzAIBTNode *last; + // Siblings + BzAIBTNode *prev; + BzAIBTNode *next; + + BzAIBTNodeType type; + union { + struct { + i32 n; + } repeat; + struct { + f32 ms; + } delay; + struct { + BzAIBTActionFn fn; + } action; + } as; + +}; + +struct BzAIBTNodeState { + const BzAIBTNode *node; + BzAIBTNodeState *next; + BzAIBTNodeState *prev; + + union { + struct { + i32 iter; + } repeat; + struct { + f32 elapsed; + } delay; + } as; +}; + + +size_t bzAIBTGetNodeSize() { + return sizeof(BzAIBTNode); +} +size_t bzAIBTGetNodeStateSize() { + return sizeof(BzAIBTNodeState); +} + +static BzAIBTNode *bzAIBTNodeMake(BzObjectPool *nodePool, BzAIBTNode *parent, BzAIBTNodeType type) { + BZ_ASSERT(nodePool); + BZ_ASSERT(bzObjectPoolGetObjectSize(nodePool) == bzAIBTGetNodeSize()); + BzAIBTNode *node = bzObjectPool(nodePool); + bzMemSet(node, 0, sizeof(*node)); + + node->type = type; + + if (parent && parent->last) { + parent->last->next = node; + node->prev = parent->last; + parent->last = node; + } else if (parent) { + parent->first = node; + parent->last = node; + } + node->parent = parent; + + return node; +} + +BzAIBTNode *bzAIBTMakeRoot(BzObjectPool *nodePool) { + return bzAIBTNodeMake(nodePool, NULL, BZ_AIBT_DECOR_DUMMY); +} + +void bzAIBTDestroyRoot(BzObjectPool *nodePool, BzAIBTNode *node) { + BZ_ASSERT(node); + BzAIBTNode *pNode = node; + + while (pNode) { + BzAIBTNode *next = pNode->next; + bzAIBTDestroyRoot(nodePool, pNode); + pNode = next; + } + + bzObjectPoolRelease(nodePool, node); +} + +BzAIBTNode *bzAIBTCompSelector(BzObjectPool *nodePool, BzAIBTNode *parent, bool parallel) { + BzAIBTNodeType type = parallel ? + BZ_AIBT_COMP_PARALLEL_SELECTOR : + BZ_AIBT_COMP_SELECTOR; + return bzAIBTNodeMake(nodePool, parent, type); +} +BzAIBTNode *bzAIBTCompSequence(BzObjectPool *nodePool, BzAIBTNode *parent, bool parallel) { + BzAIBTNodeType type = parallel ? + BZ_AIBT_COMP_PARALLEL_SEQUENCE : + BZ_AIBT_COMP_SEQUENCE; + return bzAIBTNodeMake(nodePool, parent, type); +} + +BzAIBTNode *bzAIBTDecorDummy(BzObjectPool *nodePool, BzAIBTNode *parent) { + return bzAIBTNodeMake(nodePool, parent, BZ_AIBT_DECOR_DUMMY); +} +BzAIBTNode *bzAIBTDecorSuccess(BzObjectPool *nodePool, BzAIBTNode *parent) { + return bzAIBTNodeMake(nodePool, parent, BZ_AIBT_DECOR_SUCCESS); +} +BzAIBTNode *bzAIBTDecorFail(BzObjectPool *nodePool, BzAIBTNode *parent) { + return bzAIBTNodeMake(nodePool, parent, BZ_AIBT_DECOR_FAIL); +} +BzAIBTNode *bzAIBTDecorInvert(BzObjectPool *nodePool, BzAIBTNode *parent) { + return bzAIBTNodeMake(nodePool, parent, BZ_AIBT_DECOR_INVERT); +} +BzAIBTNode *bzAIBTDecorUntilSuccess(BzObjectPool *nodePool, BzAIBTNode *parent) { + return bzAIBTNodeMake(nodePool, parent, BZ_AIBT_DECOR_UNTIL_SUCCESS); +} +BzAIBTNode *bzAIBTDecorUntilFail(BzObjectPool *nodePool, BzAIBTNode *parent) { + return bzAIBTNodeMake(nodePool, parent, BZ_AIBT_DECOR_UNTIL_FAIL); +} +BzAIBTNode *bzAIBTDecorRepeat(BzObjectPool *nodePool, BzAIBTNode *parent, i32 n) { + BzAIBTNode *node = bzAIBTNodeMake(nodePool, parent, BZ_AIBT_DECOR_REPEAT); + node->as.repeat.n = n; + return node; +} +BzAIBTNode *bzAIBTDecorDelay(BzObjectPool *nodePool, BzAIBTNode *parent, f32 ms) { + BzAIBTNode *node = bzAIBTNodeMake(nodePool, parent, BZ_AIBT_DECOR_DELAY); + node->as.delay.ms = ms; + return node; +} + +BzAIBTNode *bzAIBTAction(BzObjectPool *nodePool, BzAIBTNode *parent, BzAIBTActionFn fn) { + BzAIBTNode *node = bzAIBTNodeMake(nodePool, parent, BZ_AIBT_ACTION); + node->as.action.fn = fn; + return node; +} + +BzAIBTNodeType bzAIBTGetNodeType(BzAIBTNode *node) { + return node->type; +} + +BzAIBTState bzAIBTCreateState(const BzAIBTStateDesc *desc) { + BZ_ASSERT(desc->pool); + BZ_ASSERT(bzObjectPoolGetObjectSize(desc->pool) == bzAIBTGetNodeStateSize()); + BZ_ASSERT(desc->root); + return (BzAIBTState) { + .root = desc->root, + .first = NULL, + .last = NULL, + .nodeStatePool = desc->pool, + .userData = desc->userData + }; +} +void bzAIBTDestroyState(BzAIBTState *state) { + BzAIBTNodeState *pNodeState = state->first; + while (pNodeState) { + BzAIBTNodeState *next = pNodeState->next; + bzObjectPoolRelease(state->nodeStatePool, pNodeState); + pNodeState = next; + } + 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 bzAIBTStatePop(BzAIBTState *state, BzAIBTNodeState *nodeState) { + if (state->first == nodeState) state->first = nodeState->next; + if (state->last == nodeState) state->last = nodeState->prev; + BzAIBTNodeState *next = nodeState->next; + BzAIBTNodeState *prev = nodeState->prev; + if (nodeState->prev) + nodeState->prev->next = next; + if (nodeState->next) + nodeState->next->prev = prev; + bzObjectPoolRelease(state->nodeStatePool, 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) { + 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_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; + default: + assert(false); + return BZ_AIBT_ERROR; + } +} + */ +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; + } + + switch (node->type) { + case BZ_AIBT_DECOR_REPEAT: + if (!nodeState || nodeState->node != node) { + bzAIBTStatePush(state, nodeState, &(BzAIBTNodeState) { + .node = node, + .as.repeat.iter = 0 + }); + } + break; + case BZ_AIBT_DECOR_DELAY: + if (!nodeState || nodeState->node != node) { + bzAIBTStatePush(state, nodeState, &(BzAIBTNodeState) { + .node = node, + .as.delay = {0.2f} + }); + return BZ_AIBT_RUNNING; + } + nodeState->as.delay.elapsed += 0.2f; + if (nodeState->as.delay.elapsed < node->as.delay.ms) { + return BZ_AIBT_RUNNING; + } + bzAIBTStatePop(state, nodeState); + break; + default: + break; + } + + BzAIBTStatus inStatus = bzAIBTExecuteNode(node->first, dt, state, first); + + // ERROR, RUNNING are propagated up + if (inStatus == BZ_AIBT_ERROR) + return BZ_AIBT_ERROR; + if (inStatus == BZ_AIBT_RUNNING) + return BZ_AIBT_RUNNING; + + BzAIBTStatus status = BZ_AIBT_ERROR; + switch (node->type) { + case BZ_AIBT_DECOR_DUMMY: + case BZ_AIBT_DECOR_DELAY: // Delay already handled + status = inStatus; + break; + case BZ_AIBT_DECOR_SUCCESS: + status = BZ_AIBT_SUCCESS; + break; + case BZ_AIBT_DECOR_FAIL: + 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; + break; + case BZ_AIBT_DECOR_UNTIL_SUCCESS: + if (inStatus == BZ_AIBT_SUCCESS) + status = BZ_AIBT_SUCCESS; + else + status = BZ_AIBT_RUNNING; + break; + case BZ_AIBT_DECOR_UNTIL_FAIL: + if (inStatus == BZ_AIBT_FAIL) + status = BZ_AIBT_SUCCESS; + else + status = BZ_AIBT_RUNNING; + break; + case BZ_AIBT_DECOR_REPEAT: + BZ_ASSERT(nodeState->node == node); + nodeState->as.repeat.iter++; + if (nodeState->as.repeat.iter >= node->as.repeat.n) { + bzAIBTStatePop(state, nodeState); + status = inStatus; + break; + } + status = BZ_AIBT_RUNNING; + break; + default: + break; + } + return status; +} +static inline BzAIBTStatus bzAIBTExecuteNode(const BzAIBTNode *node, f32 dt, + BzAIBTState *state, BzAIBTNodeState *nodeState) { + 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); + break; + case BZ_AIBT_DECOR_DUMMY: + case BZ_AIBT_DECOR_SUCCESS: + case BZ_AIBT_DECOR_FAIL: + case BZ_AIBT_DECOR_INVERT: + case BZ_AIBT_DECOR_UNTIL_SUCCESS: + case BZ_AIBT_DECOR_UNTIL_FAIL: + case BZ_AIBT_DECOR_REPEAT: + case BZ_AIBT_DECOR_DELAY: + status = bzAIBTExecuteDecorator(node, dt, state, nodeState); + break; + case BZ_AIBT_ACTION: + BZ_ASSERT(node->as.action.fn); + return node->as.action.fn(state->userData); + } + return status; +} + +BzAIBTStatus bzAIBTExecute(BzAIBTState *state, f32 dt) { + BZ_ASSERT(state->nodeStatePool); + BZ_ASSERT(bzObjectPoolGetObjectSize(state->nodeStatePool) == bzAIBTGetNodeStateSize()); + BZ_ASSERT(state); + BZ_ASSERT(state->root); + BzAIBTNodeState *first = state->first; + const BzAIBTNode *firstNode = first ? first->node : state->root; + BzAIBTStatus status = bzAIBTExecuteNode(firstNode, dt, state, first); + return status; +} diff --git a/engine/breeze/ai/behaviour_tree.h b/engine/breeze/ai/behaviour_tree.h new file mode 100644 index 0000000..ff7cf14 --- /dev/null +++ b/engine/breeze/ai/behaviour_tree.h @@ -0,0 +1,82 @@ +#ifndef BREEZE_BEHAVIOUR_TREE_H +#define BREEZE_BEHAVIOUR_TREE_H + +#include "../defines.h" + +typedef struct BzAIBTNode BzAIBTNode; + +typedef enum BzAIBTStatus { + BZ_AIBT_RUNNING, + BZ_AIBT_SUCCESS, + BZ_AIBT_FAIL, + BZ_AIBT_ERROR, +} BzAIBTStatus; + +typedef BzAIBTStatus(*BzAIBTActionFn)(void *data); + +typedef enum BzAIBTNodeType { + // Composite + BZ_AIBT_COMP_SELECTOR, + BZ_AIBT_COMP_SEQUENCE, + BZ_AIBT_COMP_PARALLEL_SELECTOR, + BZ_AIBT_COMP_PARALLEL_SEQUENCE, + // Decorator + BZ_AIBT_DECOR_DUMMY, + BZ_AIBT_DECOR_SUCCESS, + BZ_AIBT_DECOR_FAIL, + BZ_AIBT_DECOR_INVERT, + BZ_AIBT_DECOR_UNTIL_SUCCESS, + BZ_AIBT_DECOR_UNTIL_FAIL, + BZ_AIBT_DECOR_REPEAT, + BZ_AIBT_DECOR_DELAY, + // Action/Task + BZ_AIBT_ACTION, +} BzAIBTNodeType; + +typedef struct BzObjectPool BzObjectPool; +typedef struct BzAIBTNodeState BzAIBTNodeState; + +typedef struct BzAIBTState { + const BzAIBTNode *root; + BzAIBTNodeState *first; + BzAIBTNodeState *last; + + BzObjectPool *nodeStatePool; + void *userData; +} BzAIBTState; + +typedef struct BzAIBTStateDesc { + const BzAIBTNode *root; + + BzObjectPool *pool; + void *userData; +} BzAIBTStateDesc; + +size_t bzAIBTGetNodeSize(); +size_t bzAIBTGetNodeStateSize(); + +BzAIBTNode *bzAIBTMakeRoot(BzObjectPool *nodePool); +void bzAIBTDestroyRoot(BzObjectPool *nodePool, BzAIBTNode *node); + +BzAIBTNode *bzAIBTCompSelector(BzObjectPool *nodePool, BzAIBTNode *parent, bool parallel); +BzAIBTNode *bzAIBTCompSequence(BzObjectPool *nodePool, BzAIBTNode *parent, bool parallel); + +BzAIBTNode *bzAIBTDecorDummy(BzObjectPool *nodePool, BzAIBTNode *parent); +BzAIBTNode *bzAIBTDecorSuccess(BzObjectPool *nodePool, BzAIBTNode *parent); +BzAIBTNode *bzAIBTDecorFail(BzObjectPool *nodePool, BzAIBTNode *parent); +BzAIBTNode *bzAIBTDecorInvert(BzObjectPool *nodePool, BzAIBTNode *parent); +BzAIBTNode *bzAIBTDecorUntilSuccess(BzObjectPool *nodePool, BzAIBTNode *parent); +BzAIBTNode *bzAIBTDecorUntilFail(BzObjectPool *nodePool, BzAIBTNode *parent); +BzAIBTNode *bzAIBTDecorRepeat(BzObjectPool *nodePool, BzAIBTNode *parent, i32 n); +BzAIBTNode *bzAIBTDecorDelay(BzObjectPool *nodePool, BzAIBTNode *parent, f32 ms); + +BzAIBTNode *bzAIBTAction(BzObjectPool *nodePool, BzAIBTNode *parent, BzAIBTActionFn fn); + +BzAIBTNodeType bzAIBTGetNodeType(BzAIBTNode *node); + +BzAIBTState bzAIBTCreateState(const BzAIBTStateDesc *desc); +void bzAIBTDestroyState(BzAIBTState *state); + +BzAIBTStatus bzAIBTExecute(BzAIBTState *state, f32 dt); + +#endif //BREEZE_BEHAVIOUR_TREE_H diff --git a/engine/breeze/util/object_pool.c b/engine/breeze/util/object_pool.c index f429d59..2311bc8 100644 --- a/engine/breeze/util/object_pool.c +++ b/engine/breeze/util/object_pool.c @@ -69,10 +69,11 @@ BzObjectPool *bzObjectPoolCreate(const BzObjectPoolDesc *desc) { if (stride < sizeof(i32)) { stride = sizeof(i32); } - BZ_ASSERT(desc->objectsPerPage > 0); + BZ_ASSERT(desc->objectsPerPage >= 0); BZ_ASSERT(desc->objectsPerPage < (2 << 23)); size_t objectsPerPage = desc->objectsPerPage; + if (objectsPerPage == 0) objectsPerPage = 512; size_t pageCapacity = 8; void **pages = bzAlloc(sizeof(*pages) * pageCapacity); @@ -100,6 +101,10 @@ void bzObjectPoolDestroy(BzObjectPool *pool) { bzFree(pool); } +size_t bzObjectPoolGetObjectSize(BzObjectPool *pool) { + return pool->stride; +} + size_t bzObjectPoolGetNumFree(BzObjectPool *pool) { return pool->numFree; } diff --git a/engine/breeze/util/object_pool.h b/engine/breeze/util/object_pool.h index 4463d9c..3dd05a1 100644 --- a/engine/breeze/util/object_pool.h +++ b/engine/breeze/util/object_pool.h @@ -17,6 +17,8 @@ typedef struct BzObjectPoolDesc { BzObjectPool *bzObjectPoolCreate(const BzObjectPoolDesc *desc); void bzObjectPoolDestroy(BzObjectPool *pool); +size_t bzObjectPoolGetObjectSize(BzObjectPool *pool); + size_t bzObjectPoolGetNumFree(BzObjectPool *pool); void *bzObjectPool(BzObjectPool *pool); void *bzObjectPoolGetObject(BzObjectPool *pool, u32 idx); diff --git a/engine/tests/CMakeLists.txt b/engine/tests/CMakeLists.txt index 234ba44..4724fe8 100644 --- a/engine/tests/CMakeLists.txt +++ b/engine/tests/CMakeLists.txt @@ -2,6 +2,9 @@ project(BreezeTests) set(CMAKE_C_STANDARD 11) +add_executable(btree_test btree_test.c) +target_link_libraries(btree_test LINK_PRIVATE Breeze) + add_executable(window_test window_test.c) target_link_libraries(window_test LINK_PRIVATE Breeze) diff --git a/engine/tests/btree_test.c b/engine/tests/btree_test.c new file mode 100644 index 0000000..a4caa28 --- /dev/null +++ b/engine/tests/btree_test.c @@ -0,0 +1,63 @@ +#define BZ_ENTRYPOINT +#include + +BzObjectPool *nodePool = NULL; +BzObjectPool *nodeStatePool = NULL; + +BzAIBTNode *printBT = NULL; + +BzAIBTStatus printAction(void *data) { + bzLogInfo("Hello, world!"); + return BZ_AIBT_FAIL; +} + +bool init(int *game) { + nodePool = bzObjectPoolCreate(&(BzObjectPoolDesc) { + .objectSize = bzAIBTGetNodeSize(), + }); + nodeStatePool = bzObjectPoolCreate(&(BzObjectPoolDesc) { + .objectSize = bzAIBTGetNodeStateSize() + }); + + // for 1..5: + // delay 1s + // print "Hello, world!" + printBT = bzAIBTMakeRoot(nodePool); + BzAIBTNode *node = bzAIBTDecorRepeat(nodePool, printBT, 5); + node = bzAIBTDecorDelay(nodePool, node, 1.0f); + bzAIBTAction(nodePool, node, printAction); + + BzAIBTState state = bzAIBTCreateState(&(BzAIBTStateDesc) { + .root = printBT, + .pool = nodeStatePool, + .userData = NULL + }); + + BzAIBTStatus status = BZ_AIBT_RUNNING; + + i32 count = 0; + while (status == BZ_AIBT_RUNNING) { + status = bzAIBTExecute(&state, 0.2f); + count++; + } + bzLogInfo("Iter: %d", count); + + bzObjectPoolDestroy(nodePool); + bzObjectPoolDestroy(nodeStatePool); + + return true; +} + +void render(float dt, int *game) { + ClearBackground(WHITE); + +} + +bool bzMain(BzAppDesc *appDesc, int argc, const char **argv) { + appDesc->init = (BzAppInitFunc) init; + appDesc->render = (BzAppRenderFunc) render; + + init(NULL); + + return false; +}