mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Basic multi-threading (#4366)
This commit is contained in:
parent
a68a241570
commit
7fa86eed0e
@ -217,6 +217,11 @@ pub struct SharedArgs {
|
|||||||
/// Arguments related to storage of packages in the system
|
/// Arguments related to storage of packages in the system
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub package_storage_args: PackageStorageArgs,
|
pub package_storage_args: PackageStorageArgs,
|
||||||
|
|
||||||
|
/// Number of parallel jobs spawned during compilation,
|
||||||
|
/// defaults to number of CPUs.
|
||||||
|
#[clap(long, short)]
|
||||||
|
pub jobs: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Arguments related to where packages are stored in the system.
|
/// Arguments related to where packages are stored in the system.
|
||||||
|
@ -56,6 +56,11 @@ pub struct SystemWorld {
|
|||||||
impl SystemWorld {
|
impl SystemWorld {
|
||||||
/// Create a new system world.
|
/// Create a new system world.
|
||||||
pub fn new(command: &SharedArgs) -> Result<Self, WorldCreationError> {
|
pub fn new(command: &SharedArgs) -> Result<Self, WorldCreationError> {
|
||||||
|
// Set up the thread pool.
|
||||||
|
if let Some(jobs) = command.jobs {
|
||||||
|
rayon::ThreadPoolBuilder::new().num_threads(jobs).build_global().ok();
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve the system-global input path.
|
// Resolve the system-global input path.
|
||||||
let input = match &command.input {
|
let input = match &command.input {
|
||||||
Input::Stdin => None,
|
Input::Stdin => None,
|
||||||
|
@ -145,6 +145,7 @@ pub fn deferred_image(image: Image) -> (Deferred<EncodedImage>, Option<ColorSpac
|
|||||||
/// whether the image has color.
|
/// whether the image has color.
|
||||||
///
|
///
|
||||||
/// Skips the alpha channel as that's encoded separately.
|
/// Skips the alpha channel as that's encoded separately.
|
||||||
|
#[typst_macros::time(name = "encode raster image")]
|
||||||
fn encode_raster_image(image: &RasterImage) -> (Vec<u8>, Filter, bool) {
|
fn encode_raster_image(image: &RasterImage) -> (Vec<u8>, Filter, bool) {
|
||||||
let dynamic = image.dynamic();
|
let dynamic = image.dynamic();
|
||||||
let channel_count = dynamic.color().channel_count();
|
let channel_count = dynamic.color().channel_count();
|
||||||
@ -169,6 +170,7 @@ fn encode_raster_image(image: &RasterImage) -> (Vec<u8>, Filter, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Encode an image's alpha channel if present.
|
/// Encode an image's alpha channel if present.
|
||||||
|
#[typst_macros::time(name = "encode alpha")]
|
||||||
fn encode_alpha(raster: &RasterImage) -> (Vec<u8>, Filter) {
|
fn encode_alpha(raster: &RasterImage) -> (Vec<u8>, Filter) {
|
||||||
let pixels: Vec<_> = raster
|
let pixels: Vec<_> = raster
|
||||||
.dynamic()
|
.dynamic()
|
||||||
@ -179,6 +181,7 @@ fn encode_alpha(raster: &RasterImage) -> (Vec<u8>, Filter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Encode an SVG into a chunk of PDF objects.
|
/// Encode an SVG into a chunk of PDF objects.
|
||||||
|
#[typst_macros::time(name = "encode svg")]
|
||||||
fn encode_svg(svg: &SvgImage) -> (Chunk, Ref) {
|
fn encode_svg(svg: &SvgImage) -> (Chunk, Ref) {
|
||||||
svg2pdf::to_chunk(svg.tree(), svg2pdf::ConversionOptions::default())
|
svg2pdf::to_chunk(svg.tree(), svg2pdf::ConversionOptions::default())
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
|||||||
|
|
||||||
use comemo::{Track, Tracked, TrackedMut, Validate};
|
use comemo::{Track, Tracked, TrackedMut, Validate};
|
||||||
use ecow::EcoVec;
|
use ecow::EcoVec;
|
||||||
|
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
|
||||||
|
|
||||||
use crate::diag::{SourceDiagnostic, SourceResult};
|
use crate::diag::{SourceDiagnostic, SourceResult};
|
||||||
use crate::foundations::{Styles, Value};
|
use crate::foundations::{Styles, Value};
|
||||||
@ -44,6 +45,46 @@ impl Engine<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Runs tasks on the engine in parallel.
|
||||||
|
pub fn parallelize<P, I, T, U, F>(&mut self, iter: P, f: F) -> impl Iterator<Item = U>
|
||||||
|
where
|
||||||
|
P: IntoIterator<IntoIter = I>,
|
||||||
|
I: Iterator<Item = T>,
|
||||||
|
T: Send,
|
||||||
|
U: Send,
|
||||||
|
F: Fn(&mut Engine, T) -> U + Send + Sync,
|
||||||
|
{
|
||||||
|
let Engine { world, introspector, traced, ref route, .. } = *self;
|
||||||
|
|
||||||
|
// We collect into a vector and then call `into_par_iter` instead of
|
||||||
|
// using `par_bridge` because it does not retain the ordering.
|
||||||
|
let work: Vec<T> = iter.into_iter().collect();
|
||||||
|
|
||||||
|
// Work in parallel.
|
||||||
|
let mut pairs: Vec<(U, Sink)> = Vec::with_capacity(work.len());
|
||||||
|
work.into_par_iter()
|
||||||
|
.map(|value| {
|
||||||
|
let mut sink = Sink::new();
|
||||||
|
let mut engine = Engine {
|
||||||
|
world,
|
||||||
|
introspector,
|
||||||
|
traced,
|
||||||
|
sink: sink.track_mut(),
|
||||||
|
route: route.clone(),
|
||||||
|
};
|
||||||
|
(f(&mut engine, value), sink)
|
||||||
|
})
|
||||||
|
.collect_into_vec(&mut pairs);
|
||||||
|
|
||||||
|
// Apply the subsinks to the outer sink.
|
||||||
|
for (_, sink) in &mut pairs {
|
||||||
|
let sink = std::mem::take(sink);
|
||||||
|
self.sink.extend(sink.delayed, sink.warnings, sink.values);
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs.into_iter().map(|(output, _)| output)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// May hold a span that is currently under inspection.
|
/// May hold a span that is currently under inspection.
|
||||||
@ -143,6 +184,22 @@ impl Sink {
|
|||||||
self.values.push((value, styles));
|
self.values.push((value, styles));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extend from another sink.
|
||||||
|
fn extend(
|
||||||
|
&mut self,
|
||||||
|
delayed: EcoVec<SourceDiagnostic>,
|
||||||
|
warnings: EcoVec<SourceDiagnostic>,
|
||||||
|
values: EcoVec<(Value, Option<Styles>)>,
|
||||||
|
) {
|
||||||
|
self.delayed.extend(delayed);
|
||||||
|
for warning in warnings {
|
||||||
|
self.warn(warning);
|
||||||
|
}
|
||||||
|
if let Some(remaining) = Self::MAX_VALUES.checked_sub(self.values.len()) {
|
||||||
|
self.values.extend(values.into_iter().take(remaining));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The route the engine took during compilation. This is used to detect
|
/// The route the engine took during compilation. This is used to detect
|
||||||
|
@ -13,7 +13,7 @@ use crate::foundations::{
|
|||||||
Packed, Resolve, Smart, StyleChain, Value,
|
Packed, Resolve, Smart, StyleChain, Value,
|
||||||
};
|
};
|
||||||
use crate::introspection::{
|
use crate::introspection::{
|
||||||
Counter, CounterDisplayElem, CounterKey, Locator, ManualPageCounter,
|
Counter, CounterDisplayElem, CounterKey, Locator, ManualPageCounter, SplitLocator,
|
||||||
};
|
};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, AlignElem, Alignment, Axes, ColumnsElem, Dir, Frame, HAlignment, Length,
|
Abs, AlignElem, Alignment, Axes, ColumnsElem, Dir, Frame, HAlignment, Length,
|
||||||
@ -348,14 +348,13 @@ impl Packed<PageElem> {
|
|||||||
/// a fragment consisting of multiple frames, one per output page of this
|
/// a fragment consisting of multiple frames, one per output page of this
|
||||||
/// page run.
|
/// page run.
|
||||||
#[typst_macros::time(name = "page", span = self.span())]
|
#[typst_macros::time(name = "page", span = self.span())]
|
||||||
pub fn layout(
|
pub fn layout<'a>(
|
||||||
&self,
|
&'a self,
|
||||||
engine: &mut Engine,
|
engine: &mut Engine,
|
||||||
locator: Locator,
|
locator: Locator<'a>,
|
||||||
styles: StyleChain,
|
styles: StyleChain<'a>,
|
||||||
page_counter: &mut ManualPageCounter,
|
|
||||||
extend_to: Option<Parity>,
|
extend_to: Option<Parity>,
|
||||||
) -> SourceResult<Vec<Page>> {
|
) -> SourceResult<PageLayout<'a>> {
|
||||||
let mut locator = locator.split();
|
let mut locator = locator.split();
|
||||||
|
|
||||||
// When one of the lengths is infinite the page fits its content along
|
// When one of the lengths is infinite the page fits its content along
|
||||||
@ -382,14 +381,6 @@ impl Packed<PageElem> {
|
|||||||
.resolve(styles)
|
.resolve(styles)
|
||||||
.relative_to(size);
|
.relative_to(size);
|
||||||
|
|
||||||
// Determine the binding.
|
|
||||||
let binding =
|
|
||||||
self.binding(styles)
|
|
||||||
.unwrap_or_else(|| match TextElem::dir_in(styles) {
|
|
||||||
Dir::LTR => Binding::Left,
|
|
||||||
_ => Binding::Right,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Realize columns.
|
// Realize columns.
|
||||||
let mut child = self.body().clone();
|
let mut child = self.body().clone();
|
||||||
let columns = self.columns(styles);
|
let columns = self.columns(styles);
|
||||||
@ -405,27 +396,70 @@ impl Packed<PageElem> {
|
|||||||
regions.root = true;
|
regions.root = true;
|
||||||
|
|
||||||
// Layout the child.
|
// Layout the child.
|
||||||
let mut frames = child
|
let frames = child
|
||||||
.layout(engine, locator.next(&self.span()), styles, regions)?
|
.layout(engine, locator.next(&self.span()), styles, regions)?
|
||||||
.into_frames();
|
.into_frames();
|
||||||
|
|
||||||
// Align the child to the pagebreak's parity.
|
Ok(PageLayout {
|
||||||
// Check for page count after adding the pending frames
|
page: self,
|
||||||
if extend_to
|
locator,
|
||||||
.is_some_and(|p| !p.matches(page_counter.physical().get() + frames.len()))
|
styles,
|
||||||
{
|
extend_to,
|
||||||
// Insert empty page after the current pages.
|
area,
|
||||||
let size = area.map(Abs::is_finite).select(area, Size::zero());
|
margin,
|
||||||
frames.push(Frame::hard(size));
|
two_sided,
|
||||||
|
frames,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let fill = self.fill(styles);
|
/// A prepared layout of a page run that can be finalized with access to the
|
||||||
let foreground = self.foreground(styles);
|
/// page counter.
|
||||||
let background = self.background(styles);
|
pub struct PageLayout<'a> {
|
||||||
let header_ascent = self.header_ascent(styles);
|
page: &'a Packed<PageElem>,
|
||||||
let footer_descent = self.footer_descent(styles);
|
locator: SplitLocator<'a>,
|
||||||
let numbering = self.numbering(styles);
|
styles: StyleChain<'a>,
|
||||||
let number_align = self.number_align(styles);
|
extend_to: Option<Parity>,
|
||||||
|
area: Size,
|
||||||
|
margin: Sides<Abs>,
|
||||||
|
two_sided: bool,
|
||||||
|
frames: Vec<Frame>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PageLayout<'_> {
|
||||||
|
/// Finalize the layout with access to the next page counter.
|
||||||
|
#[typst_macros::time(name = "finalize page", span = self.page.span())]
|
||||||
|
pub fn finalize(
|
||||||
|
mut self,
|
||||||
|
engine: &mut Engine,
|
||||||
|
page_counter: &mut ManualPageCounter,
|
||||||
|
) -> SourceResult<Vec<Page>> {
|
||||||
|
let styles = self.styles;
|
||||||
|
|
||||||
|
// Align the child to the pagebreak's parity.
|
||||||
|
// Check for page count after adding the pending frames
|
||||||
|
if self.extend_to.is_some_and(|p| {
|
||||||
|
!p.matches(page_counter.physical().get() + self.frames.len())
|
||||||
|
}) {
|
||||||
|
// Insert empty page after the current pages.
|
||||||
|
let size = self.area.map(Abs::is_finite).select(self.area, Size::zero());
|
||||||
|
self.frames.push(Frame::hard(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
let fill = self.page.fill(styles);
|
||||||
|
let foreground = self.page.foreground(styles);
|
||||||
|
let background = self.page.background(styles);
|
||||||
|
let header_ascent = self.page.header_ascent(styles);
|
||||||
|
let footer_descent = self.page.footer_descent(styles);
|
||||||
|
let numbering = self.page.numbering(styles);
|
||||||
|
let number_align = self.page.number_align(styles);
|
||||||
|
let binding =
|
||||||
|
self.page
|
||||||
|
.binding(styles)
|
||||||
|
.unwrap_or_else(|| match TextElem::dir_in(styles) {
|
||||||
|
Dir::LTR => Binding::Left,
|
||||||
|
_ => Binding::Right,
|
||||||
|
});
|
||||||
|
|
||||||
// Construct the numbering (for header or footer).
|
// Construct the numbering (for header or footer).
|
||||||
let numbering_marginal = numbering.as_ref().map(|numbering| {
|
let numbering_marginal = numbering.as_ref().map(|numbering| {
|
||||||
@ -440,7 +474,7 @@ impl Packed<PageElem> {
|
|||||||
both,
|
both,
|
||||||
)
|
)
|
||||||
.pack()
|
.pack()
|
||||||
.spanned(self.span());
|
.spanned(self.page.span());
|
||||||
|
|
||||||
// We interpret the Y alignment as selecting header or footer
|
// We interpret the Y alignment as selecting header or footer
|
||||||
// and then ignore it for aligning the actual number.
|
// and then ignore it for aligning the actual number.
|
||||||
@ -451,8 +485,8 @@ impl Packed<PageElem> {
|
|||||||
counter
|
counter
|
||||||
});
|
});
|
||||||
|
|
||||||
let header = self.header(styles);
|
let header = self.page.header(styles);
|
||||||
let footer = self.footer(styles);
|
let footer = self.page.footer(styles);
|
||||||
let (header, footer) = if matches!(number_align.y(), Some(OuterVAlignment::Top)) {
|
let (header, footer) = if matches!(number_align.y(), Some(OuterVAlignment::Top)) {
|
||||||
(
|
(
|
||||||
header.as_ref().unwrap_or(&numbering_marginal),
|
header.as_ref().unwrap_or(&numbering_marginal),
|
||||||
@ -466,16 +500,16 @@ impl Packed<PageElem> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Post-process pages.
|
// Post-process pages.
|
||||||
let mut pages = Vec::with_capacity(frames.len());
|
let mut pages = Vec::with_capacity(self.frames.len());
|
||||||
for mut frame in frames {
|
for mut frame in self.frames {
|
||||||
// The padded width of the page's content without margins.
|
// The padded width of the page's content without margins.
|
||||||
let pw = frame.width();
|
let pw = frame.width();
|
||||||
|
|
||||||
// If two sided, left becomes inside and right becomes outside.
|
// If two sided, left becomes inside and right becomes outside.
|
||||||
// Thus, for left-bound pages, we want to swap on even pages and
|
// Thus, for left-bound pages, we want to swap on even pages and
|
||||||
// for right-bound pages, we want to swap on odd pages.
|
// for right-bound pages, we want to swap on odd pages.
|
||||||
let mut margin = margin;
|
let mut margin = self.margin;
|
||||||
if two_sided && binding.swap(page_counter.physical()) {
|
if self.two_sided && binding.swap(page_counter.physical()) {
|
||||||
std::mem::swap(&mut margin.left, &mut margin.right);
|
std::mem::swap(&mut margin.left, &mut margin.right);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -511,7 +545,7 @@ impl Packed<PageElem> {
|
|||||||
let sub = content
|
let sub = content
|
||||||
.clone()
|
.clone()
|
||||||
.styled(AlignElem::set_alignment(align))
|
.styled(AlignElem::set_alignment(align))
|
||||||
.layout(engine, locator.next(&content.span()), styles, pod)?
|
.layout(engine, self.locator.next(&content.span()), styles, pod)?
|
||||||
.into_frame();
|
.into_frame();
|
||||||
|
|
||||||
if ptr::eq(marginal, header) || ptr::eq(marginal, background) {
|
if ptr::eq(marginal, header) || ptr::eq(marginal, background) {
|
||||||
|
@ -79,29 +79,32 @@ impl Packed<DocumentElem> {
|
|||||||
locator: Locator,
|
locator: Locator,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Document> {
|
) -> SourceResult<Document> {
|
||||||
let mut pages = Vec::with_capacity(self.children().len());
|
|
||||||
let mut page_counter = ManualPageCounter::new();
|
|
||||||
|
|
||||||
let children = self.children();
|
let children = self.children();
|
||||||
let mut iter = children.chain(&styles).peekable();
|
let mut peekable = children.chain(&styles).peekable();
|
||||||
let mut locator = locator.split();
|
let mut locator = locator.split();
|
||||||
|
|
||||||
while let Some((child, styles)) = iter.next() {
|
let iter = std::iter::from_fn(|| {
|
||||||
if let Some(page) = child.to_packed::<PageElem>() {
|
let (child, styles) = peekable.next()?;
|
||||||
let extend_to = iter
|
let extend_to = peekable
|
||||||
.peek()
|
.peek()
|
||||||
.and_then(|(next, _)| *next.to_packed::<PageElem>()?.clear_to()?);
|
.and_then(|(next, _)| *next.to_packed::<PageElem>()?.clear_to()?);
|
||||||
let run = page.layout(
|
let locator = locator.next(&child.span());
|
||||||
engine,
|
Some((child, styles, extend_to, locator))
|
||||||
locator.next(&page.span()),
|
});
|
||||||
styles,
|
|
||||||
&mut page_counter,
|
let layouts =
|
||||||
extend_to,
|
engine.parallelize(iter, |engine, (child, styles, extend_to, locator)| {
|
||||||
)?;
|
if let Some(page) = child.to_packed::<PageElem>() {
|
||||||
pages.extend(run);
|
page.layout(engine, locator, styles, extend_to)
|
||||||
} else {
|
} else {
|
||||||
bail!(child.span(), "unexpected document child");
|
bail!(child.span(), "unexpected document child");
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut page_counter = ManualPageCounter::new();
|
||||||
|
let mut pages = Vec::with_capacity(self.children().len());
|
||||||
|
for result in layouts {
|
||||||
|
pages.extend(result?.finalize(engine, &mut page_counter)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Document {
|
Ok(Document {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user