raylib 6.0.0-rc.2

Safe Rust bindings for Raylib.
use crate::audio::{AudioSample, AudioStream};
use crate::error::UpdateAudioStreamError;
use crate::ffi;
use std::os::raw::c_void;
use std::ptr::null_mut;
use std::slice::from_raw_parts_mut;
use std::sync::atomic::{AtomicPtr, Ordering};

type AudioCallbackFn = Box<dyn FnMut(&mut [u8]) + Send>;

struct AudioCallbackWrapper {
    channels: u16,
    bytes_per_sample: usize,
    callback_fn: AudioCallbackFn,
}

static AUDIO_STREAM_CALLBACK_SLOT: AtomicPtr<AudioCallbackWrapper> = AtomicPtr::new(null_mut());

/// Audio thread callback to request new data
pub fn set_audio_stream_callback<T, F>(
    stream: &AudioStream,
    cb: F,
) -> Result<(), UpdateAudioStreamError>
where
    T: AudioSample,
    F: FnMut(&mut [T]) + Send + 'static,
{
    if !AUDIO_STREAM_CALLBACK_SLOT.load(Ordering::Acquire).is_null() {
        return Err(UpdateAudioStreamError::CallbackSlotBusy);
    }
    let expected_sample_size =
        usize::try_from(stream.sample_size()).expect("sampleSize should be 8, 16, or 32");
    let provided_sample_size_bits = size_of::<T>() * u8::BITS as usize;
    if provided_sample_size_bits != expected_sample_size {
        return Err(UpdateAudioStreamError::SampleSizeMismatch {
            expected: expected_sample_size,
            provided: provided_sample_size_bits,
        });
    }
    let channels = u16::try_from(stream.channels()).expect("channels should fit in u16");
    let bytes_per_sample = size_of::<T>();
    let callback_fn: AudioCallbackFn = Box::new({
        let mut closure_captured = cb;
        move |bytes: &mut [u8]| {
            let byte_len = bytes.len() / bytes_per_sample;
            let buffer_ptr = bytes.as_mut_ptr() as *mut T;
            let buffer_slice = unsafe { from_raw_parts_mut(buffer_ptr, byte_len) };
            closure_captured(buffer_slice);
        }
    });
    let callback_wrapper = Box::new(AudioCallbackWrapper {
        channels,
        bytes_per_sample,
        callback_fn,
    });

    let raw_ptr_for_callback = Box::into_raw(callback_wrapper);
    AUDIO_STREAM_CALLBACK_SLOT.store(raw_ptr_for_callback, Ordering::Release);
    unsafe { ffi::SetAudioStreamCallback(stream.0, Some(custom_audio_stream_callback)) }
    Ok(())
}

/// Remove any previously registered audio stream callback from the given [`AudioStream`] and free its state.
///
/// Calls raylib's `SetAudioStreamCallback(..., NULL)` to unbind the C-side callback, then
/// reclaims the boxed Rust trampoline that [`set_audio_stream_callback`] heap-allocated.
/// Safe to call when no callback is registered (no-op). After this call the single global
/// callback slot is free and a fresh [`set_audio_stream_callback`] succeeds.
///
/// # Examples
///
/// ```no_run
/// use raylib::prelude::*;
/// use raylib::core::callbacks::audio_stream_callback::{
///     set_audio_stream_callback, unset_audio_stream_callback,
/// };
///
/// let audio = RaylibAudio::init_audio_device().expect("audio init");
/// let stream = audio.new_audio_stream(44100, 32, 2);
/// let cb = |_buf: &mut [f32]| { /* fill samples */ };
/// set_audio_stream_callback::<f32, _>(&stream, cb).expect("install");
/// // ... play ...
/// unset_audio_stream_callback(&stream);
/// ```
///
/// # See also
///
/// - [`set_audio_stream_callback`] — the matching installer.
pub fn unset_audio_stream_callback(stream: &AudioStream) {
    unsafe { ffi::SetAudioStreamCallback(stream.0, None) }
    let raw_ptr_for_callback = AUDIO_STREAM_CALLBACK_SLOT.swap(null_mut(), Ordering::AcqRel);
    if !raw_ptr_for_callback.is_null() {
        unsafe {
            drop(Box::from_raw(raw_ptr_for_callback));
        }
    }
}

extern "C" fn custom_audio_stream_callback(data: *mut c_void, frame_count: u32) {
    let raw_ptr_to_callback = AUDIO_STREAM_CALLBACK_SLOT.load(Ordering::Acquire);
    if raw_ptr_to_callback.is_null() {
        // this should never happen, i imagine there is a more elegant way to make sure...
        return;
    }
    let callback_wrapper = unsafe { &mut *raw_ptr_to_callback };
    let channels = callback_wrapper.channels as usize;
    let byte_len = frame_count as usize * channels * callback_wrapper.bytes_per_sample;
    let buffer_slice = unsafe { from_raw_parts_mut(data as *mut u8, byte_len) };
    (callback_wrapper.callback_fn)(buffer_slice);
}