tinymist_world/
source.rs

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