1use crate::context::Context;
4use crate::effect::AsyncDisposer;
5use crate::fiber::{Fiber, FiberInner};
6use crate::utils::{BoxFuture, lock};
7use crate::{Config, CordisError, ErrorCode, Result, Value};
8use std::fmt::{self, Debug, Formatter};
9use std::future::Future;
10use std::sync::atomic::{AtomicU64, Ordering};
11use std::sync::{Arc, Mutex, Weak};
12
13#[derive(Debug, Clone)]
15pub struct Dependency {
16 pub name: String,
18 pub config: Option<Value>,
20}
21
22#[derive(Debug, Clone, Default)]
30pub struct Inject {
31 entries: Vec<Dependency>,
32}
33
34impl Inject {
35 pub fn new<I, S>(names: I) -> Self
37 where
38 I: IntoIterator<Item = S>,
39 S: Into<String>,
40 {
41 Self {
42 entries: names
43 .into_iter()
44 .map(|name| Dependency {
45 name: name.into(),
46 config: None,
47 })
48 .collect(),
49 }
50 }
51
52 pub fn none() -> Self {
54 Self::default()
55 }
56
57 pub fn require(mut self, name: impl Into<String>) -> Self {
59 self.entries.push(Dependency {
60 name: name.into(),
61 config: None,
62 });
63 self
64 }
65
66 pub fn require_with<T>(mut self, name: impl Into<String>, config: T) -> Self
68 where
69 T: Send + Sync + 'static,
70 {
71 self.entries.push(Dependency {
72 name: name.into(),
73 config: Some(Value::new(config)),
74 });
75 self
76 }
77
78 pub fn require_with_value(mut self, name: impl Into<String>, config: Value) -> Self {
80 self.entries.push(Dependency {
81 name: name.into(),
82 config: Some(config),
83 });
84 self
85 }
86
87 pub fn iter(&self) -> impl Iterator<Item = &Dependency> {
89 self.entries.iter()
90 }
91
92 pub fn len(&self) -> usize {
94 self.entries.len()
95 }
96
97 pub fn is_empty(&self) -> bool {
99 self.entries.is_empty()
100 }
101
102 pub fn contains(&self, name: &str) -> bool {
104 self.entries.iter().any(|entry| entry.name == name)
105 }
106
107 pub fn names(&self) -> impl Iterator<Item = &str> {
109 self.entries.iter().map(|entry| entry.name.as_str())
110 }
111}
112
113impl<const N: usize> From<[&str; N]> for Inject {
114 fn from(value: [&str; N]) -> Self {
115 Self::new(value)
116 }
117}
118
119impl From<Vec<String>> for Inject {
120 fn from(value: Vec<String>) -> Self {
121 Self::new(value)
122 }
123}
124
125#[derive(Debug, Default)]
127pub struct PluginOutput {
128 pub(crate) disposers: Vec<(String, AsyncDisposer)>,
129}
130
131impl PluginOutput {
132 pub fn none() -> Self {
135 Self::default()
136 }
137
138 pub fn disposer<F>(dispose: F) -> Self
140 where
141 F: FnOnce() -> Result<()> + Send + 'static,
142 {
143 Self::default().with_disposer("plugin return", dispose)
144 }
145
146 pub fn infallible<F>(dispose: F) -> Self
148 where
149 F: FnOnce() + Send + 'static,
150 {
151 let mut output = Self::default();
152 output.disposers.push((
153 "plugin return".to_owned(),
154 AsyncDisposer::infallible(dispose),
155 ));
156 output
157 }
158
159 pub fn with_disposer<F>(mut self, label: impl Into<String>, dispose: F) -> Self
161 where
162 F: FnOnce() -> Result<()> + Send + 'static,
163 {
164 self.disposers
165 .push((label.into(), AsyncDisposer::from_sync(dispose)));
166 self
167 }
168
169 pub fn with_async_disposer(mut self, label: impl Into<String>, dispose: AsyncDisposer) -> Self {
171 self.disposers.push((label.into(), dispose));
172 self
173 }
174}
175
176pub trait Plugin: Send + Sync + 'static {
182 fn name(&self) -> &str;
184
185 fn inject(&self) -> &Inject {
187 static EMPTY: Inject = Inject {
188 entries: Vec::new(),
189 };
190 &EMPTY
191 }
192
193 fn validate_config(&self, config: Config) -> Result<Config> {
195 Ok(config)
196 }
197
198 fn apply(&self, ctx: Context, config: Config) -> BoxFuture<Result<PluginOutput>>;
200}
201
202#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
204pub struct PluginKey(pub u64);
205
206static NEXT_PLUGIN: AtomicU64 = AtomicU64::new(0);
207
208#[derive(Clone)]
210pub struct PluginHandle {
211 key: PluginKey,
212 plugin: Arc<dyn Plugin>,
213}
214
215impl PluginHandle {
216 pub fn new<P: Plugin>(plugin: P) -> Self {
218 Self {
219 key: PluginKey(NEXT_PLUGIN.fetch_add(1, Ordering::Relaxed) + 1),
220 plugin: Arc::new(plugin),
221 }
222 }
223
224 pub const fn key(&self) -> PluginKey {
226 self.key
227 }
228
229 pub fn name(&self) -> &str {
231 self.plugin.name()
232 }
233
234 pub fn plugin(&self) -> &Arc<dyn Plugin> {
240 &self.plugin
241 }
242}
243
244impl Debug for PluginHandle {
245 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
246 f.debug_struct("PluginHandle")
247 .field("key", &self.key)
248 .field("name", &self.name())
249 .finish()
250 }
251}
252
253pub trait IntoPlugin {
258 fn into_plugin(self) -> PluginHandle;
260}
261
262impl IntoPlugin for PluginHandle {
263 fn into_plugin(self) -> PluginHandle {
264 self
265 }
266}
267
268impl<T: Plugin> IntoPlugin for T {
269 fn into_plugin(self) -> PluginHandle {
270 PluginHandle::new(self)
271 }
272}
273
274struct FunctionPlugin {
275 name: String,
276 inject: Inject,
277 validate: fn(Config) -> Result<Config>,
278 callback: Arc<dyn Fn(Context, Config) -> BoxFuture<Result<PluginOutput>> + Send + Sync>,
279}
280
281fn config_validator<C>() -> fn(Config) -> Result<Config>
284where
285 C: Send + Sync + 'static,
286{
287 fn validate<C>(config: Config) -> Result<Config>
288 where
289 C: Send + Sync + 'static,
290 {
291 config.downcast::<C>().map(|_| config)
292 }
293 validate::<C>
294}
295
296impl Plugin for FunctionPlugin {
297 fn name(&self) -> &str {
298 &self.name
299 }
300
301 fn inject(&self) -> &Inject {
302 &self.inject
303 }
304
305 fn validate_config(&self, config: Config) -> Result<Config> {
306 (self.validate)(config)
307 }
308
309 fn apply(&self, ctx: Context, config: Config) -> BoxFuture<Result<PluginOutput>> {
310 (self.callback)(ctx, config)
311 }
312}
313
314pub fn plugin_sync<C, F>(name: impl Into<String>, inject: Inject, callback: F) -> PluginHandle
316where
317 C: Send + Sync + 'static,
318 F: Fn(Context, Arc<C>) -> Result<PluginOutput> + Send + Sync + 'static,
319{
320 let callback = Arc::new(callback);
321 PluginHandle::new(FunctionPlugin {
322 name: name.into(),
323 inject,
324 validate: config_validator::<C>(),
325 callback: Arc::new(move |ctx, config| {
326 let callback = callback.clone();
327 Box::pin(async move {
328 let config = config.downcast::<C>().map_err(|error| {
329 CordisError::with_message(
330 ErrorCode::InvalidConfig,
331 format!("invalid config type: {error}"),
332 )
333 })?;
334 callback(ctx, config)
335 })
336 }),
337 })
338}
339
340pub fn plugin_async<C, F, Fut>(name: impl Into<String>, inject: Inject, callback: F) -> PluginHandle
348where
349 C: Send + Sync + 'static,
350 F: Fn(Context, Arc<C>) -> Fut + Send + Sync + 'static,
351 Fut: Future<Output = Result<PluginOutput>> + Send + 'static,
352{
353 let callback = Arc::new(callback);
354 PluginHandle::new(FunctionPlugin {
355 name: name.into(),
356 inject,
357 validate: config_validator::<C>(),
358 callback: Arc::new(move |ctx, config| {
359 let callback = callback.clone();
360 Box::pin(async move {
361 let config = config.downcast::<C>().map_err(|error| {
362 CordisError::with_message(
363 ErrorCode::InvalidConfig,
364 format!("invalid config type: {error}"),
365 )
366 })?;
367 callback(ctx, config).await
368 })
369 }),
370 })
371}
372
373pub(crate) struct RuntimeRecord {
374 pub(crate) handle: PluginHandle,
375 pub(crate) fibers: Vec<Weak<FiberInner>>,
376}
377
378#[derive(Default)]
379pub(crate) struct RegistryState {
380 pub(crate) runtimes: std::collections::BTreeMap<PluginKey, RuntimeRecord>,
381 pub(crate) injectors: std::collections::HashMap<String, Vec<Weak<FiberInner>>>,
384}
385
386pub(crate) struct RegistryRoot {
387 pub(crate) state: Mutex<RegistryState>,
388}
389
390impl RegistryRoot {
391 pub(crate) fn new() -> Self {
392 Self {
393 state: Mutex::new(RegistryState::default()),
394 }
395 }
396
397 pub(crate) fn remove_fiber(&self, key: PluginKey, uid: u64) {
398 let mut state = lock(&self.state);
399 let remove_runtime = if let Some(runtime) = state.runtimes.get_mut(&key) {
400 runtime.fibers.retain(|weak| {
401 weak.upgrade()
402 .and_then(|fiber| fiber.uid_value())
403 .map(|fiber_uid| fiber_uid != uid)
404 .unwrap_or(false)
405 });
406 runtime.fibers.is_empty()
407 } else {
408 false
409 };
410 if remove_runtime {
411 state.runtimes.remove(&key);
412 }
413 for weaks in state.injectors.values_mut() {
416 weaks.retain(|weak| {
417 weak.upgrade()
418 .and_then(|fiber| fiber.uid_value())
419 .map(|fiber_uid| fiber_uid != uid)
420 .unwrap_or(false)
421 });
422 }
423 state.injectors.retain(|_, weaks| !weaks.is_empty());
424 }
425
426 pub(crate) fn fibers_injecting(&self, name: &str) -> Vec<Fiber> {
429 let mut state = lock(&self.state);
430 let Some(weaks) = state.injectors.get_mut(name) else {
431 return Vec::new();
432 };
433 let mut fibers = Vec::with_capacity(weaks.len());
434 weaks.retain(|weak| {
435 if let Some(fiber) = weak.upgrade() {
436 fibers.push(Fiber::from_inner(fiber));
437 true
438 } else {
439 false
440 }
441 });
442 if weaks.is_empty() {
443 state.injectors.remove(name);
444 }
445 fibers
446 }
447}
448
449fn prune_runtimes(state: &mut RegistryState) {
455 state.runtimes.retain(|_, runtime| {
456 runtime.fibers.retain(|weak| {
457 weak.upgrade()
458 .is_some_and(|fiber| fiber.uid_value().is_some())
459 });
460 !runtime.fibers.is_empty()
461 });
462}
463
464#[derive(Debug, Clone)]
466pub struct RuntimeInfo {
467 pub key: PluginKey,
469 pub name: String,
471 pub fibers: Vec<Fiber>,
473}
474
475#[derive(Clone, Debug)]
477pub struct RegistryService {
478 ctx: Context,
479}
480
481impl RegistryService {
482 pub(crate) fn new(ctx: Context) -> Self {
483 Self { ctx }
484 }
485
486 pub fn len(&self) -> usize {
492 let mut state = lock(&self.ctx.root.registry.state);
493 prune_runtimes(&mut state);
494 state.runtimes.len()
495 }
496
497 pub fn is_empty(&self) -> bool {
499 self.len() == 0
500 }
501
502 pub fn contains(&self, plugin: &PluginHandle) -> bool {
507 let mut state = lock(&self.ctx.root.registry.state);
508 prune_runtimes(&mut state);
509 state
510 .runtimes
511 .get(&plugin.key())
512 .is_some_and(|runtime| !runtime.fibers.is_empty())
513 }
514
515 pub fn runtimes(&self) -> Vec<RuntimeInfo> {
521 let mut state = lock(&self.ctx.root.registry.state);
522 prune_runtimes(&mut state);
523 state
524 .runtimes
525 .values()
526 .map(|runtime| RuntimeInfo {
527 key: runtime.handle.key(),
528 name: runtime.handle.name().to_owned(),
529 fibers: runtime
530 .fibers
531 .iter()
532 .filter_map(|weak| weak.upgrade().map(Fiber::from_inner))
533 .collect(),
534 })
535 .collect()
536 }
537
538 pub fn plugin_value(&self, plugin: PluginHandle, config: Config) -> Fiber {
540 let fiber = Fiber::new_plugin(&self.ctx, plugin.clone(), config);
541 let weak = Arc::downgrade(&fiber.inner);
542 {
543 let mut state = lock(&self.ctx.root.registry.state);
544 state
545 .runtimes
546 .entry(plugin.key())
547 .or_insert_with(|| RuntimeRecord {
548 handle: plugin.clone(),
549 fibers: Vec::new(),
550 })
551 .fibers
552 .push(weak.clone());
553 for dependency in fiber.inject().iter() {
554 state
555 .injectors
556 .entry(dependency.name.clone())
557 .or_default()
558 .push(weak.clone());
559 }
560 }
561
562 let owned = fiber.clone();
565 match self.ctx.fiber().and_then(|parent| {
566 parent.register_effect(
567 "ctx.plugin()",
568 AsyncDisposer::from_async(move || async move { owned.dispose_async().await }),
569 )
570 }) {
571 Ok(effect) => fiber.set_parent_effect(effect),
572 Err(error) => fiber.reject(error),
573 }
574
575 if fiber.uid().is_some() {
576 let _ = self
577 .ctx
578 .events()
579 .emit("internal/plugin", [Value::new(fiber.clone())]);
580 fiber.refresh();
581 }
582 fiber
583 }
584
585 pub fn delete(&self, plugin: &PluginHandle) -> bool {
587 let fibers = {
588 let mut state = lock(&self.ctx.root.registry.state);
589 let Some(runtime) = state.runtimes.remove(&plugin.key()) else {
590 return false;
591 };
592 runtime
593 .fibers
594 .into_iter()
595 .filter_map(|fiber| fiber.upgrade())
596 .map(Fiber::from_inner)
597 .collect::<Vec<_>>()
598 };
599 for fiber in fibers {
600 if let Err(error) = fiber.dispose() {
601 self.ctx.log_error(error);
602 }
603 }
604 true
605 }
606}
607
608#[cfg(test)]
609mod tests {
610 use super::*;
611 use crate::{Context, Inject, PluginOutput};
612
613 #[test]
618 fn read_apis_prune_runtimes_with_only_dead_fibers() {
619 let root = Context::new();
620 let handle =
621 crate::plugin_sync::<(), _>(
622 "ghost",
623 Inject::none(),
624 |_, _| Ok(PluginOutput::default()),
625 );
626 {
627 let mut state = lock(&root.root.registry.state);
628 state.runtimes.insert(
629 handle.key(),
630 RuntimeRecord {
631 handle: handle.clone(),
632 fibers: vec![Weak::new()],
633 },
634 );
635 }
636 assert_eq!(root.registry().len(), 0, "len prunes empty runtimes");
637 assert!(root.registry().runtimes().is_empty());
638 assert!(!root.registry().contains(&handle));
639 }
640}