Don't lean on filesystem so much
This commit is contained in:
+56
-32
@@ -1,10 +1,11 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
any::Any,
|
||||||
|
collections::{HashMap, hash_map::Entry},
|
||||||
fs,
|
fs,
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{
|
sync::{
|
||||||
Arc, Mutex,
|
Arc, Mutex, RwLock,
|
||||||
atomic::{AtomicU64, Ordering},
|
atomic::{AtomicU64, Ordering},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -16,25 +17,22 @@ use serde::{Deserialize, Serialize};
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Persistence {
|
pub struct Persistence {
|
||||||
config_dir: Option<PathBuf>,
|
config_dir: Option<PathBuf>,
|
||||||
versions: Arc<Mutex<HashMap<String, Arc<AtomicU64>>>>,
|
states: Arc<Mutex<HashMap<String, Arc<dyn Any + Send + Sync>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Persistence {
|
impl Persistence {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
config_dir: init_config_dir(),
|
config_dir: init_config_dir(),
|
||||||
versions: Arc::new(Mutex::new(HashMap::new())),
|
states: Arc::new(Mutex::new(HashMap::new())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn config<T: ConfigCompatible>(&self, file: &str) -> Config<T> {
|
pub fn config<T: ConfigCompatible>(&self, file: &str) -> Config<T> {
|
||||||
let latest_version = self
|
let state = {
|
||||||
.versions
|
let mut lock = self.states.lock().unwrap();
|
||||||
.lock()
|
match lock.entry(file.to_string()) {
|
||||||
.unwrap()
|
Entry::Vacant(e) => {
|
||||||
.entry(file.to_string())
|
|
||||||
.or_default()
|
|
||||||
.clone();
|
|
||||||
let path = self
|
let path = self
|
||||||
.config_dir
|
.config_dir
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -47,11 +45,28 @@ impl Persistence {
|
|||||||
} else {
|
} else {
|
||||||
T::default()
|
T::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let state = Arc::new(ConfigState {
|
||||||
|
current: RwLock::new(value),
|
||||||
|
version: AtomicU64::new(0),
|
||||||
|
path,
|
||||||
|
});
|
||||||
|
e.insert(state.clone());
|
||||||
|
state
|
||||||
|
}
|
||||||
|
Entry::Occupied(e) => e
|
||||||
|
.get()
|
||||||
|
.clone()
|
||||||
|
.downcast()
|
||||||
|
.expect("same config file accessed with two types"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let value = state.current.read().unwrap().clone();
|
||||||
|
let version = state.version.load(Ordering::Relaxed);
|
||||||
Config {
|
Config {
|
||||||
value,
|
value,
|
||||||
version: latest_version.load(Ordering::Relaxed),
|
version,
|
||||||
latest_version,
|
state,
|
||||||
path,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,16 +105,27 @@ fn project_dirs() -> Option<ProjectDirs> {
|
|||||||
ProjectDirs::from("com", "virtual-boy", "Lemur")
|
ProjectDirs::from("com", "virtual-boy", "Lemur")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ConfigCompatible: Clone + Default + Eq + Serialize + for<'a> Deserialize<'a> {}
|
pub trait ConfigCompatible:
|
||||||
impl<T: Clone + Default + Eq + Serialize + for<'a> Deserialize<'a>> ConfigCompatible for T {}
|
Send + Sync + 'static + Clone + Default + Eq + Serialize + for<'a> Deserialize<'a>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
impl<T: Send + Sync + 'static + Clone + Default + Eq + Serialize + for<'a> Deserialize<'a>>
|
||||||
|
ConfigCompatible for T
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
// A wrapper for a T (probably) stored on the filesystem.
|
struct ConfigState<T> {
|
||||||
// Cheap to dereference, easy to sync changes to all copies.
|
current: RwLock<T>,
|
||||||
|
version: AtomicU64,
|
||||||
|
path: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// A wrapper for a config struct, loaded from disk and referenced across the codebase.
|
||||||
|
// Cheap to read, and lets you sync changes to all copies.
|
||||||
pub struct Config<T> {
|
pub struct Config<T> {
|
||||||
value: T,
|
value: T,
|
||||||
version: u64,
|
version: u64,
|
||||||
latest_version: Arc<AtomicU64>,
|
state: Arc<ConfigState<T>>,
|
||||||
path: Option<PathBuf>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Deref for Config<T> {
|
impl<T> Deref for Config<T> {
|
||||||
@@ -112,32 +138,30 @@ impl<T> Deref for Config<T> {
|
|||||||
|
|
||||||
impl<T: ConfigCompatible> Config<T> {
|
impl<T: ConfigCompatible> Config<T> {
|
||||||
pub fn refresh(&mut self) {
|
pub fn refresh(&mut self) {
|
||||||
let version = self.latest_version.load(Ordering::Relaxed);
|
let version = self.state.version.load(Ordering::Relaxed);
|
||||||
if self.version == version {
|
if self.version == version {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let Some(path) = &self.path
|
self.value = self.state.current.read().unwrap().clone();
|
||||||
&& let Ok(bytes) = fs::read(path)
|
|
||||||
&& let Ok(latest) = serde_json::from_slice(&bytes)
|
|
||||||
{
|
|
||||||
self.value = latest;
|
|
||||||
}
|
|
||||||
self.version = version;
|
self.version = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, update: impl FnOnce(&mut T)) {
|
pub fn update(&mut self, mut update: impl FnMut(&mut T)) {
|
||||||
let mut new_value = self.value.clone();
|
let mut new_value = self.value.clone();
|
||||||
update(&mut new_value);
|
update(&mut new_value);
|
||||||
if self.value == new_value {
|
if self.value == new_value {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let Some(path) = &self.path
|
|
||||||
&& let Ok(bytes) = serde_json::to_vec(&new_value)
|
let mut current = self.state.current.write().unwrap();
|
||||||
|
update(&mut current);
|
||||||
|
if let Some(path) = &self.state.path
|
||||||
|
&& let Ok(bytes) = serde_json::to_vec(&*current)
|
||||||
{
|
{
|
||||||
let _ = fs::write(path, bytes);
|
let _ = fs::write(path, bytes);
|
||||||
}
|
}
|
||||||
let version = self.latest_version.fetch_add(1, Ordering::Relaxed) + 1;
|
let version = self.state.version.fetch_add(1, Ordering::Relaxed) + 1;
|
||||||
self.value = new_value.clone();
|
self.value = current.clone();
|
||||||
self.version = version;
|
self.version = version;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user