use std::any::TypeId;
use std::borrow::Cow;
use std::collections::HashSet;
use std::ops::Deref;
use std::sync::{Arc, RwLock};
use crate::{FileData, Hash32, error::*};
use crate::{Globals, Item};
const GENERATOR: &str = concat!(env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION"));
const GLOB_OPTS: glob::MatchOptions = glob::MatchOptions {
case_sensitive: true,
require_literal_separator: true,
require_literal_leading_dot: true,
};
#[derive(Debug)]
pub struct WithFile<'a, D> {
pub data: &'a D,
pub file: Arc<FileData>,
}
pub struct Context<'a, G = ()>
where
G: Send + Sync,
{
pub generator: &'static str,
globals: &'a Globals<G>,
items: &'a [&'a Item],
deps: Arc<RwLock<Vec<Tracker>>>,
}
impl<'a, G> Context<'a, G>
where
G: Send + Sync,
{
pub(crate) fn new(
globals: &'a Globals<G>,
items: &'a [&'a Item],
deps: Arc<RwLock<Vec<Tracker>>>,
) -> Self {
Self {
generator: GENERATOR,
globals,
items,
deps,
}
}
pub fn get_globals(&self) -> &Globals<G> {
self.globals
}
pub fn get_refresh_script(&self) -> Option<String> {
self.globals.port.map(|port| {
format!(
r#"
const socket = new WebSocket("ws://localhost:{port}");
socket.addEventListener("message", event => {{
window.location.reload();
}});
"#
)
})
}
pub fn get<T: 'static>(&self, id: impl AsRef<str>) -> Result<&T, ContextError> {
let mut filter = FilterId::new(TypeId::of::<T>(), id.as_ref());
let item = match filter.filter(self.items) {
Some(item) => item,
None => {
let other = filter.other_types(self.items);
let id = id.as_ref().to_string();
return Err(match other.len() {
0 => ContextError::NotFound(id),
_ => ContextError::NotFoundWrongShape(id, other.join(", ")),
});
}
};
let data = match &*item.data {
Ok(ok) => ok.downcast_ref().unwrap(), Err(e) => return Err(ContextError::LazyAssetError(item.id.to_string(), e.clone())),
};
filter.store(item);
self.deps.write().unwrap().push(Tracker::Id(filter));
Ok(data)
}
pub fn glob<T: 'static>(&self, pattern: &str) -> Result<Vec<&T>, ContextError> {
let mut filter = FilterGlob::new(TypeId::of::<T>(), glob::Pattern::new(pattern)?);
let (data, hash) =
filter
.filter(self.items)
.try_fold((Vec::new(), Vec::new()), |mut acc, item| {
let data = match &*item.data {
Ok(ok) => ok,
Err(e) => {
return Err(ContextError::LazyAssetError(
item.id.to_string(),
e.clone(),
));
}
};
acc.0.push(data.downcast_ref().unwrap());
acc.1.push(item.hash);
Ok(acc)
})?;
filter.store(hash);
self.deps.write().unwrap().push(Tracker::Glob(filter));
Ok(data)
}
pub fn glob_one<T: 'static>(&self, pattern: &str) -> Result<&T, ContextError> {
let mut filter = FilterGlob::new(TypeId::of::<T>(), glob::Pattern::new(pattern)?);
let item = match filter.filter(self.items).next() {
Some(item) => item,
None => {
let other = filter.other_types(self.items).join(", ");
return Err(ContextError::NotFoundWrongShape(pattern.to_string(), other));
}
};
let data = match &*item.data {
Ok(ok) => ok,
Err(e) => return Err(ContextError::LazyAssetError(item.id.to_string(), e.clone())),
};
filter.store(vec![item.hash]);
self.deps.write().unwrap().push(Tracker::Glob(filter));
Ok(data.downcast_ref().unwrap())
}
pub fn glob_with_file<T>(&self, pattern: &str) -> Result<Vec<WithFile<'_, T>>, ContextError>
where
T: 'static,
{
let mut filter = FilterGlob::new(TypeId::of::<T>(), glob::Pattern::new(pattern)?);
let (items, hashes) = filter
.filter(self.items)
.filter_map(|item| item.file.as_ref().map(|file| (file.clone(), item)))
.try_fold(
(Vec::new(), Vec::new()),
|mut acc, (file, item)| -> Result<_, ContextError> {
let data = match &*item.data {
Ok(ok) => ok,
Err(e) => {
return Err(ContextError::LazyAssetError(
item.id.to_string(),
e.clone(),
));
}
};
let data = data.downcast_ref().unwrap();
acc.0.push(WithFile { data, file });
acc.1.push(item.hash);
Ok(acc)
},
)?;
filter.store(hashes);
self.deps.write().unwrap().push(Tracker::Glob(filter));
Ok(items)
}
pub fn glob_one_with_file<T: 'static>(
&self,
pattern: &str,
) -> Result<WithFile<'_, T>, ContextError> {
let mut filter = FilterGlob::new(TypeId::of::<T>(), glob::Pattern::new(pattern)?);
let (file, item) = match filter
.filter(self.items)
.filter_map(|item| item.file.as_ref().map(|file| (file.clone(), item)))
.next()
{
Some(item) => item,
None => {
let other = filter.other_types(self.items).join(", ");
return Err(ContextError::NotFoundWrongShape(pattern.to_string(), other));
}
};
let data = match &*item.data {
Ok(ok) => ok,
Err(e) => return Err(ContextError::LazyAssetError(item.id.to_string(), e.clone())),
};
filter.store(vec![item.hash]);
self.deps.write().unwrap().push(Tracker::Glob(filter));
Ok(WithFile {
data: data.downcast_ref().unwrap(),
file,
})
}
}
pub(crate) struct FilterId {
ty: TypeId,
id: String,
hash: Hash32,
}
impl FilterId {
fn new(ty: TypeId, id: &str) -> Self {
Self {
ty,
id: id.to_string(),
hash: Default::default(),
}
}
fn filter<'ctx>(&self, items: &'ctx [&Item]) -> Option<&'ctx Item> {
items
.iter()
.find(|item| item.refl_type == self.ty && *item.id == self.id)
.map(Deref::deref)
}
fn other_types(&self, items: &[&Item]) -> Vec<&'static str> {
items
.iter()
.filter_map(|item| {
if item.refl_type != self.ty && *item.id == self.id {
Some(item.refl_name)
} else {
None
}
})
.collect::<HashSet<_>>()
.into_iter()
.collect()
}
fn check(&self, items: &[&Item]) -> bool {
match self.filter(items) {
Some(item) => self.hash != item.hash,
None => true,
}
}
fn store(&mut self, item: &Item) {
self.hash = item.hash;
}
}
pub(crate) struct FilterGlob {
ty: TypeId,
glob: glob::Pattern,
hash: Cow<'static, [Hash32]>,
}
impl FilterGlob {
fn new(ty: TypeId, glob: glob::Pattern) -> Self {
Self {
ty,
glob,
hash: Default::default(),
}
}
fn filter<'ctx>(&self, items: &'ctx [&'ctx Item]) -> impl Iterator<Item = &'ctx Item> {
items
.iter()
.filter(|item| item.refl_type == self.ty && self.glob.matches_with(&item.id, GLOB_OPTS))
.map(Deref::deref)
}
fn other_types<'ctx>(&self, items: &'ctx [&'ctx Item]) -> Vec<&'static str> {
items
.iter()
.filter_map(|item| {
if item.refl_type != self.ty && self.glob.matches_with(&item.id, GLOB_OPTS) {
Some(item.refl_name)
} else {
None
}
})
.collect::<HashSet<_>>()
.into_iter()
.collect()
}
fn check(&self, items: &[&Item]) -> bool {
let new = self.filter(items).collect::<Vec<_>>();
if self.hash.len() != new.len() {
return true;
}
for item in new {
if !self.hash.contains(&item.hash) {
return true;
}
}
false
}
fn store(&mut self, items: Vec<Hash32>) {
self.hash = if items.is_empty() {
Default::default()
} else {
items.into()
};
}
}
pub enum Tracker {
Id(FilterId),
Glob(FilterGlob),
}
impl Tracker {
pub fn check(&self, items: &[&Item]) -> bool {
match self {
Tracker::Id(filter) => filter.check(items),
Tracker::Glob(filter) => filter.check(items),
}
}
}