use std::{
borrow::Cow,
path::{Path, PathBuf},
};
use ahash::{HashMap, HashMapExt};
use anyhow::{anyhow, ensure, Context as _};
use clean_path::Clean as _;
use parking_lot::RwLock;
pub trait FileSystem {
fn read_to_string(&self, path: impl AsRef<Path>) -> anyhow::Result<Cow<'static, str>>;
fn canonicalize(&self, path: impl AsRef<Path>) -> anyhow::Result<PathBuf>;
fn exists(&self, path: impl AsRef<Path>) -> bool;
fn create_dir_all(&self, _path: impl AsRef<Path>) -> anyhow::Result<()> {
panic!("create_dir_all() is not supported on this backend")
}
fn create_file(
&self,
_path: impl AsRef<Path>,
_contents: Cow<'static, str>,
) -> anyhow::Result<()> {
panic!("create_file() is not supported on this backend")
}
}
#[cfg(load_shaders_from_disk)]
pub fn get_filesystem() -> OsFileSystem {
OsFileSystem
}
#[cfg(not(load_shaders_from_disk))]
pub fn get_filesystem() -> &'static MemFileSystem {
MemFileSystem::get()
}
#[derive(Default)]
pub struct OsFileSystem;
impl FileSystem for OsFileSystem {
fn read_to_string(&self, path: impl AsRef<Path>) -> anyhow::Result<Cow<'static, str>> {
let path = path.as_ref();
std::fs::read_to_string(path)
.with_context(|| format!("failed to read file at {path:?}"))
.map(Into::into)
}
fn canonicalize(&self, path: impl AsRef<Path>) -> anyhow::Result<PathBuf> {
let path = path.as_ref();
std::fs::canonicalize(path)
.with_context(|| format!("failed to canonicalize path at {path:?}"))
}
fn exists(&self, path: impl AsRef<Path>) -> bool {
path.as_ref().exists()
}
}
pub struct MemFileSystem {
files: RwLock<Option<HashMap<PathBuf, Cow<'static, str>>>>,
}
static MEM_FILE_SYSTEM: MemFileSystem = MemFileSystem::new_uninit();
impl MemFileSystem {
const fn new_uninit() -> Self {
Self {
files: RwLock::new(None),
}
}
}
impl MemFileSystem {
pub fn get() -> &'static Self {
if MEM_FILE_SYSTEM.files.read().is_some() {
return &MEM_FILE_SYSTEM;
}
{
let mut files = MEM_FILE_SYSTEM.files.write();
if files.is_none() {
*files = Some(HashMap::new());
}
}
&MEM_FILE_SYSTEM
}
}
impl FileSystem for &'static MemFileSystem {
fn read_to_string(&self, path: impl AsRef<Path>) -> anyhow::Result<Cow<'static, str>> {
let path = path.as_ref().clean();
let files = self.files.read();
let files = files.as_ref().unwrap();
files
.get(&path)
.cloned()
.ok_or_else(|| anyhow!("file does not exist at {path:?}"))
}
fn canonicalize(&self, path: impl AsRef<Path>) -> anyhow::Result<PathBuf> {
let path = path.as_ref().clean();
let files = self.files.read();
let files = files.as_ref().unwrap();
ensure!(files.contains_key(&path), "file does not exist at {path:?}",);
Ok(path)
}
fn exists(&self, path: impl AsRef<Path>) -> bool {
let files = self.files.read();
let files = files.as_ref().unwrap();
files.contains_key(&path.as_ref().clean())
}
fn create_dir_all(&self, _: impl AsRef<Path>) -> anyhow::Result<()> {
Ok(())
}
fn create_file(
&self,
path: impl AsRef<Path>,
contents: Cow<'static, str>,
) -> anyhow::Result<()> {
let mut files = self.files.write();
let files = files.as_mut().unwrap();
files.insert(path.as_ref().to_owned(), contents);
Ok(())
}
}