1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use std::{
  any::TypeId,
  ops::{Deref, DerefMut},
};

use crate::{check_status, Error, Status, TaggedObject};

use super::{FromNapiValue, ToNapiValue};

pub struct External<T: 'static> {
  obj: *mut TaggedObject<T>,
  size_hint: usize,
  pub adjusted_size: i64,
}

impl<T: 'static> External<T> {
  pub fn new(value: T) -> Self {
    Self {
      obj: Box::into_raw(Box::new(TaggedObject::new(value))),
      size_hint: 0,
      adjusted_size: 0,
    }
  }

  /// `size_hint` is a value to tell Node.js GC how much memory is used by this `External` object.
  ///
  /// If getting the exact `size_hint` is difficult, you can provide an approximate value, it's only effect to the GC.
  ///
  /// If your `External` object is not effect to GC, you can use `External::new` instead.
  pub fn new_with_size_hint(value: T, size_hint: usize) -> Self {
    Self {
      obj: Box::into_raw(Box::new(TaggedObject::new(value))),
      size_hint,
      adjusted_size: 0,
    }
  }
}

impl<T: 'static> FromNapiValue for External<T> {
  unsafe fn from_napi_value(
    env: napi_sys::napi_env,
    napi_val: napi_sys::napi_value,
  ) -> crate::Result<Self> {
    let mut unknown_tagged_object = std::ptr::null_mut();
    check_status!(
      napi_sys::napi_get_value_external(env, napi_val, &mut unknown_tagged_object),
      "Failed to get external value"
    )?;

    let type_id = unknown_tagged_object as *const TypeId;
    if *type_id == TypeId::of::<T>() {
      let tagged_object = unknown_tagged_object as *mut TaggedObject<T>;
      Ok(Self {
        obj: tagged_object,
        size_hint: 0,
        adjusted_size: 0,
      })
    } else {
      Err(Error::new(
        Status::InvalidArg,
        "T on `get_value_external` is not the type of wrapped object".to_owned(),
      ))
    }
  }
}

impl<T: 'static> AsRef<T> for External<T> {
  fn as_ref(&self) -> &T {
    unsafe { Box::leak(Box::from_raw(self.obj)).object.as_ref().unwrap() }
  }
}

impl<T: 'static> AsMut<T> for External<T> {
  fn as_mut(&mut self) -> &mut T {
    unsafe { Box::leak(Box::from_raw(self.obj)).object.as_mut().unwrap() }
  }
}

impl<T: 'static> Deref for External<T> {
  type Target = T;

  fn deref(&self) -> &Self::Target {
    self.as_ref()
  }
}

impl<T: 'static> DerefMut for External<T> {
  fn deref_mut(&mut self) -> &mut Self::Target {
    self.as_mut()
  }
}

impl<T: 'static> ToNapiValue for External<T> {
  unsafe fn to_napi_value(
    env: napi_sys::napi_env,
    mut val: Self,
  ) -> crate::Result<napi_sys::napi_value> {
    let mut napi_value = std::ptr::null_mut();
    check_status!(
      napi_sys::napi_create_external(
        env,
        val.obj as *mut _,
        Some(crate::raw_finalize::<T>),
        Box::into_raw(Box::new(Some(val.size_hint as i64))) as *mut _,
        &mut napi_value
      ),
      "Create external value failed"
    )?;

    let mut adjusted_external_memory_size = std::mem::MaybeUninit::new(0);

    if val.size_hint != 0 {
      check_status!(
        napi_sys::napi_adjust_external_memory(
          env,
          val.size_hint as i64,
          adjusted_external_memory_size.as_mut_ptr()
        ),
        "Adjust external memory failed"
      )?;
    };

    val.adjusted_size = adjusted_external_memory_size.assume_init();

    Ok(napi_value)
  }
}