Merge d74ec349c7c94b8cec144c364963c99f35b96581 into 9a1501ae89ffe79c38dbd6756c9e7ed647dd7dc1

This commit is contained in:
Lars Müller 2024-06-28 11:51:07 +02:00 committed by GitHub
commit 484b4e3b8e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 134 additions and 12 deletions

@ -272,3 +272,29 @@ function core.get_globals_to_transfer()
} }
return all return all
end end
do
local function valid_object_iterator(objects)
local i = 0
local function next_valid_object()
i = i + 1
local obj = objects[i]
if obj == nil then
return
end
if obj:is_valid() then
return obj
end
return next_valid_object()
end
return next_valid_object
end
function core.objects_inside_radius(center, radius)
return valid_object_iterator(core.get_objects_inside_radius(center, radius))
end
function core.objects_in_area(min_pos, max_pos)
return valid_object_iterator(core.get_objects_in_area(min_pos, max_pos))
end
end

@ -6130,12 +6130,24 @@ Environment access
* Items can be added also to unloaded and non-generated blocks. * Items can be added also to unloaded and non-generated blocks.
* `minetest.get_player_by_name(name)`: Get an `ObjectRef` to a player * `minetest.get_player_by_name(name)`: Get an `ObjectRef` to a player
* Returns nothing in case of error (player offline, doesn't exist, ...). * Returns nothing in case of error (player offline, doesn't exist, ...).
* `minetest.get_objects_inside_radius(pos, radius)` * `minetest.get_objects_inside_radius(center, radius)`
* returns a list of ObjectRefs. * returns a list of ObjectRefs
* `radius`: using a Euclidean metric * `radius`: using a Euclidean metric
* `minetest.get_objects_in_area(pos1, pos2)` * **Warning**: Any kind of interaction with the environment or other APIs
* returns a list of ObjectRefs. can cause later objects in the list to become invalid while you're iterating it.
* `pos1` and `pos2` are the min and max positions of the area to search. (e.g. punching an entity removes its children)
It is recommended to use `minetest.objects_inside_radius` instead, which
transparently takes care of this possibility.
* `minetest.objects_inside_radius(center, radius)`
* returns an iterator of valid objects
* example: `for obj in minetest.objects_inside_radius(center, radius) do obj:punch(...) end`
* `minetest.get_objects_in_area(min_pos, max_pos)`
* returns a list of ObjectRefs
* `min_pos` and `max_pos` are the min and max positions of the area to search
* **Warning**: The same warning as for `minetest.get_objects_inside_radius` applies.
Use `minetest.objects_in_area` instead to iterate only valid objects.
* `minetest.objects_in_area(min_pos, max_pos)`
* returns an iterator of valid objects
* `minetest.set_timeofday(val)`: set time of day * `minetest.set_timeofday(val)`: set time of day
* `val` is between `0` and `1`; `0` for midnight, `0.5` for midday * `val` is between `0` and `1`; `0` for midnight, `0.5` for midday
* `minetest.get_timeofday()`: get time of day * `minetest.get_timeofday()`: get time of day
@ -7797,13 +7809,18 @@ When you receive an `ObjectRef` as a callback argument or from another API
function, it is possible to store the reference somewhere and keep it around. function, it is possible to store the reference somewhere and keep it around.
It will keep functioning until the object is unloaded or removed. It will keep functioning until the object is unloaded or removed.
However, doing this is **NOT** recommended as there is (intentionally) no method However, doing this is **NOT** recommended - `ObjectRefs` should be "let go"
to test if a previously acquired `ObjectRef` is still valid. of as soon as control is returned from Lua back to the engine.
Instead, `ObjectRefs` should be "let go" of as soon as control is returned from
Lua back to the engine.
Doing so is much less error-prone and you will never need to wonder if the Doing so is much less error-prone and you will never need to wonder if the
object you are working with still exists. object you are working with still exists.
If this is not feasible, you can test whether an `ObjectRef` is still valid
via `object:is_valid()`.
Getters may be called for invalid objects and will return nothing then.
All other methods should not be called on invalid objects.
### Attachments ### Attachments
It is possible to attach objects to other objects (`set_attach` method). It is possible to attach objects to other objects (`set_attach` method).
@ -7822,6 +7839,8 @@ child will follow movement and rotation of that bone.
### Methods ### Methods
* `is_valid()`: returns whether the object is valid.
* See "Advice on handling `ObjectRefs`" above.
* `get_pos()`: returns position as vector `{x=num, y=num, z=num}` * `get_pos()`: returns position as vector `{x=num, y=num, z=num}`
* `set_pos(pos)`: * `set_pos(pos)`:
* Sets the position of the object. * Sets the position of the object.

@ -71,13 +71,13 @@ local function test_entity_lifecycle(_, pos)
-- with binary in staticdata -- with binary in staticdata
local obj = core.add_entity(pos, "unittests:callbacks", "abc\000def") local obj = core.add_entity(pos, "unittests:callbacks", "abc\000def")
assert(obj and obj:is_valid())
check_log({"on_activate(7)"}) check_log({"on_activate(7)"})
obj:set_hp(0) obj:set_hp(0)
check_log({"on_death(nil)", "on_deactivate(true)"}) check_log({"on_death(nil)", "on_deactivate(true)"})
-- objectref must be invalid now assert(not obj:is_valid())
assert(obj:get_velocity() == nil)
end end
unittests.register("test_entity_lifecycle", test_entity_lifecycle, {map=true}) unittests.register("test_entity_lifecycle", test_entity_lifecycle, {map=true})
@ -130,3 +130,57 @@ local function test_entity_attach(player, pos)
obj:remove() obj:remove()
end end
unittests.register("test_entity_attach", test_entity_attach, {player=true, map=true}) unittests.register("test_entity_attach", test_entity_attach, {player=true, map=true})
core.register_entity("unittests:dummy", {
initial_properties = {
hp_max = 1,
visual = "upright_sprite",
textures = { "no_texture.png" },
static_save = false,
},
})
local function test_entity_raycast(_, pos)
local obj1 = core.add_entity(pos, "unittests:dummy")
local obj2 = core.add_entity(pos:offset(1, 0, 0), "unittests:dummy")
local raycast = core.raycast(pos:offset(-1, 0, 0), pos:offset(2, 0, 0), true, false)
for pt in raycast do
if pt.type == "object" then
assert(pt.ref == obj1)
obj1:remove()
obj2:remove()
obj1 = nil -- object should be hit exactly one
end
end
assert(obj1 == nil)
end
unittests.register("test_entity_raycast", test_entity_raycast, {map=true})
local function test_object_iterator(pos, make_iterator)
local obj1 = core.add_entity(pos, "unittests:dummy")
local obj2 = core.add_entity(pos, "unittests:dummy")
assert(obj1 and obj2)
local found = false
-- As soon as we find one of the objects, we remove both, invalidating the other.
for obj in make_iterator() do
assert(obj:is_valid())
if obj == obj1 or obj == obj2 then
obj1:remove()
obj2:remove()
found = true
end
end
assert(found)
end
unittests.register("test_objects_inside_radius", function(_, pos)
test_object_iterator(pos, function()
return core.objects_inside_radius(pos, 1)
end)
end, {map=true})
unittests.register("test_objects_in_area", function(_, pos)
test_object_iterator(pos, function()
return core.objects_in_area(pos:offset(-1, -1, -1), pos:offset(1, 1, 1))
end)
end, {map=true})

@ -155,6 +155,7 @@ void LuaLBM::trigger(ServerEnvironment *env, v3s16 p,
int LuaRaycast::l_next(lua_State *L) int LuaRaycast::l_next(lua_State *L)
{ {
GET_PLAIN_ENV_PTR; GET_PLAIN_ENV_PTR;
ServerEnvironment *senv = dynamic_cast<ServerEnvironment*>(env);
bool csm = false; bool csm = false;
#ifndef SERVER #ifndef SERVER
@ -163,7 +164,17 @@ int LuaRaycast::l_next(lua_State *L)
LuaRaycast *o = checkObject<LuaRaycast>(L, 1); LuaRaycast *o = checkObject<LuaRaycast>(L, 1);
PointedThing pointed; PointedThing pointed;
env->continueRaycast(&o->state, &pointed); for (;;) {
env->continueRaycast(&o->state, &pointed);
if (pointed.type != POINTEDTHING_OBJECT)
break;
if (!senv)
break;
const auto *obj = senv->getActiveObject(pointed.object_id);
if (obj && !obj->isGone())
break;
// skip gone object
}
if (pointed.type == POINTEDTHING_NOTHING) if (pointed.type == POINTEDTHING_NOTHING)
lua_pushnil(L); lua_pushnil(L);
else else

@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "lua_api/l_object.h" #include "lua_api/l_object.h"
#include <cmath> #include <cmath>
#include <lua.h>
#include "lua_api/l_internal.h" #include "lua_api/l_internal.h"
#include "lua_api/l_inventory.h" #include "lua_api/l_inventory.h"
#include "lua_api/l_item.h" #include "lua_api/l_item.h"
@ -106,6 +107,13 @@ int ObjectRef::l_remove(lua_State *L)
return 0; return 0;
} }
// is_valid(self)
int ObjectRef::l_is_valid(lua_State *L)
{
lua_pushboolean(L, getobject(checkObject<ObjectRef>(L, 1)) != nullptr);
return 1;
}
// get_pos(self) // get_pos(self)
int ObjectRef::l_get_pos(lua_State *L) int ObjectRef::l_get_pos(lua_State *L)
{ {
@ -2646,6 +2654,7 @@ const char ObjectRef::className[] = "ObjectRef";
luaL_Reg ObjectRef::methods[] = { luaL_Reg ObjectRef::methods[] = {
// ServerActiveObject // ServerActiveObject
luamethod(ObjectRef, remove), luamethod(ObjectRef, remove),
luamethod(ObjectRef, is_valid),
luamethod_aliased(ObjectRef, get_pos, getpos), luamethod_aliased(ObjectRef, get_pos, getpos),
luamethod_aliased(ObjectRef, set_pos, setpos), luamethod_aliased(ObjectRef, set_pos, setpos),
luamethod(ObjectRef, add_pos), luamethod(ObjectRef, add_pos),

@ -67,6 +67,9 @@ class ObjectRef : public ModApiBase {
// remove(self) // remove(self)
static int l_remove(lua_State *L); static int l_remove(lua_State *L);
// is_valid(self)
static int l_is_valid(lua_State *L);
// get_pos(self) // get_pos(self)
static int l_get_pos(lua_State *L); static int l_get_pos(lua_State *L);