First repo code

This commit is contained in:
micialware committed 2026-08-25 15:46:39 +03:00
1 parent 1f3ad22e05
commit 807f398d81
6 files changed
+399 -13

No files matched your search

Generated
+13 -12
View File
@@ -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"
+4 -1
View File
@@ -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
+285
View File
@@ -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<Entity>, 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<Entity>, 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<i64, Entity>, entities: &[Entity]) {
map.clear();
for entity in entities {
map.insert(entity.id, entity.clone());
}
}
fn bench_btreemap_search(map: &BTreeMap<i64, Entity>, id: i64) -> Option<&Entity> {
map.get(&id)
}
fn bench_hashmap_insert(map: &mut HashMap<i64, Entity>, entities: &[Entity]) {
map.clear();
for entity in entities {
map.insert(entity.id, entity.clone());
}
}
fn bench_hashmap_search(map: &HashMap<i64, Entity>, id: i64) -> Option<&Entity> {
map.get(&id)
}
fn bench_hashbrown_insert(map: &mut HashbrownMap<i64, Entity>, entities: &[Entity]) {
map.clear();
for entity in entities {
map.insert(entity.id, entity.clone());
}
}
fn bench_hashbrown_search(map: &HashbrownMap<i64, Entity>, id: i64) -> Option<&Entity> {
map.get(&id)
}
fn bench_fxhashmap_insert(map: &mut FxHashMap<i64, Entity>, entities: &[Entity]) {
map.clear();
for entity in entities {
map.insert(entity.id, entity.clone());
}
}
fn bench_fxhashmap_search(map: &FxHashMap<i64, Entity>, id: i64) -> Option<&Entity> {
map.get(&id)
}
// ========== ГЕНЕРАЦИЯ ДАННЫХ ==========
fn generate_entities(count: usize) -> Vec<Entity> {
(0..count).map(|i| Entity::new(i as i64)).collect()
}
fn generate_search_ids(count: usize, max_id: i64) -> Vec<i64> {
// 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::<Vec<_>>();
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);
+1
View File
@@ -1,4 +1,5 @@
pub mod entities;
pub mod repositories;
use rusqlite::ToSql;
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
+50
View File
@@ -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<E, R: Repository<E>> {
pub(crate) repo: R,
pub(crate) _phantom: PhantomData<E>,
pub(crate) inner: EntityInner,
}
#[derive(Clone)]
pub(crate) struct EntityInner {
pub(crate) id: Id,
pub(crate) real: bool,
}
impl<E, R: Repository<E>> Entity<E, R> {
pub fn load(&self) -> EntityAccess<E, R> {
let clone = *self.clone();
let repo = &self.repo;
let value = repo.get(self.inner.id).unwrap();
EntityAccess { value, entity: clone, }
}
}
pub struct EntityAccess<E, R: Repository<E>> {
entity: Entity<E, R>,
value: E,
}
impl<E, R: Repository<E>> Deref for EntityAccess<E, R> {
type Target = E;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<E, R: Repository<E>> DerefMut for EntityAccess<E, R> {
fn deref_mut(&mut self) -> &mut E {
&mut self.value
}
}
pub trait Repository<T> {
fn get(&self, id: Id) -> Option<T>;
fn create(&mut self, new: T) -> Entity<T, impl Repository<T>>;
fn save(&mut self, entity: EntityInner) -> MaybeError;
}
+46
View File
@@ -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<RwLock<WordRepositoryInner>>,
}
pub struct WordRepositoryInner {
cache: Vec<WordData>,
temporal: Vec<Entity<WordData, WordRepository>>,
next_temporal: u32,
connection: Arc<Mutex<Connection>>,
}
impl Repository<WordData> for WordRepository {
fn get(&self, id: Id) -> Option<WordData> {
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<WordData, WordRepository> {
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!()
}
}