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 callback: Arc<dyn Fn(Context, Config) -> BoxFuture<Result<PluginOutput>> + Send + Sync>,
269}
270
271impl Plugin for FunctionPlugin {
272 fn name(&self) -> &str {
273 &self.name
274 }
275
276 fn inject(&self) -> &Inject {
277 &self.inject
278 }
279
280 fn apply(&self, ctx: Context, config: Config) -> BoxFuture<Result<PluginOutput>> {
281 (self.callback)(ctx, config)
282 }
283}
284
285pub fn plugin_sync<C, F>(name: impl Into<String>, inject: Inject, callback: F) -> PluginHandle
287where
288 C: Send + Sync + 'static,
289 F: Fn(Context, Arc<C>) -> Result<PluginOutput> + Send + Sync + 'static,
290{
291 let callback = Arc::new(callback);
292 PluginHandle::new(FunctionPlugin {
293 name: name.into(),
294 inject,
295 callback: Arc::new(move |ctx, config| {
296 let callback = callback.clone();
297 Box::pin(async move {
298 let config = config.downcast::<C>().map_err(|error| {
299 CordisError::with_message(
300 ErrorCode::InvalidConfig,
301 format!("invalid config type: {error}"),
302 )
303 })?;
304 callback(ctx, config)
305 })
306 }),
307 })
308}
309
310pub fn plugin_async<C, F, Fut>(name: impl Into<String>, inject: Inject, callback: F) -> PluginHandle
318where
319 C: Send + Sync + 'static,
320 F: Fn(Context, Arc<C>) -> Fut + Send + Sync + 'static,
321 Fut: Future<Output = Result<PluginOutput>> + Send + 'static,
322{
323 let callback = Arc::new(callback);
324 PluginHandle::new(FunctionPlugin {
325 name: name.into(),
326 inject,
327 callback: Arc::new(move |ctx, config| {
328 let callback = callback.clone();
329 Box::pin(async move {
330 let config = config.downcast::<C>().map_err(|error| {
331 CordisError::with_message(
332 ErrorCode::InvalidConfig,
333 format!("invalid config type: {error}"),
334 )
335 })?;
336 callback(ctx, config).await
337 })
338 }),
339 })
340}
341
342pub(crate) struct RuntimeRecord {
343 pub(crate) handle: PluginHandle,
344 pub(crate) fibers: Vec<Weak<FiberInner>>,
345}
346
347#[derive(Default)]
348pub(crate) struct RegistryState {
349 pub(crate) runtimes: std::collections::BTreeMap<PluginKey, RuntimeRecord>,
350 pub(crate) injectors: std::collections::HashMap<String, Vec<Weak<FiberInner>>>,
353}
354
355pub(crate) struct RegistryRoot {
356 pub(crate) state: Mutex<RegistryState>,
357}
358
359impl RegistryRoot {
360 pub(crate) fn new() -> Self {
361 Self {
362 state: Mutex::new(RegistryState::default()),
363 }
364 }
365
366 pub(crate) fn remove_fiber(&self, key: PluginKey, uid: u64) {
367 let mut state = lock(&self.state);
368 let remove_runtime = if let Some(runtime) = state.runtimes.get_mut(&key) {
369 runtime.fibers.retain(|weak| {
370 weak.upgrade()
371 .and_then(|fiber| fiber.uid_value())
372 .map(|fiber_uid| fiber_uid != uid)
373 .unwrap_or(false)
374 });
375 runtime.fibers.is_empty()
376 } else {
377 false
378 };
379 if remove_runtime {
380 state.runtimes.remove(&key);
381 }
382 for weaks in state.injectors.values_mut() {
385 weaks.retain(|weak| {
386 weak.upgrade()
387 .and_then(|fiber| fiber.uid_value())
388 .map(|fiber_uid| fiber_uid != uid)
389 .unwrap_or(false)
390 });
391 }
392 state.injectors.retain(|_, weaks| !weaks.is_empty());
393 }
394
395 pub(crate) fn fibers_injecting(&self, name: &str) -> Vec<Fiber> {
398 let mut state = lock(&self.state);
399 let Some(weaks) = state.injectors.get_mut(name) else {
400 return Vec::new();
401 };
402 let mut fibers = Vec::with_capacity(weaks.len());
403 weaks.retain(|weak| {
404 if let Some(fiber) = weak.upgrade() {
405 fibers.push(Fiber::from_inner(fiber));
406 true
407 } else {
408 false
409 }
410 });
411 if weaks.is_empty() {
412 state.injectors.remove(name);
413 }
414 fibers
415 }
416}
417
418#[derive(Debug, Clone)]
420pub struct RuntimeInfo {
421 pub key: PluginKey,
423 pub name: String,
425 pub fibers: Vec<Fiber>,
427}
428
429#[derive(Clone, Debug)]
431pub struct RegistryService {
432 ctx: Context,
433}
434
435impl RegistryService {
436 pub(crate) fn new(ctx: Context) -> Self {
437 Self { ctx }
438 }
439
440 pub fn len(&self) -> usize {
442 lock(&self.ctx.root.registry.state).runtimes.len()
443 }
444
445 pub fn is_empty(&self) -> bool {
447 self.len() == 0
448 }
449
450 pub fn contains(&self, plugin: &PluginHandle) -> bool {
455 let mut state = lock(&self.ctx.root.registry.state);
456 let Some(runtime) = state.runtimes.get_mut(&plugin.key()) else {
457 return false;
458 };
459 runtime
460 .fibers
461 .retain(|weak| weak.upgrade().and_then(|fiber| fiber.uid_value()).is_some());
462 if runtime.fibers.is_empty() {
463 state.runtimes.remove(&plugin.key());
464 false
465 } else {
466 true
467 }
468 }
469
470 pub fn values(&self) -> Vec<RuntimeInfo> {
472 let mut state = lock(&self.ctx.root.registry.state);
473 state
474 .runtimes
475 .iter_mut()
476 .map(|(key, runtime)| {
477 let mut fibers = Vec::new();
478 runtime.fibers.retain(|fiber| {
479 if let Some(fiber) = fiber.upgrade() {
480 fibers.push(Fiber::from_inner(fiber));
481 true
482 } else {
483 false
484 }
485 });
486 RuntimeInfo {
487 key: *key,
488 name: runtime.handle.name().to_owned(),
489 fibers,
490 }
491 })
492 .collect()
493 }
494
495 pub fn plugin_value(&self, plugin: PluginHandle, config: Config) -> Fiber {
497 let fiber = Fiber::new_plugin(&self.ctx, plugin.clone(), config);
498 let weak = Arc::downgrade(&fiber.inner);
499 {
500 let mut state = lock(&self.ctx.root.registry.state);
501 state
502 .runtimes
503 .entry(plugin.key())
504 .or_insert_with(|| RuntimeRecord {
505 handle: plugin.clone(),
506 fibers: Vec::new(),
507 })
508 .fibers
509 .push(weak.clone());
510 for dependency in fiber.inject().iter() {
511 state
512 .injectors
513 .entry(dependency.name.clone())
514 .or_default()
515 .push(weak.clone());
516 }
517 }
518
519 let owned = fiber.clone();
522 match self.ctx.fiber().and_then(|parent| {
523 parent.register_effect(
524 "ctx.plugin()",
525 AsyncDisposer::from_async(move || async move { owned.dispose_async().await }),
526 )
527 }) {
528 Ok(effect) => fiber.set_parent_effect(effect),
529 Err(error) => fiber.reject(error),
530 }
531
532 if fiber.uid().is_some() {
533 let _ = self
534 .ctx
535 .events()
536 .emit("internal/plugin", [Value::new(fiber.clone())]);
537 fiber.refresh();
538 }
539 fiber
540 }
541
542 pub fn delete(&self, plugin: &PluginHandle) -> bool {
544 let fibers = {
545 let mut state = lock(&self.ctx.root.registry.state);
546 let Some(runtime) = state.runtimes.remove(&plugin.key()) else {
547 return false;
548 };
549 runtime
550 .fibers
551 .into_iter()
552 .filter_map(|fiber| fiber.upgrade())
553 .map(Fiber::from_inner)
554 .collect::<Vec<_>>()
555 };
556 for fiber in fibers {
557 if let Err(error) = fiber.dispose() {
558 self.ctx.log_error(error);
559 }
560 }
561 true
562 }
563}