1mod authoring;
4mod managed_tasks;
5
6use std::{
7 collections::BTreeMap,
8 rc::Rc,
9 sync::{Mutex, OnceLock},
10};
11
12#[doc(hidden)]
13pub use authoring::{CompleteObjectLifecycle, ConstructionContext, LifecycleContext, PluginObject};
14#[doc(hidden)]
15pub use inventory as __inventory;
16use lenso_app_plan::{
17 ExecutionClassId, ResolvedAppPlan,
18 authoring::{HostCatalog, HostDefaultPlugin, HostPluginRelease, HostSlot, PluginDescriptor},
19};
20use lenso_kernel::{ActivateContext, DeactivateContext, PrepareContext};
21pub use lenso_kernel::{CancellationToken, RuntimeFailure};
22pub use lenso_native_adapter_macros::{PluginConfig, plugin, plugin_impl, provides};
23pub use lenso_runtime_codec::InstanceResources;
24pub use managed_tasks::{ManagedTasks, ManagedTasksError};
25
26#[allow(async_fn_in_trait)]
32pub trait Lifecycle: Clone + 'static {
33 async fn prepare(&self, _context: PrepareContext) -> Result<(), RuntimeFailure> {
34 Ok(())
35 }
36
37 async fn activate(&self, _context: ActivateContext) -> Result<(), RuntimeFailure> {
38 Ok(())
39 }
40
41 async fn deactivate(&self, _context: DeactivateContext) -> Result<(), RuntimeFailure> {
42 Ok(())
43 }
44}
45
46#[doc(hidden)]
48pub mod __private {
49 pub use crate::authoring::{ErasedConstructionFuture, LinkedPluginConstruction};
50 pub use crate::{
51 __inventory, CompleteObjectLifecycle, ConstructionContext, Lifecycle, LifecycleContext,
52 LinkedNativePluginFactory, NativePluginFactory, NativePluginFactoryContext,
53 NativePluginInstance, PluginObject, RuntimeFailure, link_native_plugin,
54 };
55 pub use futures;
56 pub use futures::future::LocalBoxFuture;
57 pub use lenso_kernel::{
58 ActivateContext, DeactivateContext, InvocationContext, NativeEventEndpoint,
59 NativeRequestEndpoint, NativeRequestFuture, NativeStreamEndpoint, NativeStreamSession,
60 PluginFuture, PluginLifecycle, PrepareContext,
61 };
62 pub use lenso_plugin_authoring::{
63 BoundCapabilityClient, CapabilityClient, CapabilityClientMany,
64 };
65 pub use lenso_runtime_codec::InstanceResources;
66 pub use serde_json;
67}
68
69use lenso_kernel::{
70 NativeEndpointSet, NativeEventEndpoint, NativeExecutionAdapter, NativeRequestEndpoint,
71 NativeStreamEndpoint, NoopPluginLifecycle, PluginLifecycle, PreparedBinding,
72 PreparedEventBinding, PreparedNativeApp, PreparedNativePlugin, PreparedStreamBinding,
73};
74
75#[derive(Clone, Copy, Debug)]
77#[doc(hidden)]
78pub struct LinkedNativePluginFactory {
79 constructor: fn() -> Rc<dyn NativePluginFactory>,
80 descriptor: &'static str,
81}
82
83impl LinkedNativePluginFactory {
84 #[doc(hidden)]
86 pub const fn new(
87 constructor: fn() -> Rc<dyn NativePluginFactory>,
88 descriptor: &'static str,
89 ) -> Self {
90 Self {
91 constructor,
92 descriptor,
93 }
94 }
95}
96
97inventory::collect!(LinkedNativePluginFactory);
98
99fn explicitly_linked_factories() -> &'static Mutex<Vec<LinkedNativePluginFactory>> {
100 static FACTORIES: OnceLock<Mutex<Vec<LinkedNativePluginFactory>>> = OnceLock::new();
101 FACTORIES.get_or_init(|| Mutex::new(Vec::new()))
102}
103
104#[doc(hidden)]
106pub fn link_native_plugin(factory: LinkedNativePluginFactory) {
107 let mut factories = explicitly_linked_factories()
108 .lock()
109 .unwrap_or_else(std::sync::PoisonError::into_inner);
110 if !factories.iter().any(|linked| {
111 linked.descriptor == factory.descriptor
112 && std::ptr::fn_addr_eq(linked.constructor, factory.constructor)
113 }) {
114 factories.push(factory);
115 }
116}
117
118fn linked_factories() -> Vec<LinkedNativePluginFactory> {
119 let factories = inventory::iter::<LinkedNativePluginFactory>
120 .into_iter()
121 .copied()
122 .collect::<Vec<_>>();
123 let mut factories = factories;
124 factories.extend(
125 explicitly_linked_factories()
126 .lock()
127 .unwrap_or_else(std::sync::PoisonError::into_inner)
128 .iter()
129 .copied(),
130 );
131 factories
132 .into_iter()
133 .fold(Vec::new(), |mut unique, factory| {
134 if !unique.iter().any(|linked: &LinkedNativePluginFactory| {
135 linked.descriptor == factory.descriptor
136 && std::ptr::fn_addr_eq(linked.constructor, factory.constructor)
137 }) {
138 unique.push(factory);
139 }
140 unique
141 })
142}
143
144#[derive(Debug)]
146pub struct NativePluginInstance {
147 endpoints: NativeEndpointSet,
148 lifecycle: Rc<dyn PluginLifecycle>,
149}
150
151impl NativePluginInstance {
152 pub fn new(endpoints: Vec<Rc<dyn NativeRequestEndpoint>>) -> Self {
154 Self::with_lifecycle(endpoints, NoopPluginLifecycle)
155 }
156
157 pub fn with_lifecycle(
159 endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
160 lifecycle: impl PluginLifecycle,
161 ) -> Self {
162 Self {
163 endpoints: NativeEndpointSet::new(endpoints, Vec::new(), Vec::new()),
164 lifecycle: Rc::new(lifecycle),
165 }
166 }
167
168 pub fn with_endpoints(
170 endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
171 stream_endpoints: Vec<Rc<dyn NativeStreamEndpoint>>,
172 lifecycle: impl PluginLifecycle,
173 ) -> Self {
174 Self {
175 endpoints: NativeEndpointSet::new(endpoints, stream_endpoints, Vec::new()),
176 lifecycle: Rc::new(lifecycle),
177 }
178 }
179
180 pub fn with_stream_endpoints(
182 stream_endpoints: Vec<Rc<dyn NativeStreamEndpoint>>,
183 lifecycle: impl PluginLifecycle,
184 ) -> Self {
185 Self::with_endpoints(Vec::new(), stream_endpoints, lifecycle)
186 }
187
188 pub fn with_event_endpoints(
190 event_endpoints: Vec<Rc<dyn NativeEventEndpoint>>,
191 lifecycle: impl PluginLifecycle,
192 ) -> Self {
193 Self {
194 endpoints: NativeEndpointSet::new(Vec::new(), Vec::new(), event_endpoints),
195 lifecycle: Rc::new(lifecycle),
196 }
197 }
198
199 pub fn with_all_endpoints(
201 endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
202 stream_endpoints: Vec<Rc<dyn NativeStreamEndpoint>>,
203 event_endpoints: Vec<Rc<dyn NativeEventEndpoint>>,
204 lifecycle: impl PluginLifecycle,
205 ) -> Self {
206 Self {
207 endpoints: NativeEndpointSet::new(endpoints, stream_endpoints, event_endpoints),
208 lifecycle: Rc::new(lifecycle),
209 }
210 }
211
212 pub fn lifecycle(&self) -> Rc<dyn PluginLifecycle> {
214 self.lifecycle.clone()
215 }
216
217 pub fn endpoints(&self) -> &[Rc<dyn NativeRequestEndpoint>] {
219 self.endpoints.request()
220 }
221
222 pub fn stream_endpoints(&self) -> &[Rc<dyn NativeStreamEndpoint>] {
224 self.endpoints.stream()
225 }
226
227 pub fn event_endpoints(&self) -> &[Rc<dyn NativeEventEndpoint>] {
229 self.endpoints.event()
230 }
231}
232
233impl Default for NativePluginInstance {
234 fn default() -> Self {
235 Self::new(Vec::new())
236 }
237}
238
239pub trait NativePluginFactory: std::fmt::Debug + 'static {
241 fn package_id(&self) -> &'static str;
243 fn package_version(&self) -> &'static str {
245 ""
246 }
247 fn runtime_profile(&self) -> &'static str {
249 "lenso.native-authoring@1"
250 }
251 fn factory_identity(&self) -> String {
258 let version = self.package_version();
259 if version.is_empty() {
260 self.package_id().to_owned()
261 } else {
262 format!("{}@{version}", self.package_id())
263 }
264 }
265 fn instantiate(
267 &self,
268 context: NativePluginFactoryContext<'_>,
269 ) -> Result<NativePluginInstance, RuntimeFailure>;
270}
271
272#[derive(Clone, Copy, Debug)]
274pub struct NativePluginFactoryContext<'a> {
275 instance_key: &'a str,
276 entrypoint: &'a str,
277 configuration: &'a str,
278 resources: &'a InstanceResources,
279}
280
281impl<'a> NativePluginFactoryContext<'a> {
282 fn from_plan(
283 instance: &'a lenso_app_plan::PluginInstancePlan,
284 resources: &'a InstanceResources,
285 ) -> Self {
286 Self {
287 instance_key: instance.instance_key(),
288 entrypoint: instance.entrypoint(),
289 configuration: instance.configuration(),
290 resources,
291 }
292 }
293
294 pub const fn instance_key(self) -> &'a str {
296 self.instance_key
297 }
298
299 pub const fn entrypoint(self) -> &'a str {
301 self.entrypoint
302 }
303
304 pub const fn configuration(self) -> &'a str {
306 self.configuration
307 }
308
309 pub const fn resources(self) -> &'a InstanceResources {
311 self.resources
312 }
313}
314
315#[derive(Debug, Default)]
317pub struct NativePluginRegistry {
318 factories: Vec<Rc<dyn NativePluginFactory>>,
319 resources: lenso_runtime_codec::InstanceResourceCatalog,
320}
321
322type NativeInstances = BTreeMap<String, NativePluginInstance>;
323type PreparedGenerations = BTreeMap<String, PreparedNativePlugin>;
324type NativeBindings = (
325 Vec<PreparedBinding>,
326 Vec<PreparedStreamBinding>,
327 Vec<PreparedEventBinding>,
328);
329
330fn factory_matches(
331 factory: &dyn NativePluginFactory,
332 expected: &lenso_app_plan::PluginInstancePlan,
333) -> bool {
334 factory.package_id() == expected.package_id()
335 && factory.runtime_profile() == expected.runtime_profile()
336 && (expected.package_revision().is_empty()
337 || factory.package_version() == expected.package_revision()
338 || factory.factory_identity() == expected.package_revision())
339}
340
341impl NativePluginRegistry {
342 pub fn new() -> Self {
344 Self::default()
345 }
346
347 #[must_use]
352 pub fn with_linked_factories(mut self) -> Self {
353 self.factories.extend(
354 linked_factories()
355 .into_iter()
356 .map(|linked| (linked.constructor)()),
357 );
358 self.factories
359 .sort_by_key(|factory| factory.factory_identity());
360 self.factories
361 .dedup_by(|left, right| left.factory_identity() == right.factory_identity());
362 self
363 }
364
365 #[must_use]
367 pub fn with_resources(
368 mut self,
369 resources: lenso_runtime_codec::InstanceResourceCatalog,
370 ) -> Self {
371 self.resources = resources;
372 self
373 }
374
375 pub fn factories(&self) -> impl Iterator<Item = &dyn NativePluginFactory> {
377 self.factories.iter().map(std::convert::AsRef::as_ref)
378 }
379
380 pub fn host_catalog(
382 slots: impl IntoIterator<Item = HostSlot>,
383 defaults: impl IntoIterator<Item = HostDefaultPlugin>,
384 ) -> Result<HostCatalog, RuntimeFailure> {
385 let plugins = linked_factories()
386 .into_iter()
387 .map(|linked| {
388 serde_json::from_str::<PluginDescriptor>(linked.descriptor)
389 .map(HostPluginRelease::new)
390 .map_err(|error| RuntimeFailure::InvalidResolvedPlan {
391 detail: format!("invalid linked Plugin Descriptor: {error}"),
392 })
393 })
394 .collect::<Result<Vec<_>, _>>()?;
395 Ok(HostCatalog::new(slots, plugins, defaults))
396 }
397 #[must_use]
399 pub fn with_factory(mut self, factory: impl NativePluginFactory) -> Self {
400 self.factories.push(Rc::new(factory));
401 self
402 }
403
404 fn prepare_instances(
405 &self,
406 plan: &ResolvedAppPlan,
407 ) -> Result<(NativeInstances, PreparedGenerations), RuntimeFailure> {
408 let mut instances = BTreeMap::new();
409 let mut generations = BTreeMap::new();
410 for expected in plan
411 .plugin_instances()
412 .iter()
413 .filter(|instance| instance.execution_class() == &ExecutionClassId::native_rust())
414 {
415 let matching_factories: Vec<_> = self
416 .factories
417 .iter()
418 .filter(|factory| factory_matches(factory.as_ref(), expected))
419 .collect();
420 let factory = match matching_factories.as_slice() {
421 [] => {
422 return Err(RuntimeFailure::MissingPluginFactory {
423 instance: expected.instance_key().to_owned(),
424 package_id: expected.package_id().to_owned(),
425 });
426 }
427 [factory] => *factory,
428 _ => {
429 return invalid(format!(
430 "multiple statically linked factories declare package `{}`",
431 expected.package_id()
432 ));
433 }
434 };
435 let generation = factory.instantiate(NativePluginFactoryContext::from_plan(
436 expected,
437 self.resources.for_instance(expected.instance_key()),
438 ))?;
439 generations.insert(
440 expected.instance_key().to_owned(),
441 PreparedNativePlugin::with_endpoint_set_lifecycle(
442 generation.endpoints.clone(),
443 generation.lifecycle(),
444 ),
445 );
446 if instances
447 .insert(expected.instance_key().to_owned(), generation)
448 .is_some()
449 {
450 return invalid(format!(
451 "duplicate Plugin Instance `{}`",
452 expected.instance_key()
453 ));
454 }
455 }
456 Ok((instances, generations))
457 }
458}
459
460impl NativeExecutionAdapter for NativePluginRegistry {
461 fn supports_runtime_profile(&self, authoring_version: u32, profile: &str) -> bool {
462 matches!(
463 (authoring_version, profile),
464 (1, "lenso.native-authoring@1" | "lenso.native-rust@1")
465 | (2, "lenso.native-authoring@2")
466 )
467 }
468
469 fn prepare(&self, plan: &ResolvedAppPlan) -> Result<PreparedNativeApp, RuntimeFailure> {
470 plan.validate()
471 .map_err(|error| RuntimeFailure::InvalidResolvedPlan {
472 detail: error.to_string(),
473 })?;
474
475 let (instances, generations) = self.prepare_instances(plan)?;
476 let (bindings, stream_bindings, event_bindings) = prepare_bindings(plan, &instances)?;
477 Ok(PreparedNativeApp::new(bindings, generations)
478 .with_stream_bindings(stream_bindings)
479 .with_event_bindings(event_bindings))
480 }
481
482 fn recreate(
483 &self,
484 plan: &ResolvedAppPlan,
485 instance_key: &str,
486 ) -> Result<PreparedNativePlugin, RuntimeFailure> {
487 let expected = plan
488 .plugin_instances()
489 .iter()
490 .find(|instance| instance.instance_key() == instance_key)
491 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
492 detail: format!("unknown Plugin Instance `{instance_key}`"),
493 })?;
494 let matching_factories: Vec<_> = self
495 .factories
496 .iter()
497 .filter(|factory| factory_matches(factory.as_ref(), expected))
498 .collect();
499 let factory = match matching_factories.as_slice() {
500 [] => {
501 return Err(RuntimeFailure::MissingPluginFactory {
502 instance: expected.instance_key().to_owned(),
503 package_id: expected.package_id().to_owned(),
504 });
505 }
506 [factory] => *factory,
507 _ => {
508 return invalid(format!(
509 "multiple statically linked factories declare package `{}`",
510 expected.package_id()
511 ));
512 }
513 };
514 let generation = factory.instantiate(NativePluginFactoryContext::from_plan(
515 expected,
516 self.resources.for_instance(expected.instance_key()),
517 ))?;
518 Ok(PreparedNativePlugin::with_endpoint_set_lifecycle(
519 generation.endpoints.clone(),
520 generation.lifecycle(),
521 ))
522 }
523}
524
525fn prepare_bindings(
526 plan: &ResolvedAppPlan,
527 instances: &NativeInstances,
528) -> Result<NativeBindings, RuntimeFailure> {
529 let mut bindings = Vec::new();
530 let mut stream_bindings = Vec::new();
531 let mut event_bindings = Vec::new();
532 for binding in plan.capability_bindings() {
533 if !instances.contains_key(binding.provider_instance()) {
534 continue;
535 }
536 let provider = plan
537 .plugin_instance(binding.provider_instance())
538 .expect("validated binding provider should exist");
539 let descriptor = provider
540 .provided_capabilities()
541 .iter()
542 .find(|descriptor| descriptor.capability_id() == binding.capability_id())
543 .expect("validated binding descriptor should exist");
544 if !descriptor.request_operations().is_empty() {
545 let endpoint = instances
546 .get(binding.provider_instance())
547 .and_then(|instance| {
548 instance.endpoints.request().iter().find(|endpoint| {
549 endpoint.capability_id() == binding.capability_id()
550 && endpoint.descriptor_version() == binding.descriptor_version()
551 })
552 })
553 .cloned()
554 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
555 detail: format!(
556 "Capability `{}` version `{}` has no request endpoint on provider `{}`",
557 binding.capability_id(),
558 binding.descriptor_version(),
559 binding.provider_instance()
560 ),
561 })?;
562 bindings.push(
563 PreparedBinding::new(
564 binding.consumer_instance(),
565 binding.provider_instance(),
566 endpoint,
567 )
568 .with_requirement_id(binding.requirement_id()),
569 );
570 }
571 if !descriptor.stream_operations().is_empty() {
572 let endpoint = instances
573 .get(binding.provider_instance())
574 .and_then(|instance| {
575 instance.endpoints.stream().iter().find(|endpoint| {
576 endpoint.capability_id() == binding.capability_id()
577 && endpoint.descriptor_version() == binding.descriptor_version()
578 })
579 })
580 .cloned()
581 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
582 detail: format!(
583 "Capability `{}` version `{}` has no stream endpoint on provider `{}`",
584 binding.capability_id(),
585 binding.descriptor_version(),
586 binding.provider_instance()
587 ),
588 })?;
589 stream_bindings.push(
590 PreparedStreamBinding::new(
591 binding.consumer_instance(),
592 binding.provider_instance(),
593 endpoint,
594 )
595 .with_requirement_id(binding.requirement_id()),
596 );
597 }
598 if !descriptor.event_operations().is_empty() {
599 let endpoint = instances
600 .get(binding.provider_instance())
601 .and_then(|instance| {
602 instance.endpoints.event().iter().find(|endpoint| {
603 endpoint.capability_id() == binding.capability_id()
604 && endpoint.descriptor_version() == binding.descriptor_version()
605 })
606 })
607 .cloned()
608 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
609 detail: format!(
610 "Capability `{}` version `{}` has no Event endpoint on provider `{}`",
611 binding.capability_id(),
612 binding.descriptor_version(),
613 binding.provider_instance()
614 ),
615 })?;
616 event_bindings.push(
617 PreparedEventBinding::new(
618 binding.consumer_instance(),
619 binding.provider_instance(),
620 endpoint,
621 )
622 .with_requirement_id(binding.requirement_id()),
623 );
624 }
625 }
626 Ok((bindings, stream_bindings, event_bindings))
627}
628
629fn invalid<T>(detail: String) -> Result<T, RuntimeFailure> {
630 Err(RuntimeFailure::InvalidResolvedPlan { detail })
631}