compo_platform_loop/
loop.rs

1//! Platform-specific event loop implementations for the Compo framework.
2//! 
3//! This module provides cross-platform event loop integration that allows Compo
4//! applications to run natively on different operating systems. Each platform
5//! uses its native event loop mechanism:
6//! 
7//! - **Windows**: Win32 message loop with PeekMessage for non-blocking processing
8//! - **macOS**: NSApplication with NSRunLoop and NSTimer for periodic polling
9//! - **iOS**: NSRunLoop with NSTimer for periodic polling (without NSApplication)
10//! - **Android**: JNI integration with Java MainLoop for Android event system
11//! 
12//! The module exports platform-appropriate `run` functions that initialize the
13//! Compo runtime and integrate it with the platform's native event loop.
14
15use compo::prelude::*;
16
17#[cfg(windows)]
18use windows::Win32::UI::WindowsAndMessaging::{
19    DispatchMessageW, MSG, PM_REMOVE, PeekMessageW, TranslateMessage, WM_QUIT,
20};
21#[cfg(target_os = "ios")]
22use {
23    block2::RcBlock,
24    objc2::{ClassType, msg_send},
25    objc2_foundation::{NSRunLoop, NSString, NSTimer},
26};
27#[cfg(target_os = "android")]
28use {
29    jni::{JNIEnv, JavaVM, NativeMethod},
30    std::{any::Any, cell::Cell},
31    tracing::error,
32};
33
34#[cfg(target_os = "macos")]
35use {
36    block2::RcBlock,
37    objc2::{ClassType, MainThreadMarker, msg_send},
38    objc2_app_kit::NSApplication,
39    objc2_foundation::{NSRunLoop, NSString, NSTimer},
40};
41
42/// Thread-local storage for Android runtime and component management.
43/// 
44/// On Android, we use thread-local storage to maintain the Compo runtime and
45/// component instances. This is necessary because Android's JNI callbacks
46/// need access to the runtime from the same thread where it was created.
47#[cfg(target_os = "android")]
48thread_local! {
49    /// The Compo runtime instance for processing async tasks
50    static RT: Rc<Runtime<'static, ()>> = Rc::new(Runtime::new());
51    /// Storage for the root component instance
52    static COMPONENT: Cell<Rc<dyn Any>> = Cell::new(Rc::new(()));
53}
54
55/// Handles Windows message processing for the event loop.
56/// 
57/// This function processes Windows messages using PeekMessage instead of GetMessage
58/// to avoid blocking the event loop. It handles WM_QUIT messages for graceful shutdown
59/// and dispatches other messages to their appropriate window procedures.
60/// 
61/// # Arguments
62/// * `r#loop` - Reference to the Loop instance for controlling the event loop
63#[cfg(windows)]
64fn handle_windows_message(r#loop: &Loop) {
65    // Use PeekMessage instead of GetMessage because GetMessage blocks until a message is available
66    unsafe {
67        let mut msg = MSG::default();
68        // Check if there are messages in the queue without blocking
69        while PeekMessageW(&mut msg, None, 0, 0, PM_REMOVE).as_bool() {
70            // If it's a WM_QUIT message, exit the loop
71            if msg.message == WM_QUIT {
72                r#loop.quit();
73                break;
74            }
75
76            // Translate virtual key messages
77            let _ = TranslateMessage(&msg);
78            // Dispatch message to window procedure
79            DispatchMessageW(&msg);
80        }
81    }
82}
83
84/// Runs the platform-specific event loop with the given entry component.
85/// 
86/// This function initializes and starts the appropriate event loop for the current platform:
87/// - **Windows**: Uses Win32 message loop with PeekMessage for non-blocking message processing
88/// - **macOS**: Uses NSApplication with NSRunLoop and NSTimer for periodic runtime polling
89/// - **iOS**: Uses NSRunLoop with NSTimer for periodic runtime polling (without NSApplication)
90/// 
91/// The function creates a Compo runtime, spawns the entry component as an async task,
92/// and integrates with the platform's native event loop to ensure proper execution
93/// of async components.
94/// 
95/// # Type Parameters
96/// * `C` - The component type that implements `Component<'a>`
97/// * `F` - The async function type that takes a `Weak<C>` and returns a future
98/// 
99/// # Arguments
100/// * `entry` - The entry point async function that will be executed as the root component
101/// 
102/// # Platform-specific behavior
103/// - **Windows**: Registers a message handler and runs the Loop with Windows message processing
104/// - **macOS**: Creates NSApplication, sets up a timer for runtime polling, and runs the app loop
105/// - **iOS**: Sets up NSRunLoop with a timer for runtime polling (suitable for iOS apps)
106/// 
107/// # Examples
108/// ```rust
109/// //! Platform-specific event loop implementations for the Compo framework.
110/// //!
111/// //! This module provides cross-platform event loop integration that allows Compo
112/// //! applications to run natively on different operating systems. Each platform
113/// //! uses its native event loop mechanism:
114/// //!
115/// //! - **Windows**: Win32 message loop with PeekMessage for non-blocking processing
116/// //! - **macOS**: NSApplication with NSRunLoop and NSTimer for periodic polling
117/// //! - **iOS**: NSRunLoop with NSTimer for periodic polling (without NSApplication)
118/// //! - **Android**: JNI integration with Java MainLoop for Android event system
119/// //!
120/// //! The module exports platform-appropriate `run` functions that initialize the
121/// //! Compo runtime and integrate it with the platform's native event loop.
122///
123/// use compo::prelude::*;
124/// use compo_platform_loop::prelude::run;
125/// 
126/// #[component]
127/// async fn app() {
128///     println!("Hello from Compo!");
129/// }
130/// 
131/// fn main() {
132///     run(app);
133/// }
134/// ```
135#[cfg(not(target_os = "android"))]
136pub fn run<'a, C, F>(entry: F)
137where
138    C: Component<'a> + 'a,
139    F: AsyncFn(Weak<C>) + 'a,
140{
141    #[cfg(windows)]
142    Loop::new()
143        .register_poll_handler(handle_windows_message)
144        .run(entry);
145
146    #[cfg(target_os = "ios")]
147    {
148        // 创建运行时和组件
149        let rt = Rc::new(Runtime::new());
150        let rt_weak = Rc::downgrade(&rt);
151        let c = Rc::new(C::new(rt_weak.clone()));
152        let c_weak = Rc::downgrade(&c);
153
154        // 启动异步任务
155        rt.spawn(async move { entry(c_weak).await });
156
157        // 获取主线程的运行循环
158        let run_loop = NSRunLoop::mainRunLoop();
159
160        // 创建一个定时器,用于定期轮询 Runtime
161        let poll_block = RcBlock::new(move || {
162            // 轮询 Runtime 以推进异步任务
163            if let Some(rt) = rt_weak.upgrade() {
164                rt.poll_all();
165            }
166        });
167
168        // 创建一个重复的定时器,每 0.01 秒轮询一次
169        let timer: *mut NSTimer = unsafe {
170            msg_send![NSTimer::class(),
171                scheduledTimerWithTimeInterval: 0.01,
172                repeats: true,
173                block: &*poll_block
174            ]
175        };
176
177        // 将定时器添加到运行循环中
178        let mode = NSString::from_str("NSDefaultRunLoopMode");
179        let _: () = unsafe { msg_send![&run_loop, addTimer: timer, forMode: &*mode] };
180
181        // 运行主循环,这会阻塞当前线程
182        let _: () = unsafe { msg_send![&run_loop, run] };
183    }
184
185    #[cfg(target_os = "macos")]
186    {
187        // 创建运行时和组件
188        let rt = Rc::new(Runtime::new());
189        let rt_weak = Rc::downgrade(&rt);
190        let c = Rc::new(C::new(rt_weak.clone()));
191        let c_weak = Rc::downgrade(&c);
192
193        // 启动异步任务
194        rt.spawn(async move { entry(c_weak).await });
195
196        // 获取 NSApplication 实例
197        let mtm = MainThreadMarker::new().unwrap();
198        let app = NSApplication::sharedApplication(mtm);
199
200        // 获取主线程的运行循环
201        let run_loop = NSRunLoop::mainRunLoop();
202
203        // 创建一个定时器,用于定期轮询 Runtime
204        let poll_block = RcBlock::new(move || {
205            // 轮询 Runtime 以推进异步任务
206            if let Some(rt) = rt_weak.upgrade() {
207                rt.poll_all();
208            }
209        });
210
211        // 创建一个重复的定时器,每 0.01 秒轮询一次
212        let timer: *mut NSTimer = unsafe {
213            msg_send![NSTimer::class(),
214                scheduledTimerWithTimeInterval: 0.01,
215                repeats: true,
216                block: &*poll_block
217            ]
218        };
219
220        // 将定时器添加到运行循环中
221        // 创建 NSDefaultRunLoopMode 字符串
222        let mode = NSString::from_str("NSDefaultRunLoopMode");
223        let _: () = unsafe { msg_send![&run_loop, addTimer: timer, forMode: &*mode] };
224
225        // 激活应用程序
226        app.activate();
227
228        // 运行应用程序主循环,这会阻塞当前线程
229        app.run();
230    }
231}
232
233/// Runs the Android-specific event loop with JNI integration.
234/// 
235/// This function sets up the event loop for Android applications using JNI (Java Native Interface).
236/// It creates a Compo runtime in thread-local storage, spawns the entry component, and integrates
237/// with the Android Java MainLoop class for proper event loop execution.
238/// 
239/// The function registers a native method `poll_all` that can be called from Java to advance
240/// the async runtime, enabling proper integration with Android's event system.
241/// 
242/// # Type Parameters
243/// * `C` - The component type that implements `Component<'static>` (must be 'static for Android)
244/// * `F` - The async function type that takes a `Weak<C>` and returns a future (must be 'static)
245/// 
246/// # Arguments
247/// * `vm` - The JavaVM instance provided by the Android runtime
248/// * `entry` - The entry point async function that will be executed as the root component
249/// 
250/// # Android Integration
251/// This function:
252/// 1. Creates a thread-local Compo runtime and component
253/// 2. Spawns the entry component as an async task
254/// 3. Calls the Java `MainLoop.run()` method to start the Android event loop
255/// 4. Registers the native `poll_all` method for runtime advancement
256/// 
257/// # Examples
258/// ```rust
259/// //! Platform-specific event loop implementations for the Compo framework.
260/// //!
261/// //! This module provides cross-platform event loop integration that allows Compo
262/// //! applications to run natively on different operating systems. Each platform
263/// //! uses its native event loop mechanism:
264/// //!
265/// //! - **Windows**: Win32 message loop with PeekMessage for non-blocking processing
266/// //! - **macOS**: NSApplication with NSRunLoop and NSTimer for periodic polling
267/// //! - **iOS**: NSRunLoop with NSTimer for periodic polling (without NSApplication)
268/// //! - **Android**: JNI integration with Java MainLoop for Android event system
269/// //!
270/// //! The module exports platform-appropriate `run` functions that initialize the
271/// //! Compo runtime and integrate it with the platform's native event loop.
272///
273/// use compo::prelude::*;
274/// use compo_platform_loop::prelude::run;
275/// use jni::JavaVM;
276/// 
277/// #[component]
278/// async fn app() {
279///     println!("Hello from Android!");
280/// }
281/// 
282/// fn main(vm: JavaVM) {
283///     run(vm, app);
284/// }
285/// ```
286#[cfg(target_os = "android")]
287pub fn run<C, F>(vm: JavaVM, entry: F)
288where
289    C: Component<'static> + 'static,
290    F: AsyncFn(Weak<C>) + 'static,
291{
292    RT.with(|rt| {
293        let rt_weak = Rc::downgrade(rt);
294        let c = Rc::new(C::new(rt_weak.clone()));
295        let c_weak = Rc::downgrade(&c);
296
297        // 启动异步任务
298        rt.spawn(async move { entry(c_weak).await });
299        COMPONENT.set(c);
300    });
301    if let Ok(mut env) = vm.attach_current_thread() {
302        const CLASS: &str = "rust/compo/MainLoop";
303        if let Err(e) = env.call_static_method(CLASS, "run", "()V", &[]) {
304            error!(?e, "Run failed.");
305        }
306        let method = NativeMethod {
307            name: "poll_all".into(),
308            sig: "()V".into(),
309            fn_ptr: poll_all as *mut _,
310        };
311        if let Err(e) = env.register_native_methods(CLASS, &[method]) {
312            error!(?e, "Register native method failed.");
313        }
314    }
315}
316
317/// Native method called from Java to advance the Compo runtime.
318/// 
319/// This function is registered as a JNI native method and called by the Android
320/// Java MainLoop to poll and advance all pending async tasks in the Compo runtime.
321/// It accesses the thread-local runtime and calls `poll_all()` to process any
322/// ready futures.
323/// 
324/// # Safety
325/// This function is marked as `unsafe` because it's a C-style callback function
326/// that will be called from Java via JNI. The JNI environment parameter is
327/// currently unused but required by the JNI interface.
328/// 
329/// # Arguments
330/// * `_env` - The JNI environment (unused in current implementation)
331#[cfg(target_os = "android")]
332unsafe extern "C" fn poll_all(_env: JNIEnv) {
333    RT.with(|rt| rt.poll_all());
334}