use std::{
fmt::Debug,
path::{Path, PathBuf},
sync::Arc,
time::Duration,
};
use parking_lot::RwLock;
use resolved_shared::{ScriptResponse, instance_dir, shmem_path};
use serde::de::DeserializeOwned;
use tokio::{
fs::{create_dir, write},
process::Child,
sync::{Mutex, MutexGuard},
};
use crate::{
Error, ItemRef, ItemRefList, ResolveConfig, Script, cleanup,
packet::ShmemClient,
script_handler::{
LUA_MODULE, LUA_MODULE_TRACING, MODULE_NAME, Pipe, dll_script, handle_module_request,
new_module_pipe, new_pipe, spawn_script_server, write_config,
},
};
macro_rules! log_script_resposne {
($script:expr, $eval:expr, $name:literal) => {
#[cfg(feature = "tracing")]
{
let args = $script.args.len();
let with = &$script.with;
let script = &$script.lua;
tracing::trace!(eval_time = ?$eval, ?args, ?with, ?script, $name);
}
};
}
#[derive(Debug, Clone)]
pub struct Resolve {
inner: Arc<InnerResolve>,
}
#[derive(Debug)]
struct InnerResolve {
id: u32,
default_timeout: Duration,
cancelled: Arc<RwLock<bool>>,
packet_handler: Mutex<PacketHandler>,
_module_pipe: Pipe,
child: Child,
}
#[derive(Debug)]
pub(crate) struct PacketHandler {
pub(crate) shmem: ShmemClient,
pub(crate) pipe: Pipe,
}
impl Drop for InnerResolve {
fn drop(&mut self) {
self.cancel();
let _ = self.child.start_kill();
}
}
impl PartialEq<Resolve> for Resolve {
fn eq(&self, other: &Resolve) -> bool {
self.id() == other.id()
}
}
impl Eq for Resolve {}
impl std::hash::Hash for Resolve {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id().hash(state);
}
}
impl Resolve {
pub async fn new() -> Result<Self, Error> {
Self::new_with_config(&ResolveConfig::default()).await
}
pub async fn new_with_config(config: &ResolveConfig) -> Result<Self, Error> {
if !config.skip_cleanup {
cleanup::check().await?;
}
#[cfg(feature = "tracing")]
let creation_time = std::time::Instant::now();
let id = fastrand::u32(..);
#[cfg(feature = "tracing")]
let span = tracing::trace_span!("new_resolve", id);
#[cfg(feature = "tracing")]
let _enter = span.enter();
let instance_dir = instance_dir(id);
create_dir(&instance_dir).await?;
let cancelled = Arc::new(RwLock::new(false));
let shmem = ShmemClient::new(shmem_path(&instance_dir))?;
let (child, module_pipe, pipe) =
start(&instance_dir, config, cancelled.clone(), id).await?;
let packet_handler = Mutex::new(PacketHandler { shmem, pipe });
#[cfg(feature = "tracing")]
let creation_time = creation_time.elapsed();
#[cfg(feature = "tracing")]
tracing::trace!(?creation_time, "Created resolve client");
Ok(Self {
inner: Arc::new(InnerResolve {
id,
default_timeout: config.timeout,
cancelled,
packet_handler,
_module_pipe: module_pipe,
child,
}),
})
}
#[inline]
#[must_use]
pub fn id(&self) -> u32 {
self.inner.id
}
#[inline]
#[must_use]
pub fn dir(&self) -> PathBuf {
instance_dir(self.id())
}
#[inline]
pub(crate) fn cancelled(&self) -> bool {
*self.inner.cancelled.read()
}
#[inline]
pub(crate) fn timeout(&self) -> Duration {
self.inner.default_timeout
}
#[inline]
pub(crate) async fn packet_handler(&self) -> MutexGuard<'_, PacketHandler> {
self.inner.packet_handler.lock().await
}
pub async fn execute<T>(&self, script: impl Into<Script<'_>>) -> Result<T, Error>
where
T: DeserializeOwned,
{
let script = script.into();
match self.send_execute(&script).await? {
ScriptResponse::Err(e) => Err(Error::LuaModuleErr(e)),
ScriptResponse::Ok {
value,
#[allow(unused_variables, reason = "used when 'tracing' is enabled")]
eval_time,
} => {
log_script_resposne!(script, eval_time, "execute");
Ok(value)
}
}
}
pub async fn store(&self, script: impl Into<Script<'_>>) -> Result<ItemRef, Error> {
self.store_option(script).await?.ok_or(Error::NilItemRef)
}
pub async fn store_option(
&self,
script: impl Into<Script<'_>>,
) -> Result<Option<ItemRef>, Error> {
let script = script.into();
match self.send_store(&script).await? {
ScriptResponse::Err(e) => Err(Error::LuaModuleErr(e)),
ScriptResponse::Ok {
value,
#[allow(unused_variables, reason = "used when 'tracing' is enabled")]
eval_time,
} => {
log_script_resposne!(script, eval_time, "store");
Ok(value.map(|v| unsafe { ItemRef::new(self.clone(), v) }))
}
}
}
pub async fn store_list(&self, script: impl Into<Script<'_>>) -> Result<ItemRefList, Error> {
let script = script.into();
match self.send_store_table(&script).await? {
resolved_shared::ScriptResponse::Err(e) => Err(Error::LuaModuleErr(e)),
resolved_shared::ScriptResponse::Ok {
value: (source, list),
#[allow(unused_variables, reason = "used when 'tracing' is enabled")]
eval_time,
} => {
log_script_resposne!(script, eval_time, "store_list");
Ok(ItemRefList::new(
unsafe { ItemRef::new(self.clone(), source) },
list.into_iter()
.map(|x| unsafe { ItemRef::new(self.clone(), x) })
.collect(),
))
}
}
}
pub async fn table_keys<T>(&self, item: &ItemRef) -> Result<Vec<T>, Error>
where
T: DeserializeOwned,
{
if self.id() != item.resolve().id() {
return Err(Error::MismatchedItemRef(self.id(), item.resolve().id()));
}
match self.send_table_keys(item.id()).await? {
ScriptResponse::Err(e) => Err(Error::LuaModuleErr(e)),
ScriptResponse::Ok {
value,
eval_time: _,
} => Ok(value),
}
}
pub(crate) async fn item_value<T>(&self, item: &ItemRef) -> Result<T, Error>
where
T: DeserializeOwned,
{
match self.send_item_value(item.id()).await? {
ScriptResponse::Err(e) => Err(Error::LuaModuleErr(e)),
ScriptResponse::Ok {
value,
eval_time: _,
} => Ok(value),
}
}
pub(crate) async fn execute_with<'c, T>(
&self,
item: &'c ItemRef,
script: impl Into<Script<'c>>,
) -> Result<T, Error>
where
T: DeserializeOwned,
{
let mut script = script.into();
script = script.with(item)?;
self.execute(script).await
}
pub(crate) async fn store_with<'c>(
&self,
item: &'c ItemRef,
script: impl Into<Script<'c>>,
) -> Result<ItemRef, Error> {
let mut script = script.into();
script = script.with(item)?;
self.store(script).await
}
pub(crate) async fn store_option_with<'c>(
&self,
item: &'c ItemRef,
script: impl Into<Script<'c>>,
) -> Result<Option<ItemRef>, Error> {
let mut script = script.into();
script = script.with(item)?;
self.store_option(script).await
}
pub(crate) async fn store_list_with<'c>(
&self,
item: &'c ItemRef,
script: impl Into<Script<'c>>,
) -> Result<ItemRefList, Error> {
let mut script = script.into();
script = script.with(item)?;
self.store_list(script).await
}
pub async unsafe fn shutdown(&self) -> Result<(), Error> {
self.send_shutdown().await?;
self.inner.cancel();
Ok(())
}
}
impl InnerResolve {
pub(crate) fn cancel(&self) {
*self.cancelled.write() = true;
}
}
async fn start(
instance_dir: &Path,
config: &ResolveConfig,
cancelled: Arc<RwLock<bool>>,
id: u32,
) -> Result<(Child, Pipe, Pipe), Error> {
let module_pipe = new_module_pipe(id)?;
let pipe = new_pipe(id)?;
let dll = instance_dir.join(format!("{MODULE_NAME}.dll"));
let raw_module = if config.tracing {
LUA_MODULE_TRACING
} else {
LUA_MODULE
};
write(&dll, raw_module).await?;
let script = dll_script(instance_dir, id);
let script_path = instance_dir.join("script.lua");
#[cfg(feature = "tracing")]
tracing::trace!(script, "Startup script");
write(&script_path, &script).await?;
let child = spawn_script_server(&script_path, cancelled).await?;
let mut module_pipe = module_pipe.accept().await?;
#[cfg(feature = "tracing")]
tracing::trace!("Module pipe connected");
write_config(&mut module_pipe, config).await?;
let pipe = handle_module_request(&mut module_pipe, pipe).await?;
#[cfg(feature = "tracing")]
tracing::trace!("Module started correctly, finished");
Ok((child, module_pipe, pipe))
}