Skip to main content

webfont_generator/result/
regeneration.rs

1use std::sync::Arc;
2
3use crate::{GlyphChange, RegenerationFiles};
4
5use super::GenerateWebfontsResult;
6
7impl GenerateWebfontsResult {
8    /// Rebuild on Tokio's blocking pool, preserving this result as a readable snapshot.
9    ///
10    /// Returns a replacement result on success. This result remains readable, but can no
11    /// longer regenerate after it has been replaced. Concurrent regeneration attempts on
12    /// the same result are rejected. On failure, its regeneration state is restored for
13    /// retry, including reconciliation of any partially completed variant output writes.
14    ///
15    /// Pass `Some(changes)` to supply explicit changes, or `None` to infer them from the
16    /// complete input membership. Requires generation with `incremental: true`.
17    ///
18    /// Unlike [`Self::regenerate_async`], this method borrows rather than consumes the
19    /// receiver. Prefer the consuming API when Rust ownership can enforce replacement.
20    /// This future is not cancellation-safe: dropping it does not stop blocking work or
21    /// filesystem writes. If that work succeeds, this receiver remains replaced even
22    /// though the new result can no longer be retrieved from the dropped future.
23    pub async fn regenerate_snapshot_async(
24        &self,
25        files: RegenerationFiles,
26        changes: Option<Vec<(String, GlyphChange)>>,
27    ) -> std::io::Result<Self> {
28        let state = self.take_regeneration_state()?;
29        let original_sources = Arc::clone(&self.source_files);
30        let is_variant = self.options.variants.is_some();
31        let original_state = Arc::clone(&self.regeneration_state);
32        let mut replacement = self.snapshot_for_regeneration(state);
33        tokio::task::spawn_blocking(move || -> std::io::Result<Self> {
34            let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| match changes {
35                Some(changes) => replacement.regenerate(&files, &changes),
36                None => replacement.regenerate_all(&files),
37            }));
38            if !matches!(result, Ok(Ok(()))) {
39                let mut state = replacement.take_regeneration_state().ok();
40                if let Some(state) = state.as_mut()
41                    && is_variant
42                    && !Arc::ptr_eq(&original_sources, &replacement.source_files)
43                {
44                    // Authoritative sources belong to the receiver, not this movable cache
45                    // state. Discard caches for the failed replacement's committed sources.
46                    state.variant_cache = None;
47                    // The write map describes actual successful writes, including partial
48                    // ones. A no-op retry must reconcile disk output with the old receiver.
49                    state.variant_write_pending = replacement.options.write_files;
50                }
51                *original_state.lock().unwrap() = state;
52            }
53            match result {
54                Ok(Ok(())) => Ok(replacement),
55                Ok(Err(error)) => Err(error),
56                Err(payload) => std::panic::resume_unwind(payload),
57            }
58        })
59        .await
60        .map_err(|error| {
61            std::io::Error::other(format!("Native webfont regeneration task failed: {error}"))
62        })?
63    }
64}