summaryrefslogtreecommitdiff
path: root/racer-tracer/src/scene_controller/interactive.rs
diff options
context:
space:
mode:
authorSakarias Johansson <sakarias.johansson@goodbyekansas.com>2023-04-09 19:11:39 +0200
committerSakarias Johansson <sakariasjohansson@hotmail.com>2023-04-14 16:39:04 +0200
commit0ae6ca062f5936ae6f595f45ca0a78ed049452bc (patch)
tree712111e3aa823de7dc85eadca8fd5f78124afa6b /racer-tracer/src/scene_controller/interactive.rs
parent5f8faa17457426c4ca8c54bf67b5aa96eb7a52ea (diff)
downloadracer-tracer-0ae6ca062f5936ae6f595f45ca0a78ed049452bc.tar.gz
racer-tracer-0ae6ca062f5936ae6f595f45ca0a78ed049452bc.tar.xz
racer-tracer-0ae6ca062f5936ae6f595f45ca0a78ed049452bc.zip
✨ Add scene controller
Wanted to be able to for example move around freely in the scene but also have something that would for example follow key frames and render a gif. Abstracted it with a scene controller. You can hook up keybinds and other thigns for it as well. Right now there is only the interactive scene controller which keep the behaviours previous to this change. Now I could possibly switch it out with something that uses key frames to render several images to create for example a gif. List of other Misc changes: - Add configuration setting for scene controller (`scene_controller`) - Add configuration setting for renderer - Add configuration setting for preview renderer (`preview_renderer`) - Add clone to Config. - Add from implementation for Renderer to be created from the config object. - Add cancel event to image action. An action could be blocking when the application wants to exit. Actions can now listen to the cancel event to exit early and not block. - Fixed bug where WaitForSignal action would block after application tries to exit. - Add method to KeyInputs to be able to take a list of callbacks instead of manually registering every callback one at the time.
Diffstat (limited to 'racer-tracer/src/scene_controller/interactive.rs')
-rw-r--r--racer-tracer/src/scene_controller/interactive.rs209
1 files changed, 209 insertions, 0 deletions
diff --git a/racer-tracer/src/scene_controller/interactive.rs b/racer-tracer/src/scene_controller/interactive.rs
new file mode 100644
index 0000000..4624ba1
--- /dev/null
+++ b/racer-tracer/src/scene_controller/interactive.rs
@@ -0,0 +1,209 @@
+use std::{
+ sync::RwLock,
+ time::{Duration, Instant},
+};
+
+use minifb::Key;
+use slog::Logger;
+use synchronoise::SignalEvent;
+
+use crate::{
+ camera::Camera,
+ config::Config,
+ error::TracerError,
+ image::Image,
+ image_action::ImageAction,
+ key_inputs::{KeyCallback, KeyEvent, KeyInputs},
+ renderer::{RenderData, Renderer},
+ scene::Scene,
+ terminal::Terminal,
+};
+
+use super::{create_screen_buffer, SceneController, SceneData};
+
+pub struct InteractiveScene<'renderer, 'action> {
+ screen_buffer: RwLock<Vec<u32>>,
+ preview_buffer: RwLock<Vec<u32>>,
+ camera_speed: f64,
+ render_image_event: SignalEvent,
+ buffer_updated: SignalEvent,
+ stop_event: SignalEvent,
+
+ log: Logger,
+ term: Terminal,
+ image_action: &'action dyn ImageAction,
+ config: Config,
+ scene: Scene,
+ camera: RwLock<Camera>,
+ image: Image,
+ renderer: &'renderer dyn Renderer,
+ renderer_preview: &'renderer dyn Renderer,
+}
+
+impl<'renderer, 'action> InteractiveScene<'renderer, 'action> {
+ pub fn new(scene_data: SceneData, camera_speed: f64) -> Self {
+ Self {
+ screen_buffer: RwLock::new(create_screen_buffer(&scene_data.image)),
+ preview_buffer: RwLock::new(create_screen_buffer(&scene_data.image)),
+ camera_speed,
+ render_image_event: SignalEvent::manual(false),
+ buffer_updated: SignalEvent::manual(false),
+ stop_event: SignalEvent::manual(false),
+
+ log: scene_data.log,
+ term: scene_data.term,
+ image_action: (&scene_data.config.image_action).into(),
+ scene: scene_data.scene,
+ camera: RwLock::new(scene_data.camera),
+ image: scene_data.image,
+ renderer: (&scene_data.config.renderer).into(),
+ renderer_preview: (&scene_data.config.preview_renderer).into(),
+ config: scene_data.config,
+ }
+ }
+}
+
+impl<'renderer, 'action> SceneController for InteractiveScene<'renderer, 'action> {
+ fn get_inputs(&self) -> Vec<KeyCallback> {
+ vec![
+ KeyInputs::input(KeyEvent::Release, Key::R, |_| {
+ self.render_image_event.signal();
+ Ok(())
+ }),
+ KeyInputs::input(KeyEvent::Down, Key::W, |dt| {
+ self.camera
+ .write()
+ .map_err(|e| TracerError::KeyError(e.to_string()))
+ .map(|mut cam| {
+ cam.go_forward(-dt * self.camera_speed);
+ })
+ }),
+ KeyInputs::input(KeyEvent::Down, Key::S, |dt| {
+ self.camera
+ .write()
+ .map_err(|e| TracerError::KeyError(e.to_string()))
+ .map(|mut cam| {
+ cam.go_forward(dt * self.camera_speed);
+ })
+ }),
+ KeyInputs::input(KeyEvent::Down, Key::A, |dt| {
+ self.camera
+ .write()
+ .map_err(|e| TracerError::KeyError(e.to_string()))
+ .map(|mut cam| {
+ cam.go_right(-dt * self.camera_speed);
+ })
+ }),
+ KeyInputs::input(KeyEvent::Down, Key::D, |dt| {
+ self.camera
+ .write()
+ .map_err(|e| TracerError::KeyError(e.to_string()))
+ .map(|mut cam| {
+ cam.go_right(dt * self.camera_speed);
+ })
+ }),
+ ]
+ }
+
+ fn get_buffer(&self) -> Result<Option<Vec<u32>>, TracerError> {
+ self.buffer_updated
+ .wait_timeout(Duration::from_secs(0))
+ .then_some(|| ())
+ .map_or(Ok(None), |_| {
+ self.screen_buffer
+ .read()
+ .map_err(|e| TracerError::FailedToAcquireLock(e.to_string()))
+ .map(|v| Some(v.to_owned()))
+ })
+ }
+
+ fn render(&self) -> Result<(), TracerError> {
+ self.render_image_event
+ .wait_timeout(Duration::from_secs(0))
+ .then_some(|| ())
+ .map_or_else(
+ || {
+ // Render preview
+ self.render_image_event.reset();
+
+ // We do not want partial screen updates for the preview.
+ // Which is why we send in a different buffer.
+ self.renderer_preview
+ .render(RenderData {
+ buffer: &self.preview_buffer,
+ camera: &self.camera,
+ image: &self.image,
+ scene: &self.scene,
+ config: &self.config,
+ cancel_event: None,
+ buffer_updated: None,
+ })
+ .and_then(|_| {
+ self.screen_buffer
+ .write()
+ .map_err(|e| TracerError::FailedToAcquireLock(e.to_string()))
+ })
+ .and_then(|w_buffer| {
+ self.preview_buffer
+ .read()
+ .map_err(|e| TracerError::FailedToAcquireLock(e.to_string()))
+ .map(|r_buffer| (r_buffer, w_buffer))
+ })
+ .map(|(r_buffer, mut w_buffer)| {
+ // For the preview we want the complete image
+ // result before signaling the image is done.
+ *w_buffer = r_buffer.to_owned();
+ self.buffer_updated.signal();
+ })
+ },
+ |_| {
+ let render_time = Instant::now();
+ self.render_image_event.reset();
+
+ // When we render the final image we want partial
+ // updates to the screen buffer. We send in our
+ // original screen_buffer and the buffer_updated
+ // signal. This will ensure that the window will
+ // get updated with a new buffer as soon as a
+ // thread finishes writing a block and we get
+ // partial updates of the rendered image.
+ self.renderer
+ .render(RenderData {
+ buffer: &self.screen_buffer,
+ camera: &self.camera,
+ image: &self.image,
+ scene: &self.scene,
+ config: &self.config,
+ cancel_event: Some(&self.render_image_event),
+ buffer_updated: Some(&self.buffer_updated),
+ })
+ .and_then(|_| {
+ if !self.render_image_event.status() {
+ info!(
+ self.log,
+ "It took {} seconds to render the image.",
+ Instant::now().duration_since(render_time).as_secs()
+ );
+ } else {
+ info!(self.log, "Image render cancelled.");
+ }
+
+ self.image_action.action(
+ &self.screen_buffer,
+ &self.stop_event,
+ &self.render_image_event,
+ &self.config,
+ self.log.new(o!("scope" => "image-action")),
+ &self.term,
+ )
+ })
+ },
+ )
+ }
+
+ fn stop(&self) {
+ // If we are currently rendering anything we try to cancel it
+ self.render_image_event.signal();
+ self.stop_event.signal();
+ }
+}