///|
pub struct WindowImageTaskStarter {
priv start_fn : (
@render.RendererImageLoadRequest,
(@render.RendererImageLoadCompletion) -> Unit,
) -> (() -> Unit)?
}
///|
pub fn WindowImageTaskStarter::new(
start~ : (
@render.RendererImageLoadRequest,
(@render.RendererImageLoadCompletion) -> Unit,
) -> (() -> Unit)?,
) -> WindowImageTaskStarter {
{ start_fn: start, }
}
///|
pub fn WindowImageTaskStarter::immediate() -> WindowImageTaskStarter {
WindowImageTaskStarter::new(start=(request, complete) => {
complete(request.load())
None
})
}
///|
/// A starter that resolves remote sources on a worker and everything else
/// synchronously.
///
/// The completion callback must never run on a worker thread: it mutates
/// `WindowImageTasks` bookkeeping and the renderer's image lifecycle, both of
/// which belong to the UI thread. `deferred` therefore takes two host hooks
/// and keeps the ordering explicit:
///
/// * `spawn` runs a closure off the caller's thread and returns an optional
/// cancel handle — an OS thread, a thread pool, a GCD queue.
/// * `post` marshals a closure back onto the UI thread — a main-queue
/// dispatch. The worker only ever touches `post`.
///
/// Non-remote sources complete inline, so a local file never leaves the UI
/// thread. Without host hooks a platform can keep using `immediate`, which
/// makes this a purely additive seam.
pub fn WindowImageTaskStarter::deferred(
fetch~ : (String) -> Bytes?,
spawn~ : (() -> Unit) -> (() -> Unit)?,
post~ : (() -> Unit) -> Unit,
) -> WindowImageTaskStarter {
WindowImageTaskStarter::new(start=(request, complete) => {
let source = request.source()
if !window_image_source_is_remote(source) {
complete(request.load())
return None
}
spawn(() => {
match fetch(source) {
Some(bytes) => post(() => complete(request.load_bytes(bytes)))
// A failed fetch falls back to the default path, which surfaces the
// failure through the renderer's image lifecycle rather than stalling
// the request in the loading state.
None => post(() => complete(request.load()))
}
})
})
}
///|
/// True for sources a platform must reach over the network rather than through
/// the filesystem.
pub fn window_image_source_is_remote(source : String) -> Bool {
source.has_prefix("http://") || source.has_prefix("https://")
}
///|
/// App-installed starter that replaces the backends' built-in default.
///
/// Platform backends create one image task workspace per window and otherwise
/// hardcode `immediate()`. An application that can resolve sources the backend
/// cannot — remote `https:` assets fetched through an async runtime, say —
/// installs its starter here before launching, and every backend picks it up
/// without each app needing a bespoke host option. An explicitly passed
/// `image_task_starter` still wins, which keeps tests and multi-starter hosts
/// unaffected.
let window_image_task_starter_override : Ref[WindowImageTaskStarter?] = Ref(
None,
)
///|
/// Install the starter used by backends that were not given one explicitly.
pub fn window_image_task_starter_install(
starter : WindowImageTaskStarter,
) -> Unit {
window_image_task_starter_override.val = Some(starter)
}
///|
/// Drop the installed starter, restoring the synchronous default.
pub fn window_image_task_starter_reset() -> Unit {
window_image_task_starter_override.val = None
}
///|
/// The starter a backend should use when it has none of its own: the installed
/// starter when present, otherwise the synchronous `immediate` starter.
pub fn window_image_task_starter_default() -> WindowImageTaskStarter {
match window_image_task_starter_override.val {
Some(starter) => starter
None => WindowImageTaskStarter::immediate()
}
}
///|
priv struct WindowImageTask {
token : @render.RendererImageLoadToken
cancel : (() -> Unit)?
}
///|
/// Per-window owner for cancellable image I/O only. Resource state, cache
/// residency, staleness, and repaint decisions remain in RendererSession.
pub struct WindowImageTasks {
priv starter : WindowImageTaskStarter
priv request_redraw : () -> Unit
priv mut tasks : Array[WindowImageTask]
priv mut scheduled_since_observation : Int
priv mut disposed : Bool
}
///|
pub fn WindowImageTasks::new(
starter? : WindowImageTaskStarter = WindowImageTaskStarter::immediate(),
request_redraw~ : () -> Unit,
) -> WindowImageTasks {
{
starter,
request_redraw,
tasks: [],
scheduled_since_observation: 0,
disposed: false,
}
}
///|
pub fn WindowImageTasks::schedule(
self : WindowImageTasks,
renderer : @render.RendererSession,
request : @render.RendererImageLoadRequest,
) -> Bool {
if self.disposed || self.contains(request.token()) {
return false
}
self.tasks.push({ token: request.token(), cancel: None, })
self.scheduled_since_observation = self.scheduled_since_observation + 1
let cancel = (self.starter.start_fn)(request, completion => {
if self.remove(completion.token()) {
match renderer.apply_image_load_completion(completion) {
Applied(true) => (self.request_redraw)()
Applied(false) | Stale | Disposed => ()
}
}
})
self.set_cancel(request.token(), cancel)
true
}
///|
pub fn WindowImageTasks::take_scheduled_count(self : WindowImageTasks) -> Int {
let count = self.scheduled_since_observation
self.scheduled_since_observation = 0
count
}
///|
pub fn WindowImageTasks::pending_count(self : WindowImageTasks) -> Int {
if self.disposed {
0
} else {
self.tasks.length()
}
}
///|
pub fn WindowImageTasks::cancel(self : WindowImageTasks) -> Int {
if self.disposed {
return 0
}
let tasks = self.tasks
self.tasks = []
for task in tasks {
match task.cancel {
Some(cancel) => cancel()
None => ()
}
}
tasks.length()
}
///|
pub fn WindowImageTasks::dispose(self : WindowImageTasks) -> Unit {
if !self.disposed {
ignore(self.cancel())
self.disposed = true
}
}
///|
fn WindowImageTasks::contains(
self : WindowImageTasks,
token : @render.RendererImageLoadToken,
) -> Bool {
self.tasks.any(task => task.token == token)
}
///|
fn WindowImageTasks::remove(
self : WindowImageTasks,
token : @render.RendererImageLoadToken,
) -> Bool {
let mut removed = false
let kept : Array[WindowImageTask] = []
for task in self.tasks {
if !removed && task.token == token {
removed = true
} else {
kept.push(task)
}
}
self.tasks = kept
removed
}
///|
fn WindowImageTasks::set_cancel(
self : WindowImageTasks,
token : @render.RendererImageLoadToken,
cancel : (() -> Unit)?,
) -> Unit {
for index, task in self.tasks {
if task.token == token {
self.tasks[index] = { token, cancel, }
return
}
}
}