diff --git a/rjkiwi/.gitignore b/rjkiwi/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/rjkiwi/.gitignore @@ -0,0 +1 @@ +/target diff --git a/rjkiwi/Cargo.lock b/rjkiwi/Cargo.lock new file mode 100644 index 0000000..d3a65eb --- /dev/null +++ b/rjkiwi/Cargo.lock @@ -0,0 +1,82 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "casuarius" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df736fe6aeccf2e2b2133091a69bccc8914a22d91230f70b1dabe06e828d0d9" +dependencies = [ + "ordered-float", + "rustc-hash", +] + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libmimalloc-sys" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3979b5c37ece694f1f5e51e7ecc871fdb0f517ed04ee45f88d15d6d553cb9664" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "mimalloc" +version = "0.1.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa01922b5ea280a911e323e4d2fd24b7fe5cc4042e0d2cda3c40775cdc4bdc9c" +dependencies = [ + "libmimalloc-sys", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "ordered-float" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3305af35278dd29f46fcdd139e0b1fbfae2153f0e5928b39b035542dd31e37b7" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rjkiwi" +version = "0.1.1" +dependencies = [ + "casuarius", + "mimalloc", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" diff --git a/rjkiwi/Cargo.toml b/rjkiwi/Cargo.toml new file mode 100644 index 0000000..63199d8 --- /dev/null +++ b/rjkiwi/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rjkiwi" +version = "0.1.1" +edition = "2021" + +[lib] +crate-type = ["cdylib"] +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +casuarius = "0.1.1" + +[target.'cfg(not(target_os = "linux"))'.dependencies] +mimalloc = "0.1" + +[target.'cfg(target_os = "linux")'.dependencies] +mimalloc = { version = "0.1", features = ["local_dynamic_tls"] } diff --git a/rjkiwi/src/constraint.rs b/rjkiwi/src/constraint.rs new file mode 100644 index 0000000..3aee1df --- /dev/null +++ b/rjkiwi/src/constraint.rs @@ -0,0 +1,162 @@ +use std::{ + f64::NAN, + ffi::{c_double, c_int, c_uint, c_void}, + ptr::{self, drop_in_place}, +}; + +use casuarius::{Expression, RelationalOperator, Term}; + +use crate::expr::{KiwiExpression, KiwiExpressionPtr}; +use crate::var::SolverVariable; +use crate::*; + +#[repr(C)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] +pub enum KiwiRelOp { + LE, + GE, + EQ, +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_constraint_type_info(sz_align: *mut c_uint) { + *sz_align.offset(0) = std::mem::size_of::() as c_uint; + *sz_align.offset(1) = std::mem::align_of::() as c_uint; +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_constraint_init( + c: *mut KiwiConstraint, + lhs: *const KiwiExpressionPtr, + rhs: *const KiwiExpressionPtr, + op: KiwiRelOp, + strength: f64, +) { + let term_count = (if !lhs.is_null() { + (*lhs).term_count.max(0) + } else { + 0 + } + if !rhs.is_null() { + (*rhs).term_count.max(0) + } else { + 0 + }) as usize; + + let mut terms = Vec::>::new(); + terms.reserve(term_count); + + let mut constant = 0.0; + + if !lhs.is_null() { + let lhs = + &*(core::slice::from_raw_parts(lhs as *const (), (*lhs).term_count.max(0) as usize) + as *const [()] as *const KiwiExpression); + + terms.extend(lhs.terms.iter().filter_map(|t| match as_solver_var(t.var) { + Some(var) => Some(Term::new(var, t.coefficient)), + None => None, + })); + constant += lhs.constant; + } + if !rhs.is_null() { + let rhs = + &*(core::slice::from_raw_parts(rhs as *const (), (*rhs).term_count.max(0) as usize) + as *const [()] as *const KiwiExpression); + + terms.extend(rhs.terms.iter().filter_map(|t| match as_solver_var(t.var) { + Some(var) => Some(Term::new(var, -t.coefficient)), + None => None, + })); + constant -= rhs.constant; + } + + ptr::write( + c, + Constraint::new( + Expression::new(terms, constant), + match op { + KiwiRelOp::LE => RelationalOperator::LessOrEqual, + KiwiRelOp::GE => RelationalOperator::GreaterOrEqual, + KiwiRelOp::EQ => RelationalOperator::Equal, + }, + strength, + ), + ); +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_constraint_destroy(c: *mut KiwiConstraint) { + if let Some(c) = not_null_mut(c) { + drop_in_place(c); + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_constraint_strength(c: *const KiwiConstraint) -> c_double { + match not_null_ref(c) { + Some(c) => c.strength(), + None => NAN, + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_constraint_op(c: *const KiwiConstraint) -> KiwiRelOp { + match not_null_ref(c) { + Some(c) => match c.op() { + RelationalOperator::LessOrEqual => KiwiRelOp::LE, + RelationalOperator::GreaterOrEqual => KiwiRelOp::GE, + RelationalOperator::Equal => KiwiRelOp::EQ, + }, + None => KiwiRelOp::EQ, + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_constraint_violated(c: *const KiwiConstraint) -> bool { + match not_null_ref(c) { + Some(c) => { + let e = c.expr(); + let value = e + .terms + .iter() + .map(|t| t.variable.value_ * t.coefficient.into_inner()) + .sum::() + + e.constant.into_inner(); + match c.op() { + RelationalOperator::LessOrEqual => value > 0.0, + RelationalOperator::GreaterOrEqual => value < 0.0, + RelationalOperator::Equal => value.abs() > 1.0e-8, + } + } + None => false, + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_constraint_expression( + c: *const KiwiConstraint, + out: *mut KiwiExpressionPtr, + out_size: c_int, +) -> c_int { + match not_null_ref(c) { + Some(c) => { + let expr = c.expr(); + let n_terms = expr.terms.len().min(c_int::MAX as usize) as c_int; + if out.is_null() || out_size < n_terms { + return n_terms; + } + let out = core::slice::from_raw_parts_mut(out as *mut (), n_terms as usize) + as *mut [()] as *mut KiwiExpression; + + (*out).owner = out as *mut c_void; + (*out).term_count = n_terms; + (*out).constant = expr.constant.into(); + for (o, i) in (*out).terms.iter_mut().zip(expr.terms.iter()) { + o.var = i.variable.retain_raw(); + o.coefficient = i.coefficient.into(); + } + n_terms + } + None => 0, + } +} diff --git a/rjkiwi/src/expr.rs b/rjkiwi/src/expr.rs new file mode 100644 index 0000000..d56eba6 --- /dev/null +++ b/rjkiwi/src/expr.rs @@ -0,0 +1,57 @@ +use std::ffi::{c_double, c_int, c_void}; + +use crate::*; + +pub type KiwiExpressionPtr = expr::KiwiExpression<[expr::KiwiTerm; 0]>; + +#[repr(C)] +#[derive(Debug)] +pub struct KiwiTerm { + pub var: *mut KiwiVar, + pub coefficient: c_double, +} + +#[repr(C)] +#[derive(Debug)] +pub struct KiwiExpression { + pub constant: c_double, + pub term_count: c_int, + pub owner: *mut c_void, + pub terms: T, +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_expression_retain(e: *mut KiwiExpressionPtr) { + if let Some(e) = not_null(e) { + let e = core::slice::from_raw_parts_mut(e as *mut (), (*e).term_count.max(0) as usize) + as *mut [()] as *mut KiwiExpression; + + (*e).terms.iter().for_each(|t| { + if let Some(var) = not_null_mut(t.var) { + Rc::increment_strong_count(var); + } + }); + (*e).owner = e as *mut c_void; + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_expression_destroy(e: *mut KiwiExpressionPtr) { + if let Some(e) = not_null_mut(e) { + let e = core::slice::from_raw_parts_mut(e as *mut (), (*e).term_count.max(0) as usize) + as *mut [()] as *mut KiwiExpression; + match (*e).owner { + expr if expr == e as *mut c_void => { + (*e).terms.iter().for_each(|t| { + if let Some(var) = not_null_mut(t.var) { + Rc::decrement_strong_count(var); + } + }); + } + c if !c.is_null() => { + Rc::decrement_strong_count(c as *mut KiwiConstraint); + } + _ => (), + } + } +} diff --git a/rjkiwi/src/lib.rs b/rjkiwi/src/lib.rs new file mode 100644 index 0000000..76a5c80 --- /dev/null +++ b/rjkiwi/src/lib.rs @@ -0,0 +1,71 @@ +use std::{ + ffi::{c_char, CString}, + rc::Rc, +}; + +use casuarius::Constraint; +use mimalloc::MiMalloc; + +#[global_allocator] +static GLOBAL: MiMalloc = MiMalloc; + +mod constraint; +mod expr; +mod solver; +mod var; + +type KiwiVar = var::VarData; +type KiwiConstraint = Constraint; + +unsafe fn as_solver_var(ptr: *const KiwiVar) -> Option { + if ptr.is_null() { + None + } else { + Some(var::SolverVariable::from_raw(ptr)) + } +} + +fn not_null(ptr: *const T) -> Option<*const T> { + if ptr.is_null() { + None + } else { + Some(ptr) + } +} + +fn not_null_mut(ptr: *mut T) -> Option<*mut T> { + if ptr.is_null() { + None + } else { + Some(ptr) + } +} + +unsafe fn not_null_ref(ptr: *const T) -> Option<&'static T> +where + T: ?Sized, +{ + if ptr.is_null() { + None + } else { + Some(&*ptr) + } +} + +unsafe fn not_null_ref_mut(ptr: *mut T) -> Option<&'static mut T> +where + T: ?Sized, +{ + if ptr.is_null() { + None + } else { + Some(&mut *ptr) + } +} + +#[no_mangle] +unsafe extern "C" fn kiwi_str_release(p: *mut c_char) { + if let Some(p) = not_null_mut(p) { + drop(CString::from_raw(p)); + } +} diff --git a/rjkiwi/src/solver.rs b/rjkiwi/src/solver.rs new file mode 100644 index 0000000..eeadee4 --- /dev/null +++ b/rjkiwi/src/solver.rs @@ -0,0 +1,289 @@ +use std::{ + ffi::{c_char, c_double, c_uint}, + ptr::{self, drop_in_place}, +}; + +use casuarius::{ + AddConstraintError, AddEditVariableError, RemoveConstraintError, RemoveEditVariableError, + Solver, SuggestValueError, +}; + +use crate::{var::SolverVariable, *}; + +#[repr(C)] +#[derive(Debug)] +pub enum KiwiErrKind { + KiwiErrUnsatisfiableConstraint = 1, + KiwiErrUnknownConstraint, + KiwiErrDuplicateConstraint, + KiwiErrUnknownEditVar, + KiwiErrDuplicateEditVar, + KiwiErrBadRequiredStrength, + KiwiErrInternalSolverError, + KiwiErrNullObject = 9, +} + +#[repr(C)] +#[derive(Debug)] +pub struct KiwiErr { + kind: KiwiErrKind, + message: *const c_char, + must_release: bool, + message_str: Option, +} + +impl KiwiErr { + const fn new_static(kind: KiwiErrKind) -> Self { + KiwiErr { + kind, + message: core::ptr::null(), + must_release: false, + message_str: None, + } + } + fn new_boxed(kind: KiwiErrKind, message: &str) -> *mut Self { + let message_str = CString::new(message).unwrap(); + Box::into_raw(Box::new(KiwiErr { + kind, + message: message_str.as_ptr(), + must_release: true, + message_str: Some(message_str), + })) + } +} + +// SAFETY: pointer is for C compatibility, it is only ever null statically. +unsafe impl Sync for KiwiErr {} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_err_release(e: *mut KiwiErr) { + if let Some(e) = not_null_mut(e) { + if (*e).must_release { + drop(Box::from_raw(e)); + } + } +} + +#[repr(C)] +#[derive(Debug)] +pub struct KiwiSolver { + error_mask: c_uint, + solver: Solver, +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_solver_type_info(sz_align: *mut c_uint) { + *sz_align.offset(0) = std::mem::size_of::() as c_uint; + *sz_align.offset(1) = std::mem::align_of::() as c_uint; +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_solver_init(s: *mut KiwiSolver, error_mask: c_uint) { + ptr::write( + s, + KiwiSolver { + error_mask, + solver: Solver::default(), + }, + ); +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_solver_destroy(s: *mut KiwiSolver) { + if let Some(s) = not_null_mut(s) { + drop_in_place(s); + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_solver_get_error_mask(s: *const KiwiSolver) -> c_uint { + match not_null_ref(s) { + Some(s) => s.error_mask, + None => 0, + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_solver_set_error_mask(s: *mut KiwiSolver, mask: c_uint) { + if let Some(s) = not_null_ref_mut(s) { + s.error_mask = mask; + } +} + +static NULL_OBJECT_ERR: KiwiErr = KiwiErr::new_static(KiwiErrKind::KiwiErrNullObject); +static UNSATISFIABLE_CONSTRAINT_ERR: KiwiErr = + KiwiErr::new_static(KiwiErrKind::KiwiErrUnsatisfiableConstraint); +static UNKNOWN_CONSTRAINT_ERROR: KiwiErr = + KiwiErr::new_static(KiwiErrKind::KiwiErrUnknownConstraint); +static DUPLICATE_CONSTRAINT_ERROR: KiwiErr = + KiwiErr::new_static(KiwiErrKind::KiwiErrDuplicateConstraint); +static UNKNOWN_EDIT_VAR_ERROR: KiwiErr = KiwiErr::new_static(KiwiErrKind::KiwiErrUnknownEditVar); +static DUPLICATE_EDIT_VAR_ERROR: KiwiErr = + KiwiErr::new_static(KiwiErrKind::KiwiErrDuplicateEditVar); +static BAD_REQUIRED_STRENGTH_ERROR: KiwiErr = + KiwiErr::new_static(KiwiErrKind::KiwiErrBadRequiredStrength); + +#[no_mangle] +pub unsafe extern "C" fn kiwi_solver_add_constraint( + s: *mut KiwiSolver, + constraint: *const KiwiConstraint, +) -> *const KiwiErr { + use AddConstraintError::*; + + let (Some(s), Some(constraint)) = (not_null_ref_mut(s), not_null_ref(constraint)) else { + return &NULL_OBJECT_ERR; + }; + + match s.solver.add_constraint(constraint.clone()) { + Ok(_) => core::ptr::null(), + Err(err) => match err { + DuplicateConstraint => &DUPLICATE_CONSTRAINT_ERROR, + UnsatisfiableConstraint => &UNSATISFIABLE_CONSTRAINT_ERR, + InternalSolverError(m) => { + KiwiErr::new_boxed(KiwiErrKind::KiwiErrInternalSolverError, m) + } + }, + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_solver_remove_constraint( + s: *mut KiwiSolver, + constraint: *const KiwiConstraint, +) -> *const KiwiErr { + use RemoveConstraintError::*; + + let (Some(s), Some(constraint)) = (not_null_ref_mut(s), not_null_ref(constraint)) else { + return &NULL_OBJECT_ERR; + }; + match s.solver.remove_constraint(constraint) { + Ok(_) => core::ptr::null(), + Err(err) => match err { + UnknownConstraint => &UNKNOWN_CONSTRAINT_ERROR, + InternalSolverError(m) => { + KiwiErr::new_boxed(KiwiErrKind::KiwiErrInternalSolverError, m) + } + }, + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_solver_add_edit_var( + s: *mut KiwiSolver, + var: *mut KiwiVar, + strength: c_double, +) -> *const KiwiErr { + use AddEditVariableError::*; + + let (Some(s), Some(var)) = (not_null_ref_mut(s), as_solver_var(var)) else { + return &NULL_OBJECT_ERR; + }; + match s.solver.add_edit_variable(var, strength) { + Ok(_) => core::ptr::null(), + Err(err) => match err { + DuplicateEditVariable => &DUPLICATE_EDIT_VAR_ERROR, + BadRequiredStrength => &BAD_REQUIRED_STRENGTH_ERROR, + }, + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_solver_remove_edit_var( + s: *mut KiwiSolver, + var: *mut KiwiVar, +) -> *const KiwiErr { + use RemoveEditVariableError::*; + + let (Some(s), Some(var)) = (not_null_ref_mut(s), as_solver_var(var)) else { + return &NULL_OBJECT_ERR; + }; + + match s.solver.remove_edit_variable(var) { + Ok(_) => core::ptr::null(), + Err(err) => match err { + UnknownEditVariable => &UNKNOWN_EDIT_VAR_ERROR, + InternalSolverError(m) => { + KiwiErr::new_boxed(KiwiErrKind::KiwiErrInternalSolverError, m) + } + }, + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_solver_suggest_value( + s: *mut KiwiSolver, + var: *const KiwiVar, + value: c_double, +) -> *const KiwiErr { + use SuggestValueError::*; + + let (Some(s), Some(var)) = (not_null_ref_mut(s), as_solver_var(var)) else { + return &NULL_OBJECT_ERR; + }; + + match s.solver.suggest_value(var, value) { + Ok(_) => core::ptr::null(), + Err(err) => match err { + UnknownEditVariable => &UNKNOWN_EDIT_VAR_ERROR, + InternalSolverError(m) => { + KiwiErr::new_boxed(KiwiErrKind::KiwiErrInternalSolverError, m) + } + }, + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_solver_has_edit_var( + s: *const KiwiSolver, + var: *const KiwiVar, +) -> bool { + match (not_null_ref(s), as_solver_var(var)) { + (Some(s), Some(var)) => s.solver.has_edit_variable(&var), + _ => false, + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_solver_has_constraint( + s: *const KiwiSolver, + constraint: *const KiwiConstraint, +) -> bool { + match (not_null_ref(s), not_null_ref(constraint)) { + (Some(s), Some(constraint)) => s.solver.has_constraint(&constraint), + _ => false, + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_solver_update_vars(s: *mut KiwiSolver) { + if let Some(s) = not_null_ref_mut(s) { + s.solver.fetch_changes().iter().for_each(|(v, val)| { + (*(Rc::as_ptr(v) as *mut KiwiVar)).value_ = *val; + }); + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_solver_reset(s: *mut KiwiSolver) { + if let Some(s) = not_null_ref_mut(s) { + s.solver.reset(); + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_solver_dump(s: *const KiwiSolver) { + if let Some(s) = not_null_ref(s) { + println!("{:?}", s.solver); + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_solver_dumps(s: *const KiwiSolver) -> *mut c_char { + match not_null_ref(s) { + Some(s) => match CString::new(format!("{:?}", s.solver)) { + Ok(s) => s.into_raw(), + Err(_) => ptr::null_mut(), + }, + None => ptr::null_mut(), + } +} diff --git a/rjkiwi/src/var.rs b/rjkiwi/src/var.rs new file mode 100644 index 0000000..beff815 --- /dev/null +++ b/rjkiwi/src/var.rs @@ -0,0 +1,133 @@ +use crate::*; + +use core::fmt; + +use std::{ + f64::NAN, + ffi::{c_char, CStr, CString}, + hash::{Hash, Hasher}, + ops::Deref, + ptr::null_mut, + rc::Rc, +}; + +#[repr(C)] +#[derive(Clone, Debug, PartialEq)] +pub struct VarData { + pub value_: f64, + pub name_: *mut c_char, +} + +impl Drop for VarData { + fn drop(&mut self) { + if !self.name_.is_null() { + unsafe { + drop(CString::from_raw(self.name_)); + } + } + } +} + +#[derive(Clone)] +pub(crate) struct SolverVariable(Rc); + +impl SolverVariable { + #[inline] + pub(crate) unsafe fn from_raw(ptr: *const VarData) -> Self { + Rc::increment_strong_count(ptr); + Self(Rc::from_raw(ptr)) + } + + #[inline] + pub(crate) fn retain_raw(&self) -> *mut VarData { + Rc::into_raw(self.0.clone()) as *mut VarData + } +} + +impl Eq for SolverVariable {} + +impl PartialEq for SolverVariable { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.0, &other.0) + } +} + +impl Hash for SolverVariable { + fn hash(&self, state: &mut H) { + Rc::as_ptr(&self.0).hash(state); + } +} + +impl Deref for SolverVariable { + type Target = Rc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl fmt::Debug for SolverVariable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("SolverVariable") + .field(unsafe { &CStr::from_ptr(self.0.name_) }) + .finish() + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_var_new(name: *const c_char) -> *const KiwiVar { + Rc::into_raw(Rc::new(VarData { + value_: 0.0, + name_: match not_null(name) { + None => null_mut(), + Some(name) => CStr::from_ptr(name).to_owned().into_raw(), + }, + })) +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_var_release(var: *const KiwiVar) { + if let Some(var) = not_null(var) { + Rc::decrement_strong_count(var); + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_var_retain(var: *const KiwiVar) { + if let Some(var) = not_null(var) { + Rc::increment_strong_count(var); + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_var_value(var: *const KiwiVar) -> f64 { + match not_null_ref(var) { + Some(var) => var.value_, + None => NAN, + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_var_set_value(var: *mut KiwiVar, value: f64) { + if let Some(var) = not_null_ref_mut(var) { + var.value_ = value; + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_var_name(var: *const KiwiVar) -> *const c_char { + match not_null_ref(var) { + None => CStr::from_bytes_with_nul_unchecked(b"\0").as_ptr(), + Some(var) => var.name_, + } +} + +#[no_mangle] +pub unsafe extern "C" fn kiwi_var_set_name(var: *mut KiwiVar, name: *const c_char) { + if let Some(var) = not_null_ref_mut(var) { + var.name_ = match not_null(name) { + None => null_mut(), + Some(name) => CStr::from_ptr(name).to_owned().into_raw(), + } + } +}