270 lines
8.0 KiB
Markdown
270 lines
8.0 KiB
Markdown
# V Lang Actor Interface
|
|
|
|
This module provides a V lang port of the Rust actor trait interface for the Hero Baobab system. It enables implementing actors in V lang with a simple interface: implement `process_job` and use `spawn` to run the actor.
|
|
|
|
## Architecture
|
|
|
|
The V lang actor interface mirrors the Rust implementation with these key components:
|
|
|
|
```text
|
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
│ SyncActor │ │ AsyncActor │ │ MyCustomActor │
|
|
│ │ │ │ │ │
|
|
│ process_job() │ │ process_job() │ │ process_job() │
|
|
│ (sequential) │ │ (concurrent) │ │ (custom logic) │
|
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
│ │ │
|
|
└───────────────┬───────────────────────────────┘
|
|
│
|
|
┌───────▼───────┐
|
|
│ Actor Interface│
|
|
│ │
|
|
│ spawn_actor() │
|
|
│ process_job() │
|
|
│ config │
|
|
└───────────────┘
|
|
```
|
|
|
|
## Core Components
|
|
|
|
### Actor Interface
|
|
|
|
All actors must implement the `Actor` interface:
|
|
|
|
```v
|
|
pub interface Actor {
|
|
// Process a single job - must be implemented by concrete actors
|
|
process_job(job Job, mut redis_conn redisclient.Redis) !
|
|
|
|
// Get the actor type identifier
|
|
actor_type() string
|
|
|
|
// Get the actor ID
|
|
actor_id() string
|
|
|
|
// Get the Redis URL
|
|
redis_url() string
|
|
|
|
// Get the database path
|
|
db_path() string
|
|
|
|
// Check if tasks should be preserved
|
|
preserve_tasks() bool
|
|
}
|
|
```
|
|
|
|
### ActorConfig
|
|
|
|
Configuration structure for all actors:
|
|
|
|
```v
|
|
pub struct ActorConfig {
|
|
pub:
|
|
actor_id string
|
|
db_path string
|
|
redis_url string
|
|
preserve_tasks bool
|
|
default_timeout ?time.Duration // Optional timeout for async actors
|
|
}
|
|
```
|
|
|
|
### Job Structure
|
|
|
|
Jobs processed by actors:
|
|
|
|
```v
|
|
pub struct Job {
|
|
pub mut:
|
|
id string
|
|
caller_id string
|
|
context_id string
|
|
script string
|
|
status JobStatus
|
|
result string
|
|
error string
|
|
created_at time.Time
|
|
started_at time.Time
|
|
finished_at time.Time
|
|
}
|
|
```
|
|
|
|
## Built-in Actor Implementations
|
|
|
|
### SyncActor
|
|
|
|
Processes jobs sequentially, one at a time:
|
|
|
|
```v
|
|
import freeflowuniverse.herolib.lib.baobab.actor
|
|
|
|
// Create configuration
|
|
config := actor.new_actor_config(
|
|
'sync_actor_1',
|
|
'/path/to/database',
|
|
'redis://localhost:6379',
|
|
false
|
|
)
|
|
|
|
// Create and spawn sync actor
|
|
sync_actor := actor.new_sync_actor(config)!
|
|
mut shutdown_chan := chan bool{cap: 1}
|
|
go actor.spawn_actor(sync_actor, mut shutdown_chan)
|
|
|
|
// Later, shutdown the actor
|
|
shutdown_chan <- true
|
|
```
|
|
|
|
### AsyncActor
|
|
|
|
Processes jobs concurrently with timeout support:
|
|
|
|
```v
|
|
import time
|
|
import freeflowuniverse.herolib.lib.baobab.actor
|
|
|
|
// Create configuration with timeout
|
|
mut config := actor.new_actor_config(
|
|
'async_actor_1',
|
|
'/path/to/database',
|
|
'redis://localhost:6379',
|
|
false
|
|
)
|
|
config = config.with_default_timeout(time.Duration(300 * time.second))
|
|
|
|
// Create and spawn async actor
|
|
async_actor := actor.new_async_actor(config)!
|
|
mut shutdown_chan := chan bool{cap: 1}
|
|
go actor.spawn_actor(async_actor, mut shutdown_chan)
|
|
|
|
// Later, shutdown the actor
|
|
shutdown_chan <- true
|
|
```
|
|
|
|
## Creating Custom Actors
|
|
|
|
To implement a custom actor, simply implement the `Actor` interface:
|
|
|
|
```v
|
|
import freeflowuniverse.herolib.lib.baobab.actor
|
|
|
|
struct MyCustomActor {
|
|
pub:
|
|
config actor.ActorConfig
|
|
mut:
|
|
engine rhai.Engine
|
|
// Add your custom fields here
|
|
}
|
|
|
|
pub fn new_custom_actor(config actor.ActorConfig) !MyCustomActor {
|
|
mut engine := rhai.new_engine()!
|
|
|
|
return MyCustomActor{
|
|
config: config
|
|
engine: engine
|
|
}
|
|
}
|
|
|
|
// Implement the Actor interface
|
|
pub fn (mut actor MyCustomActor) process_job(job actor.Job, mut redis_conn redisclient.Redis) ! {
|
|
// Your custom job processing logic here
|
|
|
|
// Update job status to started
|
|
actor.update_job_status(mut redis_conn, job.id, .started)!
|
|
|
|
// Execute the script (or your custom logic)
|
|
result := actor.execute_job_with_engine(mut actor.engine, job, actor.config.db_path)!
|
|
|
|
// Update job status to finished and set result
|
|
actor.update_job_status(mut redis_conn, job.id, .finished)!
|
|
actor.set_job_result(mut redis_conn, job.id, result)!
|
|
|
|
// Clean up if needed
|
|
actor.cleanup_job(mut redis_conn, job.id, job.context_id, actor.config.preserve_tasks)!
|
|
}
|
|
|
|
// Implement required interface methods
|
|
pub fn (actor MyCustomActor) actor_type() string { return 'MyCustomActor' }
|
|
pub fn (actor MyCustomActor) actor_id() string { return actor.config.actor_id }
|
|
pub fn (actor MyCustomActor) redis_url() string { return actor.config.redis_url }
|
|
pub fn (actor MyCustomActor) db_path() string { return actor.config.db_path }
|
|
pub fn (actor MyCustomActor) preserve_tasks() bool { return actor.config.preserve_tasks }
|
|
|
|
// Usage
|
|
config := actor.new_actor_config('my_actor', '/db/path', 'redis://localhost:6379', false)
|
|
custom_actor := new_custom_actor(config)!
|
|
mut shutdown_chan := chan bool{cap: 1}
|
|
go actor.spawn_actor(custom_actor, mut shutdown_chan)
|
|
```
|
|
|
|
## Key Features
|
|
|
|
### Unified Interface
|
|
- Same `spawn_actor()` function works with all actor implementations
|
|
- Consistent configuration and lifecycle management
|
|
- Clean separation between actor logic and infrastructure
|
|
|
|
### Redis Integration
|
|
- Automatic Redis connection management
|
|
- Job polling from actor-specific queues (`hero:job:actor_queue:{actor_id}`)
|
|
- Job status tracking and result storage
|
|
- Optional job cleanup based on `preserve_tasks` setting
|
|
|
|
### Script Execution
|
|
- Rhai script engine integration
|
|
- Automatic job context setup (DB_PATH, CALLER_ID, CONTEXT_ID)
|
|
- Error handling and status updates
|
|
|
|
### Graceful Shutdown
|
|
- Channel-based shutdown signaling
|
|
- Proper cleanup of resources
|
|
- Support for cancelling running jobs (AsyncActor)
|
|
|
|
## Job Processing Flow
|
|
|
|
1. **Job Polling**: Actor polls Redis queue using BLPOP
|
|
2. **Job Loading**: Load job details from Redis hash
|
|
3. **Status Update**: Mark job as "started"
|
|
4. **Script Execution**: Execute Rhai script with job context
|
|
5. **Result Storage**: Store result or error in Redis
|
|
6. **Status Update**: Mark job as "finished" or "error"
|
|
7. **Cleanup**: Optionally remove job from Redis
|
|
|
|
## Error Handling
|
|
|
|
All operations include comprehensive error handling:
|
|
- Redis connection failures
|
|
- Job loading errors
|
|
- Script execution errors
|
|
- Timeout handling (AsyncActor)
|
|
- Graceful degradation and logging
|
|
|
|
## Migration from Rust
|
|
|
|
This V lang implementation provides the same functionality as the Rust version:
|
|
- ✅ Actor trait abstraction
|
|
- ✅ Sync and async actor implementations
|
|
- ✅ Unified spawn interface
|
|
- ✅ Redis job queue integration
|
|
- ✅ Rhai script execution
|
|
- ✅ Job lifecycle management
|
|
- ✅ Error handling and logging
|
|
- ✅ Graceful shutdown
|
|
|
|
## Dependencies
|
|
|
|
The actor interface depends on these herolib modules:
|
|
- `freeflowuniverse.herolib.clients.redisclient` - Redis operations
|
|
- `freeflowuniverse.herolib.data.rhai` - Script execution
|
|
- `freeflowuniverse.herolib.core.base` - Base utilities
|
|
|
|
## Usage Summary
|
|
|
|
To implement an actor in V lang:
|
|
|
|
1. **Create ActorConfig**: Configure actor ID, database path, Redis URL, etc.
|
|
2. **Implement Actor Interface**: Create a struct that implements the `Actor` interface
|
|
3. **Implement process_job()**: Add your job processing logic
|
|
4. **Use spawn()**: Call `spawn_actor()` to start the actor loop
|
|
|
|
That's it! The interface handles all the Redis polling, job management, and infrastructure concerns automatically.
|