use gc_abi::{GCAbiConversion, GCDatapointValue, raw};
use std::fmt::{self, Debug, Formatter};
use std::ops::Deref;
use std::sync::OnceLock;
static RELEASE_DATAPOINT: OnceLock<unsafe extern "C" fn(arg1: raw::GCDatapointValue) -> bool> = OnceLock::new();
pub fn set_datapoint_release_fn(release_fn: Option<unsafe extern "C" fn(arg1: raw::GCDatapointValue) -> bool>) -> bool {
if let Some(release_fn) = release_fn {
RELEASE_DATAPOINT.get_or_init(|| release_fn);
true
} else {
false
}
}
#[repr(transparent)]
pub struct GCBorrowedDatapointValue(gc_abi::raw::GCDatapointValue);
impl GCBorrowedDatapointValue {
#[cfg(feature = "testing")]
pub fn new(dpv: &GCDatapointValue) -> Self {
let dpv = Box::new(dpv.clone());
let dpv_ptr = Box::into_raw(dpv) as raw::GCDatapointValue;
unsafe extern "C" fn dealloc(dp: raw::GCDatapointValue) -> bool {
unsafe {
drop(Box::from_raw(dp));
true
}
}
RELEASE_DATAPOINT.get_or_init(|| dealloc);
GCBorrowedDatapointValue(dpv_ptr)
}
pub fn copy(&self) -> GCDatapointValue {
let copy: &GCDatapointValue = unsafe { GCDatapointValue::from_c_ptr(self.0) };
copy.clone()
}
}
impl Drop for GCBorrowedDatapointValue {
fn drop(&mut self) {
let dealloc = RELEASE_DATAPOINT.get();
if let Some(dealloc) = dealloc {
unsafe {
dealloc(self.0);
}
} else {
panic!("The release function for datapoint values was not set, this should never happen");
}
}
}
impl Deref for GCBorrowedDatapointValue {
type Target = GCDatapointValue;
fn deref(&self) -> &Self::Target {
unsafe { GCDatapointValue::from_c_ptr(self.0) }
}
}
impl From<raw::GCDatapointValue> for GCBorrowedDatapointValue {
fn from(datapoint_value: raw::GCDatapointValue) -> Self {
GCBorrowedDatapointValue(datapoint_value)
}
}
impl Debug for GCBorrowedDatapointValue {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("GCBorrowedDatapointValue")
.field("value", &self.get_value())
.field("timestamp", &self.get_timestamp())
.field("id", &self.get_id())
.field("quality", &self.get_quality())
.finish()
}
}
unsafe impl Send for GCBorrowedDatapointValue {}
unsafe impl Sync for GCBorrowedDatapointValue {}
#[cfg(test)]
mod tests {
use super::*;
use gc_abi::GCDatapointValueType;
use raw::sGCDatapointValue__bindgen_ty_1 as BindagenValue;
unsafe extern "C" fn dealloc(dp: raw::GCDatapointValue) -> bool {
unsafe {
drop(Box::from_raw(dp));
true
}
}
#[test]
fn test_conversion_from_c() {
set_datapoint_release_fn(Some(dealloc));
let raw_dp = Box::new(raw::sGCDatapointValue {
type_: raw::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_INT32,
value: BindagenValue { i32_: 1234 },
timestamp: 10,
datapoint_id: 0,
quality: 0x0,
});
let raw_dp_ptr = Box::into_raw(raw_dp) as raw::GCDatapointValue;
let dp_wrapper: GCBorrowedDatapointValue = raw_dp_ptr.into();
assert_eq!(
dp_wrapper.0, raw_dp_ptr,
"The conversion should not allocate new memory, the address of both structs should be the same"
);
assert_eq!(GCDatapointValueType::Int32(1234), dp_wrapper.get_value().unwrap());
assert_eq!(10, dp_wrapper.get_timestamp());
assert_eq!(0, dp_wrapper.get_id());
}
#[test]
fn test_copy() {
set_datapoint_release_fn(Some(dealloc));
let alloc_info = allocation_counter::measure(|| {
let raw_dp = Box::new(raw::sGCDatapointValue {
type_: raw::eGCDatapointValueType_GCOS_DATAPOINT_TYPE_INT32,
value: BindagenValue { i32_: 1234 },
timestamp: 10,
datapoint_id: 0,
quality: 0x0,
});
let raw_dp_ptr = Box::into_raw(raw_dp) as raw::GCDatapointValue;
let dp_wrapper: GCBorrowedDatapointValue = raw_dp_ptr.into();
let copy = dp_wrapper.copy();
assert_eq!(GCDatapointValueType::Int32(1234), copy.get_value().unwrap());
assert_eq!(10, copy.get_timestamp());
assert_eq!(0, copy.get_id());
});
assert_eq!(alloc_info.bytes_current, 0, "Memory leak detected");
assert_eq!(
alloc_info.count_total, 1,
"Additional memory allocation detected, the conversion shouldn't allocate any memory"
);
}
}