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..8b44a4f --- /dev/null +++ b/benches/repo_cache_bench.rs @@ -0,0 +1,325 @@ +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)] +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); 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..5fd80ae 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::cmp::Ordering; use std::collections::HashMap; +use std::marker::PhantomData; use std::time::Instant; #[derive(Clone, Debug)] @@ -17,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, @@ -164,3 +178,75 @@ impl CardStatistics { (self.score as f32 * multiplier).max(1.0) } } + +#[derive(Copy)] +pub struct EntityHandle { + pub(crate) id: Id, + index: i32, + pub(crate) _phantom: PhantomData, +} + +impl Clone for EntityHandle { + fn clone(&self) -> Self { + EntityHandle { + id: self.id, + 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 + } +} +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 new file mode 100644 index 0000000..0c0e54b --- /dev/null +++ b/src/data/repositories.rs @@ -0,0 +1,531 @@ +use crate::data::Id; +use crate::data::entities::{EntityData, EntityHandle}; +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::marker::PhantomData; +use std::ops::Deref; +use std::sync::Arc; + +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; +} + +struct QueryManger +where + R: Repository, + D: DbLayer, +{ + queries: Vec>>>, +} + +impl QueryManger +where + R: Repository, + D: DbLayer, + T: EntityData, +{ + pub(crate) fn handle_save(&self, old: EntityHandle, new: &mut EntityHandle, data: &T) { + return; + 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) { + return; + 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 + Send + Sync>, + parallel: bool, + _marker: PhantomData<(R, D)>, + shift_stamp: u16, +} + +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); + } + } + } + + pub fn load(&mut self, data_repository: &DataRepositoryInner) + where + T: Send + Sync, + { + self.cache.clear(); + if self.parallel { + self.cache = data_repository + .cache + .par_iter() + .enumerate() + .filter(|(_, entity)| (self.rule)(entity)) + .map(|(i, entity)| EntityHandle::new(entity.id(), i, true)) + .collect(); + } else { + self.cache = data_repository + .cache + .iter() + .enumerate() + .filter(|(_, entity)| (self.rule)(entity)) + .map(|(i, entity)| EntityHandle::new(entity.id(), i, true)) + .collect(); + } + + self.cache.extend( + data_repository + .temporal + .iter() + .enumerate() + .filter(|(_, entity)| (self.rule)(entity)) + .map(|(i, entity)| EntityHandle::new(entity.id(), i, false)) + .collect::>(), + ); + } +} + +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_entities(&mut self, ids: &[Id], connection: &Connection) -> MaybeError; + fn add_many_entities(&mut self, entity: &mut [T], connection: &Connection) -> MaybeError; +} + +#[derive(Clone)] +pub struct DataRepository> { + inner: Arc>>, +} + +pub struct DataRepositoryInner> { + cache: Vec, + temporal: Vec, + next_temporal: u32, + connection: Arc>, + data_layer: D, + query_manger: QueryManger, + version: u16, + shifts: u16, +} + +impl> Deref for DataRepository { + type Target = Arc>>; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl> DataRepositoryInner { + pub fn query_par( + &self, + rule: Box bool + Send + Sync>, + ) -> Query, D> + where + T: Send + Sync, + { + let mut cache: Vec> = self + .cache + .par_iter() + .enumerate() + .filter(|(_, entity)| rule(entity)) + .map(|(i, entity)| EntityHandle::new(entity.id(), i, true)) + .collect(); + + cache.extend( + self.temporal + .iter() + .enumerate() + .filter(|(_, entity)| rule(entity)) + .map(|(i, entity)| EntityHandle::new(entity.id(), i, false)) + .collect::>(), + ); + + Query { + cache, + rule, + parallel: true, + _marker: PhantomData, + shift_stamp: self.shifts, + } + } + + pub fn query( + &self, + rule: Box bool + Send + Sync>, + ) -> Query, D> + where + T: Send + Sync, + { + let mut cache: Vec> = self + .cache + .iter() + .enumerate() + .filter(|(_, entity)| rule(entity)) + .map(|(i, entity)| EntityHandle::new(entity.id(), i, true)) + .collect(); + + cache.extend( + self.temporal + .iter() + .enumerate() + .filter(|(_, entity)| rule(entity)) + .map(|(i, entity)| EntityHandle::new(entity.id(), i, false)) + .collect::>(), + ); + + Query { + cache, + rule, + parallel: false, + _marker: PhantomData, + shift_stamp: self.shifts, + } + } +} + +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 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::new(id, id.0 as usize, true) + } + + fn create(&mut self, mut new: T) -> EntityHandle { + let next_temporal = self.next_temporal; + self.next_temporal += 1; + let entity = EntityHandle::new(next_temporal.into(), self.temporal.len(), false); + new.set_id(entity.id); + self.temporal.push(new); + entity + } + + 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.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.read())?; + entity.set_real(true); + entity.id = data.id(); + entity.set_index(self.cache.len()); + self.query_manger.handle_save(original_state, entity, &data); + + self.cache.push(data); + self.shifts += 1; + } + + Ok(()) + } + + fn delete(&mut self, mut handle: EntityHandle) -> MaybeError { + let vec = if handle.real() { + self.data_layer + .delete_entity(handle.id, &self.connection.read())?; + &mut self.cache + } else { + &mut self.temporal + }; + + 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); + self.shifts += 1; + Ok(()) + } + + fn init(&mut self) -> MaybeError { + self.data_layer + .load_all(&mut self.cache, &self.connection.read())?; + self.version += 1; + Ok(()) + } + + fn reload(&mut self) -> MaybeError { + self.cache.clear(); + self.temporal.clear(); + self.init()?; + Ok(()) + } +} +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 temp_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 { + cell += 1; + temp_values.push(x.clone()); + } + !remove + }); + + self.data_layer + .add_many_entities(&mut temp_values, &self.connection.read())?; + + let start = self.cache.len(); + + for (i, (handle, data)) in temp.into_iter().zip(&mut temp_values).enumerate() { + let old = handle.clone(); + handle.id = data.id(); + handle.set_index(start + i); + handle.set_real(true); + self.query_manger.handle_save(old, handle, data); + } + + 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.read())?; + } + self.shifts += 1; + + Ok(()) + } + + fn delete_many(&mut self, handles: &mut [EntityHandle]) -> MaybeError { + 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; + } + let find = temp[cell]; + let remove = x.id() == find; + if remove { + cell += 1; + } + !remove + }); + self.data_layer + .delete_many_entities(cached.as_slice(), &self.connection.read())?; + 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 + }); + self.shifts += 1; + + 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, +) -> Option { + if storage.is_empty() { + return None; + } + + let max_idx = storage.len() - 1; + let upper = (handle.index()).min(max_idx); + let peek = storage[upper].id(); + + if peek == handle.id { + handle.set_index(upper); + 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.set_index(new_index); + 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.set_index(index); + 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..c2e777a --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,123 @@ +#[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::new(8.into(), 3, true); + 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::new(8.into(), 4, true); + 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::new(9.into(), 5, true); + assert_eq!( + fast_position_of(&[1, 2, 3, 8, 9, 12].map(ProtoEntity::new), &mut entity), + Some(4) + ); + } + + #[test] + fn search_2() { + let mut entity = EntityHandle::new(106.into(), 25, true); + 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(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) + } +}