e_midi_shared 0.1.6

Shared types/midi/MusicXML logic for e_midi and its build script.
Documentation
//! Publisher module for sending events via iceoryx2
//!
//! Provides lock-free, zero-copy publishing of events to subscribers
use iceoryx2::port::publisher::Publisher;
use iceoryx2::prelude::*;
// use iceoryx2::service::ipc::Service;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

use super::{serialize_to_payload, AppId, Event, IpcError, IpcPayload, IpcResult};

/// Lock-free event publisher
///
#[derive(Debug)]
pub struct EventPublisher {
    publisher: Publisher<ipc::Service, IpcPayload, ()>,
    app_id: AppId,
    is_active: Arc<AtomicBool>,
}

impl EventPublisher {
    /// Create a new event publisher for the specified app
    pub fn new(app_id: AppId) -> IpcResult<Self> {
        let service_name = "e_midi_events".to_string();
        println!(
            "[IPC PUBLISHER DEBUG] Using service name: {} (app_id: {:?})",
            service_name, app_id
        );

        // Create node (suppress debug output)
        let node = NodeBuilder::new().create::<ipc::Service>().map_err(|e| {
            eprintln!("[IPC PUBLISHER ERROR] Node creation failed: {:?}", e);
            IpcError::NodeCreation("Node creation failed".to_string())
        })?;

        let service = node
            .service_builder(&ServiceName::new(&service_name).map_err(|e| {
                eprintln!("[IPC PUBLISHER ERROR] Invalid service name: {:?}", e);
                IpcError::ServiceCreation("Invalid service name".to_string())
            })?)
            .publish_subscribe::<IpcPayload>()
            .max_publishers(16)
            .max_subscribers(16)
            .open_or_create()
            .map_err(|e| {
                eprintln!("[IPC PUBLISHER ERROR] Failed to create service: {:?}", e);
                IpcError::ServiceCreation("Failed to create service".to_string())
            })?;

        let publisher = service.publisher_builder().create().map_err(|e| {
            eprintln!("[IPC PUBLISHER ERROR] Failed to create publisher: {:?}", e);
            IpcError::PublisherCreation("Failed to create publisher".to_string())
        })?;

        Ok(Self {
            publisher,
            app_id,
            is_active: Arc::new(AtomicBool::new(true)),
        })
    }
    /// Publish an event (lock-free, zero-copy when possible)
    pub fn publish(&self, event: Event) -> IpcResult<()> {
        if !self.is_active.load(Ordering::Relaxed) {
            return Err(IpcError::SendError("Publisher is not active".to_string()));
        }

        // Serialize event to payload
        let payload = serialize_to_payload(&event)?;
        // Send via iceoryx2 using send_copy for simplicity
        match self.publisher.send_copy(payload) {
            Ok(_) => {
                println!("[IPC PUBLISHER DEBUG] Successfully sent event: {:?}", event);
            }
            Err(e) => {
                eprintln!(
                    "[IPC PUBLISHER ERROR] Failed to send event: {:?} (error: {:?})",
                    event, e
                );
                return Err(IpcError::SendError(format!(
                    "Failed to send event: {:?}",
                    e
                )));
            }
        }

        Ok(())
    }
    /// Publish multiple events in batch (more efficient)
    pub fn publish_batch(&self, events: Vec<Event>) -> IpcResult<()> {
        if !self.is_active.load(Ordering::Relaxed) {
            return Err(IpcError::SendError("Publisher is not active".to_string()));
        }

        // Serialize all events to a single payload
        let payload = serialize_to_payload(&events)?;
        self.publisher
            .send_copy(payload)
            .map_err(|_| IpcError::SendError("Failed to send batch".to_string()))?;

        Ok(())
    }

    /// Check if the publisher is active
    pub fn is_active(&self) -> bool {
        self.is_active.load(Ordering::Relaxed)
    }

    /// Deactivate the publisher (graceful shutdown)
    pub fn deactivate(&self) {
        self.is_active.store(false, Ordering::Relaxed);
    }

    /// Get the app ID this publisher represents
    pub fn app_id(&self) -> AppId {
        self.app_id
    }
}

impl Drop for EventPublisher {
    fn drop(&mut self) {
        self.deactivate();
    }
}

// Publisher is Send + Sync for lock-free usage across threads
unsafe impl Send for EventPublisher {}
unsafe impl Sync for EventPublisher {}

/// Convenience functions for common publishing patterns
impl EventPublisher {
    /// Publish a heartbeat event
    pub fn heartbeat(&self) -> IpcResult<()> {
        self.publish(Event::system_heartbeat(self.app_id))
    }

    /// Publish MIDI events
    pub fn midi_started(&self, song_index: usize, song_name: String) -> IpcResult<()> {
        self.publish(Event::midi_playback_started(song_index, song_name))
    }

    pub fn midi_stopped(&self) -> IpcResult<()> {
        self.publish(Event::midi_playback_stopped())
    }

    pub fn midi_tempo_changed(&self, new_tempo: u32) -> IpcResult<()> {
        self.publish(Event::midi_tempo_changed(new_tempo))
    }

    pub fn midi_progress(&self, progress_ms: u32, total_ms: u32) -> IpcResult<()> {
        self.publish(Event::midi_progress_update(progress_ms, total_ms))
    }
}