Related
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.
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug, PartialEq, Eq)]
pub struct TreeNode {
pub val: i32,
pub left: Option<Rc<RefCell<TreeNode>>>,
pub right: Option<Rc<RefCell<TreeNode>>>,
}
impl TreeNode {
#[inline]
pub fn new(val: i32) -> Self {
TreeNode {
val,
left: None,
right: None,
}
}
pub fn invalid_path_error(self) {
panic!("Invalid path");
}
pub fn insert(&mut self, directions: &[&str], val: i32) {
let mut cur_node = &mut None;
let l = directions.len();
if directions[0] == "left" {
cur_node = &mut self.left;
}
if directions[0] == "right" {
cur_node = &mut self.right;
}
for dir in &directions[1..] {
let mut n;
if *dir == "left" {
if let Some(z) = cur_node {
n = &mut z.borrow_mut().left;
} else {
panic!("Invalid path");
}
}
if *dir == "right" {
if let Some(z) = cur_node {
n = &mut z.borrow_mut().right;
} else {
panic!("Invalid path");
}
}
cur_node = n;
}
//cur_node = Some(Rc::new(RefCell::new(TreeNode::new(2))));
}
}
I am trying to learn rust by solving some leet code questions. I am trying to implement insert function for binary tree. This is the struct given in leet code. I am trying to implement insert by pass list of strings for path For eg. go left , right , left etc. After traversing at the end I will add new node. I am trying to use cur node as a temp pointer and want to change it with each string. But every time I get this error - " temporary value dropped while borrowed consider using a let binding to create a longer lived value ". How can I fix this and implement insert ?
cargo check -
Here's a solution that replaces Rc<RefCell<_>> with Box<_>. Generally you only need to reach for Rc when multiple people will want ownership over the underlying data. In this case, each node is only owned by one other node, so Box is preferable.
This solution iterates through the tree to find the last node, then uses the last direction to assign to the correct child node. No checks are implemented to make sure that the nodes along the path exist or that an existing node isn't being overwritten.
#[derive(Debug, PartialEq, Eq)]
pub struct TreeNode {
pub val: i32,
pub left: Option<Box<TreeNode>>,
pub right: Option<Box<TreeNode>>,
}
impl TreeNode {
#[inline]
pub fn new(val: i32) -> Self {
TreeNode {
val,
left: None,
right: None,
}
}
pub fn insert(&mut self, directions: &[&str], val: i32) {
let mut cur_node = self;
let len = directions.len();
let mut directions = directions.into_iter().copied();
for direction in directions.by_ref().take(len - 1) {
let child_node = match direction {
"left" => &mut cur_node.left,
"right" => &mut cur_node.right,
_ => panic!("invalid direction {direction}"),
};
cur_node = child_node.as_mut().expect("invalid path").as_mut();
}
let new_node = Some(Box::new(TreeNode::new(val)));
match directions.next().unwrap() {
"left" => cur_node.left = new_node,
"right" => cur_node.right = new_node,
direction # _ => panic!("invalid direction {direction}"),
}
}
}
fn main() {
let mut tree = TreeNode::new(0);
tree.insert(&["left"], 1);
tree.insert(&["right"], 2);
tree.insert(&["left", "left"], 2);
tree.insert(&["right", "left"], 3);
println!("{:#?}", tree);
}
Prints
TreeNode {
val: 0,
left: Some(
TreeNode {
val: 1,
left: Some(
TreeNode {
val: 2,
left: None,
right: None,
},
),
right: None,
},
),
right: Some(
TreeNode {
val: 2,
left: Some(
TreeNode {
val: 3,
left: None,
right: None,
},
),
right: None,
},
),
}
I am programing a LinkedList. Here is My Struct, I need some help on performing a get by index (usize) method.
Here is My Struct Code:
struct Node<T>
where
T: Clone,
{
value: T,
prev: Option<Rc<RefCell<Node<T>>>>,
next: Option<Rc<RefCell<Node<T>>>>,
}
impl<T> Node<T>
where
T: Clone,
{
pub fn new(val: T) -> Self {
Node {
value: val,
prev: None,
next: None,
}
}
}
struct ListNode<T: Clone> {
length: usize,
header: Option<Rc<RefCell<Node<T>>>>,
tail: Option<Rc<RefCell<Node<T>>>>,
}
Now I want to perform a get method to get T by an index:usize, There is a problem
impl<T> ListNode<T>
where
T: Clone,
{
pub fn get(&self, idx: usize) -> Option<T> {
let mut p = &self.header;
for _ in 0..=idx {
if let Some(x) = p {
let clone_p = Rc::clone(x);
// Problem 1 is here, I want p point to the next
p = &((*clone_p).borrow().next);
} else {
return None;
}
}
if let Some(x) = p {
let clone_p = Rc::clone(x);
return Some((*clone_p).borrow().value.clone());
}
None
}
}
How to fix this?
I think I have to clone the ReCell, Here is the code, any optimization?
pub fn get(&self, idx: usize) -> Option<T> {
if let Some(p) = &self.header {
let mut ptr: Option<Rc<RefCell<Node<T>>>> = None;
for _ in 0..=idx {
let v = Rc::clone(p);
let next = &(*v).borrow().next;
if let Some(x) = next {
ptr = Some(Rc::clone(x));
} else {
return None;
}
}
if let Some(x) = ptr {
return Some((*x).borrow().value.clone());
}
}
None
}
I want to draw a lot of cells based on 2d array in rust piston library. I try to edit hello world example and have function like so:
fn render(&mut self, args: &RenderArgs) {
const GREEN: [f32; 4] = [0.0f32, 1.0f32, 0.0f32, 1.0f32];
const RED: [f32; 4] = [1.0f32, 0.0f32, 0.0f32, 1.0f32];
let square = rectangle::square(0.0, 0.0, 10.0);
let field = &mut self.field;
self.gl.draw(args.viewport(), |c, gl| -> () {
clear(GREEN, gl);
for x_y_item in field {
let item = x_y_item.2;
if item == 7 {
let x = x_y_item.0;
let y = x_y_item.1;
rectangle(
RED,
square,
c.transform.trans(x as f64 * 10.0, y as f64 * 10.0),
gl,
);
}
}
rectangle(
RED,
square,
c.transform.trans(0 as f64 * 10.0, 0 as f64 * 10.0),
gl,
);
})
}
sadly it doesn't work within the loop.
as in it works fine if I debug print the variables and hardcode them one by one outside of the loop but inside - it just doesn't draw anything, yet doesn't crash.
Update:
I was asked to provide the code to clarify what field is and so it can be runed, so here it is:
Field:
pub struct Grid {
cursor: i32,
body: Vec<Vec<i32>>,
height: i32,
width: i32,
}
impl Grid {
pub fn new(width: i32, height: i32) -> Self {
let mut body: Vec<Vec<i32>> = Vec::with_capacity(height as usize);
let mut temp: Vec<i32> = Vec::with_capacity(width as usize);
for i in 0..width {
temp.push(0);
}
for i in 0..height {
body.push(temp.clone());
}
println!("W: {}, TW:{}, H:{}, TH: {}",width,body[0].len() ,height, body.len());
return Self {cursor: 1 ,width : width, height : height, body : body};
}
pub fn fill(&mut self, value: i32) {
for y in 0..self.height-1 {
for x in 0..self.width - 1 {
self.body[y as usize][x as usize] = value;
}
}
}
pub fn inject(&mut self, pos: (i32,i32), arg: i32) {
self.body[(pos.1 - 1) as usize][(pos.0 - 1) as usize] = arg;
}
pub fn extract(&mut self, pos: (i32,i32)) -> i32 {
return self.body[(pos.1 - 1) as usize][(pos.0 - 1) as usize];
}
pub fn pinpoint(&mut self, val: i32) -> Vec<[i32;2]> {
let mut ret: Vec<[i32;2]> = Vec::new();
for y in 0..self.height {
for x in 0..self.width {
if self.body[y as usize][x as usize] == val {
ret.push([x as i32 + 1,y as i32 + 1]);
}
}
}
return ret;
}
pub fn d_show(&mut self) {
let mut counter: i32 = 1;
for row in self.body.iter() {
println!("{}. {:?}",counter,*row);
counter += 1;
}
}
}
impl Iterator for Grid {
type Item = (i32,i32,i32);
fn next(&mut self) -> Option<Self::Item> {
if self.cursor == self.width * self.height {
return None;
} else {
let x: i32 = self.cursor - (self.cursor/self.width)*self.width;
let y: i32 = self.cursor/self.width;
let val: i32 = 0;
self.cursor += 1;
return Some((x,y,self.body[y as usize][x as usize]));
}
}
}
Main:
extern crate glutin_window;
extern crate graphics;
extern crate opengl_graphics;
extern crate piston;
use glutin_window::GlutinWindow as Window;
use opengl_graphics::{GlGraphics, OpenGL};
use piston::event_loop::{EventSettings, Events};
use piston::input::{RenderArgs, RenderEvent, UpdateArgs, UpdateEvent};
use piston::window::WindowSettings;
use graphics::*;
mod arr2d;
use crate::arr2d::Grid;
pub struct State {
field: arr2d::Grid,
gl: GlGraphics,
}
impl State {
fn render(&mut self, args: &RenderArgs) {
const GREEN: [f32;4] = [0.0f32,1.0f32,0.0f32,1.0f32];
const RED: [f32;4] = [1.0f32,0.0f32,0.0f32,1.0f32];
let square = rectangle::square(0.0,0.0,10.0);
let field = &mut self.field;
self.gl.draw(args.viewport(), |c, gl| -> () {
clear(GREEN,gl);
for x_y_item in field {
let item = x_y_item.2;
if item == 7 {
let x = x_y_item.0;
let y = x_y_item.1;
rectangle(RED, square, c.transform.trans(x as f64 * 10.0, y as f64 * 10.0), gl);
}
}
rectangle(RED, square, c.transform.trans(0 as f64 *10.0,0 as f64 *10.0), gl);
})
}
fn update(&mut self, args: &UpdateArgs) {
}
fn new(gl: OpenGL ,w: i32,h: i32) -> Self {
let mut body = Grid::new(w,h);
body.inject((4,4),7);
body.inject((4,5),7);
body.inject((4,6),7);
return Self{gl: GlGraphics::new(gl), field: body};
}
}
fn main() {
let open_gl = OpenGL::V3_2;
let mut window : Window = WindowSettings::new("Spinning Square",[200,200])
.opengl(open_gl)
.exit_on_esc(true)
.build()
.unwrap();
let mut app = State::new(open_gl,20,20);
let mut events = Events::new(EventSettings::new());
while let Some(e) = events.next(&mut window) {
if let Some(args) = e.render_args() {
app.render(&args);
}
if let Some(args) = e.update_args() {
app.update(&args);
}
}
}
Update #2:
I did more research - and created much simpler version of the code based on other tutorials and examples, but that issue still seem to persist:
extern crate piston;
use piston_window::*;
use std::ffi::OsString;
use std::path::Path;
mod tileSet;
mod arr2d;
fn main() {
let mut window: PistonWindow = WindowSettings::new("Hello Piston!", [640, 480]).exit_on_esc(true)
.build()
.unwrap();
let mut tilez: tileSet::Textures = tileSet::Textures::from_directory(OsString::from("C:\\Users\\grass\\Desktop\\codes\\Rust\\ghandalf\\assets"),
window.create_texture_context(),
|x: &Path| -> bool {
return x.to_str().unwrap().ends_with(".png");
}
);
let mut field: arr2d::Grid = arr2d::Grid::new(40,40);
field.fill(0);
let mut image: Image = Image::new();
while let Some(event) = window.next() {
window.draw_2d(&event, |context, graphics, _device| {
clear([1.0; 4], graphics);
for (x,y,item) in &mut field {
let tile: String = match item {
0 => "tile0.png".to_string(),
_ => "tile1.png".to_string()
};
image.draw(tilez.get(tile).unwrap(), &DrawState::default(), context.transform.trans(40.0,40.0), graphics); //empty screen here
}
image.draw(tilez.get(tile).unwrap(), &DrawState::default(), context.transform.trans(40.0,40.0), graphics); //works fine here
});
}
}
I tried using hardcoded variables, debugging whether the loop iterates, check if any variable is out of scope but it doesn't seem to give the answer. I think that it has to do with some open gl concept like clearing the screen per iteration perhaps?
It doesn't help that compiler doesn't throw an error.
Yeah, I'm retarded - I forgot to reset iterator implied for arr2d -_-, so it was drawing but was over within 1 frame.
I was looking at the Method syntax section of the Rust documentation and came across an example of the builder pattern. The CircleBuilder struct in the example below is an exact duplicate of the Circle struct. It seems like this redundant code violates the usual norms of programming.
I understand why the example created a new struct, because the creator did not want to implement the builder methods against the original Circle struct. That is fine, but is there a way to rewrite this example so that there is no redundancy--yet still keeping the nice builder interface in the main() function intact?
I tried to create an empty struct or a struct with just one throwaway element, but that did not work.
struct Circle {
x: f64,
y: f64,
radius: f64,
}
impl Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
struct CircleBuilder {
x: f64,
y: f64,
radius: f64,
}
impl CircleBuilder {
fn new() -> CircleBuilder {
CircleBuilder { x: 0.0, y: 0.0, radius: 1.0, }
}
fn x(&mut self, coordinate: f64) -> &mut CircleBuilder {
self.x = coordinate;
self
}
fn y(&mut self, coordinate: f64) -> &mut CircleBuilder {
self.y = coordinate;
self
}
fn radius(&mut self, radius: f64) -> &mut CircleBuilder {
self.radius = radius;
self
}
fn finalize(&self) -> Circle {
Circle { x: self.x, y: self.y, radius: self.radius }
}
}
fn main() {
let c = CircleBuilder::new()
.x(1.0)
.y(2.0)
.radius(2.0)
.finalize();
println!("area: {}", c.area());
println!("x: {}", c.x);
println!("y: {}", c.y);
}
Do Rust builder patterns have to use redundant struct code?
No. But sometimes they might. For example, consider if we wanted to have special logic (or even just complicated logic) around our constructor:
/// Width must always be greater than height!
struct HorizontalEllipse {
width: f64,
height: f64,
}
impl HorizontalEllipse {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.width / 2.0) * (self.height / 2.0)
}
}
struct HorizontalEllipseBuilder {
width: f64,
height: f64,
}
impl HorizontalEllipseBuilder {
fn new() -> HorizontalEllipseBuilder {
HorizontalEllipseBuilder {
width: 0.0,
height: 0.0,
}
}
fn width(&mut self, width: f64) -> &mut HorizontalEllipseBuilder {
self.width = width;
self
}
fn height(&mut self, height: f64) -> &mut HorizontalEllipseBuilder {
self.height = height;
self
}
fn finalize(&self) -> Result<HorizontalEllipse, String> {
let HorizontalEllipseBuilder { height, width } = *self;
if height >= width {
Err("This is not horizontal".into())
} else {
Ok(HorizontalEllipse { width, height })
}
}
}
fn main() {
let c = HorizontalEllipseBuilder::new()
.width(1.0)
.height(2.0)
.finalize()
.expect("not a valid ellipse");
println!("area: {}", c.area());
println!("width: {}", c.width);
println!("height: {}", c.height);
}
Now a HorizontalEllipse knows that it is always true that width > height. We've moved that check from many potential places (each method) to one, the constructor. We then moved the constructor to a new type because it was complicated (not really, but truly complicated examples are usually... complicated).
Many builders I've seen also have "enhanced" types of the real object:
#[derive(Debug)]
struct Person {
name: String,
}
#[derive(Debug, Default)]
struct PersonBuilder {
name: Option<String>,
}
impl PersonBuilder {
fn name(self, name: &str) -> Self {
PersonBuilder { name: Some(name.into()), ..self }
}
fn build(self) -> Person {
Person {
name: self.name.unwrap_or_else(|| "Stefani Joanne Angelina Germanotta".into()),
}
}
}
fn main() {
let person = PersonBuilder::default().build();
println!("{:?}", person);
let person = PersonBuilder::default().name("krishnab").build();
println!("{:?}", person);
}
You don't see that in the book's example because it's trying to be simpler and not involve ownership concerns.
This seems like the sort of thing a macro might be able to do. A quick search found the derive_builder and builder_macro crates which seem to implement this functionality.