koharu-diffusion 0.62.0-alpha.1

Safe Rust bindings for stable-diffusion.cpp with runtime dynamic loading.
use std::{
    cell::Cell,
    ffi::CString,
    path::Path,
    sync::{Condvar, Mutex, OnceLock},
};

use crate::{Error, Result};

#[derive(Default)]
struct NativeState {
    active_calls: usize,
    configuring: bool,
}

fn native_state() -> &'static (Mutex<NativeState>, Condvar) {
    static STATE: OnceLock<(Mutex<NativeState>, Condvar)> = OnceLock::new();
    STATE.get_or_init(|| (Mutex::new(NativeState::default()), Condvar::new()))
}

thread_local! {
    static NATIVE_DEPTH: Cell<usize> = const { Cell::new(0) };
    static CONFIGURATION_DEPTH: Cell<usize> = const { Cell::new(0) };
}

pub(crate) struct NativeCall;

impl NativeCall {
    pub(crate) fn enter() -> Self {
        let (mutex, condvar) = native_state();
        let mut state = mutex.lock().unwrap_or_else(|poison| poison.into_inner());
        while state.configuring {
            state = condvar
                .wait(state)
                .unwrap_or_else(|poison| poison.into_inner());
        }
        state.active_calls += 1;
        drop(state);
        NATIVE_DEPTH.with(|depth| depth.set(depth.get() + 1));
        Self
    }
}

impl Drop for NativeCall {
    fn drop(&mut self) {
        NATIVE_DEPTH.with(|depth| depth.set(depth.get() - 1));
        let (mutex, condvar) = native_state();
        let mut state = mutex.lock().unwrap_or_else(|poison| poison.into_inner());
        state.active_calls -= 1;
        if state.active_calls == 0 {
            condvar.notify_all();
        }
    }
}

struct NativeConfiguration;

impl Drop for NativeConfiguration {
    fn drop(&mut self) {
        CONFIGURATION_DEPTH.with(|depth| depth.set(depth.get() - 1));
        let (mutex, condvar) = native_state();
        let mut state = mutex.lock().unwrap_or_else(|poison| poison.into_inner());
        state.configuring = false;
        condvar.notify_all();
    }
}

pub(crate) fn configure_native<T>(configure: impl FnOnce() -> T) -> Result<T> {
    if NATIVE_DEPTH.with(|depth| depth.get() != 0)
        || CONFIGURATION_DEPTH.with(|depth| depth.get() != 0)
    {
        return Err(Error::ReentrantCallbackConfiguration);
    }

    let (mutex, condvar) = native_state();
    let mut state = mutex.lock().unwrap_or_else(|poison| poison.into_inner());
    while state.configuring || state.active_calls != 0 {
        state = condvar
            .wait(state)
            .unwrap_or_else(|poison| poison.into_inner());
    }
    state.configuring = true;
    drop(state);

    CONFIGURATION_DEPTH.with(|depth| depth.set(depth.get() + 1));
    let _configuration = NativeConfiguration;
    Ok(configure())
}

pub(crate) fn cstring(value: &str, field: &'static str) -> Result<CString> {
    CString::new(value).map_err(|source| Error::InteriorNul { field, source })
}

pub(crate) fn optional_cstring(
    value: Option<&str>,
    field: &'static str,
) -> Result<Option<CString>> {
    value.map(|value| cstring(value, field)).transpose()
}

pub(crate) fn path_cstring(path: &Path, field: &'static str) -> Result<CString> {
    let value = path.to_str().ok_or_else(|| Error::NonUnicodePath {
        field,
        path: path.to_owned(),
    })?;
    cstring(value, field)
}

pub(crate) fn optional_path_cstring(
    path: Option<&Path>,
    field: &'static str,
) -> Result<Option<CString>> {
    path.map(|path| path_cstring(path, field)).transpose()
}

pub(crate) fn c_int_len(len: usize, field: &'static str) -> Result<i32> {
    i32::try_from(len).map_err(|_| Error::CountOverflow { field, len })
}

pub(crate) fn u32_len(len: usize, field: &'static str) -> Result<u32> {
    u32::try_from(len).map_err(|_| Error::CountOverflow { field, len })
}