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}