From bd6cd5eacc7ad61b78513bacd088c4fc1c9d0377 Mon Sep 17 00:00:00 2001
From: Quaternions <krakow20@gmail.com>
Date: Wed, 4 Oct 2023 01:30:33 -0700
Subject: [PATCH] worker module

---
 src/body.rs   |  2 +-
 src/main.rs   |  1 +
 src/worker.rs | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 86 insertions(+), 1 deletion(-)
 create mode 100644 src/worker.rs

diff --git a/src/body.rs b/src/body.rs
index 71f089fc..6e4727c8 100644
--- a/src/body.rs
+++ b/src/body.rs
@@ -34,7 +34,7 @@ pub enum InputInstruction {
 		//for interpolation / networking / playback reasons, most playback heads will always want
 		//to be 1 instruction ahead to generate the next state for interpolation.
 }
-
+#[derive(Clone,Debug)]
 pub struct Body {
 	position: glam::Vec3,//I64 where 2^32 = 1 u
 	velocity: glam::Vec3,//I64 where 2^32 = 1 u/s
diff --git a/src/main.rs b/src/main.rs
index 0fab7fed..053279d4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,6 +11,7 @@ mod framework;
 mod primitives;
 mod instruction;
 mod load_roblox;
+mod worker;
 
 struct Entity {
 	index_count: u32,
diff --git a/src/worker.rs b/src/worker.rs
new file mode 100644
index 00000000..2e457cf1
--- /dev/null
+++ b/src/worker.rs
@@ -0,0 +1,84 @@
+use std::thread;
+use std::sync::{mpsc,Arc};
+use parking_lot::Mutex;
+
+//The goal here is to have a worker thread that parks itself when it runs out of work.
+//The worker thread publishes the result of its work back to the worker object for every item in the work queue.
+//The physics (target use case) knows when it has not changed the body, so not updating the value is also an option.
+
+struct Worker<Task:Send,Value:Clone> {
+	sender: mpsc::Sender<Task>,
+	value:Arc<Mutex<Value>>,
+}
+
+impl<Task:Send+'static,Value:Clone+Send+'static> Worker<Task,Value> {
+	fn new<F:Fn(Task)->Value+Send+'static>(value:Value,f:F) -> Self {
+		let (sender, receiver) = mpsc::channel::<Task>();
+		let ret=Self {
+			sender,
+			value:Arc::new(Mutex::new(value)),
+		};
+		let value=ret.value.clone();
+		thread::spawn(move || {
+			loop {
+				match receiver.recv() {
+					Ok(task) => {
+						println!("Worker got a task");
+						// Process the task
+						let v=f(task);//make sure function is evaluated before lock is acquired
+						*value.lock()=v;
+					}
+					Err(_) => {
+						println!("Worker stopping.",);
+						break;
+					}
+				}
+			}
+		});
+		ret
+	}
+
+	fn send(&self,task:Task)->Result<(), mpsc::SendError<Task>>{
+		self.sender.send(task)
+	}
+
+	fn grab_clone(&self)->Value{
+		self.value.lock().clone()
+	}
+}
+
+#[test]//How to run this test with printing: cargo test --release -- --nocapture
+fn test_worker() {
+	println!("hiiiii");
+	// Create the worker thread
+	let worker = Worker::new(crate::body::Body::with_pva(glam::Vec3::ZERO,glam::Vec3::ZERO,glam::Vec3::ZERO),
+		|_|crate::body::Body::with_pva(glam::Vec3::ONE,glam::Vec3::ONE,glam::Vec3::ONE)
+	);
+
+	// Send tasks to the worker
+	for i in 0..5 {
+		let task = crate::instruction::TimedInstruction{
+			time:0,
+			instruction:crate::body::PhysicsInstruction::StrafeTick,
+		};
+		worker.send(task).unwrap();
+	}
+
+	// Optional: Signal the worker to stop (in a real-world scenario)
+	// sender.send("STOP".to_string()).unwrap();
+
+	// Sleep to allow the worker thread to finish processing
+	thread::sleep(std::time::Duration::from_secs(2));
+
+	// Send a new task
+	let task = crate::instruction::TimedInstruction{
+		time:0,
+		instruction:crate::body::PhysicsInstruction::StrafeTick,
+	};
+	worker.send(task).unwrap();
+
+	println!("value={:?}",worker.grab_clone());
+
+	// wait long enough to see print from final task
+	thread::sleep(std::time::Duration::from_secs(1));
+}