1use crate::{Engine, EngineResult, RetrieveInput, RetrieveOutput};
6use hippmem_core::hash::stable_hash64;
7use hippmem_core::ids::MemoryId;
8use hippmem_core::model::links::{ActivationStep, RecallChannel, RetrievalResult};
9use hippmem_core::model::unit::MemoryUnit;
10use hippmem_core::time::Clock;
11use hippmem_model::deterministic::extract::DeterministicExtractor;
12use hippmem_model::lang::active_locales;
13use hippmem_retrieval::explain::deduce_dimensions;
14use hippmem_retrieval::seeds::{multi_channel_seeds, rrf_fuse};
15use hippmem_retrieval::spreading::spread_multi_hop_fused;
16use hippmem_retrieval::warnings::check_warnings;
17use hippmem_store::activation_log::ActivationLogger;
18use hippmem_store::kv::InvertedIndex;
19use hippmem_store::semantic::vector_index::BinaryIndex;
20use hippmem_store::semantic::vector_index::VectorIndex;
21use std::collections::HashMap;
22
23impl Engine {
24 pub fn retrieve(&self, input: RetrieveInput) -> EngineResult<RetrieveOutput> {
26 let params = self.params.read();
27
28 let extractor = DeterministicExtractor;
30 let query_content = hippmem_core::model::unit::MemoryContent {
31 raw: input.query.clone(),
32 summary: None,
33 normalized: None,
34 language: hippmem_core::model::unit::Language::Zh,
35 content_type: hippmem_core::model::enums::ContentType::UserStatement,
36 };
37 let understanding = extractor
38 .extract_sync_immediate(&query_content)
39 .unwrap_or_else(|_| hippmem_model::traits::ImmediateExtraction {
40 entities: vec![],
41 topics: vec![],
42 explicit_causals: vec![],
43 language: hippmem_core::model::unit::Language::Zh,
44 content_type: None,
45 importance: hippmem_core::score::UnitScore::new(0.0),
46 });
47
48 let inverted = InvertedIndex::new(self.store.db_arc());
50
51 let entity_hits: Vec<(MemoryId, f32)> = understanding
53 .entities
54 .iter()
55 .filter_map(|em| {
56 let key = hippmem_core::hash::stable_hash64(&em.canonical);
57 inverted.get_entity(&key).ok().map(|ids| {
58 ids.into_iter()
59 .map(|id| (MemoryId(id), 0.2f32))
60 .collect::<Vec<_>>()
61 })
62 })
63 .flatten()
64 .collect();
65
66 let topic_hits: Vec<(MemoryId, f32)> = understanding
68 .topics
69 .iter()
70 .filter_map(|t| {
71 let key = hippmem_core::hash::stable_hash64(&t.label);
72 inverted.get_topic(&key).ok().map(|ids| {
73 ids.into_iter()
74 .map(|id| (MemoryId(id), 0.15f32))
75 .collect::<Vec<_>>()
76 })
77 })
78 .flatten()
79 .collect();
80
81 let now = hippmem_core::time::SystemClock.now();
83 let temporal_keys = temporal_bucket_keys(now);
84 let mut temporal_hit_ids = std::collections::HashSet::new();
85 for tk in &temporal_keys {
86 if let Ok(ids) = inverted.get_temporal(tk) {
87 for id in ids {
88 temporal_hit_ids.insert(MemoryId(id));
89 }
90 }
91 }
92 let temporal_hits: Vec<(MemoryId, bool)> =
93 temporal_hit_ids.into_iter().map(|id| (id, true)).collect();
94
95 let bm25_hits: Vec<(MemoryId, f32)> = self
97 .fulltext_index
98 .lock()
99 .search(&input.query, params.seed_per_channel as usize)
100 .unwrap_or_default()
101 .into_iter()
102 .map(|(id, score)| {
103 let norm = (score / params.bm25_norm_factor).tanh();
104 (MemoryId(id), norm)
105 })
106 .collect();
107
108 let semantic_hits: Vec<(MemoryId, f32)> = {
110 let query_texts = vec![input.query.clone()];
111 self.embedder
112 .embed_sync(&query_texts)
113 .ok()
114 .and_then(|vectors| vectors.first().cloned())
115 .map(|query_vec| {
116 let idx = self.dense_vector_index.lock();
117 idx.search(&query_vec, params.seed_per_channel as usize)
118 .unwrap_or_default()
119 .into_iter()
120 .map(|(id, l2_dist)| {
121 let cos_sim = 1.0 / (1.0 + l2_dist);
123 (MemoryId(id), cos_sim)
124 })
125 .filter(|(_, sim)| *sim > 0.0)
126 .collect()
127 })
128 .unwrap_or_default()
129 };
130
131 let binary_hits: Vec<(MemoryId, f32)> = {
133 let query_bc = query_binary_code(&input.query);
134 let idx = self.binary_code_index.lock();
135 idx.search(&query_bc, params.seed_per_channel as usize)
136 .unwrap_or_default()
137 .into_iter()
138 .map(|(id, hamming)| {
139 let sim = 1.0 - (hamming as f32 / 128.0);
140 (MemoryId(id), sim.max(0.0))
141 })
142 .filter(|(_, sim)| *sim > 0.0)
143 .collect()
144 };
145
146 let query_goals = extract_query_goals(&input.query);
148 let goal_hits: Vec<(MemoryId, usize)> = query_goals
149 .iter()
150 .filter_map(|goal| {
151 let key = stable_hash64(goal);
152 inverted.get_goal(&key).ok().map(|ids| {
153 ids.into_iter()
154 .map(|id| (MemoryId(id), 1))
155 .collect::<Vec<_>>()
156 })
157 })
158 .flatten()
159 .collect();
160
161 let query_events = extract_query_events(&input.query);
163 let event_hits: Vec<(MemoryId, usize)> = query_events
164 .iter()
165 .filter_map(|event| {
166 let key = stable_hash64(event);
167 inverted.get_event(&key).ok().map(|ids| {
168 ids.into_iter()
169 .map(|id| (MemoryId(id), 1))
170 .collect::<Vec<_>>()
171 })
172 })
173 .flatten()
174 .collect();
175
176 let causal_hits: Vec<(MemoryId, usize)> = understanding
178 .explicit_causals
179 .iter()
180 .filter_map(|c| {
181 let causal_str = format!("{} -> {}", c.cause, c.effect);
182 let key = stable_hash64(&causal_str);
183 inverted.get_causal(&key).ok().map(|ids| {
184 ids.into_iter()
185 .map(|id| (MemoryId(id), 1))
186 .collect::<Vec<_>>()
187 })
188 })
189 .flatten()
190 .collect();
191
192 let recent_hits: Vec<(MemoryId, f32)> = {
194 let mut recent_map: HashMap<MemoryId, f32> = HashMap::new();
195
196 for mid in &input.context.recent_memory_ids {
198 recent_map
199 .entry(*mid)
200 .and_modify(|s| *s = (*s + 0.3).min(1.0))
201 .or_insert(0.3);
202 }
203
204 let graph = hippmem_store::graph::GraphStore::new(self.store.db_arc());
206 for mid in &input.context.recent_memory_ids {
207 if let Ok(links) = graph.get_outgoing(mid) {
208 for link in links.iter().take(8) {
209 recent_map
210 .entry(link.target_id)
211 .and_modify(|s| *s = (*s + 0.15).min(1.0))
212 .or_insert(0.15);
213 }
214 }
215 }
216
217 let act_log = ActivationLogger::new(self.store.db_arc());
219 if let Ok(records) = act_log.read_all() {
220 let mut freq: HashMap<MemoryId, u32> = HashMap::new();
221 for rec in records.iter() {
222 for mid_u64 in &rec.used_memory_ids {
223 *freq.entry(MemoryId(*mid_u64 as u128)).or_default() += 1;
224 }
225 }
226 let max_freq = freq.values().max().copied().unwrap_or(1) as f32;
227 for (mid, count) in freq {
228 let score = (count as f32 / max_freq) * 0.25;
229 recent_map
230 .entry(mid)
231 .and_modify(|s| *s = (*s + score).min(1.0))
232 .or_insert(score);
233 }
234 }
235
236 let mut hits: Vec<(MemoryId, f32)> = recent_map.into_iter().collect();
237 hits.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
238 hits.truncate(params.seed_per_channel as usize);
239 hits
240 };
241
242 let seed_result = multi_channel_seeds(
243 &input.query,
244 &entity_hits,
245 &temporal_hits,
246 &semantic_hits,
247 &topic_hits,
248 &bm25_hits,
249 &binary_hits,
250 &goal_hits,
251 &event_hits,
252 &causal_hits,
253 &recent_hits,
254 params.seed_per_channel as usize,
255 );
256
257 let fused_scores: HashMap<MemoryId, (f32, RecallChannel)> = if seed_result.seeds.is_empty()
259 {
260 let fallback = load_limited_units(self.store.db_arc(), 50);
262 fallback
263 .into_iter()
264 .map(|u| (u.id, (0.3_f32, RecallChannel::RecentActivation)))
265 .collect()
266 } else {
267 rrf_fuse(&seed_result.seeds, ¶ms)
268 };
269
270 let seed_ids: Vec<MemoryId> = fused_scores.keys().cloned().collect();
272 let mut unit_map: HashMap<MemoryId, MemoryUnit> = HashMap::new();
273 for unit in load_units_by_ids(self.store.db_arc(), &seed_ids) {
274 unit_map.insert(unit.id, unit);
275 }
276
277 let importance_map: HashMap<MemoryId, f32> = unit_map
279 .iter()
280 .map(|(id, unit)| (*id, unit.understanding.importance.value()))
281 .collect();
282
283 let graph = hippmem_store::graph::GraphStore::new(self.store.db_arc());
284 let mut links_map: HashMap<MemoryId, Vec<hippmem_core::model::links::AssociationLink>> =
285 HashMap::new();
286
287 for sid in &seed_ids {
289 if let Ok(links) = graph.get_outgoing(sid) {
290 links_map.insert(*sid, links);
291 }
292 }
293
294 let neighbor_ids: Vec<MemoryId> = links_map
296 .values()
297 .flatten()
298 .map(|l| l.target_id)
299 .filter(|tid| !links_map.contains_key(tid))
300 .collect();
301 for nid in &neighbor_ids {
302 if let Ok(links) = graph.get_outgoing(nid) {
303 links_map.insert(*nid, links);
304 }
305 }
306 for unit in load_units_by_ids(self.store.db_arc(), &neighbor_ids) {
308 unit_map.entry(unit.id).or_insert(unit);
309 }
310
311 let activated = spread_multi_hop_fused(&fused_scores, &links_map, ¶ms, &importance_map);
313 let max_k = input.top_k.min(activated.len());
314
315 let extra_ids: Vec<MemoryId> = activated
317 .iter()
318 .map(|(id, _, _)| *id)
319 .filter(|id| !unit_map.contains_key(id))
320 .collect();
321 for unit in load_units_by_ids(self.store.db_arc(), &extra_ids) {
322 unit_map.insert(unit.id, unit);
323 }
324
325 let loaded_units: Vec<MemoryUnit> = activated
327 .iter()
328 .filter_map(|(id, _, _)| unit_map.get(id).cloned())
329 .collect();
330 let mut reranked = hippmem_retrieval::rerank::rerank_by_energy(&activated, &loaded_units);
331
332 apply_question_aware_boost(&input.query, &mut reranked, ¶ms);
335
336 let results: Vec<RetrievalResult> = reranked
338 .iter()
339 .take(max_k)
340 .map(|(_id, energy, trace, unit)| {
341 let matched = deduce_dimensions(trace);
342 let warns = check_warnings(unit, *energy);
343 RetrievalResult {
344 memory: unit.clone(),
345 final_score: *energy,
346 activation_trace: trace.clone(),
347 matched_dimensions: matched,
348 warnings: warns,
349 }
350 })
351 .collect();
352
353 let channel_contributions: Vec<(RecallChannel, u32)> = {
355 let mut map: HashMap<RecallChannel, u32> = HashMap::new();
356 for seed in &seed_result.seeds {
357 *map.entry(seed.channel).or_default() += 1;
358 }
359 map.into_iter().collect()
360 };
361
362 {
364 let act_log = ActivationLogger::new(self.store.db_arc());
365 let used_ids: Vec<u64> = results.iter().map(|r| r.memory.id.0 as u64).collect();
366 let now_ms =
367 if let Ok(t) = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
368 t.as_millis() as i64
369 } else {
370 0
371 };
372 let _ = act_log.record(&hippmem_store::activation_log::ActivationRecord {
373 retrieval_id: now_ms as u64,
374 used_memory_ids: used_ids,
375 signal: "retrieve".into(),
376 recorded_at_ms: now_ms,
377 });
378 }
379
380 Ok(RetrieveOutput {
381 results,
382 trace: crate::RetrievalTrace {
383 seeds: seed_result
384 .seeds
385 .iter()
386 .map(|s| crate::SeedRecord {
387 id: s.id,
388 channel: s.channel,
389 initial_energy: s.score,
390 rank_in_channel: s.rank_in_channel,
391 })
392 .collect(),
393 steps: activated
394 .iter()
395 .flat_map(|(_, _, trace)| trace.clone())
396 .collect(),
397 hops_used: 0,
398 merged_count: 0,
399 },
400 diagnostics: crate::RetrievalDiagnostics {
401 channel_contributions,
402 reranked: true,
403 pruned_branches: 0,
404 backend_used: crate::BackendUsage {
405 embedder: self.embedder.backend_id().to_string(),
406 reranker: Some("rule".into()),
407 },
408 latency_ms: 0,
409 },
410 })
411 }
412}
413
414#[derive(Debug, Clone, Copy, PartialEq)]
420enum QuestionType {
421 Why,
423 How,
425 What,
427 Correction,
429 Preference,
431 None,
433}
434
435fn detect_question_type(query: &str) -> QuestionType {
441 let q = query.to_lowercase();
442
443 for lang in active_locales() {
445 if let Some((before, after)) = lang.change_pair {
446 if q.contains(before) && q.contains(after) {
447 return QuestionType::Correction;
448 }
449 }
450 }
451
452 for lang in active_locales() {
456 for keyword in lang.q_correction {
457 if q.contains(keyword) {
458 return QuestionType::Correction;
459 }
460 }
461 }
462 for lang in active_locales() {
463 for keyword in lang.q_preference {
464 if q.contains(keyword) {
465 return QuestionType::Preference;
466 }
467 }
468 }
469 for lang in active_locales() {
470 for keyword in lang.q_why {
471 if q.contains(keyword) {
472 return QuestionType::Why;
473 }
474 }
475 }
476 for lang in active_locales() {
477 for keyword in lang.q_how {
478 if q.contains(keyword) {
479 return QuestionType::How;
480 }
481 }
482 }
483 for lang in active_locales() {
484 for keyword in lang.q_what {
485 if q.contains(keyword) {
486 return QuestionType::What;
487 }
488 }
489 }
490 QuestionType::None
491}
492
493fn explanatory_pattern_score(text: &str) -> f32 {
495 let mut score = 0.0f32;
496 for lang in active_locales() {
497 for (pattern, boost) in lang.explanatory {
498 if text.contains(pattern) {
499 score += boost;
500 }
501 }
502 }
503 score.min(0.20) }
505
506fn content_type_boost(query: &str) -> Vec<(hippmem_core::model::unit::ContentType, f32)> {
516 let qt = detect_question_type(query);
517 let mut boosts = Vec::new();
518
519 match qt {
520 QuestionType::Correction => {
521 boosts.push((hippmem_core::model::unit::ContentType::Correction, 0.12));
523 }
524 QuestionType::Preference => {
525 boosts.push((hippmem_core::model::unit::ContentType::Preference, 0.08));
527 boosts.push((hippmem_core::model::unit::ContentType::Decision, 0.04));
529 }
530 QuestionType::Why => {
531 boosts.push((hippmem_core::model::unit::ContentType::Decision, 0.08));
533 boosts.push((hippmem_core::model::unit::ContentType::TaskState, 0.08));
534 }
535 QuestionType::How => {
536 boosts.push((hippmem_core::model::unit::ContentType::TaskState, 0.08));
538 }
539 QuestionType::What => {
540 boosts.push((
546 hippmem_core::model::unit::ContentType::ProjectKnowledge,
547 0.15,
548 ));
549 }
550 QuestionType::None => {
551 }
553 }
554
555 if qt != QuestionType::Correction {
557 let q = query.to_lowercase();
558 let has_correction_signal = active_locales().iter().any(|lang| {
559 lang.q_correction.iter().any(|kw| q.contains(kw))
560 || lang
561 .change_pair
562 .is_some_and(|(b, a)| q.contains(b) && q.contains(a))
563 });
564 if has_correction_signal {
565 boosts.push((hippmem_core::model::unit::ContentType::Correction, 0.10));
566 }
567 }
568
569 boosts
570}
571
572fn apply_question_aware_boost(
582 query: &str,
583 reranked: &mut [(MemoryId, f32, Vec<ActivationStep>, MemoryUnit)],
584 params: &hippmem_core::config::AlgoParams,
585) {
586 let qt = detect_question_type(query);
587 let ct_boosts = content_type_boost(query);
588 let cap = params.seed_energy_cap;
589 let what_subject: Option<String> = if qt == QuestionType::What {
591 extract_subject_for_what_query(query)
592 } else {
593 None
594 };
595
596 match qt {
598 QuestionType::Why => {
599 for (_, energy, _, unit) in reranked.iter_mut() {
600 let boost = explanatory_pattern_score(&unit.content.raw);
601 if boost > 0.0 {
602 *energy = (*energy + boost).min(cap);
603 }
604 }
605 }
606 QuestionType::Correction
607 | QuestionType::Preference
608 | QuestionType::How
609 | QuestionType::What
610 | QuestionType::None => {
611 }
613 }
614
615 if !ct_boosts.is_empty() {
619 for (_, energy, _, unit) in reranked.iter_mut() {
620 for (ct, boost) in &ct_boosts {
621 if unit.content.content_type != *ct {
622 continue;
623 }
624 if qt == QuestionType::What
626 && *ct == hippmem_core::model::unit::ContentType::ProjectKnowledge
627 {
628 if let Some(ref subject) = what_subject {
629 let content_lower = unit.content.raw.to_lowercase();
630 if !content_lower.contains(&subject.to_lowercase()) {
631 break; }
633 }
634 }
635 *energy = (*energy + boost).min(cap);
636 break; }
638 }
639 }
640
641 let keywords = extract_discriminative_keywords(query);
647 if !keywords.is_empty() {
648 for (_, energy, _, unit) in reranked.iter_mut() {
649 let mut kw_bonus = 0.0f32;
650 let content_lower = unit.content.raw.to_lowercase();
651 for kw in &keywords {
652 if content_lower.contains(&kw.to_lowercase()) {
653 kw_bonus += 0.04;
654 }
655 }
656 if kw_bonus > 0.0 {
657 *energy = (*energy + kw_bonus.min(0.08)).min(cap);
658 }
659 }
660 }
661
662 if qt == QuestionType::What {
667 if let Some(ref subject) = extract_subject_for_what_query(query) {
668 let subject_lower = subject.to_lowercase();
669 for (_, energy, _, unit) in reranked.iter_mut() {
670 let content_lower = unit.content.raw.to_lowercase();
671 let has_definition = active_locales().iter().any(|lang| {
672 lang.definition_patterns
673 .iter()
674 .any(|pat| content_lower.contains(&format!("{} {pat}", subject_lower)))
675 });
676 if has_definition {
677 *energy = (*energy + 0.05).min(cap);
678 }
679 }
680 }
681 }
682
683 reranked.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
685}
686
687fn extract_subject_for_what_query(query: &str) -> Option<String> {
696 let q = query.to_lowercase();
697 for lang in active_locales() {
698 for delimiter in lang.what_delimiters {
699 if let Some(pos) = q.find(delimiter) {
700 let prefix = &q[..pos];
701 let subject = if let Some(particle) = lang.possessive_particle {
702 prefix
705 .rsplit(particle)
706 .next()
707 .unwrap_or("")
708 .rsplit(|c: char| c.is_whitespace() || c == '?' || c == '?')
709 .next()
710 .unwrap_or("")
711 .trim()
712 .to_string()
713 } else {
714 prefix
715 .rsplit(|c: char| c.is_whitespace() || c == '?' || c == '?')
716 .next()
717 .unwrap_or("")
718 .trim()
719 .to_string()
720 };
721 if subject.len() >= 2 {
722 return Some(subject);
723 }
724 return None;
725 }
726 }
727 }
728 None
729}
730
731fn extract_discriminative_keywords(query: &str) -> Vec<String> {
739 let stop_words: Vec<&str> = active_locales()
741 .iter()
742 .flat_map(|lang| lang.stop_words.iter().copied())
743 .collect();
744
745 let mut keywords: Vec<String> = Vec::new();
746 let mut seen = std::collections::HashSet::new();
747
748 for word in query.split(|c: char| !c.is_alphanumeric()) {
750 let is_keyword = (word.len() >= 2 && word.chars().any(|c| c.is_uppercase()))
751 || (word.chars().all(|c| c.is_ascii_alphabetic()) && word.len() >= 3);
752 if is_keyword
753 && !stop_words.contains(&word.to_lowercase().as_str())
754 && seen.insert(word.to_string())
755 {
756 keywords.push(word.to_string());
757 }
758 }
759
760 for word in query
762 .split(|c: char| c.is_whitespace() || c.is_ascii_punctuation() || c == '?' || c == '?')
763 {
764 let trimmed = word.trim();
765 if trimmed.chars().count() >= 2
766 && trimmed.chars().all(|c| c as u32 > 0x2E80) && !stop_words.contains(&trimmed)
768 && seen.insert(trimmed.to_string())
769 {
770 keywords.push(trimmed.to_string());
771 }
772 }
773
774 keywords.truncate(5); keywords
776}
777
778fn query_binary_code(text: &str) -> [u8; 16] {
780 let bc0 = stable_hash64(&format!("bc_0_{}", text));
781 let bc1 = stable_hash64(&format!("bc_1_{}", text));
782 let mut bytes = [0u8; 16];
783 bytes[..8].copy_from_slice(&bc0.to_le_bytes());
784 bytes[8..].copy_from_slice(&bc1.to_le_bytes());
785 bytes
786}
787
788fn temporal_bucket_keys(ts: hippmem_core::time::Timestamp) -> Vec<u32> {
790 let ms = ts.0;
791 vec![
792 (ms / 3_600_000) as u32, (ms / 86_400_000) as u32, (ms / 604_800_000) as u32, ]
796}
797
798pub(crate) fn load_all_units(db: std::sync::Arc<redb::Database>) -> Vec<MemoryUnit> {
799 use redb::ReadableDatabase;
800 use redb::ReadableTable;
801 let mut units = Vec::new();
802 let read_txn = db.begin_read().expect("read transaction should succeed");
803 let table = read_txn
804 .open_table(hippmem_store::store::MEMORY_KV)
805 .expect("memory_kv table should exist");
806 let iter = table.iter().expect("iter should succeed");
807 for entry in iter.flatten() {
808 let (_key, value) = entry;
809 if let Ok((unit, _)) = bincode::serde::decode_from_slice::<MemoryUnit, _>(
810 value.value(),
811 bincode::config::standard(),
812 ) {
813 units.push(unit);
814 }
815 }
816 units
817}
818
819fn load_units_by_ids(db: std::sync::Arc<redb::Database>, ids: &[MemoryId]) -> Vec<MemoryUnit> {
821 if ids.is_empty() {
822 return vec![];
823 }
824 use redb::ReadableDatabase;
825 let mut units = Vec::new();
826 let read_txn = db.begin_read().expect("read transaction should succeed");
827 let table = read_txn
828 .open_table(hippmem_store::store::MEMORY_KV)
829 .expect("memory_kv table should exist");
830 for id in ids {
831 if let Some(value) = table.get(id.0).expect("get should succeed") {
832 if let Ok((unit, _)) = bincode::serde::decode_from_slice::<MemoryUnit, _>(
833 value.value(),
834 bincode::config::standard(),
835 ) {
836 units.push(unit);
837 }
838 }
839 }
840 units
841}
842
843fn extract_query_goals(text: &str) -> Vec<String> {
845 let mut goals = Vec::new();
846 for lang in active_locales() {
847 for m in lang.goal_markers {
848 if text.contains(m) {
849 goals.push(format!("goal_marker:{m}"));
850 }
851 }
852 }
853 goals
854}
855
856fn extract_query_events(text: &str) -> Vec<String> {
858 let mut events = Vec::new();
859 for lang in active_locales() {
860 for m in lang.event_markers {
861 if text.contains(m) {
862 events.push(format!("event_marker:{m}"));
863 }
864 }
865 }
866 events
867}
868
869fn load_limited_units(db: std::sync::Arc<redb::Database>, limit: usize) -> Vec<MemoryUnit> {
871 use redb::ReadableDatabase;
872 use redb::ReadableTable;
873 let mut units = Vec::new();
874 let read_txn = db.begin_read().expect("read transaction should succeed");
875 let table = read_txn
876 .open_table(hippmem_store::store::MEMORY_KV)
877 .expect("memory_kv table should exist");
878 let iter = table.iter().expect("iter should succeed");
879 for entry in iter.flatten().take(limit) {
880 let (_key, value) = entry;
881 if let Ok((unit, _)) = bincode::serde::decode_from_slice::<MemoryUnit, _>(
882 value.value(),
883 bincode::config::standard(),
884 ) {
885 units.push(unit);
886 }
887 }
888 units
889}