This commit represents a major refactoring of our Rhai scripting system, transforming it from a factory-based approach to a more robust system-based architecture with improved hot reloading capabilities. Key Changes: - Renamed package from rhai_factory to rhai_system to better reflect its purpose - Renamed system_factory.rs to factory.rs for consistency and clarity - Implemented support for multiple script files in hot reloading - Added cross-script function calls, allowing functions in one script to call functions in another - Improved file watching to monitor all script files for changes - Enhanced error handling for script compilation failures - Simplified the API with a cleaner create_hot_reloadable_system function - Removed unused modules (error.rs, factory.rs, hot_reload_old.rs, module_cache.rs, relative_resolver.rs) - Updated all tests to work with the new architecture The new architecture: - Uses a System struct that holds references to script paths and provides a clean API - Compiles and merges multiple Rhai script files into a single AST - Automatically detects changes to any script file and recompiles them - Maintains thread safety with proper synchronization primitives - Provides better error messages when scripts fail to compile This refactoring aligns with our BasePathModuleResolver approach for module imports, making the resolution process more predictable and consistent. The hot reload example has been updated to demonstrate the new capabilities, showing how to: 1. Load and execute multiple script files 2. Watch for changes to these files 3. Automatically reload scripts when they change 4. Call functions across different script files All tests are passing, and the example demonstrates the improved functionality.
702 lines
19 KiB
Markdown
702 lines
19 KiB
Markdown
# Rhai Factory Project Structure
|
|
|
|
This document outlines the structure and content of the implementation files for the Rhai Factory project.
|
|
|
|
## Directory Structure
|
|
|
|
```
|
|
rhai_factory/
|
|
├── Cargo.toml
|
|
├── src/
|
|
│ ├── lib.rs
|
|
│ ├── factory.rs
|
|
│ ├── error.rs
|
|
│ └── module_cache.rs
|
|
└── tests/
|
|
├── common/
|
|
│ └── mod.rs
|
|
├── integration_tests.rs
|
|
└── rhai_scripts/
|
|
├── main.rhai
|
|
├── module1.rhai
|
|
└── module2.rhai
|
|
```
|
|
|
|
## File Contents
|
|
|
|
### Cargo.toml
|
|
|
|
```toml
|
|
[package]
|
|
name = "rhai_factory"
|
|
version = "0.1.0"
|
|
edition = "2021"
|
|
authors = ["Your Name <your.email@example.com>"]
|
|
description = "A thread-safe factory for creating and managing Rhai script engines"
|
|
repository = "https://github.com/yourusername/rhai_factory"
|
|
license = "MIT"
|
|
readme = "README.md"
|
|
|
|
[dependencies]
|
|
rhai = { version = "1.15.0", features = ["sync"] }
|
|
thiserror = "1.0"
|
|
log = "0.4"
|
|
|
|
[dev-dependencies]
|
|
tokio = { version = "1.28", features = ["full"] }
|
|
tempfile = "3.5"
|
|
```
|
|
|
|
### src/lib.rs
|
|
|
|
```rust
|
|
//! A thread-safe factory for creating and managing Rhai script engines.
|
|
//!
|
|
//! This crate provides a factory for creating thread-safe Rhai engines with
|
|
//! pre-compiled scripts. It handles module imports, provides detailed error
|
|
//! information, and ensures thread safety.
|
|
|
|
mod factory;
|
|
mod error;
|
|
mod module_cache;
|
|
|
|
pub use factory::RhaiFactory;
|
|
pub use error::RhaiFactoryError;
|
|
|
|
/// Re-export commonly used Rhai types for convenience
|
|
pub use rhai::{Engine, AST, Scope, Module};
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::path::Path;
|
|
|
|
// Test fixture for common setup
|
|
struct TestFixture {
|
|
factory: RhaiFactory,
|
|
}
|
|
|
|
impl TestFixture {
|
|
fn new() -> Self {
|
|
Self {
|
|
factory: RhaiFactory::new(),
|
|
}
|
|
}
|
|
|
|
fn with_caching() -> Self {
|
|
Self {
|
|
factory: RhaiFactory::with_caching(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn engine_can_evaluate_simple_expressions() {
|
|
let fixture = TestFixture::new();
|
|
let engine = fixture.factory.create_engine();
|
|
|
|
let result: i64 = engine.eval("40 + 2").unwrap();
|
|
assert_eq!(result, 42);
|
|
}
|
|
|
|
#[test]
|
|
fn factory_creates_thread_safe_engine() {
|
|
let fixture = TestFixture::new();
|
|
let engine = fixture.factory.create_engine();
|
|
|
|
// This test verifies that the engine can be sent between threads
|
|
std::thread::spawn(move || {
|
|
let result: i64 = engine.eval("40 + 2").unwrap();
|
|
assert_eq!(result, 42);
|
|
}).join().unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn module_cache_improves_performance() {
|
|
let fixture_no_cache = TestFixture::new();
|
|
let fixture_with_cache = TestFixture::with_caching();
|
|
|
|
// First compilation without cache
|
|
let start = std::time::Instant::now();
|
|
let _ = fixture_no_cache.factory.compile_modules(
|
|
&[Path::new("tests/rhai_scripts/main.rhai")],
|
|
Some(Path::new(".")),
|
|
).unwrap();
|
|
let no_cache_time = start.elapsed();
|
|
|
|
// First compilation with cache
|
|
let start = std::time::Instant::now();
|
|
let _ = fixture_with_cache.factory.compile_modules(
|
|
&[Path::new("tests/rhai_scripts/main.rhai")],
|
|
Some(Path::new(".")),
|
|
).unwrap();
|
|
let first_cache_time = start.elapsed();
|
|
|
|
// Second compilation with cache should be faster
|
|
let start = std::time::Instant::now();
|
|
let _ = fixture_with_cache.factory.compile_modules(
|
|
&[Path::new("tests/rhai_scripts/main.rhai")],
|
|
Some(Path::new(".")),
|
|
).unwrap();
|
|
let second_cache_time = start.elapsed();
|
|
|
|
// The second compilation with cache should be faster than the first
|
|
assert!(second_cache_time < first_cache_time);
|
|
}
|
|
}
|
|
```
|
|
|
|
### src/factory.rs
|
|
|
|
```rust
|
|
//! Implementation of the RhaiFactory.
|
|
|
|
use rhai::{Engine, AST, Scope, Module, EvalAltResult};
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::Arc;
|
|
use crate::error::RhaiFactoryError;
|
|
use crate::module_cache::ModuleCache;
|
|
|
|
/// A factory for creating thread-safe Rhai engines with pre-compiled scripts.
|
|
pub struct RhaiFactory {
|
|
/// Optional module cache for improved performance
|
|
module_cache: Option<ModuleCache>,
|
|
}
|
|
|
|
impl RhaiFactory {
|
|
/// Create a new RhaiFactory with default settings.
|
|
pub fn new() -> Self {
|
|
Self {
|
|
module_cache: None,
|
|
}
|
|
}
|
|
|
|
/// Create a new RhaiFactory with module caching enabled.
|
|
pub fn with_caching() -> Self {
|
|
Self {
|
|
module_cache: Some(ModuleCache::new()),
|
|
}
|
|
}
|
|
|
|
/// Create a thread-safe Rhai engine.
|
|
pub fn create_engine(&self) -> Engine {
|
|
let mut engine = Engine::new();
|
|
|
|
// Configure the engine for thread safety
|
|
// The sync feature ensures the engine is Send + Sync
|
|
|
|
engine
|
|
}
|
|
|
|
/// Compile a list of Rhai modules into a self-contained AST.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `module_paths` - A list of paths to Rhai script modules
|
|
/// * `base_path` - An optional base path for resolving relative module paths
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A Result containing either the compiled AST or a RhaiFactoryError
|
|
pub fn compile_modules<P: AsRef<Path>>(&self, module_paths: &[P], base_path: Option<P>)
|
|
-> Result<AST, RhaiFactoryError> {
|
|
// Implementation details...
|
|
// 1. Create a new engine
|
|
// 2. Set up a file module resolver with the base path
|
|
// 3. Compile the main module
|
|
// 4. Compile into a self-contained AST to handle imports
|
|
// 5. Return the compiled AST
|
|
}
|
|
|
|
/// Create an engine with pre-compiled modules.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `module_paths` - A list of paths to Rhai script modules
|
|
/// * `base_path` - An optional base path for resolving relative module paths
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A Result containing either a tuple of (Engine, AST) or a RhaiFactoryError
|
|
pub fn create_engine_with_modules<P: AsRef<Path>>(&self, module_paths: &[P], base_path: Option<P>)
|
|
-> Result<(Engine, AST), RhaiFactoryError> {
|
|
// Implementation details...
|
|
// 1. Create a new engine
|
|
// 2. Compile the modules
|
|
// 3. Return the engine and compiled AST
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::path::Path;
|
|
|
|
#[test]
|
|
fn compile_modules_handles_single_module() {
|
|
let factory = RhaiFactory::new();
|
|
let result = factory.compile_modules(
|
|
&[Path::new("tests/rhai_scripts/module2.rhai")],
|
|
Some(Path::new(".")),
|
|
);
|
|
|
|
assert!(result.is_ok());
|
|
|
|
let ast = result.unwrap();
|
|
let engine = factory.create_engine();
|
|
|
|
// Verify the module was compiled correctly
|
|
let scope = Scope::new();
|
|
let result = engine.eval_ast_with_scope::<()>(&mut scope.clone(), &ast);
|
|
assert!(result.is_ok());
|
|
|
|
// Verify the function was defined
|
|
let result = engine.call_fn::<i64>(&mut scope, &ast, "multiply", (6, 7));
|
|
assert!(result.is_ok());
|
|
assert_eq!(result.unwrap(), 42);
|
|
}
|
|
|
|
#[test]
|
|
fn compile_modules_handles_module_imports() {
|
|
let factory = RhaiFactory::new();
|
|
let result = factory.compile_modules(
|
|
&[Path::new("tests/rhai_scripts/main.rhai")],
|
|
Some(Path::new(".")),
|
|
);
|
|
|
|
assert!(result.is_ok());
|
|
|
|
let ast = result.unwrap();
|
|
let engine = factory.create_engine();
|
|
let result: i64 = engine.eval_ast(&ast).unwrap();
|
|
|
|
assert_eq!(result, 42);
|
|
}
|
|
|
|
#[test]
|
|
fn create_engine_with_modules_returns_usable_engine_and_ast() {
|
|
let factory = RhaiFactory::new();
|
|
let result = factory.create_engine_with_modules(
|
|
&[Path::new("tests/rhai_scripts/main.rhai")],
|
|
Some(Path::new(".")),
|
|
);
|
|
|
|
assert!(result.is_ok());
|
|
|
|
let (engine, ast) = result.unwrap();
|
|
let result: i64 = engine.eval_ast(&ast).unwrap();
|
|
|
|
assert_eq!(result, 42);
|
|
}
|
|
}
|
|
```
|
|
|
|
### src/error.rs
|
|
|
|
```rust
|
|
//! Error types for the RhaiFactory.
|
|
|
|
use std::error::Error;
|
|
use std::fmt;
|
|
use std::path::PathBuf;
|
|
|
|
/// Error type for RhaiFactory operations.
|
|
#[derive(Debug)]
|
|
pub struct RhaiFactoryError {
|
|
/// Path to the module that caused the error, if any
|
|
module_path: Option<PathBuf>,
|
|
/// Error message
|
|
message: String,
|
|
/// Source error, if any
|
|
source: Option<Box<dyn Error + Send + Sync>>,
|
|
}
|
|
|
|
impl RhaiFactoryError {
|
|
/// Create a new RhaiFactoryError with the given message.
|
|
pub fn new(message: impl Into<String>) -> Self {
|
|
Self {
|
|
module_path: None,
|
|
message: message.into(),
|
|
source: None,
|
|
}
|
|
}
|
|
|
|
/// Add a module path to the error.
|
|
pub fn with_module(mut self, module_path: impl Into<PathBuf>) -> Self {
|
|
self.module_path = Some(module_path.into());
|
|
self
|
|
}
|
|
|
|
/// Add a source error to the error.
|
|
pub fn with_source(mut self, source: impl Error + Send + Sync + 'static) -> Self {
|
|
self.source = Some(Box::new(source));
|
|
self
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for RhaiFactoryError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
if let Some(ref path) = self.module_path {
|
|
write!(f, "Error in module '{}': {}", path.display(), self.message)
|
|
} else {
|
|
write!(f, "{}", self.message)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Error for RhaiFactoryError {
|
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
|
self.source.as_ref().map(|s| s.as_ref() as &(dyn Error + 'static))
|
|
}
|
|
}
|
|
|
|
/// Convert Rhai's EvalAltResult to RhaiFactoryError.
|
|
impl From<Box<rhai::EvalAltResult>> for RhaiFactoryError {
|
|
fn from(err: Box<rhai::EvalAltResult>) -> Self {
|
|
RhaiFactoryError::new(format!("Rhai evaluation error: {}", err))
|
|
.with_source(RhaiEvalError(err))
|
|
}
|
|
}
|
|
|
|
/// Wrapper for Rhai's EvalAltResult to implement Error.
|
|
#[derive(Debug)]
|
|
struct RhaiEvalError(Box<rhai::EvalAltResult>);
|
|
|
|
impl fmt::Display for RhaiEvalError {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", self.0)
|
|
}
|
|
}
|
|
|
|
impl Error for RhaiEvalError {}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::io::{Error as IoError, ErrorKind};
|
|
|
|
#[test]
|
|
fn error_displays_module_path_when_available() {
|
|
let error = RhaiFactoryError::new("test error")
|
|
.with_module("test/path.rhai");
|
|
|
|
assert_eq!(
|
|
format!("{}", error),
|
|
"Error in module 'test/path.rhai': test error"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn error_displays_message_without_module_path() {
|
|
let error = RhaiFactoryError::new("test error");
|
|
|
|
assert_eq!(format!("{}", error), "test error");
|
|
}
|
|
|
|
#[test]
|
|
fn error_preserves_source_error() {
|
|
let io_error = IoError::new(ErrorKind::NotFound, "file not found");
|
|
let error = RhaiFactoryError::new("test error")
|
|
.with_source(io_error);
|
|
|
|
assert!(error.source().is_some());
|
|
assert_eq!(
|
|
error.source().unwrap().to_string(),
|
|
"file not found"
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
### src/module_cache.rs
|
|
|
|
```rust
|
|
//! Module caching for improved performance.
|
|
|
|
use std::collections::HashMap;
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::{Arc, Mutex};
|
|
use rhai::AST;
|
|
|
|
/// A cache for compiled Rhai modules.
|
|
pub struct ModuleCache {
|
|
/// Map of module paths to compiled ASTs
|
|
cache: Arc<Mutex<HashMap<PathBuf, Arc<AST>>>>,
|
|
}
|
|
|
|
impl ModuleCache {
|
|
/// Create a new empty module cache.
|
|
pub fn new() -> Self {
|
|
Self {
|
|
cache: Arc::new(Mutex::new(HashMap::new())),
|
|
}
|
|
}
|
|
|
|
/// Get a cached AST for the given module path, if available.
|
|
pub fn get<P: AsRef<Path>>(&self, path: P) -> Option<Arc<AST>> {
|
|
let path = path.as_ref().to_path_buf();
|
|
let cache = self.cache.lock().unwrap();
|
|
cache.get(&path).cloned()
|
|
}
|
|
|
|
/// Store an AST in the cache for the given module path.
|
|
pub fn put<P: AsRef<Path>>(&self, path: P, ast: AST) -> Arc<AST> {
|
|
let path = path.as_ref().to_path_buf();
|
|
let ast = Arc::new(ast);
|
|
let mut cache = self.cache.lock().unwrap();
|
|
cache.insert(path, ast.clone());
|
|
ast
|
|
}
|
|
|
|
/// Clear the cache.
|
|
pub fn clear(&self) {
|
|
let mut cache = self.cache.lock().unwrap();
|
|
cache.clear();
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use rhai::{Engine, Scope};
|
|
|
|
#[test]
|
|
fn cache_stores_and_retrieves_ast() {
|
|
let cache = ModuleCache::new();
|
|
let engine = Engine::new();
|
|
let ast = engine.compile("40 + 2").unwrap();
|
|
let path = PathBuf::from("test.rhai");
|
|
|
|
// Store the AST in the cache
|
|
let cached_ast = cache.put(&path, ast);
|
|
|
|
// Retrieve the AST from the cache
|
|
let retrieved_ast = cache.get(&path);
|
|
|
|
assert!(retrieved_ast.is_some());
|
|
|
|
// Verify the retrieved AST works correctly
|
|
let retrieved_ast = retrieved_ast.unwrap();
|
|
let result: i64 = engine.eval_ast(&retrieved_ast).unwrap();
|
|
assert_eq!(result, 42);
|
|
}
|
|
|
|
#[test]
|
|
fn cache_returns_none_for_missing_ast() {
|
|
let cache = ModuleCache::new();
|
|
let path = PathBuf::from("nonexistent.rhai");
|
|
|
|
let retrieved_ast = cache.get(&path);
|
|
|
|
assert!(retrieved_ast.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn cache_clear_removes_all_entries() {
|
|
let cache = ModuleCache::new();
|
|
let engine = Engine::new();
|
|
|
|
// Add multiple ASTs to the cache
|
|
let ast1 = engine.compile("40 + 2").unwrap();
|
|
let ast2 = engine.compile("50 + 3").unwrap();
|
|
|
|
cache.put("test1.rhai", ast1);
|
|
cache.put("test2.rhai", ast2);
|
|
|
|
// Verify the ASTs are in the cache
|
|
assert!(cache.get("test1.rhai").is_some());
|
|
assert!(cache.get("test2.rhai").is_some());
|
|
|
|
// Clear the cache
|
|
cache.clear();
|
|
|
|
// Verify the ASTs are no longer in the cache
|
|
assert!(cache.get("test1.rhai").is_none());
|
|
assert!(cache.get("test2.rhai").is_none());
|
|
}
|
|
}
|
|
```
|
|
|
|
### tests/common/mod.rs
|
|
|
|
```rust
|
|
//! Common utilities for integration tests.
|
|
|
|
use rhai_factory::RhaiFactory;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
/// Test fixture for integration tests.
|
|
pub struct TestFixture {
|
|
pub factory: RhaiFactory,
|
|
pub scripts_dir: PathBuf,
|
|
}
|
|
|
|
impl TestFixture {
|
|
/// Create a new test fixture.
|
|
pub fn new() -> Self {
|
|
let factory = RhaiFactory::new();
|
|
let scripts_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
.join("tests")
|
|
.join("rhai_scripts");
|
|
|
|
Self {
|
|
factory,
|
|
scripts_dir,
|
|
}
|
|
}
|
|
|
|
/// Get the path to a test script.
|
|
pub fn script_path(&self, name: &str) -> PathBuf {
|
|
self.scripts_dir.join(name)
|
|
}
|
|
}
|
|
```
|
|
|
|
### tests/integration_tests.rs
|
|
|
|
```rust
|
|
//! Integration tests for the RhaiFactory.
|
|
|
|
mod common;
|
|
|
|
use common::TestFixture;
|
|
use rhai_factory::{RhaiFactory, RhaiFactoryError};
|
|
use std::path::Path;
|
|
|
|
#[test]
|
|
fn factory_compiles_and_runs_scripts() {
|
|
let fixture = TestFixture::new();
|
|
|
|
// Compile the main script
|
|
let result = fixture.factory.compile_modules(
|
|
&[fixture.script_path("main.rhai")],
|
|
Some(&fixture.scripts_dir),
|
|
);
|
|
|
|
assert!(result.is_ok());
|
|
|
|
// Run the compiled script
|
|
let ast = result.unwrap();
|
|
let engine = fixture.factory.create_engine();
|
|
let result: i64 = engine.eval_ast(&ast).unwrap();
|
|
|
|
assert_eq!(result, 42);
|
|
}
|
|
|
|
#[test]
|
|
fn factory_handles_recursive_imports() {
|
|
let fixture = TestFixture::new();
|
|
|
|
// Compile the main script which imports module1, which imports module2
|
|
let result = fixture.factory.compile_modules(
|
|
&[fixture.script_path("main.rhai")],
|
|
Some(&fixture.scripts_dir),
|
|
);
|
|
|
|
assert!(result.is_ok());
|
|
|
|
// Run the compiled script
|
|
let ast = result.unwrap();
|
|
let engine = fixture.factory.create_engine();
|
|
let result: i64 = engine.eval_ast(&ast).unwrap();
|
|
|
|
assert_eq!(result, 42);
|
|
}
|
|
|
|
#[test]
|
|
fn factory_provides_detailed_error_for_missing_module() {
|
|
let fixture = TestFixture::new();
|
|
|
|
// Try to compile a non-existent script
|
|
let result = fixture.factory.compile_modules(
|
|
&[fixture.script_path("non_existent.rhai")],
|
|
Some(&fixture.scripts_dir),
|
|
);
|
|
|
|
assert!(result.is_err());
|
|
|
|
// Verify the error contains the module path
|
|
let err = result.unwrap_err();
|
|
assert!(format!("{}", err).contains("non_existent.rhai"));
|
|
}
|
|
|
|
#[test]
|
|
fn factory_creates_thread_safe_engine_and_ast() {
|
|
let fixture = TestFixture::new();
|
|
|
|
// Compile the main script
|
|
let result = fixture.factory.compile_modules(
|
|
&[fixture.script_path("main.rhai")],
|
|
Some(&fixture.scripts_dir),
|
|
);
|
|
|
|
assert!(result.is_ok());
|
|
|
|
let ast = result.unwrap();
|
|
let engine = fixture.factory.create_engine();
|
|
|
|
// Verify the engine and AST can be sent to another thread
|
|
let handle = std::thread::spawn(move || {
|
|
let result: i64 = engine.eval_ast(&ast).unwrap();
|
|
result
|
|
});
|
|
|
|
let result = handle.join().unwrap();
|
|
assert_eq!(result, 42);
|
|
}
|
|
```
|
|
|
|
### tests/rhai_scripts/main.rhai
|
|
|
|
```rhai
|
|
// Import the module1 module
|
|
import "module1" as m1;
|
|
|
|
// Call a function from the imported module
|
|
let result = m1::add(40, 2);
|
|
|
|
// Return the result
|
|
result
|
|
```
|
|
|
|
### tests/rhai_scripts/module1.rhai
|
|
|
|
```rhai
|
|
// Import the module2 module
|
|
import "module2" as m2;
|
|
|
|
// Define a function that uses a function from module2
|
|
fn add(a, b) {
|
|
// Call the multiply function from module2
|
|
let product = m2::multiply(a, 1);
|
|
|
|
// Add b to the product
|
|
product + b
|
|
}
|
|
```
|
|
|
|
### tests/rhai_scripts/module2.rhai
|
|
|
|
```rhai
|
|
// Define a function that multiplies two numbers
|
|
fn multiply(a, b) {
|
|
a * b
|
|
}
|
|
```
|
|
|
|
## Implementation Notes
|
|
|
|
1. The `sync` feature of Rhai is used to ensure thread safety
|
|
2. Module compilation uses the `compile_into_self_contained` method to handle imports
|
|
3. Error handling provides detailed information about which module failed to import and why
|
|
4. Module caching is optional but can improve performance when repeatedly using the same modules
|
|
5. Tests follow Rust's standard approach with unit tests in each module and integration tests in the tests directory
|
|
|
|
## Next Steps
|
|
|
|
To implement this project:
|
|
|
|
1. Create the directory structure as outlined above
|
|
2. Create the implementation files with the provided content
|
|
3. Run the tests to verify that everything works as expected
|
|
4. Add additional features or optimizations as needed |