Refactor behaviour tree decorators

This commit is contained in:
2024-01-10 21:17:39 +01:00
parent b09f1d4ca9
commit 93b665b290
2 changed files with 163 additions and 115 deletions

View File

@@ -253,59 +253,104 @@ void bzBTDestroyState(BzBTState *state) {
bzMemSet(state, 0, sizeof(*state)); bzMemSet(state, 0, sizeof(*state));
} }
void bzBTStateAppend(BzBTState *state, BzBTNodeState *nodeState) { typedef struct BzBTExecState {
BzBTNodeState *first;
BzBTNodeState *last;
void *userData;
} BzBTExecState;
typedef struct BzBTExecStatus {
BzBTStatus status;
BzBTExecState state;
} BzBTExecStatus;
BzBTNodeState *execStatePopFront(BzBTExecState *state) {
BZ_ASSERT(state->first);
BzBTNodeState *popped = state->first;
if (state->first)
state->first = state->first->next;
if (state->first == NULL)
state->last = NULL;
popped->prev = NULL;
popped->next = NULL;
return popped;
}
void execStatePushBack(BzBTExecState *state, BzBTNodeState *nodeState) {
BZ_ASSERT(state);
nodeState->next = NULL; nodeState->next = NULL;
nodeState->prev = state->_last; nodeState->prev = state->last;
if (state->_last)
state->_last->next = nodeState; if (state->last) {
else state->last->next = nodeState;
state->_first = nodeState; } else {
state->_last = nodeState; state->first = nodeState;
state->last = nodeState;
}
state->last = nodeState;
} }
void bzBTStatePop(BzBTState *state, BzBTNodeState *nodeState) { void execStateClear(BzBTExecState *state, BzObjectPool *pool) {
if (state->_first == nodeState) state->_first = nodeState->next; BzBTNodeState *pState = state->first;
if (state->_last == nodeState) state->_last = nodeState->prev; while (pState) {
BzBTNodeState *next = nodeState->next; BzBTNodeState *next = pState->next;
BzBTNodeState *prev = nodeState->prev; bzObjectPoolRelease(pool, pState);
if (nodeState->prev) pState = next;
nodeState->prev->next = next; }
if (nodeState->next) state->first = NULL;
nodeState->next->prev = prev; state->last = NULL;
nodeState->next = NULL;
nodeState->prev = NULL;
} }
void bzBTStateRenew(BzBTState *oldState, BzBTState *newState, BzBTNodeState *nodeState) { void execStateMerge(BzBTExecState *state, BzBTExecState *toMerge) {
// Pop nodeState and transfer it to the back BzBTNodeState *pState = toMerge->first;
bzBTStatePop(oldState, nodeState); while (pState) {
bzBTStateAppend(newState, nodeState); BzBTNodeState *next = pState->next;
} execStatePushBack(state, pState);
BzBTNodeState *bzBTStatePool(BzBTState *state, const BzBTNode *node) { pState = next;
BzBTNodeState *nodeState = bzObjectPool(state->nodeStatePool); }
nodeState->next = NULL;
nodeState->prev = NULL; toMerge->first = NULL;
nodeState->node = node; toMerge->last = NULL;
return nodeState;
}
void bzBTStateRelease(BzBTState *state, BzBTNodeState *nodeState) {
bzBTStatePop(state, nodeState);
bzObjectPoolRelease(state->nodeStatePool, nodeState);
} }
bool nodeMatchesState(const BzBTNode *node, const BzBTNodeState *state) {
return bzBTNodeMatchesState(node, state); /**
*
* @param node
* @param state
* @return
*/
bool nodeMatchesState(const BzBTNode *node, const BzBTExecState *state) {
return state->first && state->first->node == node;
} }
BzBTNodeState *getNextNodeState(const BzBTNode *node, BzBTNodeState *nodeState) { /**
if (nodeState && nodeMatchesState(node, nodeState)) * Return BzBTNodeState for given node, if node matches first state node.
return nodeState->next; */
return nodeState; static BzBTNodeState *getNodeState(const BzBTNode *node, BzBTExecState *state) {
if (nodeMatchesState(node, state)) {
return execStatePopFront(state);
}
return NULL;
}
static BzBTNodeState *ensureValidNodeState(const BzBTNode *node, BzObjectPool *pool, BzBTNodeState *existing) {
if (existing)
return existing;
existing = bzObjectPool(pool);
existing->node = node;
existing->prev = NULL;
existing->next = NULL;
bzMemSet(&existing->as, 0, sizeof(existing->as));
return existing;
}
static void releaseNodeState(BzObjectPool *pool, BzBTNodeState *nodeState) {
bzObjectPoolRelease(pool, nodeState);
} }
static inline BzBTStatus bzBTExecuteNode(const BzBTNode *node, f32 dt, static inline BzBTExecStatus bzBTExecuteNode(const BzBTNode *node, f32 dt,
BzBTNodeState *nodeState, BzBTExecState *nodeState, BzObjectPool *statePool);
BzBTState *oldState, BzBTState *newState); #if 0
static inline BzBTStatus bzBTExecuteComposite(const BzBTNode *node, f32 dt, static inline BzBTExecStatus bzBTExecuteComposite(const BzBTNode *node, f32 dt,
BzBTNodeState *nodeState, BzBTExecState *nodeState, BzObjectPool *statePool) {
BzBTState *oldState, BzBTState *newState) {
BzBTNodeState *nextState = getNextNodeState(node, nodeState); BzBTNodeState *nextState = getNextNodeState(node, nodeState);
BzBTNode *start = node->first; BzBTNode *start = node->first;
bool isParallel = node->type == BZ_BT_COMP_PARALLEL_SEQUENCE || bool isParallel = node->type == BZ_BT_COMP_PARALLEL_SEQUENCE ||
@@ -395,107 +440,111 @@ static inline BzBTStatus bzBTExecuteComposite(const BzBTNode *node, f32 dt,
} }
return status; return status;
} }
static inline BzBTStatus bzBTExecuteDecorator(const BzBTNode *node, f32 dt, #endif
BzBTNodeState *nodeState, static inline BzBTExecStatus bzBTExecuteDecorator(const BzBTNode *node, f32 dt,
BzBTState *oldState, BzBTState *newState) { BzBTExecState *state, BzObjectPool *statePool) {
// Ensure decorator has only one child, if any // Ensure decorator has only one child, if any
BZ_ASSERT(!node->first || node->first == node->last); BZ_ASSERT(!node->first || node->first == node->last);
BzBTNodeState *nextState = getNextNodeState(node, nodeState);
BzBTNodeState *nodeState = getNodeState(node, state);
BzBTExecStatus retStatus = { .status = BZ_BT_ERROR };
switch (node->type) { switch (node->type) {
case BZ_BT_DECOR_REPEAT: case BZ_BT_DECOR_REPEAT:
if (!nodeMatchesState(node, nodeState)) { nodeState = ensureValidNodeState(node, statePool, nodeState);
BzBTNodeState *newNodeState = bzBTStatePool(oldState, node); execStatePushBack(&retStatus.state, nodeState);
newNodeState->as.repeat.iter = 0;
bzBTStateAppend(newState, newNodeState);
nodeState = newNodeState;
} else {
bzBTStateRenew(oldState, newState, nodeState);
}
break; break;
case BZ_BT_DECOR_DELAY: case BZ_BT_DECOR_DELAY:
if (!nodeMatchesState(node, nodeState)) { nodeState = ensureValidNodeState(node, statePool, nodeState);
BzBTNodeState *newNodeState = bzBTStatePool(oldState, node);
newNodeState->as.delay.elapsed = dt;
bzBTStateAppend(newState, newNodeState);
return BZ_BT_RUNNING;
}
nodeState->as.delay.elapsed += dt; nodeState->as.delay.elapsed += dt;
execStatePushBack(&retStatus.state, nodeState);
if (nodeState->as.delay.elapsed < node->as.delay.ms) { if (nodeState->as.delay.elapsed < node->as.delay.ms) {
bzBTStateRenew(oldState, newState, nodeState); retStatus.status = BZ_BT_RUNNING;
return BZ_BT_RUNNING; return retStatus;
} }
BZ_ASSERT(nodeState == execStatePopFront(&retStatus.state));
releaseNodeState(statePool, nodeState);
break; break;
default: default:
break; break;
} }
// Implicit success, if no children are present // Implicit success, if no children are present
BzBTStatus inStatus = BZ_BT_SUCCESS; BzBTExecStatus inStatus = { .status = BZ_BT_SUCCESS };
if (node->first) if (node->first)
inStatus = bzBTExecuteNode(node->first, dt, nextState, oldState, newState); inStatus = bzBTExecuteNode(node->first, dt, state, statePool);
// Propagate ERROR, RUNNING up // Propagate ERROR, RUNNING up
if (inStatus == BZ_BT_ERROR) if (inStatus.status == BZ_BT_ERROR) {
return BZ_BT_ERROR; execStateClear(&inStatus.state, statePool);
if (inStatus == BZ_BT_RUNNING) execStateClear(&retStatus.state, statePool);
return BZ_BT_RUNNING; retStatus.status = BZ_BT_ERROR;
return retStatus;
}
if (inStatus.status == BZ_BT_RUNNING) {
execStateMerge(&retStatus.state, &inStatus.state);
retStatus.status = BZ_BT_RUNNING;
return retStatus;
}
BZ_ASSERT(inStatus.state.first == NULL);
BzBTStatus status = BZ_BT_ERROR;
switch (node->type) { switch (node->type) {
case BZ_BT_DECOR_DUMMY: case BZ_BT_DECOR_DUMMY:
case BZ_BT_DECOR_DELAY: // Delay already handled case BZ_BT_DECOR_DELAY: // Delay already handled
status = inStatus; retStatus.status = inStatus.status;
break; break;
case BZ_BT_DECOR_SUCCESS: case BZ_BT_DECOR_SUCCESS:
status = BZ_BT_SUCCESS; retStatus.status = BZ_BT_SUCCESS;
break; break;
case BZ_BT_DECOR_FAIL: case BZ_BT_DECOR_FAIL:
status = BZ_BT_FAIL; retStatus.status = BZ_BT_FAIL;
break; break;
case BZ_BT_DECOR_INVERT: case BZ_BT_DECOR_INVERT:
if (inStatus == BZ_BT_FAIL) if (inStatus.status == BZ_BT_FAIL)
status = BZ_BT_SUCCESS; retStatus.status = BZ_BT_SUCCESS;
if (inStatus == BZ_BT_SUCCESS) if (inStatus.status == BZ_BT_SUCCESS)
status = BZ_BT_FAIL; retStatus.status = BZ_BT_FAIL;
break; break;
case BZ_BT_DECOR_UNTIL_SUCCESS: case BZ_BT_DECOR_UNTIL_SUCCESS:
if (inStatus == BZ_BT_SUCCESS) if (inStatus.status == BZ_BT_SUCCESS)
status = BZ_BT_SUCCESS; retStatus.status = BZ_BT_SUCCESS;
else else
status = BZ_BT_RUNNING; retStatus.status = BZ_BT_RUNNING;
break; break;
case BZ_BT_DECOR_UNTIL_FAIL: case BZ_BT_DECOR_UNTIL_FAIL:
if (inStatus == BZ_BT_FAIL) if (inStatus.status == BZ_BT_FAIL)
status = BZ_BT_SUCCESS; retStatus.status = BZ_BT_SUCCESS;
else else
status = BZ_BT_RUNNING; retStatus.status = BZ_BT_RUNNING;
break; break;
case BZ_BT_DECOR_REPEAT: case BZ_BT_DECOR_REPEAT:
BZ_ASSERT(nodeState->node == node); BZ_ASSERT(nodeMatchesState(node, &retStatus.state));
BZ_ASSERT(nodeState);
nodeState->as.repeat.iter++; nodeState->as.repeat.iter++;
if (nodeState->as.repeat.iter >= node->as.repeat.n) { if (nodeState->as.repeat.iter >= node->as.repeat.n) {
bzBTStateRelease(newState, nodeState); BZ_ASSERT(nodeState == execStatePopFront(&retStatus.state));
status = inStatus; releaseNodeState(statePool, nodeState);
retStatus.status = inStatus.status;
break; break;
} }
status = BZ_BT_RUNNING; retStatus.status = BZ_BT_RUNNING;
break; break;
default: default:
break; break;
} }
return status; return retStatus;
} }
static inline BzBTStatus bzBTExecuteNode(const BzBTNode *node, f32 dt, static inline BzBTExecStatus bzBTExecuteNode(const BzBTNode *node, f32 dt,
BzBTNodeState *nodeState, BzBTExecState *nodeState, BzObjectPool *statePool) {
BzBTState *oldState, BzBTState *newState) { BzBTExecStatus status = { .status = BZ_BT_ERROR };
BzBTStatus status = BZ_BT_ERROR;
switch (node->type) { switch (node->type) {
case BZ_BT_COMP_SELECTOR: case BZ_BT_COMP_SELECTOR:
case BZ_BT_COMP_SEQUENCE: case BZ_BT_COMP_SEQUENCE:
case BZ_BT_COMP_PARALLEL_SELECTOR: case BZ_BT_COMP_PARALLEL_SELECTOR:
case BZ_BT_COMP_PARALLEL_SEQUENCE: case BZ_BT_COMP_PARALLEL_SEQUENCE:
status = bzBTExecuteComposite(node, dt, nodeState, oldState, newState); //status = bzBTExecuteComposite(node, dt, nodeState, statePool);
break; break;
case BZ_BT_DECOR_DUMMY: case BZ_BT_DECOR_DUMMY:
case BZ_BT_DECOR_SUCCESS: case BZ_BT_DECOR_SUCCESS:
@@ -505,11 +554,12 @@ static inline BzBTStatus bzBTExecuteNode(const BzBTNode *node, f32 dt,
case BZ_BT_DECOR_UNTIL_FAIL: case BZ_BT_DECOR_UNTIL_FAIL:
case BZ_BT_DECOR_REPEAT: case BZ_BT_DECOR_REPEAT:
case BZ_BT_DECOR_DELAY: case BZ_BT_DECOR_DELAY:
status = bzBTExecuteDecorator(node, dt, nodeState, oldState, newState); status = bzBTExecuteDecorator(node, dt, nodeState, statePool);
break; break;
case BZ_BT_ACTION: case BZ_BT_ACTION:
BZ_ASSERT(node->as.action.fn); BZ_ASSERT(node->as.action.fn);
return node->as.action.fn(oldState->userData, dt); status.status = node->as.action.fn(nodeState->userData, dt);
break;
} }
return status; return status;
} }
@@ -522,27 +572,22 @@ BzBTStatus bzBTExecute(BzBTState *state, f32 dt) {
return BZ_BT_FAIL; return BZ_BT_FAIL;
} }
BzBTState newState = {
._first = NULL,
._last = NULL,
.nodeStatePool = state->nodeStatePool
};
BzBTNodeState *first = state->_first; BzBTNodeState *first = state->_first;
const BzBTNode *firstNode = first ? first->node : state->root; const BzBTNode *firstNode = first ? first->node : state->root;
BzBTStatus status = bzBTExecuteNode(firstNode, dt, first, state, &newState); BzBTExecState execState = {
.first = state->_first,
.last = state->_last,
.userData = state->userData
};
BzBTExecStatus status = bzBTExecuteNode(firstNode, dt, &execState, state->nodeStatePool);
if (status.status == BZ_BT_ERROR)
execStateClear(&status.state, state->nodeStatePool);
BZ_ASSERT(execState.first == NULL && execState.last == NULL);
// Release leftover states state->_first = status.state.first;
BzBTNodeState *pState = state->_first; state->_last = status.state.last;
while (pState) {
BzBTNodeState *next = pState->next;
bzBTStateRelease(state, pState);
pState = next;
}
state->_first = newState._first;
state->_last = newState._last;
switch (status) { switch (status.status) {
case BZ_BT_SUCCESS: case BZ_BT_SUCCESS:
state->onSuccess && state->onSuccess(state->userData, dt); state->onSuccess && state->onSuccess(state->userData, dt);
break; break;
@@ -556,5 +601,5 @@ BzBTStatus bzBTExecute(BzBTState *state, f32 dt) {
break; break;
} }
return status; return status.status;
} }

View File

@@ -117,6 +117,8 @@ void igVisualizeBTState(const BzBTNode *node, const BzBTNodeState *state, bool s
void igRenderBT(BzBTState *state) { void igRenderBT(BzBTState *state) {
const BzBTNode *root = state->root; const BzBTNode *root = state->root;
if (igBegin("BehaviourTree", NULL, 0)) { if (igBegin("BehaviourTree", NULL, 0)) {
igText("NodeState pool: %d", bzObjectPoolGetNumFree(nodeStatePool));
igSeparatorText("");
igVisualizeBTState(root, state->_first, false, 0); igVisualizeBTState(root, state->_first, false, 0);
} }
igEnd(); igEnd();
@@ -126,6 +128,7 @@ void render(float dt, int *game) {
ClearBackground(WHITE); ClearBackground(WHITE);
BzBTStatus status = bzBTExecute(&agentState, dt); BzBTStatus status = bzBTExecute(&agentState, dt);
assert(status != BZ_BT_ERROR);
rlImGuiBegin(); rlImGuiBegin();
igRenderBT(&agentState); igRenderBT(&agentState);