refactor: Overhaul Rhai scripting with multi-file hot reloading
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.
This commit is contained in:
parent
939b6b4e57
commit
372b7a2772
1023
rhai_system/Cargo.lock
generated
Normal file
1023
rhai_system/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
rhai_system/Cargo.toml
Normal file
21
rhai_system/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "rhai_system"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "A thread-safe system for creating and managing Rhai script engines with hot reloading support"
|
||||
|
||||
[dependencies]
|
||||
rhai = { version = "1.15.0", features = ["sync"] }
|
||||
thiserror = "1.0"
|
||||
log = "0.4"
|
||||
notify = "5.1.0"
|
||||
uuid = { version = "1.3.0", features = ["v4", "serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.28", features = ["full"] }
|
||||
tempfile = "3.5"
|
||||
rand = "0.8"
|
||||
|
||||
[[example]]
|
||||
name = "hot_reload"
|
||||
path = "examples/hot_reload/main.rs"
|
152
rhai_system/README.md
Normal file
152
rhai_system/README.md
Normal file
@ -0,0 +1,152 @@
|
||||
# Rhai System
|
||||
|
||||
A thread-safe system for creating and managing Rhai script engines with hot reloading support for multiple script files.
|
||||
|
||||
## Overview
|
||||
|
||||
Rhai System is a Rust module that simplifies working with the [Rhai scripting language](https://rhai.rs/) by providing a system for creating thread-safe Rhai engines with pre-compiled scripts. It supports hot reloading of multiple script files, ensuring your application always uses the latest version of your scripts without requiring a restart.
|
||||
|
||||
## Features
|
||||
|
||||
- **Thread Safety**: Uses Rhai's `sync` feature to ensure engines are `Send + Sync`
|
||||
- **Multiple Script Support**: Compiles and merges multiple Rhai script files into a single AST
|
||||
- **Hot Reload**: Automatically detects changes to script files and recompiles them without requiring application restart
|
||||
- **Cross-Script Function Calls**: Functions defined in one script can call functions defined in another script
|
||||
- **Detailed Error Handling**: Provides clear error messages when scripts fail to compile
|
||||
- **Performance Optimized**: Efficiently recompiles only the scripts that have changed
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```rust
|
||||
use rhai_system::create_hot_reloadable_system;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// Create a hot reloadable system with multiple script files
|
||||
let script_paths = vec![
|
||||
PathBuf::from("scripts/main.rhai"),
|
||||
PathBuf::from("scripts/utils.rhai"),
|
||||
];
|
||||
let system = create_hot_reloadable_system(&script_paths, None).unwrap();
|
||||
|
||||
// Call a function from the script
|
||||
let result: i64 = system.call_fn("add", (40, 2)).unwrap();
|
||||
assert_eq!(result, 42);
|
||||
|
||||
// The system will automatically reload scripts when they change
|
||||
```
|
||||
|
||||
### Watching for Changes
|
||||
|
||||
The system automatically sets up file watchers for all script files:
|
||||
|
||||
```rust
|
||||
use rhai_system::create_hot_reloadable_system;
|
||||
use std::path::PathBuf;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
// Create a hot reloadable system
|
||||
let script_paths = vec![PathBuf::from("scripts/main.rhai")];
|
||||
let system = create_hot_reloadable_system(&script_paths, None).unwrap();
|
||||
|
||||
// Start watching for changes to the script files
|
||||
system.watch();
|
||||
|
||||
// The system will now automatically reload scripts when they change
|
||||
// Your application can continue running and using the latest version of the scripts
|
||||
```
|
||||
|
||||
### Thread-Safe Usage
|
||||
|
||||
The system is designed to be thread-safe, allowing you to use it from multiple threads:
|
||||
|
||||
```rust
|
||||
use rhai_system::create_hot_reloadable_system;
|
||||
use std::path::PathBuf;
|
||||
use std::thread;
|
||||
use std::sync::Arc;
|
||||
|
||||
// Create a hot reloadable system
|
||||
let script_paths = vec![PathBuf::from("scripts/main.rhai")];
|
||||
let system = Arc::new(create_hot_reloadable_system(&script_paths, None).unwrap());
|
||||
|
||||
// Clone the system for use in another thread
|
||||
let system_clone = Arc::clone(&system);
|
||||
|
||||
// Start watching for changes in the main thread
|
||||
system.watch();
|
||||
|
||||
// Use the system in another thread
|
||||
let handle = thread::spawn(move || {
|
||||
// Create a thread-local clone of the system
|
||||
let thread_system = system_clone.clone_for_thread();
|
||||
|
||||
// Call functions from the script
|
||||
let result: i64 = thread_system.call_fn("add", (40, 2)).unwrap();
|
||||
assert_eq!(result, 42);
|
||||
});
|
||||
|
||||
handle.join().unwrap();
|
||||
```
|
||||
|
||||
## Module Resolution
|
||||
|
||||
Rhai System supports the BasePathModuleResolver approach for resolving module imports:
|
||||
|
||||
- Uses a single base path for resolving all module imports
|
||||
- Makes the resolution process more predictable and consistent
|
||||
- Simplifies the Rhai module import system by removing the complexity of relative path resolution
|
||||
|
||||
See the `examples/base_path_imports` directory for a comprehensive example of this approach.
|
||||
|
||||
## Error Handling
|
||||
|
||||
The system provides detailed error information when scripts fail to compile:
|
||||
|
||||
```rust
|
||||
use rhai_system::create_hot_reloadable_system;
|
||||
use std::path::PathBuf;
|
||||
|
||||
let script_paths = vec![PathBuf::from("non_existent.rhai")];
|
||||
let result = create_hot_reloadable_system(&script_paths, None);
|
||||
|
||||
match result {
|
||||
Err(err) => {
|
||||
// Error will contain:
|
||||
// - Which script failed to compile
|
||||
// - The reason for the failure
|
||||
println!("Error: {}", err);
|
||||
}
|
||||
Ok(_) => panic!("Expected an error"),
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
The project follows Rust's standard testing approach:
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
cargo test
|
||||
|
||||
# Run a specific test
|
||||
cargo test test_hot_reload_callback
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
rhai_system = { path = "path/to/rhai_system" }
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
See the `examples` directory for complete examples:
|
||||
|
||||
- `hot_reload`: Demonstrates hot reloading of multiple script files
|
||||
- `base_path_imports`: Demonstrates the BasePathModuleResolver approach for module imports
|
969
rhai_system/architecture.md
Normal file
969
rhai_system/architecture.md
Normal file
@ -0,0 +1,969 @@
|
||||
# Rhai Engine Factory Implementation Plan
|
||||
|
||||
Based on our exploration of the Rhai documentation and your requirements, I'll now outline a detailed plan for implementing the Rhai engine factory module.
|
||||
|
||||
## Overview
|
||||
|
||||
We'll create a Rust module called `rhai_factory` that provides a factory for creating thread-safe Rhai engines with pre-compiled scripts. The factory will:
|
||||
|
||||
1. Use the `sync` feature to ensure the engine is `Send + Sync`
|
||||
2. Compile Rhai modules into a self-contained AST for better performance
|
||||
3. Provide detailed error handling that shows which module failed to import and why
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[RhaiFactory] --> B[create_engine]
|
||||
A --> C[compile_modules]
|
||||
A --> D[create_engine_with_modules]
|
||||
|
||||
B --> E[Engine with sync feature]
|
||||
C --> F[Self-contained AST]
|
||||
D --> G[Engine with compiled modules]
|
||||
|
||||
H[FileModuleResolver] --> C
|
||||
I[Error Handling] --> C
|
||||
J[Module Cache] --> C
|
||||
```
|
||||
|
||||
## Component Details
|
||||
|
||||
### 1. RhaiFactory Module Structure
|
||||
|
||||
```
|
||||
rhai_factory/
|
||||
├── Cargo.toml # Dependencies including rhai with sync feature
|
||||
├── src/
|
||||
│ ├── lib.rs # Main module exports and unit tests
|
||||
│ ├── factory.rs # Factory implementation and unit tests
|
||||
│ ├── error.rs # Custom error types and unit tests
|
||||
│ └── module_cache.rs # Optional module caching and unit tests
|
||||
└── tests/
|
||||
├── common/ # Common test utilities
|
||||
│ └── mod.rs # Test fixtures and helpers
|
||||
├── integration_tests.rs # Integration tests
|
||||
└── rhai_scripts/ # Test Rhai scripts
|
||||
├── main.rhai
|
||||
├── module1.rhai
|
||||
└── module2.rhai
|
||||
```
|
||||
|
||||
### 2. Factory Implementation
|
||||
|
||||
The core factory will provide these main functions:
|
||||
|
||||
1. **create_engine()** - Creates a basic thread-safe Rhai engine with default configuration
|
||||
2. **compile_modules(modules_paths, base_path)** - Compiles a list of Rhai modules into a self-contained AST
|
||||
3. **create_engine_with_modules(modules_paths, base_path)** - Creates an engine with pre-compiled modules
|
||||
|
||||
### 3. Error Handling
|
||||
|
||||
We'll create a custom error type `RhaiFactoryError` that provides detailed information about:
|
||||
- Which module failed to import
|
||||
- The reason for the failure
|
||||
- The path that was attempted
|
||||
- Any nested module import failures
|
||||
|
||||
### 4. Module Caching (Optional Enhancement)
|
||||
|
||||
To improve performance when repeatedly using the same modules:
|
||||
- Implement a module cache that stores compiled ASTs
|
||||
- Use a hash of the module content as the cache key
|
||||
- Provide options to invalidate the cache when modules change
|
||||
|
||||
### 5. Thread Safety
|
||||
|
||||
We'll ensure thread safety by:
|
||||
- Using the `sync` feature of Rhai
|
||||
- Ensuring all our factory methods return thread-safe types
|
||||
- Using appropriate synchronization primitives for any shared state
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. **Setup Project Structure**
|
||||
- Create the directory structure
|
||||
- Set up Cargo.toml with appropriate dependencies
|
||||
- Create initial module files
|
||||
|
||||
2. **Implement Basic Factory**
|
||||
- Create the factory struct with configuration options
|
||||
- Implement `create_engine()` method
|
||||
- Add engine configuration options
|
||||
|
||||
3. **Implement Module Compilation**
|
||||
- Create the `compile_modules()` method
|
||||
- Implement module resolution logic
|
||||
- Handle recursive module imports
|
||||
|
||||
4. **Implement Error Handling**
|
||||
- Create custom error types
|
||||
- Implement detailed error reporting
|
||||
- Add context to error messages
|
||||
|
||||
5. **Implement Combined Factory Method**
|
||||
- Create `create_engine_with_modules()` method
|
||||
- Ensure proper error propagation
|
||||
- Add configuration options
|
||||
|
||||
6. **Write Tests**
|
||||
- Create test fixtures and helpers
|
||||
- Implement unit tests within each module
|
||||
- Create integration tests
|
||||
- Test thread safety
|
||||
|
||||
7. **Documentation**
|
||||
- Add comprehensive documentation
|
||||
- Include examples
|
||||
- Document thread safety guarantees
|
||||
|
||||
## Code Outline
|
||||
|
||||
Here's a sketch of the main components:
|
||||
|
||||
### lib.rs
|
||||
```rust
|
||||
mod factory;
|
||||
mod error;
|
||||
mod module_cache;
|
||||
|
||||
pub use factory::RhaiFactory;
|
||||
pub use error::RhaiFactoryError;
|
||||
|
||||
// Re-export commonly used Rhai types
|
||||
pub use rhai::{Engine, AST, Scope, Module};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// Unit tests for the library as a whole
|
||||
}
|
||||
```
|
||||
|
||||
### factory.rs
|
||||
```rust
|
||||
use rhai::{Engine, AST, Scope, Module};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use crate::error::RhaiFactoryError;
|
||||
|
||||
pub struct RhaiFactory {
|
||||
// Configuration options
|
||||
}
|
||||
|
||||
impl RhaiFactory {
|
||||
pub fn new() -> Self {
|
||||
// Initialize with default options
|
||||
}
|
||||
|
||||
pub fn create_engine(&self) -> Engine {
|
||||
// Create a thread-safe engine
|
||||
}
|
||||
|
||||
pub fn compile_modules<P: AsRef<Path>>(&self, module_paths: &[P], base_path: Option<P>)
|
||||
-> Result<AST, RhaiFactoryError> {
|
||||
// Compile modules into a self-contained AST
|
||||
}
|
||||
|
||||
pub fn create_engine_with_modules<P: AsRef<Path>>(&self, module_paths: &[P], base_path: Option<P>)
|
||||
-> Result<(Engine, AST), RhaiFactoryError> {
|
||||
// Create engine and compile modules
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// Unit tests for the factory implementation
|
||||
}
|
||||
```
|
||||
|
||||
### error.rs
|
||||
```rust
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RhaiFactoryError {
|
||||
module_path: Option<PathBuf>,
|
||||
message: String,
|
||||
source: Option<Box<dyn Error + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl RhaiFactoryError {
|
||||
pub fn new(message: impl Into<String>) -> Self {
|
||||
// Create a new error
|
||||
}
|
||||
|
||||
pub fn with_module(mut self, module_path: impl Into<PathBuf>) -> Self {
|
||||
// Add module path context
|
||||
}
|
||||
|
||||
pub fn with_source(mut self, source: impl Error + Send + Sync + 'static) -> Self {
|
||||
// Add source error
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RhaiFactoryError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Format error message with context
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for RhaiFactoryError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
// Return source error if any
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### 1. Unit Tests
|
||||
|
||||
We'll follow Rust's standard approach of including unit tests in each module:
|
||||
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// Test fixtures for common setup
|
||||
struct TestFixture {
|
||||
// Common test setup
|
||||
}
|
||||
|
||||
impl TestFixture {
|
||||
fn new() -> Self {
|
||||
// Initialize test fixture
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_specific_functionality() {
|
||||
// Test a specific function or behavior
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Key unit test areas:
|
||||
- Factory creation and configuration
|
||||
- Engine creation
|
||||
- Module compilation
|
||||
- Error handling
|
||||
- Module caching
|
||||
- Thread safety
|
||||
|
||||
### 2. Integration Tests
|
||||
|
||||
Integration tests will be placed in the `tests/` directory:
|
||||
|
||||
```
|
||||
tests/
|
||||
├── common/ # Common test utilities
|
||||
│ └── mod.rs # Test fixtures and helpers
|
||||
├── integration_tests.rs # Integration tests
|
||||
└── rhai_scripts/ # Test Rhai scripts
|
||||
```
|
||||
|
||||
The integration tests will focus on:
|
||||
- End-to-end functionality
|
||||
- Module imports and resolution
|
||||
- Thread safety in a real-world context
|
||||
- Error handling with real scripts
|
||||
|
||||
### 3. Test Fixtures and Helpers
|
||||
|
||||
We'll create test fixtures to simplify test setup and reduce code duplication:
|
||||
|
||||
```rust
|
||||
// In tests/common/mod.rs
|
||||
pub struct TestFixture {
|
||||
pub factory: RhaiFactory,
|
||||
pub scripts_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl TestFixture {
|
||||
pub fn new() -> Self {
|
||||
// Initialize test fixture
|
||||
}
|
||||
|
||||
pub fn script_path(&self, name: &str) -> PathBuf {
|
||||
// Get path to a test script
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Example Usage
|
||||
|
||||
We'll provide example usage in the README.md and documentation:
|
||||
|
||||
```rust
|
||||
// Create a factory
|
||||
let factory = RhaiFactory::new();
|
||||
|
||||
// Create a thread-safe engine
|
||||
let engine = factory.create_engine();
|
||||
|
||||
// Compile modules
|
||||
let ast = factory.compile_modules(&["main.rhai"], Some(Path::new("./scripts")))?;
|
||||
|
||||
// Use the engine and AST
|
||||
let result: i64 = engine.eval_ast(&ast)?;
|
||||
```
|
||||
|
||||
## Thread Safety Considerations
|
||||
|
||||
1. The Rhai engine with the `sync` feature is `Send + Sync`
|
||||
2. All factory methods will be thread-safe
|
||||
3. Any shared state will use appropriate synchronization primitives
|
||||
4. The compiled AST will be shareable between threads
|
||||
|
||||
## Hot Reload Feature
|
||||
|
||||
### Overview
|
||||
|
||||
The hot reload feature will allow the Rhai Factory to automatically detect changes to script files and recompile them without requiring application restart. This is particularly useful for development environments and systems where scripts control behavior that needs to be modified dynamically.
|
||||
|
||||
As described in the Rhai documentation (_archive/rhai_engine/rhaibook/patterns/hot-reload.md), hot reloading allows scripts to be modified dynamically without re-initializing the host system. The Rhai `Engine` is re-entrant, meaning it's decoupled from scripts, and a new script only needs to be recompiled and the new `AST` replaces the old for new behaviors to be active.
|
||||
|
||||
### Architecture Extension
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[RhaiFactory] --> B[HotReloadManager]
|
||||
B --> C[FileWatcher]
|
||||
B --> D[ScriptRegistry]
|
||||
|
||||
C --> E[File System Events]
|
||||
D --> F[Script Metadata]
|
||||
|
||||
B --> G[Reload Callbacks]
|
||||
G --> H[Script Consumers]
|
||||
|
||||
I[ModuleCache] --> B
|
||||
```
|
||||
|
||||
### Component Details
|
||||
|
||||
#### 1. Hot Reload Manager
|
||||
|
||||
The Hot Reload Manager will be responsible for:
|
||||
1. Tracking which scripts are being watched
|
||||
2. Detecting changes to script files
|
||||
3. Recompiling scripts when changes are detected
|
||||
4. Notifying consumers when scripts have been reloaded
|
||||
|
||||
This follows the pattern described in the Rhai hot reload documentation, where the system watches for script file changes and recompiles the scripts when changes are detected.
|
||||
|
||||
#### 2. File Watcher
|
||||
|
||||
The File Watcher will:
|
||||
1. Monitor the file system for changes to script files
|
||||
2. Notify the Hot Reload Manager when changes are detected
|
||||
3. Support watching individual files or directories
|
||||
|
||||
This component will implement the "watch for script file change" functionality mentioned in the hot reload documentation.
|
||||
|
||||
#### 3. Script Registry
|
||||
|
||||
The Script Registry will:
|
||||
1. Maintain metadata about watched scripts
|
||||
2. Track dependencies between scripts
|
||||
3. Determine which scripts need to be recompiled when a file changes
|
||||
|
||||
This is important for handling recursive imports and ensuring that all dependent scripts are recompiled when a dependency changes.
|
||||
|
||||
#### 4. Reload Callbacks
|
||||
|
||||
The system will provide:
|
||||
1. A callback mechanism for consumers to be notified when scripts are reloaded
|
||||
2. Options for synchronous or asynchronous notification
|
||||
|
||||
This follows the pattern in the hot reload documentation where a callback is provided to handle the reloaded script.
|
||||
|
||||
### Implementation Details
|
||||
|
||||
#### 1. Enhanced RhaiFactory API
|
||||
|
||||
We'll extend the RhaiFactory with new methods:
|
||||
|
||||
```rust
|
||||
impl RhaiFactory {
|
||||
// Existing methods...
|
||||
|
||||
/// Enable hot reloading for a compiled AST
|
||||
pub fn enable_hot_reload<P: AsRef<Path>>(&self,
|
||||
ast: Arc<RwLock<AST>>,
|
||||
module_paths: &[P],
|
||||
base_path: Option<P>,
|
||||
callback: Option<Box<dyn Fn() + Send + Sync>>
|
||||
) -> Result<HotReloadHandle, RhaiFactoryError>;
|
||||
|
||||
/// Disable hot reloading for a previously enabled AST
|
||||
pub fn disable_hot_reload(&self, handle: HotReloadHandle);
|
||||
|
||||
/// Check if any scripts have changed and trigger reloads if necessary
|
||||
pub fn check_for_changes(&self) -> Result<bool, RhaiFactoryError>;
|
||||
}
|
||||
```
|
||||
|
||||
These methods provide the core functionality needed for hot reloading, following the pattern described in the Rhai documentation.
|
||||
|
||||
#### 2. Hot Reload Handle
|
||||
|
||||
We'll create a handle type to manage hot reload sessions:
|
||||
|
||||
```rust
|
||||
/// Handle for a hot reload session
|
||||
pub struct HotReloadHandle {
|
||||
id: uuid::Uuid,
|
||||
// Other fields as needed
|
||||
}
|
||||
```
|
||||
|
||||
This handle will be used to identify and manage hot reload sessions.
|
||||
|
||||
#### 3. Thread-Safe AST Container
|
||||
|
||||
We'll create a thread-safe container for ASTs that can be updated when scripts change:
|
||||
|
||||
```rust
|
||||
/// Thread-safe container for an AST that can be hot reloaded
|
||||
pub struct HotReloadableAST {
|
||||
ast: Arc<RwLock<AST>>,
|
||||
factory: Arc<RhaiFactory>,
|
||||
handle: Option<HotReloadHandle>,
|
||||
}
|
||||
|
||||
impl HotReloadableAST {
|
||||
/// Create a new hot reloadable AST
|
||||
pub fn new(ast: AST, factory: Arc<RhaiFactory>) -> Self;
|
||||
|
||||
/// Enable hot reloading for this AST
|
||||
pub fn enable_hot_reload<P: AsRef<Path>>(
|
||||
&mut self,
|
||||
module_paths: &[P],
|
||||
base_path: Option<P>,
|
||||
callback: Option<Box<dyn Fn() + Send + Sync>>
|
||||
) -> Result<(), RhaiFactoryError>;
|
||||
|
||||
/// Disable hot reloading for this AST
|
||||
pub fn disable_hot_reload(&mut self);
|
||||
|
||||
/// Get a reference to the underlying AST
|
||||
pub fn ast(&self) -> &Arc<RwLock<AST>>;
|
||||
}
|
||||
```
|
||||
|
||||
This follows the pattern in the hot reload documentation where the AST is kept with interior mutability using `Rc<RefCell<AST>>`, but adapted for thread safety using `Arc<RwLock<AST>>` as recommended in the documentation for multi-threaded environments.
|
||||
|
||||
#### 4. Module Cache Extensions
|
||||
|
||||
We'll extend the ModuleCache to support invalidation when files change:
|
||||
|
||||
```rust
|
||||
impl ModuleCache {
|
||||
// Existing methods...
|
||||
|
||||
/// Invalidate a specific module in the cache
|
||||
pub fn invalidate<P: AsRef<Path>>(&self, path: P);
|
||||
|
||||
/// Check if a module in the cache is outdated
|
||||
pub fn is_outdated<P: AsRef<Path>>(&self, path: P) -> bool;
|
||||
|
||||
/// Update the cache timestamp for a module
|
||||
pub fn update_timestamp<P: AsRef<Path>>(&self, path: P);
|
||||
}
|
||||
```
|
||||
|
||||
These methods will help manage the cache when files change, ensuring that the cache is invalidated when necessary.
|
||||
|
||||
#### 5. File Monitoring
|
||||
|
||||
We'll implement file monitoring using the `notify` crate:
|
||||
|
||||
```rust
|
||||
/// File watcher for hot reloading
|
||||
struct FileWatcher {
|
||||
watcher: notify::RecommendedWatcher,
|
||||
event_receiver: mpsc::Receiver<notify::Result<notify::Event>>,
|
||||
watched_paths: HashMap<PathBuf, Vec<uuid::Uuid>>,
|
||||
}
|
||||
|
||||
impl FileWatcher {
|
||||
/// Create a new file watcher
|
||||
pub fn new() -> Result<Self, RhaiFactoryError>;
|
||||
|
||||
/// Watch a file or directory
|
||||
pub fn watch<P: AsRef<Path>>(&mut self, path: P, id: uuid::Uuid) -> Result<(), RhaiFactoryError>;
|
||||
|
||||
/// Stop watching a file or directory
|
||||
pub fn unwatch<P: AsRef<Path>>(&mut self, path: P, id: uuid::Uuid);
|
||||
|
||||
/// Check for file changes
|
||||
pub fn check_for_changes(&mut self) -> Vec<PathBuf>;
|
||||
}
|
||||
```
|
||||
|
||||
This implements the file watching functionality needed for hot reloading, similar to the "watch for script file change" functionality mentioned in the hot reload documentation.
|
||||
|
||||
### Error Handling
|
||||
|
||||
We'll extend the RhaiFactoryError to include hot reload specific errors:
|
||||
|
||||
```rust
|
||||
impl RhaiFactoryError {
|
||||
// Existing methods...
|
||||
|
||||
/// Create a new hot reload error
|
||||
pub fn hot_reload_error(message: impl Into<String>) -> Self;
|
||||
|
||||
/// Add file watcher context to the error
|
||||
pub fn with_watcher_context(mut self, context: impl Into<String>) -> Self;
|
||||
}
|
||||
```
|
||||
|
||||
These methods will help provide detailed error information when hot reloading fails.
|
||||
|
||||
## Testing Strategy for Hot Reload
|
||||
|
||||
### 1. Unit Tests
|
||||
|
||||
We'll add unit tests for the hot reload functionality:
|
||||
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// Existing tests...
|
||||
|
||||
#[test]
|
||||
fn hot_reload_detects_file_changes() {
|
||||
// Test that file changes are detected
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hot_reload_recompiles_changed_scripts() {
|
||||
// Test that scripts are recompiled when changed
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hot_reload_updates_ast() {
|
||||
// Test that the AST is updated when scripts change
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hot_reload_triggers_callbacks() {
|
||||
// Test that callbacks are triggered when scripts are reloaded
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hot_reload_handles_errors() {
|
||||
// Test error handling during hot reload
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
These tests will verify that the hot reload functionality works as expected.
|
||||
|
||||
### 2. Integration Tests
|
||||
|
||||
We'll add integration tests for the hot reload functionality:
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn factory_hot_reloads_scripts() {
|
||||
// Create a temporary script file
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let script_path = temp_dir.path().join("test.rhai");
|
||||
std::fs::write(&script_path, "40 + 2").unwrap();
|
||||
|
||||
// Create a factory and compile the script
|
||||
let factory = Arc::new(RhaiFactory::with_caching());
|
||||
let ast = factory.compile_modules(&[&script_path], None).unwrap();
|
||||
let ast = Arc::new(RwLock::new(ast));
|
||||
|
||||
// Enable hot reloading
|
||||
let reload_detected = Arc::new(AtomicBool::new(false));
|
||||
let reload_detected_clone = reload_detected.clone();
|
||||
let callback = Box::new(move || {
|
||||
reload_detected_clone.store(true, Ordering::SeqCst);
|
||||
});
|
||||
|
||||
let handle = factory.enable_hot_reload(
|
||||
ast.clone(),
|
||||
&[&script_path],
|
||||
None,
|
||||
Some(callback)
|
||||
).unwrap();
|
||||
|
||||
// Modify the script
|
||||
std::fs::write(&script_path, "50 + 10").unwrap();
|
||||
|
||||
// Wait for the file system to register the change
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
// Check for changes
|
||||
factory.check_for_changes().unwrap();
|
||||
|
||||
// Verify the callback was triggered
|
||||
assert!(reload_detected.load(Ordering::SeqCst));
|
||||
|
||||
// Verify the AST was updated
|
||||
let engine = factory.create_engine();
|
||||
let result: i64 = engine.eval_ast(&ast.read().unwrap()).unwrap();
|
||||
assert_eq!(result, 60);
|
||||
|
||||
// Disable hot reloading
|
||||
factory.disable_hot_reload(handle);
|
||||
}
|
||||
```
|
||||
|
||||
This test verifies that the hot reload functionality works end-to-end, following the pattern described in the Rhai documentation.
|
||||
|
||||
### 3. Thread Safety Tests
|
||||
|
||||
We'll add tests to verify thread safety:
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn hot_reload_is_thread_safe() {
|
||||
// Create a temporary script file
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let script_path = temp_dir.path().join("test.rhai");
|
||||
std::fs::write(&script_path, "40 + 2").unwrap();
|
||||
|
||||
// Create a factory and compile the script
|
||||
let factory = Arc::new(RhaiFactory::with_caching());
|
||||
let ast = factory.compile_modules(&[&script_path], None).unwrap();
|
||||
let ast = Arc::new(RwLock::new(ast));
|
||||
|
||||
// Enable hot reloading
|
||||
let handle = factory.enable_hot_reload(
|
||||
ast.clone(),
|
||||
&[&script_path],
|
||||
None,
|
||||
None
|
||||
).unwrap();
|
||||
|
||||
// Create threads that read the AST while it's being modified
|
||||
let threads: Vec<_> = (0..10).map(|_| {
|
||||
let factory_clone = factory.clone();
|
||||
let ast_clone = ast.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
for _ in 0..100 {
|
||||
let engine = factory_clone.create_engine();
|
||||
let _: Result<i64, _> = engine.eval_ast(&ast_clone.read().unwrap());
|
||||
std::thread::yield_now();
|
||||
}
|
||||
})
|
||||
}).collect();
|
||||
|
||||
// Modify the script multiple times
|
||||
for i in 0..5 {
|
||||
std::fs::write(&script_path, format!("40 + {}", i)).unwrap();
|
||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||
factory.check_for_changes().unwrap();
|
||||
}
|
||||
|
||||
// Wait for all threads to complete
|
||||
for thread in threads {
|
||||
thread.join().unwrap();
|
||||
}
|
||||
|
||||
// Disable hot reloading
|
||||
factory.disable_hot_reload(handle);
|
||||
}
|
||||
```
|
||||
|
||||
This test verifies that the hot reload functionality is thread-safe, following the recommendation in the Rhai documentation to use `Arc`, `RwLock`, and the `sync` feature for multi-threaded environments.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```rust
|
||||
// Create a factory with caching enabled
|
||||
let factory = Arc::new(RhaiFactory::with_caching());
|
||||
|
||||
// Compile the initial script
|
||||
let ast = factory.compile_modules(&["main.rhai"], Some("scripts")).unwrap();
|
||||
let ast = Arc::new(RwLock::new(ast));
|
||||
|
||||
// Enable hot reloading
|
||||
let handle = factory.enable_hot_reload(
|
||||
ast.clone(),
|
||||
&["main.rhai"],
|
||||
Some("scripts"),
|
||||
Some(Box::new(|| println!("Script reloaded!")))
|
||||
).unwrap();
|
||||
|
||||
// Create an engine and use the AST
|
||||
let engine = factory.create_engine();
|
||||
|
||||
// In your application loop
|
||||
loop {
|
||||
// Check for script changes
|
||||
if factory.check_for_changes().unwrap() {
|
||||
println!("Scripts were reloaded");
|
||||
}
|
||||
|
||||
// Use the latest version of the script
|
||||
let result: i64 = engine.eval_ast(&ast.read().unwrap()).unwrap();
|
||||
|
||||
// Do something with the result
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
||||
|
||||
// When done
|
||||
factory.disable_hot_reload(handle);
|
||||
```
|
||||
|
||||
This example shows how to use the hot reload functionality in a basic application, following the pattern described in the Rhai documentation.
|
||||
|
||||
### With HotReloadableAST
|
||||
|
||||
```rust
|
||||
// Create a factory with caching enabled
|
||||
let factory = Arc::new(RhaiFactory::with_caching());
|
||||
|
||||
// Compile the initial script
|
||||
let ast = factory.compile_modules(&["main.rhai"], Some("scripts")).unwrap();
|
||||
|
||||
// Create a hot reloadable AST
|
||||
let mut hot_ast = HotReloadableAST::new(ast, factory.clone());
|
||||
|
||||
// Enable hot reloading
|
||||
hot_ast.enable_hot_reload(
|
||||
&["main.rhai"],
|
||||
Some("scripts"),
|
||||
Some(Box::new(|| println!("Script reloaded!")))
|
||||
).unwrap();
|
||||
|
||||
// Create an engine and use the AST
|
||||
let engine = factory.create_engine();
|
||||
|
||||
// In your application loop
|
||||
loop {
|
||||
// Check for script changes
|
||||
factory.check_for_changes().unwrap();
|
||||
|
||||
// Use the latest version of the script
|
||||
let result: i64 = engine.eval_ast(&hot_ast.ast().read().unwrap()).unwrap();
|
||||
|
||||
// Do something with the result
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
||||
|
||||
// When done
|
||||
hot_ast.disable_hot_reload();
|
||||
```
|
||||
|
||||
This example shows how to use the `HotReloadableAST` wrapper for a more convenient API.
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
1. **Add Dependencies**
|
||||
- Add the `notify` crate for file system monitoring
|
||||
- Add the `uuid` crate for unique identifiers
|
||||
|
||||
2. **Create Hot Reload Manager**
|
||||
- Implement the `HotReloadManager` struct
|
||||
- Implement the `FileWatcher` struct
|
||||
- Implement the `ScriptRegistry` struct
|
||||
|
||||
3. **Extend RhaiFactory**
|
||||
- Add hot reload methods to `RhaiFactory`
|
||||
- Implement the `HotReloadHandle` struct
|
||||
- Implement the `HotReloadableAST` struct
|
||||
|
||||
4. **Extend ModuleCache**
|
||||
- Add methods for cache invalidation
|
||||
- Add timestamp tracking for modules
|
||||
|
||||
5. **Implement Error Handling**
|
||||
- Extend `RhaiFactoryError` for hot reload errors
|
||||
|
||||
6. **Write Tests**
|
||||
- Implement unit tests
|
||||
- Implement integration tests
|
||||
- Implement thread safety tests
|
||||
|
||||
7. **Update Documentation**
|
||||
- Add hot reload documentation to README
|
||||
- Add examples and usage guidelines
|
||||
|
||||
## Considerations and Trade-offs
|
||||
|
||||
1. **Performance Impact**
|
||||
- File system monitoring can have a performance impact
|
||||
- We'll provide options to control the frequency of checks
|
||||
|
||||
2. **Memory Usage**
|
||||
- Keeping multiple versions of scripts in memory can increase memory usage
|
||||
- We'll provide options to control caching behavior
|
||||
|
||||
3. **Thread Safety**
|
||||
- Hot reloading in a multi-threaded environment requires careful synchronization
|
||||
- We'll use `RwLock` to allow multiple readers but exclusive writers
|
||||
- This follows the recommendation in the Rhai documentation to use `Arc`, `RwLock`, and the `sync` feature for multi-threaded environments
|
||||
|
||||
4. **Error Handling**
|
||||
- Script compilation errors during hot reload need to be handled gracefully
|
||||
- We'll provide options to keep the old script or propagate errors
|
||||
|
||||
5. **Dependency Tracking**
|
||||
- Changes to imported modules need to trigger recompilation of dependent modules
|
||||
- We'll implement dependency tracking in the `ScriptRegistry`
|
||||
|
||||
## References
|
||||
|
||||
- Rhai Documentation: `_archive/rhai_engine/rhaibook/`
|
||||
- Hot Reload Pattern: `_archive/rhai_engine/rhaibook/patterns/hot-reload.md`
|
||||
- Rhai Engine: `rhai::Engine`
|
||||
- Rhai AST: `rhai::AST`
|
||||
- Rhai Module: `rhai::Module`
|
||||
- Rhai Scope: `rhai::Scope`
|
||||
- Rhai Sync Feature: `sync`
|
||||
|
||||
## Conclusion
|
||||
|
||||
The hot reload feature will enhance the `rhai_factory` module by allowing scripts to be modified dynamically without requiring application restart. This will improve the development experience and enable more flexible runtime behavior.
|
||||
|
||||
By following the patterns described in the Rhai documentation, we can implement a robust hot reload feature that is thread-safe and provides a good developer experience.
|
||||
|
||||
## Tera Engine Factory with Hot Reloadable Rhai Integration
|
||||
|
||||
### Overview
|
||||
|
||||
We'll create a `TeraFactory` module that provides a factory for creating Tera template engines with integrated Rhai scripting support. The factory will:
|
||||
|
||||
1. Create Tera engines with specified template directories
|
||||
2. Integrate with the hot reloadable Rhai AST from the `RhaiFactory`
|
||||
3. Allow Rhai functions to be called from Tera templates
|
||||
4. Automatically update available functions when Rhai scripts are hot reloaded
|
||||
|
||||
### Architecture Extension
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[TeraFactory] --> B[create_tera_engine]
|
||||
A --> C[create_tera_with_rhai]
|
||||
|
||||
C --> D[Tera Engine with Rhai Functions]
|
||||
|
||||
E[RhaiFactory] --> F[HotReloadableAST]
|
||||
F --> C
|
||||
|
||||
G[RhaiFunctionAdapter] --> D
|
||||
H[Template Directories] --> B
|
||||
```
|
||||
|
||||
### Component Details
|
||||
|
||||
#### 1. TeraFactory Module Structure
|
||||
|
||||
```
|
||||
tera_factory/
|
||||
├── Cargo.toml # Dependencies including tera and rhai_factory
|
||||
└── src/
|
||||
├── lib.rs # Main module exports and unit tests
|
||||
├── factory.rs # Factory implementation and unit tests
|
||||
├── error.rs # Custom error types and unit tests
|
||||
└── function_adapter.rs # Rhai function adapter for Tera
|
||||
```
|
||||
|
||||
#### 2. TeraFactory Implementation
|
||||
|
||||
The core factory will provide these main functions:
|
||||
|
||||
1. **create_tera_engine(template_dirs)** - Creates a basic Tera engine with the specified template directories
|
||||
2. **create_tera_with_rhai(template_dirs, hot_ast)** - Creates a Tera engine with Rhai function integration using a hot reloadable AST
|
||||
|
||||
#### 3. RhaiFunctionAdapter
|
||||
|
||||
We'll enhance the existing `RhaiFunctionAdapter` to work with the hot reloadable AST:
|
||||
|
||||
```rust
|
||||
/// Thread-safe adapter to use Rhai functions in Tera templates with hot reload support
|
||||
pub struct RhaiFunctionAdapter {
|
||||
fn_name: String,
|
||||
hot_ast: Arc<RwLock<AST>>,
|
||||
}
|
||||
|
||||
impl TeraFunction for RhaiFunctionAdapter {
|
||||
fn call(&self, args: &HashMap<String, Value>) -> TeraResult<Value> {
|
||||
// Convert args from Tera into Rhai's Dynamic
|
||||
let mut scope = Scope::new();
|
||||
for (key, value) in args {
|
||||
// Convert Tera value to Rhai Dynamic
|
||||
let dynamic = convert_tera_to_rhai(value);
|
||||
scope.push_dynamic(key.clone(), dynamic);
|
||||
}
|
||||
|
||||
// Create a new engine for each call
|
||||
let engine = Engine::new();
|
||||
|
||||
// Get a read lock on the AST
|
||||
let ast = self.hot_ast.read().unwrap();
|
||||
|
||||
// Call the function using the latest AST
|
||||
let result = engine
|
||||
.call_fn::<Dynamic>(&mut scope, &ast, &self.fn_name, ())
|
||||
.map_err(|e| tera::Error::msg(format!("Rhai error: {}", e)))?;
|
||||
|
||||
// Convert Rhai result to Tera value
|
||||
let tera_value = convert_rhai_to_tera(&result);
|
||||
Ok(tera_value)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. TeraFactory API
|
||||
|
||||
```rust
|
||||
pub struct TeraFactory {
|
||||
// Configuration options
|
||||
}
|
||||
|
||||
impl TeraFactory {
|
||||
/// Create a new TeraFactory with default settings
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
/// Create a Tera engine with the specified template directories
|
||||
pub fn create_tera_engine<P: AsRef<Path>>(&self, template_dirs: &[P])
|
||||
-> Result<Tera, TeraFactoryError> {
|
||||
// Create a Tera engine with the specified template directories
|
||||
}
|
||||
|
||||
/// Create a Tera engine with Rhai function integration
|
||||
pub fn create_tera_with_rhai<P: AsRef<Path>>(
|
||||
&self,
|
||||
template_dirs: &[P],
|
||||
hot_ast: Arc<RwLock<AST>>
|
||||
) -> Result<Tera, TeraFactoryError> {
|
||||
// Create a Tera engine with Rhai function integration
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Implementation Details
|
||||
|
||||
#### 1. Creating a Tera Engine
|
||||
|
||||
```rust
|
||||
impl TeraFactory {
|
||||
pub fn create_tera_engine<P: AsRef<Path>>(&self, template_dirs: &[P])
|
||||
-> Result<Tera, TeraFactoryError> {
|
||||
let mut tera = Tera::default();
|
||||
|
||||
// Add templates from each directory
|
||||
for template_dir in template_dirs {
|
||||
let pattern = format!("{}/**/*.html", template_dir.as_ref().display());
|
||||
match Tera::parse(&pattern) {
|
||||
Ok(parsed_tera) => {
|
||||
tera.extend(&parsed_tera).map_err(|e| {
|
950
rhai_system/examples/base_path_imports/Cargo.lock
generated
Normal file
950
rhai_system/examples/base_path_imports/Cargo.lock
generated
Normal file
@ -0,0 +1,950 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"const-random",
|
||||
"getrandom 0.2.15",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "base_path_imports"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"rhai",
|
||||
"rhai_factory",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"libredox",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
|
||||
dependencies = [
|
||||
"kqueue-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue-sys"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.171"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "no-std-compat"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crossbeam-channel",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"mio",
|
||||
"walkdir",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai"
|
||||
version = "1.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce4d759a4729a655ddfdbb3ff6e77fb9eadd902dae12319455557796e435d2a6"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bitflags 2.9.0",
|
||||
"instant",
|
||||
"no-std-compat",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"rhai_codegen",
|
||||
"smallvec",
|
||||
"smartstring",
|
||||
"thin-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_codegen"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_factory"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"notify",
|
||||
"rhai",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||
|
||||
[[package]]
|
||||
name = "smartstring"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"static_assertions",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thin-vec"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
|
||||
dependencies = [
|
||||
"getrandom 0.3.2",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.42.2",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm 0.42.2",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
9
rhai_system/examples/base_path_imports/Cargo.toml
Normal file
9
rhai_system/examples/base_path_imports/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "base_path_imports"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
rhai_factory = { path = "../.." }
|
||||
rhai = "1.14.0"
|
||||
chrono = "0.4.24"
|
89
rhai_system/examples/base_path_imports/README.md
Normal file
89
rhai_system/examples/base_path_imports/README.md
Normal file
@ -0,0 +1,89 @@
|
||||
# Base Path Module Resolver Example
|
||||
|
||||
This example demonstrates the use of the `BasePathModuleResolver` for simplifying Rhai script module imports.
|
||||
|
||||
## Overview
|
||||
|
||||
The `BasePathModuleResolver` provides a more predictable and consistent approach to module resolution by:
|
||||
|
||||
1. Using a single base path for resolving all module imports
|
||||
2. Eliminating the complexity of resolving imports relative to the importing file
|
||||
3. Requiring all import paths to be specified relative to the base path
|
||||
|
||||
## Directory Structure
|
||||
|
||||
The example follows a hierarchical directory structure:
|
||||
|
||||
```
|
||||
base_path_imports/
|
||||
├── components/
|
||||
│ ├── calendar/
|
||||
│ │ └── controller/
|
||||
│ │ └── mock/
|
||||
│ │ └── calendar_model.rhai
|
||||
│ ├── common/
|
||||
│ │ └── utils/
|
||||
│ │ ├── date_utils.rhai
|
||||
│ │ └── string_utils.rhai
|
||||
│ └── website/
|
||||
│ └── controller/
|
||||
│ └── mock/
|
||||
│ └── website_model.rhai
|
||||
├── main.rhai
|
||||
└── src/
|
||||
└── main.rs
|
||||
```
|
||||
|
||||
## Key Components
|
||||
|
||||
### 1. BasePathModuleResolver
|
||||
|
||||
Located in `rhai_factory/src/relative_resolver.rs`, this resolver simplifies module imports by:
|
||||
|
||||
- Taking a base path during initialization
|
||||
- Resolving all module imports relative to this base path
|
||||
- Providing clear logging of the resolution process
|
||||
|
||||
### 2. Utility Modules
|
||||
|
||||
Common utility functions are organized in the `components/common/utils/` directory:
|
||||
|
||||
- `string_utils.rhai`: Basic string manipulation functions
|
||||
- `date_utils.rhai`: Date formatting and validation functions
|
||||
|
||||
### 3. Component-Specific Modules
|
||||
|
||||
Component-specific functionality is organized in dedicated directories:
|
||||
|
||||
- `components/calendar/controller/mock/calendar_model.rhai`: Calendar event creation and validation
|
||||
- `components/website/controller/mock/website_model.rhai`: Website page creation and validation
|
||||
|
||||
### 4. Main Script
|
||||
|
||||
The `main.rhai` script demonstrates importing and using modules from different components.
|
||||
|
||||
## Running the Example
|
||||
|
||||
To run the example:
|
||||
|
||||
```bash
|
||||
cd rhai_factory/examples/base_path_imports
|
||||
cargo run
|
||||
```
|
||||
|
||||
## Key Benefits
|
||||
|
||||
1. **Simplified Imports**: All imports are relative to a single base path
|
||||
2. **Predictable Resolution**: No need to calculate relative paths between files
|
||||
3. **Cleaner Code**: No need for complex "../../../" style paths
|
||||
4. **Better Organization**: Encourages a modular, component-based structure
|
||||
5. **Improved Debugging**: Clear logging of the module resolution process
|
||||
|
||||
## Implementation Details
|
||||
|
||||
The example demonstrates:
|
||||
|
||||
1. Setting up the `BasePathModuleResolver` with a base path
|
||||
2. Importing modules using paths relative to the base path
|
||||
3. Using utility functions from common modules
|
||||
4. Creating component-specific functionality that leverages common utilities
|
@ -0,0 +1,33 @@
|
||||
// Calendar model functions
|
||||
|
||||
// Import common utility functions
|
||||
import "components/common/utils/date_utils" as date_utils;
|
||||
import "components/common/utils/string_utils" as string_utils;
|
||||
|
||||
// Creates a new calendar event
|
||||
fn create_event(title, description, year, month, day) {
|
||||
let formatted_title = string_utils::to_upper(title);
|
||||
let formatted_date = date_utils::format_date(year, month, day);
|
||||
|
||||
return #{
|
||||
title: formatted_title,
|
||||
description: description,
|
||||
date: formatted_date,
|
||||
is_valid: is_valid_event_date(year, month, day)
|
||||
};
|
||||
}
|
||||
|
||||
// Checks if an event date is valid
|
||||
fn is_valid_event_date(year, month, day) {
|
||||
// Convert all parameters to integers to ensure consistent comparison
|
||||
let y = year.to_int();
|
||||
let m = month.to_int();
|
||||
let d = day.to_int();
|
||||
|
||||
if m < 1 || m > 12 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let max_days = date_utils::days_in_month(y, m);
|
||||
return d >= 1 && d <= max_days;
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
// Simple date utility functions
|
||||
|
||||
// Format a date as YYYY-MM-DD
|
||||
fn format_date(year, month, day) {
|
||||
let month_str = month;
|
||||
let day_str = day;
|
||||
|
||||
return year + "-" + month_str + "-" + day_str;
|
||||
}
|
||||
|
||||
// Check if a year is a leap year
|
||||
fn is_leap_year(year) {
|
||||
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
|
||||
}
|
||||
|
||||
// Get the number of days in a month
|
||||
fn days_in_month(year, month) {
|
||||
if month == 2 {
|
||||
if is_leap_year(year) {
|
||||
return 29;
|
||||
} else {
|
||||
return 28;
|
||||
}
|
||||
}
|
||||
|
||||
if month == 4 || month == 6 || month == 9 || month == 11 {
|
||||
return 30;
|
||||
}
|
||||
|
||||
return 31;
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
// Simple string utility functions
|
||||
|
||||
// Make a string uppercase
|
||||
fn to_upper(s) {
|
||||
return s.to_upper();
|
||||
}
|
||||
|
||||
// Make a string lowercase
|
||||
fn to_lower(s) {
|
||||
return s.to_lower();
|
||||
}
|
||||
|
||||
// Get string length
|
||||
fn length(s) {
|
||||
return s.len();
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
// Website model functions
|
||||
|
||||
// Import common utility functions
|
||||
import "components/common/utils/string_utils" as string_utils;
|
||||
|
||||
// Creates a new website page
|
||||
fn create_page(title, content, slug) {
|
||||
let formatted_title = string_utils::to_upper(title);
|
||||
let slug_length = string_utils::length(slug);
|
||||
|
||||
return #{
|
||||
title: formatted_title,
|
||||
content: content,
|
||||
slug: slug,
|
||||
slug_length: slug_length,
|
||||
is_valid: is_valid_slug(slug)
|
||||
};
|
||||
}
|
||||
|
||||
// Validates a page slug
|
||||
fn is_valid_slug(slug) {
|
||||
// Simple validation - check if it's not empty and has reasonable length
|
||||
if slug.len() == 0 || slug.len() > 100 {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
36
rhai_system/examples/base_path_imports/main.rhai
Normal file
36
rhai_system/examples/base_path_imports/main.rhai
Normal file
@ -0,0 +1,36 @@
|
||||
// Main script that demonstrates importing modules from different components
|
||||
|
||||
// Import the calendar and website models
|
||||
import "components/calendar/controller/mock/calendar_model" as calendar;
|
||||
import "components/website/controller/mock/website_model" as website;
|
||||
|
||||
// Create a calendar event
|
||||
fn create_calendar_event(title, description, year, month, day) {
|
||||
if !calendar::is_valid_event_date(year, month, day) {
|
||||
return #{ error: "Invalid date" };
|
||||
}
|
||||
|
||||
return calendar::create_event(title, description, year, month, day);
|
||||
}
|
||||
|
||||
// Create a website page
|
||||
fn create_website_page(title, content, slug) {
|
||||
if !website::is_valid_slug(slug) {
|
||||
return #{ error: "Invalid slug" };
|
||||
}
|
||||
|
||||
return website::create_page(title, content, slug);
|
||||
}
|
||||
|
||||
// Example function that combines calendar and website functionality
|
||||
fn demo() {
|
||||
// Create a calendar event
|
||||
let event = create_calendar_event("Team Meeting", "Weekly sync", 2025, 4, 15);
|
||||
print("Calendar event: " + event);
|
||||
|
||||
// Create a website page
|
||||
let page = create_website_page("About Us", "Company information", "about-us");
|
||||
print("Website page: " + page);
|
||||
|
||||
return "Demo completed successfully";
|
||||
}
|
69
rhai_system/examples/base_path_imports/src/main.rs
Normal file
69
rhai_system/examples/base_path_imports/src/main.rs
Normal file
@ -0,0 +1,69 @@
|
||||
use rhai_factory::{RhaiFactory, BasePathModuleResolver};
|
||||
use std::path::Path;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Get the base path for our scripts
|
||||
let base_path = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
|
||||
println!("Base path: {}", base_path.display());
|
||||
println!("This example demonstrates the BasePathModuleResolver which resolves all imports relative to a base path");
|
||||
|
||||
// Create a new RhaiFactory
|
||||
let factory = RhaiFactory::new();
|
||||
|
||||
// Create an engine with the base path for module resolution
|
||||
let mut engine = factory.create_engine();
|
||||
|
||||
// Set up the BasePathModuleResolver - this is the key component that simplifies module imports
|
||||
// All module imports will be resolved relative to this base path
|
||||
let resolver = BasePathModuleResolver::new_with_path(&base_path);
|
||||
engine.set_module_resolver(resolver);
|
||||
|
||||
// Register basic functions needed by our scripts
|
||||
engine.register_fn("now", || chrono::Utc::now().to_rfc3339());
|
||||
|
||||
// Path to our main script
|
||||
let main_script_path = base_path.join("main.rhai");
|
||||
println!("\nEvaluating script: {}", main_script_path.display());
|
||||
|
||||
// Compile the main script to an AST
|
||||
let ast = engine.compile_file(main_script_path)?;
|
||||
|
||||
// Create a scope for evaluation
|
||||
let mut scope = rhai::Scope::new();
|
||||
|
||||
// Example 1: Call the demo function which demonstrates both calendar and website functionality
|
||||
println!("\nExample 1: Running demo function");
|
||||
let result: String = engine.call_fn(
|
||||
&mut scope,
|
||||
&ast,
|
||||
"demo",
|
||||
()
|
||||
)?;
|
||||
println!("Demo result: {}", result);
|
||||
|
||||
// Example 2: Call the calendar event creation function
|
||||
println!("\nExample 2: Creating a calendar event");
|
||||
let result: rhai::Map = engine.call_fn(
|
||||
&mut scope,
|
||||
&ast,
|
||||
"create_calendar_event",
|
||||
("Conference", "Annual tech conference", 2025, 6, 15)
|
||||
)?;
|
||||
println!("Calendar event: {:#?}", result);
|
||||
|
||||
// Example 3: Call the website page creation function
|
||||
println!("\nExample 3: Creating a website page");
|
||||
let result: rhai::Map = engine.call_fn(
|
||||
&mut scope,
|
||||
&ast,
|
||||
"create_website_page",
|
||||
("Contact", "Our contact information", "contact-us")
|
||||
)?;
|
||||
println!("Website page: {:#?}", result);
|
||||
|
||||
println!("\nThe BasePathModuleResolver successfully resolved all module imports relative to the base path.");
|
||||
println!("This approach simplifies module imports and makes the resolution process more predictable.");
|
||||
|
||||
Ok(())
|
||||
}
|
105
rhai_system/examples/hot_reload/README.md
Normal file
105
rhai_system/examples/hot_reload/README.md
Normal file
@ -0,0 +1,105 @@
|
||||
# Hot Reload Example
|
||||
|
||||
This example demonstrates hot reloading of multiple Rhai script files using the `rhai_system` crate. It shows 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
|
||||
|
||||
## How It Works
|
||||
|
||||
The example uses two main components:
|
||||
|
||||
1. **The System**: Created by `create_hot_reloadable_system` which:
|
||||
- Loads multiple script files (`script.rhai` and `utils.rhai`)
|
||||
- Compiles them into a single AST
|
||||
- Sets up file watchers for each script file
|
||||
- Provides a clean API for calling functions
|
||||
|
||||
2. **Two Threads**:
|
||||
- **Execution Thread**: Continuously executes functions from the scripts
|
||||
- **Modification Thread**: Modifies the script files at specific intervals to demonstrate hot reloading
|
||||
|
||||
## Cross-Script Function Calls
|
||||
|
||||
The example demonstrates how functions in one script can call functions in another:
|
||||
|
||||
- `script.rhai` contains the main functions (`greet`, `advanced_calculation`, `multiply`, `divide`)
|
||||
- `utils.rhai` contains utility functions (`calculate`, `greet_from_utils`)
|
||||
- Functions in `script.rhai` call functions in `utils.rhai`
|
||||
|
||||
This shows how you can organize your code into multiple script files while still maintaining the ability to call functions across files.
|
||||
|
||||
## Running the Example
|
||||
|
||||
To run this example, navigate to the root of the rhai_system project and execute:
|
||||
|
||||
```bash
|
||||
cargo run --example hot_reload
|
||||
```
|
||||
|
||||
## What to Expect
|
||||
|
||||
When you run the example, you'll see:
|
||||
|
||||
1. The system loads both `script.rhai` and `utils.rhai`
|
||||
2. The execution thread calls functions from the scripts every second
|
||||
3. After 5 seconds, the modification thread updates `script.rhai` to add new functions
|
||||
4. The execution thread automatically starts using the updated script
|
||||
5. After another 5 seconds, both script files are modified again
|
||||
6. The system reloads both scripts and the execution thread uses the latest versions
|
||||
|
||||
This demonstrates how the system automatically detects and reloads scripts when they change, without requiring any restart of the application.
|
||||
|
||||
## Key Implementation Details
|
||||
|
||||
### Multiple Script Support
|
||||
|
||||
The system supports multiple script files through:
|
||||
|
||||
```rust
|
||||
// Create a hot reloadable system with multiple script files
|
||||
let script_paths = vec![
|
||||
PathBuf::from("examples/hot_reload/script.rhai"),
|
||||
PathBuf::from("examples/hot_reload/utils.rhai"),
|
||||
];
|
||||
let system = create_hot_reloadable_system(&script_paths, None).unwrap();
|
||||
```
|
||||
|
||||
### File Watching
|
||||
|
||||
The system automatically sets up file watchers for all script files:
|
||||
|
||||
```rust
|
||||
// Start watching for changes to the script files
|
||||
system.watch();
|
||||
```
|
||||
|
||||
### Thread-Safe Usage
|
||||
|
||||
The system is thread-safe and can be used from multiple threads:
|
||||
|
||||
```rust
|
||||
// Clone the system for use in another thread
|
||||
let system_clone = Arc::clone(&system);
|
||||
|
||||
// Create a thread-local clone for the execution thread
|
||||
let thread_system = system_clone.clone_for_thread();
|
||||
```
|
||||
|
||||
## Modifying Scripts at Runtime
|
||||
|
||||
The example includes functions to modify the script files programmatically:
|
||||
|
||||
```rust
|
||||
// Modify the script file with new content
|
||||
modify_script(
|
||||
&script_path,
|
||||
"examples/hot_reload/modified_script.rhai",
|
||||
5,
|
||||
"Modifying the script to add multiply function..."
|
||||
);
|
||||
```
|
||||
|
||||
This simulates a developer editing the script files during development, demonstrating how the system automatically detects and reloads the scripts.
|
21
rhai_system/examples/hot_reload/initial_script.rhai
Normal file
21
rhai_system/examples/hot_reload/initial_script.rhai
Normal file
@ -0,0 +1,21 @@
|
||||
// This is a simple Rhai script that will be hot reloaded
|
||||
// It contains functions that will be called by the main program
|
||||
// It also uses functions from the utils.rhai script
|
||||
|
||||
// A simple greeting function
|
||||
fn greet(name) {
|
||||
// Use the format_greeting function from utils.rhai
|
||||
let utils_greeting = format_greeting(name);
|
||||
"Hello, " + name + "! This is the original script. " + utils_greeting
|
||||
}
|
||||
|
||||
// A function to calculate the sum of two numbers
|
||||
fn add(a, b) {
|
||||
a + b
|
||||
}
|
||||
|
||||
// A function that uses the calculate function from utils.rhai
|
||||
fn advanced_calculation(x, y) {
|
||||
// Use the calculate function from utils.rhai
|
||||
calculate(x, y) * 2
|
||||
}
|
13
rhai_system/examples/hot_reload/initial_utils.rhai
Normal file
13
rhai_system/examples/hot_reload/initial_utils.rhai
Normal file
@ -0,0 +1,13 @@
|
||||
// Utility functions for the hot reload example
|
||||
|
||||
// A function to format a greeting message
|
||||
fn format_greeting(name) {
|
||||
"Greetings, " + name + "! (from utils.rhai)"
|
||||
}
|
||||
|
||||
// A function to perform a calculation
|
||||
// Keep it simple to avoid type issues
|
||||
fn calculate(a, b) {
|
||||
// Simple integer arithmetic
|
||||
(a * b) + 10
|
||||
}
|
152
rhai_system/examples/hot_reload/main.rs
Normal file
152
rhai_system/examples/hot_reload/main.rs
Normal file
@ -0,0 +1,152 @@
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::path::PathBuf;
|
||||
use std::fs::{self, File};
|
||||
use std::io::Write;
|
||||
|
||||
// Import the create_hot_reloadable_system from the library
|
||||
use rhai_system::create_hot_reloadable_system;
|
||||
|
||||
/// Function to modify a script file with content from another file
|
||||
fn modify_script(target_path: &PathBuf, source_path: &str, delay_secs: u64, message: &str) {
|
||||
println!("\n🔄 {}", message);
|
||||
|
||||
// Read the source script content
|
||||
let source_script_path = PathBuf::from(source_path);
|
||||
let source_content = fs::read_to_string(&source_script_path)
|
||||
.expect(&format!("Failed to read source script file: {}", source_path));
|
||||
|
||||
// Write the content to the target file
|
||||
let mut file = File::create(target_path)
|
||||
.expect("Failed to open target script file for writing");
|
||||
file.write_all(source_content.as_bytes())
|
||||
.expect("Failed to write to target script file");
|
||||
|
||||
println!("✅ Script modified successfully!");
|
||||
|
||||
// Wait before the next modification if delay is specified
|
||||
if delay_secs > 0 {
|
||||
thread::sleep(Duration::from_secs(delay_secs));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Set up the script paths
|
||||
let main_script_path = PathBuf::from("examples/hot_reload/script.rhai");
|
||||
let utils_script_path = PathBuf::from("examples/hot_reload/utils.rhai");
|
||||
println!("Main script path: {:?}", main_script_path);
|
||||
println!("Utils script path: {:?}", utils_script_path);
|
||||
|
||||
// Initialize script.rhai with the content from initial_script.rhai
|
||||
let initial_script_path = PathBuf::from("examples/hot_reload/initial_script.rhai");
|
||||
let initial_content = fs::read_to_string(&initial_script_path)
|
||||
.expect("Failed to read initial script file");
|
||||
|
||||
let mut file = File::create(&main_script_path)
|
||||
.expect("Failed to open script file for writing");
|
||||
file.write_all(initial_content.as_bytes())
|
||||
.expect("Failed to write to script file");
|
||||
|
||||
// Initialize utils.rhai with the content from initial_utils.rhai
|
||||
let initial_utils_path = PathBuf::from("examples/hot_reload/initial_utils.rhai");
|
||||
let initial_utils_content = fs::read_to_string(&initial_utils_path)
|
||||
.expect("Failed to read initial utils file");
|
||||
|
||||
let mut utils_file = File::create(&utils_script_path)
|
||||
.expect("Failed to open utils file for writing");
|
||||
utils_file.write_all(initial_utils_content.as_bytes())
|
||||
.expect("Failed to write to utils file");
|
||||
|
||||
// Create the hot-reloadable system with both script paths
|
||||
// We're passing a slice with both paths and using None for main_script_index
|
||||
// to use the default (first script in the slice)
|
||||
let system = create_hot_reloadable_system(&[main_script_path.clone(), utils_script_path.clone()], None)?;
|
||||
|
||||
// Start a thread that periodically executes the script
|
||||
let execution_thread = thread::spawn(move || {
|
||||
// Every second, call the greet function from the script
|
||||
loop {
|
||||
// Call the greet function
|
||||
match system.call_fn::<String>("greet", ("User",)) {
|
||||
Ok(result) => println!("Execution result: {}", result),
|
||||
Err(err) => println!("Error executing script: {}", err),
|
||||
}
|
||||
|
||||
// Call the add function
|
||||
match system.call_fn::<i32>("add", (40, 2)) {
|
||||
Ok(result) => println!("Add result: {}", result),
|
||||
Err(err) => println!("Error executing add function: {}", err),
|
||||
}
|
||||
|
||||
// Call the advanced_calculation function that uses utils.rhai
|
||||
match system.call_fn::<i64>("advanced_calculation", (5_i64, 7_i64)) {
|
||||
Ok(result) => println!("Advanced calculation result: {}", result),
|
||||
Err(err) => println!("Error executing advanced_calculation function: {}", err),
|
||||
}
|
||||
|
||||
// Try to call the multiply function, catch any errors
|
||||
match system.call_fn::<i32>("multiply", (40, 2)) {
|
||||
Ok(result) => println!("Multiply result: {}", result),
|
||||
Err(err) => {
|
||||
if err.to_string().contains("function not found") {
|
||||
println!("Multiply function not available yet");
|
||||
} else {
|
||||
println!("Error executing multiply function: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to call the divide function, catch any errors
|
||||
match system.call_fn::<i32>("divide", (40, 2)) {
|
||||
Ok(result) => println!("Divide result: {}", result),
|
||||
Err(err) => {
|
||||
if err.to_string().contains("function not found") {
|
||||
println!("Divide function not available yet");
|
||||
} else {
|
||||
println!("Error executing divide function: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait before the next execution
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
});
|
||||
|
||||
// Start a thread to modify the script files at intervals
|
||||
let main_script_path_clone = main_script_path.clone();
|
||||
let utils_script_path_clone = utils_script_path.clone();
|
||||
thread::spawn(move || {
|
||||
// Wait 5 seconds before first modification
|
||||
thread::sleep(Duration::from_secs(5));
|
||||
|
||||
// First modification - add multiply function
|
||||
modify_script(
|
||||
&main_script_path_clone,
|
||||
"examples/hot_reload/modified_script.rhai",
|
||||
10,
|
||||
"Modifying the script to add multiply function..."
|
||||
);
|
||||
|
||||
// Second modification - add divide function
|
||||
modify_script(
|
||||
&main_script_path_clone,
|
||||
"examples/hot_reload/second_modified_script.rhai",
|
||||
0,
|
||||
"Modifying the script again to add divide function..."
|
||||
);
|
||||
|
||||
// Third modification - modify utils.rhai
|
||||
modify_script(
|
||||
&utils_script_path_clone,
|
||||
"examples/hot_reload/modified_utils.rhai",
|
||||
0,
|
||||
"Modifying the utils script..."
|
||||
);
|
||||
});
|
||||
|
||||
// Wait for the execution thread to finish (it won't, but this keeps the main thread alive)
|
||||
execution_thread.join().unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
22
rhai_system/examples/hot_reload/modified_script.rhai
Normal file
22
rhai_system/examples/hot_reload/modified_script.rhai
Normal file
@ -0,0 +1,22 @@
|
||||
// This is a modified script
|
||||
// The AST will be replaced with this new version
|
||||
|
||||
// A simple greeting function with modified message
|
||||
fn greet(name) {
|
||||
"Hello, " + name + "! This is the MODIFIED script! Hot reloading works!"
|
||||
}
|
||||
|
||||
// A function to calculate the sum of two numbers
|
||||
fn add(a, b) {
|
||||
a + b
|
||||
}
|
||||
|
||||
// A new function added during hot reload
|
||||
fn multiply(a, b) {
|
||||
a * b
|
||||
}
|
||||
|
||||
// A new function added during hot reload
|
||||
fn divide(a, b) {
|
||||
a / b
|
||||
}
|
18
rhai_system/examples/hot_reload/modified_utils.rhai
Normal file
18
rhai_system/examples/hot_reload/modified_utils.rhai
Normal file
@ -0,0 +1,18 @@
|
||||
// Utility functions for the hot reload example - MODIFIED VERSION
|
||||
|
||||
// A function to format a greeting message
|
||||
fn format_greeting(name) {
|
||||
"ENHANCED Greetings, " + name + "! (from modified utils.rhai)"
|
||||
}
|
||||
|
||||
// A function to perform a calculation
|
||||
// Keep it simple to avoid type issues
|
||||
fn calculate(a, b) {
|
||||
// Enhanced calculation with additional operations
|
||||
(a * b * 2) + 20
|
||||
}
|
||||
|
||||
// A new utility function
|
||||
fn format_message(text) {
|
||||
"*** " + text + " ***"
|
||||
}
|
22
rhai_system/examples/hot_reload/script.rhai
Normal file
22
rhai_system/examples/hot_reload/script.rhai
Normal file
@ -0,0 +1,22 @@
|
||||
// This is a completely overwritten script
|
||||
// The AST will be replaced with this new version
|
||||
|
||||
// A simple greeting function with modified message
|
||||
fn greet(name) {
|
||||
"Hello, " + name + "! This is the COMPLETELY OVERWRITTEN script!"
|
||||
}
|
||||
|
||||
// A function to calculate the sum of two numbers
|
||||
fn add(a, b) {
|
||||
a + b
|
||||
}
|
||||
|
||||
// A new function added during hot reload
|
||||
fn multiply(a, b) {
|
||||
a * b
|
||||
}
|
||||
|
||||
// Another new function added during hot reload
|
||||
fn divide(a, b) {
|
||||
a / b
|
||||
}
|
22
rhai_system/examples/hot_reload/second_modified_script.rhai
Normal file
22
rhai_system/examples/hot_reload/second_modified_script.rhai
Normal file
@ -0,0 +1,22 @@
|
||||
// This is a completely overwritten script
|
||||
// The AST will be replaced with this new version
|
||||
|
||||
// A simple greeting function with modified message
|
||||
fn greet(name) {
|
||||
"Hello, " + name + "! This is the COMPLETELY OVERWRITTEN script!"
|
||||
}
|
||||
|
||||
// A function to calculate the sum of two numbers
|
||||
fn add(a, b) {
|
||||
a + b
|
||||
}
|
||||
|
||||
// A new function added during hot reload
|
||||
fn multiply(a, b) {
|
||||
a * b
|
||||
}
|
||||
|
||||
// Another new function added during hot reload
|
||||
fn divide(a, b) {
|
||||
a / b
|
||||
}
|
18
rhai_system/examples/hot_reload/utils.rhai
Normal file
18
rhai_system/examples/hot_reload/utils.rhai
Normal file
@ -0,0 +1,18 @@
|
||||
// Utility functions for the hot reload example - MODIFIED VERSION
|
||||
|
||||
// A function to format a greeting message
|
||||
fn format_greeting(name) {
|
||||
"ENHANCED Greetings, " + name + "! (from modified utils.rhai)"
|
||||
}
|
||||
|
||||
// A function to perform a calculation
|
||||
// Keep it simple to avoid type issues
|
||||
fn calculate(a, b) {
|
||||
// Enhanced calculation with additional operations
|
||||
(a * b * 2) + 20
|
||||
}
|
||||
|
||||
// A new utility function
|
||||
fn format_message(text) {
|
||||
"*** " + text + " ***"
|
||||
}
|
10
rhai_system/examples/relative_imports/Cargo.toml
Normal file
10
rhai_system/examples/relative_imports/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "relative_imports_example"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
rhai_factory = { path = "../.." }
|
||||
rhai = { version = "1.21.0", features = ["serde"] }
|
||||
log = "0.4"
|
||||
env_logger = "0.10"
|
@ -0,0 +1,67 @@
|
||||
// Calendar controller module
|
||||
// This demonstrates importing from a different directory level
|
||||
|
||||
// Import the common utils using a relative path
|
||||
import "../../../utils/common" as common;
|
||||
|
||||
/// Returns data for a single calendar event
|
||||
fn get_event_data() {
|
||||
let event = #{
|
||||
id: 1,
|
||||
title: "Team Meeting",
|
||||
date: "2025-04-15",
|
||||
time: "10:00 AM",
|
||||
location: "Conference Room A",
|
||||
description: "Weekly team sync meeting"
|
||||
};
|
||||
|
||||
// Use the common utils to check if the date is in the future
|
||||
if common::is_future_date(event.date) {
|
||||
event.status = "Upcoming";
|
||||
} else {
|
||||
event.status = "Past";
|
||||
}
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
/// Returns a list of all calendar events
|
||||
fn get_all_events() {
|
||||
let events = [
|
||||
#{
|
||||
id: 1,
|
||||
title: "Team Meeting",
|
||||
date: "2025-04-15",
|
||||
time: "10:00 AM",
|
||||
location: "Conference Room A",
|
||||
description: "Weekly team sync meeting"
|
||||
},
|
||||
#{
|
||||
id: 2,
|
||||
title: "Project Review",
|
||||
date: "2025-04-17",
|
||||
time: "2:00 PM",
|
||||
location: "Meeting Room B",
|
||||
description: "Review project progress and next steps"
|
||||
},
|
||||
#{
|
||||
id: 3,
|
||||
title: "Client Presentation",
|
||||
date: "2025-04-20",
|
||||
time: "11:30 AM",
|
||||
location: "Main Auditorium",
|
||||
description: "Present new features to the client"
|
||||
}
|
||||
];
|
||||
|
||||
// Update the status of each event using the common utils
|
||||
for event in events {
|
||||
if common::is_future_date(event.date) {
|
||||
event.status = "Upcoming";
|
||||
} else {
|
||||
event.status = "Past";
|
||||
}
|
||||
}
|
||||
|
||||
return events;
|
||||
}
|
23
rhai_system/examples/relative_imports/scripts/main.rhai
Normal file
23
rhai_system/examples/relative_imports/scripts/main.rhai
Normal file
@ -0,0 +1,23 @@
|
||||
// Import modules using relative paths
|
||||
import "utils/common" as common;
|
||||
import "components/calendar/controller/calendar" as calendar;
|
||||
|
||||
// Main function that demonstrates the relative imports
|
||||
fn main() {
|
||||
// Print a message using the common utils
|
||||
let greeting = common::get_greeting("User");
|
||||
print(greeting);
|
||||
|
||||
// Get calendar events using the calendar controller
|
||||
let events = calendar::get_all_events();
|
||||
|
||||
// Print the events using the common utils format function
|
||||
print("\nCalendar Events:");
|
||||
for event in events {
|
||||
let formatted = common::format_event(event);
|
||||
print(formatted);
|
||||
}
|
||||
|
||||
// Return a success message
|
||||
"All imports worked correctly!"
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
// Common utility functions used across the application
|
||||
|
||||
/// Returns a greeting message for the given name
|
||||
fn get_greeting(name) {
|
||||
return `Hello, ${name}! Welcome to the Rhai relative imports example.`;
|
||||
}
|
||||
|
||||
/// Formats an event object into a readable string
|
||||
fn format_event(event) {
|
||||
return `- ${event.title} on ${event.date} at ${event.time} (${event.location})`;
|
||||
}
|
||||
|
||||
/// Utility function to check if a date is in the future
|
||||
fn is_future_date(date_str) {
|
||||
// Simple implementation for the example
|
||||
// In a real application, you would parse the date and compare with current date
|
||||
return true;
|
||||
}
|
58
rhai_system/examples/relative_imports/src/main.rs
Normal file
58
rhai_system/examples/relative_imports/src/main.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use std::path::Path;
|
||||
use rhai_factory::{RhaiFactory, RelativeFileModuleResolver};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialize the logger with debug level
|
||||
std::env::set_var("RUST_LOG", "debug");
|
||||
env_logger::init();
|
||||
|
||||
println!("Starting Relative Imports Example...");
|
||||
|
||||
// Get the paths to our script files
|
||||
let scripts_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("scripts");
|
||||
let main_script_path = scripts_dir.join("main.rhai");
|
||||
|
||||
println!("Scripts directory: {:?}", scripts_dir);
|
||||
println!("Main script path: {:?}", main_script_path);
|
||||
|
||||
// Create a RhaiFactory instance
|
||||
let factory = RhaiFactory::new();
|
||||
|
||||
// Method 1: Using the RhaiFactory which now uses RelativeFileModuleResolver by default
|
||||
println!("\nMethod 1: Using RhaiFactory with RelativeFileModuleResolver (default)");
|
||||
let ast = factory.compile_modules(&[&main_script_path], Some(&scripts_dir))?;
|
||||
let engine = factory.create_engine();
|
||||
|
||||
// Evaluate the main script
|
||||
let result: String = engine.eval_ast(&ast)?;
|
||||
println!("Result: {}", result);
|
||||
|
||||
// Method 2: Creating an engine manually with RelativeFileModuleResolver
|
||||
println!("\nMethod 2: Creating an engine manually with RelativeFileModuleResolver");
|
||||
let mut manual_engine = rhai::Engine::new();
|
||||
manual_engine.set_module_resolver(RelativeFileModuleResolver::new_with_path(&scripts_dir));
|
||||
|
||||
// Evaluate the main script directly
|
||||
let result: String = manual_engine.eval_file(main_script_path)?;
|
||||
println!("Result: {}", result);
|
||||
|
||||
// Demonstrate hot reloading capability
|
||||
println!("\nDemonstrating hot reload capability:");
|
||||
println!("1. The engine will watch for changes to the script files");
|
||||
println!("2. If you modify any of the script files, the changes will be detected");
|
||||
println!("3. The engine will automatically reload the modified scripts");
|
||||
|
||||
// Create a hot reloadable engine
|
||||
let (hot_engine, hot_ast, hot_reload_handle) =
|
||||
factory.create_hot_reloadable_rhai_engine(&[&main_script_path], Some(&scripts_dir))?;
|
||||
|
||||
// Evaluate the main script with hot reloading
|
||||
let result: String = hot_engine.eval_ast(&hot_ast.read().unwrap())?;
|
||||
println!("Hot reload result: {}", result);
|
||||
|
||||
println!("\nExample completed successfully!");
|
||||
println!("This example demonstrates how the RelativeFileModuleResolver makes imports work");
|
||||
println!("relative to the file importing them, rather than just relative to a fixed base path.");
|
||||
|
||||
Ok(())
|
||||
}
|
702
rhai_system/project_structure.md
Normal file
702
rhai_system/project_structure.md
Normal file
@ -0,0 +1,702 @@
|
||||
# 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
|
69
rhai_system/src/factory.rs
Normal file
69
rhai_system/src/factory.rs
Normal file
@ -0,0 +1,69 @@
|
||||
use rhai::Engine;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use crate::system::System;
|
||||
use crate::hot_reload::hot_reload_callback;
|
||||
|
||||
/// Creates a hot-reloadable system from script files
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `script_paths` - A slice of paths to Rhai script files
|
||||
/// * `main_script_index` - Optional index of the main script in the paths slice. If None, the first script is used.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A Result containing either the System or an error
|
||||
pub fn create_hot_reloadable_system<P: AsRef<std::path::Path> + Clone>(
|
||||
script_paths: &[P],
|
||||
main_script_index: Option<usize>
|
||||
) -> Result<System, Box<dyn std::error::Error>> {
|
||||
if script_paths.is_empty() {
|
||||
return Err("No script paths provided".into());
|
||||
}
|
||||
|
||||
// Determine which script is the main script
|
||||
let main_index = main_script_index.unwrap_or(0);
|
||||
if main_index >= script_paths.len() {
|
||||
return Err(format!("Invalid main script index: {}, max index: {}", main_index, script_paths.len() - 1).into());
|
||||
}
|
||||
|
||||
// Create a new engine
|
||||
let engine = Engine::new();
|
||||
|
||||
// Compile the main script first
|
||||
let main_script_path = script_paths[main_index].as_ref();
|
||||
let mut combined_ast = engine.compile_file(main_script_path.to_path_buf())?;
|
||||
|
||||
// Compile and merge all other scripts
|
||||
for (i, script_path) in script_paths.iter().enumerate() {
|
||||
if i == main_index {
|
||||
continue; // Skip the main script as it's already compiled
|
||||
}
|
||||
|
||||
// Compile the additional script
|
||||
let path = script_path.as_ref();
|
||||
let ast = engine.compile_file(path.to_path_buf())?;
|
||||
|
||||
// Merge the AST with the main AST
|
||||
// This appends statements and functions from the additional script
|
||||
// Functions with the same name and arity will override previous definitions
|
||||
combined_ast = combined_ast.merge(&ast);
|
||||
}
|
||||
|
||||
// Wrap the combined AST in a thread-safe container
|
||||
let shared_ast = Arc::new(RwLock::new(combined_ast));
|
||||
|
||||
// Convert script paths to PathBuf
|
||||
let script_paths_vec: Vec<std::path::PathBuf> = script_paths.iter()
|
||||
.map(|path| path.as_ref().to_path_buf())
|
||||
.collect();
|
||||
|
||||
// Create the system with the engine, AST, and script paths
|
||||
let system = System::new(engine, shared_ast, script_paths_vec);
|
||||
|
||||
// Watch for script file change using the hot_reload_callback
|
||||
system.watch(hot_reload_callback)?;
|
||||
|
||||
// Return a thread-safe version of the system
|
||||
Ok(system.clone_for_thread())
|
||||
}
|
15
rhai_system/src/hot_reload.rs
Normal file
15
rhai_system/src/hot_reload.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use crate::system::System;
|
||||
|
||||
/// Callback function for hot reloading a script
|
||||
///
|
||||
/// This function is called when a script file is modified.
|
||||
/// It compiles the new script and replaces the old AST with the new one.
|
||||
pub fn hot_reload_callback(sys: &System, file: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Compile the new script
|
||||
let ast = sys.engine.compile_file(file.into())?;
|
||||
|
||||
// Hot reload - just replace the old script!
|
||||
*sys.script.write().unwrap() += ast;
|
||||
|
||||
Ok(())
|
||||
}
|
41
rhai_system/src/lib.rs
Normal file
41
rhai_system/src/lib.rs
Normal file
@ -0,0 +1,41 @@
|
||||
//! A thread-safe system for creating and managing Rhai script engines.
|
||||
//!
|
||||
//! This crate provides a system for creating thread-safe Rhai engines with
|
||||
//! pre-compiled scripts. It supports hot reloading of scripts and handles
|
||||
//! multiple script files.
|
||||
|
||||
mod hot_reload;
|
||||
mod system;
|
||||
mod factory;
|
||||
|
||||
pub use system::System;
|
||||
pub use factory::create_hot_reloadable_system;
|
||||
pub use hot_reload::hot_reload_callback;
|
||||
|
||||
/// Re-export commonly used Rhai types for convenience
|
||||
pub use rhai::{Engine, AST, Scope, Module};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// Test fixture for common setup
|
||||
struct TestFixture {
|
||||
engine: Engine,
|
||||
}
|
||||
|
||||
impl TestFixture {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
engine: Engine::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn engine_can_evaluate_simple_expressions() {
|
||||
let fixture = TestFixture::new();
|
||||
let result: i64 = fixture.engine.eval("40 + 2").unwrap();
|
||||
assert_eq!(result, 42);
|
||||
}
|
||||
}
|
101
rhai_system/src/system.rs
Normal file
101
rhai_system/src/system.rs
Normal file
@ -0,0 +1,101 @@
|
||||
use rhai::{Engine, Scope, AST};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
use std::path::PathBuf;
|
||||
use notify::{Watcher, RecursiveMode};
|
||||
use std::sync::mpsc::channel;
|
||||
|
||||
/// System struct to encapsulate the engine and script AST
|
||||
pub struct System {
|
||||
pub engine: Engine,
|
||||
pub script: Arc<RwLock<AST>>,
|
||||
pub script_paths: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl System {
|
||||
/// Create a new System with the given script
|
||||
pub fn new(engine: Engine, script: Arc<RwLock<AST>>, script_paths: Vec<PathBuf>) -> Self {
|
||||
Self { engine, script, script_paths }
|
||||
}
|
||||
|
||||
/// Execute a function from the script
|
||||
pub fn call_fn<T: Clone + Send + Sync + 'static>(&self, fn_name: &str, args: impl rhai::FuncArgs) -> Result<T, Box<dyn std::error::Error>> {
|
||||
let mut scope = Scope::new();
|
||||
let ast_guard = self.script.read().unwrap();
|
||||
let result = self.engine.call_fn(&mut scope, &ast_guard, fn_name, args)?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Get a reference to the shared AST
|
||||
pub fn get_script(&self) -> &Arc<RwLock<AST>> {
|
||||
&self.script
|
||||
}
|
||||
|
||||
/// Clone this system for another thread
|
||||
pub fn clone_for_thread(&self) -> Self {
|
||||
Self {
|
||||
engine: Engine::new(),
|
||||
script: Arc::clone(&self.script),
|
||||
script_paths: self.script_paths.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Watch for script file changes and automatically reload the AST
|
||||
pub fn watch<F>(&self, callback: F) -> Result<(), Box<dyn std::error::Error>>
|
||||
where
|
||||
F: Fn(&System, &str) -> Result<(), Box<dyn std::error::Error>> + Send + 'static
|
||||
{
|
||||
if self.script_paths.is_empty() {
|
||||
return Err("No script paths available to watch".into());
|
||||
}
|
||||
|
||||
let system_clone = self.clone_for_thread();
|
||||
|
||||
// Create a channel to receive file system events
|
||||
let (tx, rx) = channel();
|
||||
|
||||
// Create a watcher that will watch the specified paths for changes
|
||||
let mut watcher = notify::recommended_watcher(tx)?;
|
||||
|
||||
// Clone script paths for the thread
|
||||
let script_paths = self.script_paths.clone();
|
||||
|
||||
// Watch all script files for changes
|
||||
for script_path in &script_paths {
|
||||
watcher.watch(script_path, RecursiveMode::NonRecursive)?;
|
||||
println!("🔍 Watching for changes to script file: {:?}", script_path);
|
||||
}
|
||||
|
||||
// Start a thread to handle file system events
|
||||
thread::spawn(move || {
|
||||
// Move watcher into the thread to keep it alive
|
||||
let _watcher = watcher;
|
||||
|
||||
loop {
|
||||
match rx.recv() {
|
||||
Ok(event) => {
|
||||
println!("📝 Detected file system event: {:?}", event);
|
||||
|
||||
// Extract the path from the event
|
||||
// The event is a Result<Event, Error>, so we need to unwrap it first
|
||||
if let Ok(event_data) = event {
|
||||
if let Some(path) = event_data.paths.first() {
|
||||
// Convert path to string
|
||||
if let Some(path_str) = path.to_str() {
|
||||
// Call the callback with the system and script path
|
||||
match callback(&system_clone, path_str) {
|
||||
Ok(_) => println!("✅ Script reloaded successfully"),
|
||||
Err(err) => println!("❌ Error reloading script: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => println!("❌ Error receiving file system event: {:?}", e),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
547
rhai_system/tera_integration.md
Normal file
547
rhai_system/tera_integration.md
Normal file
@ -0,0 +1,547 @@
|
||||
# Tera Engine Factory with Hot Reloadable Rhai Integration
|
||||
|
||||
## Overview
|
||||
|
||||
We'll create a `TeraFactory` module that provides a factory for creating Tera template engines with integrated Rhai scripting support. The factory will:
|
||||
|
||||
1. Create Tera engines with specified template directories
|
||||
2. Integrate with the hot reloadable Rhai AST from the `RhaiFactory`
|
||||
3. Allow Rhai functions to be called from Tera templates
|
||||
4. Automatically update available functions when Rhai scripts are hot reloaded
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[TeraFactory] --> B[create_tera_engine]
|
||||
A --> C[create_tera_with_rhai]
|
||||
|
||||
C --> D[Tera Engine with Rhai Functions]
|
||||
|
||||
E[RhaiFactory] --> F[HotReloadableAST]
|
||||
F --> C
|
||||
|
||||
G[RhaiFunctionAdapter] --> D
|
||||
H[Template Directories] --> B
|
||||
```
|
||||
|
||||
## Component Details
|
||||
|
||||
### 1. TeraFactory Module Structure
|
||||
|
||||
```
|
||||
tera_factory/
|
||||
├── Cargo.toml # Dependencies including tera and rhai_factory
|
||||
└── src/
|
||||
├── lib.rs # Main module exports and unit tests
|
||||
├── factory.rs # Factory implementation and unit tests
|
||||
├── error.rs # Custom error types and unit tests
|
||||
└── function_adapter.rs # Rhai function adapter for Tera
|
||||
```
|
||||
|
||||
### 2. TeraFactory Implementation
|
||||
|
||||
The core factory will provide these main functions:
|
||||
|
||||
1. **create_tera_engine(template_dirs)** - Creates a basic Tera engine with the specified template directories
|
||||
2. **create_tera_with_rhai(template_dirs, hot_ast)** - Creates a Tera engine with Rhai function integration using a hot reloadable AST
|
||||
|
||||
### 3. RhaiFunctionAdapter
|
||||
|
||||
We'll enhance the existing `RhaiFunctionAdapter` to work with the hot reloadable AST:
|
||||
|
||||
```rust
|
||||
/// Thread-safe adapter to use Rhai functions in Tera templates with hot reload support
|
||||
pub struct RhaiFunctionAdapter {
|
||||
fn_name: String,
|
||||
hot_ast: Arc<RwLock<AST>>,
|
||||
}
|
||||
|
||||
impl TeraFunction for RhaiFunctionAdapter {
|
||||
fn call(&self, args: &HashMap<String, Value>) -> TeraResult<Value> {
|
||||
// Convert args from Tera into Rhai's Dynamic
|
||||
let mut scope = Scope::new();
|
||||
for (key, value) in args {
|
||||
// Convert Tera value to Rhai Dynamic
|
||||
let dynamic = convert_tera_to_rhai(value);
|
||||
scope.push_dynamic(key.clone(), dynamic);
|
||||
}
|
||||
|
||||
// Create a new engine for each call
|
||||
let engine = Engine::new();
|
||||
|
||||
// Get a read lock on the AST
|
||||
let ast = self.hot_ast.read().unwrap();
|
||||
|
||||
// Call the function using the latest AST
|
||||
let result = engine
|
||||
.call_fn::<Dynamic>(&mut scope, &ast, &self.fn_name, ())
|
||||
.map_err(|e| tera::Error::msg(format!("Rhai error: {}", e)))?;
|
||||
|
||||
// Convert Rhai result to Tera value
|
||||
let tera_value = convert_rhai_to_tera(&result);
|
||||
Ok(tera_value)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. TeraFactory API
|
||||
|
||||
```rust
|
||||
pub struct TeraFactory {
|
||||
// Configuration options
|
||||
}
|
||||
|
||||
impl TeraFactory {
|
||||
/// Create a new TeraFactory with default settings
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
/// Create a Tera engine with the specified template directories
|
||||
pub fn create_tera_engine<P: AsRef<Path>>(&self, template_dirs: &[P])
|
||||
-> Result<Tera, TeraFactoryError> {
|
||||
// Create a Tera engine with the specified template directories
|
||||
}
|
||||
|
||||
/// Create a Tera engine with Rhai function integration
|
||||
pub fn create_tera_with_rhai<P: AsRef<Path>>(
|
||||
&self,
|
||||
template_dirs: &[P],
|
||||
hot_ast: Arc<RwLock<AST>>
|
||||
) -> Result<Tera, TeraFactoryError> {
|
||||
// Create a Tera engine with Rhai function integration
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### 1. Creating a Tera Engine
|
||||
|
||||
```rust
|
||||
impl TeraFactory {
|
||||
pub fn create_tera_engine<P: AsRef<Path>>(&self, template_dirs: &[P])
|
||||
-> Result<Tera, TeraFactoryError> {
|
||||
let mut tera = Tera::default();
|
||||
|
||||
// Add templates from each directory
|
||||
for template_dir in template_dirs {
|
||||
let pattern = format!("{}/**/*.html", template_dir.as_ref().display());
|
||||
match Tera::parse(&pattern) {
|
||||
Ok(parsed_tera) => {
|
||||
tera.extend(&parsed_tera).map_err(|e| {
|
||||
TeraFactoryError::new(format!("Failed to extend Tera with templates: {}", e))
|
||||
})?;
|
||||
}
|
||||
Err(e) => {
|
||||
// If glob pattern fails, try to find individual HTML files
|
||||
let dir_path = template_dir.as_ref();
|
||||
if let Ok(entries) = std::fs::read_dir(dir_path) {
|
||||
for entry in entries.filter_map(Result::ok) {
|
||||
let path = entry.path();
|
||||
if path.extension().map_or(false, |ext| ext == "html") {
|
||||
let name = path.file_name().unwrap().to_string_lossy().to_string();
|
||||
if let Ok(content) = std::fs::read_to_string(&path) {
|
||||
tera.add_raw_template(&name, &content).map_err(|e| {
|
||||
TeraFactoryError::new(format!("Failed to add template {}: {}", name, e))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(TeraFactoryError::new(format!(
|
||||
"Failed to parse templates from {} and could not read directory: {}",
|
||||
dir_path.display(), e
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(tera)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Integrating with Rhai
|
||||
|
||||
```rust
|
||||
impl TeraFactory {
|
||||
pub fn create_tera_with_rhai<P: AsRef<Path>>(
|
||||
&self,
|
||||
template_dirs: &[P],
|
||||
hot_ast: Arc<RwLock<AST>>
|
||||
) -> Result<Tera, TeraFactoryError> {
|
||||
// Create a basic Tera engine
|
||||
let mut tera = self.create_tera_engine(template_dirs)?;
|
||||
|
||||
// Get a read lock on the AST to register functions
|
||||
let ast = hot_ast.read().unwrap();
|
||||
|
||||
// Register all functions from the AST
|
||||
for fn_def in ast.iter_functions() {
|
||||
let fn_name = fn_def.name.to_string();
|
||||
|
||||
// Create an adapter for this function
|
||||
let adapter = RhaiFunctionAdapter {
|
||||
fn_name: fn_name.clone(),
|
||||
hot_ast: Arc::clone(&hot_ast),
|
||||
};
|
||||
|
||||
// Register the function with Tera
|
||||
tera.register_function(&fn_name, adapter);
|
||||
}
|
||||
|
||||
Ok(tera)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Error Handling
|
||||
|
||||
```rust
|
||||
#[derive(Debug)]
|
||||
pub struct TeraFactoryError {
|
||||
message: String,
|
||||
source: Option<Box<dyn Error + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl TeraFactoryError {
|
||||
pub fn new(message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
message: message.into(),
|
||||
source: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_source(mut self, source: impl Error + Send + Sync + 'static) -> Self {
|
||||
self.source = Some(Box::new(source));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TeraFactoryError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.message)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for TeraFactoryError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
self.source.as_ref().map(|s| s.as_ref() as &(dyn Error + 'static))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Value Conversion
|
||||
|
||||
```rust
|
||||
/// Convert a Tera value to a Rhai Dynamic value
|
||||
fn convert_tera_to_rhai(value: &Value) -> Dynamic {
|
||||
match value {
|
||||
Value::Null => Dynamic::UNIT,
|
||||
Value::Bool(b) => Dynamic::from(*b),
|
||||
Value::Number(n) => {
|
||||
if n.is_i64() {
|
||||
Dynamic::from(n.as_i64().unwrap())
|
||||
} else if n.is_u64() {
|
||||
Dynamic::from(n.as_u64().unwrap())
|
||||
} else {
|
||||
Dynamic::from(n.as_f64().unwrap())
|
||||
}
|
||||
},
|
||||
Value::String(s) => Dynamic::from(s.clone()),
|
||||
Value::Array(arr) => {
|
||||
let mut rhai_array = Vec::new();
|
||||
for item in arr {
|
||||
rhai_array.push(convert_tera_to_rhai(item));
|
||||
}
|
||||
Dynamic::from(rhai_array)
|
||||
},
|
||||
Value::Object(obj) => {
|
||||
let mut rhai_map = rhai::Map::new();
|
||||
for (key, value) in obj {
|
||||
rhai_map.insert(key.clone().into(), convert_tera_to_rhai(value));
|
||||
}
|
||||
Dynamic::from(rhai_map)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a Rhai Dynamic value to a Tera value
|
||||
fn convert_rhai_to_tera(value: &Dynamic) -> Value {
|
||||
if value.is_unit() {
|
||||
Value::Null
|
||||
} else if value.is_bool() {
|
||||
Value::Bool(value.as_bool().unwrap())
|
||||
} else if value.is_i64() {
|
||||
Value::Number(serde_json::Number::from(value.as_i64().unwrap()))
|
||||
} else if value.is_f64() {
|
||||
// This is a bit tricky as serde_json::Number doesn't have a direct from_f64
|
||||
let f = value.as_f64().unwrap();
|
||||
serde_json::to_value(f).unwrap()
|
||||
} else if value.is_string() {
|
||||
Value::String(value.to_string())
|
||||
} else if value.is_array() {
|
||||
let arr = value.clone().into_array().unwrap();
|
||||
let mut tera_array = Vec::new();
|
||||
for item in arr {
|
||||
tera_array.push(convert_rhai_to_tera(&item));
|
||||
}
|
||||
Value::Array(tera_array)
|
||||
} else if value.is_map() {
|
||||
let map = value.clone().into_map().unwrap();
|
||||
let mut tera_object = serde_json::Map::new();
|
||||
for (key, value) in map {
|
||||
tera_object.insert(key.to_string(), convert_rhai_to_tera(&value));
|
||||
}
|
||||
Value::Object(tera_object)
|
||||
} else {
|
||||
// For any other type, convert to string
|
||||
Value::String(value.to_string())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### 1. Unit Tests
|
||||
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn create_tera_engine_with_valid_directories() {
|
||||
let factory = TeraFactory::new();
|
||||
let template_dirs = vec!["tests/templates"];
|
||||
|
||||
let result = factory.create_tera_engine(&template_dirs);
|
||||
assert!(result.is_ok());
|
||||
|
||||
let tera = result.unwrap();
|
||||
assert!(tera.get_template_names().count() > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_tera_with_rhai_registers_functions() {
|
||||
let rhai_factory = Arc::new(RhaiFactory::with_caching());
|
||||
let tera_factory = TeraFactory::new();
|
||||
|
||||
// Compile a script with a simple function
|
||||
let script = "fn sum(a, b) { a + b }";
|
||||
let engine = rhai_factory.create_engine();
|
||||
let ast = engine.compile(script).unwrap();
|
||||
let hot_ast = Arc::new(RwLock::new(ast));
|
||||
|
||||
// Create a Tera engine with Rhai integration
|
||||
let template_dirs = vec!["tests/templates"];
|
||||
let result = tera_factory.create_tera_with_rhai(&template_dirs, hot_ast);
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Verify the function is registered
|
||||
let tera = result.unwrap();
|
||||
let mut context = tera::Context::new();
|
||||
context.insert("a", &10);
|
||||
context.insert("b", &32);
|
||||
|
||||
let rendered = tera.render("function_test.html", &context).unwrap();
|
||||
assert_eq!(rendered.trim(), "42");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hot_reload_updates_functions() {
|
||||
let rhai_factory = Arc::new(RhaiFactory::with_caching());
|
||||
let tera_factory = TeraFactory::new();
|
||||
|
||||
// Create a temporary script file
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let script_path = temp_dir.path().join("test.rhai");
|
||||
std::fs::write(&script_path, "fn sum(a, b) { a + b }").unwrap();
|
||||
|
||||
// Compile the script
|
||||
let ast = rhai_factory.compile_modules(&[&script_path], None).unwrap();
|
||||
let hot_ast = Arc::new(RwLock::new(ast));
|
||||
|
||||
// Enable hot reloading
|
||||
let handle = rhai_factory.enable_hot_reload(
|
||||
hot_ast.clone(),
|
||||
&[&script_path],
|
||||
None,
|
||||
None
|
||||
).unwrap();
|
||||
|
||||
// Create a Tera engine with Rhai integration
|
||||
let template_dirs = vec!["tests/templates"];
|
||||
let tera = tera_factory.create_tera_with_rhai(&template_dirs, hot_ast.clone()).unwrap();
|
||||
|
||||
// Render the template with the initial function
|
||||
let mut context = tera::Context::new();
|
||||
context.insert("a", &10);
|
||||
context.insert("b", &32);
|
||||
let rendered = tera.render("function_test.html", &context).unwrap();
|
||||
assert_eq!(rendered.trim(), "42");
|
||||
|
||||
// Modify the script to change the function
|
||||
std::fs::write(&script_path, "fn sum(a, b) { (a + b) * 2 }").unwrap();
|
||||
|
||||
// Wait for the file system to register the change
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
// Check for changes
|
||||
rhai_factory.check_for_changes().unwrap();
|
||||
|
||||
// Render the template again with the updated function
|
||||
let rendered = tera.render("function_test.html", &context).unwrap();
|
||||
assert_eq!(rendered.trim(), "84");
|
||||
|
||||
// Disable hot reloading
|
||||
rhai_factory.disable_hot_reload(handle);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Integration Tests
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn integration_test_tera_with_hot_reloadable_rhai() {
|
||||
// Create the factories
|
||||
let rhai_factory = Arc::new(RhaiFactory::with_caching());
|
||||
let tera_factory = TeraFactory::new();
|
||||
|
||||
// Set up test directories
|
||||
let scripts_dir = PathBuf::from("tests/rhai_scripts");
|
||||
let templates_dir = PathBuf::from("tests/templates");
|
||||
|
||||
// Compile the initial script
|
||||
let script_path = scripts_dir.join("math.rhai");
|
||||
let ast = rhai_factory.compile_modules(&[&script_path], Some(&scripts_dir)).unwrap();
|
||||
let hot_ast = Arc::new(RwLock::new(ast));
|
||||
|
||||
// Enable hot reloading
|
||||
let handle = rhai_factory.enable_hot_reload(
|
||||
hot_ast.clone(),
|
||||
&[&script_path],
|
||||
Some(&scripts_dir),
|
||||
None
|
||||
).unwrap();
|
||||
|
||||
// Create a Tera engine with Rhai integration
|
||||
let tera = tera_factory.create_tera_with_rhai(&[&templates_dir], hot_ast.clone()).unwrap();
|
||||
|
||||
// Render the template with the initial function
|
||||
let mut context = tera::Context::new();
|
||||
context.insert("a", &20);
|
||||
context.insert("b", &22);
|
||||
let rendered = tera.render("math.html", &context).unwrap();
|
||||
assert_eq!(rendered.trim(), "42");
|
||||
|
||||
// Modify the script to change the function
|
||||
let modified_script = r#"
|
||||
fn sum(a, b) {
|
||||
// Return twice the sum
|
||||
(a + b) * 2
|
||||
}
|
||||
"#;
|
||||
std::fs::write(&script_path, modified_script).unwrap();
|
||||
|
||||
// Wait for the file system to register the change
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
// Check for changes
|
||||
rhai_factory.check_for_changes().unwrap();
|
||||
|
||||
// Render the template again with the updated function
|
||||
let rendered = tera.render("math.html", &context).unwrap();
|
||||
assert_eq!(rendered.trim(), "84");
|
||||
|
||||
// Disable hot reloading
|
||||
rhai_factory.disable_hot_reload(handle);
|
||||
}
|
||||
```
|
||||
|
||||
## Example Usage
|
||||
|
||||
Here's a complete example of how to use the TeraFactory with hot reloadable Rhai integration:
|
||||
|
||||
```rust
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use rhai_factory::{RhaiFactory, HotReloadableAST};
|
||||
use tera_factory::TeraFactory;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create the factories
|
||||
let rhai_factory = Arc::new(RhaiFactory::with_caching());
|
||||
let tera_factory = TeraFactory::new();
|
||||
|
||||
// Set up directories
|
||||
let scripts_dir = PathBuf::from("scripts");
|
||||
let templates_dir = PathBuf::from("templates");
|
||||
|
||||
// Compile the initial script
|
||||
let script_path = scripts_dir.join("math.rhai");
|
||||
let ast = rhai_factory.compile_modules(&[&script_path], Some(&scripts_dir))?;
|
||||
let hot_ast = Arc::new(RwLock::new(ast));
|
||||
|
||||
// Enable hot reloading
|
||||
let handle = rhai_factory.enable_hot_reload(
|
||||
hot_ast.clone(),
|
||||
&[&script_path],
|
||||
Some(&scripts_dir),
|
||||
Some(Box::new(|| println!("Script reloaded!")))
|
||||
)?;
|
||||
|
||||
// Create a Tera engine with Rhai integration
|
||||
let tera = tera_factory.create_tera_with_rhai(&[&templates_dir], hot_ast.clone())?;
|
||||
|
||||
// Application loop
|
||||
loop {
|
||||
// Check for script changes
|
||||
rhai_factory.check_for_changes()?;
|
||||
|
||||
// Render a template
|
||||
let mut context = tera::Context::new();
|
||||
context.insert("a", &20);
|
||||
context.insert("b", &22);
|
||||
let rendered = tera.render("math.html", &context)?;
|
||||
println!("Rendered template: {}", rendered);
|
||||
|
||||
// Wait a bit before checking again
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
|
||||
// In a real application, you would break out of this loop when done
|
||||
}
|
||||
|
||||
// Disable hot reloading when done
|
||||
rhai_factory.disable_hot_reload(handle);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Test Files
|
||||
|
||||
### 1. Rhai Script (tests/rhai_scripts/math.rhai)
|
||||
|
||||
```rhai
|
||||
// Initial version of the sum function
|
||||
fn sum(a, b) {
|
||||
a + b
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Tera Template (tests/templates/math.html)
|
||||
|
||||
```html
|
||||
{{ sum(a, b) }}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
The TeraFactory with hot reloadable Rhai integration provides a powerful way to create dynamic templates that can call Rhai functions. The hot reload feature allows these functions to be updated without restarting the application, making it ideal for development environments and systems where behavior needs to be modified dynamically.
|
||||
|
||||
By leveraging the hot reload feature of the RhaiFactory, the TeraFactory can automatically update the available functions when Rhai scripts change, ensuring that templates always use the latest version of the functions.
|
52
rhai_system/tests/common/mod.rs
Normal file
52
rhai_system/tests/common/mod.rs
Normal file
@ -0,0 +1,52 @@
|
||||
//! Common utilities for integration tests.
|
||||
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use rand;
|
||||
|
||||
/// Test fixture for integration tests.
|
||||
pub struct TestFixture {
|
||||
pub scripts_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl TestFixture {
|
||||
/// Create a new test fixture.
|
||||
pub fn new() -> Self {
|
||||
let scripts_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests")
|
||||
.join("rhai_scripts");
|
||||
|
||||
Self {
|
||||
scripts_dir,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the path to a test script.
|
||||
pub fn script_path(&self, name: &str) -> PathBuf {
|
||||
self.scripts_dir.join(name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a temporary test directory with a unique name.
|
||||
pub fn setup_test_dir(prefix: &str) -> PathBuf {
|
||||
let timestamp = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
|
||||
let test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests")
|
||||
.join("temp")
|
||||
.join(format!("{}_{}_{}", prefix, timestamp, rand::random::<u16>()));
|
||||
|
||||
fs::create_dir_all(&test_dir).unwrap();
|
||||
test_dir
|
||||
}
|
||||
|
||||
/// Clean up a temporary test directory.
|
||||
pub fn cleanup_test_dir(dir: PathBuf) {
|
||||
if dir.exists() && dir.starts_with(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests").join("temp")) {
|
||||
let _ = fs::remove_dir_all(dir);
|
||||
}
|
||||
}
|
125
rhai_system/tests/hot_reload_callback_tests.rs
Normal file
125
rhai_system/tests/hot_reload_callback_tests.rs
Normal file
@ -0,0 +1,125 @@
|
||||
use std::fs;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use rhai::Engine;
|
||||
use rhai_system::{System, hot_reload_callback};
|
||||
|
||||
#[test]
|
||||
fn test_hot_reload_callback() {
|
||||
// Create temporary script files
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let script_path = temp_dir.path().join("test_script.rhai");
|
||||
|
||||
// Write initial script content
|
||||
let initial_script = r#"
|
||||
fn greet(name) {
|
||||
"Hello, " + name + "! This is the original script."
|
||||
}
|
||||
|
||||
fn add(a, b) {
|
||||
a + b
|
||||
}
|
||||
"#;
|
||||
fs::write(&script_path, initial_script).unwrap();
|
||||
|
||||
// Create engine and compile initial script
|
||||
let engine = Engine::new();
|
||||
let ast = engine.compile_file(script_path.clone()).unwrap();
|
||||
let shared_ast = Arc::new(RwLock::new(ast));
|
||||
|
||||
// Create a system with the initial script
|
||||
let script_paths = vec![script_path.clone()];
|
||||
let system = System::new(engine, shared_ast, script_paths);
|
||||
|
||||
// Test initial script functionality
|
||||
let result: String = system.call_fn("greet", ("User",)).unwrap();
|
||||
assert_eq!(result, "Hello, User! This is the original script.");
|
||||
|
||||
let add_result: i32 = system.call_fn("add", (40, 2)).unwrap();
|
||||
assert_eq!(add_result, 42);
|
||||
|
||||
// Write modified script content with new functions
|
||||
let modified_script = r#"
|
||||
fn greet(name) {
|
||||
"Hello, " + name + "! This is the MODIFIED script!"
|
||||
}
|
||||
|
||||
fn add(a, b) {
|
||||
a + b
|
||||
}
|
||||
|
||||
fn multiply(a, b) {
|
||||
a * b
|
||||
}
|
||||
"#;
|
||||
fs::write(&script_path, modified_script).unwrap();
|
||||
|
||||
// Call the hot reload callback
|
||||
hot_reload_callback(&system, script_path.to_str().unwrap()).unwrap();
|
||||
|
||||
// Test that the script was updated
|
||||
let result: String = system.call_fn("greet", ("User",)).unwrap();
|
||||
assert_eq!(result, "Hello, User! This is the MODIFIED script!");
|
||||
|
||||
// Test that the new function is available
|
||||
let multiply_result: i32 = system.call_fn("multiply", (6, 7)).unwrap();
|
||||
assert_eq!(multiply_result, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hot_reload_callback_with_syntax_error() {
|
||||
// Create temporary script files
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let script_path = temp_dir.path().join("test_script.rhai");
|
||||
|
||||
// Write initial script content
|
||||
let initial_script = r#"
|
||||
fn greet(name) {
|
||||
"Hello, " + name + "! This is the original script."
|
||||
}
|
||||
|
||||
fn add(a, b) {
|
||||
a + b
|
||||
}
|
||||
"#;
|
||||
fs::write(&script_path, initial_script).unwrap();
|
||||
|
||||
// Create engine and compile initial script
|
||||
let engine = Engine::new();
|
||||
let ast = engine.compile_file(script_path.clone()).unwrap();
|
||||
let shared_ast = Arc::new(RwLock::new(ast));
|
||||
|
||||
// Create a system with the initial script
|
||||
let script_paths = vec![script_path.clone()];
|
||||
let system = System::new(engine, shared_ast, script_paths);
|
||||
|
||||
// Test initial script functionality
|
||||
let result: String = system.call_fn("greet", ("User",)).unwrap();
|
||||
assert_eq!(result, "Hello, User! This is the original script.");
|
||||
|
||||
// Write modified script content with syntax error
|
||||
let modified_script = r#"
|
||||
fn greet(name) {
|
||||
"Hello, " + name + "! This is the MODIFIED script!"
|
||||
}
|
||||
|
||||
fn add(a, b) {
|
||||
a + b
|
||||
}
|
||||
|
||||
fn syntax_error() {
|
||||
// Missing closing brace
|
||||
if (true) {
|
||||
"This will cause a syntax error"
|
||||
}
|
||||
"#;
|
||||
fs::write(&script_path, modified_script).unwrap();
|
||||
|
||||
// Call the hot reload callback - it should return an error
|
||||
let result = hot_reload_callback(&system, script_path.to_str().unwrap());
|
||||
assert!(result.is_err());
|
||||
|
||||
// Test that the original script functionality is still available
|
||||
let result: String = system.call_fn("greet", ("User",)).unwrap();
|
||||
assert_eq!(result, "Hello, User! This is the original script.");
|
||||
}
|
9
rhai_system/tests/rhai_scripts/main.rhai
Normal file
9
rhai_system/tests/rhai_scripts/main.rhai
Normal file
@ -0,0 +1,9 @@
|
||||
// main.rhai - Main script that imports module1
|
||||
|
||||
import "module1" as m1;
|
||||
|
||||
// Call the calculate function from module1, which in turn calls multiply from module2
|
||||
let answer = m1::calculate();
|
||||
|
||||
// Return the answer
|
||||
answer
|
9
rhai_system/tests/rhai_scripts/module1.rhai
Normal file
9
rhai_system/tests/rhai_scripts/module1.rhai
Normal file
@ -0,0 +1,9 @@
|
||||
// module1.rhai - A simple module that imports module2
|
||||
|
||||
import "module2" as m2;
|
||||
|
||||
fn calculate() {
|
||||
// Call the multiply function from module2
|
||||
let result = m2::multiply(6, 7);
|
||||
result
|
||||
}
|
5
rhai_system/tests/rhai_scripts/module2.rhai
Normal file
5
rhai_system/tests/rhai_scripts/module2.rhai
Normal file
@ -0,0 +1,5 @@
|
||||
// module2.rhai - A simple module with a multiply function
|
||||
|
||||
fn multiply(x, y) {
|
||||
x * y
|
||||
}
|
@ -0,0 +1 @@
|
||||
fn get_value() { 84 ; ; }
|
@ -0,0 +1 @@
|
||||
fn get_value() { 84 ; ; }
|
@ -0,0 +1,2 @@
|
||||
import "module" as m;
|
||||
fn calculate() { 21 * m::get_multiplier() }
|
@ -0,0 +1 @@
|
||||
fn get_multiplier() { 4 }
|
@ -0,0 +1,2 @@
|
||||
import "module" as m;
|
||||
fn calculate() { 21 * m::get_multiplier() }
|
@ -0,0 +1 @@
|
||||
fn get_multiplier() { 4 }
|
@ -0,0 +1,2 @@
|
||||
import "module" as m;
|
||||
fn calculate() { 21 * m::get_multiplier() }
|
@ -0,0 +1 @@
|
||||
fn get_multiplier() { 4 }
|
@ -0,0 +1,2 @@
|
||||
import "module" as m;
|
||||
fn calculate() { 21 * m::get_multiplier() }
|
@ -0,0 +1 @@
|
||||
fn get_multiplier() { 4 }
|
@ -0,0 +1,2 @@
|
||||
fn get_multiplier() { 4 }
|
||||
fn calculate() { 21 * get_multiplier() }
|
@ -0,0 +1 @@
|
||||
// This is a secondary file that will be modified
|
@ -0,0 +1,2 @@
|
||||
fn get_multiplier() { 4 }
|
||||
fn calculate() { 21 * get_multiplier() }
|
@ -0,0 +1 @@
|
||||
// This is a secondary file that will be modified
|
@ -0,0 +1 @@
|
||||
fn get_value() { 84 }
|
@ -0,0 +1 @@
|
||||
fn get_value() { 84 }
|
Reference in New Issue
Block a user