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