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 {
252 "lenso.native-authoring@1"
253 }
254 fn factory_identity(&self) -> String {
261 let version = self.package_version();
262 if version.is_empty() {
263 self.package_id().to_owned()
264 } else {
265 format!("{}@{version}", self.package_id())
266 }
267 }
268 fn instantiate(
270 &self,
271 context: NativePluginFactoryContext<'_>,
272 ) -> Result<NativePluginInstance, RuntimeFailure>;
273}
274
275#[derive(Clone, Copy, Debug)]
277pub struct NativePluginFactoryContext<'a> {
278 instance_key: &'a str,
279 entrypoint: &'a str,
280 configuration: &'a str,
281 resources: &'a InstanceResources,
282}
283
284impl<'a> NativePluginFactoryContext<'a> {
285 fn from_plan(
286 instance: &'a lenso_app_plan::PluginInstancePlan,
287 resources: &'a InstanceResources,
288 ) -> Self {
289 Self {
290 instance_key: instance.instance_key(),
291 entrypoint: instance.entrypoint(),
292 configuration: instance.configuration(),
293 resources,
294 }
295 }
296
297 pub const fn instance_key(self) -> &'a str {
299 self.instance_key
300 }
301
302 pub const fn entrypoint(self) -> &'a str {
304 self.entrypoint
305 }
306
307 pub const fn configuration(self) -> &'a str {
309 self.configuration
310 }
311
312 pub const fn resources(self) -> &'a InstanceResources {
314 self.resources
315 }
316}
317
318#[derive(Debug, Default)]
320pub struct NativePluginRegistry {
321 factories: Vec<Rc<dyn NativePluginFactory>>,
322 resources: lenso_runtime_codec::InstanceResourceCatalog,
323}
324
325type NativeInstances = BTreeMap<String, NativePluginInstance>;
326type PreparedGenerations = BTreeMap<String, PreparedNativePlugin>;
327type NativeBindings = (
328 Vec<PreparedBinding>,
329 Vec<PreparedStreamBinding>,
330 Vec<PreparedEventBinding>,
331);
332
333fn factory_matches(
334 factory: &dyn NativePluginFactory,
335 expected: &lenso_app_plan::PluginInstancePlan,
336) -> bool {
337 factory.package_id() == expected.package_id()
338 && (expected.package_revision().is_empty()
339 || factory.package_version() == expected.package_revision()
340 || factory.factory_identity() == expected.package_revision())
341}
342
343impl NativePluginRegistry {
344 pub fn new() -> Self {
346 Self::default()
347 }
348
349 #[must_use]
354 pub fn with_linked_factories(mut self) -> Self {
355 self.factories.extend(
356 linked_factories()
357 .into_iter()
358 .map(|linked| (linked.constructor)()),
359 );
360 self.factories
361 .sort_by_key(|factory| factory.factory_identity());
362 self.factories
363 .dedup_by(|left, right| left.factory_identity() == right.factory_identity());
364 self
365 }
366
367 #[must_use]
369 pub fn with_resources(
370 mut self,
371 resources: lenso_runtime_codec::InstanceResourceCatalog,
372 ) -> Self {
373 self.resources = resources;
374 self
375 }
376
377 pub fn factories(&self) -> impl Iterator<Item = &dyn NativePluginFactory> {
379 self.factories.iter().map(std::convert::AsRef::as_ref)
380 }
381
382 pub fn host_catalog(
384 slots: impl IntoIterator<Item = HostSlot>,
385 defaults: impl IntoIterator<Item = HostDefaultPlugin>,
386 ) -> Result<HostCatalog, RuntimeFailure> {
387 let plugins = linked_factories()
388 .into_iter()
389 .map(|linked| {
390 serde_json::from_str::<PluginDescriptor>(linked.descriptor)
391 .map(HostPluginRelease::new)
392 .map_err(|error| RuntimeFailure::InvalidResolvedPlan {
393 detail: format!("invalid linked Plugin Descriptor: {error}"),
394 })
395 })
396 .collect::<Result<Vec<_>, _>>()?;
397 Ok(HostCatalog::new(slots, plugins, defaults))
398 }
399 #[must_use]
401 pub fn with_factory(mut self, factory: impl NativePluginFactory) -> Self {
402 self.factories.push(Rc::new(factory));
403 self
404 }
405
406 fn prepare_instances(
407 &self,
408 plan: &ResolvedAppPlan,
409 ) -> Result<(NativeInstances, PreparedGenerations), RuntimeFailure> {
410 let mut instances = BTreeMap::new();
411 let mut generations = BTreeMap::new();
412 for expected in plan
413 .plugin_instances()
414 .iter()
415 .filter(|instance| instance.execution_class() == &ExecutionClassId::native_rust())
416 {
417 let matching_factories: Vec<_> = self
418 .factories
419 .iter()
420 .filter(|factory| factory_matches(factory.as_ref(), expected))
421 .collect();
422 let factory = match matching_factories.as_slice() {
423 [] => {
424 return Err(RuntimeFailure::MissingPluginFactory {
425 instance: expected.instance_key().to_owned(),
426 package_id: expected.package_id().to_owned(),
427 });
428 }
429 [factory] => *factory,
430 _ => {
431 return invalid(format!(
432 "multiple statically linked factories declare package `{}`",
433 expected.package_id()
434 ));
435 }
436 };
437 let generation = factory.instantiate(NativePluginFactoryContext::from_plan(
438 expected,
439 self.resources.for_instance(expected.instance_key()),
440 ))?;
441 generations.insert(
442 expected.instance_key().to_owned(),
443 PreparedNativePlugin::with_endpoint_set_lifecycle(
444 generation.endpoints.clone(),
445 generation.lifecycle(),
446 ),
447 );
448 if instances
449 .insert(expected.instance_key().to_owned(), generation)
450 .is_some()
451 {
452 return invalid(format!(
453 "duplicate Plugin Instance `{}`",
454 expected.instance_key()
455 ));
456 }
457 }
458 Ok((instances, generations))
459 }
460}
461
462impl NativeExecutionAdapter for NativePluginRegistry {
463 fn supports_runtime_profile(&self, authoring_version: u32, profile: &str) -> bool {
464 matches!(
465 (authoring_version, profile),
466 (1, "lenso.native-authoring@1" | "lenso.native-rust@1")
467 | (2, "lenso.native-authoring@2")
468 )
469 }
470
471 fn prepare(&self, plan: &ResolvedAppPlan) -> Result<PreparedNativeApp, RuntimeFailure> {
472 plan.validate()
473 .map_err(|error| RuntimeFailure::InvalidResolvedPlan {
474 detail: error.to_string(),
475 })?;
476
477 let (instances, generations) = self.prepare_instances(plan)?;
478 let (bindings, stream_bindings, event_bindings) = prepare_bindings(plan, &instances)?;
479 Ok(PreparedNativeApp::new(bindings, generations)
480 .with_stream_bindings(stream_bindings)
481 .with_event_bindings(event_bindings))
482 }
483
484 fn recreate(
485 &self,
486 plan: &ResolvedAppPlan,
487 instance_key: &str,
488 ) -> Result<PreparedNativePlugin, RuntimeFailure> {
489 let expected = plan
490 .plugin_instances()
491 .iter()
492 .find(|instance| instance.instance_key() == instance_key)
493 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
494 detail: format!("unknown Plugin Instance `{instance_key}`"),
495 })?;
496 let matching_factories: Vec<_> = self
497 .factories
498 .iter()
499 .filter(|factory| factory_matches(factory.as_ref(), expected))
500 .collect();
501 let factory = match matching_factories.as_slice() {
502 [] => {
503 return Err(RuntimeFailure::MissingPluginFactory {
504 instance: expected.instance_key().to_owned(),
505 package_id: expected.package_id().to_owned(),
506 });
507 }
508 [factory] => *factory,
509 _ => {
510 return invalid(format!(
511 "multiple statically linked factories declare package `{}`",
512 expected.package_id()
513 ));
514 }
515 };
516 let generation = factory.instantiate(NativePluginFactoryContext::from_plan(
517 expected,
518 self.resources.for_instance(expected.instance_key()),
519 ))?;
520 Ok(PreparedNativePlugin::with_endpoint_set_lifecycle(
521 generation.endpoints.clone(),
522 generation.lifecycle(),
523 ))
524 }
525}
526
527fn prepare_bindings(
528 plan: &ResolvedAppPlan,
529 instances: &NativeInstances,
530) -> Result<NativeBindings, RuntimeFailure> {
531 let mut bindings = Vec::new();
532 let mut stream_bindings = Vec::new();
533 let mut event_bindings = Vec::new();
534 for binding in plan.capability_bindings() {
535 if !instances.contains_key(binding.provider_instance()) {
536 continue;
537 }
538 let provider = plan
539 .plugin_instance(binding.provider_instance())
540 .expect("validated binding provider should exist");
541 let descriptor = provider
542 .provided_capabilities()
543 .iter()
544 .find(|descriptor| descriptor.capability_id() == binding.capability_id())
545 .expect("validated binding descriptor should exist");
546 if !descriptor.request_operations().is_empty() {
547 let endpoint = instances
548 .get(binding.provider_instance())
549 .and_then(|instance| {
550 instance.endpoints.request().iter().find(|endpoint| {
551 endpoint.capability_id() == binding.capability_id()
552 && endpoint.descriptor_version() == binding.descriptor_version()
553 })
554 })
555 .cloned()
556 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
557 detail: format!(
558 "Capability `{}` version `{}` has no request endpoint on provider `{}`",
559 binding.capability_id(),
560 binding.descriptor_version(),
561 binding.provider_instance()
562 ),
563 })?;
564 bindings.push(
565 PreparedBinding::new(
566 binding.consumer_instance(),
567 binding.provider_instance(),
568 endpoint,
569 )
570 .with_requirement_id(binding.requirement_id()),
571 );
572 }
573 if !descriptor.stream_operations().is_empty() {
574 let endpoint = instances
575 .get(binding.provider_instance())
576 .and_then(|instance| {
577 instance.endpoints.stream().iter().find(|endpoint| {
578 endpoint.capability_id() == binding.capability_id()
579 && endpoint.descriptor_version() == binding.descriptor_version()
580 })
581 })
582 .cloned()
583 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
584 detail: format!(
585 "Capability `{}` version `{}` has no stream endpoint on provider `{}`",
586 binding.capability_id(),
587 binding.descriptor_version(),
588 binding.provider_instance()
589 ),
590 })?;
591 stream_bindings.push(
592 PreparedStreamBinding::new(
593 binding.consumer_instance(),
594 binding.provider_instance(),
595 endpoint,
596 )
597 .with_requirement_id(binding.requirement_id()),
598 );
599 }
600 if !descriptor.event_operations().is_empty() {
601 let endpoint = instances
602 .get(binding.provider_instance())
603 .and_then(|instance| {
604 instance.endpoints.event().iter().find(|endpoint| {
605 endpoint.capability_id() == binding.capability_id()
606 && endpoint.descriptor_version() == binding.descriptor_version()
607 })
608 })
609 .cloned()
610 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
611 detail: format!(
612 "Capability `{}` version `{}` has no Event endpoint on provider `{}`",
613 binding.capability_id(),
614 binding.descriptor_version(),
615 binding.provider_instance()
616 ),
617 })?;
618 event_bindings.push(
619 PreparedEventBinding::new(
620 binding.consumer_instance(),
621 binding.provider_instance(),
622 endpoint,
623 )
624 .with_requirement_id(binding.requirement_id()),
625 );
626 }
627 }
628 Ok((bindings, stream_bindings, event_bindings))
629}
630
631fn invalid<T>(detail: String) -> Result<T, RuntimeFailure> {
632 Err(RuntimeFailure::InvalidResolvedPlan { detail })
633}