egui_flow 1.0.0

crate that helps you with tasks which cant run directly in the egui ui code
Documentation
use egui::{Context, Id};
use std::{
	any::Any,
	collections::HashMap,
	sync::{Arc, Mutex},
	thread::JoinHandle,
};

#[derive(Default)]
pub struct FlowManager {
	egui_ctx: Option<Context>,
	/// active flows (waiting for `.poll()`)
	flows: HashMap<Id, Flow>,
}

impl FlowManager {
	pub fn set_repaint_on_finish(&mut self, ctx: Option<Context>) {
		self.egui_ctx = ctx;
	}

	pub fn get_repaint_on_finish(&self) -> bool {
		self.egui_ctx.is_some()
	}

	/// starts a new flow with input arguments
	pub fn start<T, F, Args>(&mut self, id: impl Into<Id>, args: Args, func: F)
	where
		T: Send + 'static,
		F: FnOnce(Args) -> T + Send + 'static,
		Args: Send + 'static,
	{
		let flow = Flow::new(func, args, self.egui_ctx.clone());
		let id = id.into();
		self.flows.insert(id, flow);

		#[cfg(debug_assertions)]
		println!("Flow added: {id:?}");
	}

	/// starts a new flow without input arguments
	pub fn start_simple<T, F>(&mut self, id: impl Into<Id>, func: F)
	where
		T: Send + 'static,
		F: FnOnce() -> T + Send + 'static,
	{
		let flow = Flow::new_simple(func, self.egui_ctx.clone());
		let id = id.into();

		if self.flows.insert(id, flow).is_some() {
			panic!("flow with id {id:?} already exists");
		}

		#[cfg(debug_assertions)]
		println!("Flow added: {id:?}");
	}

	/// returns the resulting data if available, joins the underlying thread to achieve a clean shutdown and drops the flow
	pub fn poll<T: 'static>(&mut self, id: impl Into<Id>) -> Option<T> {
		let id = id.into();
		let result = self.flows.get(&id)?.take_result::<T>();

		if result.is_some() {
			let flow = self.flows.remove(&id).unwrap();
			flow.thread.join().unwrap();

			#[cfg(debug_assertions)]
			println!("Flow removed: {:?}", id);
		}

		result
	}

	/// check if a flow with the given `Id` is active
	pub fn is_active(&self, id: impl Into<Id>) -> bool {
		self.flows.contains_key(&id.into())
	}

	/// debug function to view all active flow ids
	pub fn get_active_flow_ids(&self) -> Vec<Id> {
		self.flows.keys().copied().collect()
	}
}

struct Flow {
	thread: JoinHandle<()>,
	result: Arc<Mutex<Option<Box<dyn Any + Send>>>>,
}

impl Flow {
	fn new<T, F, Args>(func: F, args: Args, ctx: Option<Context>) -> Self
	where
		T: Send + 'static,
		F: FnOnce(Args) -> T + Send + 'static,
		Args: Send + 'static,
	{
		let result: Arc<Mutex<Option<Box<dyn Any + Send>>>> = Arc::new(Mutex::new(None));
		let result_clone = result.clone();

		let thread = std::thread::spawn(move || {
			*result_clone.lock().unwrap() = Some(Box::new(func(args)) as Box<dyn Any + Send>);

			if let Some(ctx) = ctx {
				ctx.request_repaint();
			}
		});

		Self { thread, result }
	}

	fn new_simple<T, F>(func: F, ctx: Option<Context>) -> Self
	where
		T: Send + 'static,
		F: FnOnce() -> T + Send + 'static,
	{
		let result: Arc<Mutex<Option<Box<dyn Any + Send>>>> = Arc::new(Mutex::new(None));
		let result_clone = Arc::clone(&result);

		let thread = std::thread::spawn(move || {
			*result_clone.lock().unwrap() = Some(Box::new(func()) as Box<dyn Any + Send>);

			if let Some(ctx) = ctx { ctx.request_repaint(); }
		});

		Self { thread, result }
	}

	fn take_result<T: 'static>(&self) -> Option<T> {
		self.result
			.lock().unwrap()
			.take()
			.and_then(|boxed| boxed.downcast::<T>().ok().map(|b| *b))
	}
}