eCAL Zero-Copy in Rustecal
This guide shows you how to publish and receive large binary payloads without any memcpy by using:
TypedPublisher<BytesMessage>
- a built-in byte-array message typePayloadWriter
- for direct in-place writes into eCAL's shared memory
1. How eCAL Zero-Copy Works
eCAL zero-copy uses shared memory to avoid copying data between publisher and subscriber.
- Publisher allocates a memory file and writes directly into it via
PayloadWriter
. - Subscriber maps the same memory file and reads the buffer in place.
- Handshake and buffer management are handled by eCAL's SHM layer.
2. PayloadWriter
API
A PayloadWriter
lets you fill the shared-memory buffer in place:
#![allow(unused)] fn main() { pub trait PayloadWriter { /// Called once on first allocation or resize. fn write_full(&mut self, buf: &mut [u8]) -> bool; /// Called on subsequent sends to modify only parts of the buffer. fn write_modified(&mut self, buf: &mut [u8]) -> bool { self.write_full(buf) } /// Returns the exact number of bytes you will write. fn get_size(&self) -> usize; } }
Implement these methods for your payload type, then pass a mutable reference to send_payload_writer
.
3. Publisher Sample
use rustecal::{Configuration, Ecal, EcalComponents, TypedPublisher}; use rustecal_pubsub::PayloadWriter; use rustecal_pubsub::publisher::Timestamp; use rustecal_types_bytes::BytesMessage; /// A simple zero-copy writer that fills a buffer with a repeating pattern. pub struct CustomWriter { size: usize, counter: u8, } impl CustomWriter { pub fn new(size: usize) -> Self { Self { size, counter: 0 } } } impl PayloadWriter for CustomWriter { fn write_full(&mut self, buf: &mut [u8]) -> bool { if buf.len() < self.size { return false; } // fill entire buffer with 0xAA buf[..self.size].fill(0xAA); true } fn write_modified(&mut self, buf: &mut [u8]) -> bool { if buf.len() < self.size { return false; } // flip one byte each time let idx = (self.counter as usize) % self.size; buf[idx] ^= 0xFF; self.counter = self.counter.wrapping_add(1); true } fn get_size(&self) -> usize { self.size } } fn main() -> Result<(), Box<dyn std::error::Error>> { // configure eCAL let mut cfg = Configuration::new()?; cfg.publisher.layer.shm.zero_copy_mode = true as i32; cfg.publisher.layer.shm.acknowledge_timeout_ms = 50; Ecal::initialize( Some("zero copy publisher"), EcalComponents::DEFAULT, Some(&cfg), )?; // create typed publisher let publisher: TypedPublisher<BytesMessage> = TypedPublisher::new("buffer")?; // prepare zero-copy payload writer let mut writer = CustomWriter::new(8 * 1024 * 1024); // 8 MB // send loop while Ecal::ok() { publisher.send_payload_writer(&mut writer, Timestamp::Auto); } // finalize ecal and clean up Ecal::finalize(); Ok(()) }
4. Subscriber Sample
use std::{thread, time::Duration}; use rustecal::{Ecal, EcalComponents, TypedSubscriber}; use rustecal_types_bytes::BytesMessage; fn main() -> Result<(), Box<dyn std::error::Error>> { // initialize eCAL Ecal::initialize( Some("zero copy subscriber"), EcalComponents::DEFAULT, None, )?; // create typed subscriber let mut sub: TypedSubscriber<BytesMessage> = TypedSubscriber::new("buffer")?; // register zero-copy callback sub.set_callback(|received| { // borrow shared-memory payload let buffer: &[u8] = received.payload.data.as_ref(); // this line is just to demonstrate usage (it will kill the performance) println!("Received {} bytes", buffer.len()); }); // keep alive for callbacks while Ecal::ok() { thread::sleep(Duration::from_millis(100)); } // finalize ecal and clean up Ecal::finalize(); Ok(()) }