-
-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Performance with lots of entities #22
Comments
Nice find, this will be an important one to get right. The solution here may be a kind of This would also create a clear division between what should be I have considered |
making the observers for OnRun etc global would already cut down on entity count quite a bit. from a consumer of beet's API this feels like a pretty neutral change. doesn't really add or remove any boilerplate in my code. Before: (per-entity observers) #[derive(Clone, Component, Debug, Reflect, Action)]
#[require(ContinueRun, RootIsTargetEntity)]
#[observers(on_run)]
pub struct OrbitKeeperExecutor {
stuff: u32, // etc
}
fn on_run(
trigger: Trigger<OnRun>,
q: Query<(&TargetEntity, &OrbitKeeperExecutor)>,
q_ships: Query<&Position, With<Ship>>,
mut commands: Commands,
) {
let Ok((target_entity, orbit_keeper)) = q.get(trigger.entity()) else {
warn!("OrbitKeeper: Entity not found");
return;
};
let ship_pos = q_ships.get(target_entity.0).unwrap();
// so something with the ship for OnRun...
} After: (global observers) #[derive(Clone, Component, Debug, Reflect, Action)]
#[require(ContinueRun, RootIsTargetEntity)]
#[observers(on_run)]
pub struct OrbitKeeperExecutor {
stuff: u32, // etc
}
fn on_run(
trigger: Trigger<OnRun>,
q: Query<&OrbitKeeperExecutor>,
q_ships: Query<&Position, With<Ship>>,
mut commands: Commands,
) {
let OnRun{ target_entity, tree_entity } = trigger.event();
let Ok(orbit_keeper) = q.get(tree_entity) else {
warn!("OrbitKeeper: Entity not found");
return;
};
let ship_pos = q_ships.get(target_entity).unwrap();
// so something with the ship for OnRun...
} In fact this probably reduces boilerplate, since i always need the target entity and sometimes don't care about fetching the action component in OnRun, if i'm just doing a bit of logging – in which case it's one fewer Query. |
Moving to a shared tree – ie, one copy of each unique behavior tree in the app – seems a bit more complex, since at the moment when i spawn a behavior tree for a ship, i randomly pick an altitude and store that in the action component: It's also very convenient at the moment that you can just recursively despawn a behavior tree and it cleans up any intermediate state, without worrying about it polluting the target entity. |
I really like this solution of |
yes it would close #23 i think, as long as all the triggers did this. i think i saw one relating to ChildResult or something too. I think perhaps in this scenario #[derive(Clone, Component, Debug, Reflect, Action)]
#[require(ContinueRun, RootIsTargetEntity)]
#[observers(on_run)] /// <--- Redundant?
pub struct OrbitKeeperExecutor {
stuff: u32, // etc
}
// ..
app.add_observer(on_run);
// ..
fn on_run(
trigger: Trigger<OnRun<OrbitKeeperExecutor>>, // <-- OnRun<T>
q: Query<&OrbitKeeperExecutor>,
q_ships: Query<&Position, With<Ship>>,
mut commands: Commands,
) {
let OnRun{ target_entity, tree_entity } = trigger.event();
// ...
} |
and does that mean |
@RJ I really like this 'OnRun Context' proposal pub struct OnRun{
agent: Entity,
tree: Entity
} The more i think about it the more problems it seems to solve, particularly global observers and deprecating // Use case 1: specify tree, may be a child of the agent or shared
world.trigger(OnRun::new_with_tree(agent_entity,tree_entity));
// Use case 2: agent is root
let agent = world.spawn((
Name::new("Spaceship"),
Mesh::new("ship.glb"),
SequenceFlow
))
.with_child(FlyToMoon)
.id();
world.trigger(OnRun::new(agent)); A really nice part of use case 2 is we get impl OnRun{
/// check if the agent is the same entity as the tree
pub fn is_root(&self) -> bool{
self.agent == self.tree
}
} GenericsIf we went with per-component generic Explicit BindingI feel like there may still be some value in the explicit binding of Components, Observers and Systems, ie At the moment I'm thinking about the automatic registration and cleanup of observers/systems to solve #18, i also very commonly get stung by 'I added the component but nothing happened'. |
without generics for global OnRun observers, each time any component type needed OnRunning the triggers would fire for all components, so each trigger fn would have to do its own check if the as i understand it, it would mean that my on_run observer defined in the |
that said i'm not exactly sure how it would work with generics.. |
good point ive replied on discord. |
Looks like adding an entity to a behavior tree averages about 5-6 additional entities being spawned, for the various observers.
Trigger functions for the OnRun, OnRunResult, OnChildResult, etc.
When i spawn 1000 of my ship entities with a small behavior tree (a hierarchy of ~7 entities) i end up with over 40,000 entities created. ie, 40 entities for every one of my character entities. This includes the behavior tree nodes plus all their observers.
Here I am running 3000 ships (128k entities), each ship running the OrbitKeeper action, which is a PID controller to maintain a stable orbit within the (simplified) newtonian gravity physics environment.
Video: 3000 characters each with a behavior tree
https://github.com/user-attachments/assets/94ef5cab-b8c1-4720-a67b-444aa7b5238e
I have noticed a slight fps reduction with higher entity counts. I tested it by just putting the main executor action component directly on the character so the same systems would be running, see inline comments for fps change:
The orbiting behavior is steady state, it basically runs forever, never triggering success or failure once it gets going.
So there shouldn't really be any observers triggering.
Wondering if the overhead of having lots of entities created is generally slowing things down, or what.
Filing this in case anyone has any insight. I will endeavour to update it when I do some proper profiling.
The text was updated successfully, but these errors were encountered: