use crate::*;
use parking_lot::Mutex;
use std::{
any::{Any, TypeId},
cmp::Ordering,
collections::{BTreeMap, BTreeSet},
sync::Arc,
thread::spawn,
};
#[cfg_attr(docsrs, doc(cfg(feature = "app")))]
pub trait Resource: Sized {
type Config: PrefixedFromEnvironment;
type Customizer;
fn create(
config: Self::Config,
factory: &FactoryContext<'_>,
customizer: impl FnOnce(&mut Self::Customizer, &Self::Config) -> Void,
) -> Res<Self>;
fn post_initialized_and_registered(_res: &Arc<Self>, _factory: &FactoryContext<'_>) -> Void {
Ok(())
}
fn register_dependent_resources(_: &mut FactoryBuilder<'_>) -> Void {
Ok(())
}
fn order() -> Ordered {
PRIORITY_NORMAL
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct Ordered(i32);
impl Ordered {
pub fn after<R: Resource>(self) -> Self {
let r = R::order();
match r.cmp(&self) {
Ordering::Less => self,
Ordering::Equal => Ordered(self.0 + 1),
_ => r,
}
}
pub fn before<R: Resource>(self) -> Self {
let r = R::order();
match r.cmp(&self) {
Ordering::Less => r,
Ordering::Equal => Ordered(self.0 - 1),
_ => self,
}
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "app")))]
pub const PRIORITY_HIGHEST: Ordered = Ordered(i32::MIN + 1048576);
#[cfg_attr(docsrs, doc(cfg(feature = "app")))]
pub const PRIORITY_HIGH: Ordered = Ordered(i32::MIN + 1073741824);
#[cfg_attr(docsrs, doc(cfg(feature = "app")))]
pub const PRIORITY_NORMAL: Ordered = Ordered(0);
#[cfg_attr(docsrs, doc(cfg(feature = "app")))]
pub const PRIORITY_LOW: Ordered = Ordered(i32::MAX - 1073741824);
#[cfg_attr(docsrs, doc(cfg(feature = "app")))]
pub const PRIORITY_LOWEST: Ordered = Ordered(i32::MAX - 1048576);
#[allow(missing_debug_implementations)]
#[cfg_attr(docsrs, doc(cfg(feature = "app")))]
pub struct FactoryContext<'a> {
fac: &'a Salak,
namespace: &'static str,
}
impl FactoryContext<'_> {
pub fn current_namespace(&self) -> &'static str {
self.namespace
}
pub fn get_resource<R: Resource + Send + Sync + Any>(&self) -> Res<Arc<R>> {
self.get_resource_by_namespace("")
}
pub fn get_resource_by_namespace<R: Resource + Send + Sync + Any>(
&self,
namespace: &'static str,
) -> Res<Arc<R>> {
#[cfg(feature = "log")]
log::info!(
"Request for resource ({}) at namespace [{}].",
std::any::type_name::<R>(),
namespace
);
self.fac.res.get_ref(namespace, self.fac, false)
}
#[inline]
pub fn get_optional_resource<R: Resource + Send + Sync + Any>(&self) -> Res<Option<Arc<R>>> {
self.get_optional_resource_by_namespace("")
}
#[inline]
pub fn get_optional_resource_by_namespace<R: Resource + Send + Sync + Any>(
&self,
namespace: &'static str,
) -> Res<Option<Arc<R>>> {
match self.get_resource_by_namespace::<R>(namespace) {
Ok(v) => Ok(Some(v)),
Err(PropertyError::ResourceNotFound(_, _)) => Ok(None),
Err(err) => Err(err),
}
}
pub fn get_all_resources<R: Resource + Send + Sync + Any>(
&self,
) -> Res<BTreeMap<&'static str, Arc<R>>> {
self.fac.res.get_all_refs(self.fac, false)
}
}
struct Task(
Option<
Box<
dyn FnOnce(&Salak) -> Res<Box<dyn FnOnce() + Send + Sync + 'static>>
+ Send
+ Sync
+ 'static,
>,
>,
);
impl Task {
fn new<R: Resource + Send + Sync + 'static>(
namespace: &'static str,
task: impl Fn(Arc<R>) -> Void + Send + Sync + 'static,
) -> Self {
Task(Some(Box::new(move |env: &Salak| {
let res = env.res.get_ref::<R>(namespace, env, true)?;
Ok(Box::new(move || (task)(res).unwrap()))
})))
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "app")))]
#[allow(missing_debug_implementations)]
pub struct FactoryBuilder<'a> {
builder: &'a mut ResourceRegistry,
namespace: &'static str,
}
impl FactoryBuilder<'_> {
pub fn submit<R: Resource + Send + Sync + Any>(
&mut self,
task: impl Fn(Arc<R>) -> Void + Send + Sync + 'static,
) -> Void {
let task = Task::new(self.namespace, task);
self.builder.1.push(task);
Ok(())
}
pub fn register_resource<R: Resource + Send + Sync + Any>(&mut self) -> Void {
self.builder
.register::<R>(ResourceBuilder::new(self.namespace))
}
pub fn register_resource_with_customizer<R: Resource + Send + Sync + Any>(
&mut self,
customizer: impl FnOnce(&mut R::Customizer, &R::Config) -> Void + Send + Sync + 'static,
) -> Void {
self.builder
.register::<R>(ResourceBuilder::new(self.namespace).customize(customizer))
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "app")))]
pub trait Service: Sized {
fn create(factory: &FactoryContext<'_>) -> Res<Self>;
}
impl<T: Service> Resource for T {
type Config = ();
type Customizer = ();
#[inline]
fn create(
_: Self::Config,
factory: &FactoryContext<'_>,
_: impl FnOnce(&mut Self::Customizer, &Self::Config) -> Void,
) -> Res<Self> {
T::create(factory)
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "app")))]
pub trait Factory: Environment {
#[inline]
fn get_resource<R: Resource + Send + Sync + Any>(&self) -> Res<Arc<R>> {
self.get_resource_by_namespace("")
}
fn get_resource_by_namespace<R: Resource + Send + Sync + Any>(
&self,
namespace: &'static str,
) -> Res<Arc<R>>;
#[inline]
fn init_resource<R: Resource>(&self) -> Res<R> {
self.init_resource_with_builder(ResourceBuilder::default())
}
fn init_resource_with_builder<R: Resource>(&self, builder: ResourceBuilder<R>) -> Res<R>;
fn get_service<S: Service>(&self) -> Res<S> {
self.init_resource::<S>()
}
fn run(&mut self) -> Void;
}
impl Resource for () {
type Config = ();
type Customizer = ();
fn create(
_: Self::Config,
_: &FactoryContext<'_>,
_: impl FnOnce(&mut Self::Customizer, &Self::Config) -> Void,
) -> Res<Self> {
Ok(())
}
}
macro_rules! impl_container {
($($x:ident)+) => {$(
impl<T: Resource> Resource for $x<T> {
type Config = T::Config;
type Customizer = T::Customizer;
fn create(
config: Self::Config,
factory: &FactoryContext<'_>,
customizer: impl FnOnce(&mut Self::Customizer, &Self::Config) -> Result<(), PropertyError>,
) -> Result<Self, PropertyError> {
Ok($x::new(T::create(config, factory, customizer)?))
}
fn order() -> Ordered {
T::order()
}
}
)+};
}
mod warp {
use crate::*;
use std::{
cell::{Cell, RefCell},
rc::Rc,
sync::{Arc, Mutex, RwLock},
};
impl_container!(Cell RefCell Mutex RwLock Rc Arc);
}
impl<T: Resource> Resource for Option<T> {
type Config = T::Config;
type Customizer = T::Customizer;
fn create(
config: Self::Config,
factory: &FactoryContext<'_>,
customizer: impl FnOnce(&mut Self::Customizer, &Self::Config) -> Void,
) -> Res<Self> {
match T::create(config, factory, customizer) {
Ok(v) => Ok(Some(v)),
Err(PropertyError::ResourceNotFound(_, _)) => Ok(None),
Err(e) => Err(e),
}
}
fn order() -> Ordered {
T::order()
}
}
struct Init(Box<dyn FnOnce(&Salak, &Mutex<ResVal>) -> Void + Send>);
impl<R: Resource + Send + Sync + 'static> ResourceBuilder<R> {
#[inline]
fn into_init(self) -> Init {
Init(Box::new(move |env, val| {
#[cfg(feature = "log")]
log::info!(
"init resource ({}) at namespace [{}].",
std::any::type_name::<R>(),
self.namespace
);
let namespace = self.namespace;
let context = FactoryContext {
fac: env,
namespace,
};
let res = Arc::new(env.do_init_resource_with_builder::<R>(&context, self)?);
R::post_initialized_and_registered(&res, &context)?;
*val.lock() = Some(res);
Ok(())
}))
}
}
type ResVal = Option<Arc<dyn Any + Send + Sync>>;
struct ResourceHolder(Mutex<ResVal>, Mutex<Option<Init>>, Ordered);
impl PartialEq for ResourceHolder {
fn eq(&self, r: &ResourceHolder) -> bool {
self.2 == r.2
}
}
impl Eq for ResourceHolder {}
impl PartialOrd for ResourceHolder {
fn partial_cmp(&self, r: &ResourceHolder) -> Option<Ordering> {
self.2.partial_cmp(&r.2)
}
}
impl Ord for ResourceHolder {
fn cmp(&self, r: &Self) -> Ordering {
self.2.cmp(&r.2)
}
}
impl ResourceHolder {
fn new<R: Resource + Send + Sync + 'static>(builder: ResourceBuilder<R>) -> Self {
let order = builder.order;
Self(
Mutex::new(None),
Mutex::new(Some(builder.into_init())),
order,
)
}
#[inline]
fn init(&self, env: &Salak) -> Void {
let mut guard = self.1.lock();
if let Some(b) = guard.take() {
drop(guard);
return (b.0)(env, &self.0);
}
Ok(())
}
fn get_or_init<R: Resource + Send + Sync + 'static>(
&self,
env: &Salak,
namespace: &'static str,
query_only: bool,
) -> Res<Arc<R>> {
let guard = self.0.lock();
if let Some(arc) = guard.as_ref() {
if let Ok(v) = arc.clone().downcast::<R>() {
return Ok(v);
} else {
return Err(PropertyError::ResourceNotFound(
namespace,
std::any::type_name::<R>(),
));
}
}
drop(guard);
if query_only {
return Err(PropertyError::ResourceNotFound(
namespace,
std::any::type_name::<R>(),
));
}
self.init(env)?;
match self.get_or_init(env, namespace, true) {
Err(PropertyError::ResourceNotFound(a, b)) => {
Err(PropertyError::ResourceRecursive(a, b))
}
v => v,
}
}
}
pub(crate) struct ResourceRegistry(
BTreeMap<TypeId, BTreeMap<&'static str, ResourceHolder>>,
Vec<Task>,
);
impl ResourceRegistry {
pub(crate) fn new() -> Self {
Self(BTreeMap::new(), vec![])
}
pub(crate) fn initialize(&self, env: &Salak) -> Void {
let mut v = BTreeSet::new();
for x in self.0.values() {
for r in x.values() {
v.insert(r);
}
}
for r in v {
r.init(env)?;
}
Ok(())
}
#[inline]
pub(crate) fn register<R: Resource + Send + Sync + Any>(
&mut self,
builder: ResourceBuilder<R>,
) -> Void {
let namespace = builder.namespace;
let map = self
.0
.entry(TypeId::of::<R>())
.or_insert_with(|| BTreeMap::new());
if map.contains_key(namespace) {
return Err(PropertyError::ResourceRegistered(
namespace,
std::any::type_name::<R>(),
));
}
#[cfg(feature = "log")]
log::info!(
"Register resource ({}) at namespace [{}].",
std::any::type_name::<R>(),
namespace
);
map.insert(namespace, ResourceHolder::new(builder));
R::register_dependent_resources(&mut FactoryBuilder {
builder: self,
namespace,
})
}
#[inline]
fn get_ref<R: Resource + Send + Sync + Any>(
&self,
namespace: &'static str,
env: &Salak,
query_only: bool,
) -> Res<Arc<R>> {
if let Some(v) = self
.0
.get(&TypeId::of::<R>())
.and_then(|f| f.get(namespace))
{
return v.get_or_init(env, namespace, query_only);
}
Err(PropertyError::ResourceNotFound(
namespace,
std::any::type_name::<R>(),
))
}
#[inline]
fn get_all_refs<R: Resource + Send + Sync + Any>(
&self,
env: &Salak,
query_only: bool,
) -> Res<BTreeMap<&'static str, Arc<R>>> {
let mut r = BTreeMap::new();
for map in self.0.get(&TypeId::of::<R>()) {
for (namespace, v) in map {
r.insert(*namespace, v.get_or_init(env, namespace, query_only)?);
}
}
Ok(r)
}
}
impl Salak {
fn do_init_resource_with_builder<R: Resource>(
&self,
context: &FactoryContext<'_>,
builder: ResourceBuilder<R>,
) -> Result<R, PropertyError> {
let config = if builder.namespace.is_empty() {
self.require::<R::Config>(<R::Config>::prefix())
} else {
self.require::<R::Config>(&format!("{}.{}", <R::Config>::prefix(), builder.namespace))
}?;
R::create(config, &context, builder.customizer)
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "app")))]
impl Factory for Salak {
#[inline]
fn get_resource_by_namespace<R: Resource + Send + Sync + Any>(
&self,
namespace: &'static str,
) -> Result<Arc<R>, PropertyError> {
self.res.get_ref(namespace, self, true)
}
#[inline]
fn init_resource_with_builder<R: Resource>(&self, builder: ResourceBuilder<R>) -> Res<R> {
let context = FactoryContext {
fac: self,
namespace: builder.namespace,
};
self.do_init_resource_with_builder(&context, builder)
}
fn run(&mut self) -> Void {
let mut join = vec![];
for mut task in std::mem::replace(&mut self.res.1, vec![]) {
if let Some(v) = task.0.take() {
join.push(spawn((v)(self)?));
}
}
for join in join {
let _ = join.join();
}
Ok(())
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "app")))]
#[allow(missing_debug_implementations)]
pub struct ResourceBuilder<R: Resource> {
pub(crate) namespace: &'static str,
order: Ordered,
customizer: Box<dyn FnOnce(&mut R::Customizer, &R::Config) -> Void + Send>,
}
impl<R: Resource> Default for ResourceBuilder<R> {
fn default() -> Self {
Self::new("")
}
}
impl<R: Resource> ResourceBuilder<R> {
#[inline]
pub fn new(namespace: &'static str) -> Self {
Self {
namespace,
order: R::order(),
customizer: Box::new(|_, _| Ok(())),
}
}
#[inline]
pub fn namespace(mut self, namespace: &'static str) -> Self {
self.namespace = namespace;
self
}
#[inline]
pub fn customize(
mut self,
cust: impl FnOnce(&mut R::Customizer, &R::Config) -> Void + Send + Sync + 'static,
) -> Self {
self.customizer = Box::new(cust);
self
}
}
#[macro_export]
macro_rules! generate_service{
(@ $name:ident {} -> ($($res:tt)*)) => {
#[derive($crate::Service)]
#[allow(dead_code,unreachable_pub,missing_debug_implementations, missing_docs)]
pub struct $name {
$($res)*
}
};
(@ $name:ident {$(#[$m:meta])* $field:ident: Option<$t:ty>, $($tt:tt)*} -> ($($res:tt)*)) => {
generate_service!(@ $name {$($tt)*} -> ($($res)* $(#[$m])* $field:Option<Arc<$t>>,));
};
(@ $name:ident {$(#[$m:meta])* $field:ident: $t:ty, $($tt:tt)*} -> ($($res:tt)*)) => {
generate_service!(@ $name {$($tt)*} -> ($($res)* $(#[$m])* $field:Arc<$t>,));
};
(@ $name:ident {$(#[$m:meta])* $field:ident: Option<$t:ty>} -> ($($res:tt)*)) => {
generate_service!(@ $name {} -> ($($res)* $(#[$m])* $field:Option<Arc<$t>>,));
};
(@ $name:ident { $(#[$m:meta])* $field:ident: $t:ty} -> ($($res:tt)*)) => {
generate_service!(@ $name {} -> ($($res)* $(#[$m])* $field:Arc<$t>,));
};
($name: ident {$($tt:tt)*}) => {
generate_service!(@ $name {$($tt)*} -> ());
};
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn app_test() {
let env = Salak::builder().build().unwrap();
let v = env.get_resource::<()>();
assert_eq!(true, v.is_err());
let env = Salak::builder()
.register_default_resource::<()>()
.unwrap()
.build()
.unwrap();
let v = env.get_resource::<()>();
assert_eq!(true, v.is_ok());
}
use std::sync::Arc;
generate_service!(X { a: Option<()>, b: ()});
generate_service!(Y { a: Option<()>});
generate_service!(Z {
#[salak(namespace="hello", access="pub")]
b:(),
a: Option<()>
});
}