use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::panic::{RefUnwindSafe, UnwindSafe};
use crate::Metric;
pub fn request_value<'a, T>(metric: &'a (impl Metric + ?Sized)) -> Option<T>
where
T: 'static,
{
request_by_type_tag::<'a, tags::Value<T>>(metric)
}
pub fn request_ref<'a, T>(metric: &'a (impl Metric + ?Sized)) -> Option<&'a T>
where
T: ?Sized + 'static,
{
request_by_type_tag::<'a, tags::Ref<tags::MaybeSizedValue<T>>>(metric)
}
fn request_by_type_tag<'a, I>(metric: &'a (impl Metric + ?Sized)) -> Option<I::Reified>
where
I: tags::Type<'a>,
{
let mut tagged = TaggedOption::<'a, I>(None);
metric.provide(tagged.as_request());
tagged.0
}
#[repr(transparent)]
pub struct Request<'a>(dyn Erased<'a>);
impl<'a> Request<'a> {
fn new<'b>(erased: &'b mut (dyn Erased<'a> + 'a)) -> &'b mut Request<'a> {
unsafe { &mut *(erased as *mut dyn Erased<'a> as *mut Request<'a>) }
}
pub fn provide_value<T>(&mut self, value: T) -> &mut Self
where
T: 'static,
{
self.provide::<tags::Value<T>>(value)
}
pub fn provide_value_with<T>(&mut self, fulfil: impl FnOnce() -> T) -> &mut Self
where
T: 'static,
{
self.provide_with::<tags::Value<T>>(fulfil)
}
pub fn provide_ref<T: ?Sized + 'static>(&mut self, value: &'a T) -> &mut Self {
self.provide::<tags::Ref<tags::MaybeSizedValue<T>>>(value)
}
pub fn provide_ref_with<T: ?Sized + 'static>(
&mut self,
fulfil: impl FnOnce() -> &'a T,
) -> &mut Self {
self.provide_with::<tags::Ref<tags::MaybeSizedValue<T>>>(fulfil)
}
fn provide<I>(&mut self, value: I::Reified) -> &mut Self
where
I: tags::Type<'a>,
{
self.provide_with::<I>(move || value)
}
fn provide_with<I>(&mut self, fulfil: impl FnOnce() -> I::Reified) -> &mut Self
where
I: tags::Type<'a>,
{
if let Some(res @ TaggedOption(None)) = self.0.downcast_mut::<I>() {
res.0 = Some(fulfil());
}
self
}
pub fn would_be_satisfied_by_value_of<T>(&self) -> bool
where
T: 'static,
{
self.would_be_satisfied_by::<tags::Value<T>>()
}
pub fn would_be_satisfied_by_ref_of<T>(&self) -> bool
where
T: ?Sized + 'static,
{
self.would_be_satisfied_by::<tags::Ref<tags::MaybeSizedValue<T>>>()
}
fn would_be_satisfied_by<I>(&self) -> bool
where
I: tags::Type<'a>,
{
matches!(self.0.downcast::<I>(), Some(TaggedOption(None)))
}
}
mod tags {
use std::marker::PhantomData;
pub(crate) trait Type<'a>: Sized + 'static {
type Reified: 'a;
}
pub(crate) trait MaybeSizedType<'a>: Sized + 'static {
type Reified: ?Sized + 'a;
}
impl<'a, T: Type<'a>> MaybeSizedType<'a> for T {
type Reified = T;
}
#[derive(Debug)]
pub(crate) struct Value<T: 'static>(PhantomData<T>);
impl<'a, T: 'static> Type<'a> for Value<T> {
type Reified = T;
}
#[derive(Debug)]
pub(crate) struct MaybeSizedValue<T: ?Sized + 'static>(PhantomData<T>);
impl<'a, T: ?Sized + 'static> MaybeSizedType<'a> for MaybeSizedValue<T> {
type Reified = T;
}
pub(crate) struct Ref<I>(PhantomData<I>);
impl<'a, I: MaybeSizedType<'a>> Type<'a> for Ref<I> {
type Reified = &'a I::Reified;
}
}
#[repr(transparent)]
pub(crate) struct TaggedOption<'a, I: tags::Type<'a>>(pub Option<I::Reified>);
impl<'a, I: tags::Type<'a>> TaggedOption<'a, I> {
pub(crate) fn as_request(&mut self) -> &mut Request<'a> {
Request::new(self as &mut (dyn Erased<'a> + 'a))
}
}
unsafe trait Erased<'a>: 'a {
fn tag_id(&self) -> TypeId;
}
unsafe impl<'a, I: tags::Type<'a>> Erased<'a> for TaggedOption<'a, I> {
fn tag_id(&self) -> TypeId {
TypeId::of::<I>()
}
}
impl<'a> dyn Erased<'a> + 'a {
fn downcast<I>(&self) -> Option<&TaggedOption<'a, I>>
where
I: tags::Type<'a>,
{
if self.tag_id() == TypeId::of::<I>() {
Some(unsafe { &*(self as *const Self).cast::<TaggedOption<'a, I>>() })
} else {
None
}
}
fn downcast_mut<I>(&mut self) -> Option<&mut TaggedOption<'a, I>>
where
I: tags::Type<'a>,
{
if self.tag_id() == TypeId::of::<I>() {
Some(unsafe { &mut *(self as *mut Self).cast::<TaggedOption<'a, I>>() })
} else {
None
}
}
}
#[derive(Default)]
pub(crate) struct ProviderMap(HashMap<TypeId, Box<dyn Provide>>);
impl ProviderMap {
pub fn new() -> Self {
Self::default()
}
pub fn insert<T: Provide>(&mut self, value: T) {
self.0.insert(Self::typeid_for::<T>(), Box::new(value));
}
pub fn provide<'a>(&'a self, request: &mut Request<'a>) {
if let Some(element) = self.0.get(&request.0.tag_id()) {
Self::provide_dyn(&**element, request);
}
}
fn typeid_for<T: Provide>() -> TypeId {
TypeId::of::<tags::Ref<tags::MaybeSizedValue<T>>>()
}
fn provide_dyn<'a>(provider: &'a dyn Provide, request: &mut Request<'a>) {
provider.provide(request);
}
}
impl UnwindSafe for ProviderMap {}
impl RefUnwindSafe for ProviderMap {}
pub(crate) trait Provide: Any + Send + Sync + 'static {
fn provide<'a>(&'a self, request: &mut Request<'a>);
}
impl<T: Any + Send + Sync> Provide for T {
fn provide<'a>(&'a self, request: &mut Request<'a>) {
request.provide_ref::<T>(self);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Metadata;
struct ProviderMetric(ProviderMap);
impl Metric for ProviderMetric {
fn is_enabled(&self) -> bool {
false
}
fn as_any(&self) -> Option<&dyn Any> {
None
}
fn value(&self) -> Option<crate::Value<'_>> {
None
}
fn provide<'a>(&'a self, request: &mut Request<'a>) {
self.0.provide(request)
}
}
#[test]
fn insert_into_provider_map() {
let mut map = ProviderMap::new();
map.insert(Metadata::default_const());
let metric = ProviderMetric(map);
let value = request_ref::<Metadata>(&metric);
assert!(value.is_some());
}
}