1use std::{
2 cell::Cell,
3 ffi::c_void,
4 future::Future,
5 pin::Pin,
6 rc::Rc,
7 task::{Context, Poll, Waker},
8 thread::LocalKey,
9};
10
11pub use wasm_split_macro::{lazy_loader, wasm_split};
12
13pub type Result<T> = std::result::Result<T, SplitLoaderError>;
14
15#[non_exhaustive]
16#[derive(Debug, Clone)]
17pub enum SplitLoaderError {
18 FailedToLoad,
19}
20impl std::fmt::Display for SplitLoaderError {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 match self {
23 SplitLoaderError::FailedToLoad => write!(f, "Failed to load wasm-split module"),
24 }
25 }
26}
27
28pub struct LazyLoader<Args, Ret> {
71 imported: unsafe extern "C" fn(arg: Args) -> Ret,
72 key: &'static LocalKey<LazySplitLoader>,
73}
74
75impl<Args, Ret> LazyLoader<Args, Ret> {
76 #[doc(hidden)]
82 pub const unsafe fn new(
83 imported: unsafe extern "C" fn(arg: Args) -> Ret,
84 key: &'static LocalKey<LazySplitLoader>,
85 ) -> Self {
86 Self { imported, key }
87 }
88
89 pub const fn preloaded(f: fn(Args) -> Ret) -> Self {
91 let imported =
92 unsafe { std::mem::transmute::<fn(Args) -> Ret, unsafe extern "C" fn(Args) -> Ret>(f) };
93
94 thread_local! {
95 static LAZY: LazySplitLoader = LazySplitLoader::preloaded();
96 };
97
98 Self {
99 imported,
100 key: &LAZY,
101 }
102 }
103
104 pub async fn load(&'static self) -> bool {
106 *self.key.with(|inner| inner.lazy.clone()).as_ref().await
107 }
108
109 pub fn call(&'static self, args: Args) -> Result<Ret> {
111 let Some(true) = self.key.with(|inner| inner.lazy.try_get().copied()) else {
112 return Err(SplitLoaderError::FailedToLoad);
113 };
114
115 Ok(unsafe { (self.imported)(args) })
116 }
117}
118
119type Lazy = async_once_cell::Lazy<bool, SplitLoaderFuture>;
120type LoadCallbackFn = unsafe extern "C" fn(*const c_void, bool) -> ();
121type LoadFn = unsafe extern "C" fn(LoadCallbackFn, *const c_void) -> ();
122
123pub struct LazySplitLoader {
124 lazy: Pin<Rc<Lazy>>,
125}
126
127impl LazySplitLoader {
128 #[doc(hidden)]
135 pub unsafe fn new(load: LoadFn) -> Self {
136 Self {
137 lazy: Rc::pin(Lazy::new({
138 SplitLoaderFuture {
139 loader: Rc::new(SplitLoader {
140 state: Cell::new(SplitLoaderState::Deferred(load)),
141 waker: Cell::new(None),
142 }),
143 }
144 })),
145 }
146 }
147
148 fn preloaded() -> Self {
149 Self {
150 lazy: Rc::pin(Lazy::new({
151 SplitLoaderFuture {
152 loader: Rc::new(SplitLoader {
153 state: Cell::new(SplitLoaderState::Completed(true)),
154 waker: Cell::new(None),
155 }),
156 }
157 })),
158 }
159 }
160
161 pub async fn ensure_loaded(loader: &'static std::thread::LocalKey<LazySplitLoader>) -> bool {
163 *loader.with(|inner| inner.lazy.clone()).as_ref().await
164 }
165}
166
167struct SplitLoader {
168 state: Cell<SplitLoaderState>,
169 waker: Cell<Option<Waker>>,
170}
171
172#[derive(Clone, Copy)]
173enum SplitLoaderState {
174 Deferred(LoadFn),
175 Pending,
176 Completed(bool),
177}
178
179struct SplitLoaderFuture {
180 loader: Rc<SplitLoader>,
181}
182
183impl Future for SplitLoaderFuture {
184 type Output = bool;
185
186 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<bool> {
187 unsafe extern "C" fn load_callback(loader: *const c_void, success: bool) {
188 let loader = unsafe { Rc::from_raw(loader as *const SplitLoader) };
189 loader.state.set(SplitLoaderState::Completed(success));
190 if let Some(waker) = loader.waker.take() {
191 waker.wake()
192 }
193 }
194
195 match self.loader.state.get() {
196 SplitLoaderState::Deferred(load) => {
197 self.loader.state.set(SplitLoaderState::Pending);
198 self.loader.waker.set(Some(cx.waker().clone()));
199 unsafe {
200 load(
201 load_callback,
202 Rc::<SplitLoader>::into_raw(self.loader.clone()) as *const c_void,
203 )
204 };
205 Poll::Pending
206 }
207 SplitLoaderState::Pending => {
208 self.loader.waker.set(Some(cx.waker().clone()));
209 Poll::Pending
210 }
211 SplitLoaderState::Completed(value) => Poll::Ready(value),
212 }
213 }
214}