#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::{boxed::Box, rc::Rc, string::String, vec::Vec};
use core::{any::type_name, cell::RefCell, fmt::Debug, marker::PhantomData};
use std::sync::OnceLock;
use abi_stable::std_types::{RArc, RVec};
use ffi::{
FfiResult::{self, FfiErr, FfiOk},
FfiStr,
};
use service_provider_factory::ServiceProviderFactoryBuilder;
use strategy::{Identifyable, Strategy};
use untyped::{ArcAutoFreePointer, AutoFreePointer, FromArcAutoFreePointer, UntypedFn};
mod binary_search;
mod cycle_detection;
mod ffi;
mod lifetime;
mod registrar;
mod resolvable;
mod service_provider;
mod service_provider_factory;
mod shared;
#[cfg(feature = "stable_abi")]
pub mod stable_abi;
mod strategy;
mod untyped;
pub use lifetime::LifetimeError;
pub use resolvable::Resolvable;
pub use service_provider::ServiceIterator;
pub use service_provider::ServiceProvider;
pub use service_provider::WeakServiceProvider;
pub use service_provider_factory::ServiceProviderFactory;
pub use shared::ShareInner;
pub use strategy::AnyStrategy;
use crate::{
cycle_detection::{CycleChecker, CycleDetectionInserter},
ffi::FfiUsizeIterator,
resolvable::SealedResolvable,
};
pub type ServiceCollection = GenericServiceCollection<AnyStrategy>;
type InternalBuildResult<TS> = FfiResult<UntypedFn<TS>, MissingDependencyWithDependee<TS>>;
type AnyPtr = *const ();
#[cfg(debug_assertions)]
pub static mut MINFAC_ERROR_HANDLER: extern "C-unwind" fn(&LifetimeError) =
lifetime::default_error_handler;
#[derive(Debug, PartialEq, Eq)]
pub struct Registered<T>(pub T);
pub struct AllRegistered<T>(pub Box<dyn Iterator<Item = T>>);
#[repr(C)]
pub struct GenericServiceCollection<TS: Strategy + 'static> {
strategy: PhantomData<TS>,
producer_factories: RVec<ServiceProducer<TS>>,
}
pub struct AliasBuilder<'a, T: ?Sized, TS: Strategy + 'static>(
Rc<RefCell<&'a mut GenericServiceCollection<TS>>>,
PhantomData<T>,
);
impl<'a, T: Identifyable<TS::Id>, TS: Strategy + 'static> AliasBuilder<'a, T, TS> {
fn new(col: &'a mut GenericServiceCollection<TS>) -> Self {
Self(Rc::new(RefCell::new(col)), PhantomData)
}
pub fn alias<TNew: Identifyable<TS::Id>>(
&mut self,
creator: fn(T) -> TNew,
) -> AliasBuilder<'a, TNew, TS> {
self.0
.borrow_mut()
.with::<Registered<T>>()
.register(creator);
AliasBuilder::<_, TS>(self.0.clone(), PhantomData)
}
}
#[repr(C)]
struct ServiceProducer<TS: Strategy + 'static> {
identifier: TS::Id,
factory: UntypedFnFactory<TS>,
}
impl<TS: Strategy + 'static> ServiceProducer<TS> {
fn new<T: Identifyable<TS::Id>>(factory: UntypedFnFactory<TS>) -> Self {
Self::new_with_type(factory, T::get_id())
}
fn new_with_type(factory: UntypedFnFactory<TS>, type_id: TS::Id) -> Self {
Self {
identifier: type_id,
factory,
}
}
}
type UntypedFnFactoryCreator<TS> = extern "C" fn(
outer_context: AutoFreePointer,
inner_context: &mut UntypedFnFactoryContext<TS>,
) -> InternalBuildResult<TS>;
#[repr(C)]
struct UntypedFnFactory<TS: Strategy + 'static> {
creator: UntypedFnFactoryCreator<TS>,
context: AutoFreePointer,
}
impl<TS: Strategy + 'static> UntypedFnFactory<TS> {
fn no_alloc(context: AnyPtr, creator: UntypedFnFactoryCreator<TS>) -> Self {
Self {
creator,
context: AutoFreePointer::no_alloc(context),
}
}
fn boxed<T>(input: T, creator: UntypedFnFactoryCreator<TS>) -> Self {
Self {
creator,
context: AutoFreePointer::boxed(input),
}
}
fn call(self, ctx: &mut UntypedFnFactoryContext<TS>) -> InternalBuildResult<TS> {
(self.creator)(self.context, ctx)
}
}
#[repr(C)]
struct UntypedFnFactoryContext<'a, TS: Strategy + 'static> {
state_counter: &'a mut usize,
final_ordered_types: &'a RVec<TS::Id>,
cyclic_reference_candidates: CycleDetectionInserter<'a>,
}
impl<TS: Strategy + 'static> UntypedFnFactoryContext<'_, TS> {
fn reserve_state_space(&mut self) -> usize {
let result: usize = *self.state_counter;
*self.state_counter += 1;
result
}
}
impl<TS: Strategy + 'static> Default for GenericServiceCollection<TS> {
fn default() -> Self {
Self::new()
}
}
impl<TS: Strategy + 'static> GenericServiceCollection<TS> {
pub fn new() -> Self {
Self {
strategy: PhantomData,
producer_factories: RVec::new(),
}
}
pub fn with<T: Resolvable<TS>>(&mut self) -> ServiceBuilder<'_, T, TS> {
ServiceBuilder(self, PhantomData)
}
pub fn register_instance<T: Identifyable<TS::Id> + Clone + 'static + Send + Sync>(
&mut self,
instance: T,
) {
extern "C" fn factory<
T: Identifyable<TS::Id> + Clone + 'static + Send + Sync,
TS: Strategy + 'static,
>(
outer_ctx: AutoFreePointer,
_ctx: &mut UntypedFnFactoryContext<TS>,
) -> InternalBuildResult<TS> {
extern "C" fn func<
T: Identifyable<TS::Id> + Clone + 'static + Send + Sync,
TS: Strategy + 'static,
>(
_: *const ServiceProvider<TS>,
outer_ctx: *const AutoFreePointer,
) -> T {
let outer_ctx = unsafe { &*outer_ctx as &AutoFreePointer };
unsafe { &*(outer_ctx.get_pointer() as *const T) }.clone()
}
FfiOk(UntypedFn::create(func::<T, TS>, outer_ctx))
}
let factory = UntypedFnFactory::boxed(instance, factory::<T, TS>);
self.producer_factories
.push(ServiceProducer::<TS>::new::<T>(factory));
}
pub fn register_with<T: registrar::Registrar<TS>>(
&mut self,
registrar: T,
) -> AliasBuilder<'_, T::Item, TS> {
registrar.register(self)
}
pub fn register<T: Identifyable<TS::Id>>(
&mut self,
creator: fn() -> T,
) -> AliasBuilder<'_, T, TS> {
self.register_with(creator)
}
pub fn register_shared<T: Send + Sync + Identifyable<TS::Id> + FromArcAutoFreePointer>(
&mut self,
creator: fn() -> T,
) -> AliasBuilder<'_, T, TS> {
type InnerContext = (usize, AnyPtr);
extern "C" fn factory<
T: Send + Sync + FromArcAutoFreePointer + Identifyable<TS::Id>,
TS: Strategy + 'static,
>(
outer_ctx: AutoFreePointer, ctx: &mut UntypedFnFactoryContext<TS>,
) -> InternalBuildResult<TS> {
extern "C" fn func<
T: Send + Sync + 'static + FromArcAutoFreePointer + Identifyable<TS::Id>,
TS: Strategy + 'static,
>(
provider: *const ServiceProvider<TS>,
outer_ctx: *const AutoFreePointer,
) -> T {
let provider = unsafe { &*provider as &ServiceProvider<TS> };
let outer_ctx = unsafe { &*outer_ctx as &AutoFreePointer };
let (service_state_idx, fnptr) =
unsafe { &*(outer_ctx.get_pointer() as *const InnerContext) };
let creator: fn() -> T = unsafe { core::mem::transmute(*fnptr) };
provider.get_or_initialize_pos(*service_state_idx, creator)
}
let service_state_idx = ctx.reserve_state_space();
let inner: InnerContext = (service_state_idx, outer_ctx.get_pointer());
FfiOk(UntypedFn::create(
func::<T, TS>,
AutoFreePointer::boxed(inner),
))
}
let factory = UntypedFnFactory::no_alloc(creator as AnyPtr, factory::<T, TS>);
self.producer_factories
.push(ServiceProducer::<TS>::new::<T>(factory));
AliasBuilder::new(self)
}
pub fn build(self) -> Result<ServiceProvider<TS>, BuildError<TS>> {
let validation = self.validate_producers(Vec::new())?;
let shared_services = (0..validation.service_states_count)
.map(|_| OnceLock::default())
.collect();
let immutable_state = service_provider::ServiceProviderImmutableState::new(
validation.types,
validation.producers,
RVec::new(),
);
Ok(ServiceProvider::<TS>::new(
RArc::new(immutable_state),
shared_services,
None,
))
}
pub fn build_factory<T: Clone + Identifyable<TS::Id> + Send + Sync>(
self,
) -> Result<ServiceProviderFactory<T, TS>, BuildError<TS>> {
ServiceProviderFactory::<_, TS>::create(self, RVec::new())
}
pub fn with_parent(
self,
provider: impl Into<WeakServiceProvider<TS>>,
) -> ServiceProviderFactoryBuilder<TS> {
ServiceProviderFactoryBuilder::create(self, provider.into())
}
fn validate_producers(
self,
mut factories: Vec<ServiceProducer<TS>>,
) -> Result<ProducerValidationResult<TS>, BuildError<TS>> {
let mut service_states_count: usize = 0;
factories.extend(self.producer_factories);
factories.sort_by_key(|a| a.identifier);
let types: RVec<_> = factories.iter().map(|f| f.identifier).collect();
let mut cyclic_reference_candidates = CycleChecker::default();
let producers = factories
.into_iter()
.enumerate()
.map(|(i, x)| {
let mut ctx = UntypedFnFactoryContext {
state_counter: &mut service_states_count,
final_ordered_types: &types,
cyclic_reference_candidates: cyclic_reference_candidates.create_inserter(i),
};
match x.factory.call(&mut ctx) {
FfiOk(producer) => {
debug_assert_eq!(&x.identifier, producer.get_result_type_id());
Ok(producer)
}
FfiErr(e) => Err(e.into_build_error()),
}
})
.collect::<Result<_, _>>()?;
cyclic_reference_candidates
.ok()
.map_err(|description| BuildError::CyclicDependency { description })?;
Ok(ProducerValidationResult {
producers,
types,
service_states_count,
})
}
}
pub(crate) struct ProducerValidationResult<TS: Strategy + 'static> {
producers: RVec<UntypedFn<TS>>,
types: RVec<TS::Id>,
service_states_count: usize,
}
#[non_exhaustive]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum BuildError<TS: Strategy> {
#[non_exhaustive]
MissingDependency {
id: TS::Id,
name: &'static str,
dependee_name: &'static str,
},
#[non_exhaustive]
CyclicDependency { description: String },
}
impl<TS: Strategy + Debug> core::fmt::Display for BuildError<TS> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
BuildError::MissingDependency {
id,
name,
dependee_name,
} => f.write_fmt(format_args!(
"{dependee_name} is missing dependency {name} ({id:?})"
)),
BuildError::CyclicDependency { description } => {
f.write_fmt(format_args!("detected cyclic dependency: {description}"))
}
}
}
}
impl<TS: Strategy + Debug> core::error::Error for BuildError<TS> {}
#[derive(Debug)]
#[repr(C)]
pub struct MissingDependencyWithDependee<TS: Strategy> {
id: <TS as Strategy>::Id,
name: FfiStr<'static>,
dependee_name: FfiStr<'static>,
}
impl<TS: Strategy + 'static> MissingDependencyWithDependee<TS> {
fn into_build_error(self) -> BuildError<TS> {
BuildError::MissingDependency {
id: self.id,
name: self.name.into(),
dependee_name: self.dependee_name.into(),
}
}
}
#[derive(Debug)]
#[repr(C)]
pub struct MissingDependency<TS: Strategy> {
id: <TS as Strategy>::Id,
name: FfiStr<'static>,
}
impl<TS: Strategy + 'static> MissingDependency<TS> {
fn new_missing_dependency<T: Identifyable<TS::Id>>() -> Self {
MissingDependency {
name: FfiStr::from(type_name::<T>()),
id: T::get_id(),
}
}
fn with_dependee<TDependee: core::any::Any>(self) -> MissingDependencyWithDependee<TS> {
MissingDependencyWithDependee {
id: self.id,
name: self.name.into(),
dependee_name: core::any::type_name::<TDependee>().into(),
}
}
}
#[doc(hidden)]
pub struct ServiceBuilder<'col, T: Resolvable<TS>, TS: Strategy + 'static = AnyStrategy>(
pub &'col mut GenericServiceCollection<TS>,
PhantomData<T>,
);
impl<TDep: Resolvable<TS> + 'static, TS: Strategy + 'static> ServiceBuilder<'_, TDep, TS> {
pub fn register<T: Identifyable<TS::Id>>(
&mut self,
creator: fn(TDep::ItemPreChecked) -> T,
) -> AliasBuilder<'_, T, TS> {
type InnerContext<TDep, TS> = (<TDep as SealedResolvable<TS>>::PrecheckResult, AnyPtr);
extern "C" fn factory<
T: Identifyable<TS::Id>,
TDep: Resolvable<TS> + 'static,
TS: Strategy + 'static,
>(
outer_ctx: AutoFreePointer, ctx: &mut UntypedFnFactoryContext<TS>,
) -> InternalBuildResult<TS> {
let key = match TDep::precheck(ctx.final_ordered_types) {
Ok(x) => x,
Err(x) => return FfiErr(x.with_dependee::<T>()),
};
let data = TDep::iter_positions(ctx.final_ordered_types);
ctx.cyclic_reference_candidates.insert(
type_name::<TDep::ItemPreChecked>().into(),
FfiUsizeIterator::from_iter(data),
);
extern "C" fn func<
T: Identifyable<TS::Id>,
TDep: Resolvable<TS> + 'static,
TS: Strategy + 'static,
>(
provider: *const ServiceProvider<TS>,
outer_ctx: *const AutoFreePointer,
) -> T {
let provider = unsafe { &*provider as &ServiceProvider<TS> };
let outer_ctx = unsafe { &*outer_ctx as &AutoFreePointer };
let (key, c): &InnerContext<TDep, TS> =
unsafe { &*(outer_ctx.get_pointer() as *const InnerContext<TDep, TS>) };
let creator: fn(TDep::ItemPreChecked) -> T = unsafe { core::mem::transmute(*c) };
let arg = TDep::resolve_prechecked(provider, key);
creator(arg)
}
let inner: InnerContext<TDep, TS> = (key, outer_ctx.get_pointer());
FfiOk(UntypedFn::create(
func::<T, TDep, TS>,
AutoFreePointer::boxed(inner),
))
}
let factory = UntypedFnFactory::no_alloc(creator as AnyPtr, factory::<T, TDep, TS>);
self.0
.producer_factories
.push(ServiceProducer::<TS>::new::<T>(factory));
AliasBuilder::new(self.0)
}
pub fn register_shared<T: Send + Sync + Identifyable<TS::Id> + FromArcAutoFreePointer>(
&mut self,
creator: fn(TDep::ItemPreChecked) -> T,
) -> AliasBuilder<'_, T, TS> {
type InnerContext<TDep, TS> = (
<TDep as SealedResolvable<TS>>::PrecheckResult,
AnyPtr,
usize,
);
extern "C" fn factory<
T: Send + Sync + FromArcAutoFreePointer + Identifyable<TS::Id>,
TDep: Resolvable<TS> + 'static,
TS: Strategy + 'static,
>(
outer_ctx: AutoFreePointer,
ctx: &mut UntypedFnFactoryContext<TS>,
) -> InternalBuildResult<TS> {
let service_state_idx = ctx.reserve_state_space();
let key = match TDep::precheck(ctx.final_ordered_types) {
Ok(x) => x,
Err(x) => return FfiErr(x.with_dependee::<T>()),
};
let data = TDep::iter_positions(ctx.final_ordered_types);
ctx.cyclic_reference_candidates.insert(
type_name::<TDep::ItemPreChecked>().into(),
FfiUsizeIterator::from_iter(data),
);
extern "C" fn func<
T: Send + Sync + 'static + FromArcAutoFreePointer + Identifyable<TS::Id>,
TDep: Resolvable<TS> + 'static,
TS: Strategy + 'static,
>(
provider: *const ServiceProvider<TS>,
outer_ctx: *const AutoFreePointer,
) -> T {
let provider = unsafe { &*provider as &ServiceProvider<TS> };
let outer_ctx = unsafe { &*outer_ctx as &AutoFreePointer };
let (key, c, service_state_idx): &InnerContext<TDep, TS> =
unsafe { &*(outer_ctx.get_pointer() as *const InnerContext<TDep, TS>) };
provider.get_or_initialize_pos(*service_state_idx, || {
let creator: fn(TDep::ItemPreChecked) -> T =
unsafe { core::mem::transmute(*c) };
creator(TDep::resolve_prechecked(provider, key))
})
}
let inner: InnerContext<TDep, TS> = (key, outer_ctx.get_pointer(), service_state_idx);
FfiOk(UntypedFn::create(
func::<T, TDep, TS>,
AutoFreePointer::boxed(inner),
))
}
let factory = UntypedFnFactory::no_alloc(creator as AnyPtr, factory::<T, TDep, TS>);
self.0
.producer_factories
.push(ServiceProducer::<TS>::new::<T>(factory));
AliasBuilder::new(self.0)
}
}
#[repr(C)]
struct TypeNamed<T> {
inner: T,
type_name: FfiStr<'static>,
}