1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
//! The `Renderer` manages all [`View`]s  and coordinates painting,
//! network requests, and event dispatch
//!
//! [`Renderer`] should be used when you want to access GPU internals and textures
//! to integerate with your own rendering pipeline.
//!
//! Before creating a renderer [`Renderer::create`] you must supply a custom
//! [`GpuDriver`](crate::gpu_driver::GpuDriver) in
//! [`platform::set_gpu_driver`](crate::platform::set_gpu_driver).
use crate::{
    config::Config,
    error::CreationError,
    string::UlString,
    view::{View, ViewConfig},
};

/// A Session stores local data such as cookies, local storage, and application
/// cache for one or more [`View`]s.
/// (See [`Renderer::create_session`](crate::renderer::Renderer::create_session))
pub struct Session {
    internal: ul_sys::ULSession,
    need_to_destroy: bool,

    is_persistent: bool,
    name: String,
    id: u64,
    disk_path: String,
}

impl Session {
    /// Internal function helper to create a session.
    /// (See [`Renderer::create_session`](crate::renderer::Renderer::create_session))
    pub(crate) unsafe fn create(
        renderer: ul_sys::ULRenderer,
        is_persistent: bool,
        name: &str,
    ) -> Result<Self, CreationError> {
        let ul_string_name = UlString::from_str(name)?;
        let internal = ul_sys::ulCreateSession(renderer, is_persistent, ul_string_name.to_ul());

        if internal.is_null() {
            return Err(CreationError::NullReference);
        }

        let id = ul_sys::ulSessionGetId(internal);
        let disk_path = UlString::copy_raw_to_string(ul_sys::ulSessionGetDiskPath(internal))?;

        Ok(Self {
            internal,
            need_to_destroy: true,

            is_persistent,
            name: name.to_string(),
            id,
            disk_path,
        })
    }

    /// Helper internal function to allow getting a reference to a managed
    /// session.
    pub(crate) unsafe fn from_raw(raw: ul_sys::ULSession) -> Result<Self, CreationError> {
        if raw.is_null() {
            return Err(CreationError::NullReference);
        }

        let id = ul_sys::ulSessionGetId(raw);
        let disk_path = UlString::copy_raw_to_string(ul_sys::ulSessionGetDiskPath(raw))?;
        let name = UlString::copy_raw_to_string(ul_sys::ulSessionGetName(raw))?;
        let is_persistent = ul_sys::ulSessionIsPersistent(raw);

        Ok(Self {
            internal: raw,
            need_to_destroy: false,

            is_persistent,
            name,
            id,
            disk_path,
        })
    }

    /// Returns the underlying [`ul_sys::ULSession`] struct, to be used locally for
    /// calling the underlying C API.
    pub(crate) unsafe fn to_ul(&self) -> ul_sys::ULSession {
        self.internal
    }
}

impl Session {
    /// A unique numeric ID identifying this session.
    pub fn id(&self) -> u64 {
        self.id
    }

    /// A unique name identifying this session.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// The disk path of this session (only valid for persistent sessions).
    pub fn disk_path(&self) -> &str {
        &self.disk_path
    }

    /// Whether or not this session is written to disk.
    pub fn is_persistent(&self) -> bool {
        self.is_persistent
    }
}

impl Drop for Session {
    fn drop(&mut self) {
        if self.need_to_destroy {
            unsafe {
                ul_sys::ulDestroySession(self.internal);
            }
        }
    }
}

/// The `Renderer` manages all [`View`]s  and coordinates painting,
/// network requests, and event dispatch
///
/// You don't have to create this instance directly if you use the AppCore API.
/// The [`App`](crate::app::App) struct will automatically create a `Renderer`
/// and perform all rendering within its run loop.
pub struct Renderer {
    internal: ul_sys::ULRenderer,

    need_to_destroy: bool,

    default_session: Session,
}

impl Renderer {
    /// Internal helper to get a reference to the underlying Renderer.
    pub(crate) unsafe fn from_raw(raw: ul_sys::ULRenderer) -> Result<Self, CreationError> {
        let raw_default_session = ul_sys::ulDefaultSession(raw);
        if raw_default_session.is_null() {
            return Err(CreationError::NullReference);
        }
        let default_session = Session::from_raw(raw_default_session)?;

        Ok(Self {
            internal: raw,
            need_to_destroy: false,
            default_session,
        })
    }

    /// Create the Ultralight Renderer directly.
    ///
    /// Unlike [`App::new`](crate::app::App::new), this does not use any native windows for drawing and allows you to manage
    /// your own runloop and painting. This method is recommended for those wishing to integrate the
    /// library into a game.
    ///
    /// This instance manages the lifetime of all [`View`]s and coordinates all painting, rendering,
    /// network requests, and event dispatch.
    ///
    /// You should only call this once per process lifetime.
    ///
    /// You shoud set up your platform handlers (eg,
    /// [`platform::set_gpu_driver`](crate::platform::set_gpu_driver),
    /// [`platform::set_logger`](crate::platform::set_logger),
    /// [`platform::enable_default_logger`](crate::platform::enable_default_logger),
    /// [`platform::enable_platform_filesystem`](crate::platform::enable_platform_filesystem),
    /// etc.) before calling this.
    ///
    /// You will also need to define a font loader before calling this --
    /// currently the only way to do this is
    /// [`platform::enable_platform_fontloader`](crate::platform::enable_platform_fontloader).
    ///
    /// You should not call this if you are using [`App::new`](crate::app::App::new),
    /// it creates its own renderer and provides default implementations for
    /// various platform handlers automatically.
    pub fn create(config: Config) -> Result<Self, CreationError> {
        let internal = unsafe { ul_sys::ulCreateRenderer(config.to_ul()) };
        if internal.is_null() {
            return Err(CreationError::NullReference);
        }
        let default_session = unsafe { Session::from_raw(ul_sys::ulDefaultSession(internal)) }?;

        Ok(Self {
            internal,
            need_to_destroy: true,
            default_session,
        })
    }
}

impl Renderer {
    /// Update timers and dispatch internal callbacks. You should call this often
    /// from your main application loop.
    pub fn update(&self) {
        unsafe { ul_sys::ulUpdate(self.internal) };
    }

    /// Render all active views to their respective render-targets/surfaces.
    ///
    /// You should call this once per frame (usually in synchrony with the
    /// monitor's refresh rate).
    ///
    /// [`View`]s are only repainted if they actually need painting.
    /// (See [`View::needs_paint`](crate::view::View::needs_paint))
    pub fn render(&self) {
        unsafe { ul_sys::ulRender(self.internal) };
    }

    /// Attempt to release as much memory as possible.
    /// Don't call this from any callbacks or driver code.
    pub fn purge_memory(&self) {
        unsafe { ul_sys::ulPurgeMemory(self.internal) };
    }

    /// Print detailed memory usage statistics to the log.
    /// (See [`platform::set_logger`](crate::platform::set_logger) or
    /// [`platform::enable_default_logger`](crate::platform::enable_default_logger))
    pub fn log_memory_usage(&self) {
        unsafe { ul_sys::ulLogMemoryUsage(self.internal) };
    }

    /// Create a Session to store local data in (such as cookies, local storage,
    /// application cache, indexed db, etc).
    ///
    /// A default, persistent Session is already created for you. You only need to call this
    /// if you want to create private, in-memory session or use a separate session for each
    /// [`View`].
    ///
    /// # Arguments
    /// * `is_persistent` - Whether or not to store the session on disk.
    /// Persistent sessions will be written to the path set in
    /// [`ConfigBuilder::cache_path`](crate::config::ConfigBuilder::cache_path).
    /// * `name` -  A unique name for this session, this will be used to
    /// generate a unique disk path for persistent sessions.
    pub fn create_session(
        &self,
        is_persistent: bool,
        name: &str,
    ) -> Result<Session, CreationError> {
        unsafe { Session::create(self.internal, is_persistent, name) }
    }

    /// Get the default Session. This session is persistent (backed to disk) and has the name
    /// "default".
    pub fn default_session(&self) -> &Session {
        &self.default_session
    }

    /// Create a new View.
    ///
    /// # Arguments
    /// * `width` - The initial width, in pixels.
    /// * `height` - The initial height, in pixels.
    /// * `config` - The configuration for the view.
    /// * `session` - The session to store local data in. Passing [`None`] will
    /// use the default session.
    pub fn create_view(
        &self,
        width: u32,
        height: u32,
        view_config: &ViewConfig,
        session: Option<&Session>,
    ) -> Option<View> {
        unsafe { View::create(self.internal, width, height, view_config, session) }
    }
}

impl Drop for Renderer {
    fn drop(&mut self) {
        if self.need_to_destroy {
            unsafe {
                ul_sys::ulDestroyRenderer(self.internal);
            }
        }
    }
}