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 {
255 fn into_plugin(self) -> PluginHandle;
257}
258
259impl IntoPlugin for PluginHandle {
260 fn into_plugin(self) -> PluginHandle {
261 self
262 }
263}
264
265struct FunctionPlugin {
266 name: String,
267 inject: Inject,
268 validate: fn(Config) -> Result<Config>,
269 callback: Arc<dyn Fn(Context, Config) -> BoxFuture<Result<PluginOutput>> + Send + Sync>,
270}
271
272fn config_validator<C>() -> fn(Config) -> Result<Config>
275where
276 C: Send + Sync + 'static,
277{
278 fn validate<C>(config: Config) -> Result<Config>
279 where
280 C: Send + Sync + 'static,
281 {
282 config.downcast::<C>().map(|_| config)
283 }
284 validate::<C>
285}
286
287impl Plugin for FunctionPlugin {
288 fn name(&self) -> &str {
289 &self.name
290 }
291
292 fn inject(&self) -> &Inject {
293 &self.inject
294 }
295
296 fn validate_config(&self, config: Config) -> Result<Config> {
297 (self.validate)(config)
298 }
299
300 fn apply(&self, ctx: Context, config: Config) -> BoxFuture<Result<PluginOutput>> {
301 (self.callback)(ctx, config)
302 }
303}
304
305pub fn plugin_sync<C, F>(name: impl Into<String>, inject: Inject, callback: F) -> PluginHandle
307where
308 C: Send + Sync + 'static,
309 F: Fn(Context, Arc<C>) -> Result<PluginOutput> + Send + Sync + 'static,
310{
311 let callback = Arc::new(callback);
312 PluginHandle::new(FunctionPlugin {
313 name: name.into(),
314 inject,
315 validate: config_validator::<C>(),
316 callback: Arc::new(move |ctx, config| {
317 let callback = callback.clone();
318 Box::pin(async move {
319 let config = config.downcast::<C>().map_err(|error| {
320 CordisError::with_message(
321 ErrorCode::InvalidConfig,
322 format!("invalid config type: {error}"),
323 )
324 })?;
325 callback(ctx, config)
326 })
327 }),
328 })
329}
330
331pub fn plugin_async<C, F, Fut>(name: impl Into<String>, inject: Inject, callback: F) -> PluginHandle
339where
340 C: Send + Sync + 'static,
341 F: Fn(Context, Arc<C>) -> Fut + Send + Sync + 'static,
342 Fut: Future<Output = Result<PluginOutput>> + Send + 'static,
343{
344 let callback = Arc::new(callback);
345 PluginHandle::new(FunctionPlugin {
346 name: name.into(),
347 inject,
348 validate: config_validator::<C>(),
349 callback: Arc::new(move |ctx, config| {
350 let callback = callback.clone();
351 Box::pin(async move {
352 let config = config.downcast::<C>().map_err(|error| {
353 CordisError::with_message(
354 ErrorCode::InvalidConfig,
355 format!("invalid config type: {error}"),
356 )
357 })?;
358 callback(ctx, config).await
359 })
360 }),
361 })
362}
363
364pub(crate) struct RuntimeRecord {
365 pub(crate) handle: PluginHandle,
366 pub(crate) fibers: Vec<Weak<FiberInner>>,
367}
368
369#[derive(Default)]
370pub(crate) struct RegistryState {
371 pub(crate) runtimes: std::collections::BTreeMap<PluginKey, RuntimeRecord>,
372 pub(crate) injectors: std::collections::HashMap<String, Vec<Weak<FiberInner>>>,
375}
376
377pub(crate) struct RegistryRoot {
378 pub(crate) state: Mutex<RegistryState>,
379}
380
381impl RegistryRoot {
382 pub(crate) fn new() -> Self {
383 Self {
384 state: Mutex::new(RegistryState::default()),
385 }
386 }
387
388 pub(crate) fn remove_fiber(&self, key: PluginKey, uid: u64) {
389 let mut state = lock(&self.state);
390 let remove_runtime = if let Some(runtime) = state.runtimes.get_mut(&key) {
391 runtime.fibers.retain(|weak| {
392 weak.upgrade()
393 .and_then(|fiber| fiber.uid_value())
394 .map(|fiber_uid| fiber_uid != uid)
395 .unwrap_or(false)
396 });
397 runtime.fibers.is_empty()
398 } else {
399 false
400 };
401 if remove_runtime {
402 state.runtimes.remove(&key);
403 }
404 for weaks in state.injectors.values_mut() {
407 weaks.retain(|weak| {
408 weak.upgrade()
409 .and_then(|fiber| fiber.uid_value())
410 .map(|fiber_uid| fiber_uid != uid)
411 .unwrap_or(false)
412 });
413 }
414 state.injectors.retain(|_, weaks| !weaks.is_empty());
415 }
416
417 pub(crate) fn fibers_injecting(&self, name: &str) -> Vec<Fiber> {
420 let mut state = lock(&self.state);
421 let Some(weaks) = state.injectors.get_mut(name) else {
422 return Vec::new();
423 };
424 let mut fibers = Vec::with_capacity(weaks.len());
425 weaks.retain(|weak| {
426 if let Some(fiber) = weak.upgrade() {
427 fibers.push(Fiber::from_inner(fiber));
428 true
429 } else {
430 false
431 }
432 });
433 if weaks.is_empty() {
434 state.injectors.remove(name);
435 }
436 fibers
437 }
438}
439
440fn prune_runtimes(state: &mut RegistryState) {
446 state.runtimes.retain(|_, runtime| {
447 runtime.fibers.retain(|weak| {
448 weak.upgrade()
449 .is_some_and(|fiber| fiber.uid_value().is_some())
450 });
451 !runtime.fibers.is_empty()
452 });
453}
454
455#[derive(Debug, Clone)]
457pub struct RuntimeInfo {
458 pub key: PluginKey,
460 pub name: String,
462 pub fibers: Vec<Fiber>,
464}
465
466#[derive(Clone, Debug)]
468pub struct RegistryService {
469 ctx: Context,
470}
471
472impl RegistryService {
473 pub(crate) fn new(ctx: Context) -> Self {
474 Self { ctx }
475 }
476
477 pub fn len(&self) -> usize {
483 let mut state = lock(&self.ctx.root.registry.state);
484 prune_runtimes(&mut state);
485 state.runtimes.len()
486 }
487
488 pub fn is_empty(&self) -> bool {
490 self.len() == 0
491 }
492
493 pub fn contains(&self, plugin: &PluginHandle) -> bool {
498 let mut state = lock(&self.ctx.root.registry.state);
499 prune_runtimes(&mut state);
500 state
501 .runtimes
502 .get(&plugin.key())
503 .is_some_and(|runtime| !runtime.fibers.is_empty())
504 }
505
506 pub fn values(&self) -> Vec<RuntimeInfo> {
512 let mut state = lock(&self.ctx.root.registry.state);
513 prune_runtimes(&mut state);
514 state
515 .runtimes
516 .values()
517 .map(|runtime| RuntimeInfo {
518 key: runtime.handle.key(),
519 name: runtime.handle.name().to_owned(),
520 fibers: runtime
521 .fibers
522 .iter()
523 .filter_map(|weak| weak.upgrade().map(Fiber::from_inner))
524 .collect(),
525 })
526 .collect()
527 }
528
529 pub fn plugin_value(&self, plugin: PluginHandle, config: Config) -> Fiber {
531 let fiber = Fiber::new_plugin(&self.ctx, plugin.clone(), config);
532 let weak = Arc::downgrade(&fiber.inner);
533 {
534 let mut state = lock(&self.ctx.root.registry.state);
535 state
536 .runtimes
537 .entry(plugin.key())
538 .or_insert_with(|| RuntimeRecord {
539 handle: plugin.clone(),
540 fibers: Vec::new(),
541 })
542 .fibers
543 .push(weak.clone());
544 for dependency in fiber.inject().iter() {
545 state
546 .injectors
547 .entry(dependency.name.clone())
548 .or_default()
549 .push(weak.clone());
550 }
551 }
552
553 let owned = fiber.clone();
556 match self.ctx.fiber().and_then(|parent| {
557 parent.register_effect(
558 "ctx.plugin()",
559 AsyncDisposer::from_async(move || async move { owned.dispose_async().await }),
560 )
561 }) {
562 Ok(effect) => fiber.set_parent_effect(effect),
563 Err(error) => fiber.reject(error),
564 }
565
566 if fiber.uid().is_some() {
567 let _ = self
568 .ctx
569 .events()
570 .emit("internal/plugin", [Value::new(fiber.clone())]);
571 fiber.refresh();
572 }
573 fiber
574 }
575
576 pub fn delete(&self, plugin: &PluginHandle) -> bool {
578 let fibers = {
579 let mut state = lock(&self.ctx.root.registry.state);
580 let Some(runtime) = state.runtimes.remove(&plugin.key()) else {
581 return false;
582 };
583 runtime
584 .fibers
585 .into_iter()
586 .filter_map(|fiber| fiber.upgrade())
587 .map(Fiber::from_inner)
588 .collect::<Vec<_>>()
589 };
590 for fiber in fibers {
591 if let Err(error) = fiber.dispose() {
592 self.ctx.log_error(error);
593 }
594 }
595 true
596 }
597}
598
599#[cfg(test)]
600mod tests {
601 use super::*;
602 use crate::{Context, Inject, PluginOutput};
603
604 #[test]
609 fn read_apis_prune_runtimes_with_only_dead_fibers() {
610 let root = Context::new();
611 let handle =
612 crate::plugin_sync::<(), _>(
613 "ghost",
614 Inject::none(),
615 |_, _| Ok(PluginOutput::default()),
616 );
617 {
618 let mut state = lock(&root.root.registry.state);
619 state.runtimes.insert(
620 handle.key(),
621 RuntimeRecord {
622 handle: handle.clone(),
623 fibers: vec![Weak::new()],
624 },
625 );
626 }
627 assert_eq!(root.registry().len(), 0, "len prunes empty runtimes");
628 assert!(root.registry().values().is_empty());
629 assert!(!root.registry().contains(&handle));
630 }
631}