summaryrefslogtreecommitdiff
path: root/crates/typst-cli/src
diff options
context:
space:
mode:
authorSébastien d'Herbais de Thun <sebastien.d.herbais@gmail.com>2023-12-18 12:32:53 +0100
committerGitHub <noreply@github.com>2023-12-18 12:32:53 +0100
commit08225e42d8748802b11b056964cb7763e7da8135 (patch)
tree7a83c55bda39becea7c095568b234fca756d8010 /crates/typst-cli/src
parentf3c39ac84a83c10afa7ed439343fcfbbfe2e3ef7 (diff)
Parallel export (#2989)
Diffstat (limited to 'crates/typst-cli/src')
-rw-r--r--crates/typst-cli/src/compile.rs82
-rw-r--r--crates/typst-cli/src/world.rs16
2 files changed, 55 insertions, 43 deletions
diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs
index dd15c8fc..5916f18b 100644
--- a/crates/typst-cli/src/compile.rs
+++ b/crates/typst-cli/src/compile.rs
@@ -4,7 +4,9 @@ use std::path::{Path, PathBuf};
use chrono::{Datelike, Timelike};
use codespan_reporting::diagnostic::{Diagnostic, Label};
use codespan_reporting::term::{self, termcolor};
-use ecow::eco_format;
+use ecow::{eco_format, EcoString};
+use parking_lot::RwLock;
+use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
use termcolor::{ColorChoice, StandardStream};
use typst::diag::{bail, At, Severity, SourceDiagnostic, StrResult};
use typst::eval::Tracer;
@@ -214,39 +216,48 @@ fn export_image(
// first page should be numbered "001" if there are between 100 and
// 999 pages.
let width = 1 + document.pages.len().checked_ilog10().unwrap_or(0) as usize;
- let mut storage;
let cache = world.export_cache();
- for (i, frame) in document.pages.iter().enumerate() {
- let path = if numbered {
- storage = string.replace("{n}", &format!("{:0width$}", i + 1));
- Path::new(&storage)
- } else {
- output.as_path()
- };
-
- // If we are not watching, don't use the cache.
- // If the frame is in the cache, skip it.
- // If the file does not exist, always create it.
- if watching && cache.is_cached(i, frame) && path.exists() {
- continue;
- }
- match fmt {
- ImageExportFormat::Png => {
- let pixmap =
- typst_render::render(frame, command.ppi / 72.0, Color::WHITE);
- pixmap
- .save_png(path)
- .map_err(|err| eco_format!("failed to write PNG file ({err})"))?;
+ // The results are collected in a `Vec<()>` which does not allocate.
+ document
+ .pages
+ .par_iter()
+ .enumerate()
+ .map(|(i, frame)| {
+ let storage;
+ let path = if numbered {
+ storage = string.replace("{n}", &format!("{:0width$}", i + 1));
+ Path::new(&storage)
+ } else {
+ output.as_path()
+ };
+
+ // If we are not watching, don't use the cache.
+ // If the frame is in the cache, skip it.
+ // If the file does not exist, always create it.
+ if watching && cache.is_cached(i, frame) && path.exists() {
+ return Ok(());
}
- ImageExportFormat::Svg => {
- let svg = typst_svg::svg(frame);
- fs::write(path, svg.as_bytes())
- .map_err(|err| eco_format!("failed to write SVG file ({err})"))?;
+
+ match fmt {
+ ImageExportFormat::Png => {
+ let pixmap =
+ typst_render::render(frame, command.ppi / 72.0, Color::WHITE);
+ pixmap
+ .save_png(path)
+ .map_err(|err| eco_format!("failed to write PNG file ({err})"))?;
+ }
+ ImageExportFormat::Svg => {
+ let svg = typst_svg::svg(frame);
+ fs::write(path, svg.as_bytes())
+ .map_err(|err| eco_format!("failed to write SVG file ({err})"))?;
+ }
}
- }
- }
+
+ Ok(())
+ })
+ .collect::<Result<Vec<()>, EcoString>>()?;
Ok(())
}
@@ -260,26 +271,27 @@ fn export_image(
/// complexity and memory usage of such a cache.
pub struct ExportCache {
/// The hashes of last compilation's frames.
- pub cache: Vec<u128>,
+ pub cache: RwLock<Vec<u128>>,
}
impl ExportCache {
/// Creates a new export cache.
pub fn new() -> Self {
- Self { cache: Vec::with_capacity(32) }
+ Self { cache: RwLock::new(Vec::with_capacity(32)) }
}
/// Returns true if the entry is cached and appends the new hash to the
/// cache (for the next compilation).
- pub fn is_cached(&mut self, i: usize, frame: &Frame) -> bool {
+ pub fn is_cached(&self, i: usize, frame: &Frame) -> bool {
let hash = typst::util::hash128(frame);
- if i >= self.cache.len() {
- self.cache.push(hash);
+ let mut cache = self.cache.upgradable_read();
+ if i >= cache.len() {
+ cache.with_upgraded(|cache| cache.push(hash));
return false;
}
- std::mem::replace(&mut self.cache[i], hash) == hash
+ cache.with_upgraded(|cache| std::mem::replace(&mut cache[i], hash) == hash)
}
}
diff --git a/crates/typst-cli/src/world.rs b/crates/typst-cli/src/world.rs
index cd4244fc..095458df 100644
--- a/crates/typst-cli/src/world.rs
+++ b/crates/typst-cli/src/world.rs
@@ -1,11 +1,12 @@
use std::collections::HashMap;
use std::path::{Path, PathBuf};
-use std::sync::{OnceLock, RwLock};
+use std::sync::OnceLock;
use std::{fs, mem};
use chrono::{DateTime, Datelike, Local};
use comemo::Prehashed;
use ecow::eco_format;
+use parking_lot::Mutex;
use typst::diag::{FileError, FileResult, StrResult};
use typst::foundations::{Bytes, Datetime, Dict, IntoValue};
use typst::syntax::{FileId, Source, VirtualPath};
@@ -34,7 +35,7 @@ pub struct SystemWorld {
/// Locations of and storage for lazily loaded fonts.
fonts: Vec<FontSlot>,
/// Maps file ids to source files and buffers.
- slots: RwLock<HashMap<FileId, FileSlot>>,
+ slots: Mutex<HashMap<FileId, FileSlot>>,
/// The current datetime if requested. This is stored here to ensure it is
/// always the same within one compilation. Reset between compilations.
now: OnceLock<DateTime<Local>>,
@@ -89,7 +90,7 @@ impl SystemWorld {
library: Prehashed::new(library),
book: Prehashed::new(searcher.book),
fonts: searcher.fonts,
- slots: RwLock::new(HashMap::new()),
+ slots: Mutex::new(HashMap::new()),
now: OnceLock::new(),
export_cache: ExportCache::new(),
})
@@ -114,7 +115,6 @@ impl SystemWorld {
pub fn dependencies(&mut self) -> impl Iterator<Item = PathBuf> + '_ {
self.slots
.get_mut()
- .unwrap()
.values()
.filter(|slot| slot.accessed())
.filter_map(|slot| system_path(&self.root, slot.id).ok())
@@ -122,7 +122,7 @@ impl SystemWorld {
/// Reset the compilation state in preparation of a new compilation.
pub fn reset(&mut self) {
- for slot in self.slots.get_mut().unwrap().values_mut() {
+ for slot in self.slots.get_mut().values_mut() {
slot.reset();
}
self.now.take();
@@ -140,8 +140,8 @@ impl SystemWorld {
}
/// Gets access to the export cache.
- pub fn export_cache(&mut self) -> &mut ExportCache {
- &mut self.export_cache
+ pub fn export_cache(&self) -> &ExportCache {
+ &self.export_cache
}
}
@@ -192,7 +192,7 @@ impl SystemWorld {
where
F: FnOnce(&mut FileSlot) -> T,
{
- let mut map = self.slots.write().unwrap();
+ let mut map = self.slots.lock();
f(map.entry(id).or_insert_with(|| FileSlot::new(id)))
}
}