tinymist_vfs/notify.rs
1use core::fmt;
2use std::path::Path;
3
4use rpds::RedBlackTreeMapSync;
5use typst::diag::FileResult;
6
7use crate::{Bytes, FileChangeSet, FileSnapshot, ImmutPath, PathAccessModel};
8
9/// A memory event that is notified by some external source
10#[derive(Debug, Clone)]
11pub enum MemoryEvent {
12 /// Reset all dependencies and update according to the given changeset
13 ///
14 /// We have not provided a way to reset all dependencies without updating
15 /// yet, but you can create a memory event with empty changeset to achieve
16 /// this:
17 ///
18 /// ```
19 /// use tinymist_vfs::{FileChangeSet, notify::MemoryEvent};
20 /// let event = MemoryEvent::Sync(FileChangeSet::default());
21 /// ```
22 Sync(FileChangeSet),
23 /// Update according to the given changeset
24 Update(FileChangeSet),
25}
26
27/// A upstream update event that is notified by some external source.
28///
29/// This event is used to notify some file watcher to invalidate some files
30/// before applying upstream changes. This is very important to make some atomic
31/// changes.
32#[derive(Debug)]
33pub struct UpstreamUpdateEvent {
34 /// Associated files that the event causes to invalidate
35 pub invalidates: Vec<ImmutPath>,
36 /// Opaque data that is passed to the file watcher
37 pub opaque: Box<dyn std::any::Any + Send>,
38}
39
40/// Aggregated filesystem events from some file watcher
41#[derive(Debug)]
42pub enum FilesystemEvent {
43 /// Update file system files according to the given changeset
44 Update(FileChangeSet),
45 /// See [`UpstreamUpdateEvent`]
46 UpstreamUpdate {
47 /// New changeset produced by invalidation
48 changeset: FileChangeSet,
49 /// The upstream event that causes the invalidation
50 upstream_event: Option<UpstreamUpdateEvent>,
51 },
52}
53
54impl FilesystemEvent {
55 /// Splits the filesystem event into a changeset and an optional upstream
56 /// event.
57 pub fn split(self) -> (FileChangeSet, Option<UpstreamUpdateEvent>) {
58 match self {
59 FilesystemEvent::UpstreamUpdate {
60 changeset,
61 upstream_event,
62 } => (changeset, upstream_event),
63 FilesystemEvent::Update(changeset) => (changeset, None),
64 }
65 }
66}
67
68/// A trait implementing dependency getter.
69pub trait NotifyDeps: fmt::Debug + Send + Sync {
70 /// Gets the dependencies recorded in the world. It is a list of
71 /// accessed file recorded during this revision, e.g. a single compilation
72 /// or other compiler tasks.
73 fn dependencies(&self, f: &mut dyn FnMut(&ImmutPath));
74}
75
76impl NotifyDeps for Vec<ImmutPath> {
77 fn dependencies(&self, f: &mut dyn FnMut(&ImmutPath)) {
78 for path in self.iter() {
79 f(path);
80 }
81 }
82}
83
84/// A message that is sent to some file watcher
85#[derive(Debug)]
86pub enum NotifyMessage {
87 /// Oettle the watching
88 Settle,
89 /// Overrides all dependencies
90 SyncDependency(Box<dyn NotifyDeps>),
91 /// upstream invalidation This is very important to make some atomic changes
92 ///
93 /// Example:
94 /// ```plain
95 /// /// Receive memory event
96 /// let event: MemoryEvent = retrieve();
97 /// let invalidates = event.invalidates();
98 ///
99 /// /// Send memory change event to [`NotifyActor`]
100 /// let event = Box::new(event);
101 /// self.send(NotifyMessage::UpstreamUpdate{ invalidates, opaque: event });
102 ///
103 /// /// Wait for [`NotifyActor`] to finish
104 /// let fs_event = self.fs_notify.block_receive();
105 /// let event: MemoryEvent = fs_event.opaque.downcast().unwrap();
106 ///
107 /// /// Apply changes
108 /// self.lock();
109 /// update_memory(event);
110 /// apply_fs_changes(fs_event.changeset);
111 /// self.unlock();
112 /// ```
113 UpstreamUpdate(UpstreamUpdateEvent),
114}
115
116/// Provides notify access model which retrieves file system events and changes
117/// from some notify backend.
118///
119/// It simply hold notified filesystem data in memory, but still have a fallback
120/// access model, whose the typical underlying access model is
121/// [`crate::system::SystemAccessModel`]
122#[derive(Debug, Clone)]
123pub struct NotifyAccessModel<M> {
124 files: RedBlackTreeMapSync<ImmutPath, FileSnapshot>,
125 /// The fallback access model when the file is not notified ever.
126 pub inner: M,
127}
128
129impl<M: PathAccessModel> NotifyAccessModel<M> {
130 /// Create a new notify access model
131 pub fn new(inner: M) -> Self {
132 Self {
133 files: RedBlackTreeMapSync::default(),
134 inner,
135 }
136 }
137
138 /// Notify the access model with a filesystem event
139 pub fn notify(&mut self, changeset: FileChangeSet) {
140 for path in changeset.removes {
141 self.files.remove_mut(&path);
142 }
143
144 for (path, contents) in changeset.inserts {
145 self.files.insert_mut(path, contents);
146 }
147 }
148}
149
150impl<M: PathAccessModel> PathAccessModel for NotifyAccessModel<M> {
151 #[inline]
152 fn reset(&mut self) {
153 self.inner.reset();
154 }
155
156 fn content(&self, src: &Path) -> FileResult<Bytes> {
157 if let Some(entry) = self.files.get(src) {
158 return entry.content().cloned();
159 }
160
161 self.inner.content(src)
162 }
163}