use crate::{Aether, Value};
pub struct EnginePool {
engines: Vec<Aether>,
available: Vec<bool>,
}
impl EnginePool {
pub fn new(capacity: usize) -> Self {
let mut engines = Vec::with_capacity(capacity);
let available = vec![true; capacity];
for _ in 0..capacity {
engines.push(Aether::new());
}
Self { engines, available }
}
pub fn acquire(&mut self) -> PooledEngine {
for (i, &is_available) in self.available.iter().enumerate() {
if is_available {
self.available[i] = false;
let mut engine = std::mem::take(&mut self.engines[i]);
engine.evaluator.reset_env();
return PooledEngine {
engine: Some(engine),
pool_index: Some(i),
pool: self as *mut Self,
};
}
}
let engine = Aether::new();
PooledEngine {
engine: Some(engine),
pool_index: None,
pool: std::ptr::null_mut(),
}
}
fn return_engine(&mut self, index: usize, engine: Aether) {
self.engines[index] = engine;
self.available[index] = true;
}
pub fn capacity(&self) -> usize {
self.engines.len()
}
pub fn available(&self) -> usize {
self.available.iter().filter(|&&x| x).count()
}
}
pub struct PooledEngine {
engine: Option<Aether>,
pool_index: Option<usize>,
pool: *mut EnginePool,
}
impl PooledEngine {
pub fn eval(&mut self, code: &str) -> Result<Value, String> {
self.engine.as_mut().unwrap().eval(code)
}
pub fn cache_stats(&self) -> crate::cache::CacheStats {
self.engine.as_ref().unwrap().cache_stats()
}
pub fn clear_cache(&mut self) {
self.engine.as_mut().unwrap().clear_cache();
}
pub fn set_optimization(
&mut self,
constant_folding: bool,
dead_code: bool,
tail_recursion: bool,
) {
self.engine
.as_mut()
.unwrap()
.set_optimization(constant_folding, dead_code, tail_recursion);
}
#[cfg(feature = "async")]
pub async fn eval_async(&mut self, code: &str) -> Result<Value, String> {
tokio::task::yield_now().await;
self.eval(code)
}
}
impl Drop for PooledEngine {
fn drop(&mut self) {
if let Some(engine) = self.engine.take()
&& let Some(index) = self.pool_index
{
unsafe {
if !self.pool.is_null() {
(*self.pool).return_engine(index, engine);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pool_single_thread() {
let mut pool = EnginePool::new(2);
{
let mut engine = pool.acquire();
let result = engine.eval("Set X 10\n(X + 20)").unwrap();
assert_eq!(result.to_string(), "30");
}
{
let mut engine = pool.acquire();
let result = engine.eval("X");
assert!(result.is_err()); }
}
#[test]
fn test_pool_multiple_acquire() {
let mut pool = EnginePool::new(2);
for i in 0..10 {
let mut engine = pool.acquire();
let code = format!("Set X {}\n(X * 2)", i);
let result = engine.eval(&code).unwrap();
assert_eq!(result.to_string(), format!("{}", i * 2));
}
}
#[test]
fn test_pool_auto_return() {
let mut pool = EnginePool::new(2);
assert_eq!(pool.available(), 2);
let _engine1 = pool.acquire();
assert_eq!(pool.available(), 1);
{
let _engine2 = pool.acquire();
assert_eq!(pool.available(), 0);
}
assert_eq!(pool.available(), 1);
}
}