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}