Skip to content

Commit 8447380

Browse files
authored
perf: check if it is already a BoxSource containing a CachedSource (#183)
* perf: check if it is already a BoxSource containing a CachedSource * chore: rm unsafe code in cached source
1 parent faf2138 commit 8447380

File tree

4 files changed

+76
-146
lines changed

4 files changed

+76
-146
lines changed

Cargo.lock

Lines changed: 1 addition & 72 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ serde = { version = "1.0.216", features = ["derive", "rc"] }
3434
serde_json = "1.0.133"
3535
dyn-clone = "1.0.17"
3636
rustc-hash = "2.1.0"
37-
dashmap = "6.1.0"
3837
memchr = "2.7.4"
3938
itertools = "0.13"
4039

src/cached_source.rs

Lines changed: 57 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
use std::{
22
borrow::Cow,
3-
hash::{BuildHasherDefault, Hash, Hasher},
3+
hash::{Hash, Hasher},
44
sync::{Arc, OnceLock},
55
};
66

7-
use dashmap::{mapref::entry::Entry, DashMap};
87
use rustc_hash::FxHasher;
98

109
use crate::{
@@ -13,9 +12,16 @@ use crate::{
1312
stream_chunks_of_source_map, StreamChunks,
1413
},
1514
rope::Rope,
16-
MapOptions, Source, SourceMap,
15+
BoxSource, MapOptions, Source, SourceExt, SourceMap,
1716
};
1817

18+
#[derive(Default)]
19+
struct CachedData {
20+
hash: OnceLock<u64>,
21+
line_only_map: OnceLock<Option<SourceMap>>,
22+
columns_map: OnceLock<Option<SourceMap>>,
23+
}
24+
1925
/// It tries to reused cached results from other methods to avoid calculations,
2026
/// usually used after modify is finished.
2127
///
@@ -49,30 +55,30 @@ use crate::{
4955
/// "Hello World\nconsole.log('test');\nconsole.log('test2');\nHello2\n"
5056
/// );
5157
/// ```
52-
pub struct CachedSource<T> {
53-
inner: Arc<T>,
54-
cached_hash: Arc<OnceLock<u64>>,
55-
cached_maps:
56-
Arc<DashMap<MapOptions, Option<SourceMap>, BuildHasherDefault<FxHasher>>>,
58+
pub struct CachedSource {
59+
inner: BoxSource,
60+
cache: Arc<CachedData>,
5761
}
5862

59-
impl<T> CachedSource<T> {
63+
impl CachedSource {
6064
/// Create a [CachedSource] with the original [Source].
61-
pub fn new(inner: T) -> Self {
62-
Self {
63-
inner: Arc::new(inner),
64-
cached_hash: Default::default(),
65-
cached_maps: Default::default(),
65+
pub fn new<T: SourceExt>(inner: T) -> Self {
66+
let box_source = inner.boxed();
67+
// Check if it's already a BoxSource containing a CachedSource
68+
if let Some(cached_source) =
69+
box_source.as_ref().as_any().downcast_ref::<CachedSource>()
70+
{
71+
return cached_source.clone();
6672
}
67-
}
6873

69-
/// Get the original [Source].
70-
pub fn original(&self) -> &T {
71-
&self.inner
74+
Self {
75+
inner: box_source,
76+
cache: Arc::new(CachedData::default()),
77+
}
7278
}
7379
}
7480

75-
impl<T: Source + Hash + PartialEq + Eq + 'static> Source for CachedSource<T> {
81+
impl Source for CachedSource {
7682
fn source(&self) -> Cow<str> {
7783
self.inner.source()
7884
}
@@ -92,12 +98,18 @@ impl<T: Source + Hash + PartialEq + Eq + 'static> Source for CachedSource<T> {
9298
}
9399

94100
fn map(&self, options: &MapOptions) -> Option<SourceMap> {
95-
if let Some(map) = self.cached_maps.get(options) {
96-
map.clone()
101+
if options.columns {
102+
self
103+
.cache
104+
.columns_map
105+
.get_or_init(|| self.inner.map(options))
106+
.clone()
97107
} else {
98-
let map = self.inner.map(options);
99-
self.cached_maps.insert(options.clone(), map.clone());
100-
map
108+
self
109+
.cache
110+
.line_only_map
111+
.get_or_init(|| self.inner.map(options))
112+
.clone()
101113
}
102114
}
103115

@@ -106,29 +118,23 @@ impl<T: Source + Hash + PartialEq + Eq + 'static> Source for CachedSource<T> {
106118
}
107119
}
108120

109-
impl<T: Source + Hash + PartialEq + Eq + 'static> StreamChunks
110-
for CachedSource<T>
111-
{
121+
impl StreamChunks for CachedSource {
112122
fn stream_chunks<'a>(
113123
&'a self,
114124
options: &MapOptions,
115125
on_chunk: crate::helpers::OnChunk<'_, 'a>,
116126
on_source: crate::helpers::OnSource<'_, 'a>,
117127
on_name: crate::helpers::OnName<'_, 'a>,
118128
) -> crate::helpers::GeneratedInfo {
119-
let cached_map = self.cached_maps.entry(options.clone());
120-
match cached_map {
121-
Entry::Occupied(entry) => {
129+
let cell = if options.columns {
130+
&self.cache.columns_map
131+
} else {
132+
&self.cache.line_only_map
133+
};
134+
match cell.get() {
135+
Some(map) => {
122136
let source = self.rope();
123-
if let Some(map) = entry.get() {
124-
#[allow(unsafe_code)]
125-
// SAFETY: We guarantee that once a `SourceMap` is stored in the cache, it will never be removed.
126-
// Therefore, even if we force its lifetime to be longer, the reference remains valid.
127-
// This is based on the following assumptions:
128-
// 1. `SourceMap` will be valid for the entire duration of the application.
129-
// 2. The cached `SourceMap` will not be manually removed or replaced, ensuring the reference's safety.
130-
let map =
131-
unsafe { std::mem::transmute::<&SourceMap, &'a SourceMap>(map) };
137+
if let Some(map) = map {
132138
stream_chunks_of_source_map(
133139
source, map, on_chunk, on_source, on_name, options,
134140
)
@@ -138,34 +144,33 @@ impl<T: Source + Hash + PartialEq + Eq + 'static> StreamChunks
138144
)
139145
}
140146
}
141-
Entry::Vacant(entry) => {
147+
None => {
142148
let (generated_info, map) = stream_and_get_source_and_map(
143-
&self.inner as &T,
149+
&self.inner,
144150
options,
145151
on_chunk,
146152
on_source,
147153
on_name,
148154
);
149-
entry.insert(map);
155+
cell.get_or_init(|| map);
150156
generated_info
151157
}
152158
}
153159
}
154160
}
155161

156-
impl<T> Clone for CachedSource<T> {
162+
impl Clone for CachedSource {
157163
fn clone(&self) -> Self {
158164
Self {
159165
inner: self.inner.clone(),
160-
cached_hash: self.cached_hash.clone(),
161-
cached_maps: self.cached_maps.clone(),
166+
cache: self.cache.clone(),
162167
}
163168
}
164169
}
165170

166-
impl<T: Source + Hash + PartialEq + Eq + 'static> Hash for CachedSource<T> {
171+
impl Hash for CachedSource {
167172
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
168-
(self.cached_hash.get_or_init(|| {
173+
(self.cache.hash.get_or_init(|| {
169174
let mut hasher = FxHasher::default();
170175
self.inner.hash(&mut hasher);
171176
hasher.finish()
@@ -174,15 +179,15 @@ impl<T: Source + Hash + PartialEq + Eq + 'static> Hash for CachedSource<T> {
174179
}
175180
}
176181

177-
impl<T: PartialEq> PartialEq for CachedSource<T> {
182+
impl PartialEq for CachedSource {
178183
fn eq(&self, other: &Self) -> bool {
179-
self.inner == other.inner
184+
self.inner.as_ref() == other.inner.as_ref()
180185
}
181186
}
182187

183-
impl<T: Eq> Eq for CachedSource<T> {}
188+
impl Eq for CachedSource {}
184189

185-
impl<T: std::fmt::Debug> std::fmt::Debug for CachedSource<T> {
190+
impl std::fmt::Debug for CachedSource {
186191
fn fmt(
187192
&self,
188193
f: &mut std::fmt::Formatter<'_>,
@@ -244,7 +249,7 @@ mod tests {
244249
source.map(&map_options);
245250

246251
assert_eq!(
247-
*clone.cached_maps.get(&map_options).unwrap().value(),
252+
*clone.cache.columns_map.get().unwrap(),
248253
source.map(&map_options)
249254
);
250255
}

0 commit comments

Comments
 (0)