I have asteroids; players and lasers are entities. Instead of the following code, is it possible to make one function that takes generic as argument and produce the same output. I understand ECS is an ideal way of handling this. But am curious if this would be possible.
// check collision between asteroid and players
pub fn check_collission_between_asteroid_and_players(asteroids: &mut Vec<Asteroid>, players: &mut Vec<Player>) {
for player in players.iter_mut() {
for asteroid in asteroids.iter_mut() {
if player.entity.position.distance(asteroid.entity.position) < asteroid.radius + player.radius {
player.kill();
asteroid.kill();
}
}
}
}
// check collission between player and players
pub fn check_collission_between_player_and_players(players: &mut Vec<Player>) {
for player in players.iter_mut() {
for other_player in players.iter_mut() {
if player.entity.position.distance(other_player.entity.position) < player.radius + other_player.radius && player != other_player {
player.kill();
other_player.kill();
}
}
}
}
// check collission between lasers and players
pub fn check_collission_between_lasers_and_players(lasers: &mut Vec<Laser>, players: &mut Vec<Player>) {
for player in players.iter_mut() {
for laser in lasers.iter_mut() {
if player.entity.position.distance(laser.entity.position) < player.radius + laser.radius {
player.kill();
laser.kill();
}
}
}
}
// check collission between lasers and asteroids
pub fn check_collission_between_lasers_and_asteroids(lasers: &mut Vec<Laser>, asteroids: &mut Vec<Asteroid>) {
for laser in lasers.iter_mut() {
for asteroid in asteroids.iter_mut() {
if laser.entity.position.distance(asteroid.entity.position) < laser.radius + asteroid.radius {
laser.kill();
asteroid.kill();
}
}
}
}
You can implement a Trait that can collide and kill, and use a generic function:
#[derive(Debug, Clone, Copy)]
struct Position {
x: f64,
y: f64,
}
struct Player {
position: Position,
}
struct Asteroid {
position: Position,
}
trait Collidable {
fn position(&self) -> Position;
fn kill(&self);
}
impl Collidable for Player {
fn position(&self) -> Position {
return self.position;
}
fn kill(&self) {
println!("Aaarghhh, Player dying at coordinate {:?}", self.position);
}
}
impl Collidable for Asteroid {
fn position(&self) -> Position {
return self.position;
}
fn kill(&self) {
println!("Aaarghhh, Asteroid dying at coordinate {:?}", self.position);
}
}
fn check_collisions<T: Collidable, U: Collidable>(t_vec: &mut[T], u_vec: &mut[U], max_dist: f64) {
for t in t_vec.iter_mut() { // Note you're now comparing the same objects twice in some cases
for u in u_vec.iter_mut() {
let t_pos = t.position();
let u_pos = u.position();
// Some calc to determine distance:
let distance = ((t_pos.x - u_pos.x).abs().powf(2.) + (t_pos.y - u_pos.y).abs().powf(2.)).powf(0.5);
if distance < max_dist {
println!("Collision!");
u.kill();
t.kill();
}
}
}
}
let mut players = vec![
Player { position: Position {x: 1.0, y: 1.0}},
Player { position: Position {x: 3.0, y: 3.0}}];
let mut asteroids = vec![
Asteroid { position: Position {x: 2.0, y: 2.0}},
Asteroid { position: Position {x: 10.0, y: 10.0}}];
check_collisions(&mut players, &mut asteroids, 5.);
Based on #Rob's answer, here is a version that uses Trait Objects so that you have one method that will check all collisions between objects. The code is mostly the same except for the check_all method and the way you have to construct the vector:
#[derive(Debug, Clone, Copy)]
struct Position {
x: f64,
y: f64,
}
struct Player {
position: Position,
}
struct Asteroid {
position: Position,
}
trait Collidable {
fn position(&self) -> Position;
fn kill(&self);
}
impl Collidable for Player {
fn position(&self) -> Position {
return self.position;
}
fn kill(&self) {
println!("Aaarghhh, Player dying at coordinate {:?}", self.position);
}
}
impl Collidable for Asteroid {
fn position(&self) -> Position {
return self.position;
}
fn kill(&self) {
println!("Aaarghhh, Asteroid dying at coordinate {:?}", self.position);
}
}
fn check_all(t_vec: &mut[Box<dyn Collidable>], max_dist: f64) {
for i in 0..t_vec.len() {
for j in i+1..t_vec.len() {
let i_pos = t_vec[i].position();
let j_pos = t_vec[j].position();
let distance = ((i_pos.x - j_pos.x).abs().powf(2.) + (i_pos.y - j_pos.y).abs().powf(2.)).powf(0.5);
if distance < max_dist {
println!("Collision!");
t_vec[i].kill();
t_vec[j].kill();
}
}
}
}
fn main() {
let mut objects: Vec<Box<dyn Collidable>> = vec![
Box::new(Player { position: Position {x: 1.0, y: 1.0}}),
Box::new(Player { position: Position {x: 3.0, y: 3.0}}),
Box::new(Asteroid { position: Position {x: 2.0, y: 2.0}}),
Box::new(Asteroid { position: Position {x: 10.0, y: 10.0}})];
check_all(&mut objects, 5.);
}
I'm using explicit indices i and j instead of grabbing the objects to avoid the problem that you can't have two mutable references to vector elements at the same time.
Btw, in the current version of the code we don't actually need the vector elements to be mutable, but probably in real code where the kill method does something to the object, we'd need to make it fn kill(&mut self) instead.
I'm building a game engine / CPU 3D renderer to learn the basics of rendering following a course.
I'm trying to organize the code so that things are encapsulated and I can provide a public API that's easy to use.
For this, I want to provide this engine.on_update(|&mut engine| {}) method so users can pass in their own code and use the engine in there.
This callback is then executed as part of the engine's update loop.
Here is what this looks like now (I trimmed the code to make this easier as its already quite long)
This is the engine module.
use std::time::{Duration, Instant};
use sdl2::{
event::Event,
keyboard::Keycode,
pixels::PixelFormatEnum,
render::{Canvas, Texture, UpdateTextureError},
video::Window,
EventPump,
};
use crate::buffer::{ClearAuto, ClearColor, ColorBuffer, Drawable};
use crate::utils::NumOption;
pub struct EngineConfigParams {
pub window_title: Option<String>,
pub width: Option<usize>,
pub height: Option<usize>,
pub clear_color: Option<u32>,
pub fps: Option<u32>,
}
impl Default for EngineConfigParams {
fn default() -> Self {
EngineConfigParams {
window_title: None,
width: None,
height: None,
clear_color: None,
fps: None,
}
}
}
pub struct EngineConfig {
window_title: String,
width: usize,
height: usize,
clear_color: u32,
fps: u32,
}
impl EngineConfig {
pub fn new(params: EngineConfigParams) -> Self {
let default = EngineConfig::default();
EngineConfig {
window_title: params.window_title.unwrap_or(default.window_title),
width: params.width.unwrap_gt_or(0, default.width),
height: params.height.unwrap_gt_or(0, default.height),
clear_color: params.clear_color.unwrap_or(default.clear_color),
fps: params.fps.unwrap_gt_or(0, default.fps),
}
}
pub fn window_title(&self) -> &String {
&self.window_title
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn clear_color(&self) -> u32 {
self.clear_color
}
pub fn fps(&self) -> u32 {
self.fps
}
}
impl Default for EngineConfig {
fn default() -> Self {
EngineConfig {
window_title: "3D renderer".to_string(),
width: 800,
height: 600,
clear_color: 0xFF000000,
fps: 60,
}
}
}
type EngineUpdateFn<'a> = &'a mut dyn FnMut(&mut EngineCore);
pub struct Engine<'a> {
core: EngineCore,
update: Option<EngineUpdateFn<'a>>,
previous_frame_time: Instant,
target_frame_time: Duration,
}
impl<'a> Engine<'a> {
pub fn build(mut config: EngineConfig) -> Engine<'a> {
let ctx = sdl2::init().unwrap();
let video = ctx.video().unwrap();
match video.display_mode(0, 0) {
Ok(mode) => {
config.width = mode.w as usize;
config.height = mode.h as usize;
println!("Display mode: {:?}", mode);
}
Err(e) => eprintln!(
"Failed to get display mode: {}, using default width and height",
e
),
};
let width = config.width;
let height = config.height;
let window = video
.window(&config.window_title, width as u32, height as u32)
.borderless()
.position_centered()
.fullscreen()
.build()
.unwrap();
let canvas = window.into_canvas().build().unwrap();
let color_buffer = ColorBuffer::new(width, height);
let event_pump = ctx.event_pump().unwrap();
println!("WindowCtx w: {} h: {}", width, height);
let fps = config.fps;
Engine {
core: EngineCore {
config,
canvas,
color_buffer,
event_pump,
},
update: None,
previous_frame_time: Instant::now(),
target_frame_time: Duration::new(0, 1_000_000_000u32 / fps),
}
}
pub fn config(&self) -> &EngineConfig {
&self.core.config
}
pub fn on_update(&mut self, f: EngineUpdateFn<'a>) {
self.update = Some(f);
self.update();
}
pub fn user_update(&mut self) {
self.update.as_mut().unwrap()(&mut self.core);
}
pub fn update(&mut self) {
self.target_frame_time = Duration::new(0, 1_000_000_000u32 / self.core.config.fps);
let texture_creator = self.core.canvas.texture_creator();
let mut texture = texture_creator
.create_texture(
PixelFormatEnum::ARGB8888,
sdl2::render::TextureAccess::Streaming,
self.core.config.width as u32,
self.core.config.height as u32,
)
.unwrap();
let mut running = true;
while running {
self.previous_frame_time = Instant::now();
running = self.core.process_input();
self.user_update();
self.core.render_buffer(&mut texture).unwrap();
self.core.clear();
let now = Instant::now();
let frame_time = now - self.previous_frame_time;
println!(
"Time this frame {}ms {} FPS",
frame_time.as_millis(),
1000u128 / frame_time.as_millis()
);
if frame_time.as_nanos() < self.target_frame_time.as_nanos() {
::std::thread::sleep(self.target_frame_time - frame_time);
}
}
}
}
pub struct EngineCore {
config: EngineConfig,
canvas: Canvas<Window>,
color_buffer: ColorBuffer,
event_pump: EventPump,
}
impl EngineCore {
fn process_input(&mut self) -> bool {
for event in self.event_pump.poll_iter() {
match event {
Event::Quit { .. }
| Event::KeyDown {
keycode: Some(Keycode::Escape),
..
} => {
println!("Received quit event, shutting down");
return false;
}
_ => {}
}
}
true
}
fn render_buffer(&mut self, texture: &mut Texture) -> Result<(), UpdateTextureError> {
self.copy_buffer_to_canvas(texture)?;
self.canvas.present();
Ok(())
}
fn copy_buffer_to_canvas(&mut self, texture: &mut Texture) -> Result<(), UpdateTextureError> {
texture.update(None, self.color_buffer.pixels(), self.config.width * 4)?;
self.canvas.copy(texture, None, None).unwrap();
Ok(())
}
pub fn config(&self) -> &EngineConfig {
&self.config
}
}
impl ClearAuto for EngineCore {
fn clear(&mut self) {
self.color_buffer.clear(self.config.clear_color);
}
}
impl Drawable for EngineCore {
fn draw_grid(&mut self, spacing: usize, color: Option<u32>) {
self.color_buffer.draw_grid(spacing, color);
}
fn draw_rect(&mut self, x: usize, y: usize, width: usize, height: usize, color: u32) {
self.color_buffer.draw_rect(x, y, width, height, color);
}
fn draw_point(&mut self, x: usize, y: usize, color: u32) {
self.color_buffer.draw_point(x, y, color);
}
}
And in main.rs (Again trimmed of the mesh/points manipulations)
use renderer3d::prelude::*;
use vecx::Vec3;
pub fn main() {
let mut eng = Engine::build(EngineConfig {
window_title: "3d Renderer".to_string(),
width: 800,
height: 600,
clear_color: 0xFF000000,
});
let points = build_cube();
let fov = 640.0;
let cam_pos = Vec3(0.0, 0.0, -5.0);
let mut rotation = Vec3(0.0, 0.0, 0.0);
println!("Start update");
eng.on_update(&mut |eng: &mut Engine| {
eng.draw_grid(10, Some(0xFF333333));
rotation = rotation + Vec3(0.01, 0.02, 0.0);
let mut points = transform_points(&points, rotation);
points = project_points(&points, cam_pos, fov);
points.iter().for_each(|point| {
let mut x = point.x();
let mut y = point.y();
x += eng.config().width as f64 / 2.0;
y += eng.config().height as f64 / 2.0;
eng.draw_rect(x as usize, y as usize, 4, 4, 0xFFFF0000);
});
});
}
As you can see, it's important that this callback can access and mutate variables from the outer scope.
I've been through a lot of trial and error and I ended up finding something that works but the fact I have to do unsafe code makes me wonder if that's really the correct way to do this.
Especially I'm worried that I'm trying to design this too much as I would in other less safe/restrictive languages and that I'm missing how to design this properly in Rust.
Update
Here is the full final code after applying the accepted answer's solution
engine module
use std::time::{Duration, Instant};
use sdl2::{
event::Event,
keyboard::Keycode,
pixels::PixelFormatEnum,
render::{Canvas, Texture, UpdateTextureError},
video::Window,
EventPump,
};
use crate::buffer::{ClearAuto, ClearColor, ColorBuffer, Drawable};
use crate::utils::NumOption;
pub struct EngineConfigParams {
pub window_title: Option<String>,
pub width: Option<usize>,
pub height: Option<usize>,
pub clear_color: Option<u32>,
pub fps: Option<u32>,
}
impl Default for EngineConfigParams {
fn default() -> Self {
EngineConfigParams {
window_title: None,
width: None,
height: None,
clear_color: None,
fps: None,
}
}
}
pub struct EngineConfig {
window_title: String,
width: usize,
height: usize,
clear_color: u32,
fps: u32,
}
impl EngineConfig {
pub fn new(params: EngineConfigParams) -> Self {
let default = EngineConfig::default();
EngineConfig {
window_title: params.window_title.unwrap_or(default.window_title),
width: params.width.unwrap_gt_or(0, default.width),
height: params.height.unwrap_gt_or(0, default.height),
clear_color: params.clear_color.unwrap_or(default.clear_color),
fps: params.fps.unwrap_gt_or(0, default.fps),
}
}
pub fn window_title(&self) -> &String {
&self.window_title
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
}
impl Default for EngineConfig {
fn default() -> Self {
EngineConfig {
window_title: "3D renderer".to_string(),
width: 800,
height: 600,
clear_color: 0xFF000000,
fps: 60,
}
}
}
type EngineUpdateFn<'a> = &'a mut dyn FnMut(&mut EngineCore);
pub struct Engine<'a> {
core: EngineCore,
update: Option<EngineUpdateFn<'a>>,
previous_frame_time: Instant,
target_frame_time: Duration,
}
impl<'a> Engine<'a> {
pub fn build(mut config: EngineConfig) -> Engine<'a> {
let ctx = sdl2::init().unwrap();
let video = ctx.video().unwrap();
config = EngineConfig {
width: config.width,
height: config.height,
..EngineConfig::default()
};
match video.display_mode(0, 0) {
Ok(mode) => {
config.width = mode.w as usize;
config.height = mode.h as usize;
println!("Display mode: {:?}", mode);
}
Err(e) => eprintln!(
"Failed to get display mode: {}, using default width and height",
e
),
};
let width = config.width;
let height = config.height;
let window = video
.window(&config.window_title, width as u32, height as u32)
.borderless()
.position_centered()
.fullscreen()
.build()
.unwrap();
let canvas = window.into_canvas().build().unwrap();
let color_buffer = ColorBuffer::new(width, height);
let event_pump = ctx.event_pump().unwrap();
println!("WindowCtx w: {} h: {}", width, height);
let fps = config.fps;
Engine {
core: EngineCore {
config,
canvas,
color_buffer,
event_pump,
},
update: None,
previous_frame_time: Instant::now(),
target_frame_time: Duration::new(0, 1_000_000_000u32 / fps),
}
}
pub fn config(&self) -> &EngineConfig {
&self.core.config
}
pub fn on_update(&mut self, f: EngineUpdateFn<'a>) {
self.update = Some(f);
self.update();
}
pub fn user_update(&mut self) {
self.update.as_mut().unwrap()(&mut self.core);
}
pub fn update(&mut self) {
self.target_frame_time = Duration::new(0, 1_000_000_000u32 / self.core.config.fps);
let texture_creator = self.core.canvas.texture_creator();
let mut texture = texture_creator
.create_texture(
PixelFormatEnum::ARGB8888,
sdl2::render::TextureAccess::Streaming,
self.core.config.width as u32,
self.core.config.height as u32,
)
.unwrap();
let mut running = true;
while running {
self.previous_frame_time = Instant::now();
running = self.core.process_input();
self.user_update();
self.core.render_buffer(&mut texture).unwrap();
self.core.clear();
let now = Instant::now();
let frame_time = now - self.previous_frame_time;
println!(
"Time this frame {}ms {} FPS",
frame_time.as_millis(),
1000u128 / frame_time.as_millis()
);
if frame_time.as_nanos() < self.target_frame_time.as_nanos() {
::std::thread::sleep(self.target_frame_time - frame_time);
}
}
}
}
pub struct EngineCore {
config: EngineConfig,
canvas: Canvas<Window>,
color_buffer: ColorBuffer,
event_pump: EventPump,
}
impl EngineCore {
fn process_input(&mut self) -> bool {
for event in self.event_pump.poll_iter() {
match event {
Event::Quit { .. }
| Event::KeyDown {
keycode: Some(Keycode::Escape),
..
} => {
println!("Received quit event, shutting down");
return false;
}
_ => {}
}
}
true
}
fn render_buffer(&mut self, texture: &mut Texture) -> Result<(), UpdateTextureError> {
self.copy_buffer_to_canvas(texture)?;
self.canvas.present();
Ok(())
}
fn copy_buffer_to_canvas(&mut self, texture: &mut Texture) -> Result<(), UpdateTextureError> {
texture.update(None, self.color_buffer.pixels(), self.config.width * 4)?;
self.canvas.copy(texture, None, None).unwrap();
Ok(())
}
pub fn config(&self) -> &EngineConfig {
&self.config
}
}
impl ClearAuto for EngineCore {
fn clear(&mut self) {
self.color_buffer.clear(self.config.clear_color);
}
}
impl Drawable for EngineCore {
fn draw_grid(&mut self, spacing: usize, color: Option<u32>) {
self.color_buffer.draw_grid(spacing, color);
}
fn draw_rect(&mut self, x: usize, y: usize, width: usize, height: usize, color: u32) {
self.color_buffer.draw_rect(x, y, width, height, color);
}
fn draw_point(&mut self, x: usize, y: usize, color: u32) {
self.color_buffer.draw_point(x, y, color);
}
}
main.rs
use renderer3d::{prelude::*, Mesh};
use vecx::Vec3;
pub fn main() {
let mut eng = Engine::build(EngineConfig::new(EngineConfigParams {
window_title: Some("3d Renderer".to_string()),
..EngineConfigParams::default()
}));
let cube = Mesh::cube();
let fov = 640.0;
let cam_pos = Vec3(0.0, 0.0, -5.0);
let mut rotation = Vec3(0.0, 0.0, 0.0);
println!("Start update");
eng.on_update(&mut |eng| {
eng.draw_grid(10, Some(0xFF333333));
rotation = rotation + Vec3(0.01, 0.02, 0.0);
let mut points = cube
.face_vertices()
.map(|fv| transform_points(&fv, rotation))
.flatten()
.collect();
points = project_points(&points, cam_pos, fov);
points.iter().for_each(|point| {
let mut x = point.x();
let mut y = point.y();
x += eng.config().width() as f64 / 2.0;
y += eng.config().height() as f64 / 2.0;
eng.draw_rect(x as usize, y as usize, 4, 4, 0xFFFF0000);
});
});
}
fn transform_points(points: &Vec<Vec3>, rotation: Vec3) -> Vec<Vec3> {
points.iter().map(|p| p.rot(rotation)).collect()
}
fn project_points(points: &Vec<Vec3>, cam_pos: Vec3, fov: f64) -> Vec<Vec3> {
points
.iter()
.map(|point| {
let z = point.z() + cam_pos.z();
let mut x = point.x() / z;
let mut y = point.y() / z;
x *= fov;
y *= fov;
Vec3(x, y, z)
})
.collect()
}
The problem is not the variables from the outer scope, it's the fact that the callback has mutable access to Engine but it itself comes from engine, so there are two coexisting mutable references. Theoretically, it could for example access the captured variables both by the data pointer in Engine and by using them directly from different threads, causing a data race.
Since you probably don't need access to the callback inside the callback, I would split Engine into two structs: one will contain the callback and the other, and have an update() and on_update() methods, and the other will include all other stuff. This way, the callback will take a mutable reference to the inner struct, which it can - since it is not contained within, but will still be able to call all engine's functions.
struct Point { x: f64, y: f64 }
struct Circle { center: Point, radius: f64 }
struct Square { lowerLeftCorner: Point, side: f64 }
trait ShapeVisitor {
fn visit_circle(&mut self, c: &Circle);
fn visit_square(&mut self, c: &Square);
}
trait Shape {
fn accept<V: ShapeVisitor>(&self, sv: &mut V);
}
impl Shape for Circle {
fn accept<V: ShapeVisitor>(&self, sv: &mut V) {
sv.visit_circle(self);
}
}
impl Shape for Square {
fn accept<V: ShapeVisitor>(&self, sv: &mut V) {
sv.visit_square(self);
}
}
So here's a method that computes the total area:
fn area(shapes: Vec<Box<dyn Shape>>) -> f64 {
struct AreaCalculator {
area: f64,
}
impl ShapeVisitor for AreaCalculator {
fn visit_circle(&mut self, c: &Circle) {
self.area += std::f64::consts::PI * c.radius * c.radius;
}
fn visit_square(&mut self, r: &Square) {
self.area += r.side * r.side;
}
}
let mut calculator = AreaCalculator { area: 0.0 };
for shape in shapes {
(*shape).accept(calculator);
}
calculator.area
}
And here's an example program
fn main() {
let mut shapes: Vec<Box<dyn Shape>> = Vec::new();
let p = Point { x: 0.0, y: 0.0 };
let circle = Circle { center: p, radius: 12.0 };
shapes.push(Box::new(circle));
let area = compute_area(shapes);
println!("Area {:?}", area);
}
Here's the error report:
error[E0038]: the trait `Shape` cannot be made into an object
--> visitor.rs:26:25
|
26 | fn compute_area(shapes: Vec<Box<dyn Shape>>) -> f64 {
| ^^^^^^^^^^^^^^^^^^^ `Shape` cannot be made into an object
|
= help: consider moving `accept` to another trait
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
--> visitor.rs:11:8
|
10 | trait Shape {
| ----- this trait cannot be made into an object...
11 | fn accept<V: ShapeVisitor>(&self, sv: &mut V);
| ^^^^^^ ...because method `accept` has generic type parameters
error: aborting due to previous error
Is there a simple way to rectify it?
In order to be able to create a trait object, the trait must be object safe. But your Shape is not object safe - it has generic type parameters. Removing the type parameters solves the issue:
#[derive(Copy, Clone)]
struct Point {
x: f64,
y: f64,
}
struct Circle {
center: Point,
radius: f64,
}
struct Square {
lowerLeftCorner: Point,
side: f64,
}
trait ShapeVisitor {
fn visit_circle(&mut self, c: &Circle);
fn visit_square(&mut self, c: &Square);
}
trait Shape {
fn accept(&self, sv: &mut dyn ShapeVisitor);
}
impl Shape for Circle {
fn accept(&self, sv: &mut dyn ShapeVisitor) {
sv.visit_circle(self);
}
}
impl Shape for Square {
fn accept(&self, sv: &mut dyn ShapeVisitor) {
sv.visit_square(self);
}
}
fn area(shapes: Vec<Box<dyn Shape>>) -> f64 {
struct AreaCalculator {
area: f64,
}
impl ShapeVisitor for AreaCalculator {
fn visit_circle(&mut self, c: &Circle) {
self.area += std::f64::consts::PI * c.radius * c.radius;
}
fn visit_square(&mut self, r: &Square) {
self.area += r.side * r.side;
}
}
let mut calculator = AreaCalculator { area: 0.0 };
for shape in shapes {
shape.accept(&mut calculator);
}
calculator.area
}
fn main() {
let mut shapes: Vec<Box<dyn Shape>> = Vec::new();
let p = Point { x: 0.0, y: 0.0 };
let circle = Circle {
center: p,
radius: 1.0,
};
shapes.push(Box::new(circle));
shapes.push(Box::new(Square {
lowerLeftCorner: p,
side: 10.0,
}));
let area = area(shapes);
println!("Area {:?}", area);
}
I have a list (sprites: Vec<RefCell<Sprite>>) in a structure
I have to add objects and remove them, no problem to add, but impossible to remove
add objects
pub fn _clean() {}
fn create_sprite(&mut self, x: f32, y: f32) {
let src_rect = Rect::new(0, 0, 9, 9);
let mut sprite = Sprite::new(0, x, y, src_rect);
self.sprites.push(RefCell::new(sprite));
println!("{}", self.sprites.len());
}
Delete objects
let mut pos: usize = 0;
for sprite in &mut self.sprites {
sprite.borrow_mut().x += 1.0;
// collision sprite shoot
if sprite.borrow_mut().x > (self.map.nbr_column as u32 * self.map.tile_wight) as f32 {
println!("shoot out {}", pos);
self.sprites.remove(pos);
//self.remove_sprite(pos);
}
pos += 1;
}
ditto if i do a pop instead of remove
I cleaned the code to produce an executable minumun code
use std::cell::RefCell;
pub struct Sprite {
pub index: usize,
pub x: f32,
pub y: f32,
pub vx: f32,
pub vy: f32,
}
impl Sprite {
pub fn new(index: usize, x: f32, y: f32) -> Sprite {
let mut sprite: Sprite = Sprite {
index: index,
x: x,
y: y,
vx: 0.0,
vy: 0.0,
};
sprite
}
}
pub struct Game {
sprites: Vec<RefCell<Sprite>>,
map: f32,
}
impl Game {
pub fn new() -> Game {
Game {
sprites: vec![],
map: 30.0,
}
}
pub fn update(&mut self) {
let mut pos: usize = 0;
self.sprites.retain(|s| {
// s is &RefCell<Sprite>
s.borrow_mut().x += 1.0;
s.borrow().x <= self.map
});
println!("{}", self.sprites.len());
}
fn create_sprite(&mut self, x: f32, y: f32) {
let mut sprite = Sprite::new(0, x, y);
self.sprites.push(RefCell::new(sprite));
println!("{}", self.sprites.len());
}
pub fn handle_event(&mut self) {
self.create_sprite(10.0, 10.0);
self.create_sprite(20.0, 10.0);
self.create_sprite(30.0, 10.0);
self.create_sprite(40.0, 10.0);
self.create_sprite(50.0, 10.0);
self.create_sprite(60.0, 10.0);
}
}
fn main() {
let mut the_game = Game::new();
the_game.handle_event();
the_game.update();
}
The line for sprite in &mut self.sprites creates a mutable borrow of the self.sprites collection that spans the entire loop body. This means that you cannot modify the collection itself inside the loop body, because doing so will cause the loop to become invalid.
When you attempt to call self.sprites.remove(pos), this call would cause all sprites after pos to move forward by one in memory. This is forbidden because you currently have a reference to the sprite that is about to be removed, and also because the sprite at pos + 1 would be skipped (the remove call moves it forward by one, but the iterator is going to move to the slot after it).
Rust provides a way to perform what your loop body is trying to do: the Vec::retain method.
self.sprites.retain(|s| { // s is &RefCell<Sprite>
s.borrow_mut().x += 1.0;
sprite.borrow_mut().x <= (self.map.nbr_column as u32 * self.map.tile_wight) as f32
});
This goes through each sprite in the list, updating it, and then returning a condition which, when true, tells the Vec to keep the sprite, and when false, tells the Vec to remove it.
Thank for you help, this code works
...
let map_width = (self.map.nbr_column as u32 * self.map.tile_wight) as f32;
self.sprites.retain(|s| {
// s is &RefCell<Sprite>
s.borrow_mut().x += 1.0;
s.borrow().x <= map_width
});
...
I am just learning Rust. I am trying to create a builder struct for my Game struct. Here is the code:
struct Input {
keys_pressed: HashMap<VirtualKeyCode, bool>,
}
pub struct GameBuilder {
settings: GameSettings,
input: Input,
}
impl GameBuilder {
pub fn new() -> GameBuilder {
GameBuilder {
settings: GameSettings {
window_dimensions: (800, 600),
title: "".to_string(),
},
input: Input {
keys_pressed: HashMap::new(),
}
}
}
pub fn with_dimensions(&mut self, width: u32, height: u32) -> &mut GameBuilder {
self.settings.window_dimensions = (width, height);
self
}
pub fn with_title(&mut self, title: &str) -> &mut GameBuilder {
self.settings.title = title.to_string();
self
}
pub fn game_keys(&mut self, keys: Vec<VirtualKeyCode>) -> &mut GameBuilder {
for key in keys {
self.input.keys_pressed.insert(key, false);
}
self
}
pub fn build(&self) -> Game {
let (width, height) = self.settings.window_dimensions;
Game {
display: glutin::WindowBuilder::new()
.with_dimensions(width, height)
.with_title(self.settings.title.to_string())
.build_glium()
.ok()
.expect("Error in WindowBuilder"),
state: GameState::Running,
input: self.input,
}
}
}
But this code complains in the last line input: self.input with this:
error: cannot move out of borrowed content
I think I understand why. Since the argument passed in the function is &self, I cannot take ownership of it, and that what the last line is doing.
I thought that maybe changing &self to self would work, but then the compile argues that I cannot mutate self.
There is also the Copy trait from what I know, and that maybe should solve the problem. But Input is basically a HashMap, which means that a copy could be expensive if the hash itself is too big.
How would be a nice way of solving this problem?
Edit:
I tried doing this:
#[derive(Debug, Copy, Clone)]
struct Input {
keys_pressed: HashMap<VirtualKeyCode, bool>,
}
But the compiler complains:
error: the trait `Copy` may not be implemented for this type; field `keys_pressed` does not implement `Copy`
Given how your method signatures are formulated, you appear to be aiming for chaining:
let game = GameBuilder::new().with_dimensions(...)
.with_title(...)
.build();
In Rust, this requires that GameBuilder be passed by value:
pub fn with_dimensions(self, ...) -> GameBuilder {
// ...
}
And in order to be able to mutate self within the method, you need to make it mut:
pub fn with_dimensions(mut self, ...) -> GameBuilder {
}
If you change the signature of with_dimensions, with_title, game_keys and build to take self by value (mut self if mutation is intended), then chaining should work.
Try the builder pattern with Option and take()
Example:
#[derive(PartialEq, Debug)]
struct Game {
window: Window,
}
#[derive(PartialEq, Debug)]
struct Window {
title: String,
dimensions: (u32, u32),
}
struct GameBuilder {
window_title: Option<String>,
window_dimensions: Option<(u32, u32)>,
}
impl GameBuilder {
fn new() -> Self {
Self {
window_title: None,
window_dimensions: None,
}
}
fn window_title(&mut self, window_title: &str) -> &mut Self {
self.window_title = Some(window_title.to_owned());
self
}
fn window_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
self.window_dimensions = Some((width, height));
self
}
fn build(&mut self) -> Result<Game, Box<dyn std::error::Error>> {
Ok(Game {
window: Window {
// `ok_or(&str)?` works, because From<&str> is implemented for Box<dyn Error>
title: self.window_title.take().ok_or("window_title is unset")?,
dimensions: self
.window_dimensions
.take()
.ok_or("window_dimensions are unset")?,
},
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test() {
let mut builder = GameBuilder::new();
builder.window_title("Awesome Builder");
builder.window_dimensions(800, 600);
let game = builder.build();
assert_eq!(
game.expect("build success"),
Game {
window: Window {
title: "Awesome Builder".into(),
dimensions: (800, 600)
}
}
);
}
#[test]
fn test_1() {
let game2 = GameBuilder::new()
.window_title("Busy Builder")
.window_dimensions(1234, 123)
.build();
assert_eq!(
game2.expect("build success"),
Game {
window: Window {
title: "Busy Builder".into(),
dimensions: (1234, 123),
}
}
)
}
}