1use std::any::TypeId;
4use std::cell::RefCell;
5use std::ops::Deref;
6use std::sync::Arc;
7
8use whale::{Durability, RevisionCounter, Runtime as WhaleRuntime};
9
10use crate::asset::{AssetKey, AssetLocator, DurabilityLevel, FullAssetKey, PendingAsset};
11use crate::db::Db;
12use crate::key::FullCacheKey;
13use crate::loading::AssetLoadingState;
14use crate::query::Query;
15use crate::storage::{
16 AssetKeyRegistry, AssetState, AssetStorage, CachedEntry, CachedValue, LocatorStorage,
17 PendingStorage, QueryRegistry, VerifierStorage,
18};
19use crate::tracer::{
20 ExecutionResult, InvalidationReason, NoopTracer, SpanId, Tracer, TracerAssetKey,
21 TracerAssetState, TracerQueryKey,
22};
23use crate::QueryError;
24
25pub type ErrorComparator = fn(&anyhow::Error, &anyhow::Error) -> bool;
30
31const DURABILITY_LEVELS: usize = 4;
33
34thread_local! {
36 static QUERY_STACK: RefCell<Vec<FullCacheKey>> = const { RefCell::new(Vec::new()) };
37}
38
39#[derive(Clone, Copy)]
43pub struct ExecutionContext {
44 span_id: SpanId,
45}
46
47impl ExecutionContext {
48 #[inline]
50 pub fn new(span_id: SpanId) -> Self {
51 Self { span_id }
52 }
53
54 #[inline]
56 pub fn span_id(&self) -> SpanId {
57 self.span_id
58 }
59}
60
61#[derive(Debug, Clone)]
82pub struct Polled<T> {
83 pub value: T,
85 pub revision: RevisionCounter,
89}
90
91impl<T: Deref> Deref for Polled<T> {
92 type Target = T::Target;
93
94 fn deref(&self) -> &Self::Target {
95 &self.value
96 }
97}
98
99pub struct QueryRuntime<T: Tracer = NoopTracer> {
122 whale: WhaleRuntime<FullCacheKey, Option<CachedEntry>, DURABILITY_LEVELS>,
125 assets: Arc<AssetStorage>,
127 locators: Arc<LocatorStorage>,
129 pending: Arc<PendingStorage>,
131 query_registry: Arc<QueryRegistry>,
133 asset_key_registry: Arc<AssetKeyRegistry>,
135 verifiers: Arc<VerifierStorage>,
137 error_comparator: ErrorComparator,
139 tracer: Arc<T>,
141}
142
143impl Default for QueryRuntime<NoopTracer> {
144 fn default() -> Self {
145 Self::new()
146 }
147}
148
149impl<T: Tracer> Clone for QueryRuntime<T> {
150 fn clone(&self) -> Self {
151 Self {
152 whale: self.whale.clone(),
153 assets: self.assets.clone(),
154 locators: self.locators.clone(),
155 pending: self.pending.clone(),
156 query_registry: self.query_registry.clone(),
157 asset_key_registry: self.asset_key_registry.clone(),
158 verifiers: self.verifiers.clone(),
159 error_comparator: self.error_comparator,
160 tracer: self.tracer.clone(),
161 }
162 }
163}
164
165fn default_error_comparator(_a: &anyhow::Error, _b: &anyhow::Error) -> bool {
169 false
170}
171
172impl<T: Tracer> QueryRuntime<T> {
173 fn get_cached_with_revision<Q: Query>(
175 &self,
176 key: &FullCacheKey,
177 ) -> Option<(CachedValue<Arc<Q::Output>>, RevisionCounter)> {
178 let node = self.whale.get(key)?;
179 let revision = node.changed_at;
180 let entry = node.data.as_ref()?;
181 let cached = entry.to_cached_value::<Q::Output>()?;
182 Some((cached, revision))
183 }
184
185 #[inline]
187 pub fn tracer(&self) -> &T {
188 &self.tracer
189 }
190}
191
192impl QueryRuntime<NoopTracer> {
193 pub fn new() -> Self {
195 Self::with_tracer(NoopTracer)
196 }
197
198 pub fn builder() -> QueryRuntimeBuilder<NoopTracer> {
214 QueryRuntimeBuilder::new()
215 }
216}
217
218impl<T: Tracer> QueryRuntime<T> {
219 pub fn with_tracer(tracer: T) -> Self {
221 QueryRuntimeBuilder::new().tracer(tracer).build()
222 }
223
224 pub fn query<Q: Query>(&self, query: Q) -> Result<Arc<Q::Output>, QueryError> {
233 self.query_internal(query)
234 .and_then(|(inner_result, _)| inner_result.map_err(QueryError::UserError))
235 }
236
237 #[allow(clippy::type_complexity)]
242 fn query_internal<Q: Query>(
243 &self,
244 query: Q,
245 ) -> Result<(Result<Arc<Q::Output>, Arc<anyhow::Error>>, RevisionCounter), QueryError> {
246 let key = query.cache_key();
247 let full_key = FullCacheKey::new::<Q, _>(&key);
248
249 self.tracer.on_query_key(&full_key);
251
252 let span_id = self.tracer.new_span_id();
254 let exec_ctx = ExecutionContext::new(span_id);
255 let query_key = TracerQueryKey::new(std::any::type_name::<Q>(), full_key.debug_repr());
256
257 self.tracer.on_query_start(span_id, query_key.clone());
258
259 let cycle_detected = QUERY_STACK.with(|stack| {
261 let stack = stack.borrow();
262 stack.iter().any(|k| k == &full_key)
263 });
264
265 if cycle_detected {
266 let path = QUERY_STACK.with(|stack| {
267 let stack = stack.borrow();
268 let mut path: Vec<String> =
269 stack.iter().map(|k| k.debug_repr().to_string()).collect();
270 path.push(full_key.debug_repr().to_string());
271 path
272 });
273
274 self.tracer.on_cycle_detected(
275 path.iter()
276 .map(|s| TracerQueryKey::new("", s.clone()))
277 .collect(),
278 );
279 self.tracer
280 .on_query_end(span_id, query_key.clone(), ExecutionResult::CycleDetected);
281
282 return Err(QueryError::Cycle { path });
283 }
284
285 let current_rev = self.whale.current_revision();
287
288 if self.whale.is_verified_at(&full_key, ¤t_rev) {
290 if let Some((cached, revision)) = self.get_cached_with_revision::<Q>(&full_key) {
292 self.tracer.on_cache_check(span_id, query_key.clone(), true);
293 self.tracer
294 .on_query_end(span_id, query_key.clone(), ExecutionResult::CacheHit);
295
296 return match cached {
297 CachedValue::Ok(output) => Ok((Ok(output), revision)),
298 CachedValue::UserError(err) => Ok((Err(err), revision)),
299 };
300 }
301 }
302
303 if self.whale.is_valid(&full_key) {
305 if let Some((cached, revision)) = self.get_cached_with_revision::<Q>(&full_key) {
307 let mut deps_verified = true;
309 if let Some(deps) = self.whale.get_dependency_ids(&full_key) {
310 for dep in deps {
311 if let Some(verifier) = self.verifiers.get(&dep) {
312 if verifier.verify(self as &dyn std::any::Any).is_err() {
314 deps_verified = false;
315 break;
316 }
317 }
318 }
319 }
320
321 if deps_verified && self.whale.is_valid(&full_key) {
323 self.whale.mark_verified(&full_key, ¤t_rev);
325
326 self.tracer.on_cache_check(span_id, query_key.clone(), true);
327 self.tracer
328 .on_query_end(span_id, query_key.clone(), ExecutionResult::CacheHit);
329
330 return match cached {
331 CachedValue::Ok(output) => Ok((Ok(output), revision)),
332 CachedValue::UserError(err) => Ok((Err(err), revision)),
333 };
334 }
335 }
337 }
338
339 self.tracer
340 .on_cache_check(span_id, query_key.clone(), false);
341
342 QUERY_STACK.with(|stack| {
344 stack.borrow_mut().push(full_key.clone());
345 });
346
347 let result = self.execute_query::<Q>(&query, &full_key, exec_ctx);
348
349 QUERY_STACK.with(|stack| {
350 stack.borrow_mut().pop();
351 });
352
353 let exec_result = match &result {
355 Ok((_, true, _)) => ExecutionResult::Changed,
356 Ok((_, false, _)) => ExecutionResult::Unchanged,
357 Err(QueryError::Suspend { .. }) => ExecutionResult::Suspended,
358 Err(QueryError::Cycle { .. }) => ExecutionResult::CycleDetected,
359 Err(e) => ExecutionResult::Error {
360 message: format!("{:?}", e),
361 },
362 };
363 self.tracer
364 .on_query_end(span_id, query_key.clone(), exec_result);
365
366 result.map(|(inner_result, _, revision)| (inner_result, revision))
367 }
368
369 #[allow(clippy::type_complexity)]
375 fn execute_query<Q: Query>(
376 &self,
377 query: &Q,
378 full_key: &FullCacheKey,
379 exec_ctx: ExecutionContext,
380 ) -> Result<
381 (
382 Result<Arc<Q::Output>, Arc<anyhow::Error>>,
383 bool,
384 RevisionCounter,
385 ),
386 QueryError,
387 > {
388 let ctx = QueryContext {
390 runtime: self,
391 current_key: full_key.clone(),
392 parent_query_type: std::any::type_name::<Q>(),
393 exec_ctx,
394 deps: RefCell::new(Vec::new()),
395 };
396
397 let result = query.clone().query(&ctx);
399
400 let deps: Vec<FullCacheKey> = ctx.deps.borrow().clone();
402
403 let durability =
405 Durability::new(query.durability() as usize).unwrap_or(Durability::volatile());
406
407 match result {
408 Ok(output) => {
409 let output = Arc::new(output);
410
411 let existing_revision = if let Some((CachedValue::Ok(old), rev)) =
414 self.get_cached_with_revision::<Q>(full_key)
415 {
416 if Q::output_eq(&old, &output) {
417 Some(rev) } else {
419 None }
421 } else {
422 None };
424 let output_changed = existing_revision.is_none();
425
426 self.tracer.on_early_cutoff_check(
428 exec_ctx.span_id(),
429 TracerQueryKey::new(std::any::type_name::<Q>(), full_key.debug_repr()),
430 output_changed,
431 );
432
433 let entry = CachedEntry::Ok(output.clone() as Arc<dyn std::any::Any + Send + Sync>);
435 let revision = if let Some(existing_rev) = existing_revision {
436 let _ = self.whale.confirm_unchanged(full_key, deps);
438 existing_rev
439 } else {
440 match self
442 .whale
443 .register(full_key.clone(), Some(entry), durability, deps)
444 {
445 Ok(result) => result.new_rev,
446 Err(missing) => {
447 return Err(QueryError::DependenciesRemoved {
448 missing_keys: missing,
449 })
450 }
451 }
452 };
453
454 let is_new_query = self.query_registry.register(query);
456 if is_new_query {
457 let sentinel = FullCacheKey::query_set_sentinel::<Q>();
458 let _ = self
459 .whale
460 .register(sentinel, None, Durability::volatile(), vec![]);
461 }
462
463 self.verifiers
465 .insert::<Q, T>(full_key.clone(), query.clone());
466
467 Ok((Ok(output), output_changed, revision))
468 }
469 Err(QueryError::UserError(err)) => {
470 let existing_revision = if let Some((CachedValue::UserError(old_err), rev)) =
473 self.get_cached_with_revision::<Q>(full_key)
474 {
475 if (self.error_comparator)(old_err.as_ref(), err.as_ref()) {
476 Some(rev) } else {
478 None }
480 } else {
481 None };
483 let output_changed = existing_revision.is_none();
484
485 self.tracer.on_early_cutoff_check(
487 exec_ctx.span_id(),
488 TracerQueryKey::new(std::any::type_name::<Q>(), full_key.debug_repr()),
489 output_changed,
490 );
491
492 let entry = CachedEntry::UserError(err.clone());
494 let revision = if let Some(existing_rev) = existing_revision {
495 let _ = self.whale.confirm_unchanged(full_key, deps);
497 existing_rev
498 } else {
499 match self
501 .whale
502 .register(full_key.clone(), Some(entry), durability, deps)
503 {
504 Ok(result) => result.new_rev,
505 Err(missing) => {
506 return Err(QueryError::DependenciesRemoved {
507 missing_keys: missing,
508 })
509 }
510 }
511 };
512
513 let is_new_query = self.query_registry.register(query);
515 if is_new_query {
516 let sentinel = FullCacheKey::query_set_sentinel::<Q>();
517 let _ = self
518 .whale
519 .register(sentinel, None, Durability::volatile(), vec![]);
520 }
521
522 self.verifiers
524 .insert::<Q, T>(full_key.clone(), query.clone());
525
526 Ok((Err(err), output_changed, revision))
527 }
528 Err(e) => {
529 Err(e)
531 }
532 }
533 }
534
535 pub fn invalidate<Q: Query>(&self, key: &Q::CacheKey) {
539 let full_key = FullCacheKey::new::<Q, _>(key);
540
541 self.tracer.on_query_invalidated(
542 TracerQueryKey::new(std::any::type_name::<Q>(), full_key.debug_repr()),
543 InvalidationReason::ManualInvalidation,
544 );
545
546 let _ = self
548 .whale
549 .register(full_key, None, Durability::volatile(), vec![]);
550 }
551
552 pub fn remove_query<Q: Query>(&self, key: &Q::CacheKey) {
560 let full_key = FullCacheKey::new::<Q, _>(key);
561
562 self.tracer.on_query_invalidated(
563 TracerQueryKey::new(std::any::type_name::<Q>(), full_key.debug_repr()),
564 InvalidationReason::ManualInvalidation,
565 );
566
567 self.verifiers.remove(&full_key);
569
570 self.whale.remove(&full_key);
572
573 if self.query_registry.remove::<Q>(key) {
575 let sentinel = FullCacheKey::query_set_sentinel::<Q>();
576 let _ = self
577 .whale
578 .register(sentinel, None, Durability::volatile(), vec![]);
579 }
580 }
581
582 pub fn clear_cache(&self) {
586 let keys = self.whale.keys();
587 for key in keys {
588 self.whale.remove(&key);
589 }
590 }
591
592 #[allow(clippy::type_complexity)]
626 pub fn poll<Q: Query>(
627 &self,
628 query: Q,
629 ) -> Result<Polled<Result<Arc<Q::Output>, Arc<anyhow::Error>>>, QueryError> {
630 let (value, revision) = self.query_internal(query)?;
631 Ok(Polled { value, revision })
632 }
633
634 pub fn changed_at<Q: Query>(&self, key: &Q::CacheKey) -> Option<RevisionCounter> {
653 let full_key = FullCacheKey::new::<Q, _>(key);
654 self.whale.get(&full_key).map(|node| node.changed_at)
655 }
656}
657
658impl<T: Tracer> QueryRuntime<T> {
663 pub fn query_keys(&self) -> Vec<FullCacheKey> {
683 self.whale.keys()
684 }
685
686 pub fn remove_query_if_unused<Q: Query>(&self, key: &Q::CacheKey) -> bool {
703 let full_key = FullCacheKey::new::<Q, _>(key);
704 self.remove_if_unused(&full_key)
705 }
706
707 pub fn remove(&self, key: &FullCacheKey) -> bool {
721 self.verifiers.remove(key);
723
724 self.whale.remove(key).is_some()
726 }
727
728 pub fn remove_if_unused(&self, key: &FullCacheKey) -> bool {
748 if self.whale.remove_if_unused(key.clone()).is_some() {
749 self.verifiers.remove(key);
751 true
752 } else {
753 false
754 }
755 }
756}
757
758pub struct QueryRuntimeBuilder<T: Tracer = NoopTracer> {
776 error_comparator: ErrorComparator,
777 tracer: T,
778}
779
780impl Default for QueryRuntimeBuilder<NoopTracer> {
781 fn default() -> Self {
782 Self::new()
783 }
784}
785
786impl QueryRuntimeBuilder<NoopTracer> {
787 pub fn new() -> Self {
789 Self {
790 error_comparator: default_error_comparator,
791 tracer: NoopTracer,
792 }
793 }
794}
795
796impl<T: Tracer> QueryRuntimeBuilder<T> {
797 pub fn error_comparator(mut self, f: ErrorComparator) -> Self {
815 self.error_comparator = f;
816 self
817 }
818
819 pub fn tracer<U: Tracer>(self, tracer: U) -> QueryRuntimeBuilder<U> {
821 QueryRuntimeBuilder {
822 error_comparator: self.error_comparator,
823 tracer,
824 }
825 }
826
827 pub fn build(self) -> QueryRuntime<T> {
829 QueryRuntime {
830 whale: WhaleRuntime::new(),
831 assets: Arc::new(AssetStorage::new()),
832 locators: Arc::new(LocatorStorage::new()),
833 pending: Arc::new(PendingStorage::new()),
834 query_registry: Arc::new(QueryRegistry::new()),
835 asset_key_registry: Arc::new(AssetKeyRegistry::new()),
836 verifiers: Arc::new(VerifierStorage::new()),
837 error_comparator: self.error_comparator,
838 tracer: Arc::new(self.tracer),
839 }
840 }
841}
842
843impl<T: Tracer> QueryRuntime<T> {
848 pub fn register_asset_locator<K, L>(&self, locator: L)
860 where
861 K: AssetKey,
862 L: AssetLocator<K>,
863 {
864 self.locators.insert::<K, L>(locator);
865 }
866
867 pub fn pending_assets(&self) -> Vec<PendingAsset> {
883 self.pending.get_all()
884 }
885
886 pub fn pending_assets_of<K: AssetKey>(&self) -> Vec<K> {
888 self.pending.get_of_type::<K>()
889 }
890
891 pub fn has_pending_assets(&self) -> bool {
893 !self.pending.is_empty()
894 }
895
896 pub fn resolve_asset<K: AssetKey>(&self, key: K, value: K::Asset) {
913 let durability = key.durability();
914 self.resolve_asset_internal(key, value, durability);
915 }
916
917 pub fn resolve_asset_with_durability<K: AssetKey>(
921 &self,
922 key: K,
923 value: K::Asset,
924 durability: DurabilityLevel,
925 ) {
926 self.resolve_asset_internal(key, value, durability);
927 }
928
929 fn resolve_asset_internal<K: AssetKey>(
930 &self,
931 key: K,
932 value: K::Asset,
933 durability_level: DurabilityLevel,
934 ) {
935 let full_asset_key = FullAssetKey::new(&key);
936 let full_cache_key = FullCacheKey::from_asset_key(&full_asset_key);
937
938 let changed = if let Some(old_value) = self.assets.get_ready::<K>(&full_asset_key) {
940 !K::asset_eq(&old_value, &value)
941 } else {
942 true };
944
945 self.tracer.on_asset_resolved(
947 TracerAssetKey::new(std::any::type_name::<K>(), format!("{:?}", key)),
948 changed,
949 );
950
951 self.assets
953 .insert_ready::<K>(full_asset_key.clone(), Arc::new(value));
954
955 self.pending.remove(&full_asset_key);
957
958 let durability =
960 Durability::new(durability_level.as_u8() as usize).unwrap_or(Durability::volatile());
961
962 if changed {
963 let _ = self
965 .whale
966 .register(full_cache_key, None, durability, vec![]);
967 } else {
968 let _ = self.whale.confirm_unchanged(&full_cache_key, vec![]);
970 }
971
972 let is_new_asset = self.asset_key_registry.register(&key);
974 if is_new_asset {
975 let sentinel = FullCacheKey::asset_key_set_sentinel::<K>();
977 let _ = self
978 .whale
979 .register(sentinel, None, Durability::volatile(), vec![]);
980 }
981 }
982
983 pub fn invalidate_asset<K: AssetKey>(&self, key: &K) {
997 let full_asset_key = FullAssetKey::new(key);
998 let full_cache_key = FullCacheKey::from_asset_key(&full_asset_key);
999
1000 self.tracer.on_asset_invalidated(TracerAssetKey::new(
1002 std::any::type_name::<K>(),
1003 format!("{:?}", key),
1004 ));
1005
1006 self.assets
1008 .insert(full_asset_key.clone(), AssetState::Loading);
1009
1010 self.pending.insert::<K>(full_asset_key, key.clone());
1012
1013 let _ = self
1015 .whale
1016 .register(full_cache_key, None, Durability::volatile(), vec![]);
1017 }
1018
1019 pub fn remove_asset<K: AssetKey>(&self, key: &K) {
1024 let full_asset_key = FullAssetKey::new(key);
1025 let full_cache_key = FullCacheKey::from_asset_key(&full_asset_key);
1026
1027 let _ = self
1030 .whale
1031 .register(full_cache_key.clone(), None, Durability::volatile(), vec![]);
1032
1033 self.assets.remove(&full_asset_key);
1035 self.pending.remove(&full_asset_key);
1036
1037 self.whale.remove(&full_cache_key);
1039
1040 if self.asset_key_registry.remove::<K>(key) {
1042 let sentinel = FullCacheKey::asset_key_set_sentinel::<K>();
1043 let _ = self
1044 .whale
1045 .register(sentinel, None, Durability::volatile(), vec![]);
1046 }
1047 }
1048
1049 pub fn get_asset<K: AssetKey>(&self, key: K) -> Result<AssetLoadingState<K>, QueryError> {
1061 self.get_asset_internal(key)
1062 }
1063
1064 fn get_asset_internal<K: AssetKey>(&self, key: K) -> Result<AssetLoadingState<K>, QueryError> {
1066 let full_asset_key = FullAssetKey::new(&key);
1067 let full_cache_key = FullCacheKey::from_asset_key(&full_asset_key);
1068
1069 let emit_requested = |tracer: &T, key: &K, state: TracerAssetState| {
1071 tracer.on_asset_requested(
1072 TracerAssetKey::new(std::any::type_name::<K>(), format!("{:?}", key)),
1073 state,
1074 );
1075 };
1076
1077 if let Some(state) = self.assets.get(&full_asset_key) {
1079 if self.whale.is_valid(&full_cache_key) {
1081 return match state {
1082 AssetState::Loading => {
1083 emit_requested(&self.tracer, &key, TracerAssetState::Loading);
1084 Ok(AssetLoadingState::loading(key))
1085 }
1086 AssetState::Ready(arc) => {
1087 emit_requested(&self.tracer, &key, TracerAssetState::Ready);
1088 match arc.downcast::<K::Asset>() {
1089 Ok(value) => Ok(AssetLoadingState::ready(key, value)),
1090 Err(_) => Err(QueryError::MissingDependency {
1091 description: format!("Asset type mismatch: {:?}", key),
1092 }),
1093 }
1094 }
1095 AssetState::NotFound => {
1096 emit_requested(&self.tracer, &key, TracerAssetState::NotFound);
1097 Err(QueryError::MissingDependency {
1098 description: format!("Asset not found: {:?}", key),
1099 })
1100 }
1101 };
1102 }
1103 }
1104
1105 if let Some(locator) = self.locators.get(TypeId::of::<K>()) {
1107 if let Some(state) = locator.locate_any(&key) {
1108 self.assets.insert(full_asset_key.clone(), state.clone());
1110
1111 match state {
1112 AssetState::Ready(arc) => {
1113 emit_requested(&self.tracer, &key, TracerAssetState::Ready);
1114
1115 let durability = Durability::new(key.durability().as_u8() as usize)
1117 .unwrap_or(Durability::volatile());
1118 self.whale
1119 .register(full_cache_key, None, durability, vec![])
1120 .expect("register with no dependencies cannot fail");
1121
1122 match arc.downcast::<K::Asset>() {
1123 Ok(value) => return Ok(AssetLoadingState::ready(key, value)),
1124 Err(_) => {
1125 return Err(QueryError::MissingDependency {
1126 description: format!("Asset type mismatch: {:?}", key),
1127 })
1128 }
1129 }
1130 }
1131 AssetState::Loading => {
1132 emit_requested(&self.tracer, &key, TracerAssetState::Loading);
1133 self.pending.insert::<K>(full_asset_key, key.clone());
1134
1135 self.whale
1137 .register(full_cache_key, None, Durability::volatile(), vec![])
1138 .expect("register with no dependencies cannot fail");
1139
1140 return Ok(AssetLoadingState::loading(key));
1141 }
1142 AssetState::NotFound => {
1143 emit_requested(&self.tracer, &key, TracerAssetState::NotFound);
1144 return Err(QueryError::MissingDependency {
1145 description: format!("Asset not found: {:?}", key),
1146 });
1147 }
1148 }
1149 }
1150 }
1151
1152 emit_requested(&self.tracer, &key, TracerAssetState::Loading);
1154 self.assets
1155 .insert(full_asset_key.clone(), AssetState::Loading);
1156 self.pending
1157 .insert::<K>(full_asset_key.clone(), key.clone());
1158
1159 self.whale
1161 .register(full_cache_key, None, Durability::volatile(), vec![])
1162 .expect("register with no dependencies cannot fail");
1163
1164 Ok(AssetLoadingState::loading(key))
1165 }
1166}
1167
1168impl<T: Tracer> Db for QueryRuntime<T> {
1169 fn query<Q: Query>(&self, query: Q) -> Result<Arc<Q::Output>, QueryError> {
1170 QueryRuntime::query(self, query)
1171 }
1172
1173 fn asset<K: AssetKey>(&self, key: K) -> Result<AssetLoadingState<K>, QueryError> {
1174 self.get_asset_internal(key)
1175 }
1176
1177 fn list_queries<Q: Query>(&self) -> Vec<Q> {
1178 self.query_registry.get_all::<Q>()
1179 }
1180
1181 fn list_asset_keys<K: AssetKey>(&self) -> Vec<K> {
1182 self.asset_key_registry.get_all::<K>()
1183 }
1184}
1185
1186pub struct QueryContext<'a, T: Tracer = NoopTracer> {
1190 runtime: &'a QueryRuntime<T>,
1191 current_key: FullCacheKey,
1192 parent_query_type: &'static str,
1193 exec_ctx: ExecutionContext,
1194 deps: RefCell<Vec<FullCacheKey>>,
1195}
1196
1197impl<'a, T: Tracer> QueryContext<'a, T> {
1198 pub fn query<Q: Query>(&self, query: Q) -> Result<Arc<Q::Output>, QueryError> {
1211 let key = query.cache_key();
1212 let full_key = FullCacheKey::new::<Q, _>(&key);
1213
1214 self.runtime.tracer.on_dependency_registered(
1216 self.exec_ctx.span_id(),
1217 TracerQueryKey::new(self.parent_query_type, self.current_key.debug_repr()),
1218 TracerQueryKey::new(std::any::type_name::<Q>(), full_key.debug_repr()),
1219 );
1220
1221 self.deps.borrow_mut().push(full_key.clone());
1223
1224 self.runtime.query(query)
1226 }
1227
1228 pub fn asset<K: AssetKey>(&self, key: K) -> Result<AssetLoadingState<K>, QueryError> {
1252 let full_asset_key = FullAssetKey::new(&key);
1253 let full_cache_key = FullCacheKey::from_asset_key(&full_asset_key);
1254
1255 self.runtime.tracer.on_asset_dependency_registered(
1257 self.exec_ctx.span_id(),
1258 TracerQueryKey::new(self.parent_query_type, self.current_key.debug_repr()),
1259 TracerAssetKey::new(std::any::type_name::<K>(), format!("{:?}", key)),
1260 );
1261
1262 self.deps.borrow_mut().push(full_cache_key);
1264
1265 let result = self.runtime.get_asset_internal(key);
1267
1268 if let Err(QueryError::MissingDependency { ref description }) = result {
1270 self.runtime.tracer.on_missing_dependency(
1271 TracerQueryKey::new(self.parent_query_type, self.current_key.debug_repr()),
1272 description.clone(),
1273 );
1274 }
1275
1276 result
1277 }
1278
1279 pub fn list_queries<Q: Query>(&self) -> Vec<Q> {
1302 let sentinel = FullCacheKey::query_set_sentinel::<Q>();
1304
1305 self.runtime.tracer.on_dependency_registered(
1306 self.exec_ctx.span_id(),
1307 TracerQueryKey::new(self.parent_query_type, self.current_key.debug_repr()),
1308 TracerQueryKey::new("QuerySet", sentinel.debug_repr()),
1309 );
1310
1311 if self.runtime.whale.get(&sentinel).is_none() {
1313 let _ =
1314 self.runtime
1315 .whale
1316 .register(sentinel.clone(), None, Durability::volatile(), vec![]);
1317 }
1318
1319 self.deps.borrow_mut().push(sentinel);
1320
1321 self.runtime.query_registry.get_all::<Q>()
1323 }
1324
1325 pub fn list_asset_keys<K: AssetKey>(&self) -> Vec<K> {
1350 let sentinel = FullCacheKey::asset_key_set_sentinel::<K>();
1352
1353 self.runtime.tracer.on_asset_dependency_registered(
1354 self.exec_ctx.span_id(),
1355 TracerQueryKey::new(self.parent_query_type, self.current_key.debug_repr()),
1356 TracerAssetKey::new("AssetKeySet", sentinel.debug_repr()),
1357 );
1358
1359 if self.runtime.whale.get(&sentinel).is_none() {
1361 let _ =
1362 self.runtime
1363 .whale
1364 .register(sentinel.clone(), None, Durability::volatile(), vec![]);
1365 }
1366
1367 self.deps.borrow_mut().push(sentinel);
1368
1369 self.runtime.asset_key_registry.get_all::<K>()
1371 }
1372}
1373
1374impl<'a, T: Tracer> Db for QueryContext<'a, T> {
1375 fn query<Q: Query>(&self, query: Q) -> Result<Arc<Q::Output>, QueryError> {
1376 QueryContext::query(self, query)
1377 }
1378
1379 fn asset<K: AssetKey>(&self, key: K) -> Result<AssetLoadingState<K>, QueryError> {
1380 QueryContext::asset(self, key)
1381 }
1382
1383 fn list_queries<Q: Query>(&self) -> Vec<Q> {
1384 QueryContext::list_queries(self)
1385 }
1386
1387 fn list_asset_keys<K: AssetKey>(&self) -> Vec<K> {
1388 QueryContext::list_asset_keys(self)
1389 }
1390}
1391
1392#[cfg(test)]
1393mod tests {
1394 use super::*;
1395
1396 #[test]
1397 fn test_simple_query() {
1398 #[derive(Clone)]
1399 struct Add {
1400 a: i32,
1401 b: i32,
1402 }
1403
1404 impl Query for Add {
1405 type CacheKey = (i32, i32);
1406 type Output = i32;
1407
1408 fn cache_key(&self) -> Self::CacheKey {
1409 (self.a, self.b)
1410 }
1411
1412 fn query(self, _db: &impl Db) -> Result<Self::Output, QueryError> {
1413 Ok(self.a + self.b)
1414 }
1415
1416 fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
1417 old == new
1418 }
1419 }
1420
1421 let runtime = QueryRuntime::new();
1422
1423 let result = runtime.query(Add { a: 1, b: 2 }).unwrap();
1424 assert_eq!(*result, 3);
1425
1426 let result2 = runtime.query(Add { a: 1, b: 2 }).unwrap();
1428 assert_eq!(*result2, 3);
1429 }
1430
1431 #[test]
1432 fn test_dependent_queries() {
1433 #[derive(Clone)]
1434 struct Base {
1435 value: i32,
1436 }
1437
1438 impl Query for Base {
1439 type CacheKey = i32;
1440 type Output = i32;
1441
1442 fn cache_key(&self) -> Self::CacheKey {
1443 self.value
1444 }
1445
1446 fn query(self, _db: &impl Db) -> Result<Self::Output, QueryError> {
1447 Ok(self.value * 2)
1448 }
1449
1450 fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
1451 old == new
1452 }
1453 }
1454
1455 #[derive(Clone)]
1456 struct Derived {
1457 base_value: i32,
1458 }
1459
1460 impl Query for Derived {
1461 type CacheKey = i32;
1462 type Output = i32;
1463
1464 fn cache_key(&self) -> Self::CacheKey {
1465 self.base_value
1466 }
1467
1468 fn query(self, db: &impl Db) -> Result<Self::Output, QueryError> {
1469 let base = db.query(Base {
1470 value: self.base_value,
1471 })?;
1472 Ok(*base + 10)
1473 }
1474
1475 fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
1476 old == new
1477 }
1478 }
1479
1480 let runtime = QueryRuntime::new();
1481
1482 let result = runtime.query(Derived { base_value: 5 }).unwrap();
1483 assert_eq!(*result, 20); }
1485
1486 #[test]
1487 fn test_cycle_detection() {
1488 #[derive(Clone)]
1489 struct CycleA {
1490 id: i32,
1491 }
1492
1493 #[derive(Clone)]
1494 struct CycleB {
1495 id: i32,
1496 }
1497
1498 impl Query for CycleA {
1499 type CacheKey = i32;
1500 type Output = i32;
1501
1502 fn cache_key(&self) -> Self::CacheKey {
1503 self.id
1504 }
1505
1506 fn query(self, db: &impl Db) -> Result<Self::Output, QueryError> {
1507 let b = db.query(CycleB { id: self.id })?;
1508 Ok(*b + 1)
1509 }
1510
1511 fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
1512 old == new
1513 }
1514 }
1515
1516 impl Query for CycleB {
1517 type CacheKey = i32;
1518 type Output = i32;
1519
1520 fn cache_key(&self) -> Self::CacheKey {
1521 self.id
1522 }
1523
1524 fn query(self, db: &impl Db) -> Result<Self::Output, QueryError> {
1525 let a = db.query(CycleA { id: self.id })?;
1526 Ok(*a + 1)
1527 }
1528
1529 fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
1530 old == new
1531 }
1532 }
1533
1534 let runtime = QueryRuntime::new();
1535
1536 let result = runtime.query(CycleA { id: 1 });
1537 assert!(matches!(result, Err(QueryError::Cycle { .. })));
1538 }
1539
1540 #[test]
1541 fn test_fallible_query() {
1542 #[derive(Clone)]
1543 struct ParseInt {
1544 input: String,
1545 }
1546
1547 impl Query for ParseInt {
1548 type CacheKey = String;
1549 type Output = Result<i32, std::num::ParseIntError>;
1550
1551 fn cache_key(&self) -> Self::CacheKey {
1552 self.input.clone()
1553 }
1554
1555 fn query(self, _db: &impl Db) -> Result<Self::Output, QueryError> {
1556 Ok(self.input.parse())
1557 }
1558
1559 fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
1560 old == new
1561 }
1562 }
1563
1564 let runtime = QueryRuntime::new();
1565
1566 let result = runtime
1568 .query(ParseInt {
1569 input: "42".to_string(),
1570 })
1571 .unwrap();
1572 assert_eq!(*result, Ok(42));
1573
1574 let result = runtime
1576 .query(ParseInt {
1577 input: "not_a_number".to_string(),
1578 })
1579 .unwrap();
1580 assert!(result.is_err());
1581 }
1582
1583 mod macro_tests {
1585 use super::*;
1586 use crate::query;
1587
1588 #[query]
1589 fn add(db: &impl Db, a: i32, b: i32) -> Result<i32, QueryError> {
1590 let _ = db; Ok(a + b)
1592 }
1593
1594 #[test]
1595 fn test_macro_basic() {
1596 let runtime = QueryRuntime::new();
1597 let result = runtime.query(Add::new(1, 2)).unwrap();
1598 assert_eq!(*result, 3);
1599 }
1600
1601 #[query(durability = 2)]
1602 fn with_durability(db: &impl Db, x: i32) -> Result<i32, QueryError> {
1603 let _ = db;
1604 Ok(x * 2)
1605 }
1606
1607 #[test]
1608 fn test_macro_durability() {
1609 let runtime = QueryRuntime::new();
1610 let result = runtime.query(WithDurability::new(5)).unwrap();
1611 assert_eq!(*result, 10);
1612 }
1613
1614 #[query(keys(id))]
1615 fn with_key_selection(
1616 db: &impl Db,
1617 id: u32,
1618 include_extra: bool,
1619 ) -> Result<String, QueryError> {
1620 let _ = db;
1621 Ok(format!("id={}, extra={}", id, include_extra))
1622 }
1623
1624 #[test]
1625 fn test_macro_key_selection() {
1626 let runtime = QueryRuntime::new();
1627
1628 let r1 = runtime.query(WithKeySelection::new(1, true)).unwrap();
1630 let r2 = runtime.query(WithKeySelection::new(1, false)).unwrap();
1631
1632 assert_eq!(*r1, "id=1, extra=true");
1634 assert_eq!(*r2, "id=1, extra=true"); }
1636
1637 #[query]
1638 fn dependent(db: &impl Db, a: i32, b: i32) -> Result<i32, QueryError> {
1639 let sum = db.query(Add::new(a, b))?;
1640 Ok(*sum * 2)
1641 }
1642
1643 #[test]
1644 fn test_macro_dependencies() {
1645 let runtime = QueryRuntime::new();
1646 let result = runtime.query(Dependent::new(3, 4)).unwrap();
1647 assert_eq!(*result, 14); }
1649
1650 #[query(output_eq)]
1651 fn with_output_eq(db: &impl Db, x: i32) -> Result<i32, QueryError> {
1652 let _ = db;
1653 Ok(x * 2)
1654 }
1655
1656 #[test]
1657 fn test_macro_output_eq() {
1658 let runtime = QueryRuntime::new();
1659 let result = runtime.query(WithOutputEq::new(5)).unwrap();
1660 assert_eq!(*result, 10);
1661 }
1662
1663 #[query(name = "CustomName")]
1664 fn original_name(db: &impl Db, x: i32) -> Result<i32, QueryError> {
1665 let _ = db;
1666 Ok(x)
1667 }
1668
1669 #[test]
1670 fn test_macro_custom_name() {
1671 let runtime = QueryRuntime::new();
1672 let result = runtime.query(CustomName::new(42)).unwrap();
1673 assert_eq!(*result, 42);
1674 }
1675
1676 #[allow(unused_variables)]
1680 #[inline]
1681 #[query]
1682 fn with_attributes(db: &impl Db, x: i32) -> Result<i32, QueryError> {
1683 let unused_var = 42;
1685 Ok(x * 2)
1686 }
1687
1688 #[test]
1689 fn test_macro_preserves_attributes() {
1690 let runtime = QueryRuntime::new();
1691 let result = runtime.query(WithAttributes::new(5)).unwrap();
1693 assert_eq!(*result, 10);
1694 }
1695 }
1696
1697 mod poll_tests {
1699 use super::*;
1700
1701 #[derive(Clone)]
1702 struct Counter {
1703 id: i32,
1704 }
1705
1706 impl Query for Counter {
1707 type CacheKey = i32;
1708 type Output = i32;
1709
1710 fn cache_key(&self) -> Self::CacheKey {
1711 self.id
1712 }
1713
1714 fn query(self, _db: &impl Db) -> Result<Self::Output, QueryError> {
1715 Ok(self.id * 10)
1716 }
1717
1718 fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
1719 old == new
1720 }
1721 }
1722
1723 #[test]
1724 fn test_poll_returns_value_and_revision() {
1725 let runtime = QueryRuntime::new();
1726
1727 let result = runtime.poll(Counter { id: 1 }).unwrap();
1728
1729 assert_eq!(**result.value.as_ref().unwrap(), 10);
1731
1732 assert!(result.revision > 0);
1734 }
1735
1736 #[test]
1737 fn test_poll_revision_stable_on_cache_hit() {
1738 let runtime = QueryRuntime::new();
1739
1740 let result1 = runtime.poll(Counter { id: 1 }).unwrap();
1742 let rev1 = result1.revision;
1743
1744 let result2 = runtime.poll(Counter { id: 1 }).unwrap();
1746 let rev2 = result2.revision;
1747
1748 assert_eq!(rev1, rev2);
1750 }
1751
1752 #[test]
1753 fn test_poll_revision_changes_on_invalidate() {
1754 let runtime = QueryRuntime::new();
1755
1756 let result1 = runtime.poll(Counter { id: 1 }).unwrap();
1758 let rev1 = result1.revision;
1759
1760 runtime.invalidate::<Counter>(&1);
1762 let result2 = runtime.poll(Counter { id: 1 }).unwrap();
1763 let rev2 = result2.revision;
1764
1765 assert_eq!(**result2.value.as_ref().unwrap(), 10);
1769
1770 assert!(rev2 >= rev1);
1773 }
1774
1775 #[test]
1776 fn test_changed_at_returns_none_for_unexecuted_query() {
1777 let runtime = QueryRuntime::new();
1778
1779 let rev = runtime.changed_at::<Counter>(&1);
1781 assert!(rev.is_none());
1782 }
1783
1784 #[test]
1785 fn test_changed_at_returns_revision_after_execution() {
1786 let runtime = QueryRuntime::new();
1787
1788 let _ = runtime.query(Counter { id: 1 }).unwrap();
1790
1791 let rev = runtime.changed_at::<Counter>(&1);
1793 assert!(rev.is_some());
1794 assert!(rev.unwrap() > 0);
1795 }
1796
1797 #[test]
1798 fn test_changed_at_matches_poll_revision() {
1799 let runtime = QueryRuntime::new();
1800
1801 let result = runtime.poll(Counter { id: 1 }).unwrap();
1803
1804 let rev = runtime.changed_at::<Counter>(&1);
1806 assert_eq!(rev, Some(result.revision));
1807 }
1808
1809 #[test]
1810 fn test_poll_value_access() {
1811 let runtime = QueryRuntime::new();
1812
1813 let result = runtime.poll(Counter { id: 5 }).unwrap();
1814
1815 let value: &i32 = result.value.as_ref().unwrap();
1817 assert_eq!(*value, 50);
1818
1819 let arc: &Arc<i32> = result.value.as_ref().unwrap();
1821 assert_eq!(**arc, 50);
1822 }
1823
1824 #[test]
1825 fn test_subscription_pattern() {
1826 let runtime = QueryRuntime::new();
1827
1828 let mut last_revision: RevisionCounter = 0;
1830 let mut notifications = 0;
1831
1832 let result = runtime.poll(Counter { id: 1 }).unwrap();
1834 if result.revision > last_revision {
1835 notifications += 1;
1836 last_revision = result.revision;
1837 }
1838
1839 let result = runtime.poll(Counter { id: 1 }).unwrap();
1841 if result.revision > last_revision {
1842 notifications += 1;
1843 last_revision = result.revision;
1844 }
1845
1846 let result = runtime.poll(Counter { id: 1 }).unwrap();
1848 if result.revision > last_revision {
1849 notifications += 1;
1850 #[allow(unused_assignments)]
1851 {
1852 last_revision = result.revision;
1853 }
1854 }
1855
1856 assert_eq!(notifications, 1);
1858 }
1859 }
1860
1861 mod gc_tests {
1863 use super::*;
1864 use std::collections::HashSet;
1865 use std::sync::atomic::{AtomicUsize, Ordering};
1866 use std::sync::Mutex;
1867
1868 #[derive(Clone)]
1869 struct Leaf {
1870 id: i32,
1871 }
1872
1873 impl Query for Leaf {
1874 type CacheKey = i32;
1875 type Output = i32;
1876
1877 fn cache_key(&self) -> Self::CacheKey {
1878 self.id
1879 }
1880
1881 fn query(self, _db: &impl Db) -> Result<Self::Output, QueryError> {
1882 Ok(self.id * 10)
1883 }
1884
1885 fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
1886 old == new
1887 }
1888 }
1889
1890 #[derive(Clone)]
1891 struct Parent {
1892 child_id: i32,
1893 }
1894
1895 impl Query for Parent {
1896 type CacheKey = i32;
1897 type Output = i32;
1898
1899 fn cache_key(&self) -> Self::CacheKey {
1900 self.child_id
1901 }
1902
1903 fn query(self, db: &impl Db) -> Result<Self::Output, QueryError> {
1904 let child = db.query(Leaf { id: self.child_id })?;
1905 Ok(*child + 1)
1906 }
1907
1908 fn output_eq(old: &Self::Output, new: &Self::Output) -> bool {
1909 old == new
1910 }
1911 }
1912
1913 #[test]
1914 fn test_query_keys_returns_all_cached_queries() {
1915 let runtime = QueryRuntime::new();
1916
1917 let _ = runtime.query(Leaf { id: 1 }).unwrap();
1919 let _ = runtime.query(Leaf { id: 2 }).unwrap();
1920 let _ = runtime.query(Leaf { id: 3 }).unwrap();
1921
1922 let keys = runtime.query_keys();
1924
1925 assert!(keys.len() >= 3);
1927 }
1928
1929 #[test]
1930 fn test_remove_removes_query() {
1931 let runtime = QueryRuntime::new();
1932
1933 let _ = runtime.query(Leaf { id: 1 }).unwrap();
1935
1936 let full_key = FullCacheKey::new::<Leaf, _>(&1);
1938
1939 assert!(runtime.changed_at::<Leaf>(&1).is_some());
1941
1942 assert!(runtime.remove(&full_key));
1944
1945 assert!(runtime.changed_at::<Leaf>(&1).is_none());
1947 }
1948
1949 #[test]
1950 fn test_remove_if_unused_removes_leaf_query() {
1951 let runtime = QueryRuntime::new();
1952
1953 let _ = runtime.query(Leaf { id: 1 }).unwrap();
1955
1956 assert!(runtime.remove_query_if_unused::<Leaf>(&1));
1958
1959 assert!(runtime.changed_at::<Leaf>(&1).is_none());
1961 }
1962
1963 #[test]
1964 fn test_remove_if_unused_does_not_remove_query_with_dependents() {
1965 let runtime = QueryRuntime::new();
1966
1967 let _ = runtime.query(Parent { child_id: 1 }).unwrap();
1969
1970 assert!(!runtime.remove_query_if_unused::<Leaf>(&1));
1972
1973 assert!(runtime.changed_at::<Leaf>(&1).is_some());
1975
1976 assert!(runtime.remove_query_if_unused::<Parent>(&1));
1978 }
1979
1980 #[test]
1981 fn test_remove_if_unused_with_full_cache_key() {
1982 let runtime = QueryRuntime::new();
1983
1984 let _ = runtime.query(Leaf { id: 1 }).unwrap();
1986
1987 let full_key = FullCacheKey::new::<Leaf, _>(&1);
1988
1989 assert!(runtime.remove_if_unused(&full_key));
1991
1992 assert!(runtime.changed_at::<Leaf>(&1).is_none());
1994 }
1995
1996 struct GcTracker {
1998 accessed_keys: Mutex<HashSet<String>>,
1999 access_count: AtomicUsize,
2000 }
2001
2002 impl GcTracker {
2003 fn new() -> Self {
2004 Self {
2005 accessed_keys: Mutex::new(HashSet::new()),
2006 access_count: AtomicUsize::new(0),
2007 }
2008 }
2009 }
2010
2011 impl Tracer for GcTracker {
2012 fn new_span_id(&self) -> SpanId {
2013 SpanId(1)
2014 }
2015
2016 fn on_query_key(&self, full_key: &FullCacheKey) {
2017 self.accessed_keys
2018 .lock()
2019 .unwrap()
2020 .insert(full_key.debug_repr().to_string());
2021 self.access_count.fetch_add(1, Ordering::Relaxed);
2022 }
2023 }
2024
2025 #[test]
2026 fn test_tracer_receives_on_query_key() {
2027 let tracker = GcTracker::new();
2028 let runtime = QueryRuntime::with_tracer(tracker);
2029
2030 let _ = runtime.query(Leaf { id: 1 }).unwrap();
2032 let _ = runtime.query(Leaf { id: 2 }).unwrap();
2033
2034 let count = runtime.tracer().access_count.load(Ordering::Relaxed);
2036 assert_eq!(count, 2);
2037
2038 let keys = runtime.tracer().accessed_keys.lock().unwrap();
2040 assert!(keys.iter().any(|k| k.contains("Leaf")));
2041 }
2042
2043 #[test]
2044 fn test_tracer_receives_on_query_key_for_cache_hits() {
2045 let tracker = GcTracker::new();
2046 let runtime = QueryRuntime::with_tracer(tracker);
2047
2048 let _ = runtime.query(Leaf { id: 1 }).unwrap();
2050 let _ = runtime.query(Leaf { id: 1 }).unwrap();
2051
2052 let count = runtime.tracer().access_count.load(Ordering::Relaxed);
2054 assert_eq!(count, 2);
2055 }
2056
2057 #[test]
2058 fn test_gc_workflow() {
2059 let tracker = GcTracker::new();
2060 let runtime = QueryRuntime::with_tracer(tracker);
2061
2062 let _ = runtime.query(Leaf { id: 1 }).unwrap();
2064 let _ = runtime.query(Leaf { id: 2 }).unwrap();
2065 let _ = runtime.query(Leaf { id: 3 }).unwrap();
2066
2067 let mut removed = 0;
2069 for key in runtime.query_keys() {
2070 if runtime.remove_if_unused(&key) {
2071 removed += 1;
2072 }
2073 }
2074
2075 assert!(removed >= 3);
2077
2078 assert!(runtime.changed_at::<Leaf>(&1).is_none());
2080 assert!(runtime.changed_at::<Leaf>(&2).is_none());
2081 assert!(runtime.changed_at::<Leaf>(&3).is_none());
2082 }
2083 }
2084}