use crate::script_engine::compiler::GenericBlockMetadata;
use crate::script_engine::instruction::{
BlockStructure, InstructionData, InstructionHandler, InstructionMetadata, ScriptError,
};
use crate::script_engine::VMContext;
use crate::scripts_builtin::terminator::TerminatorHandler;
use crate::utils::sleep_ms;
use std::time::Instant;
#[derive(Debug)]
pub struct TimeRuntimeState {
pub total_duration_ms: u32,
pub start_time: Instant,
pub body_start_ip: usize,
pub end_ip: usize,
}
pub const TIME_STACK_KEY: &str = "__builtin_time_stack";
pub fn init_time_terminator() {
use crate::script_engine::compiler::TerminatorMetadata;
TerminatorHandler::register_handler(
"time",
|vm: &mut VMContext, _metadata: &TerminatorMetadata| {
let time_stack =
vm.get_or_create_execution_state::<Vec<TimeRuntimeState>>(TIME_STACK_KEY);
if let Some(time_state) = time_stack.last() {
let elapsed_ms = time_state.start_time.elapsed().as_millis() as u32;
if elapsed_ms < time_state.total_duration_ms {
Ok(time_state.body_start_ip)
} else {
time_stack.pop();
Ok(vm.ip + 1)
}
} else {
Err(ScriptError::ExecutionError(
"Time end without active time block".into(),
))
}
},
);
}
pub struct SleepHandler;
impl InstructionHandler for SleepHandler {
fn name(&self) -> &str {
"sleep"
}
fn parse(&self, args: &[&str]) -> Result<InstructionData, ScriptError> {
if args.is_empty() {
return Err(ScriptError::ParseError(
"Missing duration. Usage: sleep <ms>".into(),
));
}
let ms = args[0].parse::<u32>().map_err(|e| {
ScriptError::ParseError(format!("Invalid duration '{}': {}", args[0], e))
})?;
Ok(InstructionData::U32(ms))
}
#[inline]
fn execute(
&self,
_vm: &mut VMContext,
data: &InstructionData,
_metadata: Option<&InstructionMetadata>,
) -> Result<(), ScriptError> {
let ms = match data {
InstructionData::U32(duration) => *duration,
_ => return Err(ScriptError::ExecutionError("Invalid sleep duration".into())),
};
sleep_ms(ms);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Instant;
#[test]
fn test_sleep_handler_parse_valid() {
let handler = SleepHandler;
let result = handler.parse(&["100"]).unwrap();
match result {
InstructionData::U32(ms) => assert_eq!(ms, 100),
_ => panic!("Expected U32"),
}
}
#[test]
fn test_sleep_handler_parse_invalid() {
let handler = SleepHandler;
assert!(handler.parse(&[]).is_err());
assert!(handler.parse(&["abc"]).is_err());
}
#[test]
fn test_sleep_handler_execution() {
let handler = SleepHandler;
let mut vm = VMContext::new();
let start = Instant::now();
handler
.execute(&mut vm, &InstructionData::U32(50), None)
.unwrap();
let elapsed = start.elapsed();
assert!(elapsed.as_millis() >= 50);
assert!(elapsed.as_millis() < 100); }
}
pub struct TimeHandler;
impl InstructionHandler for TimeHandler {
fn name(&self) -> &str {
"time"
}
fn parse(&self, args: &[&str]) -> Result<InstructionData, ScriptError> {
if args.is_empty() {
return Err(ScriptError::ParseError(
"Missing duration. Usage: time <ms>".into(),
));
}
let ms = args[0].parse::<u32>().map_err(|e| {
ScriptError::ParseError(format!("Invalid duration '{}': {}", args[0], e))
})?;
if ms == 0 {
return Err(ScriptError::ParseError(
"Time duration must be greater than 0".into(),
));
}
Ok(InstructionData::U32(ms))
}
fn execute(
&self,
vm: &mut VMContext,
data: &InstructionData,
metadata: Option<&InstructionMetadata>,
) -> Result<(), ScriptError> {
let total_duration_ms = match data {
InstructionData::U32(duration) => *duration,
_ => return Err(ScriptError::ExecutionError("Invalid time duration".into())),
};
let end_ip = metadata
.and_then(|m| m.get::<GenericBlockMetadata>())
.map(|m| m.end_ip)
.ok_or_else(|| {
ScriptError::ExecutionError("Time instruction missing metadata".into())
})?;
let body_start_ip = vm.ip + 1;
let time_stack = vm.get_or_create_execution_state::<Vec<TimeRuntimeState>>(TIME_STACK_KEY);
time_stack.push(TimeRuntimeState {
total_duration_ms,
start_time: Instant::now(),
body_start_ip,
end_ip,
});
Ok(())
}
fn declare_block_structure(&self) -> Option<BlockStructure> {
Some(BlockStructure::SimplePair {
start_name: "time",
end_name: "end",
})
}
}
#[cfg(test)]
mod time_tests {
use super::*;
#[test]
fn test_time_handler_parse_valid() {
let handler = TimeHandler;
let result = handler.parse(&["5000"]).unwrap();
match result {
InstructionData::U32(ms) => assert_eq!(ms, 5000),
_ => panic!("Expected U32"),
}
}
#[test]
fn test_time_handler_parse_zero() {
let handler = TimeHandler;
assert!(handler.parse(&["0"]).is_err());
}
#[test]
fn test_time_handler_parse_invalid() {
let handler = TimeHandler;
assert!(handler.parse(&[]).is_err());
assert!(handler.parse(&["abc"]).is_err());
}
}