linkdd / aitoolkit

Give a brain to your game's NPCs
https://linkdd.github.io/aitoolkit/
MIT License
459 stars 25 forks source link

intended pattern for executing GOAP plans #5

Closed shadowndacorner closed 9 months ago

shadowndacorner commented 9 months ago

First off, great work on this library!

I'm a bit confused as to the intended pattern for actually executing the plans that come out of this GOAP implementation. If I'm understanding the API correctly, it seems like the apply_effects function may run more than once as part of the pathfinding process. That would seemingly make it a poor fit to hook into for eg updating an agent's nav target, or having them attack another agent, which are operations you only want to execute when you have a full plan.

Is my understanding here accurate, or am I misunderstanding how your implementation works? I'm also not seeing a way to get eg a queue of actions out of the planner, but maybe apply_effects is intended to work as essentially a visitor for that purpose?

Edit: Just noticed the dry_run argument - not sure how I missed that haha. Is that meant to differentiate between the planning process and the execution process? If so, is the blackboard meant to contain a reference to the relevant agent/actor/etc, with the planner and actions being single-instance for the application, or are they meant to be instanced for each agent? If the former, is the planner thread-safe?

linkdd commented 9 months ago

Hi, Thanks for the feedback 🙂

it seems like the apply_effects function may run more than once as part of the pathfinding process.

Yes, during the "planning phase", the apply_effects() will be called. More than once. A copy of the initial state will be made, and mutated via the apply_effects() until it is equal to the final state (the goal).

The real blackboard is left unmutated once the plan is found.

Just noticed the dry_run argument - not sure how I missed that haha.

It's ok lol I only added it in the v0.3.0 for the specific problem you mentioned. During the planning phase, this argument will be true, during the plan execution it will be false.

is the blackboard meant to contain a reference to the relevant agent/actor/etc

It's up to you. The actions can have a state (private members, a constructor, other methods, etc...). But the blackboard could hold that state as well.

I am using EnTT as an ECS. My blackboard contains a reference to the global game state, while my actions contain a reference to a view (iterable collection of entities with specific components). If the dry_run argument is false, i will iterate over that view to apply changes to both the global game state and local state of entities.

I also make use of EnTT's event dispatcher to implement a message bus, so my actions can send some messages that are picked up later on.

is the planner thread-safe?

The planner works on a copy of the initial state. The only problem might be the actions. If your actions hold some state and are not thread-safe, and you share them between multiple planners (since they are basically std::shared_ptr when they should be std::unique_ptr but I failed to implement that because they are a pain to work with, cf this reddit thread).

Something like this:

auto stateful_action = std::make_shared<my_stateful_action>(...);
auto plan1 = planner<blackboard_type>(std::vector{stateful_action}, initial1, goal1);
auto plan2 = planner<blackboard_type>(std::vector{stateful_action}, initial2, goal2);

But if your actions are self-contained, stateless, or thread-safe, then there should be no problem.

I hope this helps 🙂

linkdd commented 9 months ago

FYI: I decided to force myself to use std::unique_ptr instead of std::shared_ptr, I think I found a satisfying solution. More information in the PR #6

As a result, now the planner owns all the data that are given to it. Making it thread-safe.

linkdd commented 9 months ago

I'm closing this issue since it has been resolved.

If you have any further question, don't hesitate to open a discussion 🙂