Skip to content

feat: add ScriptValue::Map and create appropriate conversions in lua and rhai #229

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl From<LuaScriptValue> for ScriptValue {
}

impl FromLua for LuaScriptValue {
fn from_lua(value: mlua::Value, _lua: &mlua::Lua) -> mlua::Result<Self> {
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),
Expand All @@ -50,12 +50,34 @@ impl FromLua for LuaScriptValue {
Value::Number(n) => ScriptValue::Float(n),
Value::String(s) => ScriptValue::String(s.to_str()?.to_owned().into()),
Value::Table(table) => {
let mut vec = Vec::with_capacity(table.len()? as usize);
for i in table.sequence_values() {
let v: LuaScriptValue = i?;
vec.push(v.into());
// check the key types, if strings then it's a map
let mut iter = table.pairs::<Value, LuaScriptValue>();

match iter.next() {
Some(v) => {
let (k, v) = v?;
// if the key is a string, then it's a map
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 {
// if the key is an integer, then it's a list
let mut vec = Vec::with_capacity(table.len()? as usize);
vec.push(v.into());
for pair in iter {
vec.push(pair?.1.into());
}
return Ok(LuaScriptValue::from(ScriptValue::List(vec)));
}
}
None => return Ok(LuaScriptValue::from(ScriptValue::List(vec![]))),
}
ScriptValue::List(vec)
}
// Value::Thread(thread) => todo!(),
Value::UserData(ud) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
local map = make_hashmap({
key1 = 2,
key2 = 3,
})

assert(map["key1"] == 2, "map[\"key1\"] should be 2")
assert(map["key2"] == 3, "map[\"key2\"] should be 3")
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ pub trait FromDynamic: Sized {
fn from_dynamic(dynamic: Dynamic) -> Result<Self, Box<EvalAltResult>>;
}

#[allow(clippy::unwrap_used, clippy::todo)]
#[allow(clippy::unwrap_used)]
impl FromDynamic for ScriptValue {
fn from_dynamic(dynamic: Dynamic) -> Result<Self, Box<EvalAltResult>> {
match dynamic {
Expand All @@ -140,6 +140,18 @@ impl FromDynamic for ScriptValue {
d if d.is_string() => Ok(ScriptValue::String(
d.into_immutable_string().unwrap().to_string().into(),
)),
mut d if d.is_map() => Ok(ScriptValue::Map(
d.as_map_mut()
.map_err(|_| {
Box::new(EvalAltResult::ErrorSystem(
"FromDynamic".to_string(),
InteropError::invariant("d is proved to be a map").into(),
))
})?
.iter()
.map(|(k, v)| Ok((k.to_string(), ScriptValue::from_dynamic(v.clone())?)))
.collect::<Result<_, Box<EvalAltResult>>>()?,
)),
d if d.is_array() => Ok(ScriptValue::List(
d.into_array()
.map_err(|_| InteropError::invariant("d is proved to be an array"))?
Expand All @@ -151,7 +163,13 @@ impl FromDynamic for ScriptValue {
if let Some(v) = d.try_cast::<RhaiReflectReference>() {
Ok(ScriptValue::Reference(v.0))
} else {
todo!("from conversion not implemented yet")
Err(Box::new(EvalAltResult::ErrorSystem(
"FromDynamic".to_string(),
Box::new(InteropError::impossible_conversion(std::any::TypeId::of::<
ScriptValue,
>(
))),
)))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
let my_map = make_hashmap.call(#{
key1: 2,
key2: 3,
});

assert(my_map["key1"] == 2, "map[\"key1\"] should be 2");
assert(my_map["key2"] == 3, "map[\"key2\"] should be 3");
5 changes: 3 additions & 2 deletions crates/script_integration_test_harness/src/test_functions.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::sync::Arc;
use std::{collections::HashMap, sync::Arc};

use bevy::{
app::App,
Expand Down Expand Up @@ -102,5 +102,6 @@ pub fn register_test_functions(world: &mut App) {
);

NamespaceBuilder::<GlobalNamespace>::new_unregistered(world)
.register("global_hello_world", || Ok("hi!"));
.register("global_hello_world", || Ok("hi!"))
.register("make_hashmap", |map: HashMap<String, usize>| map);
}
Loading