Skip to main content

typst_pack/
limits.rs

1use std::fmt;
2use std::marker::PhantomData;
3
4const MAX_RESOURCES: usize = 7;
5
6/// A resource identifier from one operation-specific profile.
7#[derive(Clone, Copy, Eq, Hash, PartialEq)]
8pub struct ResourceKind<const PROFILE: u8>(u8);
9
10impl<const PROFILE: u8> ResourceKind<PROFILE> {
11    pub(crate) const fn new(index: u8) -> Self {
12        Self(index)
13    }
14}
15
16impl<const PROFILE: u8> fmt::Debug for ResourceKind<PROFILE> {
17    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
18        formatter.write_str(resource_name(PROFILE, self.0))
19    }
20}
21
22const fn operation_name(profile: u8) -> &'static str {
23    match profile {
24        0 => "filesystem project reading",
25        1 => "filesystem Package Tree reading",
26        2 => "filesystem Font Catalog reading",
27        3 => "compilation",
28        4 => "Pack Archive Encode",
29        5 => "Pack Archive Decode",
30        6 => "Package Archive Expansion",
31        7 => "Pack Archive Read",
32        8 => "OpenDAL recursive read",
33        9 => "OpenDAL Project Read",
34        10 => "OpenDAL Font Read",
35        11 => "OpenDAL Package Tree Read",
36        12 => "OpenDAL Package Archive Read",
37        13 => "OpenDAL Package Read",
38        _ => "resource-limited operation",
39    }
40}
41
42const fn resource_name(profile: u8, index: u8) -> &'static str {
43    match (profile, index) {
44        (0, 0) | (1, 0) | (2, 0) => "VisitedEntries",
45        (0, 1) | (1, 1) => "SelectedFiles",
46        (0, 2) => "RootPolicyBytes",
47        (0, 3) | (1, 2) => "SelectedFileBytes",
48        (0, 4) => "TotalSelectedBytes",
49        (1, 3) => "PackageTreeBytes",
50        (2, 1) => "AcceptedContainers",
51        (2, 2) | (10, 4) => "ContainerBytes",
52        (2, 3) => "TotalAcceptedBytes",
53        (3, 0) => "SourcePages",
54        (3, 1) => "Artifacts",
55        (3, 2) => "PixelsPerArtifact",
56        (3, 3) => "TotalPixels",
57        (3, 4) => "ArtifactBytes",
58        (3, 5) => "RetainedArtifactBytes",
59        (3, 6) => "ExportWorkers",
60        (4, 0) | (5, 0) | (7, 0) | (12, 0) => "ArchiveBytes",
61        (4, 1) | (5, 1) | (6, 1) => "Members",
62        (4, 2) => "GeneratedMemberNameBytes",
63        (4, 3) | (5, 3) => "ManifestBytes",
64        (4, 4) | (5, 4) | (6, 3) => "MemberBytes",
65        (4, 5) | (5, 5) => "TotalContentBytes",
66        (5, 2) => "RawMemberNameBytes",
67        (6, 0) => "CompressedArchiveBytes",
68        (6, 2) => "MemberNameBytes",
69        (6, 4) => "TotalExpandedBytes",
70        (8, 0) | (9, 0) | (10, 0) | (11, 0) => "ListedEntries",
71        (8, 1) | (9, 1) | (10, 1) | (11, 1) => "ListedPathBytes",
72        (8, 2) | (9, 2) | (10, 2) | (11, 2) => "TotalListedPathBytes",
73        (8, 3) => "SelectedObjects",
74        (8, 4) | (9, 4) | (11, 4) => "ObjectBytes",
75        (8, 5) | (9, 5) | (10, 5) | (11, 5) => "TotalBytes",
76        (9, 3) | (11, 3) => "SelectedFiles",
77        (10, 3) => "SelectedContainers",
78        (13, 0) => "TreeListedEntries",
79        (13, 1) => "TreeListedPathBytes",
80        (13, 2) => "TreeTotalListedPathBytes",
81        (13, 3) => "TreeSelectedFiles",
82        (13, 4) => "TreeObjectBytes",
83        (13, 5) => "TreeTotalBytes",
84        (13, 6) => "ArchiveBytes",
85        _ => "UnknownResource",
86    }
87}
88
89/// One kind of resource governed by a finite ceiling.
90pub trait Resource: Copy + fmt::Debug + Eq + 'static {
91    /// The operation whose work this resource describes.
92    const OPERATION: &'static str;
93
94    #[doc(hidden)]
95    fn index(self) -> usize;
96}
97
98impl<const PROFILE: u8> Resource for ResourceKind<PROFILE> {
99    const OPERATION: &'static str = operation_name(PROFILE);
100
101    fn index(self) -> usize {
102        self.0 as usize
103    }
104}
105
106/// Validated finite ceilings for the resources used by one operation.
107#[derive(Clone, Copy, Eq, PartialEq)]
108pub struct Limits<R: Resource> {
109    pub(crate) ceilings: [u64; MAX_RESOURCES],
110    resource: PhantomData<R>,
111}
112
113impl<R: Resource> Limits<R> {
114    pub(crate) const fn from_ceilings(ceilings: [u64; MAX_RESOURCES]) -> Self {
115        Self {
116            ceilings,
117            resource: PhantomData,
118        }
119    }
120
121    #[track_caller]
122    pub(crate) fn assert_probe_resources(self, resources: impl IntoIterator<Item = R>) -> Self {
123        for resource in resources {
124            let ceiling = self.ceiling(resource);
125            assert_ne!(
126                ceiling,
127                u64::MAX,
128                "the {resource:?} ceiling must leave room for a plus-one probe"
129            );
130        }
131        self
132    }
133
134    /// Returns the finite ceiling for one resource.
135    pub fn ceiling(&self, resource: R) -> u64 {
136        self.ceilings[resource.index()]
137    }
138}
139
140impl<R: Resource> fmt::Debug for Limits<R> {
141    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
142        formatter
143            .debug_struct("Limits")
144            .field("ceilings", &self.ceilings)
145            .finish()
146    }
147}
148
149/// A mandatory resource ceiling was exceeded or could not be accounted.
150#[derive(Debug, Clone, Copy, Eq, PartialEq)]
151#[non_exhaustive]
152pub enum LimitError<R: Resource> {
153    Exceeded {
154        resource: R,
155        ceiling: u64,
156        observed_at_least: u64,
157    },
158    AccountingOverflow {
159        resource: R,
160    },
161}
162
163impl<R: Resource> LimitError<R> {
164    pub(crate) fn exceeded(resource: R, ceiling: u64) -> Self {
165        match ceiling.checked_add(1) {
166            Some(observed_at_least) => Self::Exceeded {
167                resource,
168                ceiling,
169                observed_at_least,
170            },
171            None => Self::AccountingOverflow { resource },
172        }
173    }
174}
175
176impl<R: Resource> fmt::Display for LimitError<R> {
177    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
178        match self {
179            Self::Exceeded {
180                resource,
181                ceiling,
182                observed_at_least,
183            } => write!(
184                formatter,
185                "{} {resource:?} limit exceeded: ceiling {ceiling}, observed at least {observed_at_least}",
186                R::OPERATION
187            ),
188            Self::AccountingOverflow { resource } => {
189                write!(
190                    formatter,
191                    "{} {resource:?} accounting overflowed",
192                    R::OPERATION
193                )
194            }
195        }
196    }
197}
198
199impl<R: Resource> std::error::Error for LimitError<R> {}