goud_engine/core/error/ffi_bridge.rs
1//! FFI error bridge infrastructure.
2//!
3//! Provides thread-local error storage and the [`GoudFFIResult`] type for passing
4//! errors across the FFI boundary to C#, Python, and other language bindings.
5//!
6//! # Usage Pattern
7//!
8//! 1. Rust function encounters an error
9//! 2. Rust function calls `set_last_error(error)`
10//! 3. Rust function returns error code via `GoudFFIResult`
11//! 4. Language binding checks if `success` is false
12//! 5. Language binding calls `goud_last_error_code()` and `goud_last_error_message()`
13//! 6. Language binding calls `take_last_error()` to clear the error
14//!
15//! # Thread Safety
16//!
17//! Each thread has its own error storage. Errors do not cross thread boundaries.
18//! This matches the behavior of `errno` in C and is safe for multi-threaded use.
19
20use std::cell::RefCell;
21
22use super::codes::{GoudErrorCode, SUCCESS};
23use super::context::GoudErrorContext;
24use super::types::GoudError;
25
26thread_local! {
27 /// Thread-local storage for the last error.
28 ///
29 /// Each thread has its own error storage, ensuring that errors from one
30 /// thread do not affect another. This is critical for thread-safe FFI.
31 static LAST_ERROR: RefCell<Option<GoudError>> = const { RefCell::new(None) };
32
33 /// Thread-local storage for error context (subsystem + operation metadata).
34 ///
35 /// Set alongside LAST_ERROR when `set_last_error_with_context` is used.
36 /// Cleared by `set_last_error`, `clear_last_error`, and `take_last_error`.
37 static LAST_ERROR_CONTEXT: RefCell<Option<GoudErrorContext>> = const { RefCell::new(None) };
38}
39
40/// Sets the last error for the current thread.
41///
42/// This function stores the error in thread-local storage where it can be
43/// retrieved by `last_error_code()` and `last_error_message()`.
44///
45/// # Thread Safety
46///
47/// The error is stored in thread-local storage and will not affect other threads.
48///
49/// # Example
50///
51/// ```
52/// use goud_engine::core::error::{GoudError, set_last_error, last_error_code, ERR_NOT_INITIALIZED};
53///
54/// set_last_error(GoudError::NotInitialized);
55/// assert_eq!(last_error_code(), ERR_NOT_INITIALIZED);
56/// ```
57pub fn set_last_error(error: GoudError) {
58 LAST_ERROR.with(|e| {
59 *e.borrow_mut() = Some(error);
60 });
61 LAST_ERROR_CONTEXT.with(|c| {
62 *c.borrow_mut() = None;
63 });
64}
65
66/// Sets the last error and its context for the current thread.
67///
68/// This function stores both the error and context metadata (subsystem, operation)
69/// in thread-local storage. Use `last_error_subsystem()` and `last_error_operation()`
70/// to retrieve the context.
71///
72/// # Thread Safety
73///
74/// Both values are stored in thread-local storage and will not affect other threads.
75pub fn set_last_error_with_context(error: GoudError, ctx: GoudErrorContext) {
76 LAST_ERROR.with(|e| {
77 *e.borrow_mut() = Some(error);
78 });
79 LAST_ERROR_CONTEXT.with(|c| {
80 *c.borrow_mut() = Some(ctx);
81 });
82}
83
84/// Takes the last error from the current thread, clearing it.
85///
86/// This function removes the error from thread-local storage and returns it.
87/// Subsequent calls will return `None` until a new error is set.
88///
89/// # Thread Safety
90///
91/// Only affects the current thread's error storage.
92///
93/// # Example
94///
95/// ```
96/// use goud_engine::core::error::{GoudError, set_last_error, take_last_error};
97///
98/// set_last_error(GoudError::NotInitialized);
99/// let error = take_last_error();
100/// assert!(error.is_some());
101/// assert!(take_last_error().is_none()); // Cleared after take
102/// ```
103pub fn take_last_error() -> Option<GoudError> {
104 LAST_ERROR_CONTEXT.with(|c| {
105 *c.borrow_mut() = None;
106 });
107 LAST_ERROR.with(|e| e.borrow_mut().take())
108}
109
110/// Gets the last error from the current thread without clearing it.
111///
112/// This function clones the error from thread-local storage. Use `take_last_error()`
113/// if you want to clear the error after retrieval.
114///
115/// # Thread Safety
116///
117/// Only accesses the current thread's error storage.
118///
119/// # Example
120///
121/// ```
122/// use goud_engine::core::error::{GoudError, set_last_error, get_last_error};
123///
124/// set_last_error(GoudError::NotInitialized);
125/// let error1 = get_last_error();
126/// let error2 = get_last_error();
127/// assert_eq!(error1, error2); // Error not cleared
128/// ```
129pub fn get_last_error() -> Option<GoudError> {
130 LAST_ERROR.with(|e| e.borrow().clone())
131}
132
133/// Returns the error code of the last error for the current thread.
134///
135/// Returns `SUCCESS` (0) if no error is set. This does not clear the error.
136///
137/// # Thread Safety
138///
139/// Only accesses the current thread's error storage.
140///
141/// # Example
142///
143/// ```
144/// use goud_engine::core::error::{set_last_error, last_error_code, clear_last_error, GoudError, SUCCESS, ERR_NOT_INITIALIZED};
145///
146/// clear_last_error();
147/// assert_eq!(last_error_code(), SUCCESS);
148///
149/// set_last_error(GoudError::NotInitialized);
150/// assert_eq!(last_error_code(), ERR_NOT_INITIALIZED);
151/// ```
152pub fn last_error_code() -> GoudErrorCode {
153 LAST_ERROR.with(|e| {
154 e.borrow()
155 .as_ref()
156 .map(|err| err.error_code())
157 .unwrap_or(SUCCESS)
158 })
159}
160
161/// Returns the error message of the last error for the current thread.
162///
163/// Returns `None` if no error is set. This does not clear the error.
164/// The returned string is a copy, safe to use across FFI.
165///
166/// # Thread Safety
167///
168/// Only accesses the current thread's error storage.
169///
170/// # Example
171///
172/// ```
173/// use goud_engine::core::error::{GoudError, set_last_error, last_error_message};
174///
175/// set_last_error(GoudError::InitializationFailed("GPU not found".to_string()));
176/// let msg = last_error_message();
177/// assert_eq!(msg, Some("GPU not found".to_string()));
178/// ```
179pub fn last_error_message() -> Option<String> {
180 LAST_ERROR.with(|e| e.borrow().as_ref().map(|err| err.message().to_string()))
181}
182
183/// Clears the last error for the current thread.
184///
185/// After calling this, `last_error_code()` will return `SUCCESS` and
186/// `last_error_message()` will return `None`.
187///
188/// # Thread Safety
189///
190/// Only affects the current thread's error storage.
191///
192/// # Example
193///
194/// ```
195/// use goud_engine::core::error::{GoudError, set_last_error, clear_last_error, last_error_code, SUCCESS};
196///
197/// set_last_error(GoudError::NotInitialized);
198/// clear_last_error();
199/// assert_eq!(last_error_code(), SUCCESS);
200/// ```
201pub fn clear_last_error() {
202 LAST_ERROR.with(|e| {
203 *e.borrow_mut() = None;
204 });
205 LAST_ERROR_CONTEXT.with(|c| {
206 *c.borrow_mut() = None;
207 });
208}
209
210/// Returns the subsystem from the last error's context, if set.
211///
212/// Returns `None` if no error context has been set or if the error was
213/// set without context via `set_last_error()`.
214pub fn last_error_subsystem() -> Option<&'static str> {
215 LAST_ERROR_CONTEXT.with(|c| c.borrow().as_ref().map(|ctx| ctx.subsystem))
216}
217
218/// Returns the operation from the last error's context, if set.
219///
220/// Returns `None` if no error context has been set or if the error was
221/// set without context via `set_last_error()`.
222pub fn last_error_operation() -> Option<&'static str> {
223 LAST_ERROR_CONTEXT.with(|c| c.borrow().as_ref().map(|ctx| ctx.operation))
224}
225
226// =============================================================================
227// FFI Result Type
228// =============================================================================
229
230/// FFI-safe result type for returning success/failure status across the FFI boundary.
231///
232/// This struct is designed to be passed by value across FFI and provides both
233/// a boolean success flag and the error code for detailed error handling.
234///
235/// # Memory Layout
236///
237/// Uses `#[repr(C)]` for predictable memory layout across language boundaries.
238/// The struct is 8 bytes (4 bytes for code, 4 bytes for success with padding).
239///
240/// # Usage
241///
242/// ```
243/// use goud_engine::core::error::{GoudFFIResult, GoudError, SUCCESS};
244///
245/// // Success case
246/// let result = GoudFFIResult::success();
247/// assert!(result.success);
248/// assert_eq!(result.code, SUCCESS);
249///
250/// // Error case
251/// let result = GoudFFIResult::from_error(GoudError::NotInitialized);
252/// assert!(!result.success);
253/// ```
254#[repr(C)]
255#[derive(Clone, Copy, Debug, PartialEq, Eq)]
256pub struct GoudFFIResult {
257 /// The error code. `SUCCESS` (0) on success, error code on failure.
258 pub code: GoudErrorCode,
259 /// True if the operation succeeded, false otherwise.
260 pub success: bool,
261}
262
263impl GoudFFIResult {
264 /// Creates a successful result.
265 ///
266 /// # Example
267 ///
268 /// ```
269 /// use goud_engine::core::error::{GoudFFIResult, SUCCESS};
270 ///
271 /// let result = GoudFFIResult::success();
272 /// assert!(result.success);
273 /// assert_eq!(result.code, SUCCESS);
274 /// ```
275 #[inline]
276 pub const fn success() -> Self {
277 Self {
278 code: SUCCESS,
279 success: true,
280 }
281 }
282
283 /// Creates a result from an error code.
284 ///
285 /// # Example
286 ///
287 /// ```
288 /// use goud_engine::core::error::{GoudFFIResult, ERR_NOT_INITIALIZED};
289 ///
290 /// let result = GoudFFIResult::from_code(ERR_NOT_INITIALIZED);
291 /// assert!(!result.success);
292 /// assert_eq!(result.code, ERR_NOT_INITIALIZED);
293 /// ```
294 #[inline]
295 pub const fn from_code(code: GoudErrorCode) -> Self {
296 Self {
297 code,
298 success: code == SUCCESS,
299 }
300 }
301
302 /// Creates a result from a `GoudError`.
303 ///
304 /// This also sets the thread-local last error for message retrieval.
305 ///
306 /// # Example
307 ///
308 /// ```
309 /// use goud_engine::core::error::{GoudFFIResult, GoudError, ERR_NOT_INITIALIZED, last_error_message};
310 ///
311 /// let result = GoudFFIResult::from_error(GoudError::NotInitialized);
312 /// assert!(!result.success);
313 /// assert_eq!(result.code, ERR_NOT_INITIALIZED);
314 /// ```
315 #[inline]
316 pub fn from_error(error: GoudError) -> Self {
317 let code = error.error_code();
318 set_last_error(error);
319 Self {
320 code,
321 success: false,
322 }
323 }
324
325 /// Creates a result from a `GoudResult<T>`.
326 ///
327 /// On success, clears any previous error. On error, sets the last error.
328 ///
329 /// # Example
330 ///
331 /// ```
332 /// use goud_engine::core::error::{GoudFFIResult, GoudResult, GoudError, SUCCESS, ERR_NOT_INITIALIZED};
333 ///
334 /// let ok_result: GoudResult<i32> = Ok(42);
335 /// let ffi_result = GoudFFIResult::from_result(ok_result);
336 /// assert!(ffi_result.success);
337 ///
338 /// let err_result: GoudResult<i32> = Err(GoudError::NotInitialized);
339 /// let ffi_result = GoudFFIResult::from_result(err_result);
340 /// assert!(!ffi_result.success);
341 /// assert_eq!(ffi_result.code, ERR_NOT_INITIALIZED);
342 /// ```
343 #[inline]
344 pub fn from_result<T>(result: super::GoudResult<T>) -> Self {
345 match result {
346 Ok(_) => {
347 clear_last_error();
348 Self::success()
349 }
350 Err(error) => Self::from_error(error),
351 }
352 }
353
354 /// Returns true if the result indicates success.
355 #[inline]
356 pub const fn is_success(&self) -> bool {
357 self.success
358 }
359
360 /// Returns true if the result indicates failure.
361 #[inline]
362 pub const fn is_error(&self) -> bool {
363 !self.success
364 }
365
366 // =========================================================================
367 // Compatibility aliases (used by GoudResult type alias and proc macros)
368 // =========================================================================
369
370 /// Alias for `success()` — creates a success result.
371 #[inline]
372 pub const fn ok() -> Self {
373 Self::success()
374 }
375
376 /// Alias for `from_code()` — creates an error result from a code.
377 #[inline]
378 pub const fn err(code: GoudErrorCode) -> Self {
379 Self::from_code(code)
380 }
381
382 /// Alias for `is_success()` — returns true if the result is ok.
383 #[inline]
384 pub const fn is_ok(&self) -> bool {
385 self.success
386 }
387
388 /// Alias for `is_error()` — returns true if the result is an error.
389 #[inline]
390 pub const fn is_err(&self) -> bool {
391 !self.success
392 }
393}
394
395impl Default for GoudFFIResult {
396 /// Default is success.
397 fn default() -> Self {
398 Self::success()
399 }
400}
401
402impl From<GoudError> for GoudFFIResult {
403 fn from(error: GoudError) -> Self {
404 Self::from_error(error)
405 }
406}
407
408impl From<GoudErrorCode> for GoudFFIResult {
409 fn from(code: GoudErrorCode) -> Self {
410 if code == SUCCESS {
411 Self::success()
412 } else {
413 Self::from_code(code)
414 }
415 }
416}
417
418impl<T> From<super::GoudResult<T>> for GoudFFIResult {
419 fn from(result: super::GoudResult<T>) -> Self {
420 Self::from_result(result)
421 }
422}
423
424impl std::fmt::Display for GoudFFIResult {
425 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
426 if self.success {
427 write!(f, "GoudResult::Success")
428 } else {
429 write!(f, "GoudResult::Error(code={})", self.code)
430 }
431 }
432}