future_form_ffi 0.1.0

FFI support for future_form: host-driven polling, opaque handles, and effect slots
Documentation
//! FFI handle that pairs a future with a stashed effect snapshot.
//!
//! [`EffectHandle`] extends [`HostHandle`] with a
//! per-handle effect cache and an application context pointer. When
//! `poll_once()` returns [`Pending`](core::task::Poll::Pending), the
//! bridge code snapshots the pending effect into the handle so the host
//! can inspect it via [`last_effect`](EffectHandle::last_effect) or
//! [`take_effect`](EffectHandle::take_effect) without racing.
//!
//! ```text
//! ┌──────────┐  poll_once()    ┌──────────────────────┐
//! │   Host   │ ──────────────> │  EffectHandle        │
//! │          │                 │  ┌────────────────┐  │
//! │          │ <────────────── │  │ HostHandle<F>  │  │
//! │          │  Pending        │  └────────────────┘  │
//! │          │                 │  last_effect: E?     │
//! │  reads   │  last_effect()  │  context: *const Ctx │
//! │  effect  │ ──────────────> │                      │
//! │          │ <────────────── │                      │
//! └──────────┘                 └──────────────────────┘
//! ```

use core::{future::Future, task::Poll};

use crate::host_handle::HostHandle;

/// FFI handle wrapping a [`HostHandle`] with a stashed effect and context.
///
/// This is the generalized version of an effect-carrying future handle.
/// It bundles:
///
/// - A [`HostHandle<F>`] for the underlying future
/// - A last-observed effect snapshot (`Option<E>`)
/// - A context pointer (`*const Ctx`) for application-specific state
///
/// The context pointer is a raw `*const Ctx` because FFI handles typically
/// outlive any particular borrow scope. Bridge authors are responsible for
/// ensuring the context outlives the handle.
///
/// # Slot ownership
///
/// For concurrent futures, each `EffectHandle` should be paired with
/// its own `Arc<EffectSlot>`. The bridge creates the `Arc`, clones it
/// into both the async closure and the handle. This gives each future
/// an independent effect channel with no capacity limit. See the
/// [crate-level docs](crate#slot-ownership) for examples.
///
/// # Type parameters
///
/// - `F`: the future type (e.g., `BoxFuture<'static, T>` or
///   `LocalBoxFuture<'static, T>`)
/// - `E`: the effect type (e.g., an enum of things the host can provide)
/// - `Ctx`: the application context type (e.g., the counter struct)
pub struct EffectHandle<F, E, Ctx> {
    handle: HostHandle<F>,
    context: *const Ctx,
    last_effect: Option<E>,
}

impl<F, E: core::fmt::Debug, Ctx> core::fmt::Debug for EffectHandle<F, E, Ctx> {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("EffectHandle")
            .field("last_effect", &self.last_effect)
            .finish_non_exhaustive()
    }
}

// SAFETY: The only non-auto-Send/Sync field is `*const Ctx`. FFI handles
// are single-owner — only the host's polling thread touches a given handle.
// The raw context pointer is only dereferenced while the handle is alive
// and the context has not been freed.
unsafe impl<F: Send, E: Send, Ctx> Send for EffectHandle<F, E, Ctx> {}

impl<F: Future + Unpin, E, Ctx> EffectHandle<F, E, Ctx> {
    /// Poll the underlying future once.
    pub fn poll_once(&mut self) -> Poll<F::Output> {
        self.handle.poll_once()
    }
}

impl<F, E, Ctx> EffectHandle<F, E, Ctx> {
    /// Create a new effect handle.
    ///
    /// # Parameters
    ///
    /// - `handle`: the underlying future handle
    /// - `context`: raw pointer to application-specific state (e.g., the
    ///   object whose methods produced the future). The caller must ensure
    ///   this pointer remains valid for the lifetime of the handle.
    #[must_use]
    pub const fn new(handle: HostHandle<F>, context: *const Ctx) -> Self {
        Self {
            handle,
            context,
            last_effect: None,
        }
    }

    /// Stash an effect snapshot for later host inspection.
    ///
    /// Typically called by bridge code after `poll_once()` returns
    /// `Pending`, using the effect read from the [`EffectSlot`](crate::effect_slot::EffectSlot).
    pub fn stash_effect(&mut self, effect: E) {
        self.last_effect = Some(effect);
    }

    /// Take the stashed effect, leaving `None` in its place.
    pub const fn take_effect(&mut self) -> Option<E> {
        self.last_effect.take()
    }

    /// Borrow the stashed effect without taking it.
    pub const fn last_effect(&self) -> Option<&E> {
        self.last_effect.as_ref()
    }

    /// Access the application context pointer.
    ///
    /// The returned pointer is only valid if the context has not been
    /// freed. The caller must ensure the context outlives the handle.
    #[must_use]
    pub const fn context(&self) -> *const Ctx {
        self.context
    }
}