runs about 4x as fast as previous

This commit is contained in:
Joe Ardent 2026-09-20 17:06:11 -07:00
parent 279ebaa4c1
commit 4a9c2bdb02
4 changed files with 154 additions and 25 deletions

10
Cargo.lock generated
View file

@ -865,6 +865,7 @@ dependencies = [
"bevy_reflect", "bevy_reflect",
"bevy_render", "bevy_render",
"bevy_shader", "bevy_shader",
"bevy_sprite_render",
"bevy_transform", "bevy_transform",
"bevy_utils", "bevy_utils",
"bytemuck", "bytemuck",
@ -963,6 +964,7 @@ dependencies = [
"bevy_ecs", "bevy_ecs",
"bevy_input", "bevy_input",
"bevy_math", "bevy_math",
"bevy_picking",
"bevy_reflect", "bevy_reflect",
"bevy_window", "bevy_window",
"log", "log",
@ -1003,6 +1005,7 @@ dependencies = [
"bevy_math", "bevy_math",
"bevy_mesh", "bevy_mesh",
"bevy_pbr", "bevy_pbr",
"bevy_picking",
"bevy_platform", "bevy_platform",
"bevy_post_process", "bevy_post_process",
"bevy_ptr", "bevy_ptr",
@ -1018,6 +1021,8 @@ dependencies = [
"bevy_time", "bevy_time",
"bevy_transform", "bevy_transform",
"bevy_ui", "bevy_ui",
"bevy_ui_render",
"bevy_ui_widgets",
"bevy_utils", "bevy_utils",
"bevy_window", "bevy_window",
"bevy_winit", "bevy_winit",
@ -1224,11 +1229,13 @@ dependencies = [
"bevy_ecs", "bevy_ecs",
"bevy_input", "bevy_input",
"bevy_math", "bevy_math",
"bevy_mesh",
"bevy_platform", "bevy_platform",
"bevy_reflect", "bevy_reflect",
"bevy_time", "bevy_time",
"bevy_transform", "bevy_transform",
"bevy_window", "bevy_window",
"crossbeam-channel",
"tracing", "tracing",
"uuid", "uuid",
] ]
@ -1464,6 +1471,7 @@ dependencies = [
"bevy_log", "bevy_log",
"bevy_math", "bevy_math",
"bevy_mesh", "bevy_mesh",
"bevy_picking",
"bevy_reflect", "bevy_reflect",
"bevy_text", "bevy_text",
"bevy_transform", "bevy_transform",
@ -4787,8 +4795,8 @@ name = "toids"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"argh", "argh",
"async-channel",
"bevy", "bevy",
"crossbeam-channel",
"rand", "rand",
"rstar", "rstar",
] ]

View file

@ -3,11 +3,12 @@ name = "toids"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features]
debug = ["bevy/debug"]
[dependencies] [dependencies]
argh = "0.1.12" argh = "0.1.12"
bevy = { version = "0.19", default-features = false, features = ["default_app", "default_platform", "bevy_scene", "3d_bevy_render", "bevy_ui"] } bevy = { version = "0.19", default-features = false, features = ["3d", "ui"] }
crossbeam-channel = "0.5" async-channel = "2"
rand = { version = "0.10", features = ["thread_rng"] } rand = { version = "0.10", features = ["thread_rng"] }
rstar = "0.13" rstar = "0.13"

View file

@ -1,12 +1,15 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use argh::FromArgs; use argh::FromArgs;
use async_channel::{bounded, Receiver, Sender};
use bevy::{input::keyboard::Key, prelude::*, tasks::AsyncComputeTaskPool}; use bevy::{input::keyboard::Key, prelude::*, tasks::AsyncComputeTaskPool};
use crossbeam_channel::{bounded, Receiver, Sender};
use rand::RngExt; use rand::RngExt;
use rstar::{primitives::GeomWithData, RTree}; use rstar::{primitives::GeomWithData, RTree};
type Index = RTree<GeomWithData<[f32; 3], Entity>>; type IndexElement = GeomWithData<[f32; 3], Entity>;
type Index = RTree<IndexElement>;
type EntityPos = (Vec3, Entity);
// toid stuff // toid stuff
const SPEED: f32 = 2.0; const SPEED: f32 = 2.0;
@ -41,22 +44,32 @@ pub struct Velocity(Vec3);
#[derive(Debug, Default, Resource)] #[derive(Debug, Default, Resource)]
pub struct Naybs { pub struct Naybs {
index: Index, pub index: Index,
positions: Option<Box<[Vec3]>>, pub positions: Option<Vec<EntityPos>>,
} }
#[derive(Resource)] #[derive(Resource)]
pub struct RawPosChannel { pub struct RawPosChannel {
tx: Sender<Box<[Vec3]>>, tx: Sender<RawMessage>,
rx: Receiver<Box<[Vec3]>>, rx: Option<Receiver<RawMessage>>,
} }
#[derive(Resource)] #[derive(Resource)]
pub struct IndexChannel { pub struct IndexChannel {
tx: Sender<(Box<[Vec3]>, Index)>, tx: Sender<IndexMessage>,
rx: Receiver<(Box<[Vec3]>, Index)>, rx: Receiver<IndexMessage>,
} }
struct IndexMessage {
raw: Vec<EntityPos>,
index: Index,
}
struct RawMessage(Vec<EntityPos>);
#[derive(Default, Resource, Deref, DerefMut)]
pub struct BackgroundHandle(Option<bevy::tasks::Task<()>>);
#[derive(Component)] #[derive(Component)]
pub struct Toid { pub struct Toid {
pub speed: f32, pub speed: f32,
@ -66,11 +79,96 @@ pub struct Toid {
#[derive(Debug, Default, Clone, Copy, Deref, DerefMut, Resource)] #[derive(Debug, Default, Clone, Copy, Deref, DerefMut, Resource)]
pub struct LookAt(Vec3); pub struct LookAt(Vec3);
pub fn setup_channels(mut commands: Commands) {
let (raw_tx, raw_rx) = bounded(1);
commands.insert_resource(RawPosChannel {
tx: raw_tx,
rx: Some(raw_rx),
});
let (idx_tx, idx_rx) = bounded(1);
commands.insert_resource(IndexChannel {
tx: idx_tx,
rx: idx_rx,
});
}
pub fn update_naybs(
mut naybs: ResMut<Naybs>,
raw_chan: Res<RawPosChannel>,
positions: Res<Positions>,
idx_chan: Res<IndexChannel>,
) {
if let Ok(idx_msg) = idx_chan.rx.try_recv() {
naybs.index = idx_msg.index;
let _ = naybs.positions.insert(idx_msg.raw);
return;
}
if let Some(mut raw) = naybs.positions.take() {
for (i, (entity, pos)) in positions.iter().enumerate() {
raw[i] = (*pos, *entity);
}
if let Err(e) = raw_chan.tx.try_send(RawMessage(raw)) {
let RawMessage(raw) = e.into_inner();
let _ = naybs.positions.insert(raw);
}
}
}
pub fn init_naybs(positions: Res<Positions>, mut naybs: ResMut<Naybs>) {
let mut raw = Vec::with_capacity(positions.len());
for (entity, pos) in positions.iter() {
raw.push((*pos, *entity));
}
naybs.positions = Some(raw);
}
pub fn spawn_background(
mut raw_chan: ResMut<RawPosChannel>,
idx_chan: Res<IndexChannel>,
mut handle: ResMut<BackgroundHandle>,
) {
let pool = AsyncComputeTaskPool::get();
let raw_rx = raw_chan.rx.take().unwrap();
let idx_tx = idx_chan.tx.clone();
let task = pool.spawn(async move {
let tx = idx_tx;
let rx = raw_rx;
loop {
match rx.recv().await {
Ok(RawMessage(e_posns)) => {
let idx = RTree::bulk_load(
e_posns
.iter()
.map(|(p, e)| GeomWithData::new(p.to_array(), *e))
.collect(),
);
let msg = IndexMessage {
raw: e_posns,
index: idx,
};
if tx.send(msg).await.is_err() {
break;
};
}
Err(_) => break,
}
}
});
let _ = handle.0.insert(task);
}
pub fn turkey_time( pub fn turkey_time(
commands: &mut Commands, commands: &mut Commands,
r: &mut impl rand::prelude::Rng, r: &mut impl rand::prelude::Rng,
assets: &AssetServer, assets: &AssetServer,
) -> Entity { positions: &mut HashMap<Entity, Vec3>,
) {
let speed_diff = r.random_range(-SPEED_DIFF_RANGE..=SPEED_DIFF_RANGE); let speed_diff = r.random_range(-SPEED_DIFF_RANGE..=SPEED_DIFF_RANGE);
let speed = SPEED + (SPEED * speed_diff); let speed = SPEED + (SPEED * speed_diff);
let vel = unit_vec(r) * speed; let vel = unit_vec(r) * speed;
@ -78,24 +176,29 @@ pub fn turkey_time(
let x = r.random_range(-10.0..=10.0); let x = r.random_range(-10.0..=10.0);
let z = r.random_range(-10.0..=10.0); let z = r.random_range(-10.0..=10.0);
let y = r.random_range(MIN_ALTITUDE..=(MIN_ALTITUDE + 5.5)); let y = r.random_range(MIN_ALTITUDE..=(MIN_ALTITUDE + 5.5));
commands
let entity = commands
.spawn(( .spawn((
Velocity(vel), Velocity(vel),
Buddies::default(), Buddies::default(),
Toid { speed, buddies }, Toid { speed, buddies },
Transform::from_xyz(x, y, z), Transform::from_xyz(x, y, z),
Visibility::Visible,
)) ))
.with_children(|t| { .with_children(|t| {
t.spawn(WorldAssetRoot( t.spawn(WorldAssetRoot(
assets.load(GltfAssetLabel::Scene(0).from_asset("models/toid.glb")), assets.load(GltfAssetLabel::Scene(0).from_asset("models/boid.glb")),
)) ))
.insert(Transform::default()); .insert(Transform::default())
.insert(Visibility::Visible);
// .insert(Transform::from_rotation(Quat::from_axis_angle( // .insert(Transform::from_rotation(Quat::from_axis_angle(
// Vec3::Y, // Vec3::Y,
// -std::f32::consts::FRAC_PI_2, // -std::f32::consts::FRAC_PI_2,
// ))); // )));
}) })
.id() .id();
positions.insert(entity, Vec3::new(x, y, z));
} }
pub fn update_vel( pub fn update_vel(

View file

@ -1,12 +1,12 @@
use std::time::Duration;
use bevy::{ use bevy::{
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
input::keyboard::Key, input::keyboard::Key,
prelude::*, prelude::*,
}; };
use rstar::RTree;
use toids::{ use toids::{
rotate_camera, turkey_time, update_buddies, update_pos, update_vel, Config, LookAt, Positions, init_naybs, rotate_camera, setup_channels, spawn_background, turkey_time, update_buddies,
update_naybs, update_pos, update_vel, BackgroundHandle, Config, LookAt, Naybs, Positions,
}; };
fn main() { fn main() {
@ -22,13 +22,25 @@ fn main() {
.insert_resource(ClearColor(Color::srgb(0.64, 0.745, 0.937))) // a nice light blue .insert_resource(ClearColor(Color::srgb(0.64, 0.745, 0.937))) // a nice light blue
.insert_resource(GlobalAmbientLight { .insert_resource(GlobalAmbientLight {
color: Color::WHITE, color: Color::WHITE,
brightness: 1.0, brightness: 150.0,
affects_lightmapped_meshes: true,
..Default::default() ..Default::default()
}) })
.insert_resource(Naybs {
index: RTree::new(),
positions: None,
})
.insert_resource(BackgroundHandle::default())
.insert_resource(LookAt::default()) .insert_resource(LookAt::default())
.add_systems(Startup, setup) .add_systems(
Startup,
(setup_channels, setup, init_naybs, spawn_background).chain(),
)
.add_systems(Update, (update_pos, update_buddies, update_vel).chain()) .add_systems(Update, (update_pos, update_buddies, update_vel).chain())
.add_systems(Update, (rotate_camera, close_on_esc)) .add_systems(
Update,
(rotate_camera, close_on_esc, update_naybs.after(update_pos)),
)
//.add_systems(Update, update_gizmos) //.add_systems(Update, update_gizmos)
.run(); .run();
} }
@ -37,6 +49,7 @@ fn setup(
mut commands: Commands, mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>, mut materials: ResMut<Assets<StandardMaterial>>,
mut positions: ResMut<Positions>,
config: Res<Config>, config: Res<Config>,
models: Res<AssetServer>, models: Res<AssetServer>,
) { ) {
@ -49,18 +62,20 @@ fn setup(
// plane // plane
commands.spawn(( commands.spawn((
Mesh3d(meshes.add(Mesh::from(Plane3d::new(Vec3::Y, Vec2::splat(500.0))))), Mesh3d(meshes.add(Mesh::from(Plane3d::new(Vec3::Y, Vec2::splat(500.0))))),
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))), MeshMaterial3d(materials.add(Color::srgb(0.3, 0.9, 0.3))),
Visibility::Visible,
Transform::default(), Transform::default(),
)); ));
// let toid_model = models.load("models/boid.glb#Scene0"); // let toid_model = models.load("models/boid.glb#Scene0");
for _ in 0..config.toids { for _ in 0..config.toids {
let _ = turkey_time(&mut commands, rand, &models); turkey_time(&mut commands, rand, &models, &mut positions.0);
} }
// instructions // instructions
commands.spawn(( commands.spawn((
Visibility::Visible,
Node { Node {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
top: px(12.0), top: px(12.0),
@ -81,6 +96,7 @@ fn close_on_esc(
mut commands: Commands, mut commands: Commands,
focused_windows: Query<(Entity, &Window)>, focused_windows: Query<(Entity, &Window)>,
input: Res<ButtonInput<Key>>, input: Res<ButtonInput<Key>>,
mut handle: ResMut<BackgroundHandle>,
) { ) {
for (window, focus) in focused_windows.iter() { for (window, focus) in focused_windows.iter() {
if !focus.focused { if !focus.focused {
@ -88,6 +104,7 @@ fn close_on_esc(
} }
if input.just_pressed(Key::Escape) { if input.just_pressed(Key::Escape) {
drop((*handle).take());
commands.entity(window).despawn(); commands.entity(window).despawn();
} }
} }