gdnative_core/export/
type_tag.rs

1use std::any::TypeId;
2use std::mem::{align_of, size_of};
3
4use indexmap::IndexSet;
5
6use crate::export::NativeClass;
7
8#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
9struct Tag {
10    type_id: TypeId,
11}
12
13impl Tag {
14    #[inline]
15    fn of<T>() -> Self
16    where
17        T: NativeClass,
18    {
19        Tag {
20            type_id: TypeId::of::<T>(),
21        }
22    }
23}
24
25/// Whether the type tag can be transmuted to `usize`. `true` if the layouts are compatible
26/// on the platform, and `type-tag-fallback` is not enabled.
27const USE_TRANSMUTE: bool = cfg!(not(feature = "type-tag-fallback"))
28    && size_of::<Tag>() == size_of::<usize>()
29    && align_of::<Tag>() == align_of::<usize>();
30
31/// Keep track of allocated type tags so they can be freed on cleanup. This should only be
32/// accessed from one thread at a time.
33static mut TAGS: Option<IndexSet<Tag, ahash::RandomState>> = None;
34
35/// Top bit of `usize`. Used to prevent producing null type tags which might have special
36/// meaning assigned.
37const MAGIC: usize = 1usize.rotate_right(1);
38/// Rest of `usize`.
39const MAGIC_MASK: usize = !MAGIC;
40
41/// Create a new type tag for type `T`. This should only be called from `InitHandle`.
42#[inline]
43pub(crate) unsafe fn create<T>() -> *const libc::c_void
44where
45    T: NativeClass,
46{
47    let tag = Tag::of::<T>();
48
49    if USE_TRANSMUTE {
50        // Safety: USE_TRANSMUTE is only true if layouts match
51        *(&tag as *const Tag as *const *const libc::c_void)
52    } else {
53        // Safety: InitHandle is not Send or Sync, so this will only be called from one thread
54        let tags = TAGS.get_or_insert_with(IndexSet::default);
55        let (idx, _) = tags.insert_full(tag);
56        // So we don't produce nulls. We're just assuming that 2^31 types will be
57        // enough for everyone here.
58        (idx | MAGIC) as *const libc::c_void
59    }
60}
61
62/// Returns `true` if `tag` corresponds to type `T`. `tag` must be one returned by `create`.
63#[inline]
64pub(crate) unsafe fn check<T>(tag: *const libc::c_void) -> bool
65where
66    T: NativeClass,
67{
68    if USE_TRANSMUTE {
69        // Safety: USE_TRANSMUTE is only true if layouts match
70        Tag::of::<T>() == *(&tag as *const *const libc::c_void as *const Tag)
71    } else {
72        let tags = TAGS.as_ref().expect("tag should be created by `create`");
73        let idx = tag as usize;
74        let tag = tags
75            .get_index(idx & MAGIC_MASK)
76            .expect("tag should be created by `create`");
77        Tag::of::<T>() == *tag
78    }
79}
80
81/// Perform any cleanup actions if required. Should only be called from
82/// `crate::cleanup_internal_state`. `create` and `check` shouldn't be called after this.
83#[inline]
84pub(crate) unsafe fn cleanup() {
85    // Safety: By the time cleanup is called, create shouldn't be called again
86    if let Some(tags) = TAGS.take() {
87        drop(tags);
88    }
89}