diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/access/multiple_read_refs.lua b/assets/tests/access/multiple_read_refs.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/access/multiple_read_refs.lua rename to assets/tests/access/multiple_read_refs.lua diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/access/multiple_read_refs.rhai b/assets/tests/access/multiple_read_refs.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/access/multiple_read_refs.rhai rename to assets/tests/access/multiple_read_refs.rhai diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/add/vec3.lua b/assets/tests/add/vec3.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/add/vec3.lua rename to assets/tests/add/vec3.lua diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/add/vec3.rhai b/assets/tests/add/vec3.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/add/vec3.rhai rename to assets/tests/add/vec3.rhai diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/add_default_component/component_no_default_or_from_world_data_errors.lua b/assets/tests/add_default_component/component_no_default_or_from_world_data_errors.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/add_default_component/component_no_default_or_from_world_data_errors.lua rename to assets/tests/add_default_component/component_no_default_or_from_world_data_errors.lua diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_no_default_or_from_world_data_errors.rhai b/assets/tests/add_default_component/component_no_default_or_from_world_data_errors.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_no_default_or_from_world_data_errors.rhai rename to assets/tests/add_default_component/component_no_default_or_from_world_data_errors.rhai diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/add_default_component/component_with_default_and_component_data_adds_default.lua b/assets/tests/add_default_component/component_with_default_and_component_data_adds_default.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/add_default_component/component_with_default_and_component_data_adds_default.lua rename to assets/tests/add_default_component/component_with_default_and_component_data_adds_default.lua diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_default_and_component_data_adds_default.rhai b/assets/tests/add_default_component/component_with_default_and_component_data_adds_default.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_default_and_component_data_adds_default.rhai rename to assets/tests/add_default_component/component_with_default_and_component_data_adds_default.rhai diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/add_default_component/component_with_default_no_component_data_errors.lua b/assets/tests/add_default_component/component_with_default_no_component_data_errors.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/add_default_component/component_with_default_no_component_data_errors.lua rename to assets/tests/add_default_component/component_with_default_no_component_data_errors.lua diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_default_no_component_data_errors.rhai b/assets/tests/add_default_component/component_with_default_no_component_data_errors.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_default_no_component_data_errors.rhai rename to assets/tests/add_default_component/component_with_default_no_component_data_errors.rhai diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/add_default_component/component_with_from_world_and_component_data_adds_default.lua b/assets/tests/add_default_component/component_with_from_world_and_component_data_adds_default.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/add_default_component/component_with_from_world_and_component_data_adds_default.lua rename to assets/tests/add_default_component/component_with_from_world_and_component_data_adds_default.lua diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_from_world_and_component_data_adds_default.rhai b/assets/tests/add_default_component/component_with_from_world_and_component_data_adds_default.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_from_world_and_component_data_adds_default.rhai rename to assets/tests/add_default_component/component_with_from_world_and_component_data_adds_default.rhai diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/add_default_component/component_with_from_world_no_component_data_errors.lua b/assets/tests/add_default_component/component_with_from_world_no_component_data_errors.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/add_default_component/component_with_from_world_no_component_data_errors.lua rename to assets/tests/add_default_component/component_with_from_world_no_component_data_errors.lua diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_from_world_no_component_data_errors.rhai b/assets/tests/add_default_component/component_with_from_world_no_component_data_errors.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/add_default_component/component_with_from_world_no_component_data_errors.rhai rename to assets/tests/add_default_component/component_with_from_world_no_component_data_errors.rhai diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/api_availability/api_available_on_callback.lua b/assets/tests/api_availability/api_available_on_callback.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/api_availability/api_available_on_callback.lua rename to assets/tests/api_availability/api_available_on_callback.lua diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/api_availability/api_available_on_callback.rhai b/assets/tests/api_availability/api_available_on_callback.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/api_availability/api_available_on_callback.rhai rename to assets/tests/api_availability/api_available_on_callback.rhai diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/api_availability/api_available_on_script_load.lua b/assets/tests/api_availability/api_available_on_script_load.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/api_availability/api_available_on_script_load.lua rename to assets/tests/api_availability/api_available_on_script_load.lua diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/api_availability/api_available_on_script_load.rhai b/assets/tests/api_availability/api_available_on_script_load.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/api_availability/api_available_on_script_load.rhai rename to assets/tests/api_availability/api_available_on_script_load.rhai diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/clear/vec.lua b/assets/tests/clear/vec.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/clear/vec.lua rename to assets/tests/clear/vec.lua diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/clear/vec.rhai b/assets/tests/clear/vec.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/clear/vec.rhai rename to assets/tests/clear/vec.rhai diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/construct/construct_enum.lua b/assets/tests/construct/construct_enum.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/construct/construct_enum.lua rename to assets/tests/construct/construct_enum.lua diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/construct/construct_struct.lua b/assets/tests/construct/construct_struct.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/construct/construct_struct.lua rename to assets/tests/construct/construct_struct.lua diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/construct/construct_tuple_struct.lua b/assets/tests/construct/construct_tuple_struct.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/construct/construct_tuple_struct.lua rename to assets/tests/construct/construct_tuple_struct.lua diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/construct/simple_enum.rhai b/assets/tests/construct/simple_enum.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/construct/simple_enum.rhai rename to assets/tests/construct/simple_enum.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/construct/simple_struct.rhai b/assets/tests/construct/simple_struct.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/construct/simple_struct.rhai rename to assets/tests/construct/simple_struct.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/construct/simple_tuple_struct.rhai b/assets/tests/construct/simple_tuple_struct.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/construct/simple_tuple_struct.rhai rename to assets/tests/construct/simple_tuple_struct.rhai diff --git a/assets/tests/data/access/multiple_read_refs.rhai b/assets/tests/data/access/multiple_read_refs.rhai new file mode 100644 index 0000000000..875e8eff06 --- /dev/null +++ b/assets/tests/data/access/multiple_read_refs.rhai @@ -0,0 +1,3 @@ +let entity = Entity.from_raw.call(9999); +// does not throw +let out = entity.eq.call(entity); \ No newline at end of file diff --git a/assets/tests/data/add/vec3.rhai b/assets/tests/data/add/vec3.rhai new file mode 100644 index 0000000000..c538e36e29 --- /dev/null +++ b/assets/tests/data/add/vec3.rhai @@ -0,0 +1,11 @@ +let a = Vec3.new_.call(1.0, 2.0, 3.0); +let b = Vec3.new_.call(4.0, 5.0, 6.0); + +assert((a + 1).x == 2.0, "Addition did not work"); +assert((a + 1).y == 3.0, "Addition did not work"); +assert((a + 1).z == 4.0, "Addition did not work"); + +assert((a + b).x == 5.0, "Addition did not work"); +assert((a + b).y == 7.0, "Addition did not work"); +assert((a + b).z == 9.0, "Addition did not work"); + diff --git a/assets/tests/data/add_default_component/component_no_default_or_from_world_data_errors.rhai b/assets/tests/data/add_default_component/component_no_default_or_from_world_data_errors.rhai new file mode 100644 index 0000000000..463d3100c0 --- /dev/null +++ b/assets/tests/data/add_default_component/component_no_default_or_from_world_data_errors.rhai @@ -0,0 +1,6 @@ +let entity = world.spawn_.call(); +let type = world.get_type_by_name.call("TestComponent"); + +assert_throws(||{ + world.add_default_component.call(entity, type); +},"Missing type data ReflectDefault or ReflectFromWorld for type: .*TestComponent.*"); \ No newline at end of file diff --git a/assets/tests/data/add_default_component/component_with_default_and_component_data_adds_default.rhai b/assets/tests/data/add_default_component/component_with_default_and_component_data_adds_default.rhai new file mode 100644 index 0000000000..e2ce895868 --- /dev/null +++ b/assets/tests/data/add_default_component/component_with_default_and_component_data_adds_default.rhai @@ -0,0 +1,9 @@ +let entity = world.spawn_.call(); +let _type = world.get_type_by_name.call("CompWithDefaultAndComponentData"); +world.add_default_component.call(entity, _type); + +let added = world.has_component.call(entity, _type); +assert(type_of(added) != "()", "Component not added"); + +let component = world.get_component.call(entity, _type); +assert(component["_0"] == "Default", "Component did not have default value, got: " + component["_0"]); \ No newline at end of file diff --git a/assets/tests/data/add_default_component/component_with_default_no_component_data_errors.rhai b/assets/tests/data/add_default_component/component_with_default_no_component_data_errors.rhai new file mode 100644 index 0000000000..988642e16c --- /dev/null +++ b/assets/tests/data/add_default_component/component_with_default_no_component_data_errors.rhai @@ -0,0 +1,6 @@ +let entity = world.spawn_.call(); +let _type = world.get_type_by_name.call("CompWithDefault"); + +assert_throws(||{ + world.add_default_component.call(entity, _type); +}, "Missing type data ReflectComponent for type: .*CompWithDefault.*") diff --git a/assets/tests/data/add_default_component/component_with_from_world_and_component_data_adds_default.rhai b/assets/tests/data/add_default_component/component_with_from_world_and_component_data_adds_default.rhai new file mode 100644 index 0000000000..63c81f3dec --- /dev/null +++ b/assets/tests/data/add_default_component/component_with_from_world_and_component_data_adds_default.rhai @@ -0,0 +1,9 @@ +let entity = world.spawn_.call(); +let _type = world.get_type_by_name.call("CompWithFromWorldAndComponentData"); +world.add_default_component.call(entity, _type); + +let added = world.has_component.call(entity, _type); +assert(type_of(added) != "()", "Component not added"); + +let component = world.get_component.call(entity, _type); +assert(component["_0"] == "Default", "Component did not have default value, got: " + component["_0"]) \ No newline at end of file diff --git a/assets/tests/data/add_default_component/component_with_from_world_no_component_data_errors.rhai b/assets/tests/data/add_default_component/component_with_from_world_no_component_data_errors.rhai new file mode 100644 index 0000000000..323dfb84d1 --- /dev/null +++ b/assets/tests/data/add_default_component/component_with_from_world_no_component_data_errors.rhai @@ -0,0 +1,6 @@ +let entity = world.spawn_.call(); +let _type = world.get_type_by_name.call("CompWithFromWorld"); + +assert_throws(||{ + world.add_default_component.call(entity, _type); +}, "Missing type data ReflectComponent for type: .*CompWithFromWorld.*") \ No newline at end of file diff --git a/assets/tests/data/api_availability/api_available_on_callback.rhai b/assets/tests/data/api_availability/api_available_on_callback.rhai new file mode 100644 index 0000000000..d37af11858 --- /dev/null +++ b/assets/tests/data/api_availability/api_available_on_callback.rhai @@ -0,0 +1,5 @@ +fn on_test() { + assert!(type_of(world) != "()", "World was not found"); + assert!(type_of(world.get_type_by_name.call("TestComponent")) != "()", "Could not find TestComponent type"); + Entity.from_raw.call(1); +} \ No newline at end of file diff --git a/assets/tests/data/api_availability/api_available_on_script_load.rhai b/assets/tests/data/api_availability/api_available_on_script_load.rhai new file mode 100644 index 0000000000..5661f55644 --- /dev/null +++ b/assets/tests/data/api_availability/api_available_on_script_load.rhai @@ -0,0 +1,3 @@ +assert!(type_of(world) != "()", "World was not found"); +assert!(type_of(world.get_type_by_name.call("TestComponent")) != "()", "Could not find TestComponent type"); +let out = Entity.from_raw.call(1); \ No newline at end of file diff --git a/assets/tests/data/clear/vec.rhai b/assets/tests/data/clear/vec.rhai new file mode 100644 index 0000000000..4f1d5e7d10 --- /dev/null +++ b/assets/tests/data/clear/vec.rhai @@ -0,0 +1,6 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +res.vec_usize.clear.call(); + +assert(res.vec_usize.len.call() == 0, "Clear did not work"); \ No newline at end of file diff --git a/assets/tests/data/construct/simple_enum.rhai b/assets/tests/data/construct/simple_enum.rhai new file mode 100644 index 0000000000..463de1f9a7 --- /dev/null +++ b/assets/tests/data/construct/simple_enum.rhai @@ -0,0 +1,18 @@ +let type = world.get_type_by_name.call("SimpleEnum"); + +// Struct Variant +let constructed = construct.call(type, #{ variant: "Struct", foo: 123 }); + +assert(constructed.variant_name.call() == "Struct", "Value was constructed incorrectly, expected constructed.variant to be Struct but got " + constructed.variant_name.call()); +assert(constructed.foo == 123, "Value was constructed incorrectly, expected constructed.foo to be 123 but got " + constructed.foo); + +// TupleStruct Variant +constructed = construct.call(type, #{ variant: "TupleStruct", "_0": 123 }); + +assert(constructed.variant_name.call() == "TupleStruct", "Value was constructed incorrectly, expected constructed.variant to be TupleStruct but got " + constructed.variant_name.call()); +assert(constructed["_0"] == 123, "Value was constructed incorrectly, expected constructed._0 to be 123 but got " + constructed["_0"]); + +// Unit Variant +constructed = construct.call(type, #{ variant: "Unit" }); + +assert(constructed.variant_name.call() == "Unit", "Value was constructed incorrectly, expected constructed.variant to be Unit but got " + constructed.variant_name.call()); \ No newline at end of file diff --git a/assets/tests/data/construct/simple_struct.rhai b/assets/tests/data/construct/simple_struct.rhai new file mode 100644 index 0000000000..e35edbddd2 --- /dev/null +++ b/assets/tests/data/construct/simple_struct.rhai @@ -0,0 +1,4 @@ +let type = world.get_type_by_name.call("SimpleStruct"); +let constructed = construct.call(type, #{ foo: 123 }); + +assert(constructed.foo == 123, "Value was constructed incorrectly, expected constructed.foo to be 123 but got " + constructed.foo); \ No newline at end of file diff --git a/assets/tests/data/construct/simple_tuple_struct.rhai b/assets/tests/data/construct/simple_tuple_struct.rhai new file mode 100644 index 0000000000..411b3e806f --- /dev/null +++ b/assets/tests/data/construct/simple_tuple_struct.rhai @@ -0,0 +1,4 @@ +let type = world.get_type_by_name.call("SimpleTupleStruct"); +let constructed = construct.call(type, #{ "_0": 123 }); + +assert(constructed["_0"] == 123, "Value was constructed incorrectly, expected constructed.foo to be 123 but got " + constructed["_0"]); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn/despawns_only_root.rhai b/assets/tests/data/despawn/despawns_only_root.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/despawn/despawns_only_root.rhai rename to assets/tests/data/despawn/despawns_only_root.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn/invalid_entity_errors.rhai b/assets/tests/data/despawn/invalid_entity_errors.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/despawn/invalid_entity_errors.rhai rename to assets/tests/data/despawn/invalid_entity_errors.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_descendants/despawns_only_child.rhai b/assets/tests/data/despawn_descendants/despawns_only_child.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_descendants/despawns_only_child.rhai rename to assets/tests/data/despawn_descendants/despawns_only_child.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_descendants/invalid_entity_errors.rhai b/assets/tests/data/despawn_descendants/invalid_entity_errors.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_descendants/invalid_entity_errors.rhai rename to assets/tests/data/despawn_descendants/invalid_entity_errors.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_recursive/despawns_recursively.rhai b/assets/tests/data/despawn_recursive/despawns_recursively.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_recursive/despawns_recursively.rhai rename to assets/tests/data/despawn_recursive/despawns_recursively.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_recursive/invalid_entity_errors.rhai b/assets/tests/data/despawn_recursive/invalid_entity_errors.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/despawn_recursive/invalid_entity_errors.rhai rename to assets/tests/data/despawn_recursive/invalid_entity_errors.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/div/vec3.rhai b/assets/tests/data/div/vec3.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/div/vec3.rhai rename to assets/tests/data/div/vec3.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/eq/vec3.rhai b/assets/tests/data/eq/vec3.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/eq/vec3.rhai rename to assets/tests/data/eq/vec3.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/functions/contains_reflect_reference_functions.rhai b/assets/tests/data/functions/contains_reflect_reference_functions.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/functions/contains_reflect_reference_functions.rhai rename to assets/tests/data/functions/contains_reflect_reference_functions.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_children/has_children_returns_them.rhai b/assets/tests/data/get_children/has_children_returns_them.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/get_children/has_children_returns_them.rhai rename to assets/tests/data/get_children/has_children_returns_them.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_children/invalid_entity_errors.rhai b/assets/tests/data/get_children/invalid_entity_errors.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/get_children/invalid_entity_errors.rhai rename to assets/tests/data/get_children/invalid_entity_errors.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_children/no_children_returns_empty_table.rhai b/assets/tests/data/get_children/no_children_returns_empty_table.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/get_children/no_children_returns_empty_table.rhai rename to assets/tests/data/get_children/no_children_returns_empty_table.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_component/component_no_component_data.rhai b/assets/tests/data/get_component/component_no_component_data.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/get_component/component_no_component_data.rhai rename to assets/tests/data/get_component/component_no_component_data.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_component/component_with_component_data.rhai b/assets/tests/data/get_component/component_with_component_data.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/get_component/component_with_component_data.rhai rename to assets/tests/data/get_component/component_with_component_data.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_component/empty_entity_component_with_component_data.rhai b/assets/tests/data/get_component/empty_entity_component_with_component_data.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/get_component/empty_entity_component_with_component_data.rhai rename to assets/tests/data/get_component/empty_entity_component_with_component_data.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_parent/has_parent_returns_it.rhai b/assets/tests/data/get_parent/has_parent_returns_it.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/get_parent/has_parent_returns_it.rhai rename to assets/tests/data/get_parent/has_parent_returns_it.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_parent/invalid_entity_errors.rhai b/assets/tests/data/get_parent/invalid_entity_errors.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/get_parent/invalid_entity_errors.rhai rename to assets/tests/data/get_parent/invalid_entity_errors.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_parent/no_parent_returns_nil.rhai b/assets/tests/data/get_parent/no_parent_returns_nil.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/get_parent/no_parent_returns_nil.rhai rename to assets/tests/data/get_parent/no_parent_returns_nil.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_resource/missing_resource_returns_nil.rhai b/assets/tests/data/get_resource/missing_resource_returns_nil.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/get_resource/missing_resource_returns_nil.rhai rename to assets/tests/data/get_resource/missing_resource_returns_nil.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_resource/no_resource_data_returns_resource.rhai b/assets/tests/data/get_resource/no_resource_data_returns_resource.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/get_resource/no_resource_data_returns_resource.rhai rename to assets/tests/data/get_resource/no_resource_data_returns_resource.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_resource/with_resource_data_returns_resource.rhai b/assets/tests/data/get_resource/with_resource_data_returns_resource.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/get_resource/with_resource_data_returns_resource.rhai rename to assets/tests/data/get_resource/with_resource_data_returns_resource.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_type_by_name/missing_type_returns_nothing.rhai b/assets/tests/data/get_type_by_name/missing_type_returns_nothing.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/get_type_by_name/missing_type_returns_nothing.rhai rename to assets/tests/data/get_type_by_name/missing_type_returns_nothing.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/get_type_by_name/registered_type_returns_correct_type.rhai b/assets/tests/data/get_type_by_name/registered_type_returns_correct_type.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/get_type_by_name/registered_type_returns_correct_type.rhai rename to assets/tests/data/get_type_by_name/registered_type_returns_correct_type.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/globals/dynamic_globals_are_in_scope.rhai b/assets/tests/data/globals/dynamic_globals_are_in_scope.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/globals/dynamic_globals_are_in_scope.rhai rename to assets/tests/data/globals/dynamic_globals_are_in_scope.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/has_component/empty_entity_mock_component_is_false.rhai b/assets/tests/data/has_component/empty_entity_mock_component_is_false.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/has_component/empty_entity_mock_component_is_false.rhai rename to assets/tests/data/has_component/empty_entity_mock_component_is_false.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/has_component/no_component_data.rhai b/assets/tests/data/has_component/no_component_data.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/has_component/no_component_data.rhai rename to assets/tests/data/has_component/no_component_data.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/has_component/with_component_data.rhai b/assets/tests/data/has_component/with_component_data.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/has_component/with_component_data.rhai rename to assets/tests/data/has_component/with_component_data.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/has_resource/existing_no_resource_data.rhai b/assets/tests/data/has_resource/existing_no_resource_data.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/has_resource/existing_no_resource_data.rhai rename to assets/tests/data/has_resource/existing_no_resource_data.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/has_resource/existing_with_resource_data.rhai b/assets/tests/data/has_resource/existing_with_resource_data.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/has_resource/existing_with_resource_data.rhai rename to assets/tests/data/has_resource/existing_with_resource_data.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/has_resource/missing_resource_mock_resource_is_false.rhai b/assets/tests/data/has_resource/missing_resource_mock_resource_is_false.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/has_resource/missing_resource_mock_resource_is_false.rhai rename to assets/tests/data/has_resource/missing_resource_mock_resource_is_false.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/hashmap/can_pass_and_return_hashmap.rhai b/assets/tests/data/hashmap/can_pass_and_return_hashmap.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/hashmap/can_pass_and_return_hashmap.rhai rename to assets/tests/data/hashmap/can_pass_and_return_hashmap.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/insert/vec.rhai b/assets/tests/data/insert/vec.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/insert/vec.rhai rename to assets/tests/data/insert/vec.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/adding_empty_list_does_nothing.rhai b/assets/tests/data/insert_children/adding_empty_list_does_nothing.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/adding_empty_list_does_nothing.rhai rename to assets/tests/data/insert_children/adding_empty_list_does_nothing.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/adds_children_at_correct_index.rhai b/assets/tests/data/insert_children/adds_children_at_correct_index.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/adds_children_at_correct_index.rhai rename to assets/tests/data/insert_children/adds_children_at_correct_index.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/adds_children_to_existing_enttity.rhai b/assets/tests/data/insert_children/adds_children_to_existing_enttity.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/adds_children_to_existing_enttity.rhai rename to assets/tests/data/insert_children/adds_children_to_existing_enttity.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/invalid_entity_errors.rhai b/assets/tests/data/insert_children/invalid_entity_errors.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/insert_children/invalid_entity_errors.rhai rename to assets/tests/data/insert_children/invalid_entity_errors.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_component/component_no_default_or_from_world_data_inserts.rhai b/assets/tests/data/insert_component/component_no_default_or_from_world_data_inserts.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/insert_component/component_no_default_or_from_world_data_inserts.rhai rename to assets/tests/data/insert_component/component_no_default_or_from_world_data_inserts.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/insert_component/component_with_default_no_component_data_errors.rhai b/assets/tests/data/insert_component/component_with_default_no_component_data_errors.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/insert_component/component_with_default_no_component_data_errors.rhai rename to assets/tests/data/insert_component/component_with_default_no_component_data_errors.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/iter/vec.rhai b/assets/tests/data/iter/vec.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/iter/vec.rhai rename to assets/tests/data/iter/vec.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/len/vec.rhai b/assets/tests/data/len/vec.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/len/vec.rhai rename to assets/tests/data/len/vec.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/mod/vec3.rhai b/assets/tests/data/mod/vec3.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/mod/vec3.rhai rename to assets/tests/data/mod/vec3.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/mul/vec3.rhai b/assets/tests/data/mul/vec3.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/mul/vec3.rhai rename to assets/tests/data/mul/vec3.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/pop/vec.rhai b/assets/tests/data/pop/vec.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/pop/vec.rhai rename to assets/tests/data/pop/vec.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/push/vec.rhai b/assets/tests/data/push/vec.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/push/vec.rhai rename to assets/tests/data/push/vec.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/push_children/adding_empty_list_does_nothing.rhai b/assets/tests/data/push_children/adding_empty_list_does_nothing.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/push_children/adding_empty_list_does_nothing.rhai rename to assets/tests/data/push_children/adding_empty_list_does_nothing.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/push_children/adds_children_to_existing_enttity.rhai b/assets/tests/data/push_children/adds_children_to_existing_enttity.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/push_children/adds_children_to_existing_enttity.rhai rename to assets/tests/data/push_children/adds_children_to_existing_enttity.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/push_children/invalid_entity_errors.rhai b/assets/tests/data/push_children/invalid_entity_errors.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/push_children/invalid_entity_errors.rhai rename to assets/tests/data/push_children/invalid_entity_errors.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/query/empty_query_returns_nothing.rhai b/assets/tests/data/query/empty_query_returns_nothing.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/query/empty_query_returns_nothing.rhai rename to assets/tests/data/query/empty_query_returns_nothing.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/query/query_returns_all_entities_matching.rhai b/assets/tests/data/query/query_returns_all_entities_matching.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/query/query_returns_all_entities_matching.rhai rename to assets/tests/data/query/query_returns_all_entities_matching.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/remove/vec.rhai b/assets/tests/data/remove/vec.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/remove/vec.rhai rename to assets/tests/data/remove/vec.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_component/empty_entity_does_nothing.rhai b/assets/tests/data/remove_component/empty_entity_does_nothing.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/remove_component/empty_entity_does_nothing.rhai rename to assets/tests/data/remove_component/empty_entity_does_nothing.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_component/no_component_data_errors.rhai b/assets/tests/data/remove_component/no_component_data_errors.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/remove_component/no_component_data_errors.rhai rename to assets/tests/data/remove_component/no_component_data_errors.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_component/with_component_data_removes_component.rhai b/assets/tests/data/remove_component/with_component_data_removes_component.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/remove_component/with_component_data_removes_component.rhai rename to assets/tests/data/remove_component/with_component_data_removes_component.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_resource/missing_resource_with_resource_data_does_nothing.rhai b/assets/tests/data/remove_resource/missing_resource_with_resource_data_does_nothing.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/remove_resource/missing_resource_with_resource_data_does_nothing.rhai rename to assets/tests/data/remove_resource/missing_resource_with_resource_data_does_nothing.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_resource/no_resource_data_errors.rhai b/assets/tests/data/remove_resource/no_resource_data_errors.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/remove_resource/no_resource_data_errors.rhai rename to assets/tests/data/remove_resource/no_resource_data_errors.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/remove_resource/with_resource_data_removes_resource.rhai b/assets/tests/data/remove_resource/with_resource_data_removes_resource.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/remove_resource/with_resource_data_removes_resource.rhai rename to assets/tests/data/remove_resource/with_resource_data_removes_resource.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/set/set_primitives_works.rhai b/assets/tests/data/set/set_primitives_works.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/set/set_primitives_works.rhai rename to assets/tests/data/set/set_primitives_works.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/sub/vec3.rhai b/assets/tests/data/sub/vec3.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/sub/vec3.rhai rename to assets/tests/data/sub/vec3.rhai diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/data/unm/vec3.rhai b/assets/tests/data/unm/vec3.rhai similarity index 100% rename from crates/languages/bevy_mod_scripting_rhai/tests/data/unm/vec3.rhai rename to assets/tests/data/unm/vec3.rhai diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/despawn/despawns_only_root.lua b/assets/tests/despawn/despawns_only_root.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/despawn/despawns_only_root.lua rename to assets/tests/despawn/despawns_only_root.lua diff --git a/assets/tests/despawn/despawns_only_root.rhai b/assets/tests/despawn/despawns_only_root.rhai new file mode 100644 index 0000000000..5c3a8fc466 --- /dev/null +++ b/assets/tests/despawn/despawns_only_root.rhai @@ -0,0 +1,7 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); +world.push_children.call(entity, [child]); +world.despawn.call(entity); + +assert(world.has_entity.call(entity) == false, "Parent should be despawned"); +assert(world.has_entity.call(child) == true, "Child should not be despawned"); diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/despawn/invalid_entity_errors.lua b/assets/tests/despawn/invalid_entity_errors.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/despawn/invalid_entity_errors.lua rename to assets/tests/despawn/invalid_entity_errors.lua diff --git a/assets/tests/despawn/invalid_entity_errors.rhai b/assets/tests/despawn/invalid_entity_errors.rhai new file mode 100644 index 0000000000..730fca0782 --- /dev/null +++ b/assets/tests/despawn/invalid_entity_errors.rhai @@ -0,0 +1,3 @@ +assert_throws(||{ + world.despawn_recursive.call(Entity.from_raw.call(9999)) +}, "Missing or invalid entity"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/despawn_descendants/despawns_only_child.lua b/assets/tests/despawn_descendants/despawns_only_child.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/despawn_descendants/despawns_only_child.lua rename to assets/tests/despawn_descendants/despawns_only_child.lua diff --git a/assets/tests/despawn_descendants/despawns_only_child.rhai b/assets/tests/despawn_descendants/despawns_only_child.rhai new file mode 100644 index 0000000000..fb6996cee4 --- /dev/null +++ b/assets/tests/despawn_descendants/despawns_only_child.rhai @@ -0,0 +1,7 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); +world.push_children.call(entity, [child]); +world.despawn_descendants.call(entity); + +assert(world.has_entity.call(entity) == true, "Parent should not be despawned"); +assert(world.has_entity.call(child) == false, "Child should be despawned"); diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/despawn_descendants/invalid_entity_errors.lua b/assets/tests/despawn_descendants/invalid_entity_errors.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/despawn_descendants/invalid_entity_errors.lua rename to assets/tests/despawn_descendants/invalid_entity_errors.lua diff --git a/assets/tests/despawn_descendants/invalid_entity_errors.rhai b/assets/tests/despawn_descendants/invalid_entity_errors.rhai new file mode 100644 index 0000000000..bdaa98ff33 --- /dev/null +++ b/assets/tests/despawn_descendants/invalid_entity_errors.rhai @@ -0,0 +1,3 @@ +assert_throws(||{ + world.despawn_recursive.call(Entity.from_raw.call(9999)); +}, "Missing or invalid entity") \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/despawn_recursive/despawns_recursively.lua b/assets/tests/despawn_recursive/despawns_recursively.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/despawn_recursive/despawns_recursively.lua rename to assets/tests/despawn_recursive/despawns_recursively.lua diff --git a/assets/tests/despawn_recursive/despawns_recursively.rhai b/assets/tests/despawn_recursive/despawns_recursively.rhai new file mode 100644 index 0000000000..8d4e2ab757 --- /dev/null +++ b/assets/tests/despawn_recursive/despawns_recursively.rhai @@ -0,0 +1,7 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); +world.push_children.call(entity, [child]); +world.despawn_recursive.call(entity); + +assert(world.has_entity.call(entity) == false, "Parent should be despawned"); +assert(world.has_entity.call(child) == false, "Child should be despawned"); diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/despawn_recursive/invalid_entity_errors.lua b/assets/tests/despawn_recursive/invalid_entity_errors.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/despawn_recursive/invalid_entity_errors.lua rename to assets/tests/despawn_recursive/invalid_entity_errors.lua diff --git a/assets/tests/despawn_recursive/invalid_entity_errors.rhai b/assets/tests/despawn_recursive/invalid_entity_errors.rhai new file mode 100644 index 0000000000..bdaa98ff33 --- /dev/null +++ b/assets/tests/despawn_recursive/invalid_entity_errors.rhai @@ -0,0 +1,3 @@ +assert_throws(||{ + world.despawn_recursive.call(Entity.from_raw.call(9999)); +}, "Missing or invalid entity") \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/div/vec3.lua b/assets/tests/div/vec3.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/div/vec3.lua rename to assets/tests/div/vec3.lua diff --git a/assets/tests/div/vec3.rhai b/assets/tests/div/vec3.rhai new file mode 100644 index 0000000000..af3a96d5f0 --- /dev/null +++ b/assets/tests/div/vec3.rhai @@ -0,0 +1,10 @@ +let a = Vec3.new_.call(2.0, 4.0, 6.0); +let b = Vec3.new_.call(1.0, 2.0, 3.0); + +assert((a / 2).x == 1.0, "Division did not work"); +assert((a / 2).y == 2.0, "Division did not work"); +assert((a / 2).z == 3.0, "Division did not work"); + +assert((a / b).x == 2.0, "Division did not work"); +assert((a / b).y == 2.0, "Division did not work"); +assert((a / b).z == 2.0, "Division did not work"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/eq/vec3.lua b/assets/tests/eq/vec3.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/eq/vec3.lua rename to assets/tests/eq/vec3.lua diff --git a/assets/tests/eq/vec3.rhai b/assets/tests/eq/vec3.rhai new file mode 100644 index 0000000000..f4652fed8c --- /dev/null +++ b/assets/tests/eq/vec3.rhai @@ -0,0 +1,7 @@ +let a = Vec3.new_.call(2.0, -4.0, 6.0); +let b = Vec3.new_.call(4.0, 5.0, 6.0); + + +assert((a == b) == false, "Equality did not work"); +assert((a != b) == true, "Inequality did not work"); +assert((a == a) == true, "Equality did not work"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/functions/contains_reflect_reference_functions.lua b/assets/tests/functions/contains_reflect_reference_functions.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/functions/contains_reflect_reference_functions.lua rename to assets/tests/functions/contains_reflect_reference_functions.lua diff --git a/assets/tests/functions/contains_reflect_reference_functions.rhai b/assets/tests/functions/contains_reflect_reference_functions.rhai new file mode 100644 index 0000000000..f83b980c99 --- /dev/null +++ b/assets/tests/functions/contains_reflect_reference_functions.rhai @@ -0,0 +1,14 @@ + +let Resource = world.get_type_by_name.call("TestResource"); +let resource = world.get_resource.call(Resource); + +let functions = resource.functions.call(); +assert(functions.len() > 0, "functions should not be empty"); + +let available_names = []; + +for function_ref in functions { + available_names.push(function_ref.name); +} + +assert("display_ref" in available_names, "functions should contain display_ref, but got: " + available_names); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/get_children/has_children_returns_them.lua b/assets/tests/get_children/has_children_returns_them.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/get_children/has_children_returns_them.lua rename to assets/tests/get_children/has_children_returns_them.lua diff --git a/assets/tests/get_children/has_children_returns_them.rhai b/assets/tests/get_children/has_children_returns_them.rhai new file mode 100644 index 0000000000..1f8bece3a4 --- /dev/null +++ b/assets/tests/get_children/has_children_returns_them.rhai @@ -0,0 +1,9 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); + +world.push_children.call(entity, [child]); + +let children = world.get_children.call(entity); + +assert(children.len == 1, "Expected 1 child"); +assert(children[0].index.call() == child.index.call(), "Child is the wrong entity"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/get_children/invalid_entity_errors.lua b/assets/tests/get_children/invalid_entity_errors.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/get_children/invalid_entity_errors.lua rename to assets/tests/get_children/invalid_entity_errors.lua diff --git a/assets/tests/get_children/invalid_entity_errors.rhai b/assets/tests/get_children/invalid_entity_errors.rhai new file mode 100644 index 0000000000..90bf506ca9 --- /dev/null +++ b/assets/tests/get_children/invalid_entity_errors.rhai @@ -0,0 +1,3 @@ +assert_throws(||{ + world.get_children.call(Entity.from_raw.call(9999)); +}, "Missing or invalid entity"); diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/get_children/no_children_returns_empty_table.lua b/assets/tests/get_children/no_children_returns_empty_table.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/get_children/no_children_returns_empty_table.lua rename to assets/tests/get_children/no_children_returns_empty_table.lua diff --git a/assets/tests/get_children/no_children_returns_empty_table.rhai b/assets/tests/get_children/no_children_returns_empty_table.rhai new file mode 100644 index 0000000000..1ecc14c5a0 --- /dev/null +++ b/assets/tests/get_children/no_children_returns_empty_table.rhai @@ -0,0 +1,4 @@ +let entity = world.spawn_.call(); +let children = world.get_children.call(entity); + +assert(children.len == 0); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/get_component/component_no_component_data.lua b/assets/tests/get_component/component_no_component_data.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/get_component/component_no_component_data.lua rename to assets/tests/get_component/component_no_component_data.lua diff --git a/assets/tests/get_component/component_no_component_data.rhai b/assets/tests/get_component/component_no_component_data.rhai new file mode 100644 index 0000000000..1000055921 --- /dev/null +++ b/assets/tests/get_component/component_no_component_data.rhai @@ -0,0 +1,6 @@ +let component = world.get_type_by_name.call("CompWithDefault"); +let entity = world._get_entity_with_test_component.call("CompWithDefault"); +let retrieved = world.get_component.call(entity, component); + +assert(type_of(retrieved) != "()", "Component was not found"); +assert(retrieved["_0"] == "Initial Value", "Component data was not retrieved correctly, retrieved._0 was: " + retrieved["_0"]); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/get_component/component_with_component_data.lua b/assets/tests/get_component/component_with_component_data.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/get_component/component_with_component_data.lua rename to assets/tests/get_component/component_with_component_data.lua diff --git a/assets/tests/get_component/component_with_component_data.rhai b/assets/tests/get_component/component_with_component_data.rhai new file mode 100644 index 0000000000..7a638c20ea --- /dev/null +++ b/assets/tests/get_component/component_with_component_data.rhai @@ -0,0 +1,6 @@ +let component = world.get_type_by_name.call("TestComponent"); +let entity = world._get_entity_with_test_component.call("TestComponent"); +let retrieved = world.get_component.call(entity, component); + +assert(type_of(retrieved) != "()", "Component was not found"); +assert(retrieved.strings[0] == "Initial", "Component data was not retrieved correctly, retrieved.strings[0] was: " + retrieved.strings[0]); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/get_component/empty_entity_component_with_component_data.lua b/assets/tests/get_component/empty_entity_component_with_component_data.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/get_component/empty_entity_component_with_component_data.lua rename to assets/tests/get_component/empty_entity_component_with_component_data.lua diff --git a/assets/tests/get_component/empty_entity_component_with_component_data.rhai b/assets/tests/get_component/empty_entity_component_with_component_data.rhai new file mode 100644 index 0000000000..42c4abc0f1 --- /dev/null +++ b/assets/tests/get_component/empty_entity_component_with_component_data.rhai @@ -0,0 +1,5 @@ +let component = world.get_type_by_name.call("TestComponent"); +let entity = world.spawn_.call(); +let retrieved = world.get_component.call(entity, component); + +assert(type_of(retrieved) == "()", "Component found"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/get_parent/has_parent_returns_it.lua b/assets/tests/get_parent/has_parent_returns_it.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/get_parent/has_parent_returns_it.lua rename to assets/tests/get_parent/has_parent_returns_it.lua diff --git a/assets/tests/get_parent/has_parent_returns_it.rhai b/assets/tests/get_parent/has_parent_returns_it.rhai new file mode 100644 index 0000000000..a992e6068c --- /dev/null +++ b/assets/tests/get_parent/has_parent_returns_it.rhai @@ -0,0 +1,9 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); + +world.push_children.call(entity, [child]); + +let parent = world.get_parent.call(child); + +assert(type_of(parent) != "()", "Expected a parent"); +assert(parent.index.call() == entity.index.call(), "Parent is the wrong entity"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/get_parent/invalid_entity_errors.lua b/assets/tests/get_parent/invalid_entity_errors.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/get_parent/invalid_entity_errors.lua rename to assets/tests/get_parent/invalid_entity_errors.lua diff --git a/assets/tests/get_parent/invalid_entity_errors.rhai b/assets/tests/get_parent/invalid_entity_errors.rhai new file mode 100644 index 0000000000..b25d0981df --- /dev/null +++ b/assets/tests/get_parent/invalid_entity_errors.rhai @@ -0,0 +1,4 @@ + +assert_throws(||{ + world.get_parent.call(Entity.from_raw.call(9999)); +}, "Missing or invalid entity"); diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/get_parent/no_parent_returns_nil.lua b/assets/tests/get_parent/no_parent_returns_nil.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/get_parent/no_parent_returns_nil.lua rename to assets/tests/get_parent/no_parent_returns_nil.lua diff --git a/assets/tests/get_parent/no_parent_returns_nil.rhai b/assets/tests/get_parent/no_parent_returns_nil.rhai new file mode 100644 index 0000000000..d4abc7d2d5 --- /dev/null +++ b/assets/tests/get_parent/no_parent_returns_nil.rhai @@ -0,0 +1,4 @@ +let entity = world.spawn_.call(); +let parent = world.get_parent.call(entity); + +assert(type_of(parent) == "()", "Expected no parents"); diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/get_resource/missing_resource_returns_nil.lua b/assets/tests/get_resource/missing_resource_returns_nil.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/get_resource/missing_resource_returns_nil.lua rename to assets/tests/get_resource/missing_resource_returns_nil.lua diff --git a/assets/tests/get_resource/missing_resource_returns_nil.rhai b/assets/tests/get_resource/missing_resource_returns_nil.rhai new file mode 100644 index 0000000000..e127d1a958 --- /dev/null +++ b/assets/tests/get_resource/missing_resource_returns_nil.rhai @@ -0,0 +1,2 @@ +let type = world._get_mock_resource_type.call(); +assert(type_of(world.get_resource.call(type)) == "()", "Resource should not exist"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/get_resource/no_resource_data_returns_resource.lua b/assets/tests/get_resource/no_resource_data_returns_resource.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/get_resource/no_resource_data_returns_resource.lua rename to assets/tests/get_resource/no_resource_data_returns_resource.lua diff --git a/assets/tests/get_resource/no_resource_data_returns_resource.rhai b/assets/tests/get_resource/no_resource_data_returns_resource.rhai new file mode 100644 index 0000000000..3c172e5ac3 --- /dev/null +++ b/assets/tests/get_resource/no_resource_data_returns_resource.rhai @@ -0,0 +1,5 @@ +let resource = world.get_type_by_name.call("ResourceWithDefault"); + +let retrieved = world.get_resource.call(resource); +assert(type_of(retrieved) != "()", "Resource should exist"); +assert(retrieved["_0"] == "Initial Value", "Resource should have default value but got: " + retrieved["_0"]); diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/get_resource/with_resource_data_returns_resource.lua b/assets/tests/get_resource/with_resource_data_returns_resource.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/get_resource/with_resource_data_returns_resource.lua rename to assets/tests/get_resource/with_resource_data_returns_resource.lua diff --git a/assets/tests/get_resource/with_resource_data_returns_resource.rhai b/assets/tests/get_resource/with_resource_data_returns_resource.rhai new file mode 100644 index 0000000000..c0f657d6b8 --- /dev/null +++ b/assets/tests/get_resource/with_resource_data_returns_resource.rhai @@ -0,0 +1,5 @@ +let resource = world.get_type_by_name.call("TestResource"); + +let retrieved = world.get_resource.call(resource); +assert(type_of(retrieved) != "()", "Resource should exist"); +assert(retrieved.bytes[1] == 1, "Resource should have default value but got resource with #retrieved.bytes[1]: " + retrieved.bytes[1]); diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/get_type_by_name/missing_type_returns_nothing.lua b/assets/tests/get_type_by_name/missing_type_returns_nothing.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/get_type_by_name/missing_type_returns_nothing.lua rename to assets/tests/get_type_by_name/missing_type_returns_nothing.lua diff --git a/assets/tests/get_type_by_name/missing_type_returns_nothing.rhai b/assets/tests/get_type_by_name/missing_type_returns_nothing.rhai new file mode 100644 index 0000000000..2ac4944019 --- /dev/null +++ b/assets/tests/get_type_by_name/missing_type_returns_nothing.rhai @@ -0,0 +1,3 @@ +let type = world.get_type_by_name.call("MissingType"); + +assert(type == (), "Unregistered type was found"); diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/get_type_by_name/registered_component_returns_correct_type.lua b/assets/tests/get_type_by_name/registered_component_returns_correct_type.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/get_type_by_name/registered_component_returns_correct_type.lua rename to assets/tests/get_type_by_name/registered_component_returns_correct_type.lua diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/get_type_by_name/registered_resource_returns_correct_type.lua b/assets/tests/get_type_by_name/registered_resource_returns_correct_type.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/get_type_by_name/registered_resource_returns_correct_type.lua rename to assets/tests/get_type_by_name/registered_resource_returns_correct_type.lua diff --git a/assets/tests/get_type_by_name/registered_type_returns_correct_type.rhai b/assets/tests/get_type_by_name/registered_type_returns_correct_type.rhai new file mode 100644 index 0000000000..00f5379b8a --- /dev/null +++ b/assets/tests/get_type_by_name/registered_type_returns_correct_type.rhai @@ -0,0 +1,17 @@ +let type = world.get_type_by_name.call("TestComponent"); + +assert(type_of(type) != "()", "Registered type was not found"); + +let expected_type_name = "test_utils::test_data::TestComponent"; +let expected_short_name = "TestComponent"; + +let received_type_name = type.type_name.call(type); +let received_short_name = type.short_name.call(type); + + +// assert(type != (), 'Type not found') +// assert(received.type_name == expected.type_name, 'type_name mismatch, expected: ' .. expected.type_name .. ', got: ' .. received.type_name) +// assert(received.short_name == expected.short_name, 'short_name mismatch, expected: ' .. expected.short_name .. ', got: ' .. received.short_name) + +assert(received_type_name == expected_type_name, "type_name mismatch, expected: " + expected_type_name + ", got: " + received_type_name); +assert(received_short_name == expected_short_name, "short_name mismatch, expected: " + expected_short_name + ", got: " + received_short_name); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/globals/dynamic_globals_are_in_scope.lua b/assets/tests/globals/dynamic_globals_are_in_scope.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/globals/dynamic_globals_are_in_scope.lua rename to assets/tests/globals/dynamic_globals_are_in_scope.lua diff --git a/assets/tests/globals/dynamic_globals_are_in_scope.rhai b/assets/tests/globals/dynamic_globals_are_in_scope.rhai new file mode 100644 index 0000000000..32455f2363 --- /dev/null +++ b/assets/tests/globals/dynamic_globals_are_in_scope.rhai @@ -0,0 +1 @@ +assert(global_hello_world.call() == "hi!", "global_hello_world() == 'hi!'") \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/has_component/empty_entity_mock_component_is_false.lua b/assets/tests/has_component/empty_entity_mock_component_is_false.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/has_component/empty_entity_mock_component_is_false.lua rename to assets/tests/has_component/empty_entity_mock_component_is_false.lua diff --git a/assets/tests/has_component/empty_entity_mock_component_is_false.rhai b/assets/tests/has_component/empty_entity_mock_component_is_false.rhai new file mode 100644 index 0000000000..65aaec5348 --- /dev/null +++ b/assets/tests/has_component/empty_entity_mock_component_is_false.rhai @@ -0,0 +1,4 @@ +let entity = world.spawn_.call(); +let type = world._get_mock_component_type.call(); + +assert(world.has_component.call(entity, type) == false, "Entity should not have component"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/has_component/no_component_data.lua b/assets/tests/has_component/no_component_data.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/has_component/no_component_data.lua rename to assets/tests/has_component/no_component_data.lua diff --git a/assets/tests/has_component/no_component_data.rhai b/assets/tests/has_component/no_component_data.rhai new file mode 100644 index 0000000000..e5b998e6b6 --- /dev/null +++ b/assets/tests/has_component/no_component_data.rhai @@ -0,0 +1,3 @@ +let entity = world._get_entity_with_test_component.call("CompWithDefault"); +let component = world.get_type_by_name.call("CompWithDefault"); +assert(world.has_component.call(entity, component) == true, "Component was not found"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/has_component/with_component_data.lua b/assets/tests/has_component/with_component_data.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/has_component/with_component_data.lua rename to assets/tests/has_component/with_component_data.lua diff --git a/assets/tests/has_component/with_component_data.rhai b/assets/tests/has_component/with_component_data.rhai new file mode 100644 index 0000000000..78240ee203 --- /dev/null +++ b/assets/tests/has_component/with_component_data.rhai @@ -0,0 +1,3 @@ +let entity = world._get_entity_with_test_component.call("TestComponent"); +let component = world.get_type_by_name.call("TestComponent"); +assert(world.has_component.call(entity, component) == true, "Component was not found"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/has_resource/existing_no_resource_data.lua b/assets/tests/has_resource/existing_no_resource_data.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/has_resource/existing_no_resource_data.lua rename to assets/tests/has_resource/existing_no_resource_data.lua diff --git a/assets/tests/has_resource/existing_no_resource_data.rhai b/assets/tests/has_resource/existing_no_resource_data.rhai new file mode 100644 index 0000000000..20c34cb167 --- /dev/null +++ b/assets/tests/has_resource/existing_no_resource_data.rhai @@ -0,0 +1,2 @@ +let component = world.get_type_by_name.call("ResourceWithDefault"); +assert(world.has_resource.call(component) == true, "Resource was not found"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/has_resource/existing_with_resource_data.lua b/assets/tests/has_resource/existing_with_resource_data.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/has_resource/existing_with_resource_data.lua rename to assets/tests/has_resource/existing_with_resource_data.lua diff --git a/assets/tests/has_resource/existing_with_resource_data.rhai b/assets/tests/has_resource/existing_with_resource_data.rhai new file mode 100644 index 0000000000..28a9bf59b1 --- /dev/null +++ b/assets/tests/has_resource/existing_with_resource_data.rhai @@ -0,0 +1,2 @@ +let component = world.get_type_by_name.call("TestResource"); +assert(world.has_resource.call(component) == true, "Resource was not found"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/has_resource/missing_resource_mock_resource_is_false.lua b/assets/tests/has_resource/missing_resource_mock_resource_is_false.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/has_resource/missing_resource_mock_resource_is_false.lua rename to assets/tests/has_resource/missing_resource_mock_resource_is_false.lua diff --git a/assets/tests/has_resource/missing_resource_mock_resource_is_false.rhai b/assets/tests/has_resource/missing_resource_mock_resource_is_false.rhai new file mode 100644 index 0000000000..191aca8f15 --- /dev/null +++ b/assets/tests/has_resource/missing_resource_mock_resource_is_false.rhai @@ -0,0 +1,2 @@ +let type = world._get_mock_resource_type.call(); +assert(world.has_resource.call(type) == false, "Resource should not exist"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/hashmap/can_pass_and_return_hashmap.lua b/assets/tests/hashmap/can_pass_and_return_hashmap.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/hashmap/can_pass_and_return_hashmap.lua rename to assets/tests/hashmap/can_pass_and_return_hashmap.lua diff --git a/assets/tests/hashmap/can_pass_and_return_hashmap.rhai b/assets/tests/hashmap/can_pass_and_return_hashmap.rhai new file mode 100644 index 0000000000..60b45a3da4 --- /dev/null +++ b/assets/tests/hashmap/can_pass_and_return_hashmap.rhai @@ -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"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/insert/vec.lua b/assets/tests/insert/vec.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/insert/vec.lua rename to assets/tests/insert/vec.lua diff --git a/assets/tests/insert/vec.rhai b/assets/tests/insert/vec.rhai new file mode 100644 index 0000000000..d5f250418a --- /dev/null +++ b/assets/tests/insert/vec.rhai @@ -0,0 +1,6 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +res.vec_usize.insert.call(2, 42); + +assert(res.vec_usize[2] == 42, "insert did not work"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/insert_children/adding_empty_list_does_nothing.lua b/assets/tests/insert_children/adding_empty_list_does_nothing.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/insert_children/adding_empty_list_does_nothing.lua rename to assets/tests/insert_children/adding_empty_list_does_nothing.lua diff --git a/assets/tests/insert_children/adding_empty_list_does_nothing.rhai b/assets/tests/insert_children/adding_empty_list_does_nothing.rhai new file mode 100644 index 0000000000..85825b47ac --- /dev/null +++ b/assets/tests/insert_children/adding_empty_list_does_nothing.rhai @@ -0,0 +1,5 @@ +let entity = world.spawn_.call(); + +world.insert_children.call(entity,0 ,[]); + +assert(world.get_children.call(entity).len == 0); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/insert_children/adds_children_at_correct_index.lua b/assets/tests/insert_children/adds_children_at_correct_index.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/insert_children/adds_children_at_correct_index.lua rename to assets/tests/insert_children/adds_children_at_correct_index.lua diff --git a/assets/tests/insert_children/adds_children_at_correct_index.rhai b/assets/tests/insert_children/adds_children_at_correct_index.rhai new file mode 100644 index 0000000000..6c922beb71 --- /dev/null +++ b/assets/tests/insert_children/adds_children_at_correct_index.rhai @@ -0,0 +1,8 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); +let child2 = world.spawn_.call(); + +world.insert_children.call(entity, 0, [child]); +world.insert_children.call(entity, 0, [child2]); + +assert(world.get_children.call(entity)[0].index.call() == child2.index.call()); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/insert_children/adds_children_to_existing_enttity.lua b/assets/tests/insert_children/adds_children_to_existing_enttity.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/insert_children/adds_children_to_existing_enttity.lua rename to assets/tests/insert_children/adds_children_to_existing_enttity.lua diff --git a/assets/tests/insert_children/adds_children_to_existing_enttity.rhai b/assets/tests/insert_children/adds_children_to_existing_enttity.rhai new file mode 100644 index 0000000000..5c5a542ad9 --- /dev/null +++ b/assets/tests/insert_children/adds_children_to_existing_enttity.rhai @@ -0,0 +1,6 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); +let child2 = world.spawn_.call(); +world.insert_children.call(entity, 0, [child, child2]); + +assert(world.get_children.call(entity).len == 2); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/insert_children/invalid_entity_errors.lua b/assets/tests/insert_children/invalid_entity_errors.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/insert_children/invalid_entity_errors.lua rename to assets/tests/insert_children/invalid_entity_errors.lua diff --git a/assets/tests/insert_children/invalid_entity_errors.rhai b/assets/tests/insert_children/invalid_entity_errors.rhai new file mode 100644 index 0000000000..2b960661ec --- /dev/null +++ b/assets/tests/insert_children/invalid_entity_errors.rhai @@ -0,0 +1,10 @@ +let fake_entity = Entity.from_raw.call(9999); + +assert_throws(||{ + world.insert_children.call(fake_entity, 0, [fake_entity]); +}, "Missing or invalid entity"); + +let entity = world.spawn_.call(); +assert_throws(||{ + world.insert_children.call(entity, 0, [fake_entity]); +}, "Missing or invalid entity"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/insert_component/component_no_default_or_from_world_data_inserts.lua b/assets/tests/insert_component/component_no_default_or_from_world_data_inserts.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/insert_component/component_no_default_or_from_world_data_inserts.lua rename to assets/tests/insert_component/component_no_default_or_from_world_data_inserts.lua diff --git a/assets/tests/insert_component/component_no_default_or_from_world_data_inserts.rhai b/assets/tests/insert_component/component_no_default_or_from_world_data_inserts.rhai new file mode 100644 index 0000000000..fcf3aa2747 --- /dev/null +++ b/assets/tests/insert_component/component_no_default_or_from_world_data_inserts.rhai @@ -0,0 +1,8 @@ +let entity = world.spawn_.call(); +let type = world.get_type_by_name.call("TestComponent"); +let entity_with_component = world._get_entity_with_test_component.call("TestComponent"); +let existing_component = world.get_component.call(entity_with_component, type); + +assert(world.has_component.call(entity, type) == false, "Expected entity to not have component before adding, test invalid"); +world.insert_component.call(entity, type, existing_component); +assert(world.has_component.call(entity, type) == true, "Expected entity to have component after adding"); diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/insert_component/component_with_default_no_component_data_errors.lua b/assets/tests/insert_component/component_with_default_no_component_data_errors.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/insert_component/component_with_default_no_component_data_errors.lua rename to assets/tests/insert_component/component_with_default_no_component_data_errors.lua diff --git a/assets/tests/insert_component/component_with_default_no_component_data_errors.rhai b/assets/tests/insert_component/component_with_default_no_component_data_errors.rhai new file mode 100644 index 0000000000..bf2997a115 --- /dev/null +++ b/assets/tests/insert_component/component_with_default_no_component_data_errors.rhai @@ -0,0 +1,8 @@ +let entity = world.spawn_.call(); +let _type = world.get_type_by_name.call("CompWithDefault"); +let entity_with_component = world._get_entity_with_test_component.call("CompWithDefault"); +let existing_component = world.get_component.call(entity_with_component, _type); + +assert_throws(||{ + world.insert_component.call(entity, _type, existing_component); +}, "Missing type data ReflectComponent for type: .*CompWithDefault.*"); diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/iter/vec.lua b/assets/tests/iter/vec.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/iter/vec.lua rename to assets/tests/iter/vec.lua diff --git a/assets/tests/iter/vec.rhai b/assets/tests/iter/vec.rhai new file mode 100644 index 0000000000..accbcd587a --- /dev/null +++ b/assets/tests/iter/vec.rhai @@ -0,0 +1,15 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +let iterated_vals = []; + +for v in res.vec_usize { + iterated_vals.push(v); +} + +assert(iterated_vals.len == 5, "Length is not 5"); +assert(iterated_vals[0] == 1, "First value is not 1"); +assert(iterated_vals[1] == 2, "Second value is not 2"); +assert(iterated_vals[2] == 3, "Third value is not 3"); +assert(iterated_vals[3] == 4, "Fourth value is not 4"); +assert(iterated_vals[4] == 5, "Fifth value is not 5"); diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/len/vec.lua b/assets/tests/len/vec.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/len/vec.lua rename to assets/tests/len/vec.lua diff --git a/assets/tests/len/vec.rhai b/assets/tests/len/vec.rhai new file mode 100644 index 0000000000..b77fecc68c --- /dev/null +++ b/assets/tests/len/vec.rhai @@ -0,0 +1,4 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +assert(res.vec_usize.len.call() == 5, "Length is not 5"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/mod/vec3.lua b/assets/tests/mod/vec3.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/mod/vec3.lua rename to assets/tests/mod/vec3.lua diff --git a/assets/tests/mod/vec3.rhai b/assets/tests/mod/vec3.rhai new file mode 100644 index 0000000000..098425dcfe --- /dev/null +++ b/assets/tests/mod/vec3.rhai @@ -0,0 +1,10 @@ +let a = Vec3.new_.call(2.0, 5.0, 6.0); +let b = Vec3.new_.call(1.0, 2.0, 3.0); + +assert((a % 2).x == 0.0, "Modulus did not work"); +assert((a % 2).y == 1.0, "Modulus did not work"); +assert((a % 2).z == 0.0, "Modulus did not work"); + +assert((a % b).x == 0.0, "Modulus did not work"); +assert((a % b).y == 1.0, "Modulus did not work"); +assert((a % b).z == 0.0, "Modulus did not work"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/mul/vec3.lua b/assets/tests/mul/vec3.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/mul/vec3.lua rename to assets/tests/mul/vec3.lua diff --git a/assets/tests/mul/vec3.rhai b/assets/tests/mul/vec3.rhai new file mode 100644 index 0000000000..5b62477cd7 --- /dev/null +++ b/assets/tests/mul/vec3.rhai @@ -0,0 +1,10 @@ +let a = Vec3.new_.call(1.0, 2.0, 3.0); +let b = Vec3.new_.call(4.0, 5.0, 6.0); + +assert((a * 2).x == 2.0, "Multiplication did not work"); +assert((a * 2).y == 4.0, "Multiplication did not work"); +assert((a * 2).z == 6.0, "Multiplication did not work"); + +assert((a * b).x == 4.0, "Multiplication did not work"); +assert((a * b).y == 10.0, "Multiplication did not work"); +assert((a * b).z == 18.0, "Multiplication did not work"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/pop/vec.lua b/assets/tests/pop/vec.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/pop/vec.lua rename to assets/tests/pop/vec.lua diff --git a/assets/tests/pop/vec.rhai b/assets/tests/pop/vec.rhai new file mode 100644 index 0000000000..bf94edb9d2 --- /dev/null +++ b/assets/tests/pop/vec.rhai @@ -0,0 +1,6 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +let popped = res.vec_usize.pop.call(); + +assert(popped == 5, "Pop did not work"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/push/vec.lua b/assets/tests/push/vec.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/push/vec.lua rename to assets/tests/push/vec.lua diff --git a/assets/tests/push/vec.rhai b/assets/tests/push/vec.rhai new file mode 100644 index 0000000000..f6c6c37610 --- /dev/null +++ b/assets/tests/push/vec.rhai @@ -0,0 +1,6 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +res.vec_usize.push.call(42); + +assert(res.vec_usize[5] == 42, "Push did not work"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/push_children/adding_empty_list_does_nothing.lua b/assets/tests/push_children/adding_empty_list_does_nothing.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/push_children/adding_empty_list_does_nothing.lua rename to assets/tests/push_children/adding_empty_list_does_nothing.lua diff --git a/assets/tests/push_children/adding_empty_list_does_nothing.rhai b/assets/tests/push_children/adding_empty_list_does_nothing.rhai new file mode 100644 index 0000000000..e4d2905284 --- /dev/null +++ b/assets/tests/push_children/adding_empty_list_does_nothing.rhai @@ -0,0 +1,5 @@ +let entity = world.spawn_.call(); + +world.push_children.call(entity, []); + +assert(world.get_children.call(entity).len == 0); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/push_children/adds_children_to_existing_enttity.lua b/assets/tests/push_children/adds_children_to_existing_enttity.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/push_children/adds_children_to_existing_enttity.lua rename to assets/tests/push_children/adds_children_to_existing_enttity.lua diff --git a/assets/tests/push_children/adds_children_to_existing_enttity.rhai b/assets/tests/push_children/adds_children_to_existing_enttity.rhai new file mode 100644 index 0000000000..7c77549edd --- /dev/null +++ b/assets/tests/push_children/adds_children_to_existing_enttity.rhai @@ -0,0 +1,7 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); +let child2 = world.spawn_.call(); + +world.push_children.call(entity, [child, child2]); + +assert(world.get_children.call(entity).len == 2); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/push_children/invalid_entity_errors.lua b/assets/tests/push_children/invalid_entity_errors.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/push_children/invalid_entity_errors.lua rename to assets/tests/push_children/invalid_entity_errors.lua diff --git a/assets/tests/push_children/invalid_entity_errors.rhai b/assets/tests/push_children/invalid_entity_errors.rhai new file mode 100644 index 0000000000..15d6ca9501 --- /dev/null +++ b/assets/tests/push_children/invalid_entity_errors.rhai @@ -0,0 +1,10 @@ +let fake_entity = Entity.from_raw.call(9999); + +assert_throws(||{ + world.push_children.call(fake_entity, [fake_entity]); +}, "Missing or invalid entity"); + +let entity = world.spawn_.call(); +assert_throws(||{ + world.push_children.call(entity, [fake_entity]); +}, "Missing or invalid entity"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/query/empty_query_returns_nothing.lua b/assets/tests/query/empty_query_returns_nothing.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/query/empty_query_returns_nothing.lua rename to assets/tests/query/empty_query_returns_nothing.lua diff --git a/assets/tests/query/empty_query_returns_nothing.rhai b/assets/tests/query/empty_query_returns_nothing.rhai new file mode 100644 index 0000000000..ba9293d2d0 --- /dev/null +++ b/assets/tests/query/empty_query_returns_nothing.rhai @@ -0,0 +1,5 @@ +let component_a = world.get_type_by_name.call("TestComponent"); + +for (result, i) in world.query.call().component.call(component_a).without.call(component_a).build.call() { + assert(false, "This should not be reached"); +} \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/query/query_returns_all_entities_matching.lua b/assets/tests/query/query_returns_all_entities_matching.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/query/query_returns_all_entities_matching.lua rename to assets/tests/query/query_returns_all_entities_matching.lua diff --git a/assets/tests/query/query_returns_all_entities_matching.rhai b/assets/tests/query/query_returns_all_entities_matching.rhai new file mode 100644 index 0000000000..232f5e71d2 --- /dev/null +++ b/assets/tests/query/query_returns_all_entities_matching.rhai @@ -0,0 +1,31 @@ +let entity_a = world.spawn_.call(); +let entity_b = world.spawn_.call(); +let entity_c = world.spawn_.call(); +let entity_d = world._get_entity_with_test_component.call("CompWithFromWorldAndComponentData"); + +let component_with = world.get_type_by_name.call("CompWithFromWorldAndComponentData"); +let component_without = world.get_type_by_name.call("CompWithDefaultAndComponentData"); + +world.add_default_component.call(entity_a, component_with); +world.add_default_component.call(entity_b, component_with); +world.add_default_component.call(entity_c, component_with); + +world.add_default_component.call(entity_b, component_without); + +let found_entities = []; +for (result, i) in world.query.call().component.call(component_with).without.call(component_without).build.call() { + found_entities.push(result.entity.call()); +} + +assert(found_entities.len == 3, "Expected 3 entities, got " + found_entities.len); + +let expected_entities = [ + entity_d, + entity_a, + entity_c, +]; + +for (entity, i) in found_entities { + assert(entity.index.call() == expected_entities[i].index.call(), "Expected entity " + expected_entities[i].index.call() + " but got " + entity.index.call()); +} + diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/remove/vec.lua b/assets/tests/remove/vec.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/remove/vec.lua rename to assets/tests/remove/vec.lua diff --git a/assets/tests/remove/vec.rhai b/assets/tests/remove/vec.rhai new file mode 100644 index 0000000000..617cc459a3 --- /dev/null +++ b/assets/tests/remove/vec.rhai @@ -0,0 +1,6 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +let removed = res.vec_usize.remove.call(4); + +assert(removed == 5, "Remove did not work"); diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/remove_component/empty_entity_does_nothing.lua b/assets/tests/remove_component/empty_entity_does_nothing.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/remove_component/empty_entity_does_nothing.lua rename to assets/tests/remove_component/empty_entity_does_nothing.lua diff --git a/assets/tests/remove_component/empty_entity_does_nothing.rhai b/assets/tests/remove_component/empty_entity_does_nothing.rhai new file mode 100644 index 0000000000..c93a8bde4f --- /dev/null +++ b/assets/tests/remove_component/empty_entity_does_nothing.rhai @@ -0,0 +1,5 @@ +let entity = world.spawn_.call(); +let type = world.get_type_by_name.call("TestComponent"); + +world.remove_component.call(entity, type); +world.remove_component.call(entity, type); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/remove_component/no_component_data_errors.lua b/assets/tests/remove_component/no_component_data_errors.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/remove_component/no_component_data_errors.lua rename to assets/tests/remove_component/no_component_data_errors.lua diff --git a/assets/tests/remove_component/no_component_data_errors.rhai b/assets/tests/remove_component/no_component_data_errors.rhai new file mode 100644 index 0000000000..07f8f714a8 --- /dev/null +++ b/assets/tests/remove_component/no_component_data_errors.rhai @@ -0,0 +1,7 @@ + +let entity = world._get_entity_with_test_component.call("CompWithDefault"); +let component = world.get_type_by_name.call("CompWithDefault"); + +assert_throws(||{ + world.remove_component.call(entity, component); +}, "Missing type data ReflectComponent for type: .*CompWithDefault.*") diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/remove_component/with_component_data_removes_component.lua b/assets/tests/remove_component/with_component_data_removes_component.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/remove_component/with_component_data_removes_component.lua rename to assets/tests/remove_component/with_component_data_removes_component.lua diff --git a/assets/tests/remove_component/with_component_data_removes_component.rhai b/assets/tests/remove_component/with_component_data_removes_component.rhai new file mode 100644 index 0000000000..ba8e3967fa --- /dev/null +++ b/assets/tests/remove_component/with_component_data_removes_component.rhai @@ -0,0 +1,5 @@ + +let entity = world._get_entity_with_test_component.call("TestComponent"); +let component = world.get_type_by_name.call("TestComponent"); +world.remove_component.call(entity, component); +assert(world.has_component.call(entity, component) == false, "Component was not removed"); diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/remove_resource/missing_resource_with_resource_data_does_nothing.lua b/assets/tests/remove_resource/missing_resource_with_resource_data_does_nothing.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/remove_resource/missing_resource_with_resource_data_does_nothing.lua rename to assets/tests/remove_resource/missing_resource_with_resource_data_does_nothing.lua diff --git a/assets/tests/remove_resource/missing_resource_with_resource_data_does_nothing.rhai b/assets/tests/remove_resource/missing_resource_with_resource_data_does_nothing.rhai new file mode 100644 index 0000000000..1ddf844425 --- /dev/null +++ b/assets/tests/remove_resource/missing_resource_with_resource_data_does_nothing.rhai @@ -0,0 +1,4 @@ +let type = world.get_type_by_name.call("TestResource"); + +world.remove_resource.call(type); +world.remove_resource.call(type); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/remove_resource/no_resource_data_errors.lua b/assets/tests/remove_resource/no_resource_data_errors.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/remove_resource/no_resource_data_errors.lua rename to assets/tests/remove_resource/no_resource_data_errors.lua diff --git a/assets/tests/remove_resource/no_resource_data_errors.rhai b/assets/tests/remove_resource/no_resource_data_errors.rhai new file mode 100644 index 0000000000..c69d9dc6d6 --- /dev/null +++ b/assets/tests/remove_resource/no_resource_data_errors.rhai @@ -0,0 +1,6 @@ + +let type = world._get_mock_resource_type.call(); + +assert_throws(||{ + world.remove_resource.call(type) +}, "Missing type data ReflectResource for type: Unregistered.TypeId.*"); diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/remove_resource/with_resource_data_removes_resource.lua b/assets/tests/remove_resource/with_resource_data_removes_resource.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/remove_resource/with_resource_data_removes_resource.lua rename to assets/tests/remove_resource/with_resource_data_removes_resource.lua diff --git a/assets/tests/remove_resource/with_resource_data_removes_resource.rhai b/assets/tests/remove_resource/with_resource_data_removes_resource.rhai new file mode 100644 index 0000000000..b8bb4b17a4 --- /dev/null +++ b/assets/tests/remove_resource/with_resource_data_removes_resource.rhai @@ -0,0 +1,4 @@ + +let type = world.get_type_by_name.call("TestResource"); +world.remove_resource.call(type); +assert(world.has_resource.call(type) == false, "Resource was not removed"); diff --git a/assets/tests/rhai/access/multiple_read_refs.rhai b/assets/tests/rhai/access/multiple_read_refs.rhai new file mode 100644 index 0000000000..875e8eff06 --- /dev/null +++ b/assets/tests/rhai/access/multiple_read_refs.rhai @@ -0,0 +1,3 @@ +let entity = Entity.from_raw.call(9999); +// does not throw +let out = entity.eq.call(entity); \ No newline at end of file diff --git a/assets/tests/rhai/add/vec3.rhai b/assets/tests/rhai/add/vec3.rhai new file mode 100644 index 0000000000..c538e36e29 --- /dev/null +++ b/assets/tests/rhai/add/vec3.rhai @@ -0,0 +1,11 @@ +let a = Vec3.new_.call(1.0, 2.0, 3.0); +let b = Vec3.new_.call(4.0, 5.0, 6.0); + +assert((a + 1).x == 2.0, "Addition did not work"); +assert((a + 1).y == 3.0, "Addition did not work"); +assert((a + 1).z == 4.0, "Addition did not work"); + +assert((a + b).x == 5.0, "Addition did not work"); +assert((a + b).y == 7.0, "Addition did not work"); +assert((a + b).z == 9.0, "Addition did not work"); + diff --git a/assets/tests/rhai/add_default_component/component_no_default_or_from_world_data_errors.rhai b/assets/tests/rhai/add_default_component/component_no_default_or_from_world_data_errors.rhai new file mode 100644 index 0000000000..463d3100c0 --- /dev/null +++ b/assets/tests/rhai/add_default_component/component_no_default_or_from_world_data_errors.rhai @@ -0,0 +1,6 @@ +let entity = world.spawn_.call(); +let type = world.get_type_by_name.call("TestComponent"); + +assert_throws(||{ + world.add_default_component.call(entity, type); +},"Missing type data ReflectDefault or ReflectFromWorld for type: .*TestComponent.*"); \ No newline at end of file diff --git a/assets/tests/rhai/add_default_component/component_with_default_and_component_data_adds_default.rhai b/assets/tests/rhai/add_default_component/component_with_default_and_component_data_adds_default.rhai new file mode 100644 index 0000000000..e2ce895868 --- /dev/null +++ b/assets/tests/rhai/add_default_component/component_with_default_and_component_data_adds_default.rhai @@ -0,0 +1,9 @@ +let entity = world.spawn_.call(); +let _type = world.get_type_by_name.call("CompWithDefaultAndComponentData"); +world.add_default_component.call(entity, _type); + +let added = world.has_component.call(entity, _type); +assert(type_of(added) != "()", "Component not added"); + +let component = world.get_component.call(entity, _type); +assert(component["_0"] == "Default", "Component did not have default value, got: " + component["_0"]); \ No newline at end of file diff --git a/assets/tests/rhai/add_default_component/component_with_default_no_component_data_errors.rhai b/assets/tests/rhai/add_default_component/component_with_default_no_component_data_errors.rhai new file mode 100644 index 0000000000..988642e16c --- /dev/null +++ b/assets/tests/rhai/add_default_component/component_with_default_no_component_data_errors.rhai @@ -0,0 +1,6 @@ +let entity = world.spawn_.call(); +let _type = world.get_type_by_name.call("CompWithDefault"); + +assert_throws(||{ + world.add_default_component.call(entity, _type); +}, "Missing type data ReflectComponent for type: .*CompWithDefault.*") diff --git a/assets/tests/rhai/add_default_component/component_with_from_world_and_component_data_adds_default.rhai b/assets/tests/rhai/add_default_component/component_with_from_world_and_component_data_adds_default.rhai new file mode 100644 index 0000000000..63c81f3dec --- /dev/null +++ b/assets/tests/rhai/add_default_component/component_with_from_world_and_component_data_adds_default.rhai @@ -0,0 +1,9 @@ +let entity = world.spawn_.call(); +let _type = world.get_type_by_name.call("CompWithFromWorldAndComponentData"); +world.add_default_component.call(entity, _type); + +let added = world.has_component.call(entity, _type); +assert(type_of(added) != "()", "Component not added"); + +let component = world.get_component.call(entity, _type); +assert(component["_0"] == "Default", "Component did not have default value, got: " + component["_0"]) \ No newline at end of file diff --git a/assets/tests/rhai/add_default_component/component_with_from_world_no_component_data_errors.rhai b/assets/tests/rhai/add_default_component/component_with_from_world_no_component_data_errors.rhai new file mode 100644 index 0000000000..323dfb84d1 --- /dev/null +++ b/assets/tests/rhai/add_default_component/component_with_from_world_no_component_data_errors.rhai @@ -0,0 +1,6 @@ +let entity = world.spawn_.call(); +let _type = world.get_type_by_name.call("CompWithFromWorld"); + +assert_throws(||{ + world.add_default_component.call(entity, _type); +}, "Missing type data ReflectComponent for type: .*CompWithFromWorld.*") \ No newline at end of file diff --git a/assets/tests/rhai/api_availability/api_available_on_callback.rhai b/assets/tests/rhai/api_availability/api_available_on_callback.rhai new file mode 100644 index 0000000000..d37af11858 --- /dev/null +++ b/assets/tests/rhai/api_availability/api_available_on_callback.rhai @@ -0,0 +1,5 @@ +fn on_test() { + assert!(type_of(world) != "()", "World was not found"); + assert!(type_of(world.get_type_by_name.call("TestComponent")) != "()", "Could not find TestComponent type"); + Entity.from_raw.call(1); +} \ No newline at end of file diff --git a/assets/tests/rhai/api_availability/api_available_on_script_load.rhai b/assets/tests/rhai/api_availability/api_available_on_script_load.rhai new file mode 100644 index 0000000000..5661f55644 --- /dev/null +++ b/assets/tests/rhai/api_availability/api_available_on_script_load.rhai @@ -0,0 +1,3 @@ +assert!(type_of(world) != "()", "World was not found"); +assert!(type_of(world.get_type_by_name.call("TestComponent")) != "()", "Could not find TestComponent type"); +let out = Entity.from_raw.call(1); \ No newline at end of file diff --git a/assets/tests/rhai/clear/vec.rhai b/assets/tests/rhai/clear/vec.rhai new file mode 100644 index 0000000000..4f1d5e7d10 --- /dev/null +++ b/assets/tests/rhai/clear/vec.rhai @@ -0,0 +1,6 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +res.vec_usize.clear.call(); + +assert(res.vec_usize.len.call() == 0, "Clear did not work"); \ No newline at end of file diff --git a/assets/tests/rhai/construct/simple_enum.rhai b/assets/tests/rhai/construct/simple_enum.rhai new file mode 100644 index 0000000000..463de1f9a7 --- /dev/null +++ b/assets/tests/rhai/construct/simple_enum.rhai @@ -0,0 +1,18 @@ +let type = world.get_type_by_name.call("SimpleEnum"); + +// Struct Variant +let constructed = construct.call(type, #{ variant: "Struct", foo: 123 }); + +assert(constructed.variant_name.call() == "Struct", "Value was constructed incorrectly, expected constructed.variant to be Struct but got " + constructed.variant_name.call()); +assert(constructed.foo == 123, "Value was constructed incorrectly, expected constructed.foo to be 123 but got " + constructed.foo); + +// TupleStruct Variant +constructed = construct.call(type, #{ variant: "TupleStruct", "_0": 123 }); + +assert(constructed.variant_name.call() == "TupleStruct", "Value was constructed incorrectly, expected constructed.variant to be TupleStruct but got " + constructed.variant_name.call()); +assert(constructed["_0"] == 123, "Value was constructed incorrectly, expected constructed._0 to be 123 but got " + constructed["_0"]); + +// Unit Variant +constructed = construct.call(type, #{ variant: "Unit" }); + +assert(constructed.variant_name.call() == "Unit", "Value was constructed incorrectly, expected constructed.variant to be Unit but got " + constructed.variant_name.call()); \ No newline at end of file diff --git a/assets/tests/rhai/construct/simple_struct.rhai b/assets/tests/rhai/construct/simple_struct.rhai new file mode 100644 index 0000000000..e35edbddd2 --- /dev/null +++ b/assets/tests/rhai/construct/simple_struct.rhai @@ -0,0 +1,4 @@ +let type = world.get_type_by_name.call("SimpleStruct"); +let constructed = construct.call(type, #{ foo: 123 }); + +assert(constructed.foo == 123, "Value was constructed incorrectly, expected constructed.foo to be 123 but got " + constructed.foo); \ No newline at end of file diff --git a/assets/tests/rhai/construct/simple_tuple_struct.rhai b/assets/tests/rhai/construct/simple_tuple_struct.rhai new file mode 100644 index 0000000000..411b3e806f --- /dev/null +++ b/assets/tests/rhai/construct/simple_tuple_struct.rhai @@ -0,0 +1,4 @@ +let type = world.get_type_by_name.call("SimpleTupleStruct"); +let constructed = construct.call(type, #{ "_0": 123 }); + +assert(constructed["_0"] == 123, "Value was constructed incorrectly, expected constructed.foo to be 123 but got " + constructed["_0"]); \ No newline at end of file diff --git a/assets/tests/rhai/despawn/despawns_only_root.rhai b/assets/tests/rhai/despawn/despawns_only_root.rhai new file mode 100644 index 0000000000..5c3a8fc466 --- /dev/null +++ b/assets/tests/rhai/despawn/despawns_only_root.rhai @@ -0,0 +1,7 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); +world.push_children.call(entity, [child]); +world.despawn.call(entity); + +assert(world.has_entity.call(entity) == false, "Parent should be despawned"); +assert(world.has_entity.call(child) == true, "Child should not be despawned"); diff --git a/assets/tests/rhai/despawn/invalid_entity_errors.rhai b/assets/tests/rhai/despawn/invalid_entity_errors.rhai new file mode 100644 index 0000000000..730fca0782 --- /dev/null +++ b/assets/tests/rhai/despawn/invalid_entity_errors.rhai @@ -0,0 +1,3 @@ +assert_throws(||{ + world.despawn_recursive.call(Entity.from_raw.call(9999)) +}, "Missing or invalid entity"); \ No newline at end of file diff --git a/assets/tests/rhai/despawn_descendants/despawns_only_child.rhai b/assets/tests/rhai/despawn_descendants/despawns_only_child.rhai new file mode 100644 index 0000000000..fb6996cee4 --- /dev/null +++ b/assets/tests/rhai/despawn_descendants/despawns_only_child.rhai @@ -0,0 +1,7 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); +world.push_children.call(entity, [child]); +world.despawn_descendants.call(entity); + +assert(world.has_entity.call(entity) == true, "Parent should not be despawned"); +assert(world.has_entity.call(child) == false, "Child should be despawned"); diff --git a/assets/tests/rhai/despawn_descendants/invalid_entity_errors.rhai b/assets/tests/rhai/despawn_descendants/invalid_entity_errors.rhai new file mode 100644 index 0000000000..bdaa98ff33 --- /dev/null +++ b/assets/tests/rhai/despawn_descendants/invalid_entity_errors.rhai @@ -0,0 +1,3 @@ +assert_throws(||{ + world.despawn_recursive.call(Entity.from_raw.call(9999)); +}, "Missing or invalid entity") \ No newline at end of file diff --git a/assets/tests/rhai/despawn_recursive/despawns_recursively.rhai b/assets/tests/rhai/despawn_recursive/despawns_recursively.rhai new file mode 100644 index 0000000000..8d4e2ab757 --- /dev/null +++ b/assets/tests/rhai/despawn_recursive/despawns_recursively.rhai @@ -0,0 +1,7 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); +world.push_children.call(entity, [child]); +world.despawn_recursive.call(entity); + +assert(world.has_entity.call(entity) == false, "Parent should be despawned"); +assert(world.has_entity.call(child) == false, "Child should be despawned"); diff --git a/assets/tests/rhai/despawn_recursive/invalid_entity_errors.rhai b/assets/tests/rhai/despawn_recursive/invalid_entity_errors.rhai new file mode 100644 index 0000000000..bdaa98ff33 --- /dev/null +++ b/assets/tests/rhai/despawn_recursive/invalid_entity_errors.rhai @@ -0,0 +1,3 @@ +assert_throws(||{ + world.despawn_recursive.call(Entity.from_raw.call(9999)); +}, "Missing or invalid entity") \ No newline at end of file diff --git a/assets/tests/rhai/div/vec3.rhai b/assets/tests/rhai/div/vec3.rhai new file mode 100644 index 0000000000..af3a96d5f0 --- /dev/null +++ b/assets/tests/rhai/div/vec3.rhai @@ -0,0 +1,10 @@ +let a = Vec3.new_.call(2.0, 4.0, 6.0); +let b = Vec3.new_.call(1.0, 2.0, 3.0); + +assert((a / 2).x == 1.0, "Division did not work"); +assert((a / 2).y == 2.0, "Division did not work"); +assert((a / 2).z == 3.0, "Division did not work"); + +assert((a / b).x == 2.0, "Division did not work"); +assert((a / b).y == 2.0, "Division did not work"); +assert((a / b).z == 2.0, "Division did not work"); \ No newline at end of file diff --git a/assets/tests/rhai/eq/vec3.rhai b/assets/tests/rhai/eq/vec3.rhai new file mode 100644 index 0000000000..f4652fed8c --- /dev/null +++ b/assets/tests/rhai/eq/vec3.rhai @@ -0,0 +1,7 @@ +let a = Vec3.new_.call(2.0, -4.0, 6.0); +let b = Vec3.new_.call(4.0, 5.0, 6.0); + + +assert((a == b) == false, "Equality did not work"); +assert((a != b) == true, "Inequality did not work"); +assert((a == a) == true, "Equality did not work"); \ No newline at end of file diff --git a/assets/tests/rhai/functions/contains_reflect_reference_functions.rhai b/assets/tests/rhai/functions/contains_reflect_reference_functions.rhai new file mode 100644 index 0000000000..f83b980c99 --- /dev/null +++ b/assets/tests/rhai/functions/contains_reflect_reference_functions.rhai @@ -0,0 +1,14 @@ + +let Resource = world.get_type_by_name.call("TestResource"); +let resource = world.get_resource.call(Resource); + +let functions = resource.functions.call(); +assert(functions.len() > 0, "functions should not be empty"); + +let available_names = []; + +for function_ref in functions { + available_names.push(function_ref.name); +} + +assert("display_ref" in available_names, "functions should contain display_ref, but got: " + available_names); \ No newline at end of file diff --git a/assets/tests/rhai/get_children/has_children_returns_them.rhai b/assets/tests/rhai/get_children/has_children_returns_them.rhai new file mode 100644 index 0000000000..1f8bece3a4 --- /dev/null +++ b/assets/tests/rhai/get_children/has_children_returns_them.rhai @@ -0,0 +1,9 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); + +world.push_children.call(entity, [child]); + +let children = world.get_children.call(entity); + +assert(children.len == 1, "Expected 1 child"); +assert(children[0].index.call() == child.index.call(), "Child is the wrong entity"); \ No newline at end of file diff --git a/assets/tests/rhai/get_children/invalid_entity_errors.rhai b/assets/tests/rhai/get_children/invalid_entity_errors.rhai new file mode 100644 index 0000000000..90bf506ca9 --- /dev/null +++ b/assets/tests/rhai/get_children/invalid_entity_errors.rhai @@ -0,0 +1,3 @@ +assert_throws(||{ + world.get_children.call(Entity.from_raw.call(9999)); +}, "Missing or invalid entity"); diff --git a/assets/tests/rhai/get_children/no_children_returns_empty_table.rhai b/assets/tests/rhai/get_children/no_children_returns_empty_table.rhai new file mode 100644 index 0000000000..1ecc14c5a0 --- /dev/null +++ b/assets/tests/rhai/get_children/no_children_returns_empty_table.rhai @@ -0,0 +1,4 @@ +let entity = world.spawn_.call(); +let children = world.get_children.call(entity); + +assert(children.len == 0); \ No newline at end of file diff --git a/assets/tests/rhai/get_component/component_no_component_data.rhai b/assets/tests/rhai/get_component/component_no_component_data.rhai new file mode 100644 index 0000000000..1000055921 --- /dev/null +++ b/assets/tests/rhai/get_component/component_no_component_data.rhai @@ -0,0 +1,6 @@ +let component = world.get_type_by_name.call("CompWithDefault"); +let entity = world._get_entity_with_test_component.call("CompWithDefault"); +let retrieved = world.get_component.call(entity, component); + +assert(type_of(retrieved) != "()", "Component was not found"); +assert(retrieved["_0"] == "Initial Value", "Component data was not retrieved correctly, retrieved._0 was: " + retrieved["_0"]); \ No newline at end of file diff --git a/assets/tests/rhai/get_component/component_with_component_data.rhai b/assets/tests/rhai/get_component/component_with_component_data.rhai new file mode 100644 index 0000000000..7a638c20ea --- /dev/null +++ b/assets/tests/rhai/get_component/component_with_component_data.rhai @@ -0,0 +1,6 @@ +let component = world.get_type_by_name.call("TestComponent"); +let entity = world._get_entity_with_test_component.call("TestComponent"); +let retrieved = world.get_component.call(entity, component); + +assert(type_of(retrieved) != "()", "Component was not found"); +assert(retrieved.strings[0] == "Initial", "Component data was not retrieved correctly, retrieved.strings[0] was: " + retrieved.strings[0]); \ No newline at end of file diff --git a/assets/tests/rhai/get_component/empty_entity_component_with_component_data.rhai b/assets/tests/rhai/get_component/empty_entity_component_with_component_data.rhai new file mode 100644 index 0000000000..42c4abc0f1 --- /dev/null +++ b/assets/tests/rhai/get_component/empty_entity_component_with_component_data.rhai @@ -0,0 +1,5 @@ +let component = world.get_type_by_name.call("TestComponent"); +let entity = world.spawn_.call(); +let retrieved = world.get_component.call(entity, component); + +assert(type_of(retrieved) == "()", "Component found"); \ No newline at end of file diff --git a/assets/tests/rhai/get_parent/has_parent_returns_it.rhai b/assets/tests/rhai/get_parent/has_parent_returns_it.rhai new file mode 100644 index 0000000000..a992e6068c --- /dev/null +++ b/assets/tests/rhai/get_parent/has_parent_returns_it.rhai @@ -0,0 +1,9 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); + +world.push_children.call(entity, [child]); + +let parent = world.get_parent.call(child); + +assert(type_of(parent) != "()", "Expected a parent"); +assert(parent.index.call() == entity.index.call(), "Parent is the wrong entity"); \ No newline at end of file diff --git a/assets/tests/rhai/get_parent/invalid_entity_errors.rhai b/assets/tests/rhai/get_parent/invalid_entity_errors.rhai new file mode 100644 index 0000000000..b25d0981df --- /dev/null +++ b/assets/tests/rhai/get_parent/invalid_entity_errors.rhai @@ -0,0 +1,4 @@ + +assert_throws(||{ + world.get_parent.call(Entity.from_raw.call(9999)); +}, "Missing or invalid entity"); diff --git a/assets/tests/rhai/get_parent/no_parent_returns_nil.rhai b/assets/tests/rhai/get_parent/no_parent_returns_nil.rhai new file mode 100644 index 0000000000..d4abc7d2d5 --- /dev/null +++ b/assets/tests/rhai/get_parent/no_parent_returns_nil.rhai @@ -0,0 +1,4 @@ +let entity = world.spawn_.call(); +let parent = world.get_parent.call(entity); + +assert(type_of(parent) == "()", "Expected no parents"); diff --git a/assets/tests/rhai/get_resource/missing_resource_returns_nil.rhai b/assets/tests/rhai/get_resource/missing_resource_returns_nil.rhai new file mode 100644 index 0000000000..e127d1a958 --- /dev/null +++ b/assets/tests/rhai/get_resource/missing_resource_returns_nil.rhai @@ -0,0 +1,2 @@ +let type = world._get_mock_resource_type.call(); +assert(type_of(world.get_resource.call(type)) == "()", "Resource should not exist"); \ No newline at end of file diff --git a/assets/tests/rhai/get_resource/no_resource_data_returns_resource.rhai b/assets/tests/rhai/get_resource/no_resource_data_returns_resource.rhai new file mode 100644 index 0000000000..3c172e5ac3 --- /dev/null +++ b/assets/tests/rhai/get_resource/no_resource_data_returns_resource.rhai @@ -0,0 +1,5 @@ +let resource = world.get_type_by_name.call("ResourceWithDefault"); + +let retrieved = world.get_resource.call(resource); +assert(type_of(retrieved) != "()", "Resource should exist"); +assert(retrieved["_0"] == "Initial Value", "Resource should have default value but got: " + retrieved["_0"]); diff --git a/assets/tests/rhai/get_resource/with_resource_data_returns_resource.rhai b/assets/tests/rhai/get_resource/with_resource_data_returns_resource.rhai new file mode 100644 index 0000000000..c0f657d6b8 --- /dev/null +++ b/assets/tests/rhai/get_resource/with_resource_data_returns_resource.rhai @@ -0,0 +1,5 @@ +let resource = world.get_type_by_name.call("TestResource"); + +let retrieved = world.get_resource.call(resource); +assert(type_of(retrieved) != "()", "Resource should exist"); +assert(retrieved.bytes[1] == 1, "Resource should have default value but got resource with #retrieved.bytes[1]: " + retrieved.bytes[1]); diff --git a/assets/tests/rhai/get_type_by_name/missing_type_returns_nothing.rhai b/assets/tests/rhai/get_type_by_name/missing_type_returns_nothing.rhai new file mode 100644 index 0000000000..2ac4944019 --- /dev/null +++ b/assets/tests/rhai/get_type_by_name/missing_type_returns_nothing.rhai @@ -0,0 +1,3 @@ +let type = world.get_type_by_name.call("MissingType"); + +assert(type == (), "Unregistered type was found"); diff --git a/assets/tests/rhai/get_type_by_name/registered_type_returns_correct_type.rhai b/assets/tests/rhai/get_type_by_name/registered_type_returns_correct_type.rhai new file mode 100644 index 0000000000..00f5379b8a --- /dev/null +++ b/assets/tests/rhai/get_type_by_name/registered_type_returns_correct_type.rhai @@ -0,0 +1,17 @@ +let type = world.get_type_by_name.call("TestComponent"); + +assert(type_of(type) != "()", "Registered type was not found"); + +let expected_type_name = "test_utils::test_data::TestComponent"; +let expected_short_name = "TestComponent"; + +let received_type_name = type.type_name.call(type); +let received_short_name = type.short_name.call(type); + + +// assert(type != (), 'Type not found') +// assert(received.type_name == expected.type_name, 'type_name mismatch, expected: ' .. expected.type_name .. ', got: ' .. received.type_name) +// assert(received.short_name == expected.short_name, 'short_name mismatch, expected: ' .. expected.short_name .. ', got: ' .. received.short_name) + +assert(received_type_name == expected_type_name, "type_name mismatch, expected: " + expected_type_name + ", got: " + received_type_name); +assert(received_short_name == expected_short_name, "short_name mismatch, expected: " + expected_short_name + ", got: " + received_short_name); \ No newline at end of file diff --git a/assets/tests/rhai/globals/dynamic_globals_are_in_scope.rhai b/assets/tests/rhai/globals/dynamic_globals_are_in_scope.rhai new file mode 100644 index 0000000000..32455f2363 --- /dev/null +++ b/assets/tests/rhai/globals/dynamic_globals_are_in_scope.rhai @@ -0,0 +1 @@ +assert(global_hello_world.call() == "hi!", "global_hello_world() == 'hi!'") \ No newline at end of file diff --git a/assets/tests/rhai/has_component/empty_entity_mock_component_is_false.rhai b/assets/tests/rhai/has_component/empty_entity_mock_component_is_false.rhai new file mode 100644 index 0000000000..65aaec5348 --- /dev/null +++ b/assets/tests/rhai/has_component/empty_entity_mock_component_is_false.rhai @@ -0,0 +1,4 @@ +let entity = world.spawn_.call(); +let type = world._get_mock_component_type.call(); + +assert(world.has_component.call(entity, type) == false, "Entity should not have component"); \ No newline at end of file diff --git a/assets/tests/rhai/has_component/no_component_data.rhai b/assets/tests/rhai/has_component/no_component_data.rhai new file mode 100644 index 0000000000..e5b998e6b6 --- /dev/null +++ b/assets/tests/rhai/has_component/no_component_data.rhai @@ -0,0 +1,3 @@ +let entity = world._get_entity_with_test_component.call("CompWithDefault"); +let component = world.get_type_by_name.call("CompWithDefault"); +assert(world.has_component.call(entity, component) == true, "Component was not found"); \ No newline at end of file diff --git a/assets/tests/rhai/has_component/with_component_data.rhai b/assets/tests/rhai/has_component/with_component_data.rhai new file mode 100644 index 0000000000..78240ee203 --- /dev/null +++ b/assets/tests/rhai/has_component/with_component_data.rhai @@ -0,0 +1,3 @@ +let entity = world._get_entity_with_test_component.call("TestComponent"); +let component = world.get_type_by_name.call("TestComponent"); +assert(world.has_component.call(entity, component) == true, "Component was not found"); \ No newline at end of file diff --git a/assets/tests/rhai/has_resource/existing_no_resource_data.rhai b/assets/tests/rhai/has_resource/existing_no_resource_data.rhai new file mode 100644 index 0000000000..20c34cb167 --- /dev/null +++ b/assets/tests/rhai/has_resource/existing_no_resource_data.rhai @@ -0,0 +1,2 @@ +let component = world.get_type_by_name.call("ResourceWithDefault"); +assert(world.has_resource.call(component) == true, "Resource was not found"); \ No newline at end of file diff --git a/assets/tests/rhai/has_resource/existing_with_resource_data.rhai b/assets/tests/rhai/has_resource/existing_with_resource_data.rhai new file mode 100644 index 0000000000..28a9bf59b1 --- /dev/null +++ b/assets/tests/rhai/has_resource/existing_with_resource_data.rhai @@ -0,0 +1,2 @@ +let component = world.get_type_by_name.call("TestResource"); +assert(world.has_resource.call(component) == true, "Resource was not found"); \ No newline at end of file diff --git a/assets/tests/rhai/has_resource/missing_resource_mock_resource_is_false.rhai b/assets/tests/rhai/has_resource/missing_resource_mock_resource_is_false.rhai new file mode 100644 index 0000000000..191aca8f15 --- /dev/null +++ b/assets/tests/rhai/has_resource/missing_resource_mock_resource_is_false.rhai @@ -0,0 +1,2 @@ +let type = world._get_mock_resource_type.call(); +assert(world.has_resource.call(type) == false, "Resource should not exist"); \ No newline at end of file diff --git a/assets/tests/rhai/hashmap/can_pass_and_return_hashmap.rhai b/assets/tests/rhai/hashmap/can_pass_and_return_hashmap.rhai new file mode 100644 index 0000000000..60b45a3da4 --- /dev/null +++ b/assets/tests/rhai/hashmap/can_pass_and_return_hashmap.rhai @@ -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"); \ No newline at end of file diff --git a/assets/tests/rhai/insert/vec.rhai b/assets/tests/rhai/insert/vec.rhai new file mode 100644 index 0000000000..d5f250418a --- /dev/null +++ b/assets/tests/rhai/insert/vec.rhai @@ -0,0 +1,6 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +res.vec_usize.insert.call(2, 42); + +assert(res.vec_usize[2] == 42, "insert did not work"); \ No newline at end of file diff --git a/assets/tests/rhai/insert_children/adding_empty_list_does_nothing.rhai b/assets/tests/rhai/insert_children/adding_empty_list_does_nothing.rhai new file mode 100644 index 0000000000..85825b47ac --- /dev/null +++ b/assets/tests/rhai/insert_children/adding_empty_list_does_nothing.rhai @@ -0,0 +1,5 @@ +let entity = world.spawn_.call(); + +world.insert_children.call(entity,0 ,[]); + +assert(world.get_children.call(entity).len == 0); \ No newline at end of file diff --git a/assets/tests/rhai/insert_children/adds_children_at_correct_index.rhai b/assets/tests/rhai/insert_children/adds_children_at_correct_index.rhai new file mode 100644 index 0000000000..6c922beb71 --- /dev/null +++ b/assets/tests/rhai/insert_children/adds_children_at_correct_index.rhai @@ -0,0 +1,8 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); +let child2 = world.spawn_.call(); + +world.insert_children.call(entity, 0, [child]); +world.insert_children.call(entity, 0, [child2]); + +assert(world.get_children.call(entity)[0].index.call() == child2.index.call()); \ No newline at end of file diff --git a/assets/tests/rhai/insert_children/adds_children_to_existing_enttity.rhai b/assets/tests/rhai/insert_children/adds_children_to_existing_enttity.rhai new file mode 100644 index 0000000000..5c5a542ad9 --- /dev/null +++ b/assets/tests/rhai/insert_children/adds_children_to_existing_enttity.rhai @@ -0,0 +1,6 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); +let child2 = world.spawn_.call(); +world.insert_children.call(entity, 0, [child, child2]); + +assert(world.get_children.call(entity).len == 2); \ No newline at end of file diff --git a/assets/tests/rhai/insert_children/invalid_entity_errors.rhai b/assets/tests/rhai/insert_children/invalid_entity_errors.rhai new file mode 100644 index 0000000000..2b960661ec --- /dev/null +++ b/assets/tests/rhai/insert_children/invalid_entity_errors.rhai @@ -0,0 +1,10 @@ +let fake_entity = Entity.from_raw.call(9999); + +assert_throws(||{ + world.insert_children.call(fake_entity, 0, [fake_entity]); +}, "Missing or invalid entity"); + +let entity = world.spawn_.call(); +assert_throws(||{ + world.insert_children.call(entity, 0, [fake_entity]); +}, "Missing or invalid entity"); \ No newline at end of file diff --git a/assets/tests/rhai/insert_component/component_no_default_or_from_world_data_inserts.rhai b/assets/tests/rhai/insert_component/component_no_default_or_from_world_data_inserts.rhai new file mode 100644 index 0000000000..fcf3aa2747 --- /dev/null +++ b/assets/tests/rhai/insert_component/component_no_default_or_from_world_data_inserts.rhai @@ -0,0 +1,8 @@ +let entity = world.spawn_.call(); +let type = world.get_type_by_name.call("TestComponent"); +let entity_with_component = world._get_entity_with_test_component.call("TestComponent"); +let existing_component = world.get_component.call(entity_with_component, type); + +assert(world.has_component.call(entity, type) == false, "Expected entity to not have component before adding, test invalid"); +world.insert_component.call(entity, type, existing_component); +assert(world.has_component.call(entity, type) == true, "Expected entity to have component after adding"); diff --git a/assets/tests/rhai/insert_component/component_with_default_no_component_data_errors.rhai b/assets/tests/rhai/insert_component/component_with_default_no_component_data_errors.rhai new file mode 100644 index 0000000000..bf2997a115 --- /dev/null +++ b/assets/tests/rhai/insert_component/component_with_default_no_component_data_errors.rhai @@ -0,0 +1,8 @@ +let entity = world.spawn_.call(); +let _type = world.get_type_by_name.call("CompWithDefault"); +let entity_with_component = world._get_entity_with_test_component.call("CompWithDefault"); +let existing_component = world.get_component.call(entity_with_component, _type); + +assert_throws(||{ + world.insert_component.call(entity, _type, existing_component); +}, "Missing type data ReflectComponent for type: .*CompWithDefault.*"); diff --git a/assets/tests/rhai/iter/vec.rhai b/assets/tests/rhai/iter/vec.rhai new file mode 100644 index 0000000000..accbcd587a --- /dev/null +++ b/assets/tests/rhai/iter/vec.rhai @@ -0,0 +1,15 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +let iterated_vals = []; + +for v in res.vec_usize { + iterated_vals.push(v); +} + +assert(iterated_vals.len == 5, "Length is not 5"); +assert(iterated_vals[0] == 1, "First value is not 1"); +assert(iterated_vals[1] == 2, "Second value is not 2"); +assert(iterated_vals[2] == 3, "Third value is not 3"); +assert(iterated_vals[3] == 4, "Fourth value is not 4"); +assert(iterated_vals[4] == 5, "Fifth value is not 5"); diff --git a/assets/tests/rhai/len/vec.rhai b/assets/tests/rhai/len/vec.rhai new file mode 100644 index 0000000000..b77fecc68c --- /dev/null +++ b/assets/tests/rhai/len/vec.rhai @@ -0,0 +1,4 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +assert(res.vec_usize.len.call() == 5, "Length is not 5"); \ No newline at end of file diff --git a/assets/tests/rhai/mod/vec3.rhai b/assets/tests/rhai/mod/vec3.rhai new file mode 100644 index 0000000000..098425dcfe --- /dev/null +++ b/assets/tests/rhai/mod/vec3.rhai @@ -0,0 +1,10 @@ +let a = Vec3.new_.call(2.0, 5.0, 6.0); +let b = Vec3.new_.call(1.0, 2.0, 3.0); + +assert((a % 2).x == 0.0, "Modulus did not work"); +assert((a % 2).y == 1.0, "Modulus did not work"); +assert((a % 2).z == 0.0, "Modulus did not work"); + +assert((a % b).x == 0.0, "Modulus did not work"); +assert((a % b).y == 1.0, "Modulus did not work"); +assert((a % b).z == 0.0, "Modulus did not work"); \ No newline at end of file diff --git a/assets/tests/rhai/mul/vec3.rhai b/assets/tests/rhai/mul/vec3.rhai new file mode 100644 index 0000000000..5b62477cd7 --- /dev/null +++ b/assets/tests/rhai/mul/vec3.rhai @@ -0,0 +1,10 @@ +let a = Vec3.new_.call(1.0, 2.0, 3.0); +let b = Vec3.new_.call(4.0, 5.0, 6.0); + +assert((a * 2).x == 2.0, "Multiplication did not work"); +assert((a * 2).y == 4.0, "Multiplication did not work"); +assert((a * 2).z == 6.0, "Multiplication did not work"); + +assert((a * b).x == 4.0, "Multiplication did not work"); +assert((a * b).y == 10.0, "Multiplication did not work"); +assert((a * b).z == 18.0, "Multiplication did not work"); \ No newline at end of file diff --git a/assets/tests/rhai/pop/vec.rhai b/assets/tests/rhai/pop/vec.rhai new file mode 100644 index 0000000000..bf94edb9d2 --- /dev/null +++ b/assets/tests/rhai/pop/vec.rhai @@ -0,0 +1,6 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +let popped = res.vec_usize.pop.call(); + +assert(popped == 5, "Pop did not work"); \ No newline at end of file diff --git a/assets/tests/rhai/push/vec.rhai b/assets/tests/rhai/push/vec.rhai new file mode 100644 index 0000000000..f6c6c37610 --- /dev/null +++ b/assets/tests/rhai/push/vec.rhai @@ -0,0 +1,6 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +res.vec_usize.push.call(42); + +assert(res.vec_usize[5] == 42, "Push did not work"); \ No newline at end of file diff --git a/assets/tests/rhai/push_children/adding_empty_list_does_nothing.rhai b/assets/tests/rhai/push_children/adding_empty_list_does_nothing.rhai new file mode 100644 index 0000000000..e4d2905284 --- /dev/null +++ b/assets/tests/rhai/push_children/adding_empty_list_does_nothing.rhai @@ -0,0 +1,5 @@ +let entity = world.spawn_.call(); + +world.push_children.call(entity, []); + +assert(world.get_children.call(entity).len == 0); \ No newline at end of file diff --git a/assets/tests/rhai/push_children/adds_children_to_existing_enttity.rhai b/assets/tests/rhai/push_children/adds_children_to_existing_enttity.rhai new file mode 100644 index 0000000000..7c77549edd --- /dev/null +++ b/assets/tests/rhai/push_children/adds_children_to_existing_enttity.rhai @@ -0,0 +1,7 @@ +let entity = world.spawn_.call(); +let child = world.spawn_.call(); +let child2 = world.spawn_.call(); + +world.push_children.call(entity, [child, child2]); + +assert(world.get_children.call(entity).len == 2); \ No newline at end of file diff --git a/assets/tests/rhai/push_children/invalid_entity_errors.rhai b/assets/tests/rhai/push_children/invalid_entity_errors.rhai new file mode 100644 index 0000000000..15d6ca9501 --- /dev/null +++ b/assets/tests/rhai/push_children/invalid_entity_errors.rhai @@ -0,0 +1,10 @@ +let fake_entity = Entity.from_raw.call(9999); + +assert_throws(||{ + world.push_children.call(fake_entity, [fake_entity]); +}, "Missing or invalid entity"); + +let entity = world.spawn_.call(); +assert_throws(||{ + world.push_children.call(entity, [fake_entity]); +}, "Missing or invalid entity"); \ No newline at end of file diff --git a/assets/tests/rhai/query/empty_query_returns_nothing.rhai b/assets/tests/rhai/query/empty_query_returns_nothing.rhai new file mode 100644 index 0000000000..ba9293d2d0 --- /dev/null +++ b/assets/tests/rhai/query/empty_query_returns_nothing.rhai @@ -0,0 +1,5 @@ +let component_a = world.get_type_by_name.call("TestComponent"); + +for (result, i) in world.query.call().component.call(component_a).without.call(component_a).build.call() { + assert(false, "This should not be reached"); +} \ No newline at end of file diff --git a/assets/tests/rhai/query/query_returns_all_entities_matching.rhai b/assets/tests/rhai/query/query_returns_all_entities_matching.rhai new file mode 100644 index 0000000000..232f5e71d2 --- /dev/null +++ b/assets/tests/rhai/query/query_returns_all_entities_matching.rhai @@ -0,0 +1,31 @@ +let entity_a = world.spawn_.call(); +let entity_b = world.spawn_.call(); +let entity_c = world.spawn_.call(); +let entity_d = world._get_entity_with_test_component.call("CompWithFromWorldAndComponentData"); + +let component_with = world.get_type_by_name.call("CompWithFromWorldAndComponentData"); +let component_without = world.get_type_by_name.call("CompWithDefaultAndComponentData"); + +world.add_default_component.call(entity_a, component_with); +world.add_default_component.call(entity_b, component_with); +world.add_default_component.call(entity_c, component_with); + +world.add_default_component.call(entity_b, component_without); + +let found_entities = []; +for (result, i) in world.query.call().component.call(component_with).without.call(component_without).build.call() { + found_entities.push(result.entity.call()); +} + +assert(found_entities.len == 3, "Expected 3 entities, got " + found_entities.len); + +let expected_entities = [ + entity_d, + entity_a, + entity_c, +]; + +for (entity, i) in found_entities { + assert(entity.index.call() == expected_entities[i].index.call(), "Expected entity " + expected_entities[i].index.call() + " but got " + entity.index.call()); +} + diff --git a/assets/tests/rhai/remove/vec.rhai b/assets/tests/rhai/remove/vec.rhai new file mode 100644 index 0000000000..617cc459a3 --- /dev/null +++ b/assets/tests/rhai/remove/vec.rhai @@ -0,0 +1,6 @@ +let res_type = world.get_type_by_name.call("TestResourceWithVariousFields"); +let res = world.get_resource.call(res_type); + +let removed = res.vec_usize.remove.call(4); + +assert(removed == 5, "Remove did not work"); diff --git a/assets/tests/rhai/remove_component/empty_entity_does_nothing.rhai b/assets/tests/rhai/remove_component/empty_entity_does_nothing.rhai new file mode 100644 index 0000000000..c93a8bde4f --- /dev/null +++ b/assets/tests/rhai/remove_component/empty_entity_does_nothing.rhai @@ -0,0 +1,5 @@ +let entity = world.spawn_.call(); +let type = world.get_type_by_name.call("TestComponent"); + +world.remove_component.call(entity, type); +world.remove_component.call(entity, type); \ No newline at end of file diff --git a/assets/tests/rhai/remove_component/no_component_data_errors.rhai b/assets/tests/rhai/remove_component/no_component_data_errors.rhai new file mode 100644 index 0000000000..07f8f714a8 --- /dev/null +++ b/assets/tests/rhai/remove_component/no_component_data_errors.rhai @@ -0,0 +1,7 @@ + +let entity = world._get_entity_with_test_component.call("CompWithDefault"); +let component = world.get_type_by_name.call("CompWithDefault"); + +assert_throws(||{ + world.remove_component.call(entity, component); +}, "Missing type data ReflectComponent for type: .*CompWithDefault.*") diff --git a/assets/tests/rhai/remove_component/with_component_data_removes_component.rhai b/assets/tests/rhai/remove_component/with_component_data_removes_component.rhai new file mode 100644 index 0000000000..ba8e3967fa --- /dev/null +++ b/assets/tests/rhai/remove_component/with_component_data_removes_component.rhai @@ -0,0 +1,5 @@ + +let entity = world._get_entity_with_test_component.call("TestComponent"); +let component = world.get_type_by_name.call("TestComponent"); +world.remove_component.call(entity, component); +assert(world.has_component.call(entity, component) == false, "Component was not removed"); diff --git a/assets/tests/rhai/remove_resource/missing_resource_with_resource_data_does_nothing.rhai b/assets/tests/rhai/remove_resource/missing_resource_with_resource_data_does_nothing.rhai new file mode 100644 index 0000000000..1ddf844425 --- /dev/null +++ b/assets/tests/rhai/remove_resource/missing_resource_with_resource_data_does_nothing.rhai @@ -0,0 +1,4 @@ +let type = world.get_type_by_name.call("TestResource"); + +world.remove_resource.call(type); +world.remove_resource.call(type); \ No newline at end of file diff --git a/assets/tests/rhai/remove_resource/no_resource_data_errors.rhai b/assets/tests/rhai/remove_resource/no_resource_data_errors.rhai new file mode 100644 index 0000000000..c69d9dc6d6 --- /dev/null +++ b/assets/tests/rhai/remove_resource/no_resource_data_errors.rhai @@ -0,0 +1,6 @@ + +let type = world._get_mock_resource_type.call(); + +assert_throws(||{ + world.remove_resource.call(type) +}, "Missing type data ReflectResource for type: Unregistered.TypeId.*"); diff --git a/assets/tests/rhai/remove_resource/with_resource_data_removes_resource.rhai b/assets/tests/rhai/remove_resource/with_resource_data_removes_resource.rhai new file mode 100644 index 0000000000..b8bb4b17a4 --- /dev/null +++ b/assets/tests/rhai/remove_resource/with_resource_data_removes_resource.rhai @@ -0,0 +1,4 @@ + +let type = world.get_type_by_name.call("TestResource"); +world.remove_resource.call(type); +assert(world.has_resource.call(type) == false, "Resource was not removed"); diff --git a/assets/tests/rhai/set/set_primitives_works.rhai b/assets/tests/rhai/set/set_primitives_works.rhai new file mode 100644 index 0000000000..8ae0d6d4e8 --- /dev/null +++ b/assets/tests/rhai/set/set_primitives_works.rhai @@ -0,0 +1,28 @@ +let Resource = world.get_type_by_name.call("TestResourceWithVariousFields"); +let resource = world.get_resource.call(Resource); + +resource.string = "Hello, World!"; +resource.bool = true; +resource.int = 42; +resource.float = 3.0; +resource.vec_usize = [ 1, 2 ]; + +assert(resource.string == "Hello, World!", "Expected 'Hello, World!', got " + resource.string); +assert(resource.bool == true, "Expected true, got " + resource.bool); +assert(resource.int == 42, "Expected 42, got " + resource.int); +assert(resource.float == 3.0, "Expected 3.14, got " + resource.float); +assert(resource.vec_usize[0] == 1, "Expected 1, got " + resource.vec_usize[1]); + +resource.string = "Goodbye, World!"; +resource.bool = false; +resource.int = 24; +resource.float = 1.0; +resource.vec_usize = [ 3, 4 ]; + +assert(resource.string == "Goodbye, World!", "Expected 'Goodbye, World!', got " + resource.string); +assert(resource.bool == false, "Expected false, got " + resource.bool); +assert(resource.int == 24, "Expected 24, got " + resource.int); +assert(resource.float == 1.0, "Expected 1.41, got " + resource.float); +assert(resource.vec_usize[0] == 3, "Expected 3, got " + resource.vec_usize[1]); + + diff --git a/assets/tests/rhai/sub/vec3.rhai b/assets/tests/rhai/sub/vec3.rhai new file mode 100644 index 0000000000..e65d428212 --- /dev/null +++ b/assets/tests/rhai/sub/vec3.rhai @@ -0,0 +1,11 @@ +let a = Vec3.new_.call(1.0, 2.0, 3.0); +let b = Vec3.new_.call(4.0, 5.0, 6.0); + + +assert((a - 1).x == 0.0, "Subtraction did not work"); +assert((a - 1).y == 1.0, "Subtraction did not work"); +assert((a - 1).z == 2.0, "Subtraction did not work"); + +assert((a - b).x == -3.0, "Subtraction did not work"); +assert((a - b).y == -3.0, "Subtraction did not work"); +assert((a - b).z == -3.0, "Subtraction did not work"); \ No newline at end of file diff --git a/assets/tests/rhai/unm/vec3.rhai b/assets/tests/rhai/unm/vec3.rhai new file mode 100644 index 0000000000..1104596bf1 --- /dev/null +++ b/assets/tests/rhai/unm/vec3.rhai @@ -0,0 +1,5 @@ +let a = Vec3.new_.call(2.0, -4.0, 6.0); + +assert((-a).x == -2.0, "Negation did not work"); +assert((-a).y == 4.0, "Negation did not work"); +assert((-a).z == -6.0, "Negation did not work"); \ No newline at end of file diff --git a/assets/tests/rhai_tests.rs b/assets/tests/rhai_tests.rs new file mode 100644 index 0000000000..2ab89b04c6 --- /dev/null +++ b/assets/tests/rhai_tests.rs @@ -0,0 +1,144 @@ +#![allow( + clippy::unwrap_used, + clippy::todo, + clippy::expect_used, + clippy::panic, + missing_docs +)] +use bevy_mod_scripting_core::{ + bindings::{pretty_print::DisplayWithWorld, ThreadWorldContainer, WorldContainer}, + error::ScriptError, + AddRuntimeInitializer, +}; +use bevy_mod_scripting_rhai::RhaiScriptingPlugin; +use libtest_mimic::{Arguments, Failed, Trial}; +use rhai::{Dynamic, EvalAltResult, FnPtr, NativeCallContext}; +use script_integration_test_harness::execute_integration_test; +use std::{ + fs::{self, DirEntry}, + io, panic, + path::{Path, PathBuf}, +}; + +struct Test { + code: String, + path: PathBuf, +} + +impl Test { + fn execute(self) -> Result<(), Failed> { + execute_integration_test::( + |world, type_registry| { + let _ = world; + let _ = type_registry; + }, + |app| { + app.add_plugins(RhaiScriptingPlugin::default()); + app.add_runtime_initializer::(|runtime| { + runtime.register_fn("assert", |a: Dynamic, b: &str| { + if !a.is::() { + panic!("Expected a boolean value, but got {:?}", a); + } + if !a.as_bool().unwrap() { + panic!("Assertion failed. {}", b); + } + }); + + runtime.register_fn("assert", |a: Dynamic| { + if !a.is::() { + panic!("Expected a boolean value, but got {:?}", a); + } + if !a.as_bool().unwrap() { + panic!("Assertion failed"); + } + }); + runtime.register_fn("assert_throws", |ctxt: NativeCallContext, fn_: FnPtr, regex: String| { + let world = ThreadWorldContainer.try_get_world()?; + let args: [Dynamic;0] = []; + let result = fn_.call_within_context::<()>(&ctxt, args); + match result { + Ok(_) => panic!("Expected function to throw error, but it did not."), + Err(e) => { + let e = ScriptError::from_rhai_error(*e); + let err = e.display_with_world(world); + let regex = regex::Regex::new(®ex).unwrap(); + if regex.is_match(&err) { + Ok::<(), Box>(()) + } else { + panic!( + "Expected error message to match the regex: \n{}\n\nBut got:\n{}", + regex.as_str(), + err + ) + } + }, + } + }); + Ok(()) + }); + }, + self.path.as_os_str().to_str().unwrap(), + self.code.as_bytes(), + ) + .map_err(Failed::from) + } + + fn name(&self) -> String { + format!( + "script_test - lua - {}", + self.path + .to_string_lossy() + .split(&format!("tests{}data", std::path::MAIN_SEPARATOR)) + .last() + .unwrap() + ) + } +} + +fn visit_dirs(dir: &Path, cb: &mut dyn FnMut(&DirEntry)) -> io::Result<()> { + if dir.is_dir() { + for entry in fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + visit_dirs(&path, cb)?; + } else { + cb(&entry); + } + } + } else { + panic!("Not a directory: {:?}", dir); + } + Ok(()) +} + +fn discover_all_tests() -> Vec { + let workspace_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let test_root = workspace_root.join("tests").join("data"); + let mut test_files = Vec::new(); + visit_dirs(&test_root, &mut |entry| { + let path = entry.path(); + let code = fs::read_to_string(&path).unwrap(); + if path.extension().unwrap() == "rhai" { + test_files.push(Test { code, path }); + } + }) + .unwrap(); + + test_files +} + +// run this with `cargo test --features lua54 --package bevy_mod_scripting_lua --test lua_tests` +// or filter using the prefix "lua test -" +fn main() { + // Parse command line arguments + let args = Arguments::from_args(); + + // Create a list of tests and/or benchmarks (in this case: two dummy tests). + let tests = discover_all_tests() + .into_iter() + .map(|t| Trial::test(t.name(), move || t.execute())); + + // Run all tests and exit the application appropriatly. + libtest_mimic::run(&args, tests.collect()).exit(); +} diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/set/set_primitives_works.lua b/assets/tests/set/set_primitives_works.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/set/set_primitives_works.lua rename to assets/tests/set/set_primitives_works.lua diff --git a/assets/tests/set/set_primitives_works.rhai b/assets/tests/set/set_primitives_works.rhai new file mode 100644 index 0000000000..8ae0d6d4e8 --- /dev/null +++ b/assets/tests/set/set_primitives_works.rhai @@ -0,0 +1,28 @@ +let Resource = world.get_type_by_name.call("TestResourceWithVariousFields"); +let resource = world.get_resource.call(Resource); + +resource.string = "Hello, World!"; +resource.bool = true; +resource.int = 42; +resource.float = 3.0; +resource.vec_usize = [ 1, 2 ]; + +assert(resource.string == "Hello, World!", "Expected 'Hello, World!', got " + resource.string); +assert(resource.bool == true, "Expected true, got " + resource.bool); +assert(resource.int == 42, "Expected 42, got " + resource.int); +assert(resource.float == 3.0, "Expected 3.14, got " + resource.float); +assert(resource.vec_usize[0] == 1, "Expected 1, got " + resource.vec_usize[1]); + +resource.string = "Goodbye, World!"; +resource.bool = false; +resource.int = 24; +resource.float = 1.0; +resource.vec_usize = [ 3, 4 ]; + +assert(resource.string == "Goodbye, World!", "Expected 'Goodbye, World!', got " + resource.string); +assert(resource.bool == false, "Expected false, got " + resource.bool); +assert(resource.int == 24, "Expected 24, got " + resource.int); +assert(resource.float == 1.0, "Expected 1.41, got " + resource.float); +assert(resource.vec_usize[0] == 3, "Expected 3, got " + resource.vec_usize[1]); + + diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/sub/vec3.lua b/assets/tests/sub/vec3.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/sub/vec3.lua rename to assets/tests/sub/vec3.lua diff --git a/assets/tests/sub/vec3.rhai b/assets/tests/sub/vec3.rhai new file mode 100644 index 0000000000..e65d428212 --- /dev/null +++ b/assets/tests/sub/vec3.rhai @@ -0,0 +1,11 @@ +let a = Vec3.new_.call(1.0, 2.0, 3.0); +let b = Vec3.new_.call(4.0, 5.0, 6.0); + + +assert((a - 1).x == 0.0, "Subtraction did not work"); +assert((a - 1).y == 1.0, "Subtraction did not work"); +assert((a - 1).z == 2.0, "Subtraction did not work"); + +assert((a - b).x == -3.0, "Subtraction did not work"); +assert((a - b).y == -3.0, "Subtraction did not work"); +assert((a - b).z == -3.0, "Subtraction did not work"); \ No newline at end of file diff --git a/crates/languages/bevy_mod_scripting_lua/tests/data/unm/vec3.lua b/assets/tests/unm/vec3.lua similarity index 100% rename from crates/languages/bevy_mod_scripting_lua/tests/data/unm/vec3.lua rename to assets/tests/unm/vec3.lua diff --git a/assets/tests/unm/vec3.rhai b/assets/tests/unm/vec3.rhai new file mode 100644 index 0000000000..1104596bf1 --- /dev/null +++ b/assets/tests/unm/vec3.rhai @@ -0,0 +1,5 @@ +let a = Vec3.new_.call(2.0, -4.0, 6.0); + +assert((-a).x == -2.0, "Negation did not work"); +assert((-a).y == 4.0, "Negation did not work"); +assert((-a).z == -6.0, "Negation did not work"); \ No newline at end of file diff --git a/crates/bevy_mod_scripting_core/Cargo.toml b/crates/bevy_mod_scripting_core/Cargo.toml index 92883c9f2e..d980cd97f1 100644 --- a/crates/bevy_mod_scripting_core/Cargo.toml +++ b/crates/bevy_mod_scripting_core/Cargo.toml @@ -40,6 +40,7 @@ itertools = "0.13" derivative = "2.2" profiling = { workspace = true } bevy_mod_scripting_derive = { workspace = true } +fixedbitset = "0.5" [dev-dependencies] test_utils = { workspace = true } diff --git a/crates/bevy_mod_scripting_core/src/asset.rs b/crates/bevy_mod_scripting_core/src/asset.rs index 2d3fff1419..a41e911237 100644 --- a/crates/bevy_mod_scripting_core/src/asset.rs +++ b/crates/bevy_mod_scripting_core/src/asset.rs @@ -161,6 +161,14 @@ pub struct AssetPathToLanguageMapper { pub map: fn(&AssetPath) -> Language, } +impl Default for AssetPathToLanguageMapper { + fn default() -> Self { + Self { + map: |_| Language::Unknown, + } + } +} + /// A cache of asset id's to their script id's. Necessary since when we drop an asset we won't have the ability to get the path from the asset. #[derive(Default, Debug, Resource)] pub struct ScriptMetadataStore { diff --git a/crates/bevy_mod_scripting_core/src/bindings/access_map.rs b/crates/bevy_mod_scripting_core/src/bindings/access_map.rs index 5a2e149966..b913957ee2 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/access_map.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/access_map.rs @@ -4,9 +4,9 @@ use std::thread::ThreadId; use bevy::{ ecs::{component::ComponentId, world::unsafe_world_cell::UnsafeWorldCell}, prelude::Resource, + utils::hashbrown::HashMap, }; -use dashmap::{DashMap, Entry}; -use parking_lot::RwLock; +use parking_lot::Mutex; use smallvec::SmallVec; use crate::error::InteropError; @@ -224,162 +224,208 @@ impl From for ReflectAllocationId { #[derive(Debug, Default)] /// A map of access claims -pub struct AccessMap { - individual_accesses: DashMap, - global_lock: RwLock, +pub struct AccessMap(Mutex); + +#[derive(Debug, Default, Clone)] +struct AccessMapInner { + individual_accesses: HashMap, + global_lock: AccessCount, } #[profiling::all_functions] impl AccessMap { - /// Checks if the map is locked exclusively + /// Increments the scope and executes operations which will be unrolled at the end. + /// Any accesses added inside the scope are rolled back after f() returns. + pub fn with_scope O>(&self, f: F) -> O { + // Snapshot the current inner state. + let backup = { + let inner = self.0.lock(); + inner.clone() + }; + + let result = f(); + + // Roll back the inner state. + { + let mut inner = self.0.lock(); + *inner = backup; + } + + result + } + + /// Checks if the map is locked exclusively. pub fn is_locked_exclusively(&self) -> bool { - let global_lock = self.global_lock.read(); - !global_lock.can_write() + let inner = self.0.lock(); + // If global_lock cannot be written, then it is locked exclusively. + !inner.global_lock.can_write() } - /// retrieves the location of the global lock if any + /// Retrieves the location of the global lock if any. pub fn global_access_location(&self) -> Option> { - let global_lock = self.global_lock.read(); - global_lock.as_location() + let inner = self.0.lock(); + inner.global_lock.as_location() } - /// Tries to claim read access, will return false if somebody else is writing to the same key, or holding a global lock + /// Tries to claim read access. Returns false if somebody else is writing to the same key, + /// or if a global lock prevents the access. #[track_caller] pub fn claim_read_access(&self, key: K) -> bool { - if self.is_locked_exclusively() { + let mut inner = self.0.lock(); + + if !inner.global_lock.can_write() { return false; } let key = key.as_index(); - let access = self.individual_accesses.try_entry(key); - match access.map(Entry::or_default) { - Some(mut entry) if entry.can_read() => { - entry.read_by.push(ClaimOwner { - id: std::thread::current().id(), - location: *std::panic::Location::caller(), - }); - true - } - _ => false, + let entry = inner.individual_accesses.entry(key).or_default(); + if entry.can_read() { + entry.read_by.push(ClaimOwner { + id: std::thread::current().id(), + location: *std::panic::Location::caller(), + }); + true + } else { + false } } + /// Tries to claim write access. Returns false if somebody else is reading or writing to the same key, + /// or if a global lock prevents the access. #[track_caller] - /// Tries to claim write access, will return false if somebody else is reading or writing to the same key, or holding a global lock pub fn claim_write_access(&self, key: K) -> bool { - if self.is_locked_exclusively() { + let mut inner = self.0.lock(); + + if !inner.global_lock.can_write() { return false; } + let key = key.as_index(); - let access = self.individual_accesses.try_entry(key); - match access.map(Entry::or_default) { - Some(mut entry) if entry.can_write() => { - entry.read_by.push(ClaimOwner { - id: std::thread::current().id(), - location: *std::panic::Location::caller(), - }); - entry.written = true; - true - } - _ => false, + let entry = inner.individual_accesses.entry(key).or_default(); + if entry.can_write() { + entry.read_by.push(ClaimOwner { + id: std::thread::current().id(), + location: *std::panic::Location::caller(), + }); + entry.written = true; + true + } else { + false } } - /// Tries to claim global access. This type of access prevents any other access from happening simulatenously - /// Will return false if anybody else is currently accessing any part of the map + /// Tries to claim global access. This prevents any other access from happening simultaneously. + /// Returns false if any access is currently active. #[track_caller] pub fn claim_global_access(&self) -> bool { - let mut global_lock = self.global_lock.write(); + let mut inner = self.0.lock(); - if !self.individual_accesses.is_empty() || !global_lock.can_write() { + if !inner.individual_accesses.is_empty() || !inner.global_lock.can_write() { return false; } - global_lock.read_by.push(ClaimOwner { + inner.global_lock.read_by.push(ClaimOwner { id: std::thread::current().id(), location: *std::panic::Location::caller(), }); - global_lock.written = true; + inner.global_lock.written = true; true } - /// Releases an access + /// Releases an access. /// /// # Panics - /// if the access is released from a different thread than it was claimed from + /// if the access is released from a different thread than it was claimed from. pub fn release_access(&self, key: K) { + let mut inner = self.0.lock(); let key = key.as_index(); - let access = self.individual_accesses.entry(key); - match access { - dashmap::mapref::entry::Entry::Occupied(mut entry) => { - let entry_mut = entry.get_mut(); - entry_mut.written = false; - if let Some(p) = entry_mut.read_by.pop() { - assert!( - p.id == std::thread::current().id(), - "Access released from wrong thread, claimed at {}", - p.location.display_location() - ); - } - if entry_mut.readers() == 0 { - entry.remove(); - } + + if let Some(entry) = inner.individual_accesses.get_mut(&key) { + entry.written = false; + if let Some(claim) = entry.read_by.pop() { + assert!( + claim.id == std::thread::current().id(), + "Access released from wrong thread, claimed at {}", + claim.location.display_location() + ); + } + if entry.readers() == 0 { + inner.individual_accesses.remove(&key); } - dashmap::mapref::entry::Entry::Vacant(_) => {} } } - /// Releases a global access + /// Releases a global access. pub fn release_global_access(&self) { - let mut global_lock = self.global_lock.write(); - global_lock.written = false; - if let Some(p) = global_lock.read_by.pop() { + let mut inner = self.0.lock(); + inner.global_lock.written = false; + if let Some(claim) = inner.global_lock.read_by.pop() { assert!( - p.id == std::thread::current().id(), - "Access released from wrong thread, claimed at {}", - p.location.display_location() + claim.id == std::thread::current().id(), + "Global access released from wrong thread, claimed at {}", + claim.location.display_location() ); } } - /// Lists all accesses + /// Lists all active accesses. pub fn list_accesses(&self) -> Vec<(K, AccessCount)> { - self.individual_accesses + let inner = self.0.lock(); + inner + .individual_accesses .iter() - .map(|e| (K::from_index(*e.key()), e.value().clone())) + .map(|(&key, count)| (K::from_index(key), count.clone())) .collect() } - /// Counts the number of accesses + /// Counts the number of active individual accesses. pub fn count_accesses(&self) -> usize { - self.individual_accesses.len() + if self.is_locked_exclusively() { + 1 + } else { + let inner = self.0.lock(); + inner.individual_accesses.len() + } } - /// Releases all accesses + /// Releases all accesses. pub fn release_all_accesses(&self) { - self.individual_accesses.clear(); - self.release_global_access(); + let mut inner = self.0.lock(); + inner.individual_accesses.clear(); + // Release global access if held. + inner.global_lock.written = false; + inner.global_lock.read_by.clear(); } - /// Accesses the location of a key + /// Returns the location where the key was first accessed. pub fn access_location( &self, key: K, ) -> Option> { + let inner = self.0.lock(); if key.as_index() == 0 { - return self.global_access_location(); + // it blocked by individual access + inner.global_lock.as_location().or_else(|| { + inner + .individual_accesses + .iter() + .next() + .and_then(|(_, access_count)| access_count.as_location()) + }) + } else { + inner + .individual_accesses + .get(&key.as_index()) + .and_then(|access| access.as_location()) } - - self.individual_accesses - .try_get(&key.as_index()) - .try_unwrap() - .and_then(|access| access.as_location()) } - /// Accesses the location of the first access + /// Returns the location of the first access among all entries. pub fn access_first_location(&self) -> Option> { - self.individual_accesses - .iter() - .find_map(|e| e.value().as_location()) + let inner = self.0.lock(); + inner + .individual_accesses + .values() + .find_map(|access| access.as_location()) } } @@ -461,6 +507,7 @@ macro_rules! with_global_access { #[cfg(test)] mod test { use super::*; + #[test] fn test_list_accesses() { let access_map = AccessMap::default(); @@ -588,4 +635,88 @@ mod test { assert_eq!(ReflectAccessId::from_index(3), second_allocation); assert_eq!(ReflectAccessId::from_index(4), second_component); } + + #[test] + fn test_with_scope_unrolls_individual_accesses() { + let access_map = AccessMap::default(); + // Claim a read access outside the scope + assert!(access_map.claim_read_access(0)); + + // Inside with_scope, claim additional accesses + access_map.with_scope(|| { + assert!(access_map.claim_read_access(1)); + assert!(access_map.claim_write_access(2)); + // At this point, individual_accesses contains keys 0, 1 and 2. + let accesses = access_map.list_accesses::(); + assert_eq!(accesses.len(), 3); + }); + + // After with_scope returns, accesses claimed inside (keys 1 and 2) are unrolled. + let accesses = access_map.list_accesses::(); + // Only the access claimed outside (key 0) remains. + assert_eq!(accesses.len(), 1); + let (k, count) = &accesses[0]; + assert_eq!(*k, 0); + // The outside access remains valid. + assert!(count.readers() > 0); + } + + #[test] + fn test_with_scope_unrolls_global_accesses() { + let access_map = AccessMap::default(); + + access_map.with_scope(|| { + assert!(access_map.claim_global_access()); + // At this point, global_access is claimed. + assert!(!access_map.claim_read_access(0)); + }); + + let accesses = access_map.list_accesses::(); + assert_eq!(accesses.len(), 0); + } + + #[test] + fn count_accesses_counts_globals() { + let access_map = AccessMap::default(); + + // Initially, no accesses are active. + assert_eq!(access_map.count_accesses(), 0); + + // Claim global access. When global access is active, + // count_accesses should return 1. + assert!(access_map.claim_global_access()); + assert_eq!(access_map.count_accesses(), 1); + access_map.release_global_access(); + + // Now claim individual accesses. + assert!(access_map.claim_read_access(1)); + assert!(access_map.claim_write_access(2)); + // Since two separate keys were claimed, count_accesses should return 2. + assert_eq!(access_map.count_accesses(), 2); + + // Cleanup individual accesses. + access_map.release_access(1); + access_map.release_access(2); + } + + #[test] + fn location_is_tracked_for_all_types_of_accesses() { + let access_map = AccessMap::default(); + + assert!(access_map.claim_global_access()); + assert!(access_map + .access_location(ReflectAccessId::for_global()) + .is_some()); + access_map.release_global_access(); + + // Claim a read access + assert!(access_map.claim_read_access(1)); + assert!(access_map.access_location(1).is_some()); + access_map.release_access(1); + + // Claim a write access + assert!(access_map.claim_write_access(2)); + assert!(access_map.access_location(2).is_some()); + access_map.release_access(2); + } } diff --git a/crates/bevy_mod_scripting_core/src/bindings/pretty_print.rs b/crates/bevy_mod_scripting_core/src/bindings/pretty_print.rs index daf489c967..1c52035988 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/pretty_print.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/pretty_print.rs @@ -12,7 +12,10 @@ use bevy::{ reflect::{PartialReflect, ReflectRef}, }; use itertools::Itertools; -use std::{any::TypeId, borrow::Cow}; +use std::{ + any::{Any, TypeId}, + borrow::Cow, +}; /// A utility for printing reflect references in a human readable format. pub struct ReflectReferencePrinter { @@ -325,7 +328,7 @@ impl ReflectReferencePrinter { /// For types which can't be pretty printed without world access. /// Implementors should try to print the best value they can, and never panick. -pub trait DisplayWithWorld: std::fmt::Debug { +pub trait DisplayWithWorld: std::fmt::Debug + AsAny { /// # Warning /// Display this type without world access. It is not recommended to use this method for anything other than debugging or necessary trait impl corners. /// For many types this will just print type id's with no further information. @@ -343,6 +346,27 @@ pub trait DisplayWithWorld: std::fmt::Debug { self.display_with_world(world) } } + +#[doc(hidden)] +pub trait AsAny: 'static { + fn as_any(&self) -> &dyn Any; +} + +#[doc(hidden)] +impl AsAny for T { + fn as_any(&self) -> &dyn Any { + self + } +} + +impl dyn DisplayWithWorld { + /// Downcasts the `DisplayWithWorld` trait object to a concrete type. + /// Trampoline function to allow downcasting of errors. + pub fn downcast_ref(&self) -> Option<&T> { + self.as_any().downcast_ref::() + } +} + #[profiling::all_functions] impl DisplayWithWorld for ReflectReference { fn display_with_world(&self, world: WorldGuard) -> String { @@ -524,7 +548,7 @@ impl DisplayWithWorld for ScriptValue { } } #[profiling::all_functions] -impl DisplayWithWorld for Vec { +impl DisplayWithWorld for Vec { fn display_with_world(&self, world: WorldGuard) -> String { let mut string = String::new(); BracketType::Square.surrounded(&mut string, |string| { @@ -579,7 +603,7 @@ impl DisplayWithWorld for String { } } #[profiling::all_functions] -impl DisplayWithWorld +impl DisplayWithWorld for std::collections::HashMap { fn display_with_world(&self, world: WorldGuard) -> String { diff --git a/crates/bevy_mod_scripting_core/src/bindings/query.rs b/crates/bevy_mod_scripting_core/src/bindings/query.rs index 68b8d72f8d..d40896c516 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/query.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/query.rs @@ -190,7 +190,7 @@ impl WorldAccessGuard<'_> { &self, query: ScriptQueryBuilder, ) -> Result, InteropError> { - with_global_access!(self.0.accesses, "Could not query", { + with_global_access!(self.inner.accesses, "Could not query", { let world = unsafe { self.as_unsafe_world_cell()?.world_mut() }; let mut dynamic_query = QueryBuilder::::new(world); diff --git a/crates/bevy_mod_scripting_core/src/bindings/reference.rs b/crates/bevy_mod_scripting_core/src/bindings/reference.rs index ab83201df0..5b9e334eee 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/reference.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/reference.rs @@ -243,7 +243,7 @@ impl ReflectReference { ) -> Result { let access_id = ReflectAccessId::for_reference(self.base.base_id.clone()); with_access_read!( - world.0.accesses, + world.inner.accesses, access_id, "could not access reflect reference", { unsafe { self.reflect_unsafe(world.clone()) }.map(f)? } @@ -260,7 +260,7 @@ impl ReflectReference { ) -> Result { let access_id = ReflectAccessId::for_reference(self.base.base_id.clone()); with_access_write!( - world.0.accesses, + world.inner.accesses, access_id, "Could not access reflect reference mutably", { unsafe { self.reflect_mut_unsafe(world.clone()) }.map(f)? } diff --git a/crates/bevy_mod_scripting_core/src/bindings/world.rs b/crates/bevy_mod_scripting_core/src/bindings/world.rs index cab2369bd9..8ef2d44af0 100644 --- a/crates/bevy_mod_scripting_core/src/bindings/world.rs +++ b/crates/bevy_mod_scripting_core/src/bindings/world.rs @@ -40,11 +40,11 @@ use bevy::{ use std::{ any::TypeId, borrow::Cow, - cell::{Cell, RefCell}, + cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc, - sync::Arc, + sync::{atomic::AtomicBool, Arc}, }; /// Prefer to directly using [`WorldAccessGuard`]. If the underlying type changes, this alias will be updated. @@ -53,12 +53,19 @@ pub type WorldGuard<'w> = WorldAccessGuard<'w>; pub type WorldGuardRef<'w> = &'w WorldAccessGuard<'w>; /// Provides safe access to the world via [`WorldAccess`] permissions, which enforce aliasing rules at runtime in multi-thread environments -#[derive(Clone)] -pub struct WorldAccessGuard<'w>(pub(crate) Rc>); +#[derive(Clone, Debug)] +pub struct WorldAccessGuard<'w> { + /// The guard this guard pointer represents + pub(crate) inner: Rc>, + /// if true the guard is invalid and cannot be used, stored as a second pointer so that this validity can be + /// stored separate from the contents of the guard + invalid: Rc, +} /// Used to decrease the stack size of [`WorldAccessGuard`] pub(crate) struct WorldAccessGuardInner<'w> { - cell: Cell>>, + /// Safety: cannot be used unless the scope depth is less than the max valid scope + cell: UnsafeWorldCell<'w>, // TODO: this is fairly hefty, explore sparse sets, bit fields etc pub(crate) accesses: AccessMap, /// Cached for convenience, since we need it for most operations, means we don't need to lock the type registry every time @@ -67,6 +74,12 @@ pub(crate) struct WorldAccessGuardInner<'w> { function_registry: AppScriptFunctionRegistry, } +impl std::fmt::Debug for WorldAccessGuardInner<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WorldAccessGuardInner").finish() + } +} + impl WorldAccessGuard<'static> { /// Shortens the lifetime of the guard to the given lifetime. pub(crate) fn shorten_lifetime<'w>(self) -> WorldGuard<'w> { @@ -76,6 +89,29 @@ impl WorldAccessGuard<'static> { } #[profiling::all_functions] impl<'w> WorldAccessGuard<'w> { + /// creates a new guard derived from this one, which if invalidated, will not invalidate the original + fn scope(&self) -> Self { + let mut new_guard = self.clone(); + new_guard.invalid = Rc::new( + new_guard + .invalid + .load(std::sync::atomic::Ordering::Relaxed) + .into(), + ); + new_guard + } + + /// Returns true if the guard is valid, false if it is invalid + fn is_valid(&self) -> bool { + !self.invalid.load(std::sync::atomic::Ordering::Relaxed) + } + + /// Invalidates the world access guard, making it and any guards derived from this one unusable. + pub fn invalidate(&self) { + self.invalid + .store(true, std::sync::atomic::Ordering::Relaxed); + } + /// Safely allows access to the world for the duration of the closure via a static [`WorldAccessGuard`]. /// /// The guard is invalidated at the end of the closure, meaning the world cannot be accessed at all after the closure ends. @@ -92,6 +128,20 @@ impl<'w> WorldAccessGuard<'w> { o } + /// Safely allows access to the world for the duration of the closure via a static [`WorldAccessGuard`] using a previously lifetimed world guard. + /// Will invalidate the static guard at the end but not the original. + pub fn with_existing_static_guard( + guard: WorldAccessGuard<'w>, + f: impl FnOnce(WorldGuard<'static>) -> O, + ) -> O { + // safety: we invalidate the guard after the closure is called, meaning the world cannot be accessed at all after the 'w lifetime ends, from the static guard + // i.e. even if somebody squirells it away, it will be useless. + let static_guard: WorldAccessGuard<'static> = unsafe { std::mem::transmute(guard.scope()) }; + let o = f(static_guard.clone()); + static_guard.invalidate(); + o + } + /// Creates a new [`WorldAccessGuard`] for the given mutable borrow of the world. /// /// Creating a guard requires that some resources exist in the world, namely: @@ -108,19 +158,16 @@ impl<'w> WorldAccessGuard<'w> { let function_registry = world .get_resource_or_init::() .clone(); - let cell = Cell::new(Some(world.as_unsafe_world_cell())); - Self(Rc::new(WorldAccessGuardInner { - cell, - accesses: Default::default(), - allocator, - type_registry, - function_registry, - })) - } - - /// Invalidates the world access guard, making it unusable. - pub fn invalidate(&self) { - self.0.cell.replace(None); + Self { + inner: Rc::new(WorldAccessGuardInner { + cell: world.as_unsafe_world_cell(), + accesses: Default::default(), + allocator, + type_registry, + function_registry, + }), + invalid: Rc::new(false.into()), + } } /// Runs a closure within an isolated access scope, releasing leftover accesses, should only be used in a single-threaded context. @@ -131,43 +178,37 @@ impl<'w> WorldAccessGuard<'w> { &self, f: F, ) -> Result { - self.begin_access_scope()?; - let o = f(); - unsafe { self.end_access_scope()? }; - Ok(o) - } - - /// Begins a new access scope. Currently this simply throws an erorr if there are any accesses held. Should only be used in a single-threaded context - fn begin_access_scope(&self) -> Result<(), InteropError> { - if self.0.accesses.count_accesses() != 0 { - return Err(InteropError::invalid_access_count(self.0.accesses.count_accesses(), 0, "When beginning access scope, presumably for a function call, some accesses are still held".to_owned())); - } - - Ok(()) - } - - /// Ends the access scope, releasing all accesses. Should only be used in a single-threaded context - unsafe fn end_access_scope(&self) -> Result<(), InteropError> { - self.0.accesses.release_all_accesses(); - - Ok(()) + Ok(self.inner.accesses.with_scope(f)) } /// Purely debugging utility to list all accesses currently held. pub fn list_accesses(&self) -> Vec<(ReflectAccessId, AccessCount)> { - self.0.accesses.list_accesses() + self.inner.accesses.list_accesses() + } + + /// Returns the number of accesses currently held. + pub fn access_len(&self) -> usize { + self.inner.accesses.count_accesses() } /// Retrieves the underlying unsafe world cell, with no additional guarantees of safety /// proceed with caution and only use this if you understand what you're doing pub fn as_unsafe_world_cell(&self) -> Result, InteropError> { - self.0.cell.get().ok_or_else(InteropError::missing_world) + if !self.is_valid() { + return Err(InteropError::missing_world()); + } + + Ok(self.inner.cell) } /// Retrieves the underlying read only unsafe world cell, with no additional guarantees of safety /// proceed with caution and only use this if you understand what you're doing pub fn as_unsafe_world_cell_readonly(&self) -> Result, InteropError> { - self.0.cell.get().ok_or_else(InteropError::missing_world) + if !self.is_valid() { + return Err(InteropError::missing_world()); + } + + Ok(self.inner.cell) } /// Gets the component id of the given component or resource @@ -191,19 +232,19 @@ impl<'w> WorldAccessGuard<'w> { &self, raid: ReflectAccessId, ) -> Option> { - self.0.accesses.access_location(raid) + self.inner.accesses.access_location(raid) } #[track_caller] /// Claims read access to the given type. pub fn claim_read_access(&self, raid: ReflectAccessId) -> bool { - self.0.accesses.claim_read_access(raid) + self.inner.accesses.claim_read_access(raid) } #[track_caller] /// Claims write access to the given type. pub fn claim_write_access(&self, raid: ReflectAccessId) -> bool { - self.0.accesses.claim_write_access(raid) + self.inner.accesses.claim_write_access(raid) } /// Releases read or write access to the given type. @@ -213,12 +254,12 @@ impl<'w> WorldAccessGuard<'w> { /// - You can only call this if you previously called one of: [`WorldAccessGuard::claim_read_access`] or [`WorldAccessGuard::claim_write_access`] /// - The number of claim and release calls for the same id must always match pub unsafe fn release_access(&self, raid: ReflectAccessId) { - self.0.accesses.release_access(raid) + self.inner.accesses.release_access(raid) } /// Claims global access to the world pub fn claim_global_access(&self) -> bool { - self.0.accesses.claim_global_access() + self.inner.accesses.claim_global_access() } /// Releases global access to the world @@ -226,22 +267,22 @@ impl<'w> WorldAccessGuard<'w> { /// # Safety /// - This can only be called safely after all references created using the access have been dropped pub unsafe fn release_global_access(&self) { - self.0.accesses.release_global_access() + self.inner.accesses.release_global_access() } /// Returns the type registry for the world pub fn type_registry(&self) -> TypeRegistryArc { - self.0.type_registry.clone() + self.inner.type_registry.clone() } /// Returns the script allocator for the world pub fn allocator(&self) -> AppReflectAllocator { - self.0.allocator.clone() + self.inner.allocator.clone() } /// Returns the function registry for the world pub fn script_function_registry(&self) -> AppScriptFunctionRegistry { - self.0.function_registry.clone() + self.inner.function_registry.clone() } /// Claims access to the world for the duration of the closure, allowing for global access to the world. @@ -250,11 +291,15 @@ impl<'w> WorldAccessGuard<'w> { &self, f: F, ) -> Result { - with_global_access!(self.0.accesses, "Could not claim exclusive world access", { - // safety: we have global access for the duration of the closure - let world = unsafe { self.as_unsafe_world_cell()?.world_mut() }; - Ok(f(world)) - })? + with_global_access!( + self.inner.accesses, + "Could not claim exclusive world access", + { + // safety: we have global access for the duration of the closure + let world = unsafe { self.as_unsafe_world_cell()?.world_mut() }; + Ok(f(world)) + } + )? } /// Safely accesses the resource by claiming and releasing access to it. @@ -270,7 +315,7 @@ impl<'w> WorldAccessGuard<'w> { let access_id = ReflectAccessId::for_resource::(&cell)?; with_access_read!( - self.0.accesses, + self.inner.accesses, access_id, format!("Could not access resource: {}", std::any::type_name::()), { @@ -298,7 +343,7 @@ impl<'w> WorldAccessGuard<'w> { let cell = self.as_unsafe_world_cell()?; let access_id = ReflectAccessId::for_resource::(&cell)?; with_access_write!( - self.0.accesses, + self.inner.accesses, access_id, format!("Could not access resource: {}", std::any::type_name::()), { @@ -323,7 +368,7 @@ impl<'w> WorldAccessGuard<'w> { let cell = self.as_unsafe_world_cell()?; let access_id = ReflectAccessId::for_component::(&cell)?; with_access_read!( - self.0.accesses, + self.inner.accesses, access_id, format!("Could not access component: {}", std::any::type_name::()), { @@ -343,7 +388,7 @@ impl<'w> WorldAccessGuard<'w> { let access_id = ReflectAccessId::for_component::(&cell)?; with_access_write!( - self.0.accesses, + self.inner.accesses, access_id, format!("Could not access component: {}", std::any::type_name::()), { @@ -815,7 +860,7 @@ impl WorldAccessGuard<'_> { ) })?; - with_global_access!(self.0.accesses, "Could not insert element", { + with_global_access!(self.inner.accesses, "Could not insert element", { let cell = self.as_unsafe_world_cell()?; let type_registry = self.type_registry(); let type_registry = type_registry.read(); @@ -1292,4 +1337,52 @@ mod test { let expected = Ok::<_, InteropError>(Box::new(SimpleEnum::Unit) as Box); pretty_assertions::assert_str_eq!(format!("{result:#?}"), format!("{expected:#?}")); } + + #[test] + fn test_scoped_handle_invalidate_doesnt_invalidate_parent() { + let mut world = setup_world(|_, _| {}); + let world = WorldAccessGuard::new(&mut world); + let scoped_world = world.scope(); + + // can use scoped & normal worlds + scoped_world.spawn().unwrap(); + world.spawn().unwrap(); + pretty_assertions::assert_eq!(scoped_world.is_valid(), true); + pretty_assertions::assert_eq!(world.is_valid(), true); + + scoped_world.invalidate(); + + // can only use normal world + pretty_assertions::assert_eq!(scoped_world.is_valid(), false); + pretty_assertions::assert_eq!(world.is_valid(), true); + world.spawn().unwrap(); + } + + #[test] + fn with_existing_static_guard_does_not_invalidate_original() { + let mut world = setup_world(|_, _| {}); + let world = WorldAccessGuard::new(&mut world); + + let mut sneaky_clone = None; + WorldAccessGuard::with_existing_static_guard(world.clone(), |g| { + pretty_assertions::assert_eq!(g.is_valid(), true); + sneaky_clone = Some(g.clone()); + }); + pretty_assertions::assert_eq!(world.is_valid(), true, "original world was invalidated"); + pretty_assertions::assert_eq!( + sneaky_clone.map(|c| c.is_valid()), + Some(false), + "scoped world was not invalidated" + ); + } + + #[test] + fn test_with_access_scope_success() { + let mut world = setup_world(|_, _| {}); + let guard = WorldAccessGuard::new(&mut world); + + // within the access scope, no extra accesses are claimed + let result = unsafe { guard.with_access_scope(|| 100) }; + assert_eq!(result.unwrap(), 100); + } } diff --git a/crates/bevy_mod_scripting_core/src/commands.rs b/crates/bevy_mod_scripting_core/src/commands.rs index 4811684d1b..e14dd14d61 100644 --- a/crates/bevy_mod_scripting_core/src/commands.rs +++ b/crates/bevy_mod_scripting_core/src/commands.rs @@ -2,9 +2,10 @@ use crate::{ asset::ScriptAsset, + bindings::WorldGuard, context::ContextBuilder, event::{IntoCallbackLabel, OnScriptLoaded, OnScriptUnloaded}, - extractors::{extract_handler_context, yield_handler_context, HandlerContext}, + extractors::{with_handler_system_state, HandlerContext}, handler::{handle_script_errors, CallbackSettings}, script::{Script, ScriptId, StaticScripts}, IntoScriptPluginParams, @@ -32,74 +33,62 @@ impl DeleteScript

{ impl Command for DeleteScript

{ fn apply(self, world: &mut bevy::prelude::World) { - let mut res_ctxt = match extract_handler_context::

(world) { - Ok(res_ctxt) => res_ctxt, - Err(e) => { - bevy::log::error_once!( - "Could not delete script: {}, as some plugin resources are missing: {}", - self.id, - e - ); - return; - } - }; - - if let Some(script) = res_ctxt.scripts.scripts.remove(&self.id) { - debug!("Deleting script with id: {}", self.id); - - match res_ctxt.script_contexts.get_mut(script.context_id) { - Some(context) => { - // first let the script uninstall itself - match (CallbackSettings::

::call)( - res_ctxt.callback_settings.callback_handler, - vec![], - bevy::ecs::entity::Entity::from_raw(0), - &self.id, - &OnScriptUnloaded::into_callback_label(), - context, - &res_ctxt - .context_loading_settings - .context_pre_handling_initializers, - &mut res_ctxt.runtime_container.runtime, - world, - ) { - Ok(_) => {} - Err(e) => { - handle_script_errors( - world, - [e.with_context(format!( - "Running unload hook for script with id: {}. Language: {}", - self.id, - P::LANGUAGE - ))] - .into_iter(), - ); + with_handler_system_state(world, |guard, handler_ctxt: &mut HandlerContext

| { + if let Some(script) = handler_ctxt.scripts.scripts.remove(&self.id) { + debug!("Deleting script with id: {}", self.id); + + match handler_ctxt.script_contexts.get_mut(script.context_id) { + Some(context) => { + // first let the script uninstall itself + match (CallbackSettings::

::call)( + handler_ctxt.callback_settings.callback_handler, + vec![], + bevy::ecs::entity::Entity::from_raw(0), + &self.id, + &OnScriptUnloaded::into_callback_label(), + context, + &handler_ctxt + .context_loading_settings + .context_pre_handling_initializers, + &mut handler_ctxt.runtime_container.runtime, + guard.clone(), + ) { + Ok(_) => {} + Err(e) => { + handle_script_errors( + guard, + [e.with_context(format!( + "Running unload hook for script with id: {}. Language: {}", + self.id, + P::LANGUAGE + ))] + .into_iter(), + ); + } } - } - debug!("Removing script with id: {}", self.id); - (res_ctxt.context_loading_settings.assigner.remove)( - script.context_id, - &script, - &mut res_ctxt.script_contexts, - ) - } - None => { - bevy::log::error!( - "Could not find context with id: {} corresponding to script with id: {}. Removing script without running callbacks.", + debug!("Removing script with id: {}", self.id); + (handler_ctxt.context_loading_settings.assigner.remove)( script.context_id, - self.id - ); - } - }; - } else { - bevy::log::error!( - "Attempted to delete script with id: {} but it does not exist, doing nothing!", - self.id - ); - } - - yield_handler_context(world, res_ctxt); + &script, + &mut handler_ctxt.script_contexts, + ) + } + None => { + bevy::log::error!( + "Could not find context with id: {} corresponding to script with id: {}. Removing script without running callbacks.", + script.context_id, + self.id + ); + } + }; + } else { + bevy::log::error!( + "Attempted to delete script with id: {} but it does not exist, doing nothing!", + self.id + ); + } + }) } } @@ -128,7 +117,7 @@ impl CreateOrUpdateScript

{ fn run_on_load_callback( &self, res_ctxt: &mut HandlerContext

, - world: &mut bevy::prelude::World, + guard: WorldGuard, ctxt: &mut

::C, ) { bevy::log::debug!( @@ -148,12 +137,12 @@ impl CreateOrUpdateScript

{ .context_loading_settings .context_pre_handling_initializers, &mut res_ctxt.runtime_container.runtime, - world, + guard.clone(), ) { Ok(_) => {} Err(e) => { handle_script_errors( - world, + guard, [e.with_context(format!( "{}: Running initialization hook for script with id: {}", P::LANGUAGE, @@ -168,7 +157,7 @@ impl CreateOrUpdateScript

{ #[inline(always)] fn reload_context( &self, - world: &mut bevy::prelude::World, + guard: WorldGuard, res_ctxt: &mut HandlerContext

, previous_context_id: u32, ) { @@ -182,20 +171,20 @@ impl CreateOrUpdateScript

{ &res_ctxt .context_loading_settings .context_pre_handling_initializers, - world, + guard.clone(), &mut res_ctxt.runtime_container.runtime, ) { Ok(_) => {} Err(e) => { handle_script_errors( - world, + guard.clone(), [e.with_context(format!("reloading script with id: {}", self.id))] .into_iter(), ); } }; - self.run_on_load_callback(res_ctxt, world, &mut previous_context); + self.run_on_load_callback(res_ctxt, guard, &mut previous_context); res_ctxt .script_contexts @@ -209,107 +198,88 @@ impl CreateOrUpdateScript

{ ); } } - - #[inline(always)] - fn execute( - self, - world: &mut bevy::prelude::World, - res_ctxt: &mut HandlerContext

, - previous_context_id: Option, - ) { - match previous_context_id { - Some(previous_context_id) => { - bevy::log::debug!( - "{}: script with id already has a context: {}", - P::LANGUAGE, - self.id - ); - self.reload_context(world, res_ctxt, previous_context_id); - } - None => { - let log_context = format!("{}: Loading script: {}", P::LANGUAGE, self.id); - - let new_context_id = (res_ctxt.context_loading_settings.assigner.assign)( - &self.id, - &self.content, - &res_ctxt.script_contexts, - ) - .unwrap_or_else(|| res_ctxt.script_contexts.allocate_id()); - if res_ctxt.script_contexts.contains(new_context_id) { - self.reload_context(world, res_ctxt, new_context_id); - } else { - // load new context - bevy::log::debug!("{}", log_context); - let ctxt = (ContextBuilder::

::load)( - res_ctxt.context_loading_settings.loader.load, - &self.id, - &self.content, - &res_ctxt.context_loading_settings.context_initializers, - &res_ctxt - .context_loading_settings - .context_pre_handling_initializers, - world, - &mut res_ctxt.runtime_container.runtime, - ); - let mut ctxt = match ctxt { - Ok(ctxt) => ctxt, - Err(e) => { - handle_script_errors(world, [e.with_context(log_context)].into_iter()); - return; - } - }; - - self.run_on_load_callback(res_ctxt, world, &mut ctxt); - - if res_ctxt - .script_contexts - .insert_with_id(new_context_id, ctxt) - .is_some() - { - bevy::log::warn!("{}: Context with id {} was not expected to exist. Overwriting it with a new context. This might happen if a script is not completely removed.", P::LANGUAGE, new_context_id); - } - } - - res_ctxt.scripts.scripts.insert( - self.id.clone(), - Script { - id: self.id, - asset: self.asset, - context_id: new_context_id, - }, - ); - } - } - } } impl Command for CreateOrUpdateScript

{ fn apply(self, world: &mut bevy::prelude::World) { - let mut res_ctxt = match extract_handler_context::

(world) { - Ok(res_ctxt) => res_ctxt, - Err(e) => { - bevy::log::error_once!( - "Could not create or update script: {}, as some plugin resources are missing: {}", - self.id, - e - ); - return; - } - }; + with_handler_system_state(world, |guard, handler_ctxt: &mut HandlerContext

| { + let script = handler_ctxt.scripts.scripts.get(&self.id); + let previous_context_id = script.as_ref().map(|s| s.context_id); + debug!( + "{}: CreateOrUpdateScript command applying (script_id: {}, previous_context_id: {:?})", + P::LANGUAGE, + self.id, + previous_context_id + ); - let script = res_ctxt.scripts.scripts.get(&self.id); - let previous_context_id = script.as_ref().map(|s| s.context_id); - debug!( - "{}: CreateOrUpdateScript command applying (script_id: {}, previous_context_id: {:?})", - P::LANGUAGE, - self.id, - previous_context_id - ); + match previous_context_id { + Some(previous_context_id) => { + bevy::log::debug!( + "{}: script with id already has a context: {}", + P::LANGUAGE, + self.id + ); + self.reload_context(guard.clone(), handler_ctxt, previous_context_id); + } + None => { + let log_context = format!("{}: Loading script: {}", P::LANGUAGE, self.id); + + let new_context_id = (handler_ctxt.context_loading_settings.assigner.assign)( + &self.id, + &self.content, + &handler_ctxt.script_contexts, + ) + .unwrap_or_else(|| handler_ctxt.script_contexts.allocate_id()); + if handler_ctxt.script_contexts.contains(new_context_id) { + self.reload_context(guard, handler_ctxt, new_context_id); + } else { + // load new context + bevy::log::debug!("{}", log_context); + let ctxt = (ContextBuilder::

::load)( + handler_ctxt.context_loading_settings.loader.load, + &self.id, + &self.content, + &handler_ctxt.context_loading_settings.context_initializers, + &handler_ctxt + .context_loading_settings + .context_pre_handling_initializers, + guard.clone(), + &mut handler_ctxt.runtime_container.runtime, + ); - // closure to prevent early returns from yielding the context - self.execute(world, &mut res_ctxt, previous_context_id); + let mut ctxt = match ctxt { + Ok(ctxt) => ctxt, + Err(e) => { + handle_script_errors( + guard, + [e.with_context(log_context)].into_iter(), + ); + return; + } + }; + + self.run_on_load_callback(handler_ctxt, guard, &mut ctxt); + + if handler_ctxt + .script_contexts + .insert_with_id(new_context_id, ctxt) + .is_some() + { + bevy::log::warn!("{}: Context with id {} was not expected to exist. Overwriting it with a new context. This might happen if a script is not completely removed.", P::LANGUAGE, new_context_id); + } + } - yield_handler_context(world, res_ctxt); + handler_ctxt.scripts.scripts.insert( + self.id.clone(), + Script { + id: self.id, + asset: self.asset, + context_id: new_context_id, + }, + ); + } + } + }) } } @@ -414,10 +384,10 @@ mod test { Ok(()) }], }) - .insert_non_send_resource(ScriptContexts:: { + .insert_resource(ScriptContexts:: { contexts: Default::default(), }) - .insert_non_send_resource(RuntimeContainer:: { + .insert_resource(RuntimeContainer:: { runtime: "Runtime".to_string(), }) .init_resource::() @@ -447,9 +417,7 @@ mod test { } fn assert_context_and_script(world: &World, id: &str, context: &str) { - let contexts = world - .get_non_send_resource::>() - .unwrap(); + let contexts = world.get_resource::>().unwrap(); let scripts = world.get_resource::().unwrap(); let script = scripts.scripts.get(id).expect("Script not found"); @@ -515,9 +483,7 @@ mod test { let scripts = world.get_resource::().unwrap(); assert!(scripts.scripts.is_empty()); - let contexts = world - .get_non_send_resource::>() - .unwrap(); + let contexts = world.get_resource::>().unwrap(); assert!(contexts.contexts.is_empty()); } @@ -585,7 +551,7 @@ mod test { // check one context exists only let context = app .world() - .get_non_send_resource::>() + .get_resource::>() .unwrap(); assert!(context.contexts.len() == 1); @@ -614,7 +580,7 @@ mod test { let contexts = app .world() - .get_non_send_resource::>() + .get_resource::>() .unwrap(); assert!(contexts.contexts.len() == 1); diff --git a/crates/bevy_mod_scripting_core/src/context.rs b/crates/bevy_mod_scripting_core/src/context.rs index 79a225233f..8d907a4f34 100644 --- a/crates/bevy_mod_scripting_core/src/context.rs +++ b/crates/bevy_mod_scripting_core/src/context.rs @@ -2,16 +2,16 @@ use crate::{ bindings::{ThreadWorldContainer, WorldContainer, WorldGuard}, - error::ScriptError, + error::{InteropError, ScriptError}, script::{Script, ScriptId}, IntoScriptPluginParams, }; -use bevy::ecs::{entity::Entity, system::Resource, world::World}; +use bevy::ecs::{entity::Entity, system::Resource}; use std::{collections::HashMap, sync::atomic::AtomicU32}; /// A trait that all script contexts must implement. -pub trait Context: 'static {} -impl Context for T {} +pub trait Context: 'static + Send + Sync {} +impl Context for T {} /// The type of a context id pub type ContextId = u32; @@ -92,6 +92,17 @@ pub struct ContextLoadingSettings { pub context_pre_handling_initializers: Vec>, } +impl Default for ContextLoadingSettings

{ + fn default() -> Self { + Self { + loader: ContextBuilder::default(), + assigner: ContextAssigner::default(), + context_initializers: Default::default(), + context_pre_handling_initializers: Default::default(), + } + } +} + impl Clone for ContextLoadingSettings { fn clone(&self) -> Self { Self { @@ -129,6 +140,17 @@ pub struct ContextBuilder { pub reload: ContextReloadFn

, } +impl Default for ContextBuilder

{ + fn default() -> Self { + Self { + load: |_, _, _, _, _| Err(InteropError::invariant("no context loader set").into()), + reload: |_, _, _, _, _, _| { + Err(InteropError::invariant("no context reloader set").into()) + }, + } + } +} + impl ContextBuilder

{ /// load a context pub fn load( @@ -137,10 +159,10 @@ impl ContextBuilder

{ content: &[u8], context_initializers: &[ContextInitializer

], pre_handling_initializers: &[ContextPreHandlingInitializer

], - world: &mut World, + world: WorldGuard, runtime: &mut P::R, ) -> Result { - WorldGuard::with_static_guard(world, |world| { + WorldGuard::with_existing_static_guard(world.clone(), |world| { ThreadWorldContainer.set_world(world)?; (loader)( script, @@ -160,10 +182,10 @@ impl ContextBuilder

{ previous_context: &mut P::C, context_initializers: &[ContextInitializer

], pre_handling_initializers: &[ContextPreHandlingInitializer

], - world: &mut World, + world: WorldGuard, runtime: &mut P::R, ) -> Result<(), ScriptError> { - WorldGuard::with_static_guard(world, |world| { + WorldGuard::with_existing_static_guard(world, |world| { ThreadWorldContainer.set_world(world)?; (reloader)( script, diff --git a/crates/bevy_mod_scripting_core/src/error.rs b/crates/bevy_mod_scripting_core/src/error.rs index 6b5c67bbbc..ad1d871da6 100644 --- a/crates/bevy_mod_scripting_core/src/error.rs +++ b/crates/bevy_mod_scripting_core/src/error.rs @@ -1,11 +1,15 @@ //! Errors that can occur when interacting with the scripting system -use crate::bindings::{ - access_map::{DisplayCodeLocation, ReflectAccessId}, - function::namespace::Namespace, - pretty_print::DisplayWithWorld, - script_value::ScriptValue, - ReflectBaseType, ReflectReference, +use crate::{ + bindings::{ + access_map::{DisplayCodeLocation, ReflectAccessId}, + function::namespace::Namespace, + pretty_print::DisplayWithWorld, + script_value::ScriptValue, + ReflectBaseType, ReflectReference, + }, + context::ContextId, + script::ScriptId, }; use bevy::{ ecs::component::ComponentId, @@ -51,9 +55,9 @@ pub struct ScriptErrorInner { /// The kind of error that occurred pub enum ErrorKind { /// An error that can be displayed - Display(Box), + Display(Box), /// An error that can be displayed with a world - WithWorld(Box), + WithWorld(Box), } impl DisplayWithWorld for ErrorKind { @@ -85,6 +89,21 @@ impl PartialEq for ScriptErrorInner { } impl ScriptError { + /// Tried to downcast a script error to an interop error + pub fn downcast_interop_inner(&self) -> Option<&InteropErrorInner> { + match self.reason.as_ref() { + ErrorKind::WithWorld(display_with_world) => { + let any: &dyn DisplayWithWorld = display_with_world.as_ref(); + if let Some(interop_error) = any.downcast_ref::() { + Some(interop_error.inner()) + } else { + None + } + } + _ => None, + } + } + #[cfg(feature = "mlua_impls")] /// Destructures mlua error into a script error, taking care to preserve as much information as possible pub fn from_mlua_error(error: mlua::Error) -> Self { @@ -511,11 +530,11 @@ impl InteropError { } /// Thrown when an invalid access count is detected - pub fn invalid_access_count(count: usize, expected: usize, context: String) -> Self { + pub fn invalid_access_count(count: usize, expected: usize, context: impl Display) -> Self { Self(Arc::new(InteropErrorInner::InvalidAccessCount { count, expected, - context, + context: context.to_string(), })) } @@ -556,15 +575,40 @@ impl InteropError { got, })) } + + /// Thrown if a script could not be found when trying to call a synchronous callback or otherwise + pub fn missing_script(script_id: impl Into) -> Self { + Self(Arc::new(InteropErrorInner::MissingScript { + script_id: script_id.into(), + })) + } + + /// Thrown if the required context for an operation is missing. + pub fn missing_context(context_id: ContextId, script_id: impl Into) -> Self { + Self(Arc::new(InteropErrorInner::MissingContext { + context_id, + script_id: script_id.into(), + })) + } + + /// Returns the inner error + pub fn inner(&self) -> &InteropErrorInner { + &self.0 + } } /// For errors to do with reflection, type conversions or other interop issues #[derive(Debug)] -pub(crate) enum InteropErrorInner { +pub enum InteropErrorInner { /// Thrown if a callback requires world access, but is unable to do so due StaleWorldAccess, /// Thrown if a callback requires world access, but is unable to do so due MissingWorld, + /// Thrown if a script could not be found when trying to call a synchronous callback. + MissingScript { + /// The script id that was not found. + script_id: ScriptId, + }, /// Thrown if a base type is not registered with the reflection system UnregisteredBase { /// The base type that was not registered @@ -732,17 +776,47 @@ pub(crate) enum InteropErrorInner { }, /// New variant for invalid enum variant errors. InvalidEnumVariant { + /// the enum type id type_id: TypeId, + /// the variant variant_name: String, }, /// Thrown when the number of arguments in a function call does not match. - ArgumentCountMismatch { expected: usize, got: usize }, + ArgumentCountMismatch { + /// The number of arguments that were expected + expected: usize, + /// The number of arguments that were received + got: usize, + }, + /// Thrown if the required context for an operation is missing. + MissingContext { + /// The context that was missing + context_id: ContextId, + /// The script that was attempting to access the context + script_id: ScriptId, + }, } /// For test purposes impl PartialEq for InteropErrorInner { fn eq(&self, _other: &Self) -> bool { match (self, _other) { + ( + InteropErrorInner::MissingScript { script_id: a }, + InteropErrorInner::MissingScript { script_id: b }, + ) => a == b, + ( + InteropErrorInner::InvalidAccessCount { + count: a, + expected: b, + context: c, + }, + InteropErrorInner::InvalidAccessCount { + count: d, + expected: e, + context: f, + }, + ) => a == d && b == e && c == f, (InteropErrorInner::StaleWorldAccess, InteropErrorInner::StaleWorldAccess) => true, (InteropErrorInner::MissingWorld, InteropErrorInner::MissingWorld) => true, ( @@ -951,6 +1025,16 @@ impl PartialEq for InteropErrorInner { got: d, }, ) => a == c && b == d, + ( + InteropErrorInner::MissingContext { + context_id: a, + script_id: b, + }, + InteropErrorInner::MissingContext { + context_id: c, + script_id: d, + }, + ) => a == c && b == d, _ => false, } } @@ -1139,6 +1223,44 @@ macro_rules! unregistered_component_or_resource_type { }; } +macro_rules! missing_script_for_callback { + ($script_id:expr) => { + format!( + "Could not find script with id: {}. Is the script loaded?", + $script_id + ) + }; +} + +// Define a single macro for the invalid enum variant error. +macro_rules! invalid_enum_variant_msg { + ($variant:expr, $enum_display:expr) => { + format!( + "Invalid enum variant: {} for enum: {}", + $variant, $enum_display + ) + }; +} + +// Define a single macro for the argument count mismatch error. +macro_rules! argument_count_mismatch_msg { + ($expected:expr, $got:expr) => { + format!( + "Argument count mismatch, expected: {}, got: {}", + $expected, $got + ) + }; +} + +macro_rules! missing_context_for_callback { + ($context_id:expr, $script_id:expr) => { + format!( + "Missing context with id: {} for script with id: {}. Was the script loaded?.", + $context_id, $script_id + ) + }; +} + impl DisplayWithWorld for InteropErrorInner { fn display_with_world(&self, world: crate::bindings::WorldGuard) -> String { match self { @@ -1229,7 +1351,7 @@ impl DisplayWithWorld for InteropErrorInner { .to_owned() } InteropErrorInner::MissingWorld => { - "Missing world. The world was not initialized in the script context.".to_owned() + "Missing world. The world was either not initialized, or invalidated.".to_owned() }, InteropErrorInner::FunctionInteropError { function_name, on, error } => { let opt_on = match on { @@ -1266,16 +1388,18 @@ impl DisplayWithWorld for InteropErrorInner { missing_data_in_constructor!(type_id.display_with_world(world), missing_data_name) }, InteropErrorInner::InvalidEnumVariant { type_id, variant_name } => { - format!( - "Invalid enum variant: {} for enum: {}", - variant_name, - type_id.display_with_world(world) - ) + invalid_enum_variant_msg!(variant_name, type_id.display_with_world(world)) }, InteropErrorInner::ArgumentCountMismatch { expected, got } => { - format!( - "Argument count mismatch, expected: {}, got: {}", - expected, got + argument_count_mismatch_msg!(expected, got) + }, + InteropErrorInner::MissingScript { script_id } => { + missing_script_for_callback!(script_id) + }, + InteropErrorInner::MissingContext { context_id, script_id } => { + missing_context_for_callback!( + context_id, + script_id ) }, } @@ -1371,7 +1495,7 @@ impl DisplayWithWorld for InteropErrorInner { .to_owned() } InteropErrorInner::MissingWorld => { - "Missing world. The world was not initialized in the script context.".to_owned() + "Missing world. The world was either not initialized, or invalidated.".to_owned() }, InteropErrorInner::FunctionInteropError { function_name, on, error } => { let opt_on = match on { @@ -1408,16 +1532,18 @@ impl DisplayWithWorld for InteropErrorInner { missing_data_in_constructor!(type_id.display_without_world(), missing_data_name) }, InteropErrorInner::InvalidEnumVariant { type_id, variant_name } => { - format!( - "Invalid enum variant: {} for enum: {}", - variant_name, - type_id.display_without_world() - ) + invalid_enum_variant_msg!(variant_name, type_id.display_without_world()) }, InteropErrorInner::ArgumentCountMismatch { expected, got } => { - format!( - "Argument count mismatch, expected: {}, got: {}", - expected, got + argument_count_mismatch_msg!(expected, got) + }, + InteropErrorInner::MissingScript { script_id } => { + missing_script_for_callback!(script_id) + }, + InteropErrorInner::MissingContext { context_id, script_id } => { + missing_context_for_callback!( + context_id, + script_id ) }, } diff --git a/crates/bevy_mod_scripting_core/src/extractors.rs b/crates/bevy_mod_scripting_core/src/extractors.rs index 02616d426d..fa7aaeb7e7 100644 --- a/crates/bevy_mod_scripting_core/src/extractors.rs +++ b/crates/bevy_mod_scripting_core/src/extractors.rs @@ -1,72 +1,532 @@ //! Systems which are used to extract the various resources and components used by BMS. //! //! These are designed to be used to pipe inputs into other systems which require them, while handling any configuration erorrs nicely. +#![allow(deprecated)] +use std::ops::{Deref, DerefMut}; -use bevy::prelude::World; +use bevy::ecs::{ + component::ComponentId, + entity::Entity, + event::{Event, EventCursor, EventIterator, Events}, + query::{Access, AccessConflicts}, + storage::SparseSetIndex, + system::{Local, Resource, SystemParam, SystemState}, + world::World, +}; +use fixedbitset::FixedBitSet; use crate::{ + bindings::{ + access_map::ReflectAccessId, pretty_print::DisplayWithWorld, script_value::ScriptValue, + WorldAccessGuard, WorldGuard, + }, context::{ContextLoadingSettings, ScriptContexts}, - error::MissingResourceError, + error::{InteropError, ScriptError}, + event::IntoCallbackLabel, handler::CallbackSettings, runtime::RuntimeContainer, - script::{Scripts, StaticScripts}, + script::{ScriptId, Scripts, StaticScripts}, IntoScriptPluginParams, }; +/// Executes `system_state.get_mut` followed by `system_state.apply` after running the given closure, makes sure state is correctly handled in the context of an exclusive system. +/// Using system state with a handler ctxt without applying the state after will leave the world in an inconsistent state. +pub fn with_handler_system_state< + P: IntoScriptPluginParams, + F: FnOnce(WorldGuard, &mut HandlerContext

) -> O, + O, +>( + world: &mut World, + f: F, +) -> O { + let mut system_state: SystemState>> = SystemState::new(world); + let mut with_guard = system_state.get_mut(world); + let (guard, handler_ctxt) = with_guard.get_mut(); + let o = f(guard, handler_ctxt); + system_state.apply(world); + o +} + +/// Semantics of [`bevy::ecs::change_detection::Res`] but doesn't claim read or write on the world by removing the resource from it ahead of time. +/// +/// Similar to using [`World::resource_scope`]. +/// +/// This is useful for interacting with scripts, since [`WithWorldGuard`] will ensure scripts cannot gain exclusive access to the world if *any* reads or writes +/// are claimed on the world. Removing the resource from the world lets you access it in the context of running scripts without blocking exclusive world access. +/// +/// # Safety +/// - Because the resource is removed during the `get_param` call, if there is a conflicting resource access, this will be unsafe +/// - You must ensure you're only using this in combination with system parameters which will not read or write to this resource in `get_param` +pub(crate) struct ResScope<'state, T: Resource + Default>(pub &'state mut T); + +impl Deref for ResScope<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl DerefMut for ResScope<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0 + } +} + +unsafe impl SystemParam for ResScope<'_, T> { + type State = (T, bool); + + type Item<'world, 'state> = ResScope<'state, T>; + + fn init_state( + _world: &mut World, + system_meta: &mut bevy::ecs::system::SystemMeta, + ) -> Self::State { + system_meta.set_has_deferred(); + (T::default(), false) + } + + unsafe fn get_param<'world, 'state>( + state: &'state mut Self::State, + _system_meta: &bevy::ecs::system::SystemMeta, + world: bevy::ecs::world::unsafe_world_cell::UnsafeWorldCell<'world>, + _change_tick: bevy::ecs::component::Tick, + ) -> Self::Item<'world, 'state> { + state.1 = true; + if let Some(mut r) = world.get_resource_mut::() { + std::mem::swap(&mut state.0, &mut r); + } + ResScope(&mut state.0) + } + + fn apply( + state: &mut Self::State, + _system_meta: &bevy::ecs::system::SystemMeta, + world: &mut bevy::ecs::world::World, + ) { + if state.1 { + world.insert_resource(std::mem::take(&mut state.0)); + state.1 = false; + } + } +} + +/// A version of [`bevy::ecs::event::EventReader`] which behaves just like [`ResScope`]. +/// +/// # Safety +/// - unsafe to use this in a way which violates the invariants on [`ResScope`]. +/// - This is hidden from docs for a reason, rust doesn't allow expressing `type signature unsafety` +/// - It is only safe to use when other system parameters do not create aliasing references inside their `get_param` calls +#[derive(SystemParam)] +#[doc(hidden)] +#[deprecated(note = "This type is unsafe to use in systems")] +pub struct EventReaderScope<'s, T: Event> { + events: ResScope<'s, Events>, + reader: Local<'s, EventCursor>, +} + +#[allow(deprecated)] +impl EventReaderScope<'_, T> { + /// Read all events that happened since the last read + pub fn read(&mut self) -> EventIterator<'_, T> { + self.reader.read(&self.events) + } +} + /// Context for systems which handle events for scripts -pub(crate) struct HandlerContext { - pub callback_settings: CallbackSettings

, - pub context_loading_settings: ContextLoadingSettings

, - pub scripts: Scripts, - pub runtime_container: RuntimeContainer

, - pub script_contexts: ScriptContexts

, - pub static_scripts: StaticScripts, +#[derive(SystemParam)] +pub struct HandlerContext<'s, P: IntoScriptPluginParams> { + /// Settings for callbacks + pub(crate) callback_settings: ResScope<'s, CallbackSettings

>, + /// Settings for loading contexts + pub(crate) context_loading_settings: ResScope<'s, ContextLoadingSettings

>, + /// Scripts + pub(crate) scripts: ResScope<'s, Scripts>, + /// The runtime container + pub(crate) runtime_container: ResScope<'s, RuntimeContainer

>, + /// The script contexts + pub(crate) script_contexts: ResScope<'s, ScriptContexts

>, + /// List of static scripts + pub(crate) static_scripts: ResScope<'s, StaticScripts>, } -#[profiling::function] -pub(crate) fn extract_handler_context( - world: &mut World, -) -> Result, MissingResourceError> { - // we don't worry about re-inserting these resources if we fail to extract them, as the plugin is misconfigured anyway, - // so the only solution is to stop the program and fix the configuration - // the config is either all in or nothing - - let callback_settings = world - .remove_resource::>() - .ok_or_else(MissingResourceError::new::>)?; - let context_loading_settings = world - .remove_resource::>() - .ok_or_else(MissingResourceError::new::>)?; - let scripts = world - .remove_resource::() - .ok_or_else(MissingResourceError::new::)?; - let runtime_container = world - .remove_non_send_resource::>() - .ok_or_else(MissingResourceError::new::>)?; - let script_contexts = world - .remove_non_send_resource::>() - .ok_or_else(MissingResourceError::new::>)?; - let static_scripts = world - .remove_resource::() - .ok_or_else(MissingResourceError::new::)?; - - Ok(HandlerContext { - callback_settings, - context_loading_settings, - scripts, - runtime_container, - script_contexts, - static_scripts, - }) + +impl HandlerContext<'_, P> { + /// Splits the handler context into its individual components. + /// + /// Useful if you are needing multiple resources from the handler context. + /// Otherwise the borrow checker will prevent you from borrowing the handler context mutably multiple times. + pub fn destructure( + &mut self, + ) -> ( + &mut CallbackSettings

, + &mut ContextLoadingSettings

, + &mut Scripts, + &mut RuntimeContainer

, + &mut ScriptContexts

, + &mut StaticScripts, + ) { + ( + &mut self.callback_settings, + &mut self.context_loading_settings, + &mut self.scripts, + &mut self.runtime_container, + &mut self.script_contexts, + &mut self.static_scripts, + ) + } + + /// Get the callback settings + pub fn callback_settings(&mut self) -> &mut CallbackSettings

{ + &mut self.callback_settings + } + + /// Get the context loading settings + pub fn context_loading_settings(&mut self) -> &mut ContextLoadingSettings

{ + &mut self.context_loading_settings + } + + /// Get the scripts + pub fn scripts(&mut self) -> &mut Scripts { + &mut self.scripts + } + + /// Get the runtime container + pub fn runtime_container(&mut self) -> &mut RuntimeContainer

{ + &mut self.runtime_container + } + + /// Get the script contexts + pub fn script_contexts(&mut self) -> &mut ScriptContexts

{ + &mut self.script_contexts + } + + /// Get the static scripts + pub fn static_scripts(&mut self) -> &mut StaticScripts { + &mut self.static_scripts + } + + /// checks if the script is loaded such that it can be executed. + pub fn is_script_fully_loaded(&self, script_id: ScriptId) -> bool { + // check script exists in scripts and contexts + let script = match self.scripts.scripts.get(&script_id) { + Some(script) => script, + None => { + return false; + } + }; + + self.script_contexts + .contexts + .contains_key(&script.context_id) + } + + /// Invoke a callback in a script immediately. + /// + /// This will return [`crate::error::InteropErrorInner::MissingScript`] or [`crate::error::InteropErrorInner::MissingContext`] errors while the script is loading. + /// Run [`Self::is_script_fully_loaded`] before calling the script to ensure the script and context were loaded ahead of time. + pub fn call( + &mut self, + script_id: ScriptId, + entity: Entity, + payload: Vec, + guard: WorldGuard<'_>, + ) -> Result { + // find script + let script = match self.scripts.scripts.get(&script_id) { + Some(script) => script, + None => return Err(InteropError::missing_script(script_id).into()), + }; + + // find context + let context = match self.script_contexts.contexts.get_mut(&script.context_id) { + Some(context) => context, + None => return Err(InteropError::missing_context(script.context_id, script_id).into()), + }; + + // call the script + let handler = self.callback_settings.callback_handler; + let pre_handling_initializers = &self + .context_loading_settings + .context_pre_handling_initializers; + let runtime = &mut self.runtime_container.runtime; + CallbackSettings::

::call( + handler, + payload, + entity, + &script_id, + &C::into_callback_label(), + context, + pre_handling_initializers, + runtime, + guard, + ) + } } -#[profiling::function] -pub(crate) fn yield_handler_context( - world: &mut World, - context: HandlerContext

, -) { - world.insert_resource(context.callback_settings); - world.insert_resource(context.context_loading_settings); - world.insert_resource(context.scripts); - world.insert_non_send_resource(context.runtime_container); - world.insert_non_send_resource(context.script_contexts); - world.insert_resource(context.static_scripts); + +/// A wrapper around a world which pre-populates access, to safely co-exist with other system params, +/// acts exactly like `&mut World` so this should be your only top-level system param +/// +/// The reason is the guard needs to know the underlying access that +pub struct WithWorldGuard<'w, 's, T: SystemParam> { + world_guard: WorldGuard<'w>, + param: T::Item<'w, 's>, +} + +impl<'w, 's, T: SystemParam> WithWorldGuard<'w, 's, T> { + /// Get the world guard and the inner system param + pub fn get(&self) -> (WorldGuard<'w>, &T::Item<'w, 's>) { + (self.world_guard.clone(), &self.param) + } + + /// Get the world guard and the inner system param mutably + pub fn get_mut(&mut self) -> (WorldGuard<'w>, &mut T::Item<'w, 's>) { + (self.world_guard.clone(), &mut self.param) + } +} + +unsafe impl SystemParam for WithWorldGuard<'_, '_, T> { + type State = (T::State, Vec<(ReflectAccessId, bool)>); + + type Item<'world, 'state> = WithWorldGuard<'world, 'state, T>; + + fn init_state( + world: &mut bevy::ecs::world::World, + system_meta: &mut bevy::ecs::system::SystemMeta, + ) -> Self::State { + // verify there are no accesses previously + let other_accessed_components = + system_meta.component_access_set().combined_access().clone(); + + let inner_state = T::init_state(world, system_meta); + + let accessed_components = system_meta.component_access_set().combined_access(); + let access_ids = get_all_access_ids(accessed_components); + let other_access_ids = get_all_access_ids(&other_accessed_components); + + // reason: we can't handle this error nicely, and continuing is a safety issue + #[allow(clippy::panic)] + if !other_access_ids.is_empty() { + panic!( + "WithWorldGuard must be the only top-level system param, cannot run system: `{}`", + system_meta.name() + ); + } + + // Safety: not removing any accesses + unsafe { system_meta.component_access_set_mut().write_all() } + unsafe { system_meta.archetype_component_access_mut().write_all() } + (inner_state, access_ids) + } + + unsafe fn get_param<'world, 'state>( + state: &'state mut Self::State, + system_meta: &bevy::ecs::system::SystemMeta, + world: bevy::ecs::world::unsafe_world_cell::UnsafeWorldCell<'world>, + change_tick: bevy::ecs::component::Tick, + ) -> Self::Item<'world, 'state> { + // read components being read in the state, and lock those accesses in the new world access guard + let guard = WorldAccessGuard::new(world.world_mut()); + + #[allow( + clippy::panic, + reason = "This API does not allow us to handle this error nicely, and continuing is a safety issue." + )] + for (raid, is_write) in &state.1 { + if *is_write { + if !guard.claim_write_access(*raid) { + panic!("System tried to access set of system params which break rust aliasing rules. Aliasing access: {}", (*raid).display_with_world(guard.clone())); + } + } else if !guard.claim_read_access(*raid) { + panic!("System tried to access set of system params which break rust aliasing rules. Aliasing access: {}", (*raid).display_with_world(guard.clone())); + } + } + + WithWorldGuard { + world_guard: guard, + param: T::get_param(&mut state.0, system_meta, world, change_tick), + } + } + + unsafe fn new_archetype( + state: &mut Self::State, + archetype: &bevy::ecs::archetype::Archetype, + system_meta: &mut bevy::ecs::system::SystemMeta, + ) { + T::new_archetype(&mut state.0, archetype, system_meta) + } + + fn apply( + state: &mut Self::State, + system_meta: &bevy::ecs::system::SystemMeta, + world: &mut World, + ) { + T::apply(&mut state.0, system_meta, world) + } + + fn queue( + state: &mut Self::State, + system_meta: &bevy::ecs::system::SystemMeta, + world: bevy::ecs::world::DeferredWorld, + ) { + T::queue(&mut state.0, system_meta, world) + } + + unsafe fn validate_param( + state: &Self::State, + system_meta: &bevy::ecs::system::SystemMeta, + world: bevy::ecs::world::unsafe_world_cell::UnsafeWorldCell, + ) -> bool { + T::validate_param(&state.0, system_meta, world) + } +} + +fn individual_conflicts(conflicts: AccessConflicts) -> FixedBitSet { + match conflicts { + // todo, not sure what to do here + AccessConflicts::All => FixedBitSet::new(), + AccessConflicts::Individual(fixed_bit_set) => fixed_bit_set, + } +} + +fn get_all_access_ids(access: &Access) -> Vec<(ReflectAccessId, bool)> { + let mut access_all_read = Access::::default(); + access_all_read.read_all(); + + let mut access_all_write = Access::::default(); + access_all_write.write_all(); + + // read conflicts with each set to figure out the necessary locks + + let mut read = individual_conflicts(access.get_conflicts(&access_all_read)); + let written = individual_conflicts(access.get_conflicts(&access_all_write)); + + // remove reads from writes + read.difference_with(&written); + + let mut result = Vec::new(); + for c in read.ones() { + result.push(( + ReflectAccessId::for_component_id(ComponentId::get_sparse_set_index(c)), + false, + )); + } + for c in written.ones() { + result.push(( + ReflectAccessId::for_component_id(ComponentId::get_sparse_set_index(c)), + true, + )); + } + + result +} + +#[cfg(test)] +mod test { + use bevy::{ + app::{App, Update}, + ecs::{ + component::Component, + event::{Event, EventReader}, + system::{Query, ResMut, Resource}, + world::FromWorld, + }, + }; + use test_utils::make_test_plugin; + + use super::*; + + make_test_plugin!(crate); + + #[derive(Component)] + struct Comp; + + #[derive(Resource, Default)] + struct Res; + + #[test] + pub fn check_with_world_correctly_locks_resource_and_component() { + let system_fn = |mut guard: WithWorldGuard<(ResMut, Query<&'static Comp>)>| { + let (guard, (_res, _entity)) = guard.get_mut(); + assert_eq!(guard.list_accesses().len(), 2, "Expected 2 accesses"); + assert!(!guard.claim_read_access( + ReflectAccessId::for_resource::(&guard.as_unsafe_world_cell().unwrap()) + .unwrap() + )); + assert!(!guard.claim_write_access( + ReflectAccessId::for_resource::(&guard.as_unsafe_world_cell().unwrap()) + .unwrap() + )); + }; + + let mut app = bevy::app::App::new(); + app.add_systems(Update, system_fn); + app.insert_resource(Res); + app.world_mut().spawn(Comp); + + app.cleanup(); + app.finish(); + app.update(); + } + + #[test] + #[should_panic( + expected = "WithWorldGuard must be the only top-level system param, cannot run system" + )] + pub fn check_with_world_panics_when_used_with_resource_top_level() { + let system_fn = |_res: ResMut, mut _guard: WithWorldGuard>| {}; + + let mut app = bevy::app::App::new(); + app.add_systems(Update, system_fn); + app.insert_resource(Res); + app.world_mut().spawn(Comp); + + app.cleanup(); + app.finish(); + app.update(); + } + + #[test] + #[should_panic( + expected = "WithWorldGuard must be the only top-level system param, cannot run system" + )] + pub fn check_with_world_panics_when_used_with_event_reader_top_level() { + #[derive(Event)] + struct TestEvent; + let system_fn = + |_res: EventReader, mut _guard: WithWorldGuard>| {}; + + let mut app = bevy::app::App::new(); + app.add_systems(Update, system_fn); + app.insert_resource(Res); + app.world_mut().spawn(Comp); + + app.cleanup(); + app.finish(); + app.update(); + } + + #[test] + pub fn resscope_reinserts_resource() { + // apply deffered system should be inserted after the system automatically + let mut app = App::new(); + + app.insert_resource(Res); + app.add_systems(Update, |_: ResScope| {}); + + app.update(); + + // check the resources are re-inserted + assert!(app.world().contains_resource::()); + } + + #[test] + pub fn rescope_does_not_remove_until_system_call() { + let mut world = World::new(); + world.insert_resource(Res); + + // this will call init, and that should't remove the resource + assert!(world.contains_resource::()); + SystemState::>::from_world(&mut world); + assert!(world.contains_resource::()); + } } diff --git a/crates/bevy_mod_scripting_core/src/handler.rs b/crates/bevy_mod_scripting_core/src/handler.rs index 5d1cf77d93..d47ccab9bd 100644 --- a/crates/bevy_mod_scripting_core/src/handler.rs +++ b/crates/bevy_mod_scripting_core/src/handler.rs @@ -1,25 +1,26 @@ //! Contains the logic for handling script callback events - +#[allow(deprecated)] use crate::{ bindings::{ pretty_print::DisplayWithWorld, script_value::ScriptValue, ThreadWorldContainer, WorldContainer, WorldGuard, }, context::ContextPreHandlingInitializer, - error::ScriptError, + error::{InteropErrorInner, ScriptError}, event::{CallbackLabel, IntoCallbackLabel, ScriptCallbackEvent, ScriptErrorEvent}, - extractors::{extract_handler_context, yield_handler_context, HandlerContext}, + extractors::{EventReaderScope, HandlerContext, WithWorldGuard}, script::{ScriptComponent, ScriptId}, IntoScriptPluginParams, }; use bevy::{ ecs::{ entity::Entity, - system::{Resource, SystemState}, - world::World, + query::QueryState, + system::{Local, Resource, SystemState}, + world::{Mut, World}, }, log::trace_once, - prelude::{EventReader, Events, Query, Ref}, + prelude::{Events, Ref}, }; /// A function that handles a callback event @@ -40,6 +41,14 @@ pub struct CallbackSettings { pub callback_handler: HandlerFn

, } +impl Default for CallbackSettings

{ + fn default() -> Self { + Self { + callback_handler: |_, _, _, _, _, _, _| Ok(ScriptValue::Unit), + } + } +} + impl Clone for CallbackSettings

{ fn clone(&self) -> Self { Self { @@ -65,9 +74,9 @@ impl CallbackSettings

{ script_ctxt: &mut P::C, pre_handling_initializers: &[ContextPreHandlingInitializer

], runtime: &mut P::R, - world: &mut World, + world: WorldGuard, ) -> Result { - WorldGuard::with_static_guard(world, |world| { + WorldGuard::with_existing_static_guard(world.clone(), |world| { ThreadWorldContainer.set_world(world)?; (handler)( args, @@ -94,40 +103,72 @@ macro_rules! push_err_and_continue { }; } -/// A utility to separate the event handling logic from the retrieval of the handler context -#[profiling::function] -pub(crate) fn event_handler_internal( +/// Passes events with the specified label to the script callback with the same name and runs the callback. +/// +/// If any of the resources required for the handler are missing, the system will log this issue and do nothing. +#[allow(deprecated)] +pub fn event_handler( world: &mut World, - res_ctxt: &mut HandlerContext

, - params: &mut SystemState<( - EventReader, - Query<(Entity, Ref)>, + state: &mut SystemState<( + Local)>>, + EventReaderScope, + WithWorldGuard>, )>, ) { - let (mut script_events, entities) = params.get_mut(world); + // we wrap the inner event handler, so that we can immediately re-insert all the resources back. + // otherwise this would happen in the next schedule + { + let (entity_query_state, script_events, handler_ctxt) = state.get_mut(world); + event_handler_inner::(entity_query_state, script_events, handler_ctxt); + } + state.apply(world); +} + +#[profiling::function] +#[allow(deprecated)] +fn event_handler_inner( + mut entity_query_state: Local)>>, + mut script_events: EventReaderScope, + mut handler_ctxt: WithWorldGuard>, +) { + let (guard, handler_ctxt) = handler_ctxt.get_mut(); let mut errors = Vec::default(); let events = script_events.read().cloned().collect::>(); - let entity_scripts = entities - .iter() - .map(|(e, s)| (e, s.0.clone())) - .chain( - // on top of script components we also want to run static scripts - // semantically these are just scripts with no entity, in our case we use an invalid entity index 0 - res_ctxt - .static_scripts - .scripts - .iter() - .map(|s| (Entity::from_raw(0), vec![s.clone()])), - ) - .collect::>(); + + // query entities + chain static scripts + let entity_and_static_scripts = guard.with_global_access(|world| { + entity_query_state + .iter(world) + .map(|(e, s)| (e, s.0.clone())) + .chain( + handler_ctxt + .static_scripts + .scripts + .iter() + .map(|s| (Entity::from_raw(0), vec![s.clone()])), + ) + .collect::>() + }); + + let entity_and_static_scripts = match entity_and_static_scripts { + Ok(entity_and_static_scripts) => entity_and_static_scripts, + Err(e) => { + bevy::log::error!( + "{}: Failed to query entities with scripts: {}", + P::LANGUAGE, + e.display_with_world(guard.clone()) + ); + return; + } + }; for event in events .into_iter() .filter(|e| e.label == L::into_callback_label()) { - for (entity, entity_scripts) in entity_scripts.iter() { + for (entity, entity_scripts) in entity_and_static_scripts.iter() { for script_id in entity_scripts.iter() { match &event.recipients { crate::event::Recipients::Script(target_script_id) @@ -143,102 +184,66 @@ pub(crate) fn event_handler_internal (), + _ => {} } - let script = match res_ctxt.scripts.scripts.get(script_id) { - Some(s) => s, - None => { - trace_once!( - "{}: Script `{}` on entity `{:?}` is either still loading or doesn't exist, ignoring until the corresponding script is loaded.", - P::LANGUAGE, - script_id, entity - ); - continue; - } - }; - let ctxt = match res_ctxt - .script_contexts - .contexts - .get_mut(&script.context_id) - { - Some(ctxt) => ctxt, - None => { - // if we don't have a context for the script, it's either: - // 1. a script for a different language, in which case we ignore it - // 2. something went wrong. This should not happen though and it's best we ignore this - continue; + let call_result = handler_ctxt.call::( + script_id.clone(), + *entity, + event.args.clone(), + guard.clone(), + ); + + match call_result { + Ok(_) => {} + Err(e) => { + match e.downcast_interop_inner() { + Some(InteropErrorInner::MissingScript { script_id }) => { + trace_once!( + "{}: Script `{}` on entity `{:?}` is either still loading or doesn't exist, ignoring until the corresponding script is loaded.", + P::LANGUAGE, + script_id, entity + ); + continue; + } + Some(InteropErrorInner::MissingContext { .. }) => { + // if we don't have a context for the script, it's either: + // 1. a script for a different language, in which case we ignore it + // 2. something went wrong. This should not happen though and it's best we ignore this + continue; + } + _ => {} + } + let e = e + .with_script(script_id.clone()) + .with_context(format!("Event handling for: Language: {}", P::LANGUAGE)); + push_err_and_continue!(errors, Err(e)); } }; - - let handler_result = (CallbackSettings::

::call)( - res_ctxt.callback_settings.callback_handler, - event.args.clone(), - *entity, - &script.id, - &L::into_callback_label(), - ctxt, - &res_ctxt - .context_loading_settings - .context_pre_handling_initializers, - &mut res_ctxt.runtime_container.runtime, - world, - ) - .map_err(|e| { - e.with_script(script.id.clone()) - .with_context(format!("Event handling for: Language: {}", P::LANGUAGE)) - }); - - let _ = push_err_and_continue!(errors, handler_result); } } } - handle_script_errors(world, errors.into_iter()); -} - -/// Passes events with the specified label to the script callback with the same name and runs the callback. -/// -/// If any of the resources required for the handler are missing, the system will log this issue and do nothing. -#[profiling::function] -pub fn event_handler( - world: &mut World, - params: &mut SystemState<( - EventReader, - Query<(Entity, Ref)>, - )>, -) { - let mut res_ctxt = match extract_handler_context::

(world) { - Ok(handler_context) => handler_context, - Err(e) => { - bevy::log::error_once!( - "Event handler for language `{}` will not run due to missing resource: {}", - P::LANGUAGE, - e - ); - return; - } - }; - - // this ensures the internal handler cannot early return without yielding the context - event_handler_internal::(world, &mut res_ctxt, params); - - yield_handler_context(world, res_ctxt); + handle_script_errors(guard, errors.into_iter()); } /// Handles errors caused by script execution and sends them to the error event channel -pub(crate) fn handle_script_errors + Clone>( - world: &mut World, - errors: I, -) { - let mut error_events = world.get_resource_or_init::>(); +pub fn handle_script_errors + Clone>(world: WorldGuard, errors: I) { + let err = world.with_resource_mut(|mut error_events: Mut>| { + for error in errors.clone() { + error_events.send(ScriptErrorEvent { error }); + } + }); - for error in errors.clone() { - error_events.send(ScriptErrorEvent { error }); + if let Err(err) = err { + bevy::log::error!( + "Failed to send script error events: {}", + err.display_with_world(world.clone()) + ); } + for error in errors { - let arc_world = WorldGuard::new(world); - bevy::log::error!("{}", error.display_with_world(arc_world)); + bevy::log::error!("{}", error.display_with_world(world.clone())); } } @@ -247,7 +252,13 @@ pub(crate) fn handle_script_errors + Clone>( mod test { use std::{borrow::Cow, collections::HashMap}; - use bevy::app::{App, Update}; + use bevy::{ + app::{App, Update}, + asset::AssetPlugin, + diagnostic::DiagnosticsPlugin, + ecs::world::FromWorld, + }; + use test_utils::make_test_plugin; use crate::{ bindings::script_value::ScriptValue, @@ -267,28 +278,7 @@ mod test { } } - struct TestPlugin; - - impl IntoScriptPluginParams for TestPlugin { - type C = TestContext; - type R = TestRuntime; - - const LANGUAGE: crate::asset::Language = crate::asset::Language::Unknown; - - fn build_runtime() -> Self::R { - TestRuntime { - invocations: vec![], - } - } - } - - struct TestRuntime { - pub invocations: Vec<(Entity, ScriptId)>, - } - - struct TestContext { - pub invocations: Vec, - } + make_test_plugin!(crate); fn setup_app( handler_fn: HandlerFn

, @@ -305,8 +295,8 @@ mod test { }); app.add_systems(Update, event_handler::); app.insert_resource::(Scripts { scripts }); - app.insert_non_send_resource(RuntimeContainer::

{ runtime }); - app.insert_non_send_resource(ScriptContexts::

{ contexts }); + app.insert_resource(RuntimeContainer::

{ runtime }); + app.insert_resource(ScriptContexts::

{ contexts }); app.init_resource::(); app.insert_resource(ContextLoadingSettings::

{ loader: ContextBuilder { @@ -367,11 +357,11 @@ mod test { let test_context = app .world() - .get_non_send_resource::>() + .get_resource::>() .unwrap(); let test_runtime = app .world() - .get_non_send_resource::>() + .get_resource::>() .unwrap(); assert_eq!( @@ -462,11 +452,11 @@ mod test { let test_context = app .world() - .get_non_send_resource::>() + .get_resource::>() .unwrap(); let test_runtime = app .world() - .get_non_send_resource::>() + .get_resource::>() .unwrap(); assert_eq!( @@ -548,7 +538,7 @@ mod test { let test_context = app .world() - .get_non_send_resource::>() + .get_resource::>() .unwrap(); assert_eq!( @@ -563,4 +553,31 @@ mod test { ] ); } + + #[test] + fn event_handler_reinserts_resources() { + let mut app = App::new(); + app.add_plugins(( + AssetPlugin::default(), + DiagnosticsPlugin, + TestPlugin::default(), + )); + + assert!(app + .world() + .contains_resource::>()); + + let mut local = SystemState::from_world(app.world_mut()); + + assert!(app + .world() + .contains_resource::>()); + + event_handler::(app.world_mut(), &mut local); + + assert!(app + .world() + .get_resource::>() + .is_some()); + } } diff --git a/crates/bevy_mod_scripting_core/src/lib.rs b/crates/bevy_mod_scripting_core/src/lib.rs index 2f556b19a2..afe9402e99 100644 --- a/crates/bevy_mod_scripting_core/src/lib.rs +++ b/crates/bevy_mod_scripting_core/src/lib.rs @@ -24,8 +24,6 @@ use handler::{CallbackSettings, HandlerFn}; use runtime::{initialize_runtime, Runtime, RuntimeContainer, RuntimeInitializer, RuntimeSettings}; use script::{ScriptId, Scripts, StaticScripts}; -mod extractors; - pub mod asset; pub mod bindings; pub mod commands; @@ -33,6 +31,7 @@ pub mod context; pub mod docgen; pub mod error; pub mod event; +pub mod extractors; pub mod handler; pub mod reflection_extensions; pub mod runtime; @@ -96,13 +95,28 @@ pub struct ScriptingPlugin { pub supported_extensions: &'static [&'static str], } +impl Default for ScriptingPlugin

{ + fn default() -> Self { + Self { + runtime_settings: Default::default(), + callback_handler: CallbackSettings::

::default().callback_handler, + context_builder: Default::default(), + context_assigner: Default::default(), + language_mapper: Default::default(), + context_initializers: Default::default(), + context_pre_handling_initializers: Default::default(), + supported_extensions: Default::default(), + } + } +} + impl Plugin for ScriptingPlugin

{ fn build(&self, app: &mut bevy::prelude::App) { app.insert_resource(self.runtime_settings.clone()) - .insert_non_send_resource::>(RuntimeContainer { + .insert_resource::>(RuntimeContainer { runtime: P::build_runtime(), }) - .init_non_send_resource::>() + .init_resource::>() .insert_resource::>(CallbackSettings { callback_handler: self.callback_handler, }) diff --git a/crates/bevy_mod_scripting_core/src/runtime.rs b/crates/bevy_mod_scripting_core/src/runtime.rs index eb3cb666a0..44230c944f 100644 --- a/crates/bevy_mod_scripting_core/src/runtime.rs +++ b/crates/bevy_mod_scripting_core/src/runtime.rs @@ -3,13 +3,13 @@ use crate::{error::ScriptError, IntoScriptPluginParams}; use bevy::{ - ecs::system::Resource, - prelude::{NonSendMut, Res}, + ecs::system::{ResMut, Resource}, + prelude::Res, }; /// A trait that all script runtimes must implement. -pub trait Runtime: 'static {} -impl Runtime for T {} +pub trait Runtime: Default + 'static + Send + Sync {} +impl Runtime for T {} /// A function that initializes a runtime. pub type RuntimeInitializer

= @@ -45,8 +45,16 @@ pub struct RuntimeContainer { pub runtime: P::R, } +impl Default for RuntimeContainer

{ + fn default() -> Self { + Self { + runtime: Default::default(), + } + } +} + pub(crate) fn initialize_runtime( - mut runtime: NonSendMut>, + mut runtime: ResMut>, settings: Res>, ) -> Result<(), ScriptError> { for initializer in settings.initializers.iter() { diff --git a/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs b/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs index 5ceddb7acd..3fa8b4b75d 100644 --- a/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs +++ b/crates/languages/bevy_mod_scripting_lua/tests/lua_tests.rs @@ -15,7 +15,6 @@ use std::{ }; struct Test { - code: String, path: PathBuf, } @@ -63,7 +62,6 @@ impl Test { })); }, self.path.as_os_str().to_str().unwrap(), - self.code.as_bytes(), ) .map_err(Failed::from) } @@ -99,12 +97,22 @@ fn visit_dirs(dir: &Path, cb: &mut dyn FnMut(&DirEntry)) -> io::Result<()> { fn discover_all_tests() -> Vec { let workspace_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let test_root = workspace_root.join("tests").join("data"); + let assets_root = workspace_root + .join("..") + .join("..") + .join("..") + .join("assets"); + let test_root = assets_root.join("tests"); let mut test_files = Vec::new(); visit_dirs(&test_root, &mut |entry| { let path = entry.path(); - let code = fs::read_to_string(&path).unwrap(); - test_files.push(Test { code, path }); + if path.extension().unwrap() == "lua" { + // only take the path from the assets bit + let relative = path.strip_prefix(&assets_root).unwrap(); + test_files.push(Test { + path: relative.to_path_buf(), + }); + } }) .unwrap(); diff --git a/crates/languages/bevy_mod_scripting_rhai/tests/rhai_tests.rs b/crates/languages/bevy_mod_scripting_rhai/tests/rhai_tests.rs index 2ab89b04c6..32bc7b7a98 100644 --- a/crates/languages/bevy_mod_scripting_rhai/tests/rhai_tests.rs +++ b/crates/languages/bevy_mod_scripting_rhai/tests/rhai_tests.rs @@ -21,7 +21,6 @@ use std::{ }; struct Test { - code: String, path: PathBuf, } @@ -78,7 +77,6 @@ impl Test { }); }, self.path.as_os_str().to_str().unwrap(), - self.code.as_bytes(), ) .map_err(Failed::from) } @@ -114,13 +112,20 @@ fn visit_dirs(dir: &Path, cb: &mut dyn FnMut(&DirEntry)) -> io::Result<()> { fn discover_all_tests() -> Vec { let workspace_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let test_root = workspace_root.join("tests").join("data"); + let assets_root = workspace_root + .join("..") + .join("..") + .join("..") + .join("assets"); + let test_root = assets_root.join("tests"); let mut test_files = Vec::new(); visit_dirs(&test_root, &mut |entry| { let path = entry.path(); - let code = fs::read_to_string(&path).unwrap(); if path.extension().unwrap() == "rhai" { - test_files.push(Test { code, path }); + let relative = path.strip_prefix(&assets_root).unwrap(); + test_files.push(Test { + path: relative.to_path_buf(), + }); } }) .unwrap(); diff --git a/crates/testing_crates/script_integration_test_harness/src/lib.rs b/crates/testing_crates/script_integration_test_harness/src/lib.rs index c25a5820a5..afa128bb84 100644 --- a/crates/testing_crates/script_integration_test_harness/src/lib.rs +++ b/crates/testing_crates/script_integration_test_harness/src/lib.rs @@ -1,16 +1,28 @@ pub mod test_functions; +use std::{ + path::PathBuf, + time::{Duration, Instant}, +}; + use bevy::{ - app::App, + app::{App, Update}, + asset::{AssetServer, Handle}, + ecs::{ + event::{Event, Events}, + system::{Local, Res}, + world::Mut, + }, prelude::{Entity, World}, reflect::TypeRegistry, }; use bevy_mod_scripting_core::{ + asset::ScriptAsset, bindings::{pretty_print::DisplayWithWorld, script_value::ScriptValue, WorldGuard}, - context::{ContextBuilder, ContextLoadingSettings}, - event::{IntoCallbackLabel, OnScriptLoaded}, - handler::CallbackSettings, - runtime::RuntimeSettings, + callback_labels, + event::ScriptErrorEvent, + extractors::{HandlerContext, WithWorldGuard}, + handler::handle_script_errors, IntoScriptPluginParams, }; use bevy_mod_scripting_functions::ScriptFunctionsPlugin; @@ -25,94 +37,99 @@ pub fn execute_integration_test< init: F, init_app: G, script_id: &str, - code: &[u8], ) -> Result<(), String> { + // set "BEVY_ASSET_ROOT" to the global assets folder, i.e. CARGO_MANIFEST_DIR/../../../assets + let mut manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + + // traverse upwards to find bevy_mod_scripting directory + loop { + if manifest_dir.ends_with("bevy_mod_scripting") { + break; + } + manifest_dir.pop(); + } + + std::env::set_var("BEVY_ASSET_ROOT", manifest_dir.clone()); + let mut app = setup_integration_test(init); app.add_plugins(ScriptFunctionsPlugin); + register_test_functions(&mut app); init_app(&mut app); + #[derive(Event)] + struct TestEventFinished; + app.add_event::(); + + callback_labels!( + OnTest => "on_test" + ); + + let script_id = script_id.to_owned(); + let script_id: &'static str = Box::leak(script_id.into_boxed_str()); + + let load_system = |server: Res, mut handle: Local>| { + *handle = server.load(script_id.to_owned()); + }; + let run_on_test_callback = |mut with_guard: WithWorldGuard>| { + let (guard, handler_ctxt) = with_guard.get_mut(); + + if !handler_ctxt.is_script_fully_loaded(script_id.into()) { + return; + } + + let res = handler_ctxt.call::( + script_id.into(), + Entity::from_raw(0), + vec![], + guard.clone(), + ); + let e = match res { + Ok(ScriptValue::Error(e)) => e.into(), + Err(e) => e, + _ => { + match guard.with_resource_mut(|mut events: Mut>| { + events.send(TestEventFinished) + }) { + Ok(_) => return, + Err(e) => e.into(), + } + } + }; + handle_script_errors(guard, vec![e].into_iter()) + }; + + app.add_systems(Update, (load_system, run_on_test_callback)); + app.cleanup(); app.finish(); - let context_settings: ContextLoadingSettings

= app - .world_mut() - .remove_resource() - .ok_or("could not find context loading settings") - .unwrap(); - - let callback_settings: CallbackSettings

= app - .world_mut() - .remove_resource() - .ok_or("could not find callback settings") - .unwrap(); - - let runtime_settings: RuntimeSettings

= app - .world_mut() - .remove_resource() - .ok_or("could not find runtime settings") - .unwrap(); - - let mut runtime = P::build_runtime(); - runtime_settings - .initializers - .iter() - .for_each(|initializer| { - (initializer)(&mut runtime).unwrap(); - }); - - // load the context as normal - let mut loaded_context = (ContextBuilder::

::load)( - context_settings.loader.load, - &(script_id.to_owned()).into(), - code, - &context_settings.context_initializers, - &context_settings.context_pre_handling_initializers, - app.world_mut(), - &mut runtime, - ) - .map_err(|e| { - let world = app.world_mut(); - e.display_with_world(WorldGuard::new(world)) - })?; - - // call on_script_loaded as normal - let val = (CallbackSettings::

::call)( - callback_settings.callback_handler, - vec![], - Entity::from_raw(0), - &(script_id.to_owned()).into(), - &OnScriptLoaded::into_callback_label(), - &mut loaded_context, - &context_settings.context_pre_handling_initializers, - &mut runtime, - app.world_mut(), - ) - .map_err(|e| e.display_with_world(WorldGuard::new(app.world_mut())))?; - - if let ScriptValue::Error(e) = val { - return Err(e.display_with_world(WorldGuard::new(app.world_mut()))); - } + let start = Instant::now(); // start the timer - // call on_test callback - let val = (CallbackSettings::

::call)( - callback_settings.callback_handler, - vec![], - Entity::from_raw(0), - &(script_id.to_owned()).into(), - &"on_test".into(), - &mut loaded_context, - &context_settings.context_pre_handling_initializers, - &mut runtime, - app.world_mut(), - ) - .map_err(|e| e.display_with_world(WorldGuard::new(app.world_mut())))?; - - if let ScriptValue::Error(e) = val { - return Err(e.display_with_world(WorldGuard::new(app.world_mut()))); - } + loop { + app.update(); + + if start.elapsed() > Duration::from_secs(10) { + return Err("Timeout after 10 seconds".into()); + } + + let events_completed = app.world_mut().resource_ref::>(); + if events_completed.len() > 0 { + return Ok(()); + } - Ok(()) + let error_events = app + .world_mut() + .resource_mut::>() + .drain() + .collect::>(); + + if let Some(event) = error_events.into_iter().next() { + return Err(event + .error + .display_with_world(WorldGuard::new(app.world_mut()))); + } + } } diff --git a/crates/testing_crates/test_utils/src/lib.rs b/crates/testing_crates/test_utils/src/lib.rs index 363a59ff80..1b883431b8 100644 --- a/crates/testing_crates/test_utils/src/lib.rs +++ b/crates/testing_crates/test_utils/src/lib.rs @@ -1 +1,2 @@ pub mod test_data; +pub mod test_plugin; diff --git a/crates/testing_crates/test_utils/src/test_data.rs b/crates/testing_crates/test_utils/src/test_data.rs index 9c7db42a6c..59bb5bf0f0 100644 --- a/crates/testing_crates/test_utils/src/test_data.rs +++ b/crates/testing_crates/test_utils/src/test_data.rs @@ -2,7 +2,9 @@ use std::alloc::Layout; use std::collections::HashMap; use bevy::asset::AssetPlugin; +use bevy::diagnostic::DiagnosticsPlugin; use bevy::ecs::{component::*, world::World}; +use bevy::log::LogPlugin; use bevy::prelude::*; use bevy::reflect::*; @@ -323,7 +325,16 @@ pub fn setup_integration_test(init: F) // first setup all normal test components and resources let mut app = setup_app(init); - app.add_plugins((MinimalPlugins, AssetPlugin::default(), HierarchyPlugin)); + app.add_plugins(( + MinimalPlugins, + AssetPlugin::default(), + HierarchyPlugin, + DiagnosticsPlugin, + LogPlugin { + filter: "bevy_mod_scripting_core=debug".to_string(), + ..Default::default() + }, + )); app } diff --git a/crates/testing_crates/test_utils/src/test_plugin.rs b/crates/testing_crates/test_utils/src/test_plugin.rs new file mode 100644 index 0000000000..f56fbcd2f2 --- /dev/null +++ b/crates/testing_crates/test_utils/src/test_plugin.rs @@ -0,0 +1,43 @@ +/// Creates a test plugin, but avoids the dependency on bms core +/// requires the root path of BMS core +#[macro_export] +macro_rules! make_test_plugin { + ($ident: ident) => { + // #[derive(Default)] + struct TestPlugin($ident::ScriptingPlugin); + + impl Default for TestPlugin { + fn default() -> Self { + Self($ident::ScriptingPlugin::::default()) + } + } + + impl bevy::app::Plugin for TestPlugin { + fn build(&self, app: &mut bevy::app::App) { + self.0.build(app); + } + } + + impl $ident::IntoScriptPluginParams for TestPlugin { + type C = TestContext; + type R = TestRuntime; + + const LANGUAGE: $ident::Language = $ident::Language::Unknown; + + fn build_runtime() -> Self::R { + TestRuntime { + invocations: vec![], + } + } + } + + #[derive(Default)] + struct TestRuntime { + pub invocations: Vec<(Entity, ScriptId)>, + } + + struct TestContext { + pub invocations: Vec, + } + }; +}