strafe-client/src/worker.rs

193 lines
5.4 KiB
Rust
Raw Normal View History

2023-10-04 08:30:33 +00:00
use std::thread;
use std::sync::{mpsc,Arc};
2023-10-20 00:59:45 +00:00
use parking_lot::{Mutex,Condvar};
2023-10-04 08:30:33 +00:00
//WorkerPool
struct Pool(u32);
enum PoolOrdering{
Single,//single thread cannot get out of order
Ordered(u32),//order matters and should be buffered/dropped according to ControlFlow
Unordered(u32),//order does not matter
}
//WorkerInput
enum Input{
//no input, workers have everything needed at creation
None,
//Immediate input to any available worker, dropped if they are overflowing (all workers are busy)
Immediate,
//Queued input is ordered, but serial jobs that mutate state (such as running physics) can only be done with a single worker
2023-10-20 05:24:14 +00:00
Queued,//"Fifo"
//Query a function to get next input when a thread becomes available
//worker stops querying when Query function returns None and dies after all threads complete
//lifetimes sound crazy on this one
Query,
}
//WorkerOutput
enum Output{
None(Pool),
Realtime(PoolOrdering),//outputs are dropped if they are out of order and order is demanded
Buffered(PoolOrdering),//outputs are held back internally if they are out of order and order is demanded
}
//realtime output is an arc mutex of the output value that is assigned every time a worker completes a job
//buffered output produces a receiver object that can be passed to the creation of another worker
//when ordering is requested, output is ordered by the order each thread is run
//which is the same as the order that the input data is processed except for Input::None which has no input data
//WorkerDescription
struct Description{
input:Input,
output:Output,
}
2023-10-04 08:30:33 +00:00
//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.
2023-10-19 23:52:50 +00:00
//Previous values do not matter as soon as a new value is produced, which is why it's called "Realtime"
2023-10-04 08:30:33 +00:00
//The physics (target use case) knows when it has not changed the body, so not updating the value is also an option.
2023-10-19 23:52:50 +00:00
/*
QR = WorkerDescription{
input:Queued,
output:Realtime(Single),
}
*/
pub struct QRWorker<Task:Send,Value:Clone>{
2023-10-04 08:30:33 +00:00
sender: mpsc::Sender<Task>,
value:Arc<Mutex<Value>>,
}
2023-10-19 23:52:50 +00:00
impl<Task:Send+'static,Value:Clone+Send+'static> QRWorker<Task,Value>{
2023-10-05 03:04:04 +00:00
pub fn new<F:FnMut(Task)->Value+Send+'static>(value:Value,mut f:F) -> Self {
2023-10-04 08:30:33 +00:00
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) => {
let v=f(task);//make sure function is evaluated before lock is acquired
*value.lock()=v;
}
Err(_) => {
println!("Worker stopping.",);
break;
}
}
}
});
ret
}
2023-10-05 03:04:04 +00:00
pub fn send(&self,task:Task)->Result<(), mpsc::SendError<Task>>{
2023-10-04 08:30:33 +00:00
self.sender.send(task)
}
2023-10-05 03:04:04 +00:00
pub fn grab_clone(&self)->Value{
2023-10-04 08:30:33 +00:00
self.value.lock().clone()
}
}
2023-10-19 21:55:24 +00:00
/*
QN = WorkerDescription{
input:Queued,
output:None(Single),
}
*/
//None Output Worker does all its work internally from the perspective of the work submitter
pub struct QNWorker<Task:Send> {
sender: mpsc::Sender<Task>,
}
impl<Task:Send+'static> QNWorker<Task>{
pub fn new<F:FnMut(Task)+Send+'static>(mut f:F)->Self{
let (sender,receiver)=mpsc::channel::<Task>();
let ret=Self {
sender,
};
thread::spawn(move ||{
loop {
match receiver.recv() {
Ok(task)=>f(task),
Err(_)=>{
println!("Worker stopping.",);
break;
}
}
}
});
ret
}
pub fn send(&self,task:Task)->Result<(),mpsc::SendError<Task>>{
self.sender.send(task)
}
}
2023-10-20 00:59:45 +00:00
/*
IN = WorkerDescription{
input:Immediate,
output:None(Single),
}
*/
//Inputs are dropped if the worker is busy
pub struct INWorker<Task:Clone>{
input:Arc<(Mutex<Task>,Condvar)>,
}
impl<Task:Clone+Send+'static> INWorker<Task>{
pub fn new<F:FnMut(Task)+Send+'static>(task:Task,mut f:F)->Self{
let ret=Self {
input:Arc::new((Mutex::new(task),Condvar::new())),
};
let input=ret.input.clone();
thread::spawn(move ||{
loop {
input.1.wait(&mut input.0.lock());
f(input.0.lock().clone());
}
});
ret
}
pub fn send(&self,task:Task){
*self.input.0.lock()=task;
self.input.1.notify_one();
}
}
2023-10-04 08:30:33 +00:00
#[test]//How to run this test with printing: cargo test --release -- --nocapture
fn test_worker() {
println!("hiiiii");
// Create the worker thread
2023-10-19 21:55:24 +00:00
let worker=QRWorker::new(crate::physics::Body::with_pva(crate::integer::Planar64Vec3::ZERO,crate::integer::Planar64Vec3::ZERO,crate::integer::Planar64Vec3::ZERO),
2023-09-27 09:12:20 +00:00
|_|crate::physics::Body::with_pva(crate::integer::Planar64Vec3::ONE,crate::integer::Planar64Vec3::ONE,crate::integer::Planar64Vec3::ONE)
2023-10-04 08:30:33 +00:00
);
// Send tasks to the worker
2023-10-10 23:30:00 +00:00
for _ in 0..5 {
2023-10-04 08:30:33 +00:00
let task = crate::instruction::TimedInstruction{
2023-09-27 09:12:20 +00:00
time:crate::integer::Time::ZERO,
2023-10-04 22:58:02 +00:00
instruction:crate::physics::PhysicsInstruction::StrafeTick,
2023-10-04 08:30:33 +00:00
};
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{
2023-09-27 09:12:20 +00:00
time:crate::integer::Time::ZERO,
2023-10-04 22:58:02 +00:00
instruction:crate::physics::PhysicsInstruction::StrafeTick,
2023-10-04 08:30:33 +00:00
};
worker.send(task).unwrap();
2023-10-14 22:41:59 +00:00
println!("value={}",worker.grab_clone());
2023-10-04 08:30:33 +00:00
// wait long enough to see print from final task
thread::sleep(std::time::Duration::from_secs(1));
}