use std::{
collections::VecDeque,
ops::{Deref, DerefMut},
};
use bevy_mod_scripting_asset::Language;
use bevy_mod_scripting_bindings::{
LocationContext, VariadicTuple, error::InteropError,
function::script_function::FunctionCallContext, script_value::ScriptValue,
};
use bevy_platform::collections::HashMap;
use mlua::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Value};
use crate::{IntoMluaError, LuaContextAppData};
use super::reference::LuaReflectReference;
pub struct MultiLuaScriptValue(pub VecDeque<ScriptValue>);
impl MultiLuaScriptValue {
pub fn into_script_value(mut self) -> ScriptValue {
if self.0.is_empty() {
ScriptValue::Unit
} else if let Some(first) = self.0.pop_front()
&& self.0.len() == 1
{
first
} else {
ScriptValue::Tuple(VariadicTuple(self.0))
}
}
pub fn from_script_value(value: ScriptValue) -> Self {
if let ScriptValue::Tuple(VariadicTuple(tuple)) = value {
Self(tuple)
} else {
MultiLuaScriptValue(VecDeque::from_iter([value]))
}
}
}
impl FromLuaMulti for MultiLuaScriptValue {
fn from_lua_multi(values: mlua::MultiValue, lua: &mlua::Lua) -> mlua::Result<Self> {
let mut vals = VecDeque::with_capacity(values.len());
for val in values {
let script_val = LuaScriptValue::from_lua(val, lua)?;
vals.push_back(script_val.into());
}
Ok(MultiLuaScriptValue(vals))
}
}
impl IntoLuaMulti for MultiLuaScriptValue {
fn into_lua_multi(self, lua: &mlua::Lua) -> mlua::Result<mlua::MultiValue> {
let mut vals = MultiValue::with_capacity(self.0.len());
let mut left = self.0.len();
for val in self.0 {
left -= 1;
match val {
ScriptValue::Tuple(VariadicTuple(tuple)) if left == 0 => {
let mut many_vals = MultiLuaScriptValue(tuple).into_lua_multi(lua)?;
vals.append(&mut many_vals);
}
_ => {
let mlua_val = LuaScriptValue(val).into_lua(lua)?;
vals.push_back(mlua_val);
}
}
}
Ok(vals)
}
}
#[derive(Debug, Clone)]
pub struct LuaScriptValue(pub ScriptValue);
impl Deref for LuaScriptValue {
type Target = ScriptValue;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for LuaScriptValue {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<ScriptValue> for LuaScriptValue {
fn from(value: ScriptValue) -> Self {
LuaScriptValue(value)
}
}
impl From<LuaScriptValue> for ScriptValue {
fn from(value: LuaScriptValue) -> Self {
value.0
}
}
#[profiling::all_functions]
impl FromLua for LuaScriptValue {
fn from_lua(value: mlua::Value, lua: &mlua::Lua) -> mlua::Result<Self> {
Ok(match value {
Value::Nil => ScriptValue::Unit,
Value::Boolean(b) => ScriptValue::Bool(b),
Value::Integer(i) => ScriptValue::Integer(i),
Value::Number(n) => ScriptValue::Float(n),
Value::String(s) => ScriptValue::String(s.to_str()?.to_owned().into()),
Value::Function(f) => ScriptValue::Function(
(move |_context: FunctionCallContext, args: VecDeque<ScriptValue>| {
match f.call::<MultiLuaScriptValue>(MultiLuaScriptValue(args)) {
Ok(v) => v.into_script_value(),
Err(e) => ScriptValue::Error(InteropError::external(Box::new(e))),
}
})
.into(),
),
Value::Table(table) => {
let mut iter = table.pairs::<Value, LuaScriptValue>();
match iter.next() {
Some(v) => {
let (k, v) = v?;
if k.is_string() {
let mut map = HashMap::new();
map.insert(k.to_string()?, v.into());
for pair in iter {
let (k, v) = pair?;
let str_: String = String::from_lua(k, lua)?;
map.insert(str_, v.into());
}
return Ok(LuaScriptValue::from(ScriptValue::Map(map)));
} else {
let mut vec = VecDeque::with_capacity(table.len()? as usize);
vec.push_back(v.into());
for pair in iter {
vec.push_back(pair?.1.into());
}
return Ok(LuaScriptValue::from(ScriptValue::List(vec)));
}
}
None => return Ok(LuaScriptValue::from(ScriptValue::List(Default::default()))),
}
}
Value::UserData(ud) => {
let ud = ud.borrow::<LuaReflectReference>().map_err(|e| {
mlua::Error::FromLuaConversionError {
from: "UserData",
to: "LuaReflectReference".to_owned(),
message: Some(e.to_string()),
}
})?;
ScriptValue::Reference(ud.clone().into())
}
_ => {
return Err(mlua::Error::FromLuaConversionError {
from: value.type_name(),
to: "ScriptValue".to_owned(),
message: Some("unsupported value type".to_owned()),
});
}
}
.into())
}
}
pub const LUA_CALLER_CONTEXT: FunctionCallContext = FunctionCallContext::new(Language::Lua);
#[profiling::all_functions]
impl IntoLua for LuaScriptValue {
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
Ok(match self.0 {
ScriptValue::Unit => Value::Nil,
ScriptValue::Bool(b) => Value::Boolean(b),
ScriptValue::Integer(i) => Value::Integer(i),
ScriptValue::Float(f) => Value::Number(f),
ScriptValue::String(s) => Value::String(lua.create_string(s.as_ref())?),
ScriptValue::Reference(r) => LuaReflectReference::from(r).into_lua(lua)?,
ScriptValue::Error(script_error) => return Err(mlua::Error::external(script_error)),
ScriptValue::Function(function) => lua
.create_function(move |lua, args: MultiLuaScriptValue| {
let loc = {
profiling::scope!("function call context");
lua.inspect_stack(1, |debug| LocationContext {
line: debug.current_line().unwrap_or_default() as u32,
col: None,
script_name: lua.app_data_ref::<LuaContextAppData>().and_then(|v| {
v.last_loaded_script_name.as_ref().map(|n| n.to_string())
}),
})
};
let out = function
.call(
args.0,
FunctionCallContext::new_with_location(Language::Lua, loc),
)
.map_err(IntoMluaError::to_lua_error)?;
Ok(MultiLuaScriptValue::from_script_value(out))
})?
.into_lua(lua)?,
ScriptValue::FunctionMut(function) => lua
.create_function(move |lua, args: MultiLuaScriptValue| {
let loc = {
profiling::scope!("function call context");
lua.inspect_stack(1, |debug| LocationContext {
line: debug.current_line().unwrap_or_default() as u32,
col: None,
script_name: lua.app_data_ref::<LuaContextAppData>().and_then(|v| {
v.last_loaded_script_name.as_ref().map(|n| n.to_string())
}),
})
};
let out = function
.call(
args.0,
FunctionCallContext::new_with_location(Language::Lua, loc),
)
.map_err(IntoMluaError::to_lua_error)?;
Ok(MultiLuaScriptValue::from_script_value(out))
})?
.into_lua(lua)?,
ScriptValue::List(vec) | ScriptValue::Tuple(VariadicTuple(vec)) => {
let table = lua.create_table_from(
vec.into_iter()
.enumerate()
.map(|(k, v)| (k + 1, LuaScriptValue::from(v))),
)?;
Value::Table(table)
}
ScriptValue::Map(map) => {
let hashmap: std::collections::HashMap<String, Value> = map
.into_iter()
.map(|(k, v)| Ok((k, LuaScriptValue::from(v).into_lua(lua)?)))
.collect::<Result<_, mlua::Error>>()?;
hashmap.into_lua(lua)?
}
})
}
}