1use std::path::PathBuf;
4use std::sync::atomic::{AtomicU64, Ordering};
5use std::sync::Arc;
6use std::time::Instant;
7
8use parking_lot::RwLock;
9
10use fusabi_host::{Engine, EngineConfig, Value};
11
12use crate::error::{Error, Result};
13use crate::lifecycle::LifecycleState;
14use crate::manifest::Manifest;
15
16static NEXT_PLUGIN_ID: AtomicU64 = AtomicU64::new(1);
17
18#[derive(Debug, Clone)]
20pub struct PluginInfo {
21 pub id: u64,
23 pub name: String,
25 pub version: String,
27 pub manifest_path: Option<PathBuf>,
29 pub entry_path: Option<PathBuf>,
31 pub loaded_at: Instant,
33 pub last_reload: Option<Instant>,
35 pub reload_count: u64,
37 pub invocation_count: u64,
39 pub state: LifecycleState,
41}
42
43impl PluginInfo {
44 fn new(id: u64, manifest: &Manifest) -> Self {
46 Self {
47 id,
48 name: manifest.name.clone(),
49 version: manifest.version.clone(),
50 manifest_path: None,
51 entry_path: None,
52 loaded_at: Instant::now(),
53 last_reload: None,
54 reload_count: 0,
55 invocation_count: 0,
56 state: LifecycleState::Created,
57 }
58 }
59}
60
61struct PluginInner {
63 manifest: Manifest,
64 info: PluginInfo,
65 engine: Option<Engine>,
66 bytecode: Option<Vec<u8>>,
67}
68
69pub struct Plugin {
71 inner: RwLock<PluginInner>,
72}
73
74impl Plugin {
75 pub fn new(manifest: Manifest) -> Self {
77 let id = NEXT_PLUGIN_ID.fetch_add(1, Ordering::Relaxed);
78 let info = PluginInfo::new(id, &manifest);
79
80 Self {
81 inner: RwLock::new(PluginInner {
82 manifest,
83 info,
84 engine: None,
85 bytecode: None,
86 }),
87 }
88 }
89
90 pub fn id(&self) -> u64 {
92 self.inner.read().info.id
93 }
94
95 pub fn name(&self) -> String {
97 self.inner.read().manifest.name.clone()
98 }
99
100 pub fn version(&self) -> String {
102 self.inner.read().manifest.version.clone()
103 }
104
105 pub fn manifest(&self) -> Manifest {
107 self.inner.read().manifest.clone()
108 }
109
110 pub fn info(&self) -> PluginInfo {
112 self.inner.read().info.clone()
113 }
114
115 pub fn state(&self) -> LifecycleState {
117 self.inner.read().info.state
118 }
119
120 pub fn set_state(&self, state: LifecycleState) {
122 self.inner.write().info.state = state;
123 }
124
125 pub fn initialize(&self, engine_config: EngineConfig) -> Result<()> {
127 let mut inner = self.inner.write();
128
129 if inner.info.state != LifecycleState::Created
131 && inner.info.state != LifecycleState::Stopped
132 {
133 return Err(Error::invalid_state(
134 "Created or Stopped",
135 format!("{:?}", inner.info.state),
136 ));
137 }
138
139 let caps = &engine_config.capabilities;
141 for required_cap in &inner.manifest.capabilities {
142 let cap = fusabi_host::Capability::from_name(required_cap)
143 .ok_or_else(|| Error::invalid_manifest(format!("unknown capability: {}", required_cap)))?;
144
145 if !caps.has(cap) {
146 return Err(Error::MissingCapability(required_cap.clone()));
147 }
148 }
149
150 let engine = Engine::new(engine_config)
152 .map_err(|e| Error::init_failed(e.to_string()))?;
153
154 inner.engine = Some(engine);
155 inner.info.state = LifecycleState::Initialized;
156
157 Ok(())
158 }
159
160 pub fn start(&self) -> Result<()> {
162 let mut inner = self.inner.write();
163
164 if inner.info.state != LifecycleState::Initialized {
165 return Err(Error::invalid_state(
166 "Initialized",
167 format!("{:?}", inner.info.state),
168 ));
169 }
170
171 if inner.manifest.exports.contains(&"init".to_string()) {
173 if let Some(ref engine) = inner.engine {
174 engine
175 .execute("init()")
176 .map_err(|e| Error::init_failed(e.to_string()))?;
177 }
178 }
179
180 inner.info.state = LifecycleState::Running;
181 Ok(())
182 }
183
184 pub fn stop(&self) -> Result<()> {
186 let mut inner = self.inner.write();
187
188 if inner.info.state != LifecycleState::Running {
189 return Err(Error::invalid_state(
190 "Running",
191 format!("{:?}", inner.info.state),
192 ));
193 }
194
195 if inner.manifest.exports.contains(&"cleanup".to_string()) {
197 if let Some(ref engine) = inner.engine {
198 let _ = engine.execute("cleanup()");
199 }
200 }
201
202 inner.info.state = LifecycleState::Stopped;
203 Ok(())
204 }
205
206 pub fn unload(&self) -> Result<()> {
208 let mut inner = self.inner.write();
209
210 if inner.info.state == LifecycleState::Running {
212 if inner.manifest.exports.contains(&"cleanup".to_string()) {
213 if let Some(ref engine) = inner.engine {
214 let _ = engine.execute("cleanup()");
215 }
216 }
217 }
218
219 inner.engine = None;
220 inner.bytecode = None;
221 inner.info.state = LifecycleState::Unloaded;
222
223 Ok(())
224 }
225
226 pub fn call(&self, function: &str, args: &[Value]) -> Result<Value> {
228 let mut inner = self.inner.write();
229
230 if inner.info.state != LifecycleState::Running {
232 return Err(Error::invalid_state(
233 "Running",
234 format!("{:?}", inner.info.state),
235 ));
236 }
237
238 if !inner.manifest.exports.contains(&function.to_string())
240 && function != "main"
241 {
242 return Err(Error::FunctionNotFound(function.to_string()));
243 }
244
245 let call_expr = if args.is_empty() {
247 format!("{}()", function)
248 } else {
249 let args_str: Vec<String> = args.iter().map(|a| a.to_string()).collect();
251 format!("{}({})", function, args_str.join(", "))
252 };
253
254 inner.info.invocation_count += 1;
256
257 let engine = inner
259 .engine
260 .as_ref()
261 .ok_or_else(|| Error::invalid_state("engine initialized", "no engine"))?;
262
263 engine
264 .execute(&call_expr)
265 .map_err(|e| Error::execution_failed(e.to_string()))
266 }
267
268 pub fn reload(&self) -> Result<()> {
270 let mut inner = self.inner.write();
271
272 if inner.info.state == LifecycleState::Unloaded {
274 return Err(Error::PluginUnloaded);
275 }
276
277 let was_running = inner.info.state == LifecycleState::Running;
278
279 if was_running {
281 if inner.manifest.exports.contains(&"cleanup".to_string()) {
282 if let Some(ref engine) = inner.engine {
283 let _ = engine.execute("cleanup()");
284 }
285 }
286 }
287
288 inner.info.state = LifecycleState::Initialized;
290 inner.info.last_reload = Some(Instant::now());
291 inner.info.reload_count += 1;
292
293 if was_running {
295 inner.info.state = LifecycleState::Running;
296 if inner.manifest.exports.contains(&"init".to_string()) {
297 if let Some(ref engine) = inner.engine {
298 engine
299 .execute("init()")
300 .map_err(|e| Error::ReloadFailed(e.to_string()))?;
301 }
302 }
303 }
304
305 Ok(())
306 }
307
308 pub fn has_export(&self, name: &str) -> bool {
310 self.inner.read().manifest.exports.contains(&name.to_string())
311 }
312
313 pub fn exports(&self) -> Vec<String> {
315 self.inner.read().manifest.exports.clone()
316 }
317
318 pub fn requires_capability(&self, cap: &str) -> bool {
320 self.inner.read().manifest.requires_capability(cap)
321 }
322
323 pub fn set_bytecode(&self, bytecode: Vec<u8>) {
325 self.inner.write().bytecode = Some(bytecode);
326 }
327
328 pub fn bytecode(&self) -> Option<Vec<u8>> {
330 self.inner.read().bytecode.clone()
331 }
332}
333
334impl std::fmt::Debug for Plugin {
335 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
336 let inner = self.inner.read();
337 f.debug_struct("Plugin")
338 .field("id", &inner.info.id)
339 .field("name", &inner.manifest.name)
340 .field("version", &inner.manifest.version)
341 .field("state", &inner.info.state)
342 .finish()
343 }
344}
345
346#[derive(Clone)]
348pub struct PluginHandle {
349 plugin: Arc<Plugin>,
350}
351
352impl PluginHandle {
353 pub fn new(plugin: Plugin) -> Self {
355 Self {
356 plugin: Arc::new(plugin),
357 }
358 }
359
360 pub fn id(&self) -> u64 {
362 self.plugin.id()
363 }
364
365 pub fn name(&self) -> String {
367 self.plugin.name()
368 }
369
370 pub fn state(&self) -> LifecycleState {
372 self.plugin.state()
373 }
374
375 pub fn call(&self, function: &str, args: &[Value]) -> Result<Value> {
377 self.plugin.call(function, args)
378 }
379
380 pub fn info(&self) -> PluginInfo {
382 self.plugin.info()
383 }
384
385 pub fn has_export(&self, name: &str) -> bool {
387 self.plugin.has_export(name)
388 }
389
390 pub fn inner(&self) -> &Plugin {
392 &self.plugin
393 }
394}
395
396impl std::fmt::Debug for PluginHandle {
397 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
398 f.debug_struct("PluginHandle")
399 .field("id", &self.id())
400 .field("name", &self.name())
401 .field("state", &self.state())
402 .finish()
403 }
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409 use crate::manifest::ManifestBuilder;
410
411 fn create_test_manifest() -> Manifest {
412 ManifestBuilder::new("test-plugin", "1.0.0")
413 .source("test.fsx")
414 .export("main")
415 .export("init")
416 .build_unchecked()
417 }
418
419 #[test]
420 fn test_plugin_creation() {
421 let manifest = create_test_manifest();
422 let plugin = Plugin::new(manifest);
423
424 assert!(plugin.id() > 0);
425 assert_eq!(plugin.name(), "test-plugin");
426 assert_eq!(plugin.version(), "1.0.0");
427 assert_eq!(plugin.state(), LifecycleState::Created);
428 }
429
430 #[test]
431 fn test_plugin_lifecycle() {
432 let manifest = create_test_manifest();
433 let plugin = Plugin::new(manifest);
434
435 plugin
437 .initialize(EngineConfig::default())
438 .unwrap();
439 assert_eq!(plugin.state(), LifecycleState::Initialized);
440
441 plugin.start().unwrap();
443 assert_eq!(plugin.state(), LifecycleState::Running);
444
445 plugin.stop().unwrap();
447 assert_eq!(plugin.state(), LifecycleState::Stopped);
448
449 plugin.unload().unwrap();
451 assert_eq!(plugin.state(), LifecycleState::Unloaded);
452 }
453
454 #[test]
455 fn test_plugin_invalid_state_transitions() {
456 let manifest = create_test_manifest();
457 let plugin = Plugin::new(manifest);
458
459 assert!(plugin.start().is_err());
461
462 assert!(plugin.stop().is_err());
464
465 plugin.initialize(EngineConfig::default()).unwrap();
467
468 assert!(plugin.stop().is_err());
470 }
471
472 #[test]
473 fn test_plugin_capabilities() {
474 let manifest = ManifestBuilder::new("test", "1.0.0")
475 .source("test.fsx")
476 .capability("fs:read")
477 .build_unchecked();
478
479 let plugin = Plugin::new(manifest);
480
481 let config = EngineConfig::default()
483 .with_capabilities(fusabi_host::Capabilities::none());
484
485 assert!(plugin.initialize(config).is_err());
486
487 let config = EngineConfig::default().with_capabilities(
489 fusabi_host::Capabilities::none().with(fusabi_host::Capability::FsRead),
490 );
491
492 assert!(plugin.initialize(config).is_ok());
493 }
494
495 #[test]
496 fn test_plugin_handle() {
497 let manifest = create_test_manifest();
498 let plugin = Plugin::new(manifest);
499 let handle = PluginHandle::new(plugin);
500
501 assert!(handle.id() > 0);
502 assert_eq!(handle.name(), "test-plugin");
503 assert!(handle.has_export("main"));
504
505 let handle2 = handle.clone();
507 assert_eq!(handle.id(), handle2.id());
508 }
509}