Skip to content

Commit 55724f1

Browse files
committed
improve iters to iter partial reflect or _clone (full Reflect)
1 parent 9d99dc3 commit 55724f1

File tree

11 files changed

+345
-1
lines changed

11 files changed

+345
-1
lines changed

assets/tests/iter/hashmap.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ local map = res.string_map
55

66
local count = 0
77
local found_keys = {}
8+
9+
--- Iterate over PartialReflect refs using pairs
810
for key, value in pairs(map) do
911
count = count + 1
1012
found_keys[key] = value
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
local res_type = world.get_type_by_name("TestResourceWithVariousFields")
2+
local res = world.get_resource(res_type)
3+
4+
local map = res.string_map
5+
6+
local iterator = map:iter_clone()
7+
local count = 0
8+
local found_keys = {}
9+
10+
local result = iterator()
11+
while result ~= nil do
12+
local key = result[1]
13+
local value = result[2]
14+
count = count + 1
15+
found_keys[key] = value
16+
result = iterator()
17+
end
18+
19+
assert(count == 2, "Expected 2 entries, got " .. count)
20+
assert(found_keys["foo"] == "bar", "Expected foo=>bar")
21+
assert(found_keys["zoo"] == "zed", "Expected zoo=>zed")
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
let res_type = world.get_type_by_name.call("TestResourceWithVariousFields");
2+
let res = world.get_resource.call(res_type);
3+
4+
let map = res.string_map;
5+
6+
let iterator = map.iter_clone();
7+
let count = 0;
8+
let found_keys = #{};
9+
10+
loop {
11+
let result = iterator.next();
12+
13+
if result == () {
14+
break;
15+
}
16+
17+
let key = result[0];
18+
let value = result[1];
19+
count += 1;
20+
found_keys[key] = value;
21+
}
22+
23+
if count != 2 {
24+
throw `Expected 2 entries, got ${count}`;
25+
}
26+
if found_keys["foo"] != "bar" {
27+
throw "Expected foo=>bar";
28+
}
29+
if found_keys["zoo"] != "zed" {
30+
throw "Expected zoo=>zed";
31+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
local res_type = world.get_type_by_name("TestResourceWithVariousFields")
2+
local res = world.get_resource(res_type)
3+
4+
local map = res.string_map
5+
6+
local count = 0
7+
local found_keys = {}
8+
9+
-- Use pairs_clone to loop over Reflect values
10+
for key, value in map:pairs_clone() do
11+
count = count + 1
12+
found_keys[key] = value
13+
end
14+
15+
if count ~= 2 then
16+
error(string.format("Expected 2 entries, got %d", count))
17+
end
18+
if found_keys["foo"] ~= "bar" then
19+
error("Expected foo=>bar")
20+
end
21+
if found_keys["zoo"] ~= "zed" then
22+
error("Expected zoo=>zed")
23+
end

assets/tests/iter_clone/vec.lua

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
local res_type = world.get_type_by_name("TestResourceWithVariousFields")
2+
local res = world.get_resource(res_type)
3+
4+
local iterated_vals = {}
5+
local iterator = res.vec_usize:iter_clone()
6+
local result = iterator()
7+
while result ~= nil do
8+
iterated_vals[#iterated_vals + 1] = result
9+
result = iterator()
10+
end
11+
12+
assert(#iterated_vals == 5, "Length is not 5")
13+
assert(iterated_vals[1] == 1, "First value is not 1")
14+
assert(iterated_vals[2] == 2, "Second value is not 2")
15+
assert(iterated_vals[3] == 3, "Third value is not 3")
16+
assert(iterated_vals[4] == 4, "Fourth value is not 4")
17+
assert(iterated_vals[5] == 5, "Fifth value is not 5")

assets/tests/iter_clone/vec.rhai

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
let res_type = world.get_type_by_name.call("TestResourceWithVariousFields");
2+
let res = world.get_resource.call(res_type);
3+
4+
let iterated_vals = [];
5+
let iterator = res.vec_usize.iter_clone();
6+
7+
loop {
8+
let result = iterator.next();
9+
10+
if result == () {
11+
break;
12+
}
13+
14+
iterated_vals.push(result);
15+
}
16+
17+
assert(iterated_vals.len == 5, "Length is not 5");
18+
assert(iterated_vals[0] == 1, "First value is not 1");
19+
assert(iterated_vals[1] == 2, "Second value is not 2");
20+
assert(iterated_vals[2] == 3, "Third value is not 3");
21+
assert(iterated_vals[3] == 4, "Fourth value is not 4");
22+
assert(iterated_vals[4] == 5, "Fifth value is not 5");
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
local res_type = world.get_type_by_name("TestResourceWithVariousFields")
2+
local res = world.get_resource(res_type)
3+
4+
local iterated_vals = {}
5+
for v in res.vec_usize:pairs_clone() do
6+
iterated_vals[#iterated_vals + 1] = v
7+
end
8+
9+
assert(#iterated_vals == 5, "Length is not 5")
10+
assert(iterated_vals[1] == 1, "First value is not 1")
11+
assert(iterated_vals[2] == 2, "Second value is not 2")
12+
assert(iterated_vals[3] == 3, "Third value is not 3")
13+
assert(iterated_vals[4] == 4, "Fourth value is not 4")
14+
assert(iterated_vals[5] == 5, "Fifth value is not 5")

crates/bevy_mod_scripting_bindings/src/reference.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,46 @@ impl ReflectRefIter {
788788
};
789789
(next, index)
790790
}
791+
792+
/// Returns the next element as a cloned value using `from_reflect`.
793+
/// Returns a fully reflected value (Box<dyn Reflect>) instead of a reference.
794+
/// Returns Ok(None) when the path is invalid (end of iteration).
795+
pub fn next_cloned(&mut self, world: WorldGuard) -> Result<Option<ReflectReference>, InteropError> {
796+
let index = match &mut self.index {
797+
IterationKey::Index(i) => {
798+
let idx = *i;
799+
*i += 1;
800+
idx
801+
}
802+
};
803+
804+
let element = self.base.with_reflect(world.clone(), |base_reflect| {
805+
match base_reflect.reflect_ref() {
806+
bevy_reflect::ReflectRef::List(list) => {
807+
list.get(index).map(|item| {
808+
<dyn PartialReflect>::from_reflect(item, world.clone())
809+
})
810+
}
811+
bevy_reflect::ReflectRef::Array(array) => {
812+
array.get(index).map(|item| {
813+
<dyn PartialReflect>::from_reflect(item, world.clone())
814+
})
815+
}
816+
_ => None,
817+
}
818+
})?;
819+
820+
match element {
821+
Some(result) => {
822+
let owned_value = result?;
823+
let allocator = world.allocator();
824+
let mut allocator_guard = allocator.write();
825+
let value_ref = ReflectReference::new_allocated_boxed(owned_value, &mut *allocator_guard);
826+
Ok(Some(value_ref))
827+
}
828+
None => Ok(None),
829+
}
830+
}
791831
}
792832

793833
const fn list_index_access(index: usize) -> Access<'static> {
@@ -842,6 +882,49 @@ impl ReflectMapRefIter {
842882
}
843883
})?
844884
}
885+
886+
/// Returns the next map entry as a (key, value) tuple, cloning the values.
887+
/// Returns Ok(None) when there are no more entries.
888+
/// This uses `from_reflect` to clone the actual values instead of creating dynamic references.
889+
/// Returns fully reflected values (Box<dyn Reflect>) like `map_get_clone` does.
890+
pub fn next_cloned(&mut self, world: WorldGuard) -> Result<Option<(ReflectReference, ReflectReference)>, InteropError> {
891+
let idx = self.index;
892+
self.index += 1;
893+
894+
// Access the map and get the entry at index
895+
self.base.with_reflect(world.clone(), |reflect| {
896+
match reflect.reflect_ref() {
897+
ReflectRef::Map(map) => {
898+
if let Some((key, value)) = map.get_at(idx) {
899+
let allocator = world.allocator();
900+
let mut allocator_guard = allocator.write();
901+
902+
let owned_key = <dyn PartialReflect>::from_reflect(key, world.clone())?;
903+
let key_ref = ReflectReference::new_allocated_boxed(
904+
owned_key,
905+
&mut *allocator_guard
906+
);
907+
908+
let owned_value = <dyn PartialReflect>::from_reflect(value, world.clone())?;
909+
let value_ref = ReflectReference::new_allocated_boxed(
910+
owned_value,
911+
&mut *allocator_guard
912+
);
913+
914+
drop(allocator_guard);
915+
Ok(Some((key_ref, value_ref)))
916+
} else {
917+
Ok(None)
918+
}
919+
}
920+
_ => Err(InteropError::unsupported_operation(
921+
reflect.get_represented_type_info().map(|ti| ti.type_id()),
922+
None,
923+
"map iteration on non-map type".to_owned(),
924+
))
925+
}
926+
})?
927+
}
845928
}
846929

847930
#[profiling::all_functions]

crates/bevy_mod_scripting_functions/src/core.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,71 @@ impl ReflectReference {
884884
}
885885
}
886886

887+
/// Iterates over the reference with cloned values, if the reference is an appropriate container type.
888+
///
889+
/// This method clones the actual values using `from_reflect` instead of creating dynamic references.
890+
/// Returns an "next" iterator function that returns fully reflected values (Box<dyn Reflect>).
891+
///
892+
/// The iterator function should be called until it returns `nil` to signal the end of the iteration.
893+
///
894+
/// Arguments:
895+
/// * `ctxt`: The function call context.
896+
/// * `reference`: The reference to iterate over.
897+
/// Returns:
898+
/// * `iter`: The iterator function that returns cloned values.
899+
fn iter_clone(
900+
ctxt: FunctionCallContext,
901+
reference: ReflectReference,
902+
) -> Result<DynamicScriptFunctionMut, InteropError> {
903+
profiling::function_scope!("iter_clone");
904+
let world = ctxt.world()?;
905+
let mut len = reference.len(world.clone())?.unwrap_or_default();
906+
// Check if this is a Map type
907+
let is_map = reference.with_reflect(world.clone(), |r| {
908+
matches!(r.reflect_ref(), ReflectRef::Map(_))
909+
})?;
910+
911+
if is_map {
912+
// Use special map iterator that clones values
913+
let mut map_iter = reference.into_map_iter();
914+
let iter_function = move || {
915+
if len == 0 {
916+
return Ok(ScriptValue::Unit);
917+
}
918+
len -= 1;
919+
let world = ThreadWorldContainer.try_get_world()?;
920+
match map_iter.next_cloned(world.clone())? {
921+
Some((key_ref, value_ref)) => {
922+
// Return both key and value as a List for Lua's pairs() to unpack
923+
let key_value = ReflectReference::into_script_ref(key_ref, world.clone())?;
924+
let value_value = ReflectReference::into_script_ref(value_ref, world)?;
925+
Ok(ScriptValue::List(vec![key_value, value_value]))
926+
}
927+
None => Ok(ScriptValue::Unit),
928+
}
929+
};
930+
Ok(iter_function.into_dynamic_script_function_mut())
931+
} else {
932+
// Use cloning iterator for lists/arrays
933+
let mut infinite_iter = reference.into_iter_infinite();
934+
let iter_function = move || {
935+
if len == 0 {
936+
return Ok(ScriptValue::Unit);
937+
}
938+
len -= 1;
939+
let world = ThreadWorldContainer.try_get_world()?;
940+
match infinite_iter.next_cloned(world.clone())? {
941+
Some(value_ref) => {
942+
// Convert the cloned value to a script value
943+
ReflectReference::into_script_ref(value_ref, world)
944+
}
945+
None => Ok(ScriptValue::Unit),
946+
}
947+
};
948+
Ok(iter_function.into_dynamic_script_function_mut())
949+
}
950+
}
951+
887952
/// Lists the functions available on the reference.
888953
///
889954
/// Arguments:

crates/languages/bevy_mod_scripting_lua/src/bindings/reference.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,6 @@ impl UserData for LuaReflectReference {
323323

324324
match result {
325325
ScriptValue::FunctionMut(func) => {
326-
// Create a Lua function that wraps our iterator and unpacks List results
327326
lua.create_function_mut(move |lua, _args: ()| {
328327
let result = func
329328
.call(vec![], LUA_CALLER_CONTEXT)
@@ -349,6 +348,50 @@ impl UserData for LuaReflectReference {
349348
}
350349
});
351350

351+
m.add_method("pairs_clone", |lua, s: &LuaReflectReference, _args: ()| {
352+
profiling::function_scope!("pairs_clone");
353+
let world = ThreadWorldContainer
354+
.try_get_world()
355+
.map_err(IntoMluaError::to_lua_error)?;
356+
357+
let iter_func = world
358+
.lookup_function([TypeId::of::<ReflectReference>()], "iter_clone")
359+
.map_err(|f| {
360+
InteropError::missing_function(f, TypeId::of::<ReflectReference>().into())
361+
})
362+
.map_err(IntoMluaError::to_lua_error)?;
363+
364+
let result = iter_func
365+
.call(vec![ScriptValue::Reference(s.clone().into())], LUA_CALLER_CONTEXT)
366+
.map_err(IntoMluaError::to_lua_error)?;
367+
368+
match result {
369+
ScriptValue::FunctionMut(func) => {
370+
lua.create_function_mut(move |lua, _args: ()| {
371+
let result = func
372+
.call(vec![], LUA_CALLER_CONTEXT)
373+
.map_err(IntoMluaError::to_lua_error)?;
374+
375+
// If the result is a List with 2 elements, unpack it into multiple return values
376+
match result {
377+
ScriptValue::List(ref items) if items.len() == 2 => {
378+
// Return as tuple (key, value) which Lua unpacks automatically
379+
let key = LuaScriptValue(items[0].clone()).into_lua(lua)?;
380+
let value = LuaScriptValue(items[1].clone()).into_lua(lua)?;
381+
Ok((key, value))
382+
}
383+
_ => {
384+
// Single value or Unit - return as-is
385+
let val = LuaScriptValue(result).into_lua(lua)?;
386+
Ok((val, mlua::Value::Nil))
387+
}
388+
}
389+
})
390+
}
391+
_ => Err(mlua::Error::RuntimeError("iter_clone function did not return a FunctionMut".to_string()))
392+
}
393+
});
394+
352395
m.add_meta_function(MetaMethod::ToString, |_, self_: LuaReflectReference| {
353396
profiling::function_scope!("MetaMethod::ToString");
354397
let world = ThreadWorldContainer

0 commit comments

Comments
 (0)