use crate::{
actors::{
lua_vm::{error::LuaVmResult, LuaVM},
queue::Queue,
scanmgr::ScanMgr,
user_engine::UserEngine,
Ping,
},
userscript_api::ApiObject,
};
use kameo::{
actor::ActorRef,
message::{Context, Message},
};
pub struct RegisterUserApi<A>(A)
where
A: ApiObject;
impl<A> Message<RegisterUserApi<A>> for LuaVM
where
A: ApiObject,
{
type Reply = LuaVmResult<()>;
async fn handle(
&mut self,
msg: RegisterUserApi<A>,
_: Context<'_, Self, Self::Reply>,
) -> Self::Reply {
msg.0.init_script(&self.vm)?;
self.vm.globals().set(msg.0.name(), msg.0)?;
Ok(())
}
}
impl<A> RegisterUserApi<A>
where
A: ApiObject,
{
pub fn with(api: A) -> Self {
Self(api)
}
}
pub struct ExecChunk(String);
impl Message<ExecChunk> for LuaVM {
type Reply = LuaVmResult<()>;
async fn handle(&mut self, msg: ExecChunk, _: Context<'_, Self, Self::Reply>) -> Self::Reply {
self.vm.load(msg.0).exec_async().await?;
Ok(())
}
}
impl<S> From<S> for ExecChunk
where
S: ToString,
{
fn from(value: S) -> Self {
Self(value.to_string())
}
}
pub struct EvalChunk(String);
impl Message<EvalChunk> for LuaVM {
type Reply = LuaVmResult<mlua::Value>;
async fn handle(&mut self, msg: EvalChunk, _: Context<'_, Self, Self::Reply>) -> Self::Reply {
Ok(self.vm.load(msg.0).eval_async().await?)
}
}
impl<S> From<S> for EvalChunk
where
S: ToString,
{
fn from(value: S) -> Self {
Self(value.to_string())
}
}
pub enum SendWarning {
Complete(String),
Incomplete(String),
}
impl Message<SendWarning> for LuaVM {
type Reply = ();
async fn handle(&mut self, msg: SendWarning, _: Context<'_, Self, Self::Reply>) -> Self::Reply {
match msg {
SendWarning::Complete(msg) => {
self.vm.warning(msg, false);
}
SendWarning::Incomplete(msg) => {
self.vm.warning(msg, true);
}
}
}
}
pub struct WaitStartup;
impl Message<WaitStartup> for LuaVM {
type Reply = ();
async fn handle(&mut self, _: WaitStartup, _: Context<'_, Self, Self::Reply>) -> Self::Reply {
let queue: &ActorRef<Queue> = self.queue.as_ref().expect("infallible");
let scanmgr: &ActorRef<ScanMgr> = self.scanmgr.as_ref().expect("infallible");
let user_engine: &ActorRef<UserEngine> = self.user_engine.as_ref().expect("infallible");
let _ = queue.ask(Ping).await;
let _ = scanmgr.ask(Ping).await;
let _ = user_engine.ask(Ping).await;
}
}
#[cfg(test)]
mod tests {
use crate::{
actors::lua_vm::{
messages::{EvalChunk, ExecChunk, RegisterUserApi},
LuaVM,
},
userscript_api::ApiObject,
};
use kameo::actor::ActorRef;
use mlua::{UserData, UserDataMethods};
#[tokio::test]
async fn should_register_api() {
struct MyApi;
impl UserData for MyApi {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("hello", |_, _this: &MyApi, ()| Ok("Hello World"));
}
}
impl ApiObject for MyApi {
fn name(&self) -> &'static str {
"myapi"
}
}
let vm: ActorRef<LuaVM> = LuaVM::spawn(None);
vm.ask(RegisterUserApi::with(MyApi)).await.unwrap();
}
#[tokio::test]
async fn should_exec_successfully() {
let vm: ActorRef<LuaVM> = LuaVM::spawn(None);
let exec_request: ExecChunk = r#"
assert(5 == 5)
"#
.into();
vm.ask(exec_request).await.unwrap();
}
#[tokio::test]
#[should_panic]
async fn should_fail_exec() {
let vm: ActorRef<LuaVM> = LuaVM::spawn(None);
let exec_request: ExecChunk = r#"
assert(5 == 4)
"#
.into();
vm.ask(exec_request).await.unwrap();
}
#[tokio::test]
async fn should_return_eval_result() {
let vm: ActorRef<LuaVM> = LuaVM::spawn(None);
let expr_request: EvalChunk = r#"
5 + 6
"#
.into();
let result: mlua::Value = vm.ask(expr_request).await.unwrap();
assert_eq!(result, mlua::Value::Integer(11));
}
#[tokio::test]
#[should_panic]
async fn should_error_on_invalid_expr() {
let vm: ActorRef<LuaVM> = LuaVM::spawn(None);
let expr_request: EvalChunk = r#"
nonexistent_table.nonexistent_key
"#
.into();
vm.ask(expr_request).await.unwrap();
}
}