#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
#[cfg(feature = "broadcast")]
pub mod broadcast;
#[cfg(feature = "type_broadcast")]
pub mod type_broadcast;
#[cfg(feature = "type_inbox")]
pub mod type_inbox;
use std::fmt::Debug;
use std::mem;
use std::sync::Arc;
use parking_lot::Mutex;
pub trait RequestRepaintTrait {
fn request_repaint(&self);
}
impl<F> RequestRepaintTrait for F
where
F: Fn() + Send + Sync + 'static,
{
fn request_repaint(&self) {
self();
}
}
#[derive(Clone)]
enum RequestRepaintInner {
#[cfg(feature = "egui")]
Ctx(egui::Context),
Arc(Arc<dyn RequestRepaintTrait + Send + Sync>),
}
#[derive(Debug, Clone)]
pub struct RequestRepaintContext(RequestRepaintInner);
impl RequestRepaintContext {
pub fn from_callback<F>(f: F) -> Self
where
F: Fn() + Send + Sync + 'static,
{
Self(RequestRepaintInner::Arc(Arc::new(f)))
}
pub fn from_trait<T>(t: T) -> Self
where
T: RequestRepaintTrait + Send + Sync + 'static,
{
Self(RequestRepaintInner::Arc(Arc::new(t)))
}
#[cfg(feature = "egui")]
pub fn from_egui_ctx(ctx: egui::Context) -> Self {
Self(RequestRepaintInner::Ctx(ctx))
}
}
impl RequestRepaintContext {
#[track_caller]
pub fn request_repaint(&self) {
match &self.0 {
#[cfg(feature = "egui")]
RequestRepaintInner::Ctx(ctx) => ctx.request_repaint(),
RequestRepaintInner::Arc(boxed) => boxed.request_repaint(),
}
}
}
impl Debug for RequestRepaintInner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RequestRepaint").finish_non_exhaustive()
}
}
pub trait AsRequestRepaint {
fn as_request_repaint(&self) -> RequestRepaintContext;
}
impl AsRequestRepaint for RequestRepaintContext {
fn as_request_repaint(&self) -> RequestRepaintContext {
self.clone()
}
}
#[cfg(feature = "egui")]
mod egui_impl {
use crate::{AsRequestRepaint, RequestRepaintContext};
use egui::Context;
impl AsRequestRepaint for Context {
fn as_request_repaint(&self) -> RequestRepaintContext {
RequestRepaintContext::from_egui_ctx(self.clone())
}
}
impl AsRequestRepaint for egui::Ui {
fn as_request_repaint(&self) -> RequestRepaintContext {
RequestRepaintContext::from_egui_ctx(self.ctx().clone())
}
}
}
pub struct UiInbox<T> {
state: Arc<Mutex<State<T>>>,
#[cfg(feature = "async")]
oneshot_channels: Vec<futures_channel::oneshot::Sender<()>>,
}
impl<T> Debug for UiInbox<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UiInbox").finish_non_exhaustive()
}
}
#[derive(Debug)]
struct State<T> {
ctx: Option<RequestRepaintContext>,
queue: Vec<T>,
dropped: bool,
}
impl<T> State<T> {
fn new(ctx: Option<RequestRepaintContext>) -> Self {
Self {
ctx,
queue: Vec::new(),
dropped: false,
}
}
}
pub struct UiInboxSender<T> {
state: Arc<Mutex<State<T>>>,
}
impl<T> Debug for UiInboxSender<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UiInboxSender").finish_non_exhaustive()
}
}
impl<T> Clone for UiInboxSender<T> {
fn clone(&self) -> Self {
Self {
state: self.state.clone(),
}
}
}
impl<T> Default for UiInbox<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Drop for UiInbox<T> {
fn drop(&mut self) {
#[cfg(feature = "async")]
{
self.oneshot_channels.drain(..).for_each(|tx| {
tx.send(()).ok();
});
}
let mut state = self.state.lock();
state.dropped = true;
}
}
impl<T> UiInbox<T> {
pub fn new() -> Self {
Self::_new(None)
}
pub fn new_with_ctx(ctx: &impl AsRequestRepaint) -> Self {
Self::_new(Some(ctx.as_request_repaint()))
}
fn _new(ctx: Option<RequestRepaintContext>) -> Self {
let state = Arc::new(Mutex::new(State::new(ctx)));
Self {
state,
#[cfg(feature = "async")]
oneshot_channels: Vec::new(),
}
}
pub fn channel() -> (UiInboxSender<T>, Self) {
let inbox = Self::new();
let sender = inbox.sender();
(sender, inbox)
}
pub fn channel_with_ctx(ctx: &impl AsRequestRepaint) -> (UiInboxSender<T>, Self) {
let inbox = Self::new_with_ctx(ctx);
let sender = inbox.sender();
(sender, inbox)
}
pub fn set_ctx(&mut self, ctx: &impl AsRequestRepaint) {
self.state.lock().ctx = Some(ctx.as_request_repaint());
}
pub fn read(&self, ui: &impl AsRequestRepaint) -> impl Iterator<Item = T> {
let mut state = self.state.lock();
if state.ctx.is_none() {
state.ctx = Some(ui.as_request_repaint());
}
mem::take(&mut state.queue).into_iter()
}
pub fn read_without_ctx(&self) -> impl Iterator<Item = T> {
let mut state = self.state.lock();
mem::take(&mut state.queue).into_iter()
}
pub fn replace(&self, ui: &impl AsRequestRepaint, target: &mut T) -> bool {
let mut state = self.state.lock();
if state.ctx.is_none() {
state.ctx = Some(ui.as_request_repaint());
}
let item = mem::take(&mut state.queue).pop();
if let Some(item) = item {
*target = item;
true
} else {
false
}
}
pub fn replace_option(&self, ui: &impl AsRequestRepaint, target: &mut Option<T>) {
let mut state = self.state.lock();
if state.ctx.is_none() {
state.ctx = Some(ui.as_request_repaint());
}
let item = mem::take(&mut state.queue).pop();
if let Some(item) = item {
*target = Some(item);
}
}
pub fn replace_without_ctx(&self, target: &mut T) -> bool {
let mut state = self.state.lock();
let item = mem::take(&mut state.queue).pop();
if let Some(item) = item {
*target = item;
true
} else {
false
}
}
pub fn sender(&self) -> UiInboxSender<T> {
UiInboxSender {
state: self.state.clone(),
}
}
}
#[cfg(feature = "async")]
mod async_impl {
use std::pin::{pin, Pin};
use std::task::{Context, Poll};
use futures::{select, FutureExt, Sink, SinkExt, StreamExt};
use hello_egui_utils::{spawn, MaybeSend};
use crate::{SendError, UiInbox, UiInboxSender};
impl<T> UiInbox<T> {
pub fn spawn<F>(&mut self, f: impl FnOnce(UiInboxSender<T>) -> F)
where
F: std::future::Future<Output = ()> + MaybeSend + 'static,
{
let (tx, mut rx) = futures_channel::oneshot::channel();
self.oneshot_channels.push(tx);
let sender = self.sender();
let future = f(sender).fuse();
spawn(async move {
let mut future = pin!(future);
select! {
() = future => {},
_ = rx => {},
}
});
}
pub fn spawn_detached<F>(&mut self, f: impl FnOnce(UiInboxSender<T>) -> F)
where
F: std::future::Future<Output = ()> + Send + 'static,
{
let sender = self.sender();
let future = f(sender);
spawn(future);
}
}
impl<T> UiInboxSender<T> {
pub async fn send_stream(
&mut self,
stream: impl futures::Stream<Item = T> + Send + 'static,
) -> Result<(), SendError<T>> {
let stream = stream.map(|i| Ok(i));
let mut stream = pin!(stream);
self.send_all(&mut stream).await
}
}
impl<T> Sink<T> for UiInboxSender<T> {
type Error = SendError<T>;
fn poll_ready(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> {
UiInboxSender::send(&self, item)
}
fn poll_flush(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn poll_close(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
}
}
impl<T> UiInboxSender<T> {
#[track_caller]
pub fn send(&self, item: T) -> Result<(), SendError<T>> {
let mut state = self.state.lock();
if state.dropped {
Err(SendError(item))
} else {
state.queue.push(item);
if let Some(ctx) = &state.ctx {
ctx.request_repaint();
}
Ok(())
}
}
pub fn send_without_request_repaint(&self, item: T) -> Result<(), SendError<T>> {
let mut state = self.state.lock();
if state.dropped {
Err(SendError(item))
} else {
state.queue.push(item);
Ok(())
}
}
}
pub struct SendError<T>(pub T);
impl<T: Debug> Debug for SendError<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SendError").field("item", &self.0).finish()
}
}