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}