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
use crate::byond_string;
use crate::raw_types;
use crate::DMResult;
use crate::Value;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering::Relaxed;
fn get_next_id() -> f32 {
// This can (should) be only called from the main thread but we need to shut Rust up.
static NEXT_WEAKREF_ID: AtomicU32 = AtomicU32::new(1);
let id = NEXT_WEAKREF_ID.fetch_add(1, Relaxed);
id as f32
}
/// A weak reference to some datum or atom in the game.
///
/// Normal [`Value`]s are not safe to move between threads.
/// Using methods like [`Value::set`] or [`Value::call`] can at best cause undefined behavior,
/// at worst crash the server.
///
/// A way to bypass that limitation is to store a raw value
/// and use [`Value::from_raw`] on the main thread to actually work
/// with it. However, if that [`Value`] is deleted, your stored value
/// will point to another datum or to simply nothing.
///
/// This struct serves to solve the latter problem. You can use
/// [`Value::as_weak`] to create a weak reference to it.
/// The reference can be stored in global structures or passed to
/// other threads. You can then return it to the main thread as needed,
/// and call [`WeakValue::upgrade`] to turn it back into a real [`Value`].
/// If the datum pointed to was deleted in the meantime, `upgrade` will
/// return None, otherwise you get your datum back.
///
/// However, this struct is not entirely thread safe, since you can
/// [`WeakValue::upgrade`] on another thread and invoke undefined behavior with
/// the resulting [`Value`]. So, don't do that.
///
/// Using this struct requires all datums to have a `__auxtools_weakref_id` variable.
///
/// # Example
/// ```ignore
/// let weakref = thing.as_weak()?;
/// callbacks.set(some_id, weakref);
///
/// ... some proc calls later ...
///
/// let weakref = callbacks.get(some_id);
/// if let Some(thing) = weakref.upgrade() {
/// thing.call("callback", &[])?;
/// }
/// ```
#[derive(Copy, Clone)]
pub struct WeakValue {
inner: raw_types::values::Value,
id: f32,
}
impl WeakValue {
/// Creates a weak reference to the given datum.
pub(crate) fn new(val: &Value) -> DMResult<Self> {
if let Ok(id) = val.get_number(byond_string!("__auxtools_weakref_id")) {
return Ok(Self { inner: val.raw, id });
}
let id = get_next_id();
val.set(byond_string!("__auxtools_weakref_id"), Value::from(id))?;
Ok(Self { inner: val.raw, id })
}
/// Converts the stored raw value to a full fledged [`Value`]
/// and checks if it has been deleted in the meantime.
pub fn upgrade(&self) -> Option<Value> {
let real_val = unsafe { Value::from_raw(self.inner) };
let id = real_val
.get_number(byond_string!("__auxtools_weakref_id"))
.ok()?;
if self.id != id {
return None;
}
Some(real_val)
}
/// Same as [`WeakValue::upgrade`] but returns a null if the datum was deleted,
/// so you can pass it straight into DM.
pub fn upgrade_or_null(&self) -> Value {
match self.upgrade() {
Some(v) => v,
None => Value::null(),
}
}
}
impl Value {
/// Creates a [`WeakValue`] referencing this datum.
pub fn as_weak(&self) -> DMResult<WeakValue> {
WeakValue::new(self)
}
}