Skip to main content

whiteout/
support.rs

1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (c) 2026 Fernando Sahmkow
3//
4// Hand-written runtime support shared by every generated module. The
5// analogue of `bindings/csharp/Whiteout/Common/`.
6
7use core::ffi::{c_char, c_void};
8use core::marker::PhantomData;
9use core::ops::Deref;
10
11/// Mirror of the C `whiteout_Bytes`.
12///
13/// `_owner` is the ownership discriminator: non-null means the C++ side
14/// heap-allocated a buffer for us and we must free it; null means either a
15/// borrowed view into memory somebody else owns, or "absent".
16#[repr(C)]
17#[derive(Clone, Copy, Debug)]
18pub struct RawBytes {
19    pub data: *const u8,
20    pub size: usize,
21    pub owner: *mut c_void,
22}
23
24/// Mirror of the C `whiteout_CString`.
25#[repr(C)]
26#[derive(Clone, Copy, Debug)]
27pub struct RawCString {
28    pub chars: *const c_char,
29    pub length: usize,
30    pub owner: *mut c_void,
31}
32
33extern "C" {
34    fn whiteout_Bytes_free(buf: RawBytes);
35    fn whiteout_CString_free(s: RawCString);
36}
37
38/// An owned byte buffer produced by the native library.
39///
40/// Derefs to `[u8]`, so it behaves like a slice; the backing memory stays
41/// in C++ and is released on drop. Nothing is copied unless you ask for it
42/// with [`Bytes::to_vec`].
43pub struct Bytes {
44    raw: RawBytes,
45}
46
47impl Bytes {
48    /// # Safety
49    /// `raw` must have come from a native call that transfers ownership
50    /// (i.e. `raw.owner` is non-null), and must not be freed elsewhere.
51    pub(crate) unsafe fn from_raw(raw: RawBytes) -> Option<Self> {
52        // `owner == null` is the library's "no value" signal. It is *not*
53        // the same as an empty buffer: a present-but-empty vector still
54        // carries a non-null owner. Keying on `data` instead — as the C#
55        // binding does — reports an empty file as missing.
56        if raw.owner.is_null() {
57            None
58        } else {
59            Some(Bytes { raw })
60        }
61    }
62
63    /// An empty buffer that owns nothing.
64    ///
65    /// Used where the native call reports failure by handing back a buffer
66    /// with no owner — the caller still gets a valid, empty slice.
67    pub(crate) fn empty() -> Self {
68        Bytes {
69            raw: RawBytes {
70                data: core::ptr::null(),
71                size: 0,
72                owner: core::ptr::null_mut(),
73            },
74        }
75    }
76
77    pub fn to_vec(&self) -> Vec<u8> {
78        self.as_ref().to_vec()
79    }
80
81    pub fn is_empty(&self) -> bool {
82        self.raw.size == 0
83    }
84
85    pub fn len(&self) -> usize {
86        self.raw.size
87    }
88}
89
90impl Deref for Bytes {
91    type Target = [u8];
92    fn deref(&self) -> &[u8] {
93        if self.raw.data.is_null() || self.raw.size == 0 {
94            return &[];
95        }
96        // SAFETY: the native side guarantees `data`/`size` describe one
97        // allocation, kept alive by `owner` until we free it in `drop`.
98        unsafe { core::slice::from_raw_parts(self.raw.data, self.raw.size) }
99    }
100}
101
102impl AsRef<[u8]> for Bytes {
103    fn as_ref(&self) -> &[u8] {
104        self
105    }
106}
107
108impl Drop for Bytes {
109    fn drop(&mut self) {
110        if self.raw.owner.is_null() {
111            return; // `Bytes::empty` — nothing was ever allocated.
112        }
113        // SAFETY: `from_raw` only constructs a Bytes for an owning buffer,
114        // and Drop runs exactly once.
115        unsafe { whiteout_Bytes_free(self.raw) }
116    }
117}
118
119impl core::fmt::Debug for Bytes {
120    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
121        f.debug_struct("Bytes").field("len", &self.len()).finish()
122    }
123}
124
125// SAFETY: the buffer is a plain heap allocation with no thread affinity;
126// `Bytes` owns it exclusively.
127unsafe impl Send for Bytes {}
128unsafe impl Sync for Bytes {}
129
130/// Consume a native `whiteout_CString` into an owned `String`.
131///
132/// # Safety
133/// `raw` must come from a native call that transfers ownership.
134pub(crate) unsafe fn take_string(raw: RawCString) -> String {
135    if raw.chars.is_null() {
136        // Still hand it back: freeing a null-owner CString is a no-op, and
137        // this keeps the caller from having to special-case the empty case.
138        unsafe { whiteout_CString_free(raw) };
139        return String::new();
140    }
141    // SAFETY: `chars`/`length` describe a valid UTF-8 run owned by the
142    // native side until we free it below.
143    let bytes = unsafe { core::slice::from_raw_parts(raw.chars as *const u8, raw.length) };
144    let out = String::from_utf8_lossy(bytes).into_owned();
145    unsafe { whiteout_CString_free(raw) };
146    out
147}
148
149/// Same, but distinguishes "absent" from "present and empty".
150///
151/// # Safety
152/// As [`take_string`].
153pub(crate) unsafe fn take_string_opt(raw: RawCString) -> Option<String> {
154    if raw.owner.is_null() {
155        return None;
156    }
157    Some(unsafe { take_string(raw) })
158}
159
160/// A borrowed view into a buffer owned by a native object.
161///
162/// This is what makes zero-copy pixel access safe: the lifetime is tied to
163/// a borrow of the owning handle, so the compiler rejects any use after the
164/// owner is dropped, resized, or mutably re-borrowed.
165pub struct BorrowedSlice<'a> {
166    ptr: *const u8,
167    len: usize,
168    _owner: PhantomData<&'a ()>,
169}
170
171impl<'a> BorrowedSlice<'a> {
172    /// # Safety
173    /// `ptr`/`len` must describe a buffer that stays valid and immutable
174    /// for `'a`.
175    pub(crate) unsafe fn new(ptr: *const u8, len: usize) -> Self {
176        BorrowedSlice {
177            ptr,
178            len,
179            _owner: PhantomData,
180        }
181    }
182}
183
184impl Deref for BorrowedSlice<'_> {
185    type Target = [u8];
186    fn deref(&self) -> &[u8] {
187        if self.ptr.is_null() || self.len == 0 {
188            return &[];
189        }
190        // SAFETY: guaranteed by the contract on `new`.
191        unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
192    }
193}
194
195impl core::fmt::Debug for BorrowedSlice<'_> {
196    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
197        f.debug_struct("BorrowedSlice")
198            .field("len", &self.len)
199            .finish()
200    }
201}
202
203/// A borrowed handle: a view of an object owned by something else.
204///
205/// The C ABI hands out interior pointers for struct fields and vector
206/// elements (`&self->field`, `&self->vec[i]`). Those must never be freed,
207/// so they cannot be represented by the owning handle types, which all
208/// implement `Drop`. `Ref` wraps one in `ManuallyDrop` and ties it to the
209/// parent's lifetime, so it neither frees nor outlives its owner.
210///
211/// Derefs to the underlying type, so it is used exactly like `&T`.
212pub struct Ref<'a, T> {
213    inner: core::mem::ManuallyDrop<T>,
214    _owner: PhantomData<&'a T>,
215}
216
217impl<'a, T> Ref<'a, T> {
218    /// # Safety
219    /// `value` must wrap a pointer that stays valid for `'a` and is owned
220    /// by something other than the returned `Ref`.
221    pub(crate) unsafe fn new(value: T) -> Self {
222        Ref {
223            inner: core::mem::ManuallyDrop::new(value),
224            _owner: PhantomData,
225        }
226    }
227}
228
229impl<T> Deref for Ref<'_, T> {
230    type Target = T;
231    fn deref(&self) -> &T {
232        &self.inner
233    }
234}
235
236impl<T: core::fmt::Debug> core::fmt::Debug for Ref<'_, T> {
237    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
238        (**self).fmt(f)
239    }
240}
241
242/// The mutable counterpart of [`Ref`].
243///
244/// Borrowing the parent mutably is what makes in-place edits safe: the
245/// compiler rejects a resize or a second view while this one is alive.
246pub struct RefMut<'a, T> {
247    inner: core::mem::ManuallyDrop<T>,
248    _owner: PhantomData<&'a mut T>,
249}
250
251impl<'a, T> RefMut<'a, T> {
252    /// # Safety
253    /// As [`Ref::new`], plus: no other view of the same object may exist.
254    pub(crate) unsafe fn new(value: T) -> Self {
255        RefMut {
256            inner: core::mem::ManuallyDrop::new(value),
257            _owner: PhantomData,
258        }
259    }
260}
261
262impl<T> Deref for RefMut<'_, T> {
263    type Target = T;
264    fn deref(&self) -> &T {
265        &self.inner
266    }
267}
268
269impl<T> core::ops::DerefMut for RefMut<'_, T> {
270    fn deref_mut(&mut self) -> &mut T {
271        &mut self.inner
272    }
273}
274
275impl<T: core::fmt::Debug> core::fmt::Debug for RefMut<'_, T> {
276    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
277        (**self).fmt(f)
278    }
279}