use alloc::{borrow::Cow, boxed::Box};
use core::{any::Any, cell::OnceCell, marker::PhantomData, ops::Deref};
use crate::component::{
metadata::MetaData,
params::Param,
storage::{Storage, UnsafeStorageCell},
};
pub mod dxe_dispatch;
pub mod memory;
pub mod perf_timer;
pub use patina_macro::IntoService;
pub trait IntoService {
fn register(self, storage: &mut Storage);
fn register_service<S: ?Sized + 'static>(storage: &mut Storage, service: &'static dyn Any) {
let id = storage.register_service::<S>();
storage.insert_service(id, service);
}
}
pub struct Service<T: ?Sized + 'static> {
value: OnceCell<&'static dyn Any>,
_marker: core::marker::PhantomData<T>,
}
impl<T: ?Sized + 'static> Service<T> {
pub const fn new_uninit() -> Self {
Self { value: OnceCell::new(), _marker: PhantomData }
}
pub fn replace(&self, service: &Service<T>) {
let v = service.value.get().expect("Provided Service was not initialized!");
self.value.set(*v).expect("Service was already initialized!");
}
pub fn is_init(&self) -> bool {
self.value.get().is_some()
}
pub fn map_or<U, F>(&self, default: U, f: F) -> U
where
F: FnOnce(&Service<T>) -> U,
{
if self.value.get().is_some() { f(self) } else { default }
}
pub fn map_or_else<U, D, F>(&self, default: D, f: F) -> U
where
D: FnOnce() -> U,
F: FnOnce(&Service<T>) -> U,
{
if self.value.get().is_some() { f(self) } else { default() }
}
pub fn map_or_default<U, F>(&self, f: F) -> U
where
U: Default,
F: FnOnce(&Service<T>) -> U,
{
if self.value.get().is_some() { f(self) } else { U::default() }
}
#[allow(clippy::test_attr_in_doctest)]
pub fn mock(value: Box<T>) -> Self {
let v: &'static T = Box::leak(value);
let value = OnceCell::new();
let leaked: &'static dyn core::any::Any = Box::leak(Box::new(v));
value.set(leaked).expect("Once Cell was just created");
Self { value, _marker: PhantomData }
}
}
impl<T: ?Sized + 'static> From<&'static dyn Any> for Service<T> {
fn from(value: &'static dyn Any) -> Self {
let s = Self::new_uninit();
s.value.set(value).expect("Once Cell was just created");
s
}
}
impl<T: ?Sized + 'static> Deref for Service<T> {
type Target = &'static T;
fn deref(&self) -> &Self::Target {
if let Some(service) = self.value.get() {
if let Some(service) = service.downcast_ref() {
service
} else {
unreachable!(
"Service downcast failed — this indicates a type mismatch in Service<{}> construction",
super::type_name::normalized::<T>()
)
}
} else {
unreachable!("Service should be initialized first!");
}
}
}
impl<T: ?Sized + 'static> Clone for Service<T> {
fn clone(&self) -> Self {
Service { value: self.value.clone(), _marker: PhantomData }
}
}
unsafe impl<T: ?Sized + 'static> Send for Service<T> {}
unsafe impl<T: ?Sized + 'static> Sync for Service<T> {}
unsafe impl<T: ?Sized + 'static> Param for Service<T> {
type State = usize;
type Item<'storage, 'state> = Service<T>;
unsafe fn get_param<'storage, 'state>(
state: &'state Self::State,
storage: UnsafeStorageCell<'storage>,
) -> Self::Item<'storage, 'state> {
Service::from(unsafe {
storage.storage().get_raw_service(*state).unwrap_or_else(|| {
panic!("Could not find Service value with id [{}] even though it was just validated.", *state)
})
})
}
fn validate(state: &Self::State, storage: UnsafeStorageCell) -> bool {
unsafe { storage.storage() }.get_raw_service(*state).is_some()
}
fn init_state(storage: &mut Storage, _meta: &mut MetaData) -> Result<Self::State, Cow<'static, str>> {
Ok(storage.register_service::<T>())
}
}
#[cfg(test)]
#[coverage(off)]
mod tests {
use super::{IntoService, *};
#[test]
fn test_service_derive_service_macro() {
use crate as patina;
trait MyService {
fn do_something(&self) -> u32;
}
trait MyService2 {
fn do_something2(&self) -> u32;
}
#[derive(IntoService)]
#[service(dyn MyService)]
struct MyServiceImpl;
impl MyService for MyServiceImpl {
fn do_something(&self) -> u32 {
42
}
}
#[derive(IntoService)]
#[service(dyn MyService2)]
struct MyService2Impl {
inner: Service<dyn MyService>,
}
impl MyService2 for MyService2Impl {
fn do_something2(&self) -> u32 {
self.inner.do_something()
}
}
let mut storage = Storage::new();
storage.add_service(MyServiceImpl);
let s = storage.get_service::<dyn MyService>().unwrap();
assert_eq!(42, s.do_something());
storage.add_service(MyService2Impl { inner: s });
let s2 = storage.get_service::<dyn MyService2>().unwrap();
assert_eq!(42, s2.do_something2());
storage.add_service(MyServiceImpl);
#[derive(IntoService)]
#[service(SomeStruct)]
struct SomeStruct {
x: u32,
}
storage.add_service(SomeStruct { x: 1 });
let s3 = storage.get_service::<SomeStruct>().unwrap();
assert_eq!(1, s3.x)
}
#[test]
fn test_available_service_validates_true() {
use crate as patina;
trait MyService {
fn do_something(&self) -> u32;
}
#[derive(IntoService)]
#[service(dyn MyService)]
struct MyServiceImpl;
impl MyService for MyServiceImpl {
fn do_something(&self) -> u32 {
42
}
}
let mut storage = Storage::default();
let mut mock_metadata = MetaData::new::<i32>();
let id = <Service<dyn MyService> as Param>::init_state(&mut storage, &mut mock_metadata).unwrap();
storage.add_service(MyServiceImpl);
assert!(<Service<dyn MyService> as Param>::try_validate(&id, (&storage).into()).is_ok());
let service = unsafe { <Service<dyn MyService> as Param>::get_param(&id, (&storage).into()) };
assert_eq!(42, service.do_something());
}
#[test]
fn test_missing_service_validates_false() {
trait MyService {
#[allow(dead_code)]
fn do_something(&self) -> u32;
}
let mut storage = Storage::default();
let mut mock_metadata = MetaData::new::<i32>();
let id = <Service<dyn MyService> as Param>::init_state(&mut storage, &mut mock_metadata).unwrap();
assert!(<Service<dyn MyService> as Param>::try_validate(&id, (&storage).into()).is_err());
}
#[test]
#[should_panic]
fn test_get_param_without_validate_should_panic_when_missing() {
trait MyService {
#[allow(dead_code)]
fn do_something(&self) -> u32;
}
let storage = Storage::default();
let _service =
unsafe { <Service<dyn MyService> as Param>::get_param(&0, UnsafeStorageCell::new_readonly(&storage)) };
}
#[test]
fn test_mocking_works() {
trait MyService {
fn do_something(&self) -> u32;
}
struct MockService;
impl MyService for MockService {
fn do_something(&self) -> u32 {
42
}
}
let service: Service<dyn MyService> = Service::mock(Box::new(MockService));
assert_eq!(42, service.do_something());
}
#[test]
fn test_services_can_be_copied() {
trait MyService {
fn do_something(&self) -> u32;
}
struct MockService;
impl MyService for MockService {
fn do_something(&self) -> u32 {
42
}
}
fn consume_service(service: Service<dyn MyService>) {
assert_eq!(42, service.do_something());
}
let service: Service<dyn MyService> = Service::mock(Box::new(MockService));
consume_service(service.clone());
consume_service(service); }
#[test]
fn test_basic_static_support() {
use crate as patina;
trait MyService {
fn do_something(&self) -> u32;
}
#[derive(IntoService)]
#[service(dyn MyService)]
struct MockService {
a: u32,
}
impl MockService {
const fn new(a: u32) -> Self {
Self { a }
}
}
impl MyService for MockService {
fn do_something(&self) -> u32 {
self.a
}
}
static MY_SERVICE: MockService = MockService::new(42);
let mut storage = Storage::default();
storage.add_service(&MY_SERVICE);
let service = storage.get_service::<dyn MyService>().unwrap();
assert_eq!(42, service.do_something());
}
#[test]
fn test_replace_service_works() {
trait MyService {
fn do_something(&self) -> u32;
}
struct MockService;
impl MyService for MockService {
fn do_something(&self) -> u32 {
42
}
}
let service1: Service<dyn MyService> = Service::mock(Box::new(MockService));
let service2: Service<dyn MyService> = Service::new_uninit();
assert_eq!(42, service1.do_something());
service2.replace(&service1);
assert_eq!(42, service2.do_something());
assert_eq!(42, service1.do_something()); }
#[test]
#[should_panic = "Service should be initialized first!"]
fn test_uninitialized_service_panics_instead_of_ub() {
trait MyService {
fn do_something(&self) -> u32;
}
let service: Service<dyn MyService> = Service::new_uninit();
service.do_something(); }
#[test]
fn test_map_function_on_uninit() {
trait TestService {
fn get_value(&self) -> u32;
}
let service: Service<dyn TestService> = Service::new_uninit();
assert!(!service.is_init());
assert_eq!(service.map_or(100, |s| s.get_value()), 100);
assert_eq!(service.map_or_else(|| 200, |s| s.get_value()), 200);
assert_eq!(service.map_or_default(|s| s.get_value()), 0);
}
#[test]
fn test_map_functions_on_init() {
trait TestService {
fn get_value(&self) -> u32;
}
struct TestServiceImpl;
impl TestService for TestServiceImpl {
fn get_value(&self) -> u32 {
42
}
}
let service: Service<dyn TestService> = Service::mock(Box::new(TestServiceImpl));
assert!(service.is_init());
assert_eq!(service.map_or(100, |s| s.get_value()), 42);
assert_eq!(service.map_or_else(|| 200, |s| s.get_value()), 42);
assert_eq!(service.map_or_default(|s| s.get_value()), 42);
}
}