gooey-rs 0.12.1

Tile-based UI library with audio support
Documentation
//! Remote FMOD backend

use std::sync;
use unbounded_spsc;

use crate::interface::{self, view, View};
use crate::tree::{NodeId, Tree};
use super::{Audio, Presentation};

/// Used to transfer view display events to the FMOD backend
#[expect(clippy::type_complexity)]
static DISPLAY_RECEIVER : sync::LazyLock <
  sync::Mutex <Option <unbounded_spsc::Receiver <Vec <(NodeId, view::Display)>>>>
> = sync::LazyLock::new (|| sync::Mutex::new (None));

/// Remote FMOD handle
#[derive(Debug)]
pub struct Remote {
  display : unbounded_spsc::Sender <Vec <(NodeId, view::Display)>>,
}

/// Audition backend that can receive and handle display updates
pub struct Fmod {
  pub inner       : super::Fmod,
  updates         : unbounded_spsc::Receiver <Vec <(NodeId, view::Display)>>,
  /// Counts number of batches of display events have been processed
  display_counter : u64,
  /// Counts number of frames that have been rendered
  frame           : u64
}

impl Default for Remote {
  fn default() -> Self {
    let display = {
      let (sender, receiver) =
        unbounded_spsc::channel::<Vec <(NodeId, view::Display)>>();
      {
        let mut display_receiver_lock = DISPLAY_RECEIVER.lock().unwrap();
        debug_assert!(display_receiver_lock.is_none());
        *display_receiver_lock = Some (receiver);
      }
      sender
    };
    Remote { display }
  }
}

impl Audio        for Remote { }
impl Presentation for Remote {
  fn with_root (root : View, root_id : NodeId) -> Self {
    let remote = Self::default();
    log::debug!("fmod remote with root: {remote:?}");
    remote.display.send (vec![(
      root_id.clone(),
      view::Display::Create (root, root_id, interface::CreateOrder::Append)
    )]).unwrap();
    remote
  }

  fn get_input (&mut self, _ : &mut Vec <view::Input>) { /* no-op */ }

  fn display_view <V : AsRef <View>> (&mut self,
    _view_tree      : &Tree <V>,
    display_values : std::vec::Drain <(NodeId, view::Display)>
  ) {
    self.display.send (display_values.collect()).unwrap();
  }
}

impl Fmod {
  pub fn init() -> Self {
    let inner = super::Fmod::default();
    let updates = loop {
      let maybe_receiver = (*DISPLAY_RECEIVER).lock().unwrap().take();
      if let Some (receiver) = maybe_receiver {
        break receiver
      }
      std::thread::sleep (std::time::Duration::from_millis (100));
    };
    Fmod {
      inner, updates,
      display_counter: 0,
      frame:           0
    }
  }

  #[inline]
  pub const fn display_counter (&self) -> u64 {
    self.display_counter
  }

  #[inline]
  pub const fn reset_display_counter (&mut self) {
    self.display_counter = 0;
  }

  pub fn process_display_events (&mut self) {
    while self.process_display_events_single() { }
  }

  /// Returns `true` if a batch of display events was processed, `false` otherwise
  pub fn process_display_events_single (&mut self) -> bool {
    if let Ok (updates) = self.updates.try_recv() {
      for (node_id, display) in updates {
        self.inner.display_value (node_id, display);
      }
      self.display_counter += 1;
      true
    } else {
      false
    }
  }

  pub fn update_audition (&mut self) {
    self.inner.audition.update();
    self.frame += 1;
  }

  /// Pump display events and update
  pub fn display (&mut self) {
    log::trace!("fmod remote display...");
    self.process_display_events();
    self.update_audition();
    log::trace!("...fmod remote display");
  }
}