medea_jason/platform/dart/utils/dart_api.rs
1//! Functionality for calling [`Dart DL API`] from Rust.
2//!
3//! [`Dart DL API`]: https://tinyurl.com/32e7fudh
4
5use core::ffi;
6
7use dart_sys::{
8 Dart_CObject, Dart_DeletePersistentHandle_DL, Dart_FinalizableHandle,
9 Dart_GetError_DL, Dart_Handle, Dart_HandleFinalizer,
10 Dart_HandleFromPersistent_DL, Dart_InitializeApiDL, Dart_IsError_DL,
11 Dart_NewFinalizableHandle_DL, Dart_NewPersistentHandle_DL,
12 Dart_NewUnhandledExceptionError_DL, Dart_PersistentHandle, Dart_Port_DL,
13 Dart_PostCObject_DL, Dart_PropagateError_DL,
14};
15
16/// Initializes usage of Dynamically Linked Dart API.
17///
18/// # Safety
19///
20/// Intended to be called ONLY with [`NativeApi.initializeApiDLData`][1] from
21/// Dart.
22///
23/// [1]: https://api.dart.dev/dart-ffi/NativeApi/initializeApiDLData.html
24pub unsafe fn initialize_api(data: *mut ffi::c_void) -> isize {
25 unsafe { Dart_InitializeApiDL(data) }
26}
27
28/// Allocates a [`Dart_PersistentHandle`] for provided [`Dart_Handle`].
29///
30/// [`Dart_PersistentHandle`]s have the lifetime of the current isolate
31/// unless they are explicitly deallocated.
32///
33/// # Safety
34///
35/// [`initialize_api`] must be called before this function.
36pub unsafe fn new_persistent_handle(
37 object: Dart_Handle,
38) -> Dart_PersistentHandle {
39 let func = unsafe {
40 #[expect(clippy::expect_used, reason = "FFI should be initialized")]
41 Dart_NewPersistentHandle_DL
42 .expect("`dart_api_dl` has not been initialized")
43 };
44 unsafe { func(object) }
45}
46
47/// Allocates a [`Dart_Handle`] in the current scope from the given
48/// [`Dart_PersistentHandle`].
49///
50/// This doesn't affect the provided [`Dart_PersistentHandle`]'s lifetime.
51///
52/// # Safety
53///
54/// [`initialize_api`] must be called before this function.
55pub unsafe fn handle_from_persistent(
56 object: Dart_PersistentHandle,
57) -> Dart_Handle {
58 let func = unsafe {
59 #[expect(clippy::expect_used, reason = "FFI should be initialized")]
60 Dart_HandleFromPersistent_DL
61 .expect("`dart_api_dl` has not been initialized")
62 };
63 unsafe { func(object) }
64}
65
66/// Deallocates the provided [`Dart_PersistentHandle`].
67///
68/// # Safety
69///
70/// [`initialize_api`] must be called before this function.
71pub unsafe fn delete_persistent_handle(object: Dart_Handle) {
72 let func = unsafe {
73 #[expect(clippy::expect_used, reason = "FFI should be initialized")]
74 Dart_DeletePersistentHandle_DL
75 .expect("`dart_api_dl` has not been initialized")
76 };
77 unsafe {
78 func(object);
79 }
80}
81
82/// Posts a `message` on some port. It will contain a [`Dart_CObject`]
83/// object graph rooted in the `message`.
84///
85/// While the `message` is being sent the state of the graph of
86/// [`Dart_CObject`] structures rooted in the `message` should not be
87/// accessed, as the message generation will make temporary modifications to
88/// the data. When the message has been sent the graph will be fully
89/// restored.
90///
91/// If `true` is returned, the `message` was enqueued, and finalizers for
92/// external typed data will eventually run, even if the receiving isolate
93/// shuts down before processing the `message`. If `false` is returned, the
94/// `message` was not enqueued and ownership of external typed data in the
95/// `message` remains with the caller.
96///
97/// # Safety
98///
99/// [`initialize_api`] must be called before this function.
100pub unsafe fn post_c_object(
101 port_id: Dart_Port_DL,
102 message: *mut Dart_CObject,
103) -> bool {
104 let func = unsafe {
105 #[expect(clippy::expect_used, reason = "FFI should be initialized")]
106 Dart_PostCObject_DL.expect("`dart_api_dl` has not been initialized")
107 };
108 unsafe { func(port_id, message) }
109}
110
111/// Allocates a finalizable handle for an object.
112///
113/// This handle has the lifetime of the current isolate group unless the
114/// object pointed to by the handle is garbage collected, in this case the
115/// VM automatically deletes the handle after invoking the callback
116/// associated with the handle.
117///
118/// Once finalizable handle is collected by GC, the provided `callback` is
119/// called. It may be executed on any thread, will have an isolate group, but
120/// won't have the current isolate.
121///
122/// `peer` argument will be provided to the `callback` on finalize.
123///
124/// `external_allocation_size` is a size of the `peer` which can be
125/// calculated via [`mem::size_of()`].
126///
127/// # Safety
128///
129/// [`initialize_api`] must be called before this function.
130///
131/// [`mem::size_of()`]: std::mem::size_of
132pub unsafe fn new_finalizable_handle(
133 object: Dart_Handle,
134 peer: *mut ffi::c_void,
135 external_allocation_size: isize,
136 callback: Dart_HandleFinalizer,
137) -> Dart_FinalizableHandle {
138 let func = unsafe {
139 #[expect(clippy::expect_used, reason = "FFI should be initialized")]
140 Dart_NewFinalizableHandle_DL
141 .expect("`dart_api_dl` has not been initialized")
142 };
143 unsafe { func(object, peer, external_allocation_size, callback) }
144}
145
146/// Checks whether the provided [`Dart_Handle`] represents a Dart error.
147///
148/// Should be called on the current isolate.
149///
150/// # Safety
151///
152/// [`initialize_api`] must be called before this function.
153pub unsafe fn is_error(handle: Dart_Handle) -> bool {
154 let func = unsafe {
155 #[expect(clippy::expect_used, reason = "FFI should be initialized")]
156 Dart_IsError_DL.expect("`dart_api_dl` has not been initialized")
157 };
158 unsafe { func(handle) }
159}
160
161/// Returns the error message from the provided Dart error handle.
162///
163/// Should be called on the current isolate.
164///
165/// Returns a C string containing a Dart error message if the provided
166/// `object` represents a Dart error, or an empty C string ("") otherwise.
167///
168/// # Safety
169///
170/// [`initialize_api`] must be called before this function.
171pub unsafe fn get_error(handle: Dart_Handle) -> *const ffi::c_char {
172 let func = unsafe {
173 #[expect(clippy::expect_used, reason = "FFI should be initialized")]
174 Dart_GetError_DL.expect("`dart_api_dl` has not been initialized")
175 };
176 unsafe { func(handle) }
177}
178
179/// Propagates the given Dart error to the Dart side.
180///
181/// If the provided [`Dart_Handle`] is an unhandled exception error, then it
182/// will be rethrown in the standard way: walking up Dart frames until an
183/// appropriate `catch` block is found, than executing `finally` blocks, and so
184/// on.
185///
186/// # Safety
187///
188/// Intended to be called ONLY with [`NativeApi.initializeApiDLData`][1] from
189/// Dart.
190///
191/// [`initialize_api`] must be called before this function.
192///
193/// [1]: https://api.dart.dev/dart-ffi/NativeApi/initializeApiDLData.html
194pub unsafe fn propagate_error(mut handle: Dart_Handle) {
195 let is_error = unsafe {
196 #[expect(clippy::expect_used, reason = "FFI should be initialized")]
197 Dart_IsError_DL.expect("`dart_api_dl` has not been initialized")
198 };
199
200 let is_error = unsafe { is_error(handle) };
201
202 if !is_error {
203 let make_unhandled = unsafe {
204 #[expect(clippy::expect_used, reason = "FFI should be initialized")]
205 Dart_NewUnhandledExceptionError_DL
206 .expect("`dart_api_dl` has not been initialized")
207 };
208
209 handle = unsafe { make_unhandled(handle) };
210 }
211
212 let propagate = unsafe {
213 #[expect(clippy::expect_used, reason = "FFI should be initialized")]
214 Dart_PropagateError_DL.expect("`dart_api_dl` has not been initialized")
215 };
216 unsafe {
217 propagate(handle);
218 }
219}