Skip to content

Commit f476eef

Browse files
committed
hashset iter
1 parent 75bded4 commit f476eef

File tree

5 files changed

+157
-9
lines changed

5 files changed

+157
-9
lines changed

assets/tests/iter/hashset.lua

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
local res_type = world.get_type_by_name("TestResourceWithVariousFields")
2+
local res = world.get_resource(res_type)
3+
4+
local set = res.string_set
5+
6+
local iterator = set:iter()
7+
local count = 0
8+
local found_values = {}
9+
10+
local result = iterator()
11+
while result ~= nil do
12+
count = count + 1
13+
found_values[result] = true
14+
result = iterator()
15+
end
16+
17+
assert(count == 2, "Expected 2 entries, got " .. count)
18+
assert(found_values["apple"] == true, "Expected to find 'apple'")
19+
assert(found_values["banana"] == true, "Expected to find 'banana'")

assets/tests/iter/hashset.rhai

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
let res_type = world.get_type_by_name.call("TestResourceWithVariousFields");
2+
let res = world.get_resource.call(res_type);
3+
4+
let set = res.string_set;
5+
6+
let iterator = set.iter();
7+
let count = 0;
8+
let found_values = #{};
9+
10+
loop {
11+
let result = iterator.next();
12+
13+
if result == () {
14+
break;
15+
}
16+
17+
count += 1;
18+
found_values[result] = true;
19+
}
20+
21+
if count != 2 {
22+
throw `Expected 2 entries, got ${count}`;
23+
}
24+
if found_values["apple"] != true {
25+
throw "Expected to find 'apple'";
26+
}
27+
if found_values["banana"] != true {
28+
throw "Expected to find 'banana'";
29+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
local res_type = world.get_type_by_name("TestResourceWithVariousFields")
2+
local res = world.get_resource(res_type)
3+
4+
local set = res.string_set
5+
6+
local count = 0
7+
local found_values = {}
8+
9+
for value in pairs(set) do
10+
count = count + 1
11+
found_values[value] = true
12+
end
13+
14+
if count ~= 2 then
15+
error(string.format("Expected 2 entries, got %d", count))
16+
end
17+
if found_values["apple"] ~= true then
18+
error("Expected to find 'apple'")
19+
end
20+
if found_values["banana"] ~= true then
21+
error("Expected to find 'banana'")
22+
end

crates/bevy_mod_scripting_bindings/src/reference.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,12 @@ impl ReflectReference {
146146
}
147147
}
148148

149+
/// Creates a new iterator for Sets specifically.
150+
/// Unlike `into_iter_infinite`, this iterator is finite and will return None when exhausted.
151+
pub fn into_set_iter(self, world: WorldGuard) -> Result<ReflectSetRefIter, InteropError> {
152+
ReflectSetRefIter::new(self, world)
153+
}
154+
149155
/// If this is a reference to something with a length accessible via reflection, returns that length.
150156
pub fn len(&self, world: WorldGuard) -> Result<Option<usize>, InteropError> {
151157
self.with_reflect(world, |r| match r.reflect_ref() {
@@ -1197,6 +1203,65 @@ impl ReflectMapRefIter {
11971203
}
11981204
}
11991205

1206+
/// Iterator specifically for Sets that doesn't use the path system.
1207+
/// This collects all set values in a Vec to enable O(1) iteration, but with memory overhead.
1208+
pub struct ReflectSetRefIter {
1209+
values: Vec<ReflectReference>,
1210+
index: usize,
1211+
}
1212+
1213+
#[profiling::all_functions]
1214+
impl ReflectSetRefIter {
1215+
/// Creates a new set iterator by collecting all values from the set in a Vec.
1216+
pub fn new(base: ReflectReference, world: WorldGuard) -> Result<Self, InteropError> {
1217+
let values = base.with_reflect(world.clone(), |reflect| match reflect.reflect_ref() {
1218+
ReflectRef::Set(set) => {
1219+
let allocator = world.allocator();
1220+
let mut allocator_guard = allocator.write();
1221+
1222+
let mut collected_values = Vec::with_capacity(set.len());
1223+
1224+
for value in set.iter() {
1225+
let owned_value = <dyn PartialReflect>::from_reflect(value, world.clone())?;
1226+
let value_ref = ReflectReference::new_allocated_boxed(
1227+
owned_value,
1228+
&mut *allocator_guard,
1229+
);
1230+
collected_values.push(value_ref);
1231+
}
1232+
1233+
drop(allocator_guard);
1234+
Ok(collected_values)
1235+
}
1236+
_ => Err(InteropError::unsupported_operation(
1237+
reflect.get_represented_type_info().map(|ti| ti.type_id()),
1238+
None,
1239+
"set iteration on non-set type".to_owned(),
1240+
)),
1241+
})??;
1242+
1243+
Ok(Self {
1244+
values,
1245+
index: 0,
1246+
})
1247+
}
1248+
1249+
/// Returns the next set entry.
1250+
/// Returns Ok(None) when there are no more entries.
1251+
pub fn next_cloned(
1252+
&mut self,
1253+
_world: WorldGuard,
1254+
) -> Result<Option<ReflectReference>, InteropError> {
1255+
if self.index < self.values.len() {
1256+
let value = self.values[self.index].clone();
1257+
self.index += 1;
1258+
Ok(Some(value))
1259+
} else {
1260+
Ok(None)
1261+
}
1262+
}
1263+
}
1264+
12001265
#[profiling::all_functions]
12011266
impl Iterator for ReflectRefIter {
12021267
type Item = Result<ReflectReference, InteropError>;

crates/bevy_mod_scripting_functions/src/core.rs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use std::ops::Deref;
77
use bevy_app::App;
88
use bevy_asset::{AssetServer, Handle};
99
use bevy_ecs::{entity::Entity, prelude::AppTypeRegistry, schedule::Schedules, world::World};
10-
use bevy_reflect::ReflectRef;
1110
use bevy_mod_scripting_bindings::{
1211
DynamicScriptFunctionMut, FunctionInfo, GlobalNamespace, InteropError, PartialReflectExt,
1312
ReflectReference, ScriptComponentRegistration, ScriptQueryBuilder, ScriptQueryResult,
@@ -729,6 +728,10 @@ impl ReflectReference {
729728
/// Iterates over the reference, if the reference is an appropriate container type.
730729
/// Uses `from_reflect` to create fully reflected clones that can be used with all operations.
731730
///
731+
/// For Maps, returns an iterator function that returns [key, value] pairs.
732+
/// For Sets, returns an iterator function that returns individual values.
733+
/// For other containers (Lists, Arrays), returns an iterator function that returns individual elements.
734+
///
732735
/// Returns an "next" iterator function that returns fully reflected values (Box<dyn Reflect>).
733736
///
734737
/// The iterator function should be called until it returns `nil` to signal the end of the iteration.
@@ -745,10 +748,8 @@ impl ReflectReference {
745748
profiling::function_scope!("iter");
746749
let world = ctxt.world()?;
747750
let mut len = reference.len(world.clone())?.unwrap_or_default();
748-
// Check if this is a Map type
749-
let is_map = reference.with_reflect(world.clone(), |r| {
750-
matches!(r.reflect_ref(), ReflectRef::Map(_))
751-
})?;
751+
let is_map = reference.is_map(world.clone())?;
752+
let is_set = reference.is_set(world.clone())?;
752753

753754
if is_map {
754755
// Use special map iterator that clones values
@@ -770,6 +771,21 @@ impl ReflectReference {
770771
}
771772
};
772773
Ok(iter_function.into_dynamic_script_function_mut())
774+
} else if is_set {
775+
// Use special set iterator that clones values upfront for efficient iteration
776+
let mut set_iter = reference.into_set_iter(world.clone())?;
777+
let iter_function = move || {
778+
if len == 0 {
779+
return Ok(ScriptValue::Unit);
780+
}
781+
len -= 1;
782+
let world = ThreadWorldContainer.try_get_world()?;
783+
match set_iter.next_cloned(world.clone())? {
784+
Some(value_ref) => ReflectReference::into_script_ref(value_ref, world),
785+
None => Ok(ScriptValue::Unit),
786+
}
787+
};
788+
Ok(iter_function.into_dynamic_script_function_mut())
773789
} else {
774790
// Use cloning iterator for lists/arrays
775791
let mut infinite_iter = reference.into_iter_infinite();
@@ -780,10 +796,7 @@ impl ReflectReference {
780796
len -= 1;
781797
let world = ThreadWorldContainer.try_get_world()?;
782798
match infinite_iter.next_cloned(world.clone())? {
783-
Some(value_ref) => {
784-
// Convert the cloned value to a script value
785-
ReflectReference::into_script_ref(value_ref, world)
786-
}
799+
Some(value_ref) => ReflectReference::into_script_ref(value_ref, world),
787800
None => Ok(ScriptValue::Unit),
788801
}
789802
};

0 commit comments

Comments
 (0)