Skip to main content

beamer_core/
buffer_storage.rs

1//! Pre-allocated buffer storage for real-time safe audio processing.
2//!
3//! This module provides [`ProcessBufferStorage`], which pre-allocates capacity
4//! for channel pointers during plugin setup. The storage is then reused for each
5//! render call without allocations.
6//!
7//! # Pattern
8//!
9//! This storage follows a consistent pattern across all plugin formats:
10//! 1. Allocate storage once during setup (non-real-time)
11//! 2. Clear storage at start of each render (O(1), no deallocation)
12//! 3. Push pointers from host buffers (never exceeds capacity)
13//! 4. Build slices from pointers
14//!
15//! # Memory Optimization Strategy
16//!
17//! The allocation strategy is **config-based, not worst-case**:
18//!
19//! - **Channel counts**: Allocates exact number of channels from bus config, not MAX_CHANNELS
20//! - **Bus counts**: Allocates only for buses that exist, not MAX_BUSES
21//! - **Lazy aux allocation**: No heap allocation for aux buses if plugin doesn't use them
22//! - **Asymmetric support**: Mono input can have stereo output (allocates 1 + 2, not 2 + 2)
23//!
24//! Examples:
25//! - Mono plugin (1in/1out): Allocates 2 pointers (16 bytes on 64-bit)
26//! - Stereo plugin (2in/2out): Allocates 4 pointers (32 bytes on 64-bit)
27//! - Stereo with sidechain (2+2in/2out): Allocates 6 pointers (48 bytes on 64-bit)
28//! - Worst-case (32ch x 16 buses): Would be MAX_CHANNELS * MAX_BUSES = 512 pointers (4KB)
29//!
30//! This means simple plugins use **32x less memory** than worst-case allocation.
31//!
32//! # Real-Time Safety
33//!
34//! - `clear()` is O(1) - only sets Vec lengths to 0
35//! - `push()` never allocates - capacity is pre-reserved
36//! - No heap operations during audio processing
37//! - All allocations happen in `allocate_from_config()` (non-real-time)
38
39use crate::bus_config::CachedBusConfig;
40use crate::sample::Sample;
41use std::slice;
42
43/// Pre-allocated storage for audio processing channel pointers.
44///
45/// Stores channel pointers collected from host audio buffers during render.
46/// The Vecs have pre-allocated capacity matching the **actual** bus configuration,
47/// ensuring no allocations occur during audio callbacks while minimizing memory usage.
48///
49/// # Memory Layout
50///
51/// The storage is optimized based on the actual plugin configuration:
52/// - `main_inputs`: Capacity = actual input channel count (e.g., 1 for mono, 2 for stereo)
53/// - `main_outputs`: Capacity = actual output channel count (e.g., 1 for mono, 2 for stereo)
54/// - `aux_inputs`: Only allocated if plugin declares aux input buses
55/// - `aux_outputs`: Only allocated if plugin declares aux output buses
56///
57/// This means a simple stereo plugin uses only 32 bytes (4 pointers x 8 bytes),
58/// not the worst-case 4KB (MAX_CHANNELS x MAX_BUSES x pointer size).
59///
60/// # Type Parameter
61///
62/// `S` is the sample type (`f32` or `f64`).
63#[derive(Clone)]
64pub struct ProcessBufferStorage<S: Sample> {
65    /// Main input channel pointers (capacity = actual channel count).
66    /// Format wrappers may access this directly for performance-critical inline collection.
67    pub main_inputs: Vec<*const S>,
68    /// Main output channel pointers (capacity = actual channel count).
69    /// Format wrappers may access this directly for performance-critical inline collection.
70    pub main_outputs: Vec<*mut S>,
71    /// Auxiliary input buses (only allocated if plugin uses them).
72    /// Format wrappers may access this directly for performance-critical inline collection.
73    pub aux_inputs: Vec<Vec<*const S>>,
74    /// Auxiliary output buses (only allocated if plugin uses them).
75    /// Format wrappers may access this directly for performance-critical inline collection.
76    pub aux_outputs: Vec<Vec<*mut S>>,
77    /// Internal output buffers for instruments (when host provides null pointers).
78    /// Only allocated for plugins with no input buses (instruments/generators).
79    /// When a host provides null output pointers, the format wrapper can use these
80    /// buffers and update the host's buffer list to point to them.
81    pub internal_output_buffers: Option<Vec<Vec<S>>>,
82    /// Max frames for internal buffers (set during allocation).
83    /// Used to validate that num_samples doesn't exceed buffer capacity.
84    pub max_frames: usize,
85}
86
87impl<S: Sample> ProcessBufferStorage<S> {
88    /// Create empty storage (no capacity reserved).
89    pub fn new() -> Self {
90        Self {
91            main_inputs: Vec::new(),
92            main_outputs: Vec::new(),
93            aux_inputs: Vec::new(),
94            aux_outputs: Vec::new(),
95            internal_output_buffers: None,
96            max_frames: 0,
97        }
98    }
99
100    /// Create storage from cached bus configuration (recommended).
101    ///
102    /// This is the preferred way to allocate storage as it automatically
103    /// extracts the correct channel counts from the bus configuration.
104    /// Should be called during plugin setup (non-real-time).
105    ///
106    /// # Memory Optimization
107    ///
108    /// This method implements smart allocation strategies:
109    /// - Allocates only for channels actually present in the config
110    /// - No pre-allocation for aux buses if plugin doesn't use them
111    /// - Uses actual channel counts, not MAX_CHANNELS worst-case
112    /// - Zero heap allocation for simple mono/stereo plugins without aux buses
113    ///
114    /// # Arguments
115    ///
116    /// * `bus_config` - Cached bus configuration
117    /// * `max_frames` - Maximum frames per render call (for internal buffer allocation)
118    ///
119    /// # Example
120    ///
121    /// ```ignore
122    /// let config = extract_bus_config();
123    /// config.validate()?;
124    /// let storage = ProcessBufferStorage::allocate_from_config(&config, 4096);
125    /// ```
126    pub fn allocate_from_config(bus_config: &CachedBusConfig, max_frames: usize) -> Self {
127        // Extract main bus channel counts (bus 0)
128        let main_in_channels = bus_config
129            .input_bus_info(0)
130            .map(|b| b.channel_count)
131            .unwrap_or(0);
132        let main_out_channels = bus_config
133            .output_bus_info(0)
134            .map(|b| b.channel_count)
135            .unwrap_or(0);
136
137        // Count auxiliary buses (all buses except main bus 0)
138        let aux_in_buses = bus_config.input_bus_count.saturating_sub(1);
139        let aux_out_buses = bus_config.output_bus_count.saturating_sub(1);
140
141        // Optimization: Only allocate aux bus storage if actually needed.
142        // For simple plugins (mono/stereo with no aux), this avoids any
143        // heap allocation for the outer Vec containers.
144        let aux_inputs = if aux_in_buses > 0 {
145            let mut vec = Vec::with_capacity(aux_in_buses);
146            for i in 1..=aux_in_buses {
147                let channels = bus_config
148                    .input_bus_info(i)
149                    .map(|b| b.channel_count)
150                    .unwrap_or(0);
151                vec.push(Vec::with_capacity(channels));
152            }
153            vec
154        } else {
155            Vec::new() // Zero-capacity allocation - no heap memory
156        };
157
158        let aux_outputs = if aux_out_buses > 0 {
159            let mut vec = Vec::with_capacity(aux_out_buses);
160            for i in 1..=aux_out_buses {
161                let channels = bus_config
162                    .output_bus_info(i)
163                    .map(|b| b.channel_count)
164                    .unwrap_or(0);
165                vec.push(Vec::with_capacity(channels));
166            }
167            vec
168        } else {
169            Vec::new() // Zero-capacity allocation - no heap memory
170        };
171
172        // For instruments (no input buses), allocate internal output buffers.
173        // Some hosts (Logic Pro, Reaper) may provide null output buffer pointers,
174        // expecting the plugin to use its own buffers.
175        let internal_output_buffers = if main_in_channels == 0 && main_out_channels > 0 {
176            let mut buffers = Vec::with_capacity(main_out_channels);
177            for _ in 0..main_out_channels {
178                buffers.push(vec![S::ZERO; max_frames]);
179            }
180            Some(buffers)
181        } else {
182            None
183        };
184
185        Self {
186            main_inputs: Vec::with_capacity(main_in_channels),
187            main_outputs: Vec::with_capacity(main_out_channels),
188            aux_inputs,
189            aux_outputs,
190            internal_output_buffers,
191            max_frames,
192        }
193    }
194
195    /// Create new storage with pre-allocated capacity (manual).
196    ///
197    /// This is a lower-level method for manual capacity specification.
198    /// Prefer `allocate_from_config()` when possible as it's less error-prone.
199    ///
200    /// # Arguments
201    ///
202    /// * `main_in_channels` - Number of main input channels
203    /// * `main_out_channels` - Number of main output channels
204    /// * `aux_in_buses` - Number of auxiliary input buses
205    /// * `aux_out_buses` - Number of auxiliary output buses
206    /// * `aux_channels` - Channels per aux bus (assumes uniform)
207    pub fn allocate(
208        main_in_channels: usize,
209        main_out_channels: usize,
210        aux_in_buses: usize,
211        aux_out_buses: usize,
212        aux_channels: usize,
213    ) -> Self {
214        let mut aux_inputs = Vec::with_capacity(aux_in_buses);
215        for _ in 0..aux_in_buses {
216            aux_inputs.push(Vec::with_capacity(aux_channels));
217        }
218
219        let mut aux_outputs = Vec::with_capacity(aux_out_buses);
220        for _ in 0..aux_out_buses {
221            aux_outputs.push(Vec::with_capacity(aux_channels));
222        }
223
224        Self {
225            main_inputs: Vec::with_capacity(main_in_channels),
226            main_outputs: Vec::with_capacity(main_out_channels),
227            aux_inputs,
228            aux_outputs,
229            internal_output_buffers: None, // Manual allocation doesn't set up internal buffers
230            max_frames: 0,
231        }
232    }
233
234    /// Clear all pointer storage without deallocating.
235    ///
236    /// This is O(1) - it only sets Vec lengths to 0 while preserving capacity.
237    /// Call this at the start of each render call.
238    #[inline]
239    pub fn clear(&mut self) {
240        self.main_inputs.clear();
241        self.main_outputs.clear();
242        for bus in &mut self.aux_inputs {
243            bus.clear();
244        }
245        for bus in &mut self.aux_outputs {
246            bus.clear();
247        }
248    }
249
250    /// Get the number of input channels collected.
251    #[inline]
252    pub fn input_channel_count(&self) -> usize {
253        self.main_inputs.len()
254    }
255
256    /// Get the number of output channels collected.
257    #[inline]
258    pub fn output_channel_count(&self) -> usize {
259        self.main_outputs.len()
260    }
261
262    /// Get the number of auxiliary input buses.
263    #[inline]
264    pub fn aux_input_bus_count(&self) -> usize {
265        self.aux_inputs.len()
266    }
267
268    /// Get the number of auxiliary output buses.
269    #[inline]
270    pub fn aux_output_bus_count(&self) -> usize {
271        self.aux_outputs.len()
272    }
273
274    /// Get the maximum frames for internal buffers.
275    #[inline]
276    pub fn max_frames(&self) -> usize {
277        self.max_frames
278    }
279
280    /// Check if internal output buffers are available.
281    #[inline]
282    pub fn has_internal_output_buffers(&self) -> bool {
283        self.internal_output_buffers.is_some()
284    }
285
286    /// Push a main input pointer.
287    ///
288    /// # Safety
289    ///
290    /// The pointer must be valid for the duration of the current render call.
291    #[inline]
292    pub unsafe fn push_main_input(&mut self, ptr: *const S) {
293        self.main_inputs.push(ptr);
294    }
295
296    /// Push a main output pointer.
297    ///
298    /// # Safety
299    ///
300    /// The pointer must be valid for the duration of the current render call.
301    #[inline]
302    pub unsafe fn push_main_output(&mut self, ptr: *mut S) {
303        self.main_outputs.push(ptr);
304    }
305
306    /// Push an auxiliary input pointer for a specific bus.
307    ///
308    /// # Safety
309    ///
310    /// The pointer must be valid for the duration of the current render call.
311    #[inline]
312    pub unsafe fn push_aux_input(&mut self, bus_index: usize, ptr: *const S) {
313        if bus_index < self.aux_inputs.len() {
314            self.aux_inputs[bus_index].push(ptr);
315        }
316    }
317
318    /// Push an auxiliary output pointer for a specific bus.
319    ///
320    /// # Safety
321    ///
322    /// The pointer must be valid for the duration of the current render call.
323    #[inline]
324    pub unsafe fn push_aux_output(&mut self, bus_index: usize, ptr: *mut S) {
325        if bus_index < self.aux_outputs.len() {
326            self.aux_outputs[bus_index].push(ptr);
327        }
328    }
329
330    /// Get the main input capacity.
331    #[inline]
332    pub fn main_input_capacity(&self) -> usize {
333        self.main_inputs.capacity()
334    }
335
336    /// Get the main output capacity.
337    #[inline]
338    pub fn main_output_capacity(&self) -> usize {
339        self.main_outputs.capacity()
340    }
341
342    /// Get an auxiliary input bus capacity.
343    #[inline]
344    pub fn aux_input_capacity(&self, bus_index: usize) -> usize {
345        self.aux_inputs.get(bus_index).map(|v| v.capacity()).unwrap_or(0)
346    }
347
348    /// Get an auxiliary output bus capacity.
349    #[inline]
350    pub fn aux_output_capacity(&self, bus_index: usize) -> usize {
351        self.aux_outputs.get(bus_index).map(|v| v.capacity()).unwrap_or(0)
352    }
353
354    /// Build input slices from collected pointers.
355    ///
356    /// # Safety
357    ///
358    /// - Pointers must still be valid (within same render call)
359    /// - num_samples must match what was used in collection
360    #[inline]
361    pub unsafe fn input_slices(&self, num_samples: usize) -> Vec<&[S]> {
362        self.main_inputs
363            .iter()
364            .map(|&ptr| slice::from_raw_parts(ptr, num_samples))
365            .collect()
366    }
367
368    /// Build output slices from collected pointers.
369    ///
370    /// # Safety
371    ///
372    /// - Pointers must still be valid (within same render call)
373    /// - num_samples must match what was used in collection
374    ///
375    /// # Clippy Allow: mut_from_ref
376    ///
377    /// Returns `&mut [S]` from `&self` because we're converting raw pointers stored in the struct,
378    /// not mutating `self`. This is a common and safe FFI pattern where:
379    /// - Raw pointers (`*mut S`) are stored during collection
380    /// - Those pointers are then converted back to safe references
381    /// - The mutable references are to the external buffer memory, not to `self`
382    /// - Host guarantees single-threaded render access, preventing aliasing
383    #[inline]
384    #[allow(clippy::mut_from_ref)]
385    pub unsafe fn output_slices(&self, num_samples: usize) -> Vec<&mut [S]> {
386        self.main_outputs
387            .iter()
388            .map(|&ptr| slice::from_raw_parts_mut(ptr, num_samples))
389            .collect()
390    }
391
392    /// Build auxiliary input slices from collected pointers.
393    ///
394    /// Returns a Vec of buses, where each bus is a Vec of channel slices.
395    ///
396    /// # Safety
397    ///
398    /// - Pointers must still be valid (within same render call)
399    /// - num_samples must match what was used in collection
400    #[inline]
401    pub unsafe fn aux_input_slices(&self, num_samples: usize) -> Vec<Vec<&[S]>> {
402        self.aux_inputs
403            .iter()
404            .map(|bus| {
405                bus.iter()
406                    .map(|&ptr| slice::from_raw_parts(ptr, num_samples))
407                    .collect()
408            })
409            .collect()
410    }
411
412    /// Build auxiliary output slices from collected pointers.
413    ///
414    /// Returns a Vec of buses, where each bus is a Vec of channel slices.
415    ///
416    /// # Safety
417    ///
418    /// - Pointers must still be valid (within same render call)
419    /// - num_samples must match what was used in collection
420    ///
421    /// # Clippy Allow: mut_from_ref
422    ///
423    /// Same justification as `output_slices` - converts raw pointers to mutable references.
424    #[inline]
425    #[allow(clippy::mut_from_ref)]
426    pub unsafe fn aux_output_slices(&self, num_samples: usize) -> Vec<Vec<&mut [S]>> {
427        self.aux_outputs
428            .iter()
429            .map(|bus| {
430                bus.iter()
431                    .map(|&ptr| slice::from_raw_parts_mut(ptr, num_samples))
432                    .collect()
433            })
434            .collect()
435    }
436
437    /// Get a mutable pointer to an internal output buffer channel.
438    ///
439    /// Returns `None` if internal buffers are not allocated or channel is out of range.
440    ///
441    /// # Arguments
442    ///
443    /// * `channel` - The channel index
444    #[inline]
445    pub fn internal_output_buffer_mut(&mut self, channel: usize) -> Option<&mut Vec<S>> {
446        self.internal_output_buffers
447            .as_mut()
448            .and_then(|buffers| buffers.get_mut(channel))
449    }
450
451    /// Get a reference to an internal output buffer channel.
452    ///
453    /// Returns `None` if internal buffers are not allocated or channel is out of range.
454    #[inline]
455    pub fn internal_output_buffer(&self, channel: usize) -> Option<&Vec<S>> {
456        self.internal_output_buffers
457            .as_ref()
458            .and_then(|buffers| buffers.get(channel))
459    }
460
461    /// Get the number of internal output buffer channels.
462    #[inline]
463    pub fn internal_output_buffer_count(&self) -> usize {
464        self.internal_output_buffers
465            .as_ref()
466            .map(|b| b.len())
467            .unwrap_or(0)
468    }
469}
470
471impl<S: Sample> Default for ProcessBufferStorage<S> {
472    fn default() -> Self {
473        Self::new()
474    }
475}
476
477// SAFETY: The raw pointers are only used within a single render call
478// where the host guarantees single-threaded access.
479unsafe impl<S: Sample> Send for ProcessBufferStorage<S> {}
480unsafe impl<S: Sample> Sync for ProcessBufferStorage<S> {}
481
482#[cfg(test)]
483mod tests {
484    use super::*;
485    use crate::bus_config::{CachedBusInfo};
486    use crate::plugin::BusType;
487    use crate::types::MAX_CHANNELS;
488
489    #[test]
490    fn test_validate_bus_limits_success() {
491        let config = CachedBusConfig::new(
492            vec![CachedBusInfo::new(2, BusType::Main)],
493            vec![CachedBusInfo::new(2, BusType::Main)],
494        );
495
496        assert!(config.validate().is_ok());
497    }
498
499    #[test]
500    fn test_validate_bus_limits_too_many_channels() {
501        let config = CachedBusConfig::new(
502            vec![CachedBusInfo::new(MAX_CHANNELS + 1, BusType::Main)],
503            vec![CachedBusInfo::new(2, BusType::Main)],
504        );
505
506        assert!(config.validate().is_err());
507    }
508
509    #[test]
510    fn test_allocate_from_config_stereo() {
511        let config = CachedBusConfig::default(); // 2in/2out
512        let storage: ProcessBufferStorage<f32> =
513            ProcessBufferStorage::allocate_from_config(&config, 4096);
514
515        assert_eq!(storage.main_inputs.capacity(), 2);
516        assert_eq!(storage.main_outputs.capacity(), 2);
517        assert_eq!(storage.aux_inputs.len(), 0);
518        assert_eq!(storage.aux_outputs.len(), 0);
519    }
520
521    #[test]
522    fn test_allocate_from_config_with_aux() {
523        let config = CachedBusConfig::new(
524            vec![
525                CachedBusInfo::new(2, BusType::Main),
526                CachedBusInfo::new(2, BusType::Aux),
527            ],
528            vec![CachedBusInfo::new(6, BusType::Main)],
529        );
530
531        let storage: ProcessBufferStorage<f32> =
532            ProcessBufferStorage::allocate_from_config(&config, 4096);
533
534        assert_eq!(storage.main_inputs.capacity(), 2);
535        assert_eq!(storage.main_outputs.capacity(), 6);
536        assert_eq!(storage.aux_inputs.len(), 1);
537        assert_eq!(storage.aux_inputs[0].capacity(), 2);
538        assert_eq!(storage.aux_outputs.len(), 0);
539    }
540
541    #[test]
542    fn test_allocate_and_clear() {
543        let mut storage: ProcessBufferStorage<f32> = ProcessBufferStorage::allocate(2, 2, 1, 0, 2);
544
545        // Verify capacities
546        assert_eq!(storage.main_inputs.capacity(), 2);
547        assert_eq!(storage.main_outputs.capacity(), 2);
548        assert_eq!(storage.aux_inputs.len(), 1);
549        assert_eq!(storage.aux_inputs[0].capacity(), 2);
550
551        // Simulate pushing pointers
552        let dummy: f32 = 0.0;
553        unsafe {
554            storage.push_main_input(&dummy as *const f32);
555            storage.push_main_input(&dummy as *const f32);
556        }
557
558        assert_eq!(storage.main_inputs.len(), 2);
559
560        // Clear should reset length but not capacity
561        storage.clear();
562        assert_eq!(storage.main_inputs.len(), 0);
563        assert_eq!(storage.main_inputs.capacity(), 2);
564    }
565
566    #[test]
567    fn test_allocate_from_config_mono() {
568        // Mono plugin: 1 in, 1 out, no aux buses
569        let config = CachedBusConfig::new(
570            vec![CachedBusInfo::new(1, BusType::Main)],
571            vec![CachedBusInfo::new(1, BusType::Main)],
572        );
573
574        let storage: ProcessBufferStorage<f32> =
575            ProcessBufferStorage::allocate_from_config(&config, 4096);
576
577        assert_eq!(storage.main_inputs.capacity(), 1);
578        assert_eq!(storage.main_outputs.capacity(), 1);
579        assert_eq!(storage.aux_inputs.len(), 0);
580        assert_eq!(storage.aux_outputs.len(), 0);
581    }
582
583    #[test]
584    fn test_instrument_internal_buffers_allocated() {
585        // Instruments have 0 inputs and >0 outputs - should allocate internal buffers
586        let config = CachedBusConfig::new(
587            vec![], // No input buses (instrument)
588            vec![CachedBusInfo::new(2, BusType::Main)],
589        );
590
591        let storage: ProcessBufferStorage<f32> =
592            ProcessBufferStorage::allocate_from_config(&config, 512);
593
594        // Internal buffers should be allocated for instruments
595        assert!(storage.internal_output_buffers.is_some());
596        let internal = storage.internal_output_buffers.as_ref().unwrap();
597        assert_eq!(internal.len(), 2); // Stereo
598        assert_eq!(internal[0].len(), 512); // max_frames
599        assert_eq!(internal[1].len(), 512);
600        assert_eq!(storage.max_frames, 512);
601    }
602
603    #[test]
604    fn test_effect_no_internal_buffers() {
605        // Effects have inputs - should NOT allocate internal buffers
606        let config = CachedBusConfig::default(); // 2in/2out
607
608        let storage: ProcessBufferStorage<f32> =
609            ProcessBufferStorage::allocate_from_config(&config, 512);
610
611        // Effects don't need internal buffers
612        assert!(storage.internal_output_buffers.is_none());
613    }
614
615    #[test]
616    fn test_clear_maintains_capacity() {
617        let config = CachedBusConfig::new(
618            vec![
619                CachedBusInfo::new(2, BusType::Main),
620                CachedBusInfo::new(2, BusType::Aux),
621            ],
622            vec![CachedBusInfo::new(2, BusType::Main)],
623        );
624
625        let mut storage: ProcessBufferStorage<f32> =
626            ProcessBufferStorage::allocate_from_config(&config, 4096);
627
628        // Record initial capacities
629        let main_in_cap = storage.main_inputs.capacity();
630        let main_out_cap = storage.main_outputs.capacity();
631        let aux_in_count = storage.aux_inputs.len();
632        let aux_in_cap = if aux_in_count > 0 {
633            storage.aux_inputs[0].capacity()
634        } else {
635            0
636        };
637
638        // Simulate some usage
639        let dummy: f32 = 0.0;
640        unsafe {
641            storage.push_main_input(&dummy as *const f32);
642            storage.push_main_input(&dummy as *const f32);
643            if aux_in_count > 0 {
644                storage.push_aux_input(0, &dummy as *const f32);
645            }
646        }
647
648        // Clear and verify capacities are unchanged
649        storage.clear();
650
651        assert_eq!(storage.main_inputs.capacity(), main_in_cap);
652        assert_eq!(storage.main_outputs.capacity(), main_out_cap);
653        assert_eq!(storage.aux_inputs.len(), aux_in_count);
654        if aux_in_count > 0 {
655            assert_eq!(storage.aux_inputs[0].capacity(), aux_in_cap);
656        }
657
658        // Verify lengths are reset to 0
659        assert_eq!(storage.main_inputs.len(), 0);
660        assert_eq!(storage.main_outputs.len(), 0);
661        if aux_in_count > 0 {
662            assert_eq!(storage.aux_inputs[0].len(), 0);
663        }
664    }
665}