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 mut factories = inventory::iter::<LinkedNativePluginFactory>
120 .into_iter()
121 .copied()
122 .collect::<Vec<_>>();
123 factories.extend(
124 explicitly_linked_factories()
125 .lock()
126 .unwrap_or_else(std::sync::PoisonError::into_inner)
127 .iter()
128 .copied(),
129 );
130 factories
131}
132
133#[derive(Debug)]
135pub struct NativePluginInstance {
136 endpoints: NativeEndpointSet,
137 lifecycle: Rc<dyn PluginLifecycle>,
138}
139
140impl NativePluginInstance {
141 pub fn new(endpoints: Vec<Rc<dyn NativeRequestEndpoint>>) -> Self {
143 Self::with_lifecycle(endpoints, NoopPluginLifecycle)
144 }
145
146 pub fn with_lifecycle(
148 endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
149 lifecycle: impl PluginLifecycle,
150 ) -> Self {
151 Self {
152 endpoints: NativeEndpointSet::new(endpoints, Vec::new(), Vec::new()),
153 lifecycle: Rc::new(lifecycle),
154 }
155 }
156
157 pub fn with_endpoints(
159 endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
160 stream_endpoints: Vec<Rc<dyn NativeStreamEndpoint>>,
161 lifecycle: impl PluginLifecycle,
162 ) -> Self {
163 Self {
164 endpoints: NativeEndpointSet::new(endpoints, stream_endpoints, Vec::new()),
165 lifecycle: Rc::new(lifecycle),
166 }
167 }
168
169 pub fn with_stream_endpoints(
171 stream_endpoints: Vec<Rc<dyn NativeStreamEndpoint>>,
172 lifecycle: impl PluginLifecycle,
173 ) -> Self {
174 Self::with_endpoints(Vec::new(), stream_endpoints, lifecycle)
175 }
176
177 pub fn with_event_endpoints(
179 event_endpoints: Vec<Rc<dyn NativeEventEndpoint>>,
180 lifecycle: impl PluginLifecycle,
181 ) -> Self {
182 Self {
183 endpoints: NativeEndpointSet::new(Vec::new(), Vec::new(), event_endpoints),
184 lifecycle: Rc::new(lifecycle),
185 }
186 }
187
188 pub fn with_all_endpoints(
190 endpoints: Vec<Rc<dyn NativeRequestEndpoint>>,
191 stream_endpoints: Vec<Rc<dyn NativeStreamEndpoint>>,
192 event_endpoints: Vec<Rc<dyn NativeEventEndpoint>>,
193 lifecycle: impl PluginLifecycle,
194 ) -> Self {
195 Self {
196 endpoints: NativeEndpointSet::new(endpoints, stream_endpoints, event_endpoints),
197 lifecycle: Rc::new(lifecycle),
198 }
199 }
200
201 pub fn lifecycle(&self) -> Rc<dyn PluginLifecycle> {
203 self.lifecycle.clone()
204 }
205
206 pub fn endpoints(&self) -> &[Rc<dyn NativeRequestEndpoint>] {
208 self.endpoints.request()
209 }
210
211 pub fn stream_endpoints(&self) -> &[Rc<dyn NativeStreamEndpoint>] {
213 self.endpoints.stream()
214 }
215
216 pub fn event_endpoints(&self) -> &[Rc<dyn NativeEventEndpoint>] {
218 self.endpoints.event()
219 }
220}
221
222impl Default for NativePluginInstance {
223 fn default() -> Self {
224 Self::new(Vec::new())
225 }
226}
227
228pub trait NativePluginFactory: std::fmt::Debug + 'static {
230 fn package_id(&self) -> &'static str;
232 fn package_version(&self) -> &'static str {
234 ""
235 }
236 fn runtime_profile(&self) -> &'static str {
238 "lenso.native-authoring@1"
239 }
240 fn factory_identity(&self) -> String {
247 let version = self.package_version();
248 if version.is_empty() {
249 self.package_id().to_owned()
250 } else {
251 format!("{}@{version}", self.package_id())
252 }
253 }
254 fn instantiate(
256 &self,
257 context: NativePluginFactoryContext<'_>,
258 ) -> Result<NativePluginInstance, RuntimeFailure>;
259}
260
261#[derive(Clone, Copy, Debug)]
263pub struct NativePluginFactoryContext<'a> {
264 instance_key: &'a str,
265 entrypoint: &'a str,
266 configuration: &'a str,
267 resources: &'a InstanceResources,
268}
269
270impl<'a> NativePluginFactoryContext<'a> {
271 fn from_plan(
272 instance: &'a lenso_app_plan::PluginInstancePlan,
273 resources: &'a InstanceResources,
274 ) -> Self {
275 Self {
276 instance_key: instance.instance_key(),
277 entrypoint: instance.entrypoint(),
278 configuration: instance.configuration(),
279 resources,
280 }
281 }
282
283 pub const fn instance_key(self) -> &'a str {
285 self.instance_key
286 }
287
288 pub const fn entrypoint(self) -> &'a str {
290 self.entrypoint
291 }
292
293 pub const fn configuration(self) -> &'a str {
295 self.configuration
296 }
297
298 pub const fn resources(self) -> &'a InstanceResources {
300 self.resources
301 }
302}
303
304#[derive(Debug, Default)]
306pub struct NativePluginRegistry {
307 factories: Vec<Rc<dyn NativePluginFactory>>,
308 resources: lenso_runtime_codec::InstanceResourceCatalog,
309}
310
311type NativeInstances = BTreeMap<String, NativePluginInstance>;
312type PreparedGenerations = BTreeMap<String, PreparedNativePlugin>;
313type NativeBindings = (
314 Vec<PreparedBinding>,
315 Vec<PreparedStreamBinding>,
316 Vec<PreparedEventBinding>,
317);
318
319fn factory_matches(
320 factory: &dyn NativePluginFactory,
321 expected: &lenso_app_plan::PluginInstancePlan,
322) -> bool {
323 factory.package_id() == expected.package_id()
324 && factory.runtime_profile() == expected.runtime_profile()
325 && (expected.package_revision().is_empty()
326 || factory.package_version() == expected.package_revision()
327 || factory.factory_identity() == expected.package_revision())
328}
329
330impl NativePluginRegistry {
331 pub fn new() -> Self {
333 Self::default()
334 }
335
336 #[must_use]
341 pub fn with_linked_factories(mut self) -> Self {
342 self.factories.extend(
343 linked_factories()
344 .into_iter()
345 .map(|linked| (linked.constructor)()),
346 );
347 self.factories
348 .sort_by_key(|factory| factory.factory_identity());
349 self.factories
350 .dedup_by(|left, right| left.factory_identity() == right.factory_identity());
351 self
352 }
353
354 #[must_use]
356 pub fn with_resources(
357 mut self,
358 resources: lenso_runtime_codec::InstanceResourceCatalog,
359 ) -> Self {
360 self.resources = resources;
361 self
362 }
363
364 pub fn factories(&self) -> impl Iterator<Item = &dyn NativePluginFactory> {
366 self.factories.iter().map(std::convert::AsRef::as_ref)
367 }
368
369 pub fn host_catalog(
371 slots: impl IntoIterator<Item = HostSlot>,
372 defaults: impl IntoIterator<Item = HostDefaultPlugin>,
373 ) -> Result<HostCatalog, RuntimeFailure> {
374 let plugins = linked_factories()
375 .into_iter()
376 .map(|linked| {
377 serde_json::from_str::<PluginDescriptor>(linked.descriptor)
378 .map(HostPluginRelease::new)
379 .map_err(|error| RuntimeFailure::InvalidResolvedPlan {
380 detail: format!("invalid linked Plugin Descriptor: {error}"),
381 })
382 })
383 .collect::<Result<Vec<_>, _>>()?;
384 Ok(HostCatalog::new(slots, plugins, defaults))
385 }
386 #[must_use]
388 pub fn with_factory(mut self, factory: impl NativePluginFactory) -> Self {
389 self.factories.push(Rc::new(factory));
390 self
391 }
392
393 fn prepare_instances(
394 &self,
395 plan: &ResolvedAppPlan,
396 ) -> Result<(NativeInstances, PreparedGenerations), RuntimeFailure> {
397 let mut instances = BTreeMap::new();
398 let mut generations = BTreeMap::new();
399 for expected in plan
400 .plugin_instances()
401 .iter()
402 .filter(|instance| instance.execution_class() == &ExecutionClassId::native_rust())
403 {
404 let matching_factories: Vec<_> = self
405 .factories
406 .iter()
407 .filter(|factory| factory_matches(factory.as_ref(), expected))
408 .collect();
409 let factory = match matching_factories.as_slice() {
410 [] => {
411 return Err(RuntimeFailure::MissingPluginFactory {
412 instance: expected.instance_key().to_owned(),
413 package_id: expected.package_id().to_owned(),
414 });
415 }
416 [factory] => *factory,
417 _ => {
418 return invalid(format!(
419 "multiple statically linked factories declare package `{}`",
420 expected.package_id()
421 ));
422 }
423 };
424 let generation = factory.instantiate(NativePluginFactoryContext::from_plan(
425 expected,
426 self.resources.for_instance(expected.instance_key()),
427 ))?;
428 generations.insert(
429 expected.instance_key().to_owned(),
430 PreparedNativePlugin::with_endpoint_set_lifecycle(
431 generation.endpoints.clone(),
432 generation.lifecycle(),
433 ),
434 );
435 if instances
436 .insert(expected.instance_key().to_owned(), generation)
437 .is_some()
438 {
439 return invalid(format!(
440 "duplicate Plugin Instance `{}`",
441 expected.instance_key()
442 ));
443 }
444 }
445 Ok((instances, generations))
446 }
447}
448
449impl NativeExecutionAdapter for NativePluginRegistry {
450 fn supports_runtime_profile(&self, authoring_version: u32, profile: &str) -> bool {
451 matches!(
452 (authoring_version, profile),
453 (1, "lenso.native-authoring@1" | "lenso.native-rust@1")
454 | (2, "lenso.native-authoring@2")
455 )
456 }
457
458 fn prepare(&self, plan: &ResolvedAppPlan) -> Result<PreparedNativeApp, RuntimeFailure> {
459 plan.validate()
460 .map_err(|error| RuntimeFailure::InvalidResolvedPlan {
461 detail: error.to_string(),
462 })?;
463
464 let (instances, generations) = self.prepare_instances(plan)?;
465 let (bindings, stream_bindings, event_bindings) = prepare_bindings(plan, &instances)?;
466 Ok(PreparedNativeApp::new(bindings, generations)
467 .with_stream_bindings(stream_bindings)
468 .with_event_bindings(event_bindings))
469 }
470
471 fn recreate(
472 &self,
473 plan: &ResolvedAppPlan,
474 instance_key: &str,
475 ) -> Result<PreparedNativePlugin, RuntimeFailure> {
476 let expected = plan
477 .plugin_instances()
478 .iter()
479 .find(|instance| instance.instance_key() == instance_key)
480 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
481 detail: format!("unknown Plugin Instance `{instance_key}`"),
482 })?;
483 let matching_factories: Vec<_> = self
484 .factories
485 .iter()
486 .filter(|factory| factory_matches(factory.as_ref(), expected))
487 .collect();
488 let factory = match matching_factories.as_slice() {
489 [] => {
490 return Err(RuntimeFailure::MissingPluginFactory {
491 instance: expected.instance_key().to_owned(),
492 package_id: expected.package_id().to_owned(),
493 });
494 }
495 [factory] => *factory,
496 _ => {
497 return invalid(format!(
498 "multiple statically linked factories declare package `{}`",
499 expected.package_id()
500 ));
501 }
502 };
503 let generation = factory.instantiate(NativePluginFactoryContext::from_plan(
504 expected,
505 self.resources.for_instance(expected.instance_key()),
506 ))?;
507 Ok(PreparedNativePlugin::with_endpoint_set_lifecycle(
508 generation.endpoints.clone(),
509 generation.lifecycle(),
510 ))
511 }
512}
513
514fn prepare_bindings(
515 plan: &ResolvedAppPlan,
516 instances: &NativeInstances,
517) -> Result<NativeBindings, RuntimeFailure> {
518 let mut bindings = Vec::new();
519 let mut stream_bindings = Vec::new();
520 let mut event_bindings = Vec::new();
521 for binding in plan.capability_bindings() {
522 if !instances.contains_key(binding.provider_instance()) {
523 continue;
524 }
525 let provider = plan
526 .plugin_instance(binding.provider_instance())
527 .expect("validated binding provider should exist");
528 let descriptor = provider
529 .provided_capabilities()
530 .iter()
531 .find(|descriptor| descriptor.capability_id() == binding.capability_id())
532 .expect("validated binding descriptor should exist");
533 if !descriptor.request_operations().is_empty() {
534 let endpoint = instances
535 .get(binding.provider_instance())
536 .and_then(|instance| {
537 instance.endpoints.request().iter().find(|endpoint| {
538 endpoint.capability_id() == binding.capability_id()
539 && endpoint.descriptor_version() == binding.descriptor_version()
540 })
541 })
542 .cloned()
543 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
544 detail: format!(
545 "Capability `{}` version `{}` has no request endpoint on provider `{}`",
546 binding.capability_id(),
547 binding.descriptor_version(),
548 binding.provider_instance()
549 ),
550 })?;
551 bindings.push(
552 PreparedBinding::new(
553 binding.consumer_instance(),
554 binding.provider_instance(),
555 endpoint,
556 )
557 .with_requirement_id(binding.requirement_id()),
558 );
559 }
560 if !descriptor.stream_operations().is_empty() {
561 let endpoint = instances
562 .get(binding.provider_instance())
563 .and_then(|instance| {
564 instance.endpoints.stream().iter().find(|endpoint| {
565 endpoint.capability_id() == binding.capability_id()
566 && endpoint.descriptor_version() == binding.descriptor_version()
567 })
568 })
569 .cloned()
570 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
571 detail: format!(
572 "Capability `{}` version `{}` has no stream endpoint on provider `{}`",
573 binding.capability_id(),
574 binding.descriptor_version(),
575 binding.provider_instance()
576 ),
577 })?;
578 stream_bindings.push(
579 PreparedStreamBinding::new(
580 binding.consumer_instance(),
581 binding.provider_instance(),
582 endpoint,
583 )
584 .with_requirement_id(binding.requirement_id()),
585 );
586 }
587 if !descriptor.event_operations().is_empty() {
588 let endpoint = instances
589 .get(binding.provider_instance())
590 .and_then(|instance| {
591 instance.endpoints.event().iter().find(|endpoint| {
592 endpoint.capability_id() == binding.capability_id()
593 && endpoint.descriptor_version() == binding.descriptor_version()
594 })
595 })
596 .cloned()
597 .ok_or_else(|| RuntimeFailure::InvalidResolvedPlan {
598 detail: format!(
599 "Capability `{}` version `{}` has no Event endpoint on provider `{}`",
600 binding.capability_id(),
601 binding.descriptor_version(),
602 binding.provider_instance()
603 ),
604 })?;
605 event_bindings.push(
606 PreparedEventBinding::new(
607 binding.consumer_instance(),
608 binding.provider_instance(),
609 endpoint,
610 )
611 .with_requirement_id(binding.requirement_id()),
612 );
613 }
614 }
615 Ok((bindings, stream_bindings, event_bindings))
616}
617
618fn invalid<T>(detail: String) -> Result<T, RuntimeFailure> {
619 Err(RuntimeFailure::InvalidResolvedPlan { detail })
620}