Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add persistent unique identifiers for objects #14135

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions builtin/game/features.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ core.features = {
abm_without_neighbors = true,
biome_weights = true,
particle_blend_clip = true,
have_guids = true,
sfence marked this conversation as resolved.
Show resolved Hide resolved
}

function core.has_feature(arg)
Expand Down
9 changes: 7 additions & 2 deletions doc/lua_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5689,6 +5689,8 @@ Utilities
biome_weights = true,
-- Particles can specify a "clip" blend mode (5.11.0)
particle_blend_clip = true,
-- objects have get_guid method (5.12.0)
object_guids = true,
}
```

Expand Down Expand Up @@ -7669,8 +7671,8 @@ Global tables
* Values in this table may be modified directly.
Note: changes to initial properties will only affect entities spawned afterwards,
as they are only read when spawning.
* `core.object_refs`
* Map of object references, indexed by active object id
* `core.objects_by_guid`
* Map of object references, indexed by active object GUID
* `core.luaentities`
* Map of Lua entities, indexed by active object id
* `core.registered_abms`
Expand Down Expand Up @@ -8366,6 +8368,9 @@ child will follow movement and rotation of that bone.
-- Default: false
}
```
* `get_guid()`: returns a global unique identifier (a string)
* For players this is a player name.
* For Lua entities, it is a unique generated string.

#### Lua entity only (no-op for other objects)

Expand Down
4 changes: 3 additions & 1 deletion doc/world_format.md
Original file line number Diff line number Diff line change
Expand Up @@ -579,9 +579,11 @@ Object types:
* `s32` yaw * 1000

Since protocol version 37:
* `u8` `version2` (=1)
* `u8` `version2` (=1 or 2)
* `s32` pitch * 1000
* `s32` roll * 1000
* if version2 >= 2:
* u8[16] guid

# Itemstring Format

Expand Down
13 changes: 13 additions & 0 deletions games/devtest/mods/unittests/entity.lua
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,16 @@ local function test_get_bone_rot(_, pos)
end
end
unittests.register("test_get_bone_rot", test_get_bone_rot, {map=true})

unittests.register("test_entity_guid", function(_, pos)
log = {}

local obj = core.add_entity(pos, "unittests:callbacks")
check_log({"on_activate(0)"})

assert(obj:get_guid()~="")
assert(core.objects_by_guid[obj:get_guid()]~=nil)

obj:remove()
check_log({"on_deactivate(true)"})
end, {map=true})
8 changes: 8 additions & 0 deletions games/devtest/mods/unittests/player.lua
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,11 @@ local function run_player_hotbar_clamp_tests(player)
player:hud_set_hotbar_itemcount(old_bar_size)
end
unittests.register("test_player_hotbar_clamp", run_player_hotbar_clamp_tests, {player=true})

--
-- Player get uuid
--
local function test_player_guid_tests(player)
assert(player:get_guid()==player:get_player_name())
end
unittests.register("test_player_guid", test_player_guid_tests, {player=true})
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ set(common_SRCS
environment.cpp
filesys.cpp
gettext.cpp
guid.cpp
inventorymanager.cpp
itemdef.cpp
light.cpp
Expand Down
75 changes: 75 additions & 0 deletions src/guid.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2024 SFENCE

#include "guid.h"
#include <sstream>

#include "serverenvironment.h"
#include "util/serialize.h"

GUId::GUId(const std::string &str) :
text(str)
{
}

void GUId::generateText() {
std::stringstream s_guid;

s_guid << std::hex << std::setfill('0');
s_guid << std::setw(8) << p1 << "-";
s_guid << std::setw(4) << p2 << "-";
s_guid << std::setw(4) << p3 << "-";
s_guid << std::setw(4) << p4 << "-";
s_guid << std::setw(8) << p5;
s_guid << std::setw(4) << p6;

text = s_guid.str();
}

void GUId::serialize(std::ostringstream &os) const {
writeU32(os, p1);
writeU16(os, p2);
writeU16(os, p3);
writeU16(os, p4);
writeU32(os, p5);
writeU16(os, p6);
}

void GUId::deSerialize(std::istream &is) {
p1 = readU32(is);
p2 = readU16(is);
p3 = readU16(is);
p4 = readU16(is);
p5 = readU32(is);
p6 = readU16(is);

generateText();
}

GUIdGenerator::GUIdGenerator() :
m_uniform(0, UINT64_MAX)
{
if (m_rand.entropy() <= 0.010)
throw BaseException("The system's provided random generator does not match "
"the entropy requirements for the GUId generator.");
}

GUId GUIdGenerator::next()
{
GUId result;

u64 rand1 = m_uniform(m_rand);
u64 rand2 = m_uniform(m_rand);

result.p1 = rand1 & 0xFFFFFFFF;
result.p2 = (rand1 >> 32) & 0xFFFF;
result.p3 = (rand1 >> 48) & 0xFFFF;
result.p4 = rand2 & 0xFFFF;
result.p5 = (rand2 >> 16) & 0xFFFFFFFF;
result.p6 = (rand2 >> 48) & 0xFFFF;

result.generateText();

return result;
}
60 changes: 60 additions & 0 deletions src/guid.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Luanti
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (C) 2024 SFENCE

#pragma once

#include "irrlichttypes.h"
#include "util/basic_macros.h"
#include <random>
#include <string>

class ServerEnvironment;

/**
* A global unique identifier.
* It is global because it stays valid in a world forever.
* It is unique because there are no collisions.
*/
struct GUId {
std::string text;
u32 p1;
u16 p2, p3, p4;
u32 p5;
u16 p6;

GUId() = default;
GUId(const std::string &str);
void generateText();
void serialize(std::ostringstream &os) const;
void deSerialize(std::istream &is);
};

/**
* Generates infinitely many guids.
*/
class GUIdGenerator {
public:
/**
* Creates a new uninitialized generator.
*/
GUIdGenerator();

~GUIdGenerator() = default;
DISABLE_CLASS_COPY(GUIdGenerator)

/**
* Generates the next guid, which it will never return again.
* @return the new guid
*/
GUId next();

private:
void setServerEnvironment(ServerEnvironment *env) { m_env = env; }

ServerEnvironment *m_env;
std::random_device m_rand;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asking the system secure random repeatedly might be slow btw

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be only one GUId generator object in the environment. So this should happen only in the time of its creation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think sfan5 has a point. The m_uniform(m_rand) calls will probably end up as syscalls asking the random device for real random.

What you might do instead is have a pseudorandom number generator (PRNG) such as std::mt19937_64 here, and seed that from real random at initialization to make the sequence harder to predict.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer a generator with entropy. It looks to be the preferred solution for GUIds generation.

std::uniform_int_distribution<u64> m_uniform;

friend class ServerEnvironment;
};
32 changes: 29 additions & 3 deletions src/script/cpp_api/s_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -405,17 +405,21 @@ void ScriptApiBase::setOriginFromTableRaw(int index, const char *fxn)
/*
* How ObjectRefs are handled in Lua:
* When an active object is created, an ObjectRef is created on the Lua side
* and stored in core.object_refs[id].
* and stored in core.object_refs[id] and in core.objects_by_guids[GUID].
* Methods that require an ObjectRef to a certain object retrieve it from that
* table instead of creating their own.(*)
* When an active object is removed, the existing ObjectRef is invalidated
* using ::set_null() and removed from the core.object_refs table.
* using ::set_null() and removed from the core.object_refs and
* core.object_by_guids tables.
* (*) An exception to this are NULL ObjectRefs and anonymous ObjectRefs
* for objects without ID.
* It's unclear what the latter are needed for and their use is problematic
* since we lose control over the ref and the contained pointer.
*
*
* DEPRECATED
* core.object_regs[id] can be removed in future!!!
*/

void ScriptApiBase::addObjectReference(ServerActiveObject *cobj)
{
SCRIPTAPI_PRECHECKHEADER
Expand All @@ -435,6 +439,17 @@ void ScriptApiBase::addObjectReference(ServerActiveObject *cobj)
lua_pushinteger(L, cobj->getId()); // Push id
lua_pushvalue(L, object); // Copy object to top of stack
lua_settable(L, objectstable);

// Get core.objects_by_guid table
lua_getglobal(L, "core");
lua_getfield(L, -1, "objects_by_guid");
luaL_checktype(L, -1, LUA_TTABLE);
objectstable = lua_gettop(L);

// objects_by_guid[GUID] = object
lua_pushstring(L, cobj->getGuid().text.c_str()); // Push GUID
lua_pushvalue(L, object); // Copy object to top of stack
lua_settable(L, objectstable);
}

void ScriptApiBase::removeObjectReference(ServerActiveObject *cobj)
Expand All @@ -444,6 +459,7 @@ void ScriptApiBase::removeObjectReference(ServerActiveObject *cobj)

// Get core.object_refs table
lua_getglobal(L, "core");
int core = lua_gettop(L);
lua_getfield(L, -1, "object_refs");
luaL_checktype(L, -1, LUA_TTABLE);
int objectstable = lua_gettop(L);
Expand All @@ -459,6 +475,16 @@ void ScriptApiBase::removeObjectReference(ServerActiveObject *cobj)
lua_pushinteger(L, cobj->getId()); // Push id
lua_pushnil(L);
lua_settable(L, objectstable);

// Get core.objects_by_guid
lua_getfield(L, core, "objects_by_guid");
luaL_checktype(L, -1, LUA_TTABLE);
objectstable = lua_gettop(L);

// Set objects_by_guid[GUID] = nil
lua_pushstring(L, cobj->getGuid().text.c_str()); // Push GUID
lua_pushnil(L);
lua_settable(L, objectstable);
}

void ScriptApiBase::objectrefGetOrCreate(lua_State *L, ServerActiveObject *cobj)
Expand Down
14 changes: 14 additions & 0 deletions src/script/lua_api/l_object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,19 @@ int ObjectRef::l_is_valid(lua_State *L)
return 1;
}

// get_guid()
int ObjectRef::l_get_guid(lua_State *L)
{
NO_MAP_LOCK_REQUIRED;
ObjectRef *ref = checkObject<ObjectRef>(L, 1);
ServerActiveObject *sao = getobject(ref);
if (sao == nullptr)
return 0;

lua_pushstring(L, sao->getGuid().text.c_str());
return 1;
}

// get_pos(self)
int ObjectRef::l_get_pos(lua_State *L)
{
Expand Down Expand Up @@ -2783,6 +2796,7 @@ luaL_Reg ObjectRef::methods[] = {
// ServerActiveObject
luamethod(ObjectRef, remove),
luamethod(ObjectRef, is_valid),
luamethod(ObjectRef, get_guid),
luamethod_aliased(ObjectRef, get_pos, getpos),
luamethod_aliased(ObjectRef, set_pos, setpos),
luamethod(ObjectRef, add_pos),
Expand Down
3 changes: 3 additions & 0 deletions src/script/lua_api/l_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ class ObjectRef : public ModApiBase {
// is_valid(self)
static int l_is_valid(lua_State *L);

// get_guid()
static int l_get_guid(lua_State *L);

// get_pos(self)
static int l_get_pos(lua_State *L);

Expand Down
3 changes: 3 additions & 0 deletions src/script/scripting_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ ServerScripting::ServerScripting(Server* server):
lua_newtable(L);
lua_setfield(L, -2, "object_refs");

lua_newtable(L);
lua_setfield(L, -2, "objects_by_guid");

lua_newtable(L);
lua_setfield(L, -2, "luaentities");

Expand Down
Loading
Loading