Compare commits

...

6 Commits

Author SHA1 Message Date
ExeVirus
391af17f6e
Merge f602479316ff03de8bcde888e6e548ea267c94d7 into 9a1501ae89ffe79c38dbd6756c9e7ed647dd7dc1 2024-06-28 08:18:15 +03:00
grorp
9a1501ae89
CIrrDeviceSDL: Fix numpad key events not having correct KeyInput.Char (#14780)
Allows you to change viewing range using numpad +/- again. This fix also works with the current unreleased version of SDL 3.

The keycodes for numpad keys (both SDL keycodes and Irrlicht keycodes) are not the same as the keycodes for the equivalent non-numpad keys and don't correspond to chars, so I mapped them to chars manually.

Since I think the resolution of https://github.com/minetest/minetest/issues/13770 was "just disable numlock", I made sure to only do this for the numpad number keys if numlock is enabled.
2024-06-27 14:44:44 +02:00
Erich Schubert
514e106414
Fix missing newline before Markdown list (#14783)
Renders incorrectly e.g. on https://api.minetest.net/spatial-vectors/
2024-06-26 22:21:18 +02:00
ExeVirus
f602479316 Add Appgurueu Integrations and Fixes 2024-06-11 15:42:21 -04:00
ExeVirus
42fdaa9838 Code Cleanup 2024-06-11 14:53:52 -04:00
ExeVirus
7e504cbd37 Add Spatial Map, add 10,000 benchmark. 2024-06-11 14:37:52 -04:00
19 changed files with 473 additions and 87 deletions

@ -3902,6 +3902,7 @@ Operators
---------
Operators can be used if all of the involved vectors have metatables:
* `v1 == v2`:
* Returns whether `v1` and `v2` are identical.
* `-v`:

@ -7,6 +7,7 @@
#include "irrMath.h"
#include <functional>
#include <array>
namespace irr
{
@ -32,6 +33,9 @@ class vector3d
//! Constructor with the same value for all elements
explicit constexpr vector3d(T n) :
X(n), Y(n), Z(n) {}
//! Array - vector conversion
constexpr vector3d(const std::array<T, 3>& arr) :
X(arr[0]), Y(arr[1]), Z(arr[2]) {}
// operators
@ -181,6 +185,26 @@ class vector3d
return *this;
}
std::array<T, 3> toArray() const {
return { X, Y, Z };
}
vector3d<T> min(const T min_component) const {
return vector3d<T>(
std::min(X, min_component),
std::min(Y, min_component),
std::min(Z, min_component)
);
}
vector3d<T> max(const T max_component) const {
return vector3d<T>(
std::max(X, max_component),
std::max(Y, max_component),
std::max(Z, max_component)
);
}
//! Get length of the vector.
T getLength() const { return core::squareroot(X * X + Y * Y + Z * Z); }

@ -129,9 +129,9 @@ EM_BOOL CIrrDeviceSDL::MouseLeaveCallback(int eventType, const EmscriptenMouseEv
}
#endif
bool CIrrDeviceSDL::keyIsKnownSpecial(EKEY_CODE key)
bool CIrrDeviceSDL::keyIsKnownSpecial(EKEY_CODE irrlichtKey)
{
switch (key) {
switch (irrlichtKey) {
// keys which are known to have safe special character interpretation
// could need changes over time (removals and additions!)
case KEY_RETURN:
@ -189,24 +189,68 @@ bool CIrrDeviceSDL::keyIsKnownSpecial(EKEY_CODE key)
}
}
int CIrrDeviceSDL::findCharToPassToIrrlicht(int assumedChar, EKEY_CODE key)
int CIrrDeviceSDL::findCharToPassToIrrlicht(uint32_t sdlKey, EKEY_CODE irrlichtKey, bool numlock)
{
switch (irrlichtKey) {
// special cases that always return a char regardless of how the SDL keycode
// looks
switch (key) {
case KEY_RETURN:
case KEY_ESCAPE:
return (int)key;
return (int)irrlichtKey;
// This is necessary for keys on the numpad because they don't use the same
// keycodes as their non-numpad versions (whose keycodes correspond to chars),
// but have their own SDL keycodes and their own Irrlicht keycodes (which
// don't correspond to chars).
case KEY_MULTIPLY:
return '*';
case KEY_ADD:
return '+';
case KEY_SUBTRACT:
return '-';
case KEY_DIVIDE:
return '/';
default:
break;
}
if (numlock) {
// Number keys on the numpad are also affected, but we only want them
// to produce number chars when numlock is enabled.
switch (irrlichtKey) {
case KEY_NUMPAD0:
return '0';
case KEY_NUMPAD1:
return '1';
case KEY_NUMPAD2:
return '2';
case KEY_NUMPAD3:
return '3';
case KEY_NUMPAD4:
return '4';
case KEY_NUMPAD5:
return '5';
case KEY_NUMPAD6:
return '6';
case KEY_NUMPAD7:
return '7';
case KEY_NUMPAD8:
return '8';
case KEY_NUMPAD9:
return '9';
default:
break;
}
}
// SDL in-place ORs values with no character representation with 1<<30
// https://wiki.libsdl.org/SDL2/SDLKeycodeLookup
if (assumedChar & (1 << 30))
// This also affects the numpad keys btw.
if (sdlKey & (1 << 30))
return 0;
switch (key) {
switch (irrlichtKey) {
case KEY_PRIOR:
case KEY_NEXT:
case KEY_HOME:
@ -218,7 +262,7 @@ int CIrrDeviceSDL::findCharToPassToIrrlicht(int assumedChar, EKEY_CODE key)
case KEY_NUMLOCK:
return 0;
default:
return assumedChar;
return sdlKey;
}
}
@ -825,7 +869,8 @@ bool CIrrDeviceSDL::run()
irrevent.KeyInput.PressedDown = (SDL_event.type == SDL_KEYDOWN);
irrevent.KeyInput.Shift = (SDL_event.key.keysym.mod & KMOD_SHIFT) != 0;
irrevent.KeyInput.Control = (SDL_event.key.keysym.mod & KMOD_CTRL) != 0;
irrevent.KeyInput.Char = findCharToPassToIrrlicht(mp.SDLKey, key);
irrevent.KeyInput.Char = findCharToPassToIrrlicht(mp.SDLKey, key,
(SDL_event.key.keysym.mod & KMOD_NUM) != 0);
postEventFromUser(irrevent);
} break;

@ -273,10 +273,10 @@ class CIrrDeviceSDL : public CIrrDeviceStub
#endif
// Check if a key is a known special character with no side effects on text boxes.
static bool keyIsKnownSpecial(EKEY_CODE key);
static bool keyIsKnownSpecial(EKEY_CODE irrlichtKey);
// Return the Char that should be sent to Irrlicht for the given key (either the one passed in or 0).
static int findCharToPassToIrrlicht(int assumedChar, EKEY_CODE key);
static int findCharToPassToIrrlicht(uint32_t sdlKey, EKEY_CODE irrlichtKey, bool numlock);
// Check if a text box is in focus. Enable or disable SDL_TEXTINPUT events only if in focus.
void resetReceiveTextInputEvents();

@ -105,7 +105,9 @@ void benchGetObjectsInArea(Catch::Benchmark::Chronometer &meter)
TEST_CASE("ActiveObjectMgr") {
BENCH_INSIDE_RADIUS(200)
BENCH_INSIDE_RADIUS(1450)
BENCH_INSIDE_RADIUS(10000)
BENCH_IN_AREA(200)
BENCH_IN_AREA(1450)
BENCH_IN_AREA(10000)
}

@ -281,13 +281,15 @@ static void add_object_boxes(Environment *env,
}
};
// Calculate distance by speed, add own extent and 1.5m of tolerance
const f32 distance = speed_f.getLength() * dtime +
box_0.getExtent().getLength() + 1.5f * BS;
const f32 tolerance = 1.5f * BS; // TODO increase tolerance
#ifndef SERVER
ClientEnvironment *c_env = dynamic_cast<ClientEnvironment*>(env);
if (c_env) {
// Calculate distance by speed, add own extent and 1.5m of tolerance
const f32 distance = speed_f.getLength() * dtime +
box_0.getExtent().getLength() + 1.5f * BS;
std::vector<DistanceSortedActiveObject> clientobjects;
c_env->getActiveObjects(pos_f, distance, clientobjects);
@ -326,9 +328,14 @@ static void add_object_boxes(Environment *env,
return false;
};
// Calculate distance by speed, add own extent and tolerance
const v3f movement = speed_f * dtime;
const v3f min = pos_f + box_0.MinEdge - v3f(tolerance) + movement.min(0);
const v3f max = pos_f + box_0.MaxEdge + v3f(tolerance) + movement.max(0);
// nothing is put into this vector
std::vector<ServerActiveObject*> s_objects;
s_env->getObjectsInsideRadius(s_objects, pos_f, distance, include_obj_cb);
s_env->getObjectsInArea(s_objects, aabb3f(min, max), include_obj_cb);
}
}
}

@ -8,6 +8,7 @@ set(server_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/serveractiveobject.cpp
${CMAKE_CURRENT_SOURCE_DIR}/serverinventorymgr.cpp
${CMAKE_CURRENT_SOURCE_DIR}/serverlist.cpp
${CMAKE_CURRENT_SOURCE_DIR}/spatial_map.cpp
${CMAKE_CURRENT_SOURCE_DIR}/unit_sao.cpp
${CMAKE_CURRENT_SOURCE_DIR}/rollback.cpp
PARENT_SCOPE)

@ -34,6 +34,12 @@ ActiveObjectMgr::~ActiveObjectMgr()
}
}
void ActiveObjectMgr::clear()
{
::ActiveObjectMgr<ServerActiveObject>::clear();
m_spatial_map.removeAll();
}
void ActiveObjectMgr::clearIf(const std::function<bool(ServerActiveObject *, u16)> &cb)
{
for (auto &it : m_active_objects.iter()) {
@ -41,7 +47,7 @@ void ActiveObjectMgr::clearIf(const std::function<bool(ServerActiveObject *, u16
continue;
if (cb(it.second.get(), it.first)) {
// Remove reference from m_active_objects
m_active_objects.remove(it.first);
removeObject(it.first);
}
}
}
@ -61,6 +67,11 @@ void ActiveObjectMgr::step(
g_profiler->avg("ActiveObjectMgr: SAO count [#]", count);
}
void ActiveObjectMgr::updateObjectPosition(u16 id, const v3f &last_position, const v3f &new_position)
{
m_spatial_map.updatePosition(id, last_position, new_position);
}
bool ActiveObjectMgr::registerObject(std::unique_ptr<ServerActiveObject> obj)
{
assert(obj); // Pre-condition
@ -83,15 +94,16 @@ bool ActiveObjectMgr::registerObject(std::unique_ptr<ServerActiveObject> obj)
return false;
}
if (objectpos_over_limit(obj->getBasePosition())) {
v3f p = obj->getBasePosition();
const v3f pos = obj->getBasePosition();
if (objectpos_over_limit(pos)) {
warningstream << "Server::ActiveObjectMgr::addActiveObjectRaw(): "
<< "object position (" << p.X << "," << p.Y << "," << p.Z
<< "object position (" << pos.X << "," << pos.Y << "," << pos.Z
<< ") outside maximum range" << std::endl;
return false;
}
auto obj_id = obj->getId();
auto obj_id = obj->getId();
m_spatial_map.insert(obj_id, obj->getBasePosition());
m_active_objects.put(obj_id, std::move(obj));
auto new_size = m_active_objects.size();
@ -110,6 +122,13 @@ void ActiveObjectMgr::removeObject(u16 id)
verbosestream << "Server::ActiveObjectMgr::removeObject(): "
<< "id=" << id << std::endl;
auto &obj = m_active_objects.get(id);
if(obj) {
m_spatial_map.remove(id, obj->getBasePosition());
} else {
m_spatial_map.remove(id);
}
// this will take the object out of the map and then destruct it
bool ok = m_active_objects.remove(id);
if (!ok) {
@ -123,34 +142,42 @@ void ActiveObjectMgr::getObjectsInsideRadius(const v3f &pos, float radius,
std::function<bool(ServerActiveObject *obj)> include_obj_cb)
{
float r2 = radius * radius;
for (auto &activeObject : m_active_objects.iter()) {
ServerActiveObject *obj = activeObject.second.get();
if (!obj)
continue;
aabb3f bounds(pos.X-radius, pos.Y-radius, pos.Z-radius,
pos.X+radius, pos.Y+radius, pos.Z+radius);
m_spatial_map.getRelevantObjectIds(bounds, [&](u16 id) {
auto obj = m_active_objects.get(id).get();
if (!obj) { // should never be hit
m_spatial_map.remove(id);
return;
}
const v3f &objectpos = obj->getBasePosition();
if (objectpos.getDistanceFromSQ(pos) > r2)
continue;
return;
if (!include_obj_cb || include_obj_cb(obj))
result.push_back(obj);
}
});
}
void ActiveObjectMgr::getObjectsInArea(const aabb3f &box,
std::vector<ServerActiveObject *> &result,
std::function<bool(ServerActiveObject *obj)> include_obj_cb)
{
for (auto &activeObject : m_active_objects.iter()) {
ServerActiveObject *obj = activeObject.second.get();
if (!obj)
continue;
m_spatial_map.getRelevantObjectIds(box,[&](u16 id) {
auto obj = m_active_objects.get(id).get();
if (!obj) { // should never be hit
m_spatial_map.remove(id);
return;
}
const v3f &objectpos = obj->getBasePosition();
if (!box.isPointInside(objectpos))
continue;
return;
if (!include_obj_cb || include_obj_cb(obj))
result.push_back(obj);
}
});
}
void ActiveObjectMgr::getAddedActiveObjectsAroundPos(v3f player_pos, f32 radius,
@ -164,32 +191,35 @@ void ActiveObjectMgr::getAddedActiveObjectsAroundPos(v3f player_pos, f32 radius,
- discard objects that are found in current_objects.
- add remaining objects to added_objects
*/
for (auto &ao_it : m_active_objects.iter()) {
u16 id = ao_it.first;
// Get object
ServerActiveObject *object = ao_it.second.get();
if (!object)
continue;
if (object->isGone())
continue;
f32 distance_f = object->getBasePosition().getDistanceFrom(player_pos);
if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
f32 offset = radius > player_radius ? radius : player_radius;
aabb3f bounds(player_pos.X-offset, player_pos.Y-offset, player_pos.Z-offset,
player_pos.X+offset, player_pos.Y+offset, player_pos.Z+offset);
m_spatial_map.getRelevantObjectIds(bounds, [&](u16 id) {
auto obj = m_active_objects.get(id).get();
if (!obj) { // should never be hit
m_spatial_map.remove(id);
return;
}
if (obj->isGone()) {
return;
}
f32 distance_f = obj->getBasePosition().getDistanceFrom(player_pos);
if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
// Discard if too far
if (distance_f > player_radius && player_radius != 0)
continue;
return;
} else if (distance_f > radius)
continue;
return;
// Discard if already on current_objects
auto n = current_objects.find(id);
if (n != current_objects.end())
continue;
return;
// Add to added_objects
added_objects.push_back(id);
}
});
}
} // namespace server

@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <vector>
#include "../activeobjectmgr.h"
#include "serveractiveobject.h"
#include "spatial_map.h"
namespace server
{
@ -35,8 +36,10 @@ class ActiveObjectMgr final : public ::ActiveObjectMgr<ServerActiveObject>
void clearIf(const std::function<bool(ServerActiveObject *, u16)> &cb);
void step(float dtime,
const std::function<void(ServerActiveObject *)> &f) override;
void clear();
bool registerObject(std::unique_ptr<ServerActiveObject> obj) override;
void removeObject(u16 id) override;
void updateObjectPosition(u16 id, const v3f &last_position, const v3f &new_position);
void getObjectsInsideRadius(const v3f &pos, float radius,
std::vector<ServerActiveObject *> &result,
@ -48,5 +51,7 @@ class ActiveObjectMgr final : public ::ActiveObjectMgr<ServerActiveObject>
void getAddedActiveObjectsAroundPos(v3f player_pos, f32 radius,
f32 player_radius, const std::set<u16> &current_objects,
std::vector<u16> &added_objects);
protected:
SpatialMap m_spatial_map;
};
} // namespace server

@ -162,7 +162,7 @@ void LuaEntitySAO::step(float dtime, bool send_recommended)
// Each frame, parent position is copied if the object is attached, otherwise it's calculated normally
// If the object gets detached this comes into effect automatically from the last known origin
if (auto *parent = getParent()) {
m_base_position = parent->getBasePosition();
setBasePosition(parent->getBasePosition());
m_velocity = v3f(0,0,0);
m_acceleration = v3f(0,0,0);
} else {
@ -171,7 +171,7 @@ void LuaEntitySAO::step(float dtime, bool send_recommended)
box.MinEdge *= BS;
box.MaxEdge *= BS;
f32 pos_max_d = BS*0.25; // Distance per iteration
v3f p_pos = m_base_position;
v3f p_pos = getBasePosition();
v3f p_velocity = m_velocity;
v3f p_acceleration = m_acceleration;
moveresult = collisionMoveSimple(m_env, m_env->getGameDef(),
@ -181,11 +181,11 @@ void LuaEntitySAO::step(float dtime, bool send_recommended)
moveresult_p = &moveresult;
// Apply results
m_base_position = p_pos;
setBasePosition(p_pos);
m_velocity = p_velocity;
m_acceleration = p_acceleration;
} else {
m_base_position += (m_velocity + m_acceleration * 0.5f * dtime) * dtime;
setBasePosition(getBasePosition() + (m_velocity + m_acceleration * 0.5f * dtime) * dtime);
m_velocity += dtime * m_acceleration;
}
@ -228,7 +228,7 @@ void LuaEntitySAO::step(float dtime, bool send_recommended)
} else if(m_last_sent_position_timer > 0.2){
minchange = 0.05*BS;
}
float move_d = m_base_position.getDistanceFrom(m_last_sent_position);
float move_d = getBasePosition().getDistanceFrom(m_last_sent_position);
move_d += m_last_sent_move_precision;
float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity);
if (move_d > minchange || vel_d > minchange ||
@ -252,7 +252,7 @@ std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version)
os << serializeString16(m_init_name); // name
writeU8(os, 0); // is_player
writeU16(os, getId()); //id
writeV3F32(os, m_base_position);
writeV3F32(os, getBasePosition());
writeV3F32(os, m_rotation);
writeU16(os, m_hp);
@ -381,7 +381,7 @@ void LuaEntitySAO::setPos(const v3f &pos)
{
if(isAttached())
return;
m_base_position = pos;
setBasePosition(pos);
sendPosition(false, true);
}
@ -389,7 +389,7 @@ void LuaEntitySAO::moveTo(v3f pos, bool continuous)
{
if(isAttached())
return;
m_base_position = pos;
setBasePosition(pos);
if(!continuous)
sendPosition(true, true);
}
@ -403,7 +403,7 @@ std::string LuaEntitySAO::getDescription()
{
std::ostringstream oss;
oss << "LuaEntitySAO \"" << m_init_name << "\" ";
auto pos = floatToInt(m_base_position, BS);
auto pos = floatToInt(getBasePosition(), BS);
oss << "at " << pos;
return oss.str();
}
@ -521,10 +521,10 @@ void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
// Send attachment updates instantly to the client prior updating position
sendOutdatedData();
m_last_sent_move_precision = m_base_position.getDistanceFrom(
m_last_sent_move_precision = getBasePosition().getDistanceFrom(
m_last_sent_position);
m_last_sent_position_timer = 0;
m_last_sent_position = m_base_position;
m_last_sent_position = getBasePosition();
m_last_sent_velocity = m_velocity;
//m_last_sent_acceleration = m_acceleration;
m_last_sent_rotation = m_rotation;
@ -532,7 +532,7 @@ void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
float update_interval = m_env->getSendRecommendedInterval();
std::string str = generateUpdatePositionCommand(
m_base_position,
getBasePosition(),
m_velocity,
m_acceleration,
m_rotation,
@ -552,8 +552,8 @@ bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
toset->MinEdge += m_base_position;
toset->MaxEdge += m_base_position;
toset->MinEdge += getBasePosition();
toset->MaxEdge += getBasePosition();
return true;
}

@ -40,7 +40,7 @@ class LuaEntitySAO : public UnitSAO
ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_LUAENTITY; }
ActiveObjectType getSendType() const { return ACTIVEOBJECT_TYPE_GENERIC; }
virtual void addedToEnvironment(u32 dtime_s);
void step(float dtime, bool send_recommended);
void step(float dtime, bool send_recommended) override;
std::string getClientInitializationData(u16 protocol_version);
bool isStaticAllowed() const { return m_prop.static_save; }

@ -86,11 +86,11 @@ std::string PlayerSAO::getDescription()
void PlayerSAO::addedToEnvironment(u32 dtime_s)
{
ServerActiveObject::addedToEnvironment(dtime_s);
ServerActiveObject::setBasePosition(m_base_position);
ServerActiveObject::setBasePosition(getBasePosition());
m_player->setPlayerSAO(this);
m_player->setPeerId(m_peer_id_initial);
m_peer_id_initial = PEER_ID_INEXISTENT; // don't try to use it again.
m_last_good_position = m_base_position;
m_last_good_position = getBasePosition();
}
// Called before removing from environment
@ -116,7 +116,7 @@ std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
os << serializeString16(m_player->getName()); // name
writeU8(os, 1); // is_player
writeS16(os, getId()); // id
writeV3F32(os, m_base_position);
writeV3F32(os, getBasePosition());
writeV3F32(os, m_rotation);
writeU16(os, getHP());
@ -195,7 +195,7 @@ void PlayerSAO::step(float dtime, bool send_recommended)
// Sequence of damage points, starting 0.1 above feet and progressing
// upwards in 1 node intervals, stopping below top damage point.
for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) {
v3s16 p = floatToInt(m_base_position +
v3s16 p = floatToInt(getBasePosition() +
v3f(0.0f, dam_height * BS, 0.0f), BS);
MapNode n = m_env->getMap().getNode(p);
const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
@ -207,7 +207,7 @@ void PlayerSAO::step(float dtime, bool send_recommended)
}
// Top damage point
v3s16 ptop = floatToInt(m_base_position +
v3s16 ptop = floatToInt(getBasePosition() +
v3f(0.0f, dam_top * BS, 0.0f), BS);
MapNode ntop = m_env->getMap().getNode(ptop);
const ContentFeatures &c = m_env->getGameDef()->ndef()->get(ntop);
@ -285,7 +285,7 @@ void PlayerSAO::step(float dtime, bool send_recommended)
if (isAttached())
pos = m_last_good_position;
else
pos = m_base_position;
pos = getBasePosition();
std::string str = generateUpdatePositionCommand(
pos,
@ -342,9 +342,9 @@ std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() const
return os.str();
}
void PlayerSAO::setBasePosition(v3f position)
void PlayerSAO::setBasePosition(const v3f &position)
{
if (m_player && position != m_base_position)
if (m_player && position != getBasePosition())
m_player->setDirty(true);
// This needs to be ran for attachments too
@ -397,7 +397,7 @@ void PlayerSAO::addPos(const v3f &added_pos)
m_env->getGameDef()->SendMovePlayerRel(getPeerID(), added_pos);
}
void PlayerSAO::moveTo(v3f pos, bool continuous)
void PlayerSAO::moveTo(const v3f &pos, bool continuous)
{
if(isAttached())
return;
@ -629,7 +629,7 @@ bool PlayerSAO::checkMovementCheat()
if (m_is_singleplayer ||
isAttached() ||
g_settings->getBool("disable_anticheat")) {
m_last_good_position = m_base_position;
m_last_good_position = getBasePosition();
return false;
}
@ -694,7 +694,7 @@ bool PlayerSAO::checkMovementCheat()
if (player_max_jump < 0.0001f)
player_max_jump = 0.0001f;
v3f diff = (m_base_position - m_last_good_position);
v3f diff = (getBasePosition() - m_last_good_position);
float d_vert = diff.Y;
diff.Y = 0;
float d_horiz = diff.getLength();
@ -710,7 +710,7 @@ bool PlayerSAO::checkMovementCheat()
}
if (m_move_pool.grab(required_time)) {
m_last_good_position = m_base_position;
m_last_good_position = getBasePosition();
} else {
const float LAG_POOL_MIN = 5.0;
float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
@ -732,8 +732,8 @@ bool PlayerSAO::getCollisionBox(aabb3f *toset) const
toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
toset->MinEdge += m_base_position;
toset->MaxEdge += m_base_position;
toset->MinEdge += getBasePosition();
toset->MaxEdge += getBasePosition();
return true;
}

@ -88,10 +88,10 @@ class PlayerSAO : public UnitSAO
std::string getClientInitializationData(u16 protocol_version) override;
void getStaticData(std::string *result) const override;
void step(float dtime, bool send_recommended) override;
void setBasePosition(v3f position);
void setBasePosition(const v3f &position);
void setPos(const v3f &pos) override;
void addPos(const v3f &added_pos) override;
void moveTo(v3f pos, bool continuous) override;
void moveTo(const v3f &pos, bool continuous) override;
void setPlayerYaw(const float yaw);
// Data should not be sent at player initialization
void setPlayerYawAndSend(const float yaw);
@ -182,7 +182,7 @@ class PlayerSAO : public UnitSAO
void finalize(RemotePlayer *player, const std::set<std::string> &privs);
v3f getEyePosition() const { return m_base_position + getEyeOffset(); }
v3f getEyePosition() const { return getBasePosition() + getEyeOffset(); }
v3f getEyeOffset() const;
float getZoomFOV() const;

@ -18,6 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/
#include "serveractiveobject.h"
#include "serverenvironment.h"
#include <fstream>
#include "inventory.h"
#include "inventorymanager.h"
@ -31,6 +32,13 @@ ServerActiveObject::ServerActiveObject(ServerEnvironment *env, v3f pos):
{
}
void ServerActiveObject::setBasePosition(const v3f &pos) {
bool changed = m_base_position != pos;
if (changed && getEnv())
getEnv()->updateObjectPosition(getId(), m_base_position, pos);
m_base_position = pos;
}
float ServerActiveObject::getMinimumSavedMovement()
{
return 2.0*BS;

@ -77,7 +77,7 @@ class ServerActiveObject : public ActiveObject
Some simple getters/setters
*/
v3f getBasePosition() const { return m_base_position; }
void setBasePosition(v3f pos){ m_base_position = pos; }
void setBasePosition(const v3f &pos);
ServerEnvironment* getEnv(){ return m_env; }
/*
@ -87,9 +87,9 @@ class ServerActiveObject : public ActiveObject
virtual void setPos(const v3f &pos)
{ setBasePosition(pos); }
virtual void addPos(const v3f &added_pos)
{ setBasePosition(m_base_position + added_pos); }
{ setBasePosition(getBasePosition() + added_pos); }
// continuous: if true, object does not stop immediately at pos
virtual void moveTo(v3f pos, bool continuous)
virtual void moveTo(const v3f &pos, bool continuous)
{ setBasePosition(pos); }
// If object has moved less than this and data has not changed,
// saving to disk may be omitted
@ -244,7 +244,6 @@ class ServerActiveObject : public ActiveObject
virtual void onDetach(int parent_id) {}
ServerEnvironment *m_env;
v3f m_base_position;
std::unordered_set<u32> m_attached_particle_spawners;
/*
@ -272,4 +271,6 @@ class ServerActiveObject : public ActiveObject
Queue of messages to be sent to the client
*/
std::queue<ActiveObjectMessage> m_messages_out;
private:
v3f m_base_position; // setBasePosition updates index and MUST be called
};

172
src/server/spatial_map.cpp Normal file

@ -0,0 +1,172 @@
/*
Minetest
Copyright (C) 2024, ExeVirus <nodecastmt@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "spatial_map.h"
#include <algorithm>
namespace server
{
// all inserted entires go into the uncached vector
void SpatialMap::insert(u16 id, const v3f &pos, bool postIteration)
{
if (m_iterators_stopping_insertion_and_deletion) {
m_pending_inserts.insert(SpatialKey(pos, id));
return;
}
SpatialKey key(pos);
if (postIteration)
key = SpatialKey(pos.X, pos.Y, pos.Z, true);
m_cached.insert({key, id});
}
// Invalidates upon position update
void SpatialMap::updatePosition(u16 id, const v3f &oldPos, const v3f &newPos)
{
// Try to leave early if already in the same bucket:
auto range = m_cached.equal_range(SpatialKey(newPos));
for (auto it = range.first; it != range.second; ++it) {
if (it->second == id) {
return; // all good, let's get out of here
}
}
remove(id, oldPos); // remove from old cache position
insert(id, newPos); // reinsert
}
void SpatialMap::remove(u16 id, const v3f &pos, bool postIteration)
{
if (m_iterators_stopping_insertion_and_deletion) {
m_pending_deletes.insert(SpatialKey(pos, id));
return;
}
SpatialKey key(pos);
if (postIteration)
key = SpatialKey(pos.X, pos.Y, pos.Z, true);
if(m_cached.find(key) != m_cached.end()) {
auto range = m_cached.equal_range(key);
for (auto it = range.first; it != range.second; ++it) {
if (it->second == id) {
m_cached.erase(it);
return; // Erase and leave early
}
}
}
remove(id); // should never be hit
}
void SpatialMap::remove(u16 id)
{
if(m_iterators_stopping_insertion_and_deletion) {
m_pending_deletes.insert(SpatialKey(v3f(), id));
return;
}
for (auto it = m_cached.begin(); it != m_cached.end(); ++it) {
if (it->second == id) {
m_cached.erase(it);
return; // Erase and leave early
}
}
}
void SpatialMap::removeAll()
{
if(m_iterators_stopping_insertion_and_deletion) {
m_remove_all = true;
} else {
m_cached.clear();
}
}
void SpatialMap::getRelevantObjectIds(const aabb3f &box, const std::function<void(u16 id)> &callback)
{
if (m_cached.empty()) return;
// When searching, we must round to maximum extent of relevant mapblock indexes.
// Since we're using floats, always assume +-1
auto low = [](f32 val) -> s16 {
s16 _val = static_cast<s16>(val / BS);
return (_val >> 4) - 1;
};
auto high = [](f32 val) -> s16 {
s16 _val = static_cast<s16>(val / BS);
return (_val >> 4) + 1;
};
v3s16 min(low(box.MinEdge.X), low(box.MinEdge.Y), low(box.MinEdge.Z)),
max(high(box.MaxEdge.X), high(box.MaxEdge.Y), high(box.MaxEdge.Z));
// We should only iterate using this spatial map when there are at least 1 objects per mapblocks to check.
// Otherwise, might as well just iterate.
v3s16 diff = max - min;
uint64_t number_of_mapblocks_to_check = std::abs(diff.X) * std::abs(diff.Y) * std::abs(diff.Z);
if(number_of_mapblocks_to_check <= m_cached.size()) { // might be worth it
for (s16 x = min.X; x < max.X;x++) {
for (s16 y = min.Y; y < max.Y;y++) {
for (s16 z = min.Z; z < max.Z;z++) {
SpatialKey key(x,y,z, false);
if (m_cached.find(key) != m_cached.end()) {
m_iterators_stopping_insertion_and_deletion++;
auto range = m_cached.equal_range(key);
for (auto &it = range.first; it != range.second; ++it) {
callback(it->second);
}
m_iterators_stopping_insertion_and_deletion--;
handleInsertsAndDeletes();
}
}
}
}
} else { // let's just iterate, it'll be faster
m_iterators_stopping_insertion_and_deletion++;
for (auto it = m_cached.begin(); it != m_cached.end(); ++it) {
callback(it->second);
}
m_iterators_stopping_insertion_and_deletion--;
handleInsertsAndDeletes();
}
}
void SpatialMap::handleInsertsAndDeletes()
{
if (m_iterators_stopping_insertion_and_deletion)
return;
if(!m_remove_all) {
for (auto key : m_pending_deletes) {
remove(key.padding_or_optional_id, v3f(key.x, key.y, key.z), true);
}
for (auto key : m_pending_inserts) {
insert(key.padding_or_optional_id, v3f(key.x, key.y, key.z), true);
}
} else {
m_cached.clear();
m_remove_all = false;
}
m_pending_inserts.clear();
m_pending_deletes.clear();
}
} // namespace server

83
src/server/spatial_map.h Normal file

@ -0,0 +1,83 @@
/*
Minetest
Copyright (C) 2024, ExeVirus <nodecastmt@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include <functional>
#include <vector>
#include <unordered_set>
#include "irrlichttypes_bloated.h"
#include "constants.h"
namespace server
{
class SpatialMap
{
public:
void insert(u16 id, const v3f &pos, bool postIteration = false);
void remove(u16 id, const v3f &pos, bool postIteration = false);
void remove(u16 id);
void removeAll();
void updatePosition(u16 id, const v3f &oldPos, const v3f &newPos);
void getRelevantObjectIds(const aabb3f &box, const std::function<void(u16 id)> &callback);
void handleInsertsAndDeletes();
protected:
struct SpatialKey {
u16 padding_or_optional_id{0};
s16 x;
s16 y;
s16 z;
SpatialKey(s16 _x, s16 _y, s16 _z, bool _shrink = true) {
if(_shrink) {
x = _x >> 4;
y = _y >> 4;
z = _z >> 4;
} else {
x = _x;
y = _y;
z = _z;
}
}
SpatialKey(const v3f &_pos) : SpatialKey(_pos.X / BS, _pos.Y / BS, _pos.Z / BS){}
// The following use case is for storing pending insertions and deletions while iterating
// using the extra 16 bit padding makes keeping track of them super efficient for hashing.
SpatialKey(const v3f &_pos, const u16 id) : SpatialKey(_pos.X / BS, _pos.Y / BS, _pos.Z / BS, false){
padding_or_optional_id = id;
}
bool operator==(const SpatialKey &other) const {
return (x == other.x && y == other.y && z == other.z);
}
};
struct SpatialKeyHash {
auto operator()(const SpatialKey &key) const -> size_t {
return std::hash<size_t>()(*reinterpret_cast<const size_t*>(&key));
}
};
std::unordered_multimap<SpatialKey, u16, SpatialKeyHash> m_cached;
std::unordered_set<SpatialKey, SpatialKeyHash> m_pending_inserts;
std::unordered_set<SpatialKey, SpatialKeyHash> m_pending_deletes;
bool m_remove_all{false};
u64 m_iterators_stopping_insertion_and_deletion{0};
};
} // namespace server

@ -1871,10 +1871,12 @@ void ServerEnvironment::getSelectedActiveObjects(
return false;
};
aabb3f search_area(shootline_on_map.start - 5 * BS, shootline_on_map.end + 5 * BS);
search_area.repair();
// Use "logic in callback" pattern to avoid useless vector filling
std::vector<ServerActiveObject*> tmp;
getObjectsInsideRadius(tmp, shootline_on_map.getMiddle(),
0.5 * shootline_on_map.getLength() + 5 * BS, process);
getObjectsInArea(tmp, search_area, process);
}
/*

@ -348,6 +348,11 @@ class ServerEnvironment final : public Environment
return m_ao_manager.getObjectsInArea(box, objects, include_obj_cb);
}
void updateObjectPosition(u16 id, const v3f &last_position, const v3f &new_position)
{
m_ao_manager.updateObjectPosition(id, last_position, new_position);
}
// Clear objects, loading and going through every MapBlock
void clearObjects(ClearObjectsMode mode);