use std::{
ops::Deref,
sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard},
};
pub mod bindings;
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
pub const COOLPROP_PATH: &str = coolprop_sys_linux_aarch64::COOLPROP_PATH;
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
pub const COOLPROP_PATH: &str = coolprop_sys_linux_x86_64::COOLPROP_PATH;
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
pub const COOLPROP_PATH: &str = coolprop_sys_macos_aarch64::COOLPROP_PATH;
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
pub const COOLPROP_PATH: &str = coolprop_sys_macos_x86_64::COOLPROP_PATH;
#[cfg(all(target_os = "windows", target_arch = "aarch64"))]
pub const COOLPROP_PATH: &str = coolprop_sys_windows_aarch64::COOLPROP_PATH;
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
pub const COOLPROP_PATH: &str = coolprop_sys_windows_x86_64::COOLPROP_PATH;
pub struct CoolPropLib(RwLock<bindings::CoolProp>);
impl CoolPropLib {
pub fn shared_access(&self) -> SharedAccess<'_> {
SharedAccess(self.0.read().unwrap_or_else(|err| err.into_inner()))
}
pub fn exclusive_access(&self) -> ExclusiveAccess<'_> {
ExclusiveAccess(self.0.write().unwrap_or_else(|err| err.into_inner()))
}
}
#[must_use]
pub struct SharedAccess<'a>(RwLockReadGuard<'a, bindings::CoolProp>);
impl Deref for SharedAccess<'_> {
type Target = bindings::CoolProp;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[must_use]
pub struct ExclusiveAccess<'a>(RwLockWriteGuard<'a, bindings::CoolProp>);
impl Deref for ExclusiveAccess<'_> {
type Target = bindings::CoolProp;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub static COOLPROP: LazyLock<CoolPropLib> = LazyLock::new(load_coolprop);
fn load_coolprop() -> CoolPropLib {
let coolprop = unsafe { bindings::CoolProp::new(COOLPROP_PATH) }
.expect("CoolProp dynamic library should load from `COOLPROP_PATH`");
let probe = unsafe { coolprop.Props1SI(c"Water".as_ptr(), c"Tcrit".as_ptr()) };
assert!(
probe.is_finite(),
"CoolProp initialization probe `Props1SI(\"Water\", \"Tcrit\")` should return a finite value"
);
CoolPropLib(RwLock::new(coolprop))
}
#[cfg(test)]
mod tests {
use std::{sync::TryLockError, thread};
use static_assertions::assert_not_impl_any;
use super::*;
assert_not_impl_any!(ExclusiveAccess<'static>: std::ops::DerefMut);
fn test_lib() -> CoolPropLib {
LazyLock::force(&COOLPROP);
let coolprop = unsafe { bindings::CoolProp::new(COOLPROP_PATH) }
.expect("CoolProp dynamic library should load from `COOLPROP_PATH`");
CoolPropLib(RwLock::new(coolprop))
}
fn shared_access_is_available(lib: &CoolPropLib) -> bool {
match lib.0.try_read() {
Ok(_) | Err(TryLockError::Poisoned(_)) => true,
Err(TryLockError::WouldBlock) => false,
}
}
fn exclusive_access_is_available(lib: &CoolPropLib) -> bool {
match lib.0.try_write() {
Ok(_) | Err(TryLockError::Poisoned(_)) => true,
Err(TryLockError::WouldBlock) => false,
}
}
#[test]
fn access_types_deref_to_coolprop() {
let lib = test_lib();
let shared = lib.shared_access();
let shared_target = std::ptr::from_ref::<bindings::CoolProp>(&shared);
drop(shared);
let exclusive = lib.exclusive_access();
let exclusive_target = std::ptr::from_ref::<bindings::CoolProp>(&exclusive);
assert_eq!(shared_target, exclusive_target);
}
#[test]
fn poisoned_lock_is_recovered() {
let lib = test_lib();
let panic_result = thread::scope(|scope| {
scope
.spawn(|| {
let _access = lib.exclusive_access();
panic!("poison the test lock");
})
.join()
});
let shared = lib.shared_access();
let shared_level = unsafe { shared.get_debug_level() };
drop(shared);
let exclusive = lib.exclusive_access();
let exclusive_level = unsafe { exclusive.get_debug_level() };
assert!(panic_result.is_err());
assert!((0..=10).contains(&shared_level));
assert!((0..=10).contains(&exclusive_level));
}
#[test]
fn shared_access_allows_another_reader_and_blocks_a_writer() {
let lib = test_lib();
let _shared = lib.shared_access();
let another_reader_is_available = shared_access_is_available(&lib);
let writer_is_available = exclusive_access_is_available(&lib);
assert!(another_reader_is_available);
assert!(!writer_is_available);
}
#[test]
fn exclusive_access_blocks_other_access() {
let lib = test_lib();
let _exclusive = lib.exclusive_access();
let reader_is_available = shared_access_is_available(&lib);
let writer_is_available = exclusive_access_is_available(&lib);
assert!(!reader_is_available);
assert!(!writer_is_available);
}
#[test]
fn unlocked_lib_allows_exclusive_access() {
let lib = test_lib();
let writer_is_available = exclusive_access_is_available(&lib);
assert!(writer_is_available);
}
}