Skip to content

Commit 20fbd37

Browse files
committed
Replace atomic_refcell with parking_lot
1 parent 6776b92 commit 20fbd37

4 files changed

Lines changed: 74 additions & 41 deletions

File tree

plinth-plugin/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ license = "MIT"
1212
standalone = ["dep:cpal", "dep:midir", "dep:winit"]
1313

1414
[dependencies]
15-
atomic_refcell = "0.1"
1615
clap-sys = "0.5"
1716
keyboard-types.workspace = true
1817
num-derive = "0.4"
1918
num-traits.workspace = true
19+
parking_lot = "0.12"
2020
plinth-core.workspace = true
2121
portable-atomic.workspace = true
2222
raw-window-handle.workspace = true

plinth-plugin/src/formats/clap/extensions/params.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,23 +142,30 @@ impl<P: ClapPlugin> Params<P> {
142142
PluginInstance::with_plugin_instance(plugin, |instance: &mut PluginInstance<P>| {
143143
instance.process_events_to_plugin();
144144

145-
let host_events = EventIterator::new(&instance.parameter_info, unsafe { &*in_events });
145+
let host_events = EventIterator::new(&instance.parameter_info, unsafe { &*in_events });
146146
let editor_events = instance.parameter_event_map.iter_and_send_to_host(&instance.parameter_info, out_events);
147147
let all_events = host_events.chain(editor_events);
148148

149149
if instance.audio_thread_state.active.load(Ordering::Acquire) {
150-
let mut processor_ref = instance.audio_thread_state.processor.borrow_mut();
150+
// Real-time safety: parking_lot Mutex is guaranteed to not do syscalls when uncontested
151+
// Contestion can only occur if we're setting up or tearing down the processor while flush is called
152+
// In that case, ignore the flush call and request a new flush
153+
let Some(mut processor_ref) = instance.audio_thread_state.processor.try_lock() else {
154+
unsafe { ((*instance.host_ext_params).request_flush.unwrap())(instance.host) };
155+
return;
156+
};
157+
151158
let Some(processor) = processor_ref.as_mut() else {
152159
return;
153160
};
154161

155162
// When we have a processor, process events directly
156163
processor.process_events(all_events);
157164
drop(processor_ref);
158-
165+
159166
// Also send them to the main thread through the queue
160167
instance.send_events_to_plugin(in_events);
161-
168+
162169
// Send a callback request so the main thread can process them
163170
unsafe { ((*instance.host).request_callback.unwrap())(instance.host); }
164171
} else {

plinth-plugin/src/formats/clap/plugin_instance.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::{collections::BTreeMap, ffi::{CStr, c_char, c_void}, iter::zip, ptr::{null, null_mut}, sync::{Arc, atomic::{AtomicBool, AtomicUsize, Ordering}}};
22

3-
use atomic_refcell::AtomicRefCell;
43
use clap_sys::{events::clap_input_events, ext::{audio_ports::CLAP_EXT_AUDIO_PORTS, draft::undo::{CLAP_EXT_UNDO, clap_host_undo}, gui::{CLAP_EXT_GUI, clap_host_gui}, latency::CLAP_EXT_LATENCY, note_ports::CLAP_EXT_NOTE_PORTS, params::{CLAP_EXT_PARAMS, clap_host_params}, render::CLAP_EXT_RENDER, state::{CLAP_EXT_STATE, clap_host_state}, tail::{CLAP_EXT_TAIL, clap_host_tail}, timer_support::{CLAP_EXT_TIMER_SUPPORT, clap_host_timer_support}}, host::clap_host, plugin::clap_plugin, process::{CLAP_PROCESS_CONTINUE, CLAP_PROCESS_CONTINUE_IF_NOT_QUIET, CLAP_PROCESS_ERROR, CLAP_PROCESS_TAIL, clap_process, clap_process_status}};
54
use tracing::error;
65
use plinth_core::signals::{ptr_signal::{PtrSignal, PtrSignalMut}, signal::SignalMut};
@@ -18,7 +17,7 @@ use super::plugin::ClapPlugin;
1817
pub struct AudioThreadState<P: ClapPlugin> {
1918
// When active is true, we have a processor
2019
pub(super) active: AtomicBool,
21-
pub(super) processor: AtomicRefCell<Option<P::Processor>>,
20+
pub(super) processor: parking_lot::Mutex<Option<P::Processor>>,
2221
pub(super) tail: AtomicUsize,
2322
}
2423

@@ -230,7 +229,7 @@ impl<P: ClapPlugin> PluginInstance<P> {
230229

231230
instance.sample_rate = sample_rate;
232231

233-
let mut processor = instance.audio_thread_state.processor.borrow_mut();
232+
let mut processor = instance.audio_thread_state.processor.lock();
234233
*processor = Some(instance.plugin.as_ref().unwrap().create_processor(config));
235234

236235
instance.audio_thread_state.active.store(true, Ordering::Release);
@@ -243,7 +242,7 @@ impl<P: ClapPlugin> PluginInstance<P> {
243242
tracing::trace!("plugin::deactivate");
244243

245244
Self::with_plugin_instance(plugin, |instance| {
246-
*instance.audio_thread_state.processor.borrow_mut() = None;
245+
*instance.audio_thread_state.processor.lock() = None;
247246
instance.audio_thread_state.active.store(false, Ordering::Release);
248247
});
249248
}
@@ -262,7 +261,9 @@ impl<P: ClapPlugin> PluginInstance<P> {
262261
tracing::trace!("plugin::reset");
263262

264263
Self::with_plugin_instance(plugin, |instance| {
265-
let mut processor = instance.audio_thread_state.processor.borrow_mut();
264+
// Real-time safety: parking_lot Mutex is guaranteed to not do syscalls when uncontested
265+
// Contestion can only occur if we're setting up or tearing down the processor while reset is called
266+
let mut processor = instance.audio_thread_state.processor.lock();
266267
if let Some(processor) = processor.as_mut() {
267268
processor.reset();
268269
}
@@ -309,7 +310,15 @@ impl<P: ClapPlugin> PluginInstance<P> {
309310
}
310311

311312
Self::with_plugin_instance(plugin, |instance| {
312-
let mut processor_ref = instance.audio_thread_state.processor.borrow_mut();
313+
// Real-time safety: parking_lot Mutex is guaranteed to not do syscalls when uncontested
314+
// Contestion can only occur if we're setting up or tearing down the processor while process is called
315+
// In that case, we will simply output silence
316+
let Some(mut processor_ref) = instance.audio_thread_state.processor.try_lock() else {
317+
output.fill(0.0);
318+
319+
return CLAP_PROCESS_CONTINUE;
320+
};
321+
313322
let Some(processor) = processor_ref.as_mut() else {
314323
return CLAP_PROCESS_ERROR;
315324
};

plinth-plugin/src/formats/vst3/component.rs

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use std::ptr::null_mut;
66
use std::rc::Rc;
77
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
88

9-
use atomic_refcell::AtomicRefCell;
109
use plinth_core::signals::ptr_signal::{PtrSignal, PtrSignalMut};
1110
use plinth_core::signals::signal::SignalMut;
1211
use vst3::Steinberg::Vst::ControllerNumbers_::kPitchBend;
@@ -32,7 +31,7 @@ const ROOT_UNIT_ID: i32 = 0;
3231
const FIRST_UNIT_ID: i32 = 1;
3332

3433
pub struct AudioThreadState<P: Vst3Plugin> {
35-
processor: AtomicRefCell<Option<P::Processor>>,
34+
processor: parking_lot::Mutex<Option<P::Processor>>,
3635
aux_active: AtomicBool,
3736
}
3837

@@ -257,7 +256,7 @@ impl<P: Vst3Plugin> IAudioProcessorTrait for PluginComponent<P> {
257256
return kResultFalse;
258257
};
259258

260-
let mut processor = self.audio_thread_state.processor.borrow_mut();
259+
let mut processor = self.audio_thread_state.processor.lock();
261260
*processor = Some(plugin.create_processor(processor_config));
262261

263262
// Cache latency since it's not allowed to change during processing
@@ -272,7 +271,7 @@ impl<P: Vst3Plugin> IAudioProcessorTrait for PluginComponent<P> {
272271
let processing = state != 0;
273272
self.processing.store(processing, Ordering::Release);
274273

275-
let mut processor = self.audio_thread_state.processor.borrow_mut();
274+
let mut processor = self.audio_thread_state.processor.lock();
276275
if let Some(processor) = processor.as_mut() && !processing {
277276
processor.reset();
278277
}
@@ -287,42 +286,60 @@ impl<P: Vst3Plugin> IAudioProcessorTrait for PluginComponent<P> {
287286
let parameter_change_iterator = ParameterChangeIterator::new(data.inputParameterChanges, *self.pitch_bend_parameter_ids.borrow());
288287
let event_iterator = EventIterator::new(data.inputEvents);
289288
let all_events = event_iterator.chain(parameter_change_iterator);
289+
let is_data_dump = data.inputs.is_null() || data.outputs.is_null() || data.numInputs == 0 || data.numSamples == 0;
290290

291-
let mut processor = self.audio_thread_state.processor.borrow_mut();
292-
let Some(processor) = processor.as_mut() else {
291+
// On some platforms, this cast is needed
292+
#[allow(clippy::unnecessary_cast)]
293+
if !is_data_dump && data.symbolicSampleSize != SymbolicSampleSizes_::kSample32 as i32 {
293294
return kResultFalse;
295+
}
296+
297+
// Prepare inputs & outputs
298+
let (main_input, main_output, aux_input) = if is_data_dump {
299+
(None, None, None)
300+
} else {
301+
let inputs = unsafe { std::slice::from_raw_parts(data.inputs, data.numInputs as _) };
302+
let outputs = unsafe { std::slice::from_raw_parts(data.outputs, data.numOutputs as _) };
303+
let main_input = inputs[0];
304+
let main_output = outputs[0];
305+
assert_eq!(main_input.numChannels, main_output.numChannels);
306+
307+
let aux_input = if P::HAS_AUX_INPUT && self.audio_thread_state.aux_active.load(Ordering::Acquire) {
308+
assert_eq!(data.numInputs, 2);
309+
let aux_input = inputs[1];
310+
Some(unsafe { PtrSignal::from_pointers(aux_input.numChannels as usize, data.numSamples as usize, aux_input.__field0.channelBuffers32 as _) })
311+
} else {
312+
None
313+
};
314+
315+
let main_input = unsafe { PtrSignal::from_pointers(main_input.numChannels as usize, data.numSamples as usize, main_input.__field0.channelBuffers32 as _) };
316+
let main_output = unsafe { PtrSignalMut::from_pointers(main_output.numChannels as usize, data.numSamples as usize, main_output.__field0.channelBuffers32) };
317+
318+
(Some(main_input), Some(main_output), aux_input)
294319
};
295320

296-
let aux_active = self.audio_thread_state.aux_active.load(Ordering::Acquire);
321+
// Real-time safety: parking_lot Mutex is guaranteed to not do syscalls when uncontested
322+
// Contestion can only occur if we're setting up or tearing down the processor while process is called
323+
// In that case, we will simply output silence
324+
let Some(mut processor) = self.audio_thread_state.processor.try_lock() else {
325+
if let Some(mut main_output) = main_output {
326+
main_output.fill(0.0);
327+
}
297328

298-
// Empty input: this is a parameter dump
299-
if data.inputs.is_null() || data.outputs.is_null() || data.numInputs == 0 || data.numSamples == 0 {
300-
processor.process_events(all_events);
301329
return kResultOk;
302-
}
330+
};
303331

304-
// On some platforms, this cast is needed
305-
#[allow(clippy::unnecessary_cast)]
306-
if data.symbolicSampleSize != SymbolicSampleSizes_::kSample32 as i32 {
332+
let Some(processor) = processor.as_mut() else {
307333
return kResultFalse;
308-
}
309-
310-
let inputs = unsafe { std::slice::from_raw_parts(data.inputs, data.numInputs as _) };
311-
let outputs = unsafe { std::slice::from_raw_parts(data.outputs, data.numOutputs as _) };
312-
let main_input = inputs[0];
313-
let main_output = outputs[0];
314-
assert_eq!(main_input.numChannels, main_output.numChannels);
315-
316-
let aux_input = if P::HAS_AUX_INPUT && aux_active {
317-
assert_eq!(data.numInputs, 2);
318-
let aux_input = inputs[1];
319-
Some(unsafe { PtrSignal::from_pointers(aux_input.numChannels as usize, data.numSamples as usize, aux_input.__field0.channelBuffers32 as _) })
320-
} else {
321-
None
322334
};
323335

324-
let main_input = unsafe { PtrSignal::from_pointers(main_input.numChannels as usize, data.numSamples as usize, main_input.__field0.channelBuffers32 as _) };
325-
let mut main_output = unsafe { PtrSignalMut::from_pointers(main_output.numChannels as usize, data.numSamples as usize, main_output.__field0.channelBuffers32) };
336+
if is_data_dump {
337+
processor.process_events(all_events);
338+
return kResultOk;
339+
}
340+
341+
let main_input = main_input.unwrap();
342+
let mut main_output = main_output.unwrap();
326343

327344
// If processing out-of-place, copy input to output
328345
if zip(main_input.pointers().iter(), main_output.pointers().iter())

0 commit comments

Comments
 (0)