From 807f398d81a2f30c182c4557773521549918bcce Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Tue, 25 Aug 2026 15:46:39 +0300 Subject: [PATCH 01/14] First repo code --- Cargo.lock | 25 ++-- Cargo.toml | 5 +- benches/repo_cache_bench.rs | 285 ++++++++++++++++++++++++++++++++++++ src/data.rs | 1 + src/data/entities.rs | 50 +++++++ src/data/repositories.rs | 46 ++++++ 6 files changed, 399 insertions(+), 13 deletions(-) create mode 100644 benches/repo_cache_bench.rs create mode 100644 src/data/repositories.rs diff --git a/Cargo.lock b/Cargo.lock index f86de79..6a66117 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1066,7 +1066,7 @@ dependencies = [ "cosmic-text", "etagere", "lru", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "wgpu", ] @@ -2074,7 +2074,7 @@ dependencies = [ "lilt", "log", "num-traits", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "smol_str", "thiserror 2.0.18", "web-time", @@ -2100,7 +2100,7 @@ dependencies = [ "futures", "iced_core", "log", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "tokio", "wasm-bindgen-futures", "wasmtimer", @@ -2120,7 +2120,7 @@ dependencies = [ "iced_futures", "log", "raw-window-handle", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "thiserror 2.0.18", "unicode-segmentation", ] @@ -2174,7 +2174,7 @@ dependencies = [ "kurbo 0.10.4", "log", "resvg", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "softbuffer", "tiny-skia", ] @@ -2195,7 +2195,7 @@ dependencies = [ "iced_graphics", "log", "resvg", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "thiserror 2.0.18", "wgpu", ] @@ -2210,7 +2210,7 @@ dependencies = [ "log", "num-traits", "ouroboros", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "thiserror 2.0.18", "unicode-segmentation", ] @@ -2225,7 +2225,7 @@ dependencies = [ "iced_program", "log", "mundy", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "thiserror 2.0.18", "tracing", "wasm-bindgen-futures", @@ -2439,6 +2439,7 @@ dependencies = [ "rhai", "rodio", "rusqlite", + "rustc-hash 2.1.3", "serde_json", "sha2", "tokio", @@ -3835,7 +3836,7 @@ dependencies = [ "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "rustls", "socket2", "thiserror 2.0.18", @@ -3857,7 +3858,7 @@ dependencies = [ "rand", "rand_pcg", "ring", - "rustc-hash 2.1.1", + "rustc-hash 2.1.3", "rustls", "rustls-pki-types", "slab", @@ -4256,9 +4257,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" diff --git a/Cargo.toml b/Cargo.toml index 06ba459..ff04fc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,9 +37,12 @@ panic = "abort" [dev-dependencies] criterion = { version = "0.8.2", features = ["html_reports"] } +rustc-hash = "2.1.3" [[bench]] name = "map_bench" harness = false - +[[bench]] +name = "repo_cache_bench" +harness = false diff --git a/benches/repo_cache_bench.rs b/benches/repo_cache_bench.rs new file mode 100644 index 0000000..3d13a1a --- /dev/null +++ b/benches/repo_cache_bench.rs @@ -0,0 +1,285 @@ +use criterion::{criterion_group, criterion_main, Criterion, BenchmarkId}; +use std::collections::{BTreeMap, HashMap}; +use std::hint::black_box; +use hashbrown::HashMap as HashbrownMap; +use rustc_hash::FxHashMap; + +// Тестовая структура (аналог вашей сущности) +#[derive(Debug, Clone)] +struct Entity { + id: i64, + data: [u8; 64], // 64 байта данных для реалистичности +} + +impl Entity { + fn new(id: i64) -> Self { + Self { + id, + data: [id as u8; 64], + } + } +} + +// ========== ТЕСТОВЫЕ ФУНКЦИИ ========== + +fn bench_vec_insert_sorted(vec: &mut Vec, entities: &[Entity]) { + vec.clear(); + for entity in entities { + vec.push(entity.clone()); + } + // Vec уже отсортирован, так как ID монотонно возрастают +} + +fn bench_vec_search_separated<'a>(ids: &[i64], vec: &'a [Entity], id: i64) -> Option<&'a Entity> { + match ids.binary_search(&id) { + Ok(index) => Some(&vec[index]), + Err(_) => None, + } +} + +fn bench_vec_search(vec: &Vec, id: i64) -> Option<&Entity> { + match vec.binary_search_by(|probe| probe.id.cmp(&id)) { + Ok(index) => Some(&vec[index]), + Err(_) => None, + } +} + +fn bench_btreemap_insert(map: &mut BTreeMap, entities: &[Entity]) { + map.clear(); + for entity in entities { + map.insert(entity.id, entity.clone()); + } +} + +fn bench_btreemap_search(map: &BTreeMap, id: i64) -> Option<&Entity> { + map.get(&id) +} + +fn bench_hashmap_insert(map: &mut HashMap, entities: &[Entity]) { + map.clear(); + for entity in entities { + map.insert(entity.id, entity.clone()); + } +} + +fn bench_hashmap_search(map: &HashMap, id: i64) -> Option<&Entity> { + map.get(&id) +} + +fn bench_hashbrown_insert(map: &mut HashbrownMap, entities: &[Entity]) { + map.clear(); + for entity in entities { + map.insert(entity.id, entity.clone()); + } +} + +fn bench_hashbrown_search(map: &HashbrownMap, id: i64) -> Option<&Entity> { + map.get(&id) +} + +fn bench_fxhashmap_insert(map: &mut FxHashMap, entities: &[Entity]) { + map.clear(); + for entity in entities { + map.insert(entity.id, entity.clone()); + } +} + +fn bench_fxhashmap_search(map: &FxHashMap, id: i64) -> Option<&Entity> { + map.get(&id) +} + +// ========== ГЕНЕРАЦИЯ ДАННЫХ ========== + +fn generate_entities(count: usize) -> Vec { + (0..count).map(|i| Entity::new(i as i64)).collect() +} + +fn generate_search_ids(count: usize, max_id: i64) -> Vec { + // 70% попаданий, 30% промахов (реалистичный сценарий) + let mut ids = Vec::with_capacity(count); + for i in 0..count { + if i % 10 < 7 { + // Попадание + ids.push((i as i64 * 13) % max_id); + } else { + // Промах (несуществующий ID) + ids.push(max_id + i as i64); + } + } + ids +} + +// ========== БЕНЧМАРКИ ========== + +fn bench_insert(c: &mut Criterion) { + let mut group = c.benchmark_group("insert"); + + for size in [10_000, 100_000, 500_000] { + let entities = generate_entities(size); + + group.bench_with_input(BenchmarkId::new("Vec (sorted)", size), &entities, |b, entities| { + let mut vec = Vec::with_capacity(size); + b.iter(|| { + bench_vec_insert_sorted(&mut vec, black_box(entities)); + }); + }); + + group.bench_with_input(BenchmarkId::new("BTreeMap", size), &entities, |b, entities| { + let mut map = BTreeMap::new(); + b.iter(|| { + bench_btreemap_insert(&mut map, black_box(entities)); + }); + }); + + group.bench_with_input(BenchmarkId::new("HashMap (std)", size), &entities, |b, entities| { + let mut map = HashMap::new(); + b.iter(|| { + bench_hashmap_insert(&mut map, black_box(entities)); + }); + }); + + group.bench_with_input(BenchmarkId::new("HashMap (hashbrown)", size), &entities, |b, entities| { + let mut map = HashbrownMap::new(); + b.iter(|| { + bench_hashbrown_insert(&mut map, black_box(entities)); + }); + }); + + group.bench_with_input(BenchmarkId::new("FxHashMap", size), &entities, |b, entities| { + let mut map = FxHashMap::default(); + b.iter(|| { + bench_fxhashmap_insert(&mut map, black_box(entities)); + }); + }); + } + + group.finish(); +} + +fn bench_search(c: &mut Criterion) { + let mut group = c.benchmark_group("search"); + + for size in [10_000, 100_000, 500_000] { + let entities = generate_entities(size); + let entity_ids = entities.iter().map(|entity| entity.id).collect::>(); + let search_ids = generate_search_ids(1000, size as i64); + + // Подготовка коллекций + let mut vec = Vec::with_capacity(size); + bench_vec_insert_sorted(&mut vec, &entities); + + let mut btreemap = BTreeMap::new(); + bench_btreemap_insert(&mut btreemap, &entities); + + let mut hashmap = HashMap::new(); + bench_hashmap_insert(&mut hashmap, &entities); + + let mut hashbrown = HashbrownMap::new(); + bench_hashbrown_insert(&mut hashbrown, &entities); + + let mut fxhashmap = FxHashMap::default(); + bench_fxhashmap_insert(&mut fxhashmap, &entities); + + group.bench_with_input(BenchmarkId::new("Vec (binary search)", size), &search_ids, |b, ids| { + b.iter(|| { + for &id in ids { + black_box(bench_vec_search_separated(&entity_ids, &vec, black_box(id))); + } + }); + }); + + group.bench_with_input(BenchmarkId::new("Vec (binary search) separated", size), &search_ids, |b, ids| { + b.iter(|| { + for &id in ids { + black_box(bench_vec_search(&vec, black_box(id))); + } + }); + }); + + group.bench_with_input(BenchmarkId::new("BTreeMap", size), &search_ids, |b, ids| { + b.iter(|| { + for &id in ids { + black_box(bench_btreemap_search(&btreemap, black_box(id))); + } + }); + }); + + group.bench_with_input(BenchmarkId::new("HashMap (std)", size), &search_ids, |b, ids| { + b.iter(|| { + for &id in ids { + black_box(bench_hashmap_search(&hashmap, black_box(id))); + } + }); + }); + + group.bench_with_input(BenchmarkId::new("HashMap (hashbrown)", size), &search_ids, |b, ids| { + b.iter(|| { + for &id in ids { + black_box(bench_hashbrown_search(&hashbrown, black_box(id))); + } + }); + }); + + group.bench_with_input(BenchmarkId::new("FxHashMap", size), &search_ids, |b, ids| { + b.iter(|| { + for &id in ids { + black_box(bench_fxhashmap_search(&fxhashmap, black_box(id))); + } + }); + }); + } + + group.finish(); +} + +fn bench_memory_efficiency(c: &mut Criterion) { + let mut group = c.benchmark_group("memory_efficiency"); + + let size = 100_000; + let entities = generate_entities(size); + + group.bench_function("Vec memory", |b| { + b.iter(|| { + let mut vec = Vec::with_capacity(size); + bench_vec_insert_sorted(&mut vec, black_box(&entities)); + black_box(vec); + }); + }); + + group.bench_function("BTreeMap memory", |b| { + b.iter(|| { + let mut map = BTreeMap::new(); + bench_btreemap_insert(&mut map, black_box(&entities)); + black_box(map); + }); + }); + + group.bench_function("HashMap (std) memory", |b| { + b.iter(|| { + let mut map = HashMap::new(); + bench_hashmap_insert(&mut map, black_box(&entities)); + black_box(map); + }); + }); + + group.bench_function("HashMap (hashbrown) memory", |b| { + b.iter(|| { + let mut map = HashbrownMap::new(); + bench_hashbrown_insert(&mut map, black_box(&entities)); + black_box(map); + }); + }); + + group.bench_function("FxHashMap memory", |b| { + b.iter(|| { + let mut map = FxHashMap::default(); + bench_fxhashmap_insert(&mut map, black_box(&entities)); + black_box(map); + }); + }); + + group.finish(); +} + +criterion_group!(benches, bench_insert, bench_search, bench_memory_efficiency); +criterion_main!(benches); \ No newline at end of file diff --git a/src/data.rs b/src/data.rs index 4589b9b..6c0c113 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,4 +1,5 @@ pub mod entities; +pub mod repositories; use rusqlite::ToSql; use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef}; diff --git a/src/data/entities.rs b/src/data/entities.rs index 7342947..401e5b3 100644 --- a/src/data/entities.rs +++ b/src/data/entities.rs @@ -1,11 +1,14 @@ use crate::app::AppState; use crate::core::srs::{FADE_PER_DAY, MAX_SCORE, OrderMode, WordOpenMode}; use crate::data::{INVALID_ID, Id}; +use crate::notification::MaybeError; use chrono::{DateTime, Utc}; use log::info; use rayon::prelude::*; use rhai::{Engine, Scope}; use std::collections::HashMap; +use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; use std::time::Instant; #[derive(Clone, Debug)] @@ -164,3 +167,50 @@ impl CardStatistics { (self.score as f32 * multiplier).max(1.0) } } + +#[derive(Clone)] +pub struct Entity> { + pub(crate) repo: R, + pub(crate) _phantom: PhantomData, + pub(crate) inner: EntityInner, +} + +#[derive(Clone)] +pub(crate) struct EntityInner { + pub(crate) id: Id, + pub(crate) real: bool, +} + +impl> Entity { + pub fn load(&self) -> EntityAccess { + let clone = *self.clone(); + let repo = &self.repo; + let value = repo.get(self.inner.id).unwrap(); + EntityAccess { value, entity: clone, } + } +} + +pub struct EntityAccess> { + entity: Entity, + value: E, +} + +impl> Deref for EntityAccess { + type Target = E; + + fn deref(&self) -> &Self::Target { + &self.value + } +} + +impl> DerefMut for EntityAccess { + fn deref_mut(&mut self) -> &mut E { + &mut self.value + } +} + +pub trait Repository { + fn get(&self, id: Id) -> Option; + fn create(&mut self, new: T) -> Entity>; + fn save(&mut self, entity: EntityInner) -> MaybeError; +} diff --git a/src/data/repositories.rs b/src/data/repositories.rs new file mode 100644 index 0000000..dad8999 --- /dev/null +++ b/src/data/repositories.rs @@ -0,0 +1,46 @@ +use crate::data::Id; +use crate::data::entities::{Entity, EntityInner, Repository, WordData}; +use crate::notification::MaybeError; +use parking_lot::{Mutex, RwLock}; +use rusqlite::Connection; +use std::marker::PhantomData; +use std::sync::Arc; + +#[derive(Clone)] +pub struct WordRepository { + inner: Arc>, +} + +pub struct WordRepositoryInner { + cache: Vec, + temporal: Vec>, + next_temporal: u32, + connection: Arc>, +} + +impl Repository for WordRepository { + fn get(&self, id: Id) -> Option { + let inner = self.inner.read(); + Some(inner.cache[inner.cache.binary_search_by(|w| w.id.cmp(&id)).ok()?].clone()) + } + + fn create(&mut self, new: WordData) -> Entity { + let mut inner = self.inner.write(); + let next_temporal = inner.next_temporal; + inner.next_temporal += 1; + drop(inner); + let entity = Entity { + inner: EntityInner { + id: next_temporal.into(), + real: false, + }, + repo: self.clone(), + _phantom: PhantomData, + }; + entity + } + + fn save(&mut self, entity: EntityInner) -> MaybeError { + todo!() + } +} From d99fd337da44cec01c9e971f808b0bdf8b719b3f Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Wed, 26 Aug 2026 17:53:09 +0300 Subject: [PATCH 02/14] More repo code --- src/data/entities.rs | 57 +++----- src/data/repositories.rs | 272 +++++++++++++++++++++++++++++++++++---- src/main.rs | 1 + src/tests.rs | 92 +++++++++++++ 4 files changed, 359 insertions(+), 63 deletions(-) create mode 100644 src/tests.rs diff --git a/src/data/entities.rs b/src/data/entities.rs index 401e5b3..f46bcb0 100644 --- a/src/data/entities.rs +++ b/src/data/entities.rs @@ -20,6 +20,17 @@ pub struct WordData { pub additional: HashMap, pub group_id: Id, } + +impl EntityData for WordData { + fn id(&self) -> Id { + self.id + } + + fn set_id(&mut self, id: Id) { + self.id = id; + } +} + #[derive(Clone)] pub struct WordGroup { pub id: Id, @@ -168,49 +179,15 @@ impl CardStatistics { } } -#[derive(Clone)] -pub struct Entity> { - pub(crate) repo: R, +#[derive(Clone, Copy)] +pub struct EntityHandle { pub(crate) _phantom: PhantomData, - pub(crate) inner: EntityInner, -} - -#[derive(Clone)] -pub(crate) struct EntityInner { pub(crate) id: Id, pub(crate) real: bool, + pub(crate) index: u32, } -impl> Entity { - pub fn load(&self) -> EntityAccess { - let clone = *self.clone(); - let repo = &self.repo; - let value = repo.get(self.inner.id).unwrap(); - EntityAccess { value, entity: clone, } - } -} - -pub struct EntityAccess> { - entity: Entity, - value: E, -} - -impl> Deref for EntityAccess { - type Target = E; - - fn deref(&self) -> &Self::Target { - &self.value - } -} - -impl> DerefMut for EntityAccess { - fn deref_mut(&mut self) -> &mut E { - &mut self.value - } -} - -pub trait Repository { - fn get(&self, id: Id) -> Option; - fn create(&mut self, new: T) -> Entity>; - fn save(&mut self, entity: EntityInner) -> MaybeError; +pub trait EntityData{ + fn id(&self) -> Id; + fn set_id(&mut self, id: Id); } diff --git a/src/data/repositories.rs b/src/data/repositories.rs index dad8999..ee3fa58 100644 --- a/src/data/repositories.rs +++ b/src/data/repositories.rs @@ -1,46 +1,272 @@ use crate::data::Id; -use crate::data::entities::{Entity, EntityInner, Repository, WordData}; +use crate::data::entities::{EntityData, EntityHandle, WordData}; use crate::notification::MaybeError; use parking_lot::{Mutex, RwLock}; use rusqlite::Connection; use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; use std::sync::Arc; +use iced::application::IntoBoot; +use rusqlite::fallible_iterator::Iterator; + +pub trait Repository where D : DbLayer { + fn get(&self, handle: &mut EntityHandle) -> Option; + fn get_ref(&self, handle: &mut EntityHandle) -> Option<&T>; + fn get_mut(&mut self, handle: &mut EntityHandle) -> Option<&mut T>; + fn handle_by_id(&self, id: Id) -> EntityHandle; + fn create(&mut self, new: T) -> EntityHandle; + fn save(&mut self, entity: &mut EntityHandle) -> MaybeError; + fn delete(&mut self, handle: EntityHandle) -> MaybeError; + fn init(&mut self) -> MaybeError; + fn reload(&mut self) -> MaybeError; +} + +pub trait BatchedRepository where D : BatchedDbLayer, T: EntityData +{ + fn save_many(&mut self, handles: &mut [EntityHandle]) -> MaybeError; + fn delete_many(&mut self, handles: &mut [EntityHandle]) -> MaybeError; + +} + +pub trait DbLayer { + fn write_entity(&self, entity: &T, connection: &Connection) -> MaybeError; + fn add_entity(&mut self, entity: &mut T, connection: &Connection) -> MaybeError; + fn delete_entity(&mut self, id: Id, connection: &Connection) -> MaybeError; + fn load_all(&self, storage: &mut Vec, connection: &Connection) -> MaybeError; +} + +pub trait BatchedDbLayer where T : EntityData{ + fn delete_many(&mut self, ids: &[Id], connection: &Connection) -> MaybeError; + fn add_many_entity(&mut self, entity: &mut [T], connection: &Connection) -> MaybeError; +} + #[derive(Clone)] -pub struct WordRepository { - inner: Arc>, +pub struct DataRepository { + inner: Arc>>, } -pub struct WordRepositoryInner { - cache: Vec, - temporal: Vec>, +pub struct DataRepositoryInner { + cache: Vec, + temporal: Vec, next_temporal: u32, connection: Arc>, + data_layer: D, } -impl Repository for WordRepository { - fn get(&self, id: Id) -> Option { - let inner = self.inner.read(); - Some(inner.cache[inner.cache.binary_search_by(|w| w.id.cmp(&id)).ok()?].clone()) +impl Deref for DataRepository { + type Target = Arc>>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl> Repository for DataRepositoryInner { + fn get(&self, handle: &mut EntityHandle) -> Option { + let slice = if handle.real { + self.cache.as_slice() + } else { + self.temporal.as_slice() + }; + let index = fast_position_of(slice, handle)?; + Some(slice[index].clone()) } - fn create(&mut self, new: WordData) -> Entity { - let mut inner = self.inner.write(); - let next_temporal = inner.next_temporal; - inner.next_temporal += 1; - drop(inner); - let entity = Entity { - inner: EntityInner { - id: next_temporal.into(), - real: false, - }, - repo: self.clone(), + fn get_ref(&self, handle: &mut EntityHandle) -> Option<&T> { + let slice = if handle.real { + self.cache.as_slice() + } else { + self.temporal.as_slice() + }; + let index = fast_position_of(slice, handle)?; + Some(&slice[index]) + } + + fn get_mut(&mut self, handle: &mut EntityHandle) -> Option<&mut T> { + let slice = if handle.real { + self.cache.as_mut_slice() + } else { + self.temporal.as_mut_slice() + }; + let index = fast_position_of(slice, handle)?; + Some(&mut slice[index]) + } + + fn handle_by_id(&self, id: Id) -> EntityHandle { + EntityHandle{ + _phantom: Default::default(), + id, + real: true, + index: id.0, + } + } + + // fn get_static(&self, mut handle: EntityHandle) -> Option { + // let slice = if handle.real { + // self.cache.as_slice() + // } else { + // self.temporal.as_slice() + // }; + // let index = fast_position_of(slice, &mut handle)?; + // Some(slice[index].clone()) + // } + // + // fn get_ref_static(&self, mut handle: EntityHandle) -> Option<&T> { + // let slice = if handle.real { + // self.cache.as_slice() + // } else { + // self.temporal.as_slice() + // }; + // let index = fast_position_of(slice, &mut handle)?; + // Some(&slice[index]) + // } + // + // fn get_mut_static(&mut self, mut handle: EntityHandle) -> Option<&mut T> { + // let slice = if handle.real { + // self.cache.as_mut_slice() + // } else { + // self.temporal.as_mut_slice() + // }; + // let index = fast_position_of(slice, &mut handle)?; + // Some(&mut slice[index]) + // } + + fn create(&mut self, mut new: T) -> EntityHandle { + let next_temporal = self.next_temporal; + self.next_temporal += 1; + let entity = EntityHandle { + id: next_temporal.into(), + real: false, + index: self.temporal.len() as u32, _phantom: PhantomData, }; + new.set_id(entity.id); + self.temporal.push(new); entity } - fn save(&mut self, entity: EntityInner) -> MaybeError { - todo!() + fn save(&mut self, entity: &mut EntityHandle) -> MaybeError { + if entity.real { + let data = self.get_ref(entity).unwrap(); + self.data_layer.write_entity(data, &self.connection.lock())?; + } else { + let index = fast_position_of(self.temporal.as_slice(), entity).unwrap(); + let mut value = self.temporal.remove(index); + self.data_layer.add_entity(&mut value, &self.connection.lock())?; + entity.real = true; + entity.id = value.id(); + entity.index = self.cache.len() as u32; + self.cache.push(value); + } + + Ok(()) + } + + fn delete(&mut self, mut handle: EntityHandle) -> MaybeError { + + let vec = if handle.real { + self.data_layer.delete_entity(handle.id, &self.connection.lock())?; + &mut self.cache + } else { + &mut self.temporal + }; + + let index = fast_position_of(vec.as_slice(), &mut handle).unwrap(); + vec.remove(index); + Ok(()) + } + + fn init(&mut self) -> MaybeError { + self.data_layer.load_all(&mut self.cache, &self.connection.lock())?; + Ok(()) + } + + fn reload(&mut self) -> MaybeError { + self.cache.clear(); + self.temporal.clear(); + self.init()?; + Ok(()) } } +impl> BatchedRepository for DataRepositoryInner { + fn save_many(&mut self, handles: &mut [EntityHandle]) -> MaybeError { + todo!() + } + + fn delete_many(&mut self, handles: &mut [EntityHandle]) -> MaybeError { + let mut temp = Vec::with_capacity(handles.len() / 2); + let mut cached = Vec::with_capacity(handles.len() / 2); + let mut cell = 0; + handles.sort_by(|a, b| {a.id.cmp(&b.id)}); + handles.iter().for_each(|handle| if handle.real { cached.push(handle.id)} else { + temp.push(handle.id) + }); + + let len = temp.len(); + self.temporal.retain(|x| { + if cell == len { return true; } + let find = temp[cell]; + let remove = x.id() == find; + if remove { cell += 1; } + !remove + }); + self.data_layer.delete_many(cached.as_slice(), &self.connection.lock())?; + let len = cached.len(); + cell = 0; + self.cache.retain(|x| { + if cell == len { return true; } + let find = cached[cell]; + let remove = x.id() == find; + if remove { cell += 1; } + !remove + }); + + Ok(()) + } +} + +pub fn fast_position_of( + storage: &[E], + handle: &mut EntityHandle, +) -> Option { + if storage.is_empty() { + return None; + } + + let max_idx = storage.len() - 1; + let upper = (handle.index as usize).min(max_idx); + let peek = storage[upper].id(); + + if peek == handle.id { + handle.index = upper as u32; + return Some(upper); + } + + if peek.0 < handle.id.0 { + return None; + } + + let target_id_val = handle.id.0 as i64; + let peek_val = peek.0 as i64; + let delta = peek_val - target_id_val; + + let new_index = (upper as i64 - delta).max(0) as usize; + + if storage[new_index].id() == handle.id { + handle.index = new_index as u32; + return Some(new_index); + } + + if new_index == upper { + return None; + } + + let index = storage[new_index..upper] + .binary_search_by(|e| e.id().cmp(&handle.id)) + .ok()? + + new_index; + + handle.index = index as u32; + Some(index) +} diff --git a/src/main.rs b/src/main.rs index 34b8108..58e8c7a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ mod navigation; pub mod notification; mod pages; mod styling; +pub mod tests; use crate::app::start; use mimalloc::MiMalloc; diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..db4efda --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,92 @@ +#[cfg(test)] +mod mod_tests { + use crate::data::Id; + use crate::data::entities::{EntityData, EntityHandle}; + use crate::data::repositories::fast_position_of; + + struct ProtoEntity { + id: Id, + _data: i128, + } + impl EntityData for ProtoEntity { + fn id(&self) -> Id { + self.id + } + + fn set_id(&mut self, id: Id) { + self.id = id; + } + } + + impl ProtoEntity { + fn new(n: i128) -> Self { + Self { + id: (n as u32).into(), + _data: n, + } + } + } + + #[test] + fn search_direct() { + let mut entity = EntityHandle { + _phantom: Default::default(), + id: 8.into(), + real: true, + index: 3, + }; + assert_eq!( + fast_position_of(&[1, 2, 3, 8, 9, 12].map(ProtoEntity::new), &mut entity), + Some(3) + ); + } + + #[test] + fn search_0() { + let mut entity = EntityHandle { + _phantom: Default::default(), + id: 8.into(), + real: true, + index: 4, + }; + assert_eq!( + fast_position_of(&[1, 2, 3, 8, 9, 12].map(ProtoEntity::new), &mut entity), + Some(3) + ); + } + + #[test] + fn search_1() { + let mut entity = EntityHandle { + _phantom: Default::default(), + id: 9.into(), + real: true, + index: 5, + }; + assert_eq!( + fast_position_of(&[1, 2, 3, 8, 9, 12].map(ProtoEntity::new), &mut entity), + Some(1) + ); + } + + #[test] + fn search_2() { + let mut entity = EntityHandle { + _phantom: Default::default(), + id: 106.into(), + real: true, + index: 5, + }; + assert_eq!( + fast_position_of( + &[ + 1, 2, 3, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 20, 21, 22, 23, 105, 106, 107, + 108, 109, 110 + ] + .map(ProtoEntity::new), + &mut entity + ), + Some(1) + ); + } +} From 3eba0c93d4e356bf4ca96b40fe8db1c7148fed47 Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Wed, 26 Aug 2026 19:48:22 +0300 Subject: [PATCH 03/14] save_many code --- src/data/repositories.rs | 134 ++++++++++++++++++++++++++++++--------- 1 file changed, 104 insertions(+), 30 deletions(-) diff --git a/src/data/repositories.rs b/src/data/repositories.rs index ee3fa58..1a6967a 100644 --- a/src/data/repositories.rs +++ b/src/data/repositories.rs @@ -1,15 +1,16 @@ use crate::data::Id; -use crate::data::entities::{EntityData, EntityHandle, WordData}; +use crate::data::entities::{EntityData, EntityHandle}; use crate::notification::MaybeError; use parking_lot::{Mutex, RwLock}; use rusqlite::Connection; use std::marker::PhantomData; -use std::ops::{Deref, DerefMut}; +use std::ops::Deref; use std::sync::Arc; -use iced::application::IntoBoot; -use rusqlite::fallible_iterator::Iterator; -pub trait Repository where D : DbLayer { +pub trait Repository +where + D: DbLayer, +{ fn get(&self, handle: &mut EntityHandle) -> Option; fn get_ref(&self, handle: &mut EntityHandle) -> Option<&T>; fn get_mut(&mut self, handle: &mut EntityHandle) -> Option<&mut T>; @@ -21,26 +22,30 @@ pub trait Repository where D : DbLayer { fn reload(&mut self) -> MaybeError; } -pub trait BatchedRepository where D : BatchedDbLayer, T: EntityData +pub trait BatchedRepository +where + D: BatchedDbLayer, + T: EntityData, { fn save_many(&mut self, handles: &mut [EntityHandle]) -> MaybeError; fn delete_many(&mut self, handles: &mut [EntityHandle]) -> MaybeError; - } pub trait DbLayer { fn write_entity(&self, entity: &T, connection: &Connection) -> MaybeError; fn add_entity(&mut self, entity: &mut T, connection: &Connection) -> MaybeError; fn delete_entity(&mut self, id: Id, connection: &Connection) -> MaybeError; - fn load_all(&self, storage: &mut Vec, connection: &Connection) -> MaybeError; + fn load_all(&self, storage: &mut Vec, connection: &Connection) -> MaybeError; } -pub trait BatchedDbLayer where T : EntityData{ +pub trait BatchedDbLayer +where + T: EntityData, +{ fn delete_many(&mut self, ids: &[Id], connection: &Connection) -> MaybeError; fn add_many_entity(&mut self, entity: &mut [T], connection: &Connection) -> MaybeError; } - #[derive(Clone)] pub struct DataRepository { inner: Arc>>, @@ -94,7 +99,7 @@ impl> Repository for DataRepositoryIn } fn handle_by_id(&self, id: Id) -> EntityHandle { - EntityHandle{ + EntityHandle { _phantom: Default::default(), id, real: true, @@ -149,11 +154,13 @@ impl> Repository for DataRepositoryIn fn save(&mut self, entity: &mut EntityHandle) -> MaybeError { if entity.real { let data = self.get_ref(entity).unwrap(); - self.data_layer.write_entity(data, &self.connection.lock())?; + self.data_layer + .write_entity(data, &self.connection.lock())?; } else { let index = fast_position_of(self.temporal.as_slice(), entity).unwrap(); let mut value = self.temporal.remove(index); - self.data_layer.add_entity(&mut value, &self.connection.lock())?; + self.data_layer + .add_entity(&mut value, &self.connection.lock())?; entity.real = true; entity.id = value.id(); entity.index = self.cache.len() as u32; @@ -164,9 +171,9 @@ impl> Repository for DataRepositoryIn } fn delete(&mut self, mut handle: EntityHandle) -> MaybeError { - let vec = if handle.real { - self.data_layer.delete_entity(handle.id, &self.connection.lock())?; + self.data_layer + .delete_entity(handle.id, &self.connection.lock())?; &mut self.cache } else { &mut self.temporal @@ -178,7 +185,8 @@ impl> Repository for DataRepositoryIn } fn init(&mut self) -> MaybeError { - self.data_layer.load_all(&mut self.cache, &self.connection.lock())?; + self.data_layer + .load_all(&mut self.cache, &self.connection.lock())?; Ok(()) } @@ -189,43 +197,109 @@ impl> Repository for DataRepositoryIn Ok(()) } } -impl> BatchedRepository for DataRepositoryInner { +impl + DbLayer> BatchedRepository +for DataRepositoryInner +{ fn save_many(&mut self, handles: &mut [EntityHandle]) -> MaybeError { + let mut temp = Vec::with_capacity(handles.len() / 2); + let mut cached = Vec::with_capacity(handles.len() / 2); + handles.sort_by_key(|a| a.id); + handles.iter_mut().for_each(|handle| { + if handle.real { + cached.push(handle) + } else { + temp.push(handle) + } + }); + + let mut values = Vec::with_capacity(temp.len()); + let mut cell = 0; + let len = temp.len(); + self.temporal.retain(|x| { + if cell == len { + return true; + } + let find = temp[cell].id; + let remove = x.id() == find; + if remove || find > x.id() { + cell += 1; + values.push(x.clone()); + } + !remove + }); + + self.data_layer + .add_many_entity(&mut values, &self.connection.lock())?; + let mut index = self.cache.len(); + values + .iter() + .zip(temp) + .for_each(|(x, y)| { + y.id = x.id(); + y.index = index as u32; + index += 1; + }); + + for in_cache in cached { + let value = self.get_ref(in_cache).unwrap(); + self.data_layer.write_entity(value, &self.connection.lock())?; + } todo!() } fn delete_many(&mut self, handles: &mut [EntityHandle]) -> MaybeError { - let mut temp = Vec::with_capacity(handles.len() / 2); - let mut cached = Vec::with_capacity(handles.len() / 2); - let mut cell = 0; - handles.sort_by(|a, b| {a.id.cmp(&b.id)}); - handles.iter().for_each(|handle| if handle.real { cached.push(handle.id)} else { - temp.push(handle.id) - }); + let (cached, temp) = split_temp_real(handles, |e| e.id); + let mut cell = 0; let len = temp.len(); self.temporal.retain(|x| { - if cell == len { return true; } + if cell == len { + return true; + } let find = temp[cell]; let remove = x.id() == find; - if remove { cell += 1; } + if remove || find > x.id() { + cell += 1; + } !remove }); - self.data_layer.delete_many(cached.as_slice(), &self.connection.lock())?; + self.data_layer + .delete_many(cached.as_slice(), &self.connection.lock())?; let len = cached.len(); cell = 0; self.cache.retain(|x| { - if cell == len { return true; } + if cell == len { + return true; + } let find = cached[cell]; let remove = x.id() == find; - if remove { cell += 1; } + if remove || find > x.id() { + cell += 1; + } !remove }); - Ok(()) + Ok(()) } } +fn split_temp_real(handles: &mut [EntityHandle], getter: F) -> (Vec, Vec) +where + F: Fn(&EntityHandle) -> V, +{ + let mut temp = Vec::with_capacity(handles.len() / 2); + let mut cached = Vec::with_capacity(handles.len() / 2); + handles.sort_by(|a, b| a.id.cmp(&b.id)); + handles.iter().for_each(|handle| { + if handle.real { + cached.push(getter(handle)) + } else { + temp.push(getter(handle)) + } + }); + (cached, temp) +} + pub fn fast_position_of( storage: &[E], handle: &mut EntityHandle, From 0326b56ded5cbab29be414e05fc6de7d18208f7c Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Wed, 26 Aug 2026 23:55:24 +0300 Subject: [PATCH 04/14] Update save_many --- src/data/entities.rs | 1 - src/data/repositories.rs | 36 ++++++++++++++++++++---------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/data/entities.rs b/src/data/entities.rs index f46bcb0..5959cfc 100644 --- a/src/data/entities.rs +++ b/src/data/entities.rs @@ -8,7 +8,6 @@ use rayon::prelude::*; use rhai::{Engine, Scope}; use std::collections::HashMap; use std::marker::PhantomData; -use std::ops::{Deref, DerefMut}; use std::time::Instant; #[derive(Clone, Debug)] diff --git a/src/data/repositories.rs b/src/data/repositories.rs index 1a6967a..d8b8948 100644 --- a/src/data/repositories.rs +++ b/src/data/repositories.rs @@ -221,7 +221,7 @@ for DataRepositoryInner } let find = temp[cell].id; let remove = x.id() == find; - if remove || find > x.id() { + if remove { cell += 1; values.push(x.clone()); } @@ -230,21 +230,25 @@ for DataRepositoryInner self.data_layer .add_many_entity(&mut values, &self.connection.lock())?; - let mut index = self.cache.len(); - values - .iter() - .zip(temp) - .for_each(|(x, y)| { - y.id = x.id(); - y.index = index as u32; - index += 1; - }); - for in_cache in cached { - let value = self.get_ref(in_cache).unwrap(); - self.data_layer.write_entity(value, &self.connection.lock())?; + let start = self.cache.len(); + self.cache.extend(values); + + for (i, handle) in temp.into_iter().enumerate() { + let h = handle; + h.id = self.cache[start + i].id(); + h.index = (start + i) as u32; + h.real = true; } - todo!() + + for handle in cached.iter_mut() { + let h = &mut **handle; + let idx = fast_position_of(self.cache.as_slice(), h).unwrap(); + self.data_layer + .write_entity(&self.cache[idx], &self.connection.lock())?; + } + + Ok(()) } fn delete_many(&mut self, handles: &mut [EntityHandle]) -> MaybeError { @@ -258,7 +262,7 @@ for DataRepositoryInner } let find = temp[cell]; let remove = x.id() == find; - if remove || find > x.id() { + if remove { cell += 1; } !remove @@ -273,7 +277,7 @@ for DataRepositoryInner } let find = cached[cell]; let remove = x.id() == find; - if remove || find > x.id() { + if remove { cell += 1; } !remove From 0f2090af7784048786a8ad53a516c912e7327870 Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Thu, 27 Aug 2026 09:21:16 +0300 Subject: [PATCH 05/14] Test update, repo update --- src/data/entities.rs | 3 ++- src/data/repositories.rs | 4 ++-- src/tests.rs | 6 +++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/data/entities.rs b/src/data/entities.rs index 5959cfc..17703c6 100644 --- a/src/data/entities.rs +++ b/src/data/entities.rs @@ -180,10 +180,11 @@ impl CardStatistics { #[derive(Clone, Copy)] pub struct EntityHandle { - pub(crate) _phantom: PhantomData, pub(crate) id: Id, pub(crate) real: bool, pub(crate) index: u32, + pub(crate) _phantom: PhantomData, + } pub trait EntityData{ diff --git a/src/data/repositories.rs b/src/data/repositories.rs index d8b8948..d517e3a 100644 --- a/src/data/repositories.rs +++ b/src/data/repositories.rs @@ -241,8 +241,8 @@ for DataRepositoryInner h.real = true; } - for handle in cached.iter_mut() { - let h = &mut **handle; + for handle in cached.into_iter() { + let h = handle; let idx = fast_position_of(self.cache.as_slice(), h).unwrap(); self.data_layer .write_entity(&self.cache[idx], &self.connection.lock())?; diff --git a/src/tests.rs b/src/tests.rs index db4efda..a49b7f9 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -65,7 +65,7 @@ mod mod_tests { }; assert_eq!( fast_position_of(&[1, 2, 3, 8, 9, 12].map(ProtoEntity::new), &mut entity), - Some(1) + Some(4) ); } @@ -75,7 +75,7 @@ mod mod_tests { _phantom: Default::default(), id: 106.into(), real: true, - index: 5, + index: 25, }; assert_eq!( fast_position_of( @@ -86,7 +86,7 @@ mod mod_tests { .map(ProtoEntity::new), &mut entity ), - Some(1) + Some(18) ); } } From 89aa1abc13f2ed80ebba21f2ce2d389664237f84 Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Thu, 27 Aug 2026 14:05:05 +0300 Subject: [PATCH 06/14] Query partial code --- src/data/repositories.rs | 41 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/data/repositories.rs b/src/data/repositories.rs index d517e3a..3c723ba 100644 --- a/src/data/repositories.rs +++ b/src/data/repositories.rs @@ -5,6 +5,8 @@ use parking_lot::{Mutex, RwLock}; use rusqlite::Connection; use std::marker::PhantomData; use std::ops::Deref; +use std::rc::Weak; +use std::slice::Iter; use std::sync::Arc; pub trait Repository @@ -20,6 +22,32 @@ where fn delete(&mut self, handle: EntityHandle) -> MaybeError; fn init(&mut self) -> MaybeError; fn reload(&mut self) -> MaybeError; + fn query(&mut self, rule: fn(&T) -> bool) -> Query where Self: Sized; +} + +struct QueryManger +where + R: Repository, + D: DbLayer, +{ + queries: Mutex>>>, +} + +pub struct Query +where + R: Repository, D: DbLayer +{ + cache: Vec>, + rule: fn(&T) -> bool, + repo: Arc, + _marker: PhantomData, +} + +impl, D: DbLayer> Query +{ + fn iter<'a>(&mut self, data_repository_inner: &'a R) -> Iter<'_, &'a T> { + self.cache.iter_mut().filter_map(|e| data_repository_inner.get_ref(e)) + } } pub trait BatchedRepository @@ -47,19 +75,20 @@ where } #[derive(Clone)] -pub struct DataRepository { +pub struct DataRepository> { inner: Arc>>, } -pub struct DataRepositoryInner { +pub struct DataRepositoryInner> { cache: Vec, temporal: Vec, next_temporal: u32, connection: Arc>, data_layer: D, + query_manger: QueryManger } -impl Deref for DataRepository { +impl> Deref for DataRepository { type Target = Arc>>; fn deref(&self) -> &Self::Target { @@ -196,9 +225,13 @@ impl> Repository for DataRepositoryIn self.init()?; Ok(()) } + + fn query(&mut self, rule: fn(&T) -> bool) -> Query { + todo!() + } } impl + DbLayer> BatchedRepository -for DataRepositoryInner + for DataRepositoryInner { fn save_many(&mut self, handles: &mut [EntityHandle]) -> MaybeError { let mut temp = Vec::with_capacity(handles.len() / 2); From 70aff6ed183103d3931521652cc7218e7677afd1 Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Thu, 27 Aug 2026 14:51:35 +0300 Subject: [PATCH 07/14] Better query code --- src/data/repositories.rs | 156 ++++++++++++++++++++++++++++----------- 1 file changed, 111 insertions(+), 45 deletions(-) diff --git a/src/data/repositories.rs b/src/data/repositories.rs index 3c723ba..24a090a 100644 --- a/src/data/repositories.rs +++ b/src/data/repositories.rs @@ -2,11 +2,13 @@ use crate::data::Id; use crate::data::entities::{EntityData, EntityHandle}; use crate::notification::MaybeError; use parking_lot::{Mutex, RwLock}; +use rayon::iter::IndexedParallelIterator; +use rayon::iter::IntoParallelRefIterator; +use rayon::iter::ParallelIterator; use rusqlite::Connection; use std::marker::PhantomData; use std::ops::Deref; use std::rc::Weak; -use std::slice::Iter; use std::sync::Arc; pub trait Repository @@ -22,7 +24,6 @@ where fn delete(&mut self, handle: EntityHandle) -> MaybeError; fn init(&mut self) -> MaybeError; fn reload(&mut self) -> MaybeError; - fn query(&mut self, rule: fn(&T) -> bool) -> Query where Self: Sized; } struct QueryManger @@ -35,18 +36,33 @@ where pub struct Query where - R: Repository, D: DbLayer + R: Repository, + D: DbLayer, { cache: Vec>, - rule: fn(&T) -> bool, - repo: Arc, - _marker: PhantomData, + rule: Box bool>, + _marker: PhantomData<(R, D)>, } -impl, D: DbLayer> Query -{ - fn iter<'a>(&mut self, data_repository_inner: &'a R) -> Iter<'_, &'a T> { - self.cache.iter_mut().filter_map(|e| data_repository_inner.get_ref(e)) +impl, D: DbLayer> Query { + pub fn iter<'a>(&mut self, data_repository_inner: &'a R) -> impl Iterator + where + T: 'a, + { + self.cache + .iter_mut() + .filter_map(move |e| data_repository_inner.get_ref(e)) + } + + pub fn for_each_mut(&mut self, repo: &mut R, mut f: F) + where + F: FnMut(&mut T), + { + for handle in &mut self.cache { + if let Some(entity) = repo.get_mut(handle) { + f(entity); + } + } } } @@ -85,7 +101,7 @@ pub struct DataRepositoryInner> next_temporal: u32, connection: Arc>, data_layer: D, - query_manger: QueryManger + query_manger: QueryManger, } impl> Deref for DataRepository { @@ -96,6 +112,90 @@ impl> Deref for DataRepository< } } +impl> DataRepository { + pub fn query_par( + &self, + rule: Box bool + Send + Sync>, + ) -> Query, D> + where + T: Send + Sync, + { + let inner = self.inner.read(); + + let mut cache: Vec> = inner + .cache + .par_iter() + .enumerate() + .filter(|(_, entity)| rule(entity)) + .map(|(i, entity)| EntityHandle { + id: entity.id(), + real: true, + index: i as u32, + _phantom: PhantomData, + }) + .collect(); + + cache.extend(inner + .temporal + .iter() + .enumerate() + .filter(|(_, entity)| rule(entity)) + .map(|(i, entity)| EntityHandle { + id: entity.id(), + real: false, + index: i as u32, + _phantom: PhantomData, + }).collect::>()); + + Query { + cache, + rule, + _marker: PhantomData, + } + } + + pub fn query( + &self, + rule: Box bool>, + ) -> Query, D> + where + T: Send + Sync, + { + let inner = self.inner.read(); + + let mut cache: Vec> = inner + .cache + .iter() + .enumerate() + .filter(|(_, entity)| rule(entity)) + .map(|(i, entity)| EntityHandle { + id: entity.id(), + real: true, + index: i as u32, + _phantom: PhantomData, + }) + .collect(); + + cache.extend(inner + .temporal + .iter() + .enumerate() + .filter(|(_, entity)| rule(entity)) + .map(|(i, entity)| EntityHandle { + id: entity.id(), + real: false, + index: i as u32, + _phantom: PhantomData, + }).collect::>()); + + Query { + cache, + rule, + _marker: PhantomData, + } + } +} + impl> Repository for DataRepositoryInner { fn get(&self, handle: &mut EntityHandle) -> Option { let slice = if handle.real { @@ -136,36 +236,6 @@ impl> Repository for DataRepositoryIn } } - // fn get_static(&self, mut handle: EntityHandle) -> Option { - // let slice = if handle.real { - // self.cache.as_slice() - // } else { - // self.temporal.as_slice() - // }; - // let index = fast_position_of(slice, &mut handle)?; - // Some(slice[index].clone()) - // } - // - // fn get_ref_static(&self, mut handle: EntityHandle) -> Option<&T> { - // let slice = if handle.real { - // self.cache.as_slice() - // } else { - // self.temporal.as_slice() - // }; - // let index = fast_position_of(slice, &mut handle)?; - // Some(&slice[index]) - // } - // - // fn get_mut_static(&mut self, mut handle: EntityHandle) -> Option<&mut T> { - // let slice = if handle.real { - // self.cache.as_mut_slice() - // } else { - // self.temporal.as_mut_slice() - // }; - // let index = fast_position_of(slice, &mut handle)?; - // Some(&mut slice[index]) - // } - fn create(&mut self, mut new: T) -> EntityHandle { let next_temporal = self.next_temporal; self.next_temporal += 1; @@ -225,10 +295,6 @@ impl> Repository for DataRepositoryIn self.init()?; Ok(()) } - - fn query(&mut self, rule: fn(&T) -> bool) -> Query { - todo!() - } } impl + DbLayer> BatchedRepository for DataRepositoryInner From a01a29c492d6a662de9e527e16c875d06b72d833 Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Fri, 28 Aug 2026 16:52:48 +0300 Subject: [PATCH 08/14] Query manager code --- src/data/entities.rs | 40 ++++++++- src/data/repositories.rs | 185 +++++++++++++++++++++++++++++++-------- 2 files changed, 186 insertions(+), 39 deletions(-) diff --git a/src/data/entities.rs b/src/data/entities.rs index 17703c6..e3ff883 100644 --- a/src/data/entities.rs +++ b/src/data/entities.rs @@ -1,3 +1,4 @@ +use std::cmp::Ordering; use crate::app::AppState; use crate::core::srs::{FADE_PER_DAY, MAX_SCORE, OrderMode, WordOpenMode}; use crate::data::{INVALID_ID, Id}; @@ -178,7 +179,7 @@ impl CardStatistics { } } -#[derive(Clone, Copy)] +#[derive(Copy)] pub struct EntityHandle { pub(crate) id: Id, pub(crate) real: bool, @@ -187,6 +188,43 @@ pub struct EntityHandle { } +impl Clone for EntityHandle { + fn clone(&self) -> Self { + EntityHandle{ + id: self.id, + real: self.real, + index: self.index, + _phantom: Default::default(), + } + } +} +impl Eq for EntityHandle {} + +impl PartialEq for EntityHandle { + fn eq(&self, other: &Self) -> bool { + self.real == other.real && self.id == other.id + } +} +impl PartialOrd for EntityHandle { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for EntityHandle { + fn cmp(&self, other: &Self) -> Ordering { + if self.real && !other.real { + return Ordering::Less + } + + if !self.real && other.real{ + return Ordering::Greater + } + + self.id.cmp(&other.id) + } +} + pub trait EntityData{ fn id(&self) -> Id; fn set_id(&mut self, id: Id); diff --git a/src/data/repositories.rs b/src/data/repositories.rs index 24a090a..30bd545 100644 --- a/src/data/repositories.rs +++ b/src/data/repositories.rs @@ -1,11 +1,13 @@ use crate::data::Id; use crate::data::entities::{EntityData, EntityHandle}; -use crate::notification::MaybeError; +use crate::helpers::into_result; +use crate::notification::{MaybeError, NotificationData}; use parking_lot::{Mutex, RwLock}; use rayon::iter::IndexedParallelIterator; use rayon::iter::IntoParallelRefIterator; use rayon::iter::ParallelIterator; use rusqlite::Connection; +use std::cell::RefCell; use std::marker::PhantomData; use std::ops::Deref; use std::rc::Weak; @@ -31,20 +33,75 @@ where R: Repository, D: DbLayer, { - queries: Mutex>>>, + queries: Vec>>>, } +impl QueryManger +where + R: Repository, + D: DbLayer, + T: EntityData, +{ + pub(crate) fn handle_save(&self, old: EntityHandle, new: &mut EntityHandle, data: &T) { + if old.real && new.real { + return; + } + + let affected = self.get_affected_queries(data); + for q in affected { + let mut query = self.queries[q].lock(); + let index = query.cache.binary_search(&old).unwrap(); + query.cache.remove(index); + let real_end = query.cache.iter().rposition(|r| r.real).unwrap() + 1; + query.cache.insert(real_end, new.clone()); + } + } + pub(crate) fn handle_delete(&self, handle: &EntityHandle, data: &T) { + let affected = self.get_affected_queries(data); + for q in affected { + let mut query = self.queries[q].lock(); + let slice = query.cache.as_slice(); + if let Ok(index) = slice.binary_search(&handle) { + query.cache.remove(index); + } + } + } + + fn get_affected_queries(&self, data: &T) -> Vec { + self.queries + .iter() + .enumerate() + .filter(|(_, q)| { + let query = q.lock(); + let rule = &query.rule; + return rule(data); + + false + }) + .map(|(i, _)| i) + .collect() + } + + fn reload_queries(&mut self, data_repository: impl Repository) { + for q in &self.queries { + let mut query = q.lock(); + query.cache.clear(); + + } + } +} pub struct Query where R: Repository, D: DbLayer, { cache: Vec>, - rule: Box bool>, + rule: Box bool + Send + Sync>, + parallel: bool, _marker: PhantomData<(R, D)>, } -impl, D: DbLayer> Query { +impl, D: DbLayer> Query { pub fn iter<'a>(&mut self, data_repository_inner: &'a R) -> impl Iterator where T: 'a, @@ -64,6 +121,39 @@ impl, D: DbLayer> Query { } } } + + pub fn load(&mut self, data_repository: DataRepositoryInner) { + self.cache.clear(); + if self.parallel { + let mut cache: Vec> = data_repository + .cache + .par_iter() + .enumerate() + .filter(|(_, entity)| (self.rule)(entity)) + .map(|(i, entity)| EntityHandle { + id: entity.id(), + real: true, + index: i as u32, + _phantom: PhantomData, + }) + .collect(); + + cache.extend( + data_repository + .temporal + .iter() + .enumerate() + .filter(|(_, entity)| (self.rule)(entity)) + .map(|(i, entity)| EntityHandle { + id: entity.id(), + real: false, + index: i as u32, + _phantom: PhantomData, + }) + .collect::>(), + ); + } + } } pub trait BatchedRepository @@ -135,29 +225,30 @@ impl> DataRepository { }) .collect(); - cache.extend(inner - .temporal - .iter() - .enumerate() - .filter(|(_, entity)| rule(entity)) - .map(|(i, entity)| EntityHandle { - id: entity.id(), - real: false, - index: i as u32, - _phantom: PhantomData, - }).collect::>()); + cache.extend( + inner + .temporal + .iter() + .enumerate() + .filter(|(_, entity)| rule(entity)) + .map(|(i, entity)| EntityHandle { + id: entity.id(), + real: false, + index: i as u32, + _phantom: PhantomData, + }) + .collect::>(), + ); Query { cache, rule, + parallel: true, _marker: PhantomData, } } - pub fn query( - &self, - rule: Box bool>, - ) -> Query, D> + pub fn query(&self, rule: Box bool + Send + Sync>) -> Query, D> where T: Send + Sync, { @@ -176,21 +267,25 @@ impl> DataRepository { }) .collect(); - cache.extend(inner - .temporal - .iter() - .enumerate() - .filter(|(_, entity)| rule(entity)) - .map(|(i, entity)| EntityHandle { - id: entity.id(), - real: false, - index: i as u32, - _phantom: PhantomData, - }).collect::>()); + cache.extend( + inner + .temporal + .iter() + .enumerate() + .filter(|(_, entity)| rule(entity)) + .map(|(i, entity)| EntityHandle { + id: entity.id(), + real: false, + index: i as u32, + _phantom: PhantomData, + }) + .collect::>(), + ); Query { cache, rule, + parallel: false, _marker: PhantomData, } } @@ -251,19 +346,22 @@ impl> Repository for DataRepositoryIn } fn save(&mut self, entity: &mut EntityHandle) -> MaybeError { + let original_state = entity.clone(); if entity.real { let data = self.get_ref(entity).unwrap(); self.data_layer .write_entity(data, &self.connection.lock())?; } else { let index = fast_position_of(self.temporal.as_slice(), entity).unwrap(); - let mut value = self.temporal.remove(index); + let mut data = self.temporal.remove(index); self.data_layer - .add_entity(&mut value, &self.connection.lock())?; + .add_entity(&mut data, &self.connection.lock())?; entity.real = true; - entity.id = value.id(); + entity.id = data.id(); entity.index = self.cache.len() as u32; - self.cache.push(value); + self.query_manger.handle_save(original_state, entity, &data); + + self.cache.push(data); } Ok(()) @@ -278,8 +376,12 @@ impl> Repository for DataRepositoryIn &mut self.temporal }; - let index = fast_position_of(vec.as_slice(), &mut handle).unwrap(); - vec.remove(index); + let index = into_result( + fast_position_of(vec.as_slice(), &mut handle), + NotificationData::DbSaveError, + )?; + let value = vec.remove(index); + self.query_manger.handle_delete(&handle, &value); Ok(()) } @@ -293,6 +395,7 @@ impl> Repository for DataRepositoryIn self.cache.clear(); self.temporal.clear(); self.init()?; + Ok(()) } } @@ -331,15 +434,20 @@ impl + DbLayer> BatchedRepository .add_many_entity(&mut values, &self.connection.lock())?; let start = self.cache.len(); - self.cache.extend(values); - for (i, handle) in temp.into_iter().enumerate() { + for (i, (handle, data)) in temp.into_iter().zip(&values).enumerate() { + let old = handle.clone(); let h = handle; h.id = self.cache[start + i].id(); h.index = (start + i) as u32; h.real = true; + self.query_manger.handle_save(old, h, data); } + self.cache.extend(values); + + + for handle in cached.into_iter() { let h = handle; let idx = fast_position_of(self.cache.as_slice(), h).unwrap(); @@ -379,6 +487,7 @@ impl + DbLayer> BatchedRepository if remove { cell += 1; } + !remove }); From d8e3a5b6db80f0ff078d78720db4be92d2d95dd4 Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Fri, 28 Aug 2026 17:15:16 +0300 Subject: [PATCH 09/14] Load code fixed --- src/data/repositories.rs | 48 ++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/data/repositories.rs b/src/data/repositories.rs index 30bd545..507f3e5 100644 --- a/src/data/repositories.rs +++ b/src/data/repositories.rs @@ -122,10 +122,10 @@ impl, D: DbLayer> Query { } } - pub fn load(&mut self, data_repository: DataRepositoryInner) { + pub fn load(&mut self, data_repository: &DataRepositoryInner) where T : Send + Sync { self.cache.clear(); if self.parallel { - let mut cache: Vec> = data_repository + self.cache = data_repository .cache .par_iter() .enumerate() @@ -138,21 +138,37 @@ impl, D: DbLayer> Query { }) .collect(); - cache.extend( - data_repository - .temporal - .iter() - .enumerate() - .filter(|(_, entity)| (self.rule)(entity)) - .map(|(i, entity)| EntityHandle { - id: entity.id(), - real: false, - index: i as u32, - _phantom: PhantomData, - }) - .collect::>(), - ); + } + else { + self.cache = data_repository + .cache + .iter() + .enumerate() + .filter(|(_, entity)| (self.rule)(entity)) + .map(|(i, entity)| EntityHandle { + id: entity.id(), + real: true, + index: i as u32, + _phantom: PhantomData, + }) + .collect(); + } + + self.cache.extend( + data_repository + .temporal + .iter() + .enumerate() + .filter(|(_, entity)| (self.rule)(entity)) + .map(|(i, entity)| EntityHandle { + id: entity.id(), + real: false, + index: i as u32, + _phantom: PhantomData, + }) + .collect::>(), + ); } } From 2c1163fc3b7bedcfb1c9d30efce6bbc49aa2d6a7 Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Sat, 29 Aug 2026 22:41:11 +0300 Subject: [PATCH 10/14] Compress EntityHandle --- src/data/entities.rs | 34 +++++++++-- src/data/repositories.rs | 118 ++++++++++++--------------------------- src/tests.rs | 79 ++++++++++++++++++-------- 3 files changed, 120 insertions(+), 111 deletions(-) diff --git a/src/data/entities.rs b/src/data/entities.rs index e3ff883..7251123 100644 --- a/src/data/entities.rs +++ b/src/data/entities.rs @@ -182,8 +182,7 @@ impl CardStatistics { #[derive(Copy)] pub struct EntityHandle { pub(crate) id: Id, - pub(crate) real: bool, - pub(crate) index: u32, + index: i32, pub(crate) _phantom: PhantomData, } @@ -192,17 +191,40 @@ impl Clone for EntityHandle { fn clone(&self) -> Self { EntityHandle{ id: self.id, - real: self.real, index: self.index, _phantom: Default::default(), } } } +impl EntityHandle { + pub fn new(id: Id, index: usize, real: bool) -> Self { + Self { + id, + index : index as i32 | (!real as i32) << 31, + _phantom: Default::default() + } + } + pub fn index(&self) -> usize { + (self.index << 1 >> 1) as usize + } + + pub fn set_index(&mut self, index: usize) { + self.index = self.index >> 31 << 31 | index as i32; + } + + pub fn real(&self) -> bool { + (self.index >> 31) == 0 + } + + pub fn set_real(&mut self, value: bool) { + self.index = self.index << 1 >> 1 | (!value as i32) << 31; + } +} impl Eq for EntityHandle {} impl PartialEq for EntityHandle { fn eq(&self, other: &Self) -> bool { - self.real == other.real && self.id == other.id + self.real() == other.real() && self.id == other.id } } impl PartialOrd for EntityHandle { @@ -213,11 +235,11 @@ impl PartialOrd for EntityHandle { impl Ord for EntityHandle { fn cmp(&self, other: &Self) -> Ordering { - if self.real && !other.real { + if self.real() && !other.real() { return Ordering::Less } - if !self.real && other.real{ + if !self.real() && other.real(){ return Ordering::Greater } diff --git a/src/data/repositories.rs b/src/data/repositories.rs index 507f3e5..afb9c58 100644 --- a/src/data/repositories.rs +++ b/src/data/repositories.rs @@ -43,7 +43,7 @@ where T: EntityData, { pub(crate) fn handle_save(&self, old: EntityHandle, new: &mut EntityHandle, data: &T) { - if old.real && new.real { + if old.real() && new.real() { return; } @@ -52,7 +52,7 @@ where let mut query = self.queries[q].lock(); let index = query.cache.binary_search(&old).unwrap(); query.cache.remove(index); - let real_end = query.cache.iter().rposition(|r| r.real).unwrap() + 1; + let real_end = query.cache.iter().rposition(|r| r.real()).unwrap() + 1; query.cache.insert(real_end, new.clone()); } } @@ -86,7 +86,6 @@ where for q in &self.queries { let mut query = q.lock(); query.cache.clear(); - } } } @@ -122,7 +121,10 @@ impl, D: DbLayer> Query { } } - pub fn load(&mut self, data_repository: &DataRepositoryInner) where T : Send + Sync { + pub fn load(&mut self, data_repository: &DataRepositoryInner) + where + T: Send + Sync, + { self.cache.clear(); if self.parallel { self.cache = data_repository @@ -130,28 +132,15 @@ impl, D: DbLayer> Query { .par_iter() .enumerate() .filter(|(_, entity)| (self.rule)(entity)) - .map(|(i, entity)| EntityHandle { - id: entity.id(), - real: true, - index: i as u32, - _phantom: PhantomData, - }) + .map(|(i, entity)| EntityHandle::new(entity.id(), i, true)) .collect(); - - - } - else { + } else { self.cache = data_repository .cache .iter() .enumerate() .filter(|(_, entity)| (self.rule)(entity)) - .map(|(i, entity)| EntityHandle { - id: entity.id(), - real: true, - index: i as u32, - _phantom: PhantomData, - }) + .map(|(i, entity)| EntityHandle::new(entity.id(), i, true)) .collect(); } @@ -161,12 +150,7 @@ impl, D: DbLayer> Query { .iter() .enumerate() .filter(|(_, entity)| (self.rule)(entity)) - .map(|(i, entity)| EntityHandle { - id: entity.id(), - real: false, - index: i as u32, - _phantom: PhantomData, - }) + .map(|(i, entity)| EntityHandle::new(entity.id(), i, false)) .collect::>(), ); } @@ -208,6 +192,7 @@ pub struct DataRepositoryInner> connection: Arc>, data_layer: D, query_manger: QueryManger, + version: u16 } impl> Deref for DataRepository { @@ -233,12 +218,7 @@ impl> DataRepository { .par_iter() .enumerate() .filter(|(_, entity)| rule(entity)) - .map(|(i, entity)| EntityHandle { - id: entity.id(), - real: true, - index: i as u32, - _phantom: PhantomData, - }) + .map(|(i, entity)| EntityHandle::new(entity.id(), i, true)) .collect(); cache.extend( @@ -247,12 +227,7 @@ impl> DataRepository { .iter() .enumerate() .filter(|(_, entity)| rule(entity)) - .map(|(i, entity)| EntityHandle { - id: entity.id(), - real: false, - index: i as u32, - _phantom: PhantomData, - }) + .map(|(i, entity)| EntityHandle::new(entity.id(), i, false)) .collect::>(), ); @@ -264,7 +239,10 @@ impl> DataRepository { } } - pub fn query(&self, rule: Box bool + Send + Sync>) -> Query, D> + pub fn query( + &self, + rule: Box bool + Send + Sync>, + ) -> Query, D> where T: Send + Sync, { @@ -275,12 +253,7 @@ impl> DataRepository { .iter() .enumerate() .filter(|(_, entity)| rule(entity)) - .map(|(i, entity)| EntityHandle { - id: entity.id(), - real: true, - index: i as u32, - _phantom: PhantomData, - }) + .map(|(i, entity)| EntityHandle::new(entity.id(), i, true)) .collect(); cache.extend( @@ -289,12 +262,7 @@ impl> DataRepository { .iter() .enumerate() .filter(|(_, entity)| rule(entity)) - .map(|(i, entity)| EntityHandle { - id: entity.id(), - real: false, - index: i as u32, - _phantom: PhantomData, - }) + .map(|(i, entity)| EntityHandle::new(entity.id(), i, false)) .collect::>(), ); @@ -309,7 +277,7 @@ impl> DataRepository { impl> Repository for DataRepositoryInner { fn get(&self, handle: &mut EntityHandle) -> Option { - let slice = if handle.real { + let slice = if handle.real() { self.cache.as_slice() } else { self.temporal.as_slice() @@ -319,7 +287,7 @@ impl> Repository for DataRepositoryIn } fn get_ref(&self, handle: &mut EntityHandle) -> Option<&T> { - let slice = if handle.real { + let slice = if handle.real() { self.cache.as_slice() } else { self.temporal.as_slice() @@ -329,7 +297,7 @@ impl> Repository for DataRepositoryIn } fn get_mut(&mut self, handle: &mut EntityHandle) -> Option<&mut T> { - let slice = if handle.real { + let slice = if handle.real() { self.cache.as_mut_slice() } else { self.temporal.as_mut_slice() @@ -339,23 +307,13 @@ impl> Repository for DataRepositoryIn } fn handle_by_id(&self, id: Id) -> EntityHandle { - EntityHandle { - _phantom: Default::default(), - id, - real: true, - index: id.0, - } + EntityHandle::new(id, id.0 as usize, false) } fn create(&mut self, mut new: T) -> EntityHandle { let next_temporal = self.next_temporal; self.next_temporal += 1; - let entity = EntityHandle { - id: next_temporal.into(), - real: false, - index: self.temporal.len() as u32, - _phantom: PhantomData, - }; + let entity = EntityHandle::new(next_temporal.into(), self.temporal.len(), false); new.set_id(entity.id); self.temporal.push(new); entity @@ -363,7 +321,7 @@ impl> Repository for DataRepositoryIn fn save(&mut self, entity: &mut EntityHandle) -> MaybeError { let original_state = entity.clone(); - if entity.real { + if entity.real() { let data = self.get_ref(entity).unwrap(); self.data_layer .write_entity(data, &self.connection.lock())?; @@ -372,9 +330,9 @@ impl> Repository for DataRepositoryIn let mut data = self.temporal.remove(index); self.data_layer .add_entity(&mut data, &self.connection.lock())?; - entity.real = true; + entity.set_real(true); entity.id = data.id(); - entity.index = self.cache.len() as u32; + entity.set_index(self.cache.len()); self.query_manger.handle_save(original_state, entity, &data); self.cache.push(data); @@ -384,7 +342,7 @@ impl> Repository for DataRepositoryIn } fn delete(&mut self, mut handle: EntityHandle) -> MaybeError { - let vec = if handle.real { + let vec = if handle.real() { self.data_layer .delete_entity(handle.id, &self.connection.lock())?; &mut self.cache @@ -404,6 +362,7 @@ impl> Repository for DataRepositoryIn fn init(&mut self) -> MaybeError { self.data_layer .load_all(&mut self.cache, &self.connection.lock())?; + self.version += 1; Ok(()) } @@ -411,7 +370,6 @@ impl> Repository for DataRepositoryIn self.cache.clear(); self.temporal.clear(); self.init()?; - Ok(()) } } @@ -423,7 +381,7 @@ impl + DbLayer> BatchedRepository let mut cached = Vec::with_capacity(handles.len() / 2); handles.sort_by_key(|a| a.id); handles.iter_mut().for_each(|handle| { - if handle.real { + if handle.real() { cached.push(handle) } else { temp.push(handle) @@ -455,15 +413,13 @@ impl + DbLayer> BatchedRepository let old = handle.clone(); let h = handle; h.id = self.cache[start + i].id(); - h.index = (start + i) as u32; - h.real = true; + h.set_index(start + i); + h.set_real(true); self.query_manger.handle_save(old, h, data); } self.cache.extend(values); - - for handle in cached.into_iter() { let h = handle; let idx = fast_position_of(self.cache.as_slice(), h).unwrap(); @@ -519,7 +475,7 @@ where let mut cached = Vec::with_capacity(handles.len() / 2); handles.sort_by(|a, b| a.id.cmp(&b.id)); handles.iter().for_each(|handle| { - if handle.real { + if handle.real() { cached.push(getter(handle)) } else { temp.push(getter(handle)) @@ -537,11 +493,11 @@ pub fn fast_position_of( } let max_idx = storage.len() - 1; - let upper = (handle.index as usize).min(max_idx); + let upper = (handle.index()).min(max_idx); let peek = storage[upper].id(); if peek == handle.id { - handle.index = upper as u32; + handle.set_index(upper); return Some(upper); } @@ -556,7 +512,7 @@ pub fn fast_position_of( let new_index = (upper as i64 - delta).max(0) as usize; if storage[new_index].id() == handle.id { - handle.index = new_index as u32; + handle.set_index(new_index); return Some(new_index); } @@ -569,6 +525,6 @@ pub fn fast_position_of( .ok()? + new_index; - handle.index = index as u32; + handle.set_index(index); Some(index) } diff --git a/src/tests.rs b/src/tests.rs index a49b7f9..c2e777a 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -29,12 +29,7 @@ mod mod_tests { #[test] fn search_direct() { - let mut entity = EntityHandle { - _phantom: Default::default(), - id: 8.into(), - real: true, - index: 3, - }; + let mut entity = EntityHandle::new(8.into(), 3, true); assert_eq!( fast_position_of(&[1, 2, 3, 8, 9, 12].map(ProtoEntity::new), &mut entity), Some(3) @@ -43,12 +38,7 @@ mod mod_tests { #[test] fn search_0() { - let mut entity = EntityHandle { - _phantom: Default::default(), - id: 8.into(), - real: true, - index: 4, - }; + let mut entity = EntityHandle::new(8.into(), 4, true); assert_eq!( fast_position_of(&[1, 2, 3, 8, 9, 12].map(ProtoEntity::new), &mut entity), Some(3) @@ -57,12 +47,7 @@ mod mod_tests { #[test] fn search_1() { - let mut entity = EntityHandle { - _phantom: Default::default(), - id: 9.into(), - real: true, - index: 5, - }; + let mut entity = EntityHandle::new(9.into(), 5, true); assert_eq!( fast_position_of(&[1, 2, 3, 8, 9, 12].map(ProtoEntity::new), &mut entity), Some(4) @@ -71,12 +56,7 @@ mod mod_tests { #[test] fn search_2() { - let mut entity = EntityHandle { - _phantom: Default::default(), - id: 106.into(), - real: true, - index: 25, - }; + let mut entity = EntityHandle::new(106.into(), 25, true); assert_eq!( fast_position_of( &[ @@ -89,4 +69,55 @@ mod mod_tests { Some(18) ); } + + #[test] + fn id_0() { + let entity: EntityHandle = EntityHandle::new(8.into(), 25, true); + assert_eq!(entity.real(), true) + } + + #[test] + fn id_1() { + let entity: EntityHandle = EntityHandle::new(8.into(), 25, false); + assert_eq!(entity.real(), false) + } + + #[test] + fn id_3() { + let entity: EntityHandle = EntityHandle::new(8.into(), 25, true); + assert_eq!(entity.index(), 25) + } + + #[test] + fn id_4() { + let entity: EntityHandle = EntityHandle::new(8.into(), 25, false); + assert_eq!(entity.index(), 25) + } + #[test] + fn id_5() { + let mut entity: EntityHandle = EntityHandle::new(8.into(), 25, true); + entity.set_real(true); + assert_eq!(entity.real(), true) + } + + #[test] + fn id_6() { + let mut entity: EntityHandle = EntityHandle::new(8.into(), 25, false); + entity.set_real(true); + assert_eq!(entity.real(), true) + } + + #[test] + fn id_7() { + let mut entity: EntityHandle = EntityHandle::new(8.into(), 25, true); + entity.set_index(0); + assert_eq!(entity.index(), 0) + } + + #[test] + fn id_8() { + let mut entity: EntityHandle = EntityHandle::new(8.into(), 25, false); + entity.set_index(0); + assert_eq!(entity.index(), 0) + } } From 4b17a5a4a7285aafcc2022b625a42df15e3488bb Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Sat, 29 Aug 2026 22:50:18 +0300 Subject: [PATCH 11/14] Fix incorrect handle_by_id --- src/data/repositories.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/repositories.rs b/src/data/repositories.rs index afb9c58..7bb6cb0 100644 --- a/src/data/repositories.rs +++ b/src/data/repositories.rs @@ -307,7 +307,7 @@ impl> Repository for DataRepositoryIn } fn handle_by_id(&self, id: Id) -> EntityHandle { - EntityHandle::new(id, id.0 as usize, false) + EntityHandle::new(id, id.0 as usize, true) } fn create(&mut self, mut new: T) -> EntityHandle { From ea214d8ac0c39ad890b5b81aa9a6df0239eb2b40 Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Sat, 29 Aug 2026 23:10:57 +0300 Subject: [PATCH 12/14] Change connection type from Mutex to RwLock --- src/data/repositories.rs | 53 ++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/src/data/repositories.rs b/src/data/repositories.rs index 7bb6cb0..59ed59e 100644 --- a/src/data/repositories.rs +++ b/src/data/repositories.rs @@ -43,6 +43,7 @@ where T: EntityData, { pub(crate) fn handle_save(&self, old: EntityHandle, new: &mut EntityHandle, data: &T) { + return; if old.real() && new.real() { return; } @@ -57,6 +58,7 @@ where } } pub(crate) fn handle_delete(&self, handle: &EntityHandle, data: &T) { + return; let affected = self.get_affected_queries(data); for q in affected { let mut query = self.queries[q].lock(); @@ -189,10 +191,10 @@ pub struct DataRepositoryInner> cache: Vec, temporal: Vec, next_temporal: u32, - connection: Arc>, + connection: Arc>, data_layer: D, query_manger: QueryManger, - version: u16 + version: u16, } impl> Deref for DataRepository { @@ -203,7 +205,7 @@ impl> Deref for DataRepository< } } -impl> DataRepository { +impl> DataRepositoryInner { pub fn query_par( &self, rule: Box bool + Send + Sync>, @@ -211,9 +213,7 @@ impl> DataRepository { where T: Send + Sync, { - let inner = self.inner.read(); - - let mut cache: Vec> = inner + let mut cache: Vec> = self .cache .par_iter() .enumerate() @@ -222,8 +222,7 @@ impl> DataRepository { .collect(); cache.extend( - inner - .temporal + self.temporal .iter() .enumerate() .filter(|(_, entity)| rule(entity)) @@ -246,9 +245,7 @@ impl> DataRepository { where T: Send + Sync, { - let inner = self.inner.read(); - - let mut cache: Vec> = inner + let mut cache: Vec> = self .cache .iter() .enumerate() @@ -257,8 +254,7 @@ impl> DataRepository { .collect(); cache.extend( - inner - .temporal + self.temporal .iter() .enumerate() .filter(|(_, entity)| rule(entity)) @@ -324,12 +320,12 @@ impl> Repository for DataRepositoryIn if entity.real() { let data = self.get_ref(entity).unwrap(); self.data_layer - .write_entity(data, &self.connection.lock())?; + .write_entity(data, &self.connection.read())?; } else { let index = fast_position_of(self.temporal.as_slice(), entity).unwrap(); let mut data = self.temporal.remove(index); self.data_layer - .add_entity(&mut data, &self.connection.lock())?; + .add_entity(&mut data, &self.connection.read())?; entity.set_real(true); entity.id = data.id(); entity.set_index(self.cache.len()); @@ -344,7 +340,7 @@ impl> Repository for DataRepositoryIn fn delete(&mut self, mut handle: EntityHandle) -> MaybeError { let vec = if handle.real() { self.data_layer - .delete_entity(handle.id, &self.connection.lock())?; + .delete_entity(handle.id, &self.connection.read())?; &mut self.cache } else { &mut self.temporal @@ -361,7 +357,7 @@ impl> Repository for DataRepositoryIn fn init(&mut self) -> MaybeError { self.data_layer - .load_all(&mut self.cache, &self.connection.lock())?; + .load_all(&mut self.cache, &self.connection.read())?; self.version += 1; Ok(()) } @@ -388,7 +384,7 @@ impl + DbLayer> BatchedRepository } }); - let mut values = Vec::with_capacity(temp.len()); + let mut temp_values = Vec::with_capacity(temp.len()); let mut cell = 0; let len = temp.len(); self.temporal.retain(|x| { @@ -399,32 +395,31 @@ impl + DbLayer> BatchedRepository let remove = x.id() == find; if remove { cell += 1; - values.push(x.clone()); + temp_values.push(x.clone()); } !remove }); self.data_layer - .add_many_entity(&mut values, &self.connection.lock())?; + .add_many_entity(&mut temp_values, &self.connection.read())?; let start = self.cache.len(); - for (i, (handle, data)) in temp.into_iter().zip(&values).enumerate() { + for (i, (handle, data)) in temp.into_iter().zip(&mut temp_values).enumerate() { let old = handle.clone(); - let h = handle; - h.id = self.cache[start + i].id(); - h.set_index(start + i); - h.set_real(true); - self.query_manger.handle_save(old, h, data); + handle.id = data.id(); + handle.set_index(start + i); + handle.set_real(true); + self.query_manger.handle_save(old, handle, data); } - self.cache.extend(values); + self.cache.extend(temp_values); for handle in cached.into_iter() { let h = handle; let idx = fast_position_of(self.cache.as_slice(), h).unwrap(); self.data_layer - .write_entity(&self.cache[idx], &self.connection.lock())?; + .write_entity(&self.cache[idx], &self.connection.read())?; } Ok(()) @@ -447,7 +442,7 @@ impl + DbLayer> BatchedRepository !remove }); self.data_layer - .delete_many(cached.as_slice(), &self.connection.lock())?; + .delete_many(cached.as_slice(), &self.connection.read())?; let len = cached.len(); cell = 0; self.cache.retain(|x| { From b006ece0c13a43b494d350f46093d7cdda3601fe Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Sat, 29 Aug 2026 23:15:22 +0300 Subject: [PATCH 13/14] Clear code and add shifts into Query --- src/data/repositories.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/data/repositories.rs b/src/data/repositories.rs index 59ed59e..f94ba8b 100644 --- a/src/data/repositories.rs +++ b/src/data/repositories.rs @@ -7,10 +7,8 @@ use rayon::iter::IndexedParallelIterator; use rayon::iter::IntoParallelRefIterator; use rayon::iter::ParallelIterator; use rusqlite::Connection; -use std::cell::RefCell; use std::marker::PhantomData; use std::ops::Deref; -use std::rc::Weak; use std::sync::Arc; pub trait Repository @@ -100,6 +98,7 @@ where rule: Box bool + Send + Sync>, parallel: bool, _marker: PhantomData<(R, D)>, + shift_stamp: u16 } impl, D: DbLayer> Query { @@ -195,6 +194,7 @@ pub struct DataRepositoryInner> data_layer: D, query_manger: QueryManger, version: u16, + shifts: u16, } impl> Deref for DataRepository { @@ -235,6 +235,7 @@ impl> DataRepositoryInner { rule, parallel: true, _marker: PhantomData, + shift_stamp: self.shifts, } } @@ -267,6 +268,7 @@ impl> DataRepositoryInner { rule, parallel: false, _marker: PhantomData, + shift_stamp: self.shifts, } } } @@ -332,6 +334,7 @@ impl> Repository for DataRepositoryIn self.query_manger.handle_save(original_state, entity, &data); self.cache.push(data); + self.shifts += 1; } Ok(()) @@ -352,6 +355,7 @@ impl> Repository for DataRepositoryIn )?; let value = vec.remove(index); self.query_manger.handle_delete(&handle, &value); + self.shifts += 1; Ok(()) } @@ -421,6 +425,7 @@ impl + DbLayer> BatchedRepository self.data_layer .write_entity(&self.cache[idx], &self.connection.read())?; } + self.shifts += 1; Ok(()) } @@ -457,6 +462,7 @@ impl + DbLayer> BatchedRepository !remove }); + self.shifts += 1; Ok(()) } From 3ac7cd068bb2bb5f845977d394fbeafa91fc1860 Mon Sep 17 00:00:00 2001 From: Mikhail Mitrofanov Date: Sun, 30 Aug 2026 19:22:28 +0300 Subject: [PATCH 14/14] Rename methods --- benches/repo_cache_bench.rs | 178 ++++++++++++++++++++++-------------- src/data/entities.rs | 23 +++-- src/data/repositories.rs | 10 +- 3 files changed, 125 insertions(+), 86 deletions(-) diff --git a/benches/repo_cache_bench.rs b/benches/repo_cache_bench.rs index 3d13a1a..8b44a4f 100644 --- a/benches/repo_cache_bench.rs +++ b/benches/repo_cache_bench.rs @@ -1,8 +1,8 @@ -use criterion::{criterion_group, criterion_main, Criterion, BenchmarkId}; -use std::collections::{BTreeMap, HashMap}; -use std::hint::black_box; +use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; use hashbrown::HashMap as HashbrownMap; use rustc_hash::FxHashMap; +use std::collections::{BTreeMap, HashMap}; +use std::hint::black_box; // Тестовая структура (аналог вашей сущности) #[derive(Debug, Clone)] @@ -117,40 +117,60 @@ fn bench_insert(c: &mut Criterion) { for size in [10_000, 100_000, 500_000] { let entities = generate_entities(size); - group.bench_with_input(BenchmarkId::new("Vec (sorted)", size), &entities, |b, entities| { - let mut vec = Vec::with_capacity(size); - b.iter(|| { - bench_vec_insert_sorted(&mut vec, black_box(entities)); - }); - }); + group.bench_with_input( + BenchmarkId::new("Vec (sorted)", size), + &entities, + |b, entities| { + let mut vec = Vec::with_capacity(size); + b.iter(|| { + bench_vec_insert_sorted(&mut vec, black_box(entities)); + }); + }, + ); - group.bench_with_input(BenchmarkId::new("BTreeMap", size), &entities, |b, entities| { - let mut map = BTreeMap::new(); - b.iter(|| { - bench_btreemap_insert(&mut map, black_box(entities)); - }); - }); + group.bench_with_input( + BenchmarkId::new("BTreeMap", size), + &entities, + |b, entities| { + let mut map = BTreeMap::new(); + b.iter(|| { + bench_btreemap_insert(&mut map, black_box(entities)); + }); + }, + ); - group.bench_with_input(BenchmarkId::new("HashMap (std)", size), &entities, |b, entities| { - let mut map = HashMap::new(); - b.iter(|| { - bench_hashmap_insert(&mut map, black_box(entities)); - }); - }); + group.bench_with_input( + BenchmarkId::new("HashMap (std)", size), + &entities, + |b, entities| { + let mut map = HashMap::new(); + b.iter(|| { + bench_hashmap_insert(&mut map, black_box(entities)); + }); + }, + ); - group.bench_with_input(BenchmarkId::new("HashMap (hashbrown)", size), &entities, |b, entities| { - let mut map = HashbrownMap::new(); - b.iter(|| { - bench_hashbrown_insert(&mut map, black_box(entities)); - }); - }); + group.bench_with_input( + BenchmarkId::new("HashMap (hashbrown)", size), + &entities, + |b, entities| { + let mut map = HashbrownMap::new(); + b.iter(|| { + bench_hashbrown_insert(&mut map, black_box(entities)); + }); + }, + ); - group.bench_with_input(BenchmarkId::new("FxHashMap", size), &entities, |b, entities| { - let mut map = FxHashMap::default(); - b.iter(|| { - bench_fxhashmap_insert(&mut map, black_box(entities)); - }); - }); + group.bench_with_input( + BenchmarkId::new("FxHashMap", size), + &entities, + |b, entities| { + let mut map = FxHashMap::default(); + b.iter(|| { + bench_fxhashmap_insert(&mut map, black_box(entities)); + }); + }, + ); } group.finish(); @@ -180,21 +200,29 @@ fn bench_search(c: &mut Criterion) { let mut fxhashmap = FxHashMap::default(); bench_fxhashmap_insert(&mut fxhashmap, &entities); - group.bench_with_input(BenchmarkId::new("Vec (binary search)", size), &search_ids, |b, ids| { - b.iter(|| { - for &id in ids { - black_box(bench_vec_search_separated(&entity_ids, &vec, black_box(id))); - } - }); - }); + group.bench_with_input( + BenchmarkId::new("Vec (binary search)", size), + &search_ids, + |b, ids| { + b.iter(|| { + for &id in ids { + black_box(bench_vec_search_separated(&entity_ids, &vec, black_box(id))); + } + }); + }, + ); - group.bench_with_input(BenchmarkId::new("Vec (binary search) separated", size), &search_ids, |b, ids| { - b.iter(|| { - for &id in ids { - black_box(bench_vec_search(&vec, black_box(id))); - } - }); - }); + group.bench_with_input( + BenchmarkId::new("Vec (binary search) separated", size), + &search_ids, + |b, ids| { + b.iter(|| { + for &id in ids { + black_box(bench_vec_search(&vec, black_box(id))); + } + }); + }, + ); group.bench_with_input(BenchmarkId::new("BTreeMap", size), &search_ids, |b, ids| { b.iter(|| { @@ -204,29 +232,41 @@ fn bench_search(c: &mut Criterion) { }); }); - group.bench_with_input(BenchmarkId::new("HashMap (std)", size), &search_ids, |b, ids| { - b.iter(|| { - for &id in ids { - black_box(bench_hashmap_search(&hashmap, black_box(id))); - } - }); - }); + group.bench_with_input( + BenchmarkId::new("HashMap (std)", size), + &search_ids, + |b, ids| { + b.iter(|| { + for &id in ids { + black_box(bench_hashmap_search(&hashmap, black_box(id))); + } + }); + }, + ); - group.bench_with_input(BenchmarkId::new("HashMap (hashbrown)", size), &search_ids, |b, ids| { - b.iter(|| { - for &id in ids { - black_box(bench_hashbrown_search(&hashbrown, black_box(id))); - } - }); - }); + group.bench_with_input( + BenchmarkId::new("HashMap (hashbrown)", size), + &search_ids, + |b, ids| { + b.iter(|| { + for &id in ids { + black_box(bench_hashbrown_search(&hashbrown, black_box(id))); + } + }); + }, + ); - group.bench_with_input(BenchmarkId::new("FxHashMap", size), &search_ids, |b, ids| { - b.iter(|| { - for &id in ids { - black_box(bench_fxhashmap_search(&fxhashmap, black_box(id))); - } - }); - }); + group.bench_with_input( + BenchmarkId::new("FxHashMap", size), + &search_ids, + |b, ids| { + b.iter(|| { + for &id in ids { + black_box(bench_fxhashmap_search(&fxhashmap, black_box(id))); + } + }); + }, + ); } group.finish(); @@ -282,4 +322,4 @@ fn bench_memory_efficiency(c: &mut Criterion) { } criterion_group!(benches, bench_insert, bench_search, bench_memory_efficiency); -criterion_main!(benches); \ No newline at end of file +criterion_main!(benches); diff --git a/src/data/entities.rs b/src/data/entities.rs index 7251123..5fd80ae 100644 --- a/src/data/entities.rs +++ b/src/data/entities.rs @@ -1,4 +1,3 @@ -use std::cmp::Ordering; use crate::app::AppState; use crate::core::srs::{FADE_PER_DAY, MAX_SCORE, OrderMode, WordOpenMode}; use crate::data::{INVALID_ID, Id}; @@ -7,6 +6,7 @@ use chrono::{DateTime, Utc}; use log::info; use rayon::prelude::*; use rhai::{Engine, Scope}; +use std::cmp::Ordering; use std::collections::HashMap; use std::marker::PhantomData; use std::time::Instant; @@ -184,24 +184,23 @@ pub struct EntityHandle { pub(crate) id: Id, index: i32, pub(crate) _phantom: PhantomData, - } impl Clone for EntityHandle { fn clone(&self) -> Self { - EntityHandle{ + EntityHandle { id: self.id, index: self.index, _phantom: Default::default(), } } } -impl EntityHandle { +impl EntityHandle { pub fn new(id: Id, index: usize, real: bool) -> Self { Self { id, - index : index as i32 | (!real as i32) << 31, - _phantom: Default::default() + index: index as i32 | (!real as i32) << 31, + _phantom: Default::default(), } } pub fn index(&self) -> usize { @@ -236,18 +235,18 @@ impl PartialOrd for EntityHandle { impl Ord for EntityHandle { fn cmp(&self, other: &Self) -> Ordering { if self.real() && !other.real() { - return Ordering::Less + return Ordering::Less; } - - if !self.real() && other.real(){ - return Ordering::Greater + + if !self.real() && other.real() { + return Ordering::Greater; } - + self.id.cmp(&other.id) } } -pub trait EntityData{ +pub trait EntityData { fn id(&self) -> Id; fn set_id(&mut self, id: Id); } diff --git a/src/data/repositories.rs b/src/data/repositories.rs index f94ba8b..0c0e54b 100644 --- a/src/data/repositories.rs +++ b/src/data/repositories.rs @@ -98,7 +98,7 @@ where rule: Box bool + Send + Sync>, parallel: bool, _marker: PhantomData<(R, D)>, - shift_stamp: u16 + shift_stamp: u16, } impl, D: DbLayer> Query { @@ -177,8 +177,8 @@ pub trait BatchedDbLayer where T: EntityData, { - fn delete_many(&mut self, ids: &[Id], connection: &Connection) -> MaybeError; - fn add_many_entity(&mut self, entity: &mut [T], connection: &Connection) -> MaybeError; + fn delete_many_entities(&mut self, ids: &[Id], connection: &Connection) -> MaybeError; + fn add_many_entities(&mut self, entity: &mut [T], connection: &Connection) -> MaybeError; } #[derive(Clone)] @@ -405,7 +405,7 @@ impl + DbLayer> BatchedRepository }); self.data_layer - .add_many_entity(&mut temp_values, &self.connection.read())?; + .add_many_entities(&mut temp_values, &self.connection.read())?; let start = self.cache.len(); @@ -447,7 +447,7 @@ impl + DbLayer> BatchedRepository !remove }); self.data_layer - .delete_many(cached.as_slice(), &self.connection.read())?; + .delete_many_entities(cached.as_slice(), &self.connection.read())?; let len = cached.len(); cell = 0; self.cache.retain(|x| {