feat: add some docs

This commit is contained in:
dusk 2024-08-29 15:49:23 +03:00
parent 07405168e8
commit ac91b9a53e
Signed by: dusk
SSH Key Fingerprint: SHA256:Abmvag+juovVufZTxyWY8KcVgrznxvBjQpJesv071Aw
8 changed files with 48 additions and 17 deletions

View File

@ -3,8 +3,8 @@ extends EditorPlugin
func _enter_tree() -> void: func _enter_tree() -> void:
add_autoload_singleton("BoidsProcess_2D", "res://addons/boids/boids_process_2d.tscn") add_autoload_singleton("ProcessBoids", "res://addons/boids/process_boids.tscn")
func _exit_tree() -> void: func _exit_tree() -> void:
remove_autoload_singleton("BoidsProcess_2D") remove_autoload_singleton("ProcessBoids")

View File

@ -1,4 +1,3 @@
[gd_scene format=3 uid="uid://c84c62urmhxbm"] [gd_scene format=3 uid="uid://c84c62urmhxbm"]
[node name="BoidsProcess" type="BoidsProcess"] [node name="BoidsProcess" type="BoidsProcess"]
process_2d = true

View File

@ -2,7 +2,7 @@ extends Node2D
func _ready() -> void: func _ready() -> void:
for flock in get_children(): for flock in get_children():
for i in 1000: spawnBoid(flock) for i in 100: spawnBoid(flock)
func spawnBoid(flock: Flock2D) -> void: func spawnBoid(flock: Flock2D) -> void:
var boid: Boid2D = preload("../example_boid.tscn").instantiate() var boid: Boid2D = preload("../example_boid.tscn").instantiate()

View File

@ -7,6 +7,8 @@ use crate::{BoidProperties, Flock2D};
#[class(init, base=Node2D)] #[class(init, base=Node2D)]
pub struct Boid2D { pub struct Boid2D {
#[export] #[export]
/// The properties of this boid.
/// Note: this cannot be changed in runtime, aside from removing and readding the node.
properties: Gd<BoidProperties>, properties: Gd<BoidProperties>,
props: BoidProperties, props: BoidProperties,
vel: Vec2, vel: Vec2,
@ -18,18 +20,21 @@ pub struct Boid2D {
impl Boid2D { impl Boid2D {
#[func] #[func]
#[inline(always)] #[inline(always)]
/// Get the current velocity of this boid.
fn get_velocity(&self) -> Vector2 { fn get_velocity(&self) -> Vector2 {
Vector2::new(self.vel.x, self.vel.y) Vector2::new(self.vel.x, self.vel.y)
} }
#[func] #[func]
#[inline(always)] #[inline(always)]
/// Get the ID of this boid.
pub fn get_id(&self) -> i64 { pub fn get_id(&self) -> i64 {
self.base().instance_id().to_i64() self.base().instance_id().to_i64()
} }
#[func] #[func]
#[inline(always)] #[inline(always)]
/// Get the flock ID of this boid.
pub fn get_flock_id(&self) -> i64 { pub fn get_flock_id(&self) -> i64 {
self.flock_id self.flock_id
} }

View File

@ -5,20 +5,26 @@ use godot::prelude::*;
pub struct BoidProperties { pub struct BoidProperties {
#[export] #[export]
#[init(val = 4.0)] #[init(val = 4.0)]
/// Max speed of this boid.
pub max_speed: f32, pub max_speed: f32,
#[export] #[export]
#[init(val = 1.0)] #[init(val = 1.0)]
/// Max force that will be applied to this boid at once.
pub max_force: f32, pub max_force: f32,
#[export] #[export]
#[init(val = 1.5)] #[init(val = 1.5)]
/// How much to align with other boids.
pub alignment: f32, pub alignment: f32,
#[export] #[export]
#[init(val = 1.0)] #[init(val = 1.0)]
/// How much to cohere to other boids.
pub cohesion: f32, pub cohesion: f32,
#[export] #[export]
#[init(val = 1.2)] #[init(val = 1.2)]
/// How much to seperate from other boids.
pub seperation: f32, pub seperation: f32,
#[export] #[export]
#[init(val = 0.8)] #[init(val = 0.8)]
/// How much to follow a flock target (if there is one).
pub targeting: f32, pub targeting: f32,
} }

View File

@ -9,9 +9,12 @@ use super::Flock;
#[class(init, base=Node2D)] #[class(init, base=Node2D)]
pub struct Flock2D { pub struct Flock2D {
#[export] #[export]
/// Properties of this flock.
/// Note: this cannot be changed in runtime, aside from removing and readding the node.
properties: Gd<FlockProperties>, properties: Gd<FlockProperties>,
props: FlockProperties, props: FlockProperties,
#[export] #[export]
/// A target node for the flock to follow.
target: Option<Gd<Node2D>>, target: Option<Gd<Node2D>>,
pub boids: FxIndexMap<i64, Gd<Boid2D>>, pub boids: FxIndexMap<i64, Gd<Boid2D>>,
base: Base<Node2D>, base: Base<Node2D>,

View File

@ -5,14 +5,14 @@ use godot::prelude::*;
pub struct FlockProperties { pub struct FlockProperties {
#[export] #[export]
#[init(val = 625.0)] #[init(val = 625.0)]
/// squared /// Distance (squared) to apply seperation force between boids in a flock.
pub goal_seperation: f32, pub goal_seperation: f32,
#[export] #[export]
#[init(val = 2500.0)] #[init(val = 2500.0)]
/// squared /// Distance (squared) to apply alignment force between boids in a flock.
pub goal_alignment: f32, pub goal_alignment: f32,
#[export] #[export]
#[init(val = 2500.0)] #[init(val = 2500.0)]
/// squared /// Distance (squared) to apply cohesion force between boids in a flock.
pub goal_cohesion: f32, pub goal_cohesion: f32,
} }

View File

@ -73,13 +73,20 @@ unsafe impl ExtensionLibrary for BoidsExtension {
#[derive(GodotClass)] #[derive(GodotClass)]
#[class(init, base=Node)] #[class(init, base=Node)]
/// Node that will make calls automatically to process 2D/3D boids, providing some configuration options.
/// It's best to use this as an autoload singleton.
pub struct BoidsProcess { pub struct BoidsProcess {
#[export] #[export]
#[init(val = true)]
/// Whether to process 2D boids or not.
process_2d: bool, process_2d: bool,
#[export] #[export]
#[init(val = true)]
/// Whether to process 3D boids or not.
process_3d: bool, process_3d: bool,
#[export] #[export]
#[init(val = 1)] #[init(val = 1)]
/// Process boids per N physics ticks.
process_per_tick: i64, process_per_tick: i64,
boids: Option<Gd<Boids>>, boids: Option<Gd<Boids>>,
engine: Option<Gd<Engine>>, engine: Option<Gd<Engine>>,
@ -117,6 +124,7 @@ impl INode for BoidsProcess {
#[derive(GodotClass)] #[derive(GodotClass)]
#[class(init, base=Object)] #[class(init, base=Object)]
/// Singleton that holds all boids and flocks and manages them.
struct Boids { struct Boids {
flocks2d: FxIndexMap<i64, Gd<Flock2D>>, flocks2d: FxIndexMap<i64, Gd<Flock2D>>,
boids2d: FxIndexMap<i64, Gd<Boid2D>>, boids2d: FxIndexMap<i64, Gd<Boid2D>>,
@ -150,15 +158,25 @@ impl Boids {
impl Boids { impl Boids {
#[func] #[func]
#[inline(always)] #[inline(always)]
/// Process all 2D boids once.
/// NOTE: This function is not intended to be manually called. Prefer using `BoidsProcess` as an autoload singleton where possible.
fn process_boids_2d(&mut self) { fn process_boids_2d(&mut self) {
process_boids(&mut self.boids2d, &self.flocks2d) process_boids(&mut self.boids2d, &self.flocks2d)
} }
#[func] #[func]
#[inline(always)] #[inline(always)]
fn get_total_boid_count(&self) -> i64 { /// Gets the total 2D boid count.
fn get_total_boid_2d_count(&self) -> i64 {
self.boids2d.len() as i64 self.boids2d.len() as i64
} }
#[func]
#[inline(always)]
/// Gets the total 2D flock count.
fn get_total_flock_2d_count(&self) -> i64 {
self.flocks2d.len() as i64
}
} }
#[inline(always)] #[inline(always)]
@ -202,8 +220,8 @@ where
} }
#[cfg(feature = "stats")] #[cfg(feature = "stats")]
godot_print!( godot_print!(
"[Boids] preparing all calculations took {} ms", "[Boids] preparing all calculations took {} micros",
time.elapsed().as_millis() time.elapsed().as_micros()
); );
#[cfg(feature = "stats")] #[cfg(feature = "stats")]
@ -211,7 +229,7 @@ where
let forces: Vec<(i64, Vec3)> = calc_funcs let forces: Vec<(i64, Vec3)> = calc_funcs
.into_par_iter() .into_par_iter()
.fold( .fold(
|| Vec::<(i64, Vec3)>::with_capacity(total_boid_count / 10), || Vec::<(i64, Vec3)>::with_capacity(total_boid_count),
|mut acc, (boid_id, calc_fn)| { |mut acc, (boid_id, calc_fn)| {
let force = calc_fn(); let force = calc_fn();
acc.push((boid_id, force)); acc.push((boid_id, force));
@ -219,7 +237,7 @@ where
}, },
) )
.reduce( .reduce(
|| Vec::<(i64, Vec3)>::with_capacity(total_boid_count / 10), || Vec::<(i64, Vec3)>::with_capacity(total_boid_count),
|mut left, mut right| { |mut left, mut right| {
left.append(&mut right); left.append(&mut right);
left left
@ -227,19 +245,19 @@ where
); );
#[cfg(feature = "stats")] #[cfg(feature = "stats")]
godot_print!( godot_print!(
"[Boids] calculating all boids took {} ms", "[Boids] calculating all boids took {} micros",
time.elapsed().as_millis() time.elapsed().as_micros()
); );
#[cfg(feature = "stats")] #[cfg(feature = "stats")]
let time = std::time::Instant::now(); let time = std::time::Instant::now();
for (boid_id, force) in forces { for (boid_id, force) in forces {
let boid = boids.get_mut(&boid_id).unwrap(); let boid = unsafe { boids.get_mut(&boid_id).unwrap_unchecked() };
boid.bind_mut().apply_force(force); boid.bind_mut().apply_force(force);
} }
#[cfg(feature = "stats")] #[cfg(feature = "stats")]
godot_print!( godot_print!(
"[Boids] applying forces took {} ms", "[Boids] applying forces took {} micros",
time.elapsed().as_millis() time.elapsed().as_micros()
); );
} }