use cudarc::driver::{
CudaContext, CudaFunction, CudaSlice, CudaStream, LaunchConfig, PushKernelArg,
};
use cudarc::nvrtc::compile_ptx;
use std::sync::Arc;
use crate::collision_free_speed::Params;
use crate::common::{Pedestrian, WallSegment};
const CFS_CUDA_SRC: &str = r#"
extern "C" __global__ void cfs_step(
const float* __restrict__ pos_x_in,
const float* __restrict__ pos_y_in,
const float* __restrict__ radius,
const float* __restrict__ desired_speed,
const float* __restrict__ dest_x,
const float* __restrict__ dest_y,
float* __restrict__ pos_x_out,
float* __restrict__ pos_y_out,
float* __restrict__ vel_x_out,
float* __restrict__ vel_y_out,
const float* __restrict__ wall_ax,
const float* __restrict__ wall_ay,
const float* __restrict__ wall_bx,
const float* __restrict__ wall_by,
unsigned int n,
unsigned int n_walls,
float time_gap,
float alpha,
float interaction_range,
float interaction_strength,
float wall_range,
float wall_strength,
float arrival_radius,
float dt)
{
unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i >= n) return;
float pxi = pos_x_in[i];
float pyi = pos_y_in[i];
float ri = radius[i];
float v0 = desired_speed[i];
float dxi = dest_x[i];
float dyi = dest_y[i];
// Desired direction toward destination + arrival taper on v0.
float to_dest_x = dxi - pxi;
float to_dest_y = dyi - pyi;
float d_to_dest = sqrtf(to_dest_x * to_dest_x + to_dest_y * to_dest_y);
float edx = 0.0f;
float edy = 0.0f;
if (d_to_dest > 1.0e-12f) {
edx = to_dest_x / d_to_dest;
edy = to_dest_y / d_to_dest;
}
float eff_v0 = v0;
if (arrival_radius > 0.0f && d_to_dest < arrival_radius) {
eff_v0 = v0 * (d_to_dest / arrival_radius);
}
// Direction-blend accumulator: alpha * e_dest + Σ repulsions.
float ax_dir = alpha * edx;
float ay_dir = alpha * edy;
// Pedestrian repulsion contribution to direction.
for (unsigned int j = 0; j < n; ++j) {
if (j == i) continue;
float ddx = pxi - pos_x_in[j];
float ddy = pyi - pos_y_in[j];
float d = sqrtf(ddx * ddx + ddy * ddy);
if (d < 1.0e-9f) continue;
float inv_d = 1.0f / d;
float ex = ddx * inv_d;
float ey = ddy * inv_d;
float clearance = d - (ri + radius[j]);
if (clearance < 0.0f) clearance = 0.0f;
float weight =
interaction_strength * expf(-clearance / interaction_range);
ax_dir += weight * ex;
ay_dir += weight * ey;
}
// Wall repulsion contribution to direction.
for (unsigned int k = 0; k < n_walls; ++k) {
float wax = wall_ax[k];
float way = wall_ay[k];
float wbx = wall_bx[k];
float wby = wall_by[k];
float abx = wbx - wax;
float aby = wby - way;
float denom = abx * abx + aby * aby;
float t = 0.0f;
if (denom > 1.0e-18f) {
t = ((pxi - wax) * abx + (pyi - way) * aby) / denom;
if (t < 0.0f) t = 0.0f;
if (t > 1.0f) t = 1.0f;
}
float cpx = wax + t * abx;
float cpy = way + t * aby;
float ddx = pxi - cpx;
float ddy = pyi - cpy;
float d = sqrtf(ddx * ddx + ddy * ddy);
if (d < 1.0e-9f) continue;
float inv_d = 1.0f / d;
float ex = ddx * inv_d;
float ey = ddy * inv_d;
float clearance = d - ri;
if (clearance < 0.0f) clearance = 0.0f;
float weight = wall_strength * expf(-clearance / wall_range);
ax_dir += weight * ex;
ay_dir += weight * ey;
}
// Normalize the chosen direction; zero accumulator → zero direction.
float a_mag = sqrtf(ax_dir * ax_dir + ay_dir * ay_dir);
float ex = 0.0f;
float ey = 0.0f;
if (a_mag > 1.0e-12f) {
ex = ax_dir / a_mag;
ey = ay_dir / a_mag;
}
// Free headroom along (ex, ey): nearest forward neighbour whose
// lateral offset is within ri + r_j of the line.
float s = 1.0e30f;
for (unsigned int j = 0; j < n; ++j) {
if (j == i) continue;
float relx = pos_x_in[j] - pxi;
float rely = pos_y_in[j] - pyi;
float forward = relx * ex + rely * ey;
if (forward <= 0.0f) continue;
float projx = ex * forward;
float projy = ey * forward;
float latx = relx - projx;
float laty = rely - projy;
float lat = sqrtf(latx * latx + laty * laty);
float r_sum = ri + radius[j];
if (lat > r_sum) continue;
float d = sqrtf(relx * relx + rely * rely);
if (d < s) s = d;
}
// Speed cap from headroom; clamp eff_v0 to it.
float cap = (s - ri) / time_gap;
if (cap < 0.0f) cap = 0.0f;
float v = eff_v0 < cap ? eff_v0 : cap;
float vx_new = ex * v;
float vy_new = ey * v;
float px_new = pxi + vx_new * dt;
float py_new = pyi + vy_new * dt;
pos_x_out[i] = px_new;
pos_y_out[i] = py_new;
vel_x_out[i] = vx_new;
vel_y_out[i] = vy_new;
}
"#;
pub struct CudaState {
ctx: Arc<CudaContext>,
stream: Arc<CudaStream>,
func: CudaFunction,
block_size: u32,
}
impl CudaState {
pub fn new() -> Result<Self, String> {
Self::with_block_size(256)
}
pub fn with_block_size(block_size: u32) -> Result<Self, String> {
if block_size == 0 {
return Err("block_size must be positive".to_string());
}
let ctx = super::new_context(0)?;
let stream = ctx.default_stream();
let ptx = compile_ptx(CFS_CUDA_SRC).map_err(|e| format!("NVRTC compile failed: {e}"))?;
let module = ctx
.load_module(ptx)
.map_err(|e| format!("module load failed: {e}"))?;
let func = module
.load_function("cfs_step")
.map_err(|e| format!("kernel lookup failed: {e}"))?;
Ok(Self {
ctx,
stream,
func,
block_size,
})
}
pub fn step(
&self,
peds: &mut [Pedestrian],
walls: &[WallSegment],
params: &Params,
dt: f64,
) -> Result<u128, String> {
let n = peds.len();
if n == 0 {
return Ok(0);
}
let stream = &self.stream;
let mut pos_x: Vec<f32> = Vec::with_capacity(n);
let mut pos_y: Vec<f32> = Vec::with_capacity(n);
let mut vel_x: Vec<f32> = vec![0.0; n];
let mut vel_y: Vec<f32> = vec![0.0; n];
let mut radius: Vec<f32> = Vec::with_capacity(n);
let mut desired_speed: Vec<f32> = Vec::with_capacity(n);
let mut dest_x: Vec<f32> = Vec::with_capacity(n);
let mut dest_y: Vec<f32> = Vec::with_capacity(n);
for p in peds.iter() {
pos_x.push(p.pos[0] as f32);
pos_y.push(p.pos[1] as f32);
radius.push(p.radius as f32);
desired_speed.push(p.desired_speed as f32);
dest_x.push(p.destination[0] as f32);
dest_y.push(p.destination[1] as f32);
}
let n_walls = walls.len();
let mut wall_ax: Vec<f32> = Vec::with_capacity(n_walls.max(1));
let mut wall_ay: Vec<f32> = Vec::with_capacity(n_walls.max(1));
let mut wall_bx: Vec<f32> = Vec::with_capacity(n_walls.max(1));
let mut wall_by: Vec<f32> = Vec::with_capacity(n_walls.max(1));
if n_walls == 0 {
wall_ax.push(0.0);
wall_ay.push(0.0);
wall_bx.push(0.0);
wall_by.push(0.0);
} else {
for w in walls {
wall_ax.push(w.a[0] as f32);
wall_ay.push(w.a[1] as f32);
wall_bx.push(w.b[0] as f32);
wall_by.push(w.b[1] as f32);
}
}
let d_pos_x: CudaSlice<f32> = stream
.clone_htod(&pos_x)
.map_err(|e| format!("htod pos_x failed: {e}"))?;
let d_pos_y: CudaSlice<f32> = stream
.clone_htod(&pos_y)
.map_err(|e| format!("htod pos_y failed: {e}"))?;
let d_radius: CudaSlice<f32> = stream
.clone_htod(&radius)
.map_err(|e| format!("htod radius failed: {e}"))?;
let d_desired_speed: CudaSlice<f32> = stream
.clone_htod(&desired_speed)
.map_err(|e| format!("htod desired_speed failed: {e}"))?;
let d_dest_x: CudaSlice<f32> = stream
.clone_htod(&dest_x)
.map_err(|e| format!("htod dest_x failed: {e}"))?;
let d_dest_y: CudaSlice<f32> = stream
.clone_htod(&dest_y)
.map_err(|e| format!("htod dest_y failed: {e}"))?;
let mut d_pos_x_out: CudaSlice<f32> = stream
.alloc_zeros(n)
.map_err(|e| format!("alloc pos_x_out failed: {e}"))?;
let mut d_pos_y_out: CudaSlice<f32> = stream
.alloc_zeros(n)
.map_err(|e| format!("alloc pos_y_out failed: {e}"))?;
let mut d_vel_x_out: CudaSlice<f32> = stream
.alloc_zeros(n)
.map_err(|e| format!("alloc vel_x_out failed: {e}"))?;
let mut d_vel_y_out: CudaSlice<f32> = stream
.alloc_zeros(n)
.map_err(|e| format!("alloc vel_y_out failed: {e}"))?;
let d_wall_ax: CudaSlice<f32> = stream
.clone_htod(&wall_ax)
.map_err(|e| format!("htod wall_ax failed: {e}"))?;
let d_wall_ay: CudaSlice<f32> = stream
.clone_htod(&wall_ay)
.map_err(|e| format!("htod wall_ay failed: {e}"))?;
let d_wall_bx: CudaSlice<f32> = stream
.clone_htod(&wall_bx)
.map_err(|e| format!("htod wall_bx failed: {e}"))?;
let d_wall_by: CudaSlice<f32> = stream
.clone_htod(&wall_by)
.map_err(|e| format!("htod wall_by failed: {e}"))?;
let n_u32 = n as u32;
let n_walls_u32 = n_walls as u32;
let time_gap = params.time_gap as f32;
let alpha = params.alpha as f32;
let interaction_range = params.interaction_range as f32;
let interaction_strength = params.interaction_strength as f32;
let wall_range = params.wall_range as f32;
let wall_strength = params.wall_strength as f32;
let arrival_radius = params.arrival_radius as f32;
let dt_f32 = dt as f32;
let grid = n.div_ceil(self.block_size as usize) as u32;
let cfg = LaunchConfig {
grid_dim: (grid.max(1), 1, 1),
block_dim: (self.block_size, 1, 1),
shared_mem_bytes: 0,
};
let t0 = std::time::Instant::now();
unsafe {
let mut b = stream.launch_builder(&self.func);
b.arg(&d_pos_x);
b.arg(&d_pos_y);
b.arg(&d_radius);
b.arg(&d_desired_speed);
b.arg(&d_dest_x);
b.arg(&d_dest_y);
b.arg(&mut d_pos_x_out);
b.arg(&mut d_pos_y_out);
b.arg(&mut d_vel_x_out);
b.arg(&mut d_vel_y_out);
b.arg(&d_wall_ax);
b.arg(&d_wall_ay);
b.arg(&d_wall_bx);
b.arg(&d_wall_by);
b.arg(&n_u32);
b.arg(&n_walls_u32);
b.arg(&time_gap);
b.arg(&alpha);
b.arg(&interaction_range);
b.arg(&interaction_strength);
b.arg(&wall_range);
b.arg(&wall_strength);
b.arg(&arrival_radius);
b.arg(&dt_f32);
b.launch(cfg)
.map_err(|e| format!("kernel launch failed: {e}"))?;
}
stream
.synchronize()
.map_err(|e| format!("stream sync failed: {e}"))?;
let kernel_us = t0.elapsed().as_micros();
stream
.memcpy_dtoh(&d_pos_x_out, &mut pos_x)
.map_err(|e| format!("dtoh pos_x failed: {e}"))?;
stream
.memcpy_dtoh(&d_pos_y_out, &mut pos_y)
.map_err(|e| format!("dtoh pos_y failed: {e}"))?;
stream
.memcpy_dtoh(&d_vel_x_out, &mut vel_x)
.map_err(|e| format!("dtoh vel_x failed: {e}"))?;
stream
.memcpy_dtoh(&d_vel_y_out, &mut vel_y)
.map_err(|e| format!("dtoh vel_y failed: {e}"))?;
for (i, p) in peds.iter_mut().enumerate() {
p.pos = [pos_x[i] as f64, pos_y[i] as f64];
p.vel = [vel_x[i] as f64, vel_y[i] as f64];
}
let _ = &self.ctx; Ok(kernel_us)
}
}
pub struct CudaResident {
ctx: Arc<CudaContext>,
stream: Arc<CudaStream>,
func: CudaFunction,
block_size: u32,
n: usize,
n_walls: usize,
d_pos_x_a: CudaSlice<f32>,
d_pos_y_a: CudaSlice<f32>,
d_vel_x_a: CudaSlice<f32>,
d_vel_y_a: CudaSlice<f32>,
d_pos_x_b: CudaSlice<f32>,
d_pos_y_b: CudaSlice<f32>,
d_vel_x_b: CudaSlice<f32>,
d_vel_y_b: CudaSlice<f32>,
d_radius: CudaSlice<f32>,
d_desired_speed: CudaSlice<f32>,
d_dest_x: CudaSlice<f32>,
d_dest_y: CudaSlice<f32>,
d_wall_ax: CudaSlice<f32>,
d_wall_ay: CudaSlice<f32>,
d_wall_bx: CudaSlice<f32>,
d_wall_by: CudaSlice<f32>,
}
impl CudaResident {
pub fn upload(peds: &[Pedestrian], walls: &[WallSegment]) -> Result<Self, String> {
Self::upload_with_block_size(peds, walls, 256)
}
pub fn upload_with_block_size(
peds: &[Pedestrian],
walls: &[WallSegment],
block_size: u32,
) -> Result<Self, String> {
if block_size == 0 {
return Err("block_size must be positive".to_string());
}
let n = peds.len();
if n == 0 {
return Err("CudaResident::upload requires at least one pedestrian".to_string());
}
let ctx = super::new_context(0)?;
let stream = ctx.default_stream();
let ptx = compile_ptx(CFS_CUDA_SRC).map_err(|e| format!("NVRTC compile failed: {e}"))?;
let module = ctx
.load_module(ptx)
.map_err(|e| format!("module load failed: {e}"))?;
let func = module
.load_function("cfs_step")
.map_err(|e| format!("kernel lookup failed: {e}"))?;
let mut pos_x = Vec::with_capacity(n);
let mut pos_y = Vec::with_capacity(n);
let mut vel_x = Vec::with_capacity(n);
let mut vel_y = Vec::with_capacity(n);
let mut radius = Vec::with_capacity(n);
let mut desired_speed = Vec::with_capacity(n);
let mut dest_x = Vec::with_capacity(n);
let mut dest_y = Vec::with_capacity(n);
for p in peds {
pos_x.push(p.pos[0] as f32);
pos_y.push(p.pos[1] as f32);
vel_x.push(p.vel[0] as f32);
vel_y.push(p.vel[1] as f32);
radius.push(p.radius as f32);
desired_speed.push(p.desired_speed as f32);
dest_x.push(p.destination[0] as f32);
dest_y.push(p.destination[1] as f32);
}
let n_walls = walls.len();
let (wall_ax, wall_ay, wall_bx, wall_by) = if n_walls == 0 {
(vec![0.0f32], vec![0.0f32], vec![0.0f32], vec![0.0f32])
} else {
let mut ax = Vec::with_capacity(n_walls);
let mut ay = Vec::with_capacity(n_walls);
let mut bx = Vec::with_capacity(n_walls);
let mut by = Vec::with_capacity(n_walls);
for w in walls {
ax.push(w.a[0] as f32);
ay.push(w.a[1] as f32);
bx.push(w.b[0] as f32);
by.push(w.b[1] as f32);
}
(ax, ay, bx, by)
};
let d_pos_x_a = stream
.clone_htod(&pos_x)
.map_err(|e| format!("htod pos_x failed: {e}"))?;
let d_pos_y_a = stream
.clone_htod(&pos_y)
.map_err(|e| format!("htod pos_y failed: {e}"))?;
let d_vel_x_a = stream
.clone_htod(&vel_x)
.map_err(|e| format!("htod vel_x failed: {e}"))?;
let d_vel_y_a = stream
.clone_htod(&vel_y)
.map_err(|e| format!("htod vel_y failed: {e}"))?;
let d_pos_x_b = stream
.alloc_zeros(n)
.map_err(|e| format!("alloc pos_x_b failed: {e}"))?;
let d_pos_y_b = stream
.alloc_zeros(n)
.map_err(|e| format!("alloc pos_y_b failed: {e}"))?;
let d_vel_x_b = stream
.alloc_zeros(n)
.map_err(|e| format!("alloc vel_x_b failed: {e}"))?;
let d_vel_y_b = stream
.alloc_zeros(n)
.map_err(|e| format!("alloc vel_y_b failed: {e}"))?;
let d_radius = stream
.clone_htod(&radius)
.map_err(|e| format!("htod radius failed: {e}"))?;
let d_desired_speed = stream
.clone_htod(&desired_speed)
.map_err(|e| format!("htod desired_speed failed: {e}"))?;
let d_dest_x = stream
.clone_htod(&dest_x)
.map_err(|e| format!("htod dest_x failed: {e}"))?;
let d_dest_y = stream
.clone_htod(&dest_y)
.map_err(|e| format!("htod dest_y failed: {e}"))?;
let d_wall_ax = stream
.clone_htod(&wall_ax)
.map_err(|e| format!("htod wall_ax failed: {e}"))?;
let d_wall_ay = stream
.clone_htod(&wall_ay)
.map_err(|e| format!("htod wall_ay failed: {e}"))?;
let d_wall_bx = stream
.clone_htod(&wall_bx)
.map_err(|e| format!("htod wall_bx failed: {e}"))?;
let d_wall_by = stream
.clone_htod(&wall_by)
.map_err(|e| format!("htod wall_by failed: {e}"))?;
stream
.synchronize()
.map_err(|e| format!("initial sync failed: {e}"))?;
Ok(Self {
ctx,
stream,
func,
block_size,
n,
n_walls,
d_pos_x_a,
d_pos_y_a,
d_vel_x_a,
d_vel_y_a,
d_pos_x_b,
d_pos_y_b,
d_vel_x_b,
d_vel_y_b,
d_radius,
d_desired_speed,
d_dest_x,
d_dest_y,
d_wall_ax,
d_wall_ay,
d_wall_bx,
d_wall_by,
})
}
#[inline]
pub fn len(&self) -> usize {
self.n
}
#[inline]
pub fn is_empty(&self) -> bool {
self.n == 0
}
pub fn step(&mut self, params: &Params, dt: f64) -> Result<u128, String> {
let n_u32 = self.n as u32;
let n_walls_u32 = self.n_walls as u32;
let time_gap = params.time_gap as f32;
let alpha = params.alpha as f32;
let interaction_range = params.interaction_range as f32;
let interaction_strength = params.interaction_strength as f32;
let wall_range = params.wall_range as f32;
let wall_strength = params.wall_strength as f32;
let arrival_radius = params.arrival_radius as f32;
let dt_f32 = dt as f32;
let grid = self.n.div_ceil(self.block_size as usize) as u32;
let cfg = LaunchConfig {
grid_dim: (grid.max(1), 1, 1),
block_dim: (self.block_size, 1, 1),
shared_mem_bytes: 0,
};
let Self {
ref stream,
ref func,
ref d_radius,
ref d_desired_speed,
ref d_dest_x,
ref d_dest_y,
ref d_wall_ax,
ref d_wall_ay,
ref d_wall_bx,
ref d_wall_by,
ref d_pos_x_a,
ref d_pos_y_a,
ref mut d_pos_x_b,
ref mut d_pos_y_b,
ref mut d_vel_x_b,
ref mut d_vel_y_b,
..
} = *self;
let t0 = std::time::Instant::now();
unsafe {
let mut b = stream.launch_builder(func);
b.arg(d_pos_x_a);
b.arg(d_pos_y_a);
b.arg(d_radius);
b.arg(d_desired_speed);
b.arg(d_dest_x);
b.arg(d_dest_y);
b.arg(d_pos_x_b);
b.arg(d_pos_y_b);
b.arg(d_vel_x_b);
b.arg(d_vel_y_b);
b.arg(d_wall_ax);
b.arg(d_wall_ay);
b.arg(d_wall_bx);
b.arg(d_wall_by);
b.arg(&n_u32);
b.arg(&n_walls_u32);
b.arg(&time_gap);
b.arg(&alpha);
b.arg(&interaction_range);
b.arg(&interaction_strength);
b.arg(&wall_range);
b.arg(&wall_strength);
b.arg(&arrival_radius);
b.arg(&dt_f32);
b.launch(cfg)
.map_err(|e| format!("kernel launch failed: {e}"))?;
}
stream
.synchronize()
.map_err(|e| format!("stream sync failed: {e}"))?;
let kernel_us = t0.elapsed().as_micros();
std::mem::swap(&mut self.d_pos_x_a, &mut self.d_pos_x_b);
std::mem::swap(&mut self.d_pos_y_a, &mut self.d_pos_y_b);
std::mem::swap(&mut self.d_vel_x_a, &mut self.d_vel_x_b);
std::mem::swap(&mut self.d_vel_y_a, &mut self.d_vel_y_b);
let _ = &self.ctx;
Ok(kernel_us)
}
pub fn download(&self, peds: &mut Vec<Pedestrian>) -> Result<(), String> {
let n = self.n;
peds.resize(
n,
Pedestrian {
pos: [0.0, 0.0],
vel: [0.0, 0.0],
radius: 0.0,
desired_speed: 0.0,
destination: [0.0, 0.0],
},
);
let mut pos_x = vec![0.0f32; n];
let mut pos_y = vec![0.0f32; n];
let mut vel_x = vec![0.0f32; n];
let mut vel_y = vec![0.0f32; n];
let mut radius = vec![0.0f32; n];
let mut desired_speed = vec![0.0f32; n];
let mut dest_x = vec![0.0f32; n];
let mut dest_y = vec![0.0f32; n];
self.stream
.memcpy_dtoh(&self.d_pos_x_a, &mut pos_x)
.map_err(|e| format!("dtoh pos_x failed: {e}"))?;
self.stream
.memcpy_dtoh(&self.d_pos_y_a, &mut pos_y)
.map_err(|e| format!("dtoh pos_y failed: {e}"))?;
self.stream
.memcpy_dtoh(&self.d_vel_x_a, &mut vel_x)
.map_err(|e| format!("dtoh vel_x failed: {e}"))?;
self.stream
.memcpy_dtoh(&self.d_vel_y_a, &mut vel_y)
.map_err(|e| format!("dtoh vel_y failed: {e}"))?;
self.stream
.memcpy_dtoh(&self.d_radius, &mut radius)
.map_err(|e| format!("dtoh radius failed: {e}"))?;
self.stream
.memcpy_dtoh(&self.d_desired_speed, &mut desired_speed)
.map_err(|e| format!("dtoh desired_speed failed: {e}"))?;
self.stream
.memcpy_dtoh(&self.d_dest_x, &mut dest_x)
.map_err(|e| format!("dtoh dest_x failed: {e}"))?;
self.stream
.memcpy_dtoh(&self.d_dest_y, &mut dest_y)
.map_err(|e| format!("dtoh dest_y failed: {e}"))?;
for (i, p) in peds.iter_mut().enumerate().take(n) {
p.pos = [pos_x[i] as f64, pos_y[i] as f64];
p.vel = [vel_x[i] as f64, vel_y[i] as f64];
p.radius = radius[i] as f64;
p.desired_speed = desired_speed[i] as f64;
p.destination = [dest_x[i] as f64, dest_y[i] as f64];
}
Ok(())
}
}
pub fn step(
peds: &mut [Pedestrian],
walls: &[WallSegment],
params: &Params,
dt: f64,
) -> Result<u128, String> {
let state = CudaState::new()?;
state.step(peds, walls, params, dt)
}
pub fn step_with_fallback(
state: &mut Option<CudaState>,
peds: &mut [Pedestrian],
walls: &[WallSegment],
params: &Params,
dt: f64,
) -> bool {
if state.is_none() {
match CudaState::new() {
Ok(s) => *state = Some(s),
Err(e) => {
eprintln!("rustsim-crowd CUDA init failed ({e}), using CPU CFS path");
#[allow(deprecated)]
crate::collision_free_speed::step(peds, walls, params, dt);
return false;
}
}
}
match state.as_ref().unwrap().step(peds, walls, params, dt) {
Ok(_) => true,
Err(e) => {
eprintln!("rustsim-crowd CUDA CFS step failed ({e}), falling back to CPU");
#[allow(deprecated)]
crate::collision_free_speed::step(peds, walls, params, dt);
false
}
}
}