ul_next/
renderer.rs

1//! The `Renderer` manages all [`View`]s  and coordinates painting,
2//! network requests, and event dispatch
3//!
4//! [`Renderer`] should be used when you want to access GPU internals and textures
5//! to integrate with your own rendering pipeline.
6//!
7//! Before creating a renderer [`Renderer::create`] you must supply a custom
8//! [`GpuDriver`](crate::gpu_driver::GpuDriver) in
9//! [`platform::set_gpu_driver`](crate::platform::set_gpu_driver).
10use std::{ffi::CString, sync::Arc};
11
12use crate::{
13    config::Config,
14    error::CreationError,
15    event::{GamepadAxisEvent, GamepadButtonEvent, GamepadEvent},
16    string::UlString,
17    view::{View, ViewConfig},
18    Library,
19};
20
21/// A Session stores local data such as cookies, local storage, and application
22/// cache for one or more [`View`]s.
23/// (See [`Renderer::create_session`](crate::renderer::Renderer::create_session))
24pub struct Session {
25    lib: Arc<Library>,
26    internal: ul_sys::ULSession,
27    need_to_destroy: bool,
28
29    is_persistent: bool,
30    name: String,
31    id: u64,
32    disk_path: String,
33}
34
35impl Session {
36    /// Internal function helper to create a session.
37    /// (See [`Renderer::create_session`](crate::renderer::Renderer::create_session))
38    pub(crate) unsafe fn create(
39        lib: Arc<Library>,
40        renderer: ul_sys::ULRenderer,
41        is_persistent: bool,
42        name: &str,
43    ) -> Result<Self, CreationError> {
44        let ul_string_name = UlString::from_str(lib.clone(), name)?;
45        let internal =
46            lib.ultralight()
47                .ulCreateSession(renderer, is_persistent, ul_string_name.to_ul());
48
49        if internal.is_null() {
50            return Err(CreationError::NullReference);
51        }
52
53        let id = lib.ultralight().ulSessionGetId(internal);
54        let disk_path =
55            UlString::copy_raw_to_string(&lib, lib.ultralight().ulSessionGetDiskPath(internal))?;
56
57        Ok(Self {
58            lib,
59            internal,
60            need_to_destroy: true,
61
62            is_persistent,
63            name: name.to_string(),
64            id,
65            disk_path,
66        })
67    }
68
69    /// Helper internal function to allow getting a reference to a managed
70    /// session.
71    pub(crate) unsafe fn from_raw(
72        lib: Arc<Library>,
73        raw: ul_sys::ULSession,
74    ) -> Result<Self, CreationError> {
75        if raw.is_null() {
76            return Err(CreationError::NullReference);
77        }
78
79        let id = lib.ultralight().ulSessionGetId(raw);
80        let disk_path =
81            UlString::copy_raw_to_string(&lib, lib.ultralight().ulSessionGetDiskPath(raw))?;
82        let name = UlString::copy_raw_to_string(&lib, lib.ultralight().ulSessionGetName(raw))?;
83        let is_persistent = lib.ultralight().ulSessionIsPersistent(raw);
84
85        Ok(Self {
86            lib,
87            internal: raw,
88            need_to_destroy: false,
89
90            is_persistent,
91            name,
92            id,
93            disk_path,
94        })
95    }
96
97    /// Returns the underlying [`ul_sys::ULSession`] struct, to be used locally for
98    /// calling the underlying C API.
99    pub(crate) unsafe fn to_ul(&self) -> ul_sys::ULSession {
100        self.internal
101    }
102}
103
104impl Session {
105    /// A unique numeric ID identifying this session.
106    pub fn id(&self) -> u64 {
107        self.id
108    }
109
110    /// A unique name identifying this session.
111    pub fn name(&self) -> &str {
112        &self.name
113    }
114
115    /// The disk path of this session (only valid for persistent sessions).
116    pub fn disk_path(&self) -> &str {
117        &self.disk_path
118    }
119
120    /// Whether or not this session is written to disk.
121    pub fn is_persistent(&self) -> bool {
122        self.is_persistent
123    }
124}
125
126impl Drop for Session {
127    fn drop(&mut self) {
128        if self.need_to_destroy {
129            unsafe {
130                self.lib.ultralight().ulDestroySession(self.internal);
131            }
132        }
133    }
134}
135
136/// The `Renderer` manages all [`View`]s  and coordinates painting,
137/// network requests, and event dispatch
138///
139/// You don't have to create this instance directly if you use the AppCore API.
140/// The [`App`](crate::app::App) struct will automatically create a `Renderer`
141/// and perform all rendering within its run loop.
142pub struct Renderer {
143    lib: Arc<Library>,
144    internal: ul_sys::ULRenderer,
145
146    need_to_destroy: bool,
147    default_session: Session,
148}
149
150impl Renderer {
151    /// Internal helper to get a reference to the underlying Renderer.
152    #[allow(dead_code)]
153    pub(crate) unsafe fn from_raw(
154        lib: Arc<Library>,
155        raw: ul_sys::ULRenderer,
156    ) -> Result<Self, CreationError> {
157        let raw_default_session = lib.ultralight().ulDefaultSession(raw);
158        if raw_default_session.is_null() {
159            return Err(CreationError::NullReference);
160        }
161        let default_session = Session::from_raw(lib.clone(), raw_default_session)?;
162
163        Ok(Self {
164            lib,
165            internal: raw,
166            need_to_destroy: false,
167            default_session,
168        })
169    }
170
171    /// Create the Ultralight Renderer directly.
172    ///
173    /// Unlike [`App::new`](crate::app::App::new), this does not use any native windows for drawing and allows you to manage
174    /// your own runloop and painting. This method is recommended for those wishing to integrate the
175    /// library into a game.
176    ///
177    /// This instance manages the lifetime of all [`View`]s and coordinates all painting, rendering,
178    /// network requests, and event dispatch.
179    ///
180    /// You should only call this once per process lifetime.
181    ///
182    /// You must set up your platform handlers (eg,
183    /// [`platform::set_gpu_driver`](crate::platform::set_gpu_driver),
184    /// [`platform::set_logger`](crate::platform::set_logger),
185    /// [`platform::enable_default_logger`](crate::platform::enable_default_logger),
186    /// [`platform::enable_platform_filesystem`](crate::platform::enable_platform_filesystem),
187    /// etc.) before calling this.
188    ///
189    /// You will also need to define a font loader before calling this --
190    /// currently the only way to do this is
191    /// [`platform::enable_platform_fontloader`](crate::platform::enable_platform_fontloader).
192    ///
193    /// You should not call this if you are using [`App::new`](crate::app::App::new),
194    /// it creates its own renderer and provides default implementations for
195    /// various platform handlers automatically.
196    pub fn create(config: Config) -> Result<Self, CreationError> {
197        let lib = config.lib();
198        let internal = unsafe { lib.ultralight().ulCreateRenderer(config.to_ul()) };
199        if internal.is_null() {
200            return Err(CreationError::NullReference);
201        }
202        let default_session =
203            unsafe { Session::from_raw(lib.clone(), lib.ultralight().ulDefaultSession(internal)) }?;
204
205        Ok(Self {
206            lib: lib.clone(),
207            internal,
208            need_to_destroy: true,
209            default_session,
210        })
211    }
212}
213
214impl Renderer {
215    /// Update timers and dispatch internal callbacks. You should call this often
216    /// from your main application loop.
217    pub fn update(&self) {
218        unsafe { self.lib.ultralight().ulUpdate(self.internal) };
219    }
220
221    /// Render all active views to their respective render-targets/surfaces.
222    ///
223    /// You should call this once per frame (usually in synchrony with the
224    /// monitor's refresh rate).
225    ///
226    /// [`View`]s are only repainted if they actually need painting.
227    /// (See [`View::needs_paint`](crate::view::View::needs_paint))
228    pub fn render(&self) {
229        unsafe { self.lib.ultralight().ulRender(self.internal) };
230    }
231
232    /// Attempt to release as much memory as possible.
233    /// Don't call this from any callbacks or driver code.
234    pub fn purge_memory(&self) {
235        unsafe { self.lib.ultralight().ulPurgeMemory(self.internal) };
236    }
237
238    /// Print detailed memory usage statistics to the log.
239    /// (See [`platform::set_logger`](crate::platform::set_logger) or
240    /// [`platform::enable_default_logger`](crate::platform::enable_default_logger))
241    pub fn log_memory_usage(&self) {
242        unsafe { self.lib.ultralight().ulLogMemoryUsage(self.internal) };
243    }
244
245    /// Create a Session to store local data in (such as cookies, local storage,
246    /// application cache, indexed db, etc).
247    ///
248    /// A default, persistent Session is already created for you. You only need to call this
249    /// if you want to create private, in-memory session or use a separate session for each
250    /// [`View`].
251    ///
252    /// # Arguments
253    /// * `is_persistent` - Whether or not to store the session on disk.
254    ///   Persistent sessions will be written to the path set in
255    ///   [`ConfigBuilder::cache_path`](crate::config::ConfigBuilder::cache_path).
256    /// * `name` -  A unique name for this session, this will be used to
257    ///   generate a unique disk path for persistent sessions.
258    pub fn create_session(
259        &self,
260        is_persistent: bool,
261        name: &str,
262    ) -> Result<Session, CreationError> {
263        unsafe { Session::create(self.lib.clone(), self.internal, is_persistent, name) }
264    }
265
266    /// Get the default Session. This session is persistent (backed to disk) and has the name
267    /// "default".
268    pub fn default_session(&self) -> &Session {
269        &self.default_session
270    }
271
272    /// Create a new View.
273    ///
274    /// # Arguments
275    /// * `width` - The initial width, in pixels.
276    /// * `height` - The initial height, in pixels.
277    /// * `config` - The configuration for the view.
278    /// * `session` - The session to store local data in. Passing [`None`] will
279    ///   use the default session.
280    pub fn create_view(
281        &self,
282        width: u32,
283        height: u32,
284        view_config: &ViewConfig,
285        session: Option<&Session>,
286    ) -> Option<View> {
287        unsafe { View::create(self.internal, width, height, view_config, session) }
288    }
289
290    /// Start the remote inspector server.
291    ///
292    /// While the remote inspector is active, Views that are loaded into this renderer
293    /// will be able to be remotely inspected from another Ultralight instance either locally
294    /// (another app on same machine) or remotely (over the network) by navigating a View to:
295    /// ```txt
296    ///  inspector://<address>:<port>
297    /// ```
298    ///
299    /// Returns `true` if the server was started successfully, `false` otherwise.
300    pub fn start_remote_inspector_server(
301        &self,
302        address: &str,
303        port: u16,
304    ) -> Result<bool, CreationError> {
305        unsafe {
306            let c_str = CString::new(address)?;
307            Ok(self.lib.ultralight().ulStartRemoteInspectorServer(
308                self.internal,
309                c_str.as_ptr(),
310                port,
311            ))
312        }
313    }
314
315    /// Notify the renderer that a display has refreshed (you should call this after vsync).
316    ///
317    /// This updates animations, smooth scroll, and `window.requestAnimationFrame()` for all Views
318    /// matching the display id.
319    pub fn refresh_display(&self, display_id: u32) {
320        unsafe {
321            self.lib
322                .ultralight()
323                .ulRefreshDisplay(self.internal, display_id)
324        }
325    }
326
327    /// Describe the details of a gamepad, to be used with FireGamepadEvent and related
328    /// events below. This can be called multiple times with the same index if the details change.
329    ///
330    /// # Arguments
331    /// * `index` - The unique index (or "connection slot") of the gamepad. For example,
332    ///   controller #1 would be "1", controller #2 would be "2" and so on.
333    /// * `id` - A string ID representing the device, this will be made available
334    ///   in JavaScript as gamepad.id
335    /// * `axis_count` - The number of axes on the device.
336    /// * `button_count` - The number of buttons on the device
337    pub fn set_gamepad_details(
338        &self,
339        index: u32,
340        id: &str,
341        axis_count: u32,
342        button_count: u32,
343    ) -> Result<(), CreationError> {
344        unsafe {
345            let ul_string_id = UlString::from_str(self.lib.clone(), id)?;
346
347            self.lib.ultralight().ulSetGamepadDetails(
348                self.internal,
349                index,
350                ul_string_id.to_ul(),
351                axis_count,
352                button_count,
353            );
354        }
355        Ok(())
356    }
357
358    /// Fire a gamepad event (connection / disconnection).
359    ///
360    /// Note:  The gamepad should first be described via [`set_gamepad_details`][Self::set_gamepad_details] before calling this
361    ///        function.
362    ///
363    /// See <https://developer.mozilla.org/en-US/docs/Web/API/Gamepad>
364    pub fn fire_gamepad_event(&self, event: GamepadEvent) -> Result<(), CreationError> {
365        unsafe {
366            self.lib
367                .ultralight()
368                .ulFireGamepadEvent(self.internal, event.to_ul())
369        };
370        Ok(())
371    }
372
373    /// Fire a gamepad axis event (to be called when an axis value is changed).
374    ///
375    /// Note:  The gamepad should be connected via a call to [`fire_gamepad_event`][Self::fire_gamepad_event] before calling this function.
376    ///
377    /// See <https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/axes>
378    pub fn fire_gamepad_axis_event(&self, event: GamepadAxisEvent) -> Result<(), CreationError> {
379        unsafe {
380            self.lib
381                .ultralight()
382                .ulFireGamepadAxisEvent(self.internal, event.to_ul())
383        };
384        Ok(())
385    }
386
387    /// Fire a gamepad button event (to be called when a button value is changed).
388    ///
389    /// Note:  The gamepad should be connected via a call to [`fire_gamepad_event`][Self::fire_gamepad_event] before calling this function.
390    ///
391    /// See <https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/axes>
392    pub fn fire_gamepad_button_event(
393        &self,
394        event: GamepadButtonEvent,
395    ) -> Result<(), CreationError> {
396        unsafe {
397            self.lib
398                .ultralight()
399                .ulFireGamepadButtonEvent(self.internal, event.to_ul())
400        };
401        Ok(())
402    }
403}
404
405impl Drop for Renderer {
406    fn drop(&mut self) {
407        if self.need_to_destroy {
408            unsafe {
409                self.lib.ultralight().ulDestroyRenderer(self.internal);
410            }
411        }
412    }
413}