use crate::context::Context;
use crate::effect::AsyncDisposer;
use crate::fiber::{Fiber, FiberInner};
use crate::utils::{BoxFuture, lock};
use crate::{Config, CordisError, ErrorCode, Result, Value};
use std::fmt::{self, Debug, Formatter};
use std::future::Future;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, Weak};
#[derive(Debug, Clone)]
pub struct Dependency {
pub name: String,
pub config: Option<Value>,
}
#[derive(Debug, Clone, Default)]
pub struct Inject {
entries: Vec<Dependency>,
}
impl Inject {
pub fn new<I, S>(names: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
entries: names
.into_iter()
.map(|name| Dependency {
name: name.into(),
config: None,
})
.collect(),
}
}
pub fn none() -> Self {
Self::default()
}
pub fn require(mut self, name: impl Into<String>) -> Self {
self.entries.push(Dependency {
name: name.into(),
config: None,
});
self
}
pub fn require_with<T>(mut self, name: impl Into<String>, config: T) -> Self
where
T: Send + Sync + 'static,
{
self.entries.push(Dependency {
name: name.into(),
config: Some(Value::new(config)),
});
self
}
pub fn require_with_value(mut self, name: impl Into<String>, config: Value) -> Self {
self.entries.push(Dependency {
name: name.into(),
config: Some(config),
});
self
}
pub fn iter(&self) -> impl Iterator<Item = &Dependency> {
self.entries.iter()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn contains(&self, name: &str) -> bool {
self.entries.iter().any(|entry| entry.name == name)
}
pub fn names(&self) -> impl Iterator<Item = &str> {
self.entries.iter().map(|entry| entry.name.as_str())
}
}
impl<const N: usize> From<[&str; N]> for Inject {
fn from(value: [&str; N]) -> Self {
Self::new(value)
}
}
impl From<Vec<String>> for Inject {
fn from(value: Vec<String>) -> Self {
Self::new(value)
}
}
#[derive(Debug, Default)]
pub struct PluginOutput {
pub(crate) disposers: Vec<(String, AsyncDisposer)>,
}
impl PluginOutput {
pub fn none() -> Self {
Self::default()
}
pub fn disposer<F>(dispose: F) -> Self
where
F: FnOnce() -> Result<()> + Send + 'static,
{
Self::default().with_disposer("plugin return", dispose)
}
pub fn infallible<F>(dispose: F) -> Self
where
F: FnOnce() + Send + 'static,
{
let mut output = Self::default();
output.disposers.push((
"plugin return".to_owned(),
AsyncDisposer::infallible(dispose),
));
output
}
pub fn with_disposer<F>(mut self, label: impl Into<String>, dispose: F) -> Self
where
F: FnOnce() -> Result<()> + Send + 'static,
{
self.disposers
.push((label.into(), AsyncDisposer::from_sync(dispose)));
self
}
pub fn with_async_disposer(mut self, label: impl Into<String>, dispose: AsyncDisposer) -> Self {
self.disposers.push((label.into(), dispose));
self
}
}
pub trait Plugin: Send + Sync + 'static {
fn name(&self) -> &str;
fn inject(&self) -> Inject {
Inject::default()
}
fn validate_config(&self, config: Config) -> Result<Config> {
Ok(config)
}
fn apply(&self, ctx: Context, config: Config) -> BoxFuture<Result<PluginOutput>>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct PluginKey(pub u64);
static NEXT_PLUGIN: AtomicU64 = AtomicU64::new(0);
#[derive(Clone)]
pub struct PluginHandle {
key: PluginKey,
plugin: Arc<dyn Plugin>,
}
impl PluginHandle {
pub fn new<P: Plugin>(plugin: P) -> Self {
Self {
key: PluginKey(NEXT_PLUGIN.fetch_add(1, Ordering::Relaxed) + 1),
plugin: Arc::new(plugin),
}
}
pub const fn key(&self) -> PluginKey {
self.key
}
pub fn name(&self) -> &str {
self.plugin.name()
}
pub(crate) fn plugin(&self) -> &Arc<dyn Plugin> {
&self.plugin
}
}
impl Debug for PluginHandle {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("PluginHandle")
.field("key", &self.key)
.field("name", &self.name())
.finish()
}
}
pub trait IntoPlugin {
fn into_plugin(self) -> PluginHandle;
}
impl IntoPlugin for PluginHandle {
fn into_plugin(self) -> PluginHandle {
self
}
}
struct FunctionPlugin {
name: String,
inject: Inject,
callback: Arc<dyn Fn(Context, Config) -> BoxFuture<Result<PluginOutput>> + Send + Sync>,
}
impl Plugin for FunctionPlugin {
fn name(&self) -> &str {
&self.name
}
fn inject(&self) -> Inject {
self.inject.clone()
}
fn apply(&self, ctx: Context, config: Config) -> BoxFuture<Result<PluginOutput>> {
(self.callback)(ctx, config)
}
}
pub fn plugin_sync<C, F>(name: impl Into<String>, inject: Inject, callback: F) -> PluginHandle
where
C: Send + Sync + 'static,
F: Fn(Context, Arc<C>) -> Result<PluginOutput> + Send + Sync + 'static,
{
let callback = Arc::new(callback);
PluginHandle::new(FunctionPlugin {
name: name.into(),
inject,
callback: Arc::new(move |ctx, config| {
let callback = callback.clone();
Box::pin(async move {
let config = config.downcast::<C>().map_err(|error| {
CordisError::with_message(
ErrorCode::InvalidConfig,
format!("invalid config type: {error}"),
)
})?;
callback(ctx, config)
})
}),
})
}
pub fn plugin_async<C, F, Fut>(name: impl Into<String>, inject: Inject, callback: F) -> PluginHandle
where
C: Send + Sync + 'static,
F: Fn(Context, Arc<C>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<PluginOutput>> + Send + 'static,
{
let callback = Arc::new(callback);
PluginHandle::new(FunctionPlugin {
name: name.into(),
inject,
callback: Arc::new(move |ctx, config| {
let callback = callback.clone();
Box::pin(async move {
let config = config.downcast::<C>().map_err(|error| {
CordisError::with_message(
ErrorCode::InvalidConfig,
format!("invalid config type: {error}"),
)
})?;
callback(ctx, config).await
})
}),
})
}
pub(crate) struct RuntimeRecord {
pub(crate) handle: PluginHandle,
pub(crate) fibers: Vec<Weak<FiberInner>>,
}
#[derive(Default)]
pub(crate) struct RegistryState {
pub(crate) runtimes: std::collections::BTreeMap<PluginKey, RuntimeRecord>,
pub(crate) injectors: std::collections::HashMap<String, Vec<Weak<FiberInner>>>,
}
pub(crate) struct RegistryRoot {
pub(crate) state: Mutex<RegistryState>,
}
impl RegistryRoot {
pub(crate) fn new() -> Self {
Self {
state: Mutex::new(RegistryState::default()),
}
}
pub(crate) fn remove_fiber(&self, key: PluginKey, uid: u64) {
let mut state = lock(&self.state);
let remove_runtime = if let Some(runtime) = state.runtimes.get_mut(&key) {
runtime.fibers.retain(|weak| {
weak.upgrade()
.and_then(|fiber| fiber.uid_value())
.map(|fiber_uid| fiber_uid != uid)
.unwrap_or(false)
});
runtime.fibers.is_empty()
} else {
false
};
if remove_runtime {
state.runtimes.remove(&key);
}
for weaks in state.injectors.values_mut() {
weaks.retain(|weak| {
weak.upgrade()
.and_then(|fiber| fiber.uid_value())
.map(|fiber_uid| fiber_uid != uid)
.unwrap_or(false)
});
}
state.injectors.retain(|_, weaks| !weaks.is_empty());
}
pub(crate) fn fibers_injecting(&self, name: &str) -> Vec<Fiber> {
let mut state = lock(&self.state);
let Some(weaks) = state.injectors.get_mut(name) else {
return Vec::new();
};
let mut fibers = Vec::with_capacity(weaks.len());
weaks.retain(|weak| {
if let Some(fiber) = weak.upgrade() {
fibers.push(Fiber::from_inner(fiber));
true
} else {
false
}
});
if weaks.is_empty() {
state.injectors.remove(name);
}
fibers
}
}
#[derive(Debug, Clone)]
pub struct RuntimeInfo {
pub key: PluginKey,
pub name: String,
pub fibers: Vec<Fiber>,
}
#[derive(Clone, Debug)]
pub struct RegistryService {
ctx: Context,
}
impl RegistryService {
pub(crate) fn new(ctx: Context) -> Self {
Self { ctx }
}
pub fn len(&self) -> usize {
lock(&self.ctx.root.registry.state).runtimes.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn contains(&self, plugin: &PluginHandle) -> bool {
lock(&self.ctx.root.registry.state)
.runtimes
.contains_key(&plugin.key())
}
pub fn values(&self) -> Vec<RuntimeInfo> {
let mut state = lock(&self.ctx.root.registry.state);
state
.runtimes
.iter_mut()
.map(|(key, runtime)| {
let mut fibers = Vec::new();
runtime.fibers.retain(|fiber| {
if let Some(fiber) = fiber.upgrade() {
fibers.push(Fiber::from_inner(fiber));
true
} else {
false
}
});
RuntimeInfo {
key: *key,
name: runtime.handle.name().to_owned(),
fibers,
}
})
.collect()
}
pub fn plugin_value(&self, plugin: PluginHandle, config: Config) -> Fiber {
let fiber = Fiber::new_plugin(&self.ctx, plugin.clone(), config);
let weak = Arc::downgrade(&fiber.inner);
{
let mut state = lock(&self.ctx.root.registry.state);
state
.runtimes
.entry(plugin.key())
.or_insert_with(|| RuntimeRecord {
handle: plugin.clone(),
fibers: Vec::new(),
})
.fibers
.push(weak.clone());
for dependency in fiber.inject().iter() {
state
.injectors
.entry(dependency.name.clone())
.or_default()
.push(weak.clone());
}
}
let owned = fiber.clone();
match self.ctx.fiber().and_then(|parent| {
parent.register_effect(
"ctx.plugin()",
AsyncDisposer::from_async(move || async move { owned.dispose_async().await }),
)
}) {
Ok(effect) => fiber.set_parent_effect(effect),
Err(error) => fiber.reject(error),
}
if fiber.uid().is_some() {
let _ = self
.ctx
.events()
.emit("internal/plugin", [Value::new(fiber.clone())]);
fiber.refresh();
}
fiber
}
pub fn delete(&self, plugin: &PluginHandle) -> bool {
let fibers = {
let mut state = lock(&self.ctx.root.registry.state);
let Some(runtime) = state.runtimes.remove(&plugin.key()) else {
return false;
};
runtime
.fibers
.into_iter()
.filter_map(|fiber| fiber.upgrade())
.map(Fiber::from_inner)
.collect::<Vec<_>>()
};
for fiber in fibers {
if let Err(error) = fiber.dispose() {
self.ctx.log_error(error);
}
}
true
}
}