tinymist_world/
source.rs

1//! The source database of the world.
2
3use core::fmt;
4use std::sync::Arc;
5
6use parking_lot::Mutex;
7use tinymist_std::{QueryRef, hash::FxHashMap};
8use tinymist_vfs::{Bytes, FileId, FsProvider};
9use typst::diag::{FileError, FileResult};
10use typst::syntax::Source;
11
12type FileQuery<T> = QueryRef<T, FileError>;
13
14/// A cache for a single file.
15pub struct SourceCache {
16    /// Whether the file is touched by the compile.
17    touched_by_compile: bool,
18    /// The file id.
19    fid: FileId,
20    /// The source of the file.
21    source: FileQuery<Source>,
22    /// The buffer of the file.
23    buffer: FileQuery<Bytes>,
24}
25
26impl fmt::Debug for SourceCache {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        f.debug_struct("SourceCache").finish()
29    }
30}
31
32/// The source database of the world.
33#[derive(Clone)]
34pub struct SourceDb {
35    /// Whether the database is currently compiling.
36    pub is_compiling: bool,
37    /// The slots for all the files during a single lifecycle.
38    pub slots: Arc<Mutex<FxHashMap<FileId, SourceCache>>>,
39}
40
41impl fmt::Debug for SourceDb {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        f.debug_struct("SourceDb").finish()
44    }
45}
46
47impl SourceDb {
48    /// Sets whether the database is currently compiling.
49    pub fn set_is_compiling(&mut self, is_compiling: bool) {
50        self.is_compiling = is_compiling;
51    }
52
53    /// Gets the overall memory usage for the stored files.
54    pub fn memory_usage(&self) -> usize {
55        let mut w = self.slots.lock().len() * core::mem::size_of::<SourceCache>();
56        w += self
57            .slots
58            .lock()
59            .values()
60            .map(|slot| {
61                slot.source
62                    .get_uninitialized()
63                    .and_then(|e| e.as_ref().ok())
64                    .map_or(16, |e| e.text().len() * 8)
65                    + slot
66                        .buffer
67                        .get_uninitialized()
68                        .and_then(|e| e.as_ref().ok())
69                        .map_or(16, |e| e.len())
70            })
71            .sum::<usize>();
72
73        w
74    }
75
76    /// Gets all the files that are currently in the VFS.
77    ///
78    /// This is typically corresponds to the file dependencies of a single
79    /// compilation.
80    ///
81    /// When you don't reset the vfs for each compilation, this function will
82    /// still return remaining files from the previous compilation.
83    pub fn iter_dependencies_dyn(&self, f: &mut dyn FnMut(FileId)) {
84        for slot in self.slots.lock().values() {
85            if !slot.touched_by_compile {
86                continue;
87            }
88            f(slot.fid);
89        }
90    }
91
92    /// Gets the file content by path.
93    pub fn file(&self, fid: FileId, p: &impl FsProvider) -> FileResult<Bytes> {
94        self.slot(fid, |slot| slot.buffer.compute(|| p.read(fid)).cloned())
95    }
96
97    /// Gets the source content by path and assign the source with a given typst
98    /// global file id.
99    ///
100    /// See `Vfs::resolve_with_f` for more information.
101    pub fn source(&self, fid: FileId, p: &impl FsProvider) -> FileResult<Source> {
102        self.slot(fid, |slot| {
103            slot.source.compute(|| p.read_source(fid)).cloned()
104        })
105    }
106
107    /// Inserts a new slot into the vfs.
108    fn slot<T>(&self, fid: FileId, f: impl FnOnce(&SourceCache) -> T) -> T {
109        let mut slots = self.slots.lock();
110        f({
111            let entry = slots.entry(fid).or_insert_with(|| SourceCache {
112                touched_by_compile: self.is_compiling,
113                fid,
114                source: FileQuery::default(),
115                buffer: FileQuery::default(),
116            });
117            if self.is_compiling && !entry.touched_by_compile {
118                // We put the mutation behind the if statement to avoid
119                // unnecessary writes to the cache.
120                entry.touched_by_compile = true;
121            }
122            entry
123        })
124    }
125
126    /// Takes state of the source database.
127    pub(crate) fn take(&mut self) -> Self {
128        Self {
129            is_compiling: self.is_compiling,
130            slots: std::mem::take(&mut self.slots),
131        }
132    }
133}