use std::fmt;
use std::marker::PhantomData;
const MAX_RESOURCES: usize = 7;
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
pub struct ResourceKind<const PROFILE: u8>(u8);
impl<const PROFILE: u8> ResourceKind<PROFILE> {
pub(crate) const fn new(index: u8) -> Self {
Self(index)
}
}
impl<const PROFILE: u8> fmt::Debug for ResourceKind<PROFILE> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(resource_name(PROFILE, self.0))
}
}
const fn operation_name(profile: u8) -> &'static str {
match profile {
0 => "filesystem project reading",
1 => "filesystem Package Tree reading",
2 => "filesystem Font Catalog reading",
3 => "compilation",
4 => "Pack Archive Encode",
5 => "Pack Archive Decode",
6 => "Package Archive Expansion",
7 => "Pack Archive Read",
8 => "OpenDAL recursive read",
9 => "OpenDAL Project Read",
10 => "OpenDAL Font Read",
11 => "OpenDAL Package Tree Read",
12 => "OpenDAL Package Archive Read",
13 => "OpenDAL Package Read",
_ => "resource-limited operation",
}
}
const fn resource_name(profile: u8, index: u8) -> &'static str {
match (profile, index) {
(0, 0) | (1, 0) | (2, 0) => "VisitedEntries",
(0, 1) | (1, 1) => "SelectedFiles",
(0, 2) => "RootPolicyBytes",
(0, 3) | (1, 2) => "SelectedFileBytes",
(0, 4) => "TotalSelectedBytes",
(1, 3) => "PackageTreeBytes",
(2, 1) => "AcceptedContainers",
(2, 2) | (10, 4) => "ContainerBytes",
(2, 3) => "TotalAcceptedBytes",
(3, 0) => "SourcePages",
(3, 1) => "Artifacts",
(3, 2) => "PixelsPerArtifact",
(3, 3) => "TotalPixels",
(3, 4) => "ArtifactBytes",
(3, 5) => "RetainedArtifactBytes",
(3, 6) => "ExportWorkers",
(4, 0) | (5, 0) | (7, 0) | (12, 0) => "ArchiveBytes",
(4, 1) | (5, 1) | (6, 1) => "Members",
(4, 2) => "GeneratedMemberNameBytes",
(4, 3) | (5, 3) => "ManifestBytes",
(4, 4) | (5, 4) | (6, 3) => "MemberBytes",
(4, 5) | (5, 5) => "TotalContentBytes",
(5, 2) => "RawMemberNameBytes",
(6, 0) => "CompressedArchiveBytes",
(6, 2) => "MemberNameBytes",
(6, 4) => "TotalExpandedBytes",
(8, 0) | (9, 0) | (10, 0) | (11, 0) => "ListedEntries",
(8, 1) | (9, 1) | (10, 1) | (11, 1) => "ListedPathBytes",
(8, 2) | (9, 2) | (10, 2) | (11, 2) => "TotalListedPathBytes",
(8, 3) => "SelectedObjects",
(8, 4) | (9, 4) | (11, 4) => "ObjectBytes",
(8, 5) | (9, 5) | (10, 5) | (11, 5) => "TotalBytes",
(9, 3) | (11, 3) => "SelectedFiles",
(10, 3) => "SelectedContainers",
(13, 0) => "TreeListedEntries",
(13, 1) => "TreeListedPathBytes",
(13, 2) => "TreeTotalListedPathBytes",
(13, 3) => "TreeSelectedFiles",
(13, 4) => "TreeObjectBytes",
(13, 5) => "TreeTotalBytes",
(13, 6) => "ArchiveBytes",
_ => "UnknownResource",
}
}
pub trait Resource: Copy + fmt::Debug + Eq + 'static {
const OPERATION: &'static str;
#[doc(hidden)]
fn index(self) -> usize;
}
impl<const PROFILE: u8> Resource for ResourceKind<PROFILE> {
const OPERATION: &'static str = operation_name(PROFILE);
fn index(self) -> usize {
self.0 as usize
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct Limits<R: Resource> {
pub(crate) ceilings: [u64; MAX_RESOURCES],
resource: PhantomData<R>,
}
impl<R: Resource> Limits<R> {
pub(crate) const fn from_ceilings(ceilings: [u64; MAX_RESOURCES]) -> Self {
Self {
ceilings,
resource: PhantomData,
}
}
#[track_caller]
pub(crate) fn assert_probe_resources(self, resources: impl IntoIterator<Item = R>) -> Self {
for resource in resources {
let ceiling = self.ceiling(resource);
assert_ne!(
ceiling,
u64::MAX,
"the {resource:?} ceiling must leave room for a plus-one probe"
);
}
self
}
pub fn ceiling(&self, resource: R) -> u64 {
self.ceilings[resource.index()]
}
}
impl<R: Resource> fmt::Debug for Limits<R> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Limits")
.field("ceilings", &self.ceilings)
.finish()
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[non_exhaustive]
pub enum LimitError<R: Resource> {
Exceeded {
resource: R,
ceiling: u64,
observed_at_least: u64,
},
AccountingOverflow {
resource: R,
},
}
impl<R: Resource> LimitError<R> {
pub(crate) fn exceeded(resource: R, ceiling: u64) -> Self {
match ceiling.checked_add(1) {
Some(observed_at_least) => Self::Exceeded {
resource,
ceiling,
observed_at_least,
},
None => Self::AccountingOverflow { resource },
}
}
}
impl<R: Resource> fmt::Display for LimitError<R> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Exceeded {
resource,
ceiling,
observed_at_least,
} => write!(
formatter,
"{} {resource:?} limit exceeded: ceiling {ceiling}, observed at least {observed_at_least}",
R::OPERATION
),
Self::AccountingOverflow { resource } => {
write!(
formatter,
"{} {resource:?} accounting overflowed",
R::OPERATION
)
}
}
}
}
impl<R: Resource> std::error::Error for LimitError<R> {}