sound-visualisation/lib/resources/src/shared/mod.rs

372 lines
13 KiB
Rust

use crate::backend::{Backend, BackendSyncPoint, Modification};
use crate::path::{ResourcePath, ResourcePathBuf};
use slab::Slab;
use std::collections::{HashMap, BTreeMap, VecDeque};
use std::hash::BuildHasherDefault;
use std::time::Instant;
use twox_hash::XxHash;
mod resource_metadata;
use self::resource_metadata::{ResourceMetadata, ResourceUserMetadata};
#[derive(Clone, Debug, Eq, PartialEq)]
struct LoaderKey {
id: String,
order: isize,
}
impl Ord for LoaderKey {
fn cmp(&self, other: &LoaderKey) -> ::std::cmp::Ordering {
match self.order.cmp(&other.order) {
::std::cmp::Ordering::Equal => self.id.cmp(&other.id),
ordering => ordering,
}
}
}
impl PartialOrd for LoaderKey {
fn partial_cmp(&self, other: &LoaderKey) -> Option<::std::cmp::Ordering> {
Some(self.cmp(other))
}
}
#[derive(Copy, Clone)]
pub struct UserKey {
pub resource_id: usize,
user_id: usize,
}
#[derive(Eq, PartialEq, Copy, Clone)]
pub enum InternalSyncPoint {
Backend {
backend_hash: u64,
sync_point: BackendSyncPoint,
},
Everything {
time: Instant,
},
}
pub struct SharedResources {
resource_metadata: Slab<ResourceMetadata>,
path_resource_ids: HashMap<ResourcePathBuf, usize, BuildHasherDefault<XxHash>>,
backends: BTreeMap<LoaderKey, Box<Backend>>,
outdated_at: Option<Instant>,
modification_queue: VecDeque<Modification>,
}
fn backend_hash(id: &str) -> u64 {
use std::hash::Hasher;
let mut hasher = XxHash::with_seed(8745287);
hasher.write(id.as_bytes());
hasher.finish()
}
impl SharedResources {
pub fn new() -> SharedResources {
SharedResources {
resource_metadata: Slab::with_capacity(1024), // 1024 files is enough for everyone
path_resource_ids: HashMap::default(),
backends: BTreeMap::new(),
outdated_at: None,
modification_queue: VecDeque::new(),
}
}
pub fn new_changes(&mut self) -> Option<InternalSyncPoint> {
if let Some(instant) = self.outdated_at {
return Some(InternalSyncPoint::Everything { time: instant });
}
let mut new_change_point = None;
let mut mod_queue = ::std::mem::replace(&mut self.modification_queue, VecDeque::new());
for (key, backend) in self.backends.iter_mut() {
mod_queue.clear();
if let Some(sync_point) = backend.new_changes(&mut mod_queue) {
new_change_point = Some(InternalSyncPoint::Backend {
backend_hash: backend_hash(&key.id),
sync_point,
});
break;
}
}
if let Some(InternalSyncPoint::Backend { backend_hash: bh, sync_point }) = new_change_point {
let mut some_resource_is_modified = false;
while let Some(modification) = mod_queue.pop_front() {
match modification {
Modification::Create(p) => {
if let Some(resource_id) = self.path_resource_ids.get(&p) {
if let Some(ref mut meta) = self.resource_metadata.get_mut(*resource_id) {
meta.everyone_should_reload(sync_point.instant);
some_resource_is_modified = true;
}
}
},
Modification::Write(p) => {
if let Some(resource_id) = self.path_resource_ids.get(&p) {
if let Some(ref mut meta) = self.resource_metadata.get_mut(*resource_id) {
meta.everyone_should_reload(sync_point.instant);
some_resource_is_modified = true;
}
}
},
Modification::Remove(p) => {
if let Some(resource_id) = self.path_resource_ids.get(&p) {
if let Some(ref mut meta) = self.resource_metadata.get_mut(*resource_id) {
meta.everyone_should_reload(sync_point.instant);
some_resource_is_modified = true;
}
}
},
Modification::Rename { from, to } => {
if let (Some(resource_id), Some(resource_id_to)) = (self.path_resource_ids.get(&from), self.path_resource_ids.get(&to)) {
if let Some(ref mut meta) = self.resource_metadata.get_mut(*resource_id) {
meta.everyone_should_reload(sync_point.instant);
some_resource_is_modified = true;
}
if let Some(ref mut meta) = self.resource_metadata.get_mut(*resource_id_to) {
meta.everyone_should_reload(sync_point.instant);
some_resource_is_modified = true;
}
}
},
}
}
if let false = some_resource_is_modified {
for (key, backend) in self.backends.iter_mut() {
if backend_hash(&key.id) == bh {
backend.notify_changes_synced(sync_point);
break;
}
}
new_change_point = None;
}
}
::std::mem::replace(&mut self.modification_queue, mod_queue);
new_change_point
}
pub fn notify_changes_synced(&mut self, sync_point: InternalSyncPoint) {
match sync_point {
InternalSyncPoint::Everything { time } => if self.outdated_at == Some(time) {
self.outdated_at = None;
},
InternalSyncPoint::Backend {
backend_hash: bh,
sync_point: sp,
} => {
for (key, backend) in self.backends.iter_mut() {
if backend_hash(&key.id) == bh {
backend.notify_changes_synced(sp);
break;
}
}
}
}
}
pub fn new_resource_user<P: AsRef<ResourcePath>>(&mut self, path: P) -> UserKey {
let clean_path_str: &ResourcePath = path.as_ref().as_clean_str().into();
let maybe_id = self.path_resource_ids.get(clean_path_str).cloned();
match maybe_id {
Some(id) => self.append_resource_user(id),
None => {
let mut metadata = ResourceMetadata::new(clean_path_str);
let user_id = metadata.new_user();
let resource_id = self.resource_metadata.insert(metadata);
self.path_resource_ids
.insert(ResourcePathBuf::from(clean_path_str), resource_id);
UserKey {
resource_id,
user_id,
}
}
}
}
/// Appends user to resource, the resource id must exist.
pub fn append_resource_user(&mut self, resource_id: usize) -> UserKey {
UserKey {
resource_id,
user_id: self
.resource_metadata
.get_mut(resource_id)
.expect("expected resource_id to exist when appending new user")
.new_user(),
}
}
pub fn remove_resource_user(&mut self, key: UserKey) {
let has_users = {
if let Some(metadata) = self.resource_metadata.get_mut(key.resource_id) {
metadata.remove_user(key.user_id);
Some(metadata.has_users())
} else {
None
}
};
if let Some(false) = has_users {
let metadata = self.resource_metadata.remove(key.resource_id);
self.path_resource_ids.remove(&metadata.path);
}
}
pub fn get_path_user_metadata(&self, key: UserKey) -> Option<&ResourceUserMetadata> {
self.resource_metadata
.get(key.resource_id)
.and_then(|path_metadata| path_metadata.get_user_metadata(key.user_id))
}
fn get_path_user_metadata_mut(&mut self, key: UserKey) -> Option<&mut ResourceUserMetadata> {
self.resource_metadata
.get_mut(key.resource_id)
.and_then(|path_metadata| path_metadata.get_user_metadata_mut(key.user_id))
}
pub fn insert_loader<L: Backend + 'static>(
&mut self,
loader_id: &str,
order: isize,
backend: L,
) {
let outdated_at = Instant::now();
for (path, resource_id) in self.path_resource_ids.iter() {
if backend.exists(&path) {
if let Some(metadata) = self.resource_metadata.get_mut(*resource_id) {
metadata.everyone_should_reload(outdated_at);
}
}
}
self.backends.insert(
LoaderKey {
id: loader_id.into(),
order,
},
Box::new(backend) as Box<Backend>,
);
if self.path_resource_ids.len() > 0 {
self.outdated_at = Some(outdated_at);
}
}
pub fn remove_loader(&mut self, loader_id: &str) {
let outdated_at = Instant::now();
let remove_keys: Vec<_> = self
.backends
.keys()
.filter(|k| k.id == loader_id)
.map(|k| k.clone())
.collect();
for removed_key in remove_keys {
if let Some(removed_backend) = self.backends.remove(&removed_key) {
for (path, resource_id) in self.path_resource_ids.iter() {
if removed_backend.exists(&path) {
if let Some(metadata) = self.resource_metadata.get_mut(*resource_id) {
metadata.everyone_should_reload(outdated_at);
}
}
}
}
}
if self.path_resource_ids.len() > 0 {
self.outdated_at = Some(outdated_at);
}
}
pub fn resource_backends(
&mut self,
key: UserKey,
) -> impl Iterator<Item = (&ResourcePath, Option<Instant>, &mut Box<Backend>)> {
let path_with_modification_time =
self.resource_metadata.get(key.resource_id).and_then(|m| {
m.users
.get(key.user_id)
.map(|u| (m.path.as_ref(), u.outdated_at))
});
self.backends.iter_mut().rev().filter_map(move |(_, b)| {
path_with_modification_time.map(move |(path, instant)| (path, instant, b))
})
}
#[allow(dead_code)]
pub fn get_resource_path_backend(
&self,
backend_id: &str,
key: UserKey,
) -> Option<(&ResourcePath, Option<Instant>, &Box<Backend>)> {
let path_with_modification_time =
self.resource_metadata.get(key.resource_id).and_then(|m| {
m.users
.get(key.user_id)
.map(|u| (m.path.as_ref(), u.outdated_at))
});
if let (Some((path, modification_time)), Some((_, backend))) = (
path_with_modification_time,
self.backends
.iter()
.filter(|(k, _)| &k.id == backend_id)
.next(),
) {
return Some((path, modification_time, backend));
}
None
}
pub fn get_resource_path(&self, key: UserKey) -> Option<&ResourcePath> {
self.resource_metadata
.get(key.resource_id)
.map(|m| m.path.as_ref())
}
pub fn get_resource_path_backend_containing_resource(
&self,
key: UserKey,
) -> Option<(&ResourcePath, Option<Instant>, &Box<Backend>)> {
let path_with_modification_time =
self.resource_metadata.get(key.resource_id).and_then(|m| {
m.users
.get(key.user_id)
.map(|u| (m.path.as_ref(), u.outdated_at))
});
if let Some((path, modification_time)) = path_with_modification_time {
for backend in self.backends.values().rev() {
if backend.exists(path) {
return Some((path, modification_time, backend));
}
}
}
None
}
pub fn notify_did_read(&mut self, key: UserKey, modified_time: Option<Instant>) {
if let Some(metadata) = self.get_path_user_metadata_mut(key) {
if metadata.outdated_at == modified_time {
metadata.outdated_at = None;
}
}
}
pub fn notify_did_write(&mut self, key: UserKey, modified_time: Instant) {
if let Some(metadata) = self.resource_metadata.get_mut(key.resource_id) {
metadata.everyone_should_reload_except(key.user_id, modified_time)
}
}
}