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