51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include <algorithm>
#include "serverenvironment.h"
-#include "content_sao.h"
#include "settings.h"
#include "log.h"
#include "mapblock.h"
#include "nodemetadata.h"
#include "gamedef.h"
#include "map.h"
+#include "porting.h"
#include "profiler.h"
#include "raycast.h"
#include "remoteplayer.h"
#if USE_POSTGRESQL
#include "database/database-postgresql.h"
#endif
-#include <algorithm>
+#if USE_LEVELDB
+#include "database/database-leveldb.h"
+#endif
+#include "server/luaentity_sao.h"
+#include "server/player_sao.h"
#define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:"
v3s16 pos;
MapNode n;
content_t c;
- lbm_lookup_map::const_iterator it = getLBMsIntroducedAfter(stamp);
- for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++)
- for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++)
- for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++)
- {
- n = block->getNodeNoEx(pos);
- c = n.getContent();
- for (LBMManager::lbm_lookup_map::const_iterator iit = it;
- iit != m_lbm_lookup.end(); ++iit) {
- const std::vector<LoadingBlockModifierDef *> *lbm_list =
- iit->second.lookup(c);
+ auto it = getLBMsIntroducedAfter(stamp);
+ for (; it != m_lbm_lookup.end(); ++it) {
+ // Cache previous version to speedup lookup which has a very high performance
+ // penalty on each call
+ content_t previous_c = CONTENT_IGNORE;
+ const std::vector<LoadingBlockModifierDef *> *lbm_list = nullptr;
+
+ for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++)
+ for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++)
+ for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) {
+ n = block->getNodeNoCheck(pos, nullptr);
+ c = n.getContent();
+
+ // If content_t are not matching perform an LBM lookup
+ if (previous_c != c) {
+ lbm_list = it->second.lookup(c);
+ previous_c = c;
+ }
+
if (!lbm_list)
continue;
for (auto lbmdef : *lbm_list) {
lbmdef->trigger(env, pos + pos_of_block, n);
}
}
- }
+ }
}
/*
// only do this if this would add blocks
if (player_ao_range > active_block_range) {
v3f camera_dir = v3f(0,0,1);
- camera_dir.rotateYZBy(playersao->getPitch());
- camera_dir.rotateXZBy(playersao->getYaw());
+ camera_dir.rotateYZBy(playersao->getLookPitch());
+ camera_dir.rotateXZBy(playersao->getRotation().Y);
fillViewConeBlock(pos,
player_ao_range,
playersao->getEyePosition(),
/*
Update m_list
*/
- m_list.clear();
- for (v3s16 p : newlist) {
- m_list.insert(p);
- }
+ m_list = std::move(newlist);
}
/*
ServerEnvironment
*/
+// Random device to seed pseudo random generators.
+static std::random_device seed;
+
ServerEnvironment::ServerEnvironment(ServerMap *map,
ServerScripting *scriptIface, Server *server,
- const std::string &path_world):
+ const std::string &path_world, MetricsBackend *mb):
Environment(server),
m_map(map),
m_script(scriptIface),
m_server(server),
- m_path_world(path_world)
+ m_path_world(path_world),
+ m_rgen(seed())
{
// Determine which database backend to use
std::string conf_path = path_world + DIR_DELIM + "world.mt";
Settings conf;
+
+ std::string player_backend_name = "sqlite3";
+ std::string auth_backend_name = "sqlite3";
+
bool succeeded = conf.readConfigFile(conf_path.c_str());
- if (!succeeded || !conf.exists("player_backend")) {
- // fall back to files
- conf.set("player_backend", "files");
+
+ // If we open world.mt read the backend configurations.
+ if (succeeded) {
+ // Read those values before setting defaults
+ bool player_backend_exists = conf.exists("player_backend");
+ bool auth_backend_exists = conf.exists("auth_backend");
+
+ // player backend is not set, assume it's legacy file backend.
+ if (!player_backend_exists) {
+ // fall back to files
+ conf.set("player_backend", "files");
+ player_backend_name = "files";
+
+ if (!conf.updateConfigFile(conf_path.c_str())) {
+ errorstream << "ServerEnvironment::ServerEnvironment(): "
+ << "Failed to update world.mt!" << std::endl;
+ }
+ } else {
+ conf.getNoEx("player_backend", player_backend_name);
+ }
+
+ // auth backend is not set, assume it's legacy file backend.
+ if (!auth_backend_exists) {
+ conf.set("auth_backend", "files");
+ auth_backend_name = "files";
+
+ if (!conf.updateConfigFile(conf_path.c_str())) {
+ errorstream << "ServerEnvironment::ServerEnvironment(): "
+ << "Failed to update world.mt!" << std::endl;
+ }
+ } else {
+ conf.getNoEx("auth_backend", auth_backend_name);
+ }
+ }
+
+ if (player_backend_name == "files") {
warningstream << "/!\\ You are using old player file backend. "
- << "This backend is deprecated and will be removed in next release /!\\"
+ << "This backend is deprecated and will be removed in a future release /!\\"
<< std::endl << "Switching to SQLite3 or PostgreSQL is advised, "
<< "please read http://wiki.minetest.net/Database_backends." << std::endl;
+ }
- if (!conf.updateConfigFile(conf_path.c_str())) {
- errorstream << "ServerEnvironment::ServerEnvironment(): "
- << "Failed to update world.mt!" << std::endl;
- }
+ if (auth_backend_name == "files") {
+ warningstream << "/!\\ You are using old auth file backend. "
+ << "This backend is deprecated and will be removed in a future release /!\\"
+ << std::endl << "Switching to SQLite3 is advised, "
+ << "please read http://wiki.minetest.net/Database_backends." << std::endl;
}
- std::string name;
- conf.getNoEx("player_backend", name);
- m_player_database = openPlayerDatabase(name, path_world, conf);
+ m_player_database = openPlayerDatabase(player_backend_name, path_world, conf);
+ m_auth_database = openAuthDatabase(auth_backend_name, path_world, conf);
+
+ m_step_time_counter = mb->addCounter(
+ "minetest_env_step_time", "Time spent in environment step (in microseconds)");
+
+ m_active_block_gauge = mb->addGauge(
+ "minetest_env_active_blocks", "Number of active blocks");
+
+ m_active_object_gauge = mb->addGauge(
+ "minetest_env_active_objects", "Number of active objects");
}
ServerEnvironment::~ServerEnvironment()
}
delete m_player_database;
+ delete m_auth_database;
}
Map & ServerEnvironment::getMap()
return m_player_database->removePlayer(name);
}
-bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, v3s16 *p)
-{
- // Iterate trough nodes on the line
- voxalgo::VoxelLineIterator iterator(pos1 / BS, (pos2 - pos1) / BS);
- do {
- MapNode n = getMap().getNodeNoEx(iterator.m_current_node_pos);
-
- // Return non-air
- if (n.param0 != CONTENT_AIR) {
- if (p)
- *p = iterator.m_current_node_pos;
- return false;
- }
- iterator.next();
- } while (iterator.m_current_index <= iterator.m_last_index);
- return true;
-}
-
void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason,
const std::string &str_reason, bool reconnect)
{
- for (RemotePlayer *player : m_players) {
- m_server->DenyAccessVerCompliant(player->getPeerId(),
- player->protocol_version, reason, str_reason, reconnect);
- }
+ for (RemotePlayer *player : m_players)
+ m_server->DenyAccess(player->getPeerId(), reason, str_reason, reconnect);
}
-void ServerEnvironment::saveLoadedPlayers()
+void ServerEnvironment::saveLoadedPlayers(bool force)
{
- std::string players_path = m_path_world + DIR_DELIM + "players";
- fs::CreateDir(players_path);
-
for (RemotePlayer *player : m_players) {
- if (player->checkModified() || (player->getPlayerSAO() &&
- player->getPlayerSAO()->extendedAttributesModified())) {
+ if (force || player->checkModified() || (player->getPlayerSAO() &&
+ player->getPlayerSAO()->getMeta().isModified())) {
try {
m_player_database->savePlayer(player);
} catch (DatabaseException &e) {
/* Add object to environment */
addActiveObject(playersao);
+ // Update active blocks asap so objects in those blocks appear on the client
+ m_force_update_active_blocks = true;
+
return playersao;
}
void ServerEnvironment::saveMeta()
{
+ if (!m_meta_loaded)
+ return;
+
std::string path = m_path_world + DIR_DELIM "env_meta.txt";
// Open file and serialize
std::ostringstream ss(std::ios_base::binary);
- Settings args;
+ Settings args("EnvArgsEnd");
args.setU64("game_time", m_game_time);
args.setU64("time_of_day", getTimeOfDay());
args.setU64("last_clear_objects_time", m_last_clear_objects_time);
m_lbm_mgr.createIntroductionTimesString());
args.setU64("day_count", m_day_count);
args.writeLines(ss);
- ss<<"EnvArgsEnd\n";
if(!fs::safeWriteToFile(path, ss.str()))
{
void ServerEnvironment::loadMeta()
{
+ SANITY_CHECK(!m_meta_loaded);
+ m_meta_loaded = true;
+
// If file doesn't exist, load default environment metadata
if (!fs::PathExists(m_path_world + DIR_DELIM "env_meta.txt")) {
infostream << "ServerEnvironment: Loading default environment metadata"
throw SerializationError("Couldn't load env meta");
}
- Settings args;
+ Settings args("EnvArgsEnd");
- if (!args.parseConfigLines(is, "EnvArgsEnd")) {
+ if (!args.parseConfigLines(is)) {
throw SerializationError("ServerEnvironment::loadMeta(): "
"EnvArgsEnd not found!");
}
int chance;
std::vector<content_t> required_neighbors;
bool check_required_neighbors; // false if required_neighbors is known to be empty
+ s16 min_y;
+ s16 max_y;
};
class ABMHandler
} else {
aabm.chance = chance;
}
+ // y limits
+ aabm.min_y = abm->getMinY();
+ aabm.max_y = abm->getMaxY();
// Trigger neighbors
const std::vector<std::string> &required_neighbors_s =
return active_object_count;
}
- void apply(MapBlock *block)
+ void apply(MapBlock *block, int &blocks_scanned, int &abms_run, int &blocks_cached)
{
if(m_aabms.empty() || block->isDummy())
return;
+ // Check the content type cache first
+ // to see whether there are any ABMs
+ // to be run at all for this block.
+ if (block->contents_cached) {
+ blocks_cached++;
+ bool run_abms = false;
+ for (content_t c : block->contents) {
+ if (c < m_aabms.size() && m_aabms[c]) {
+ run_abms = true;
+ break;
+ }
+ }
+ if (!run_abms)
+ return;
+ } else {
+ // Clear any caching
+ block->contents.clear();
+ }
+ blocks_scanned++;
+
ServerMap *map = &m_env->getServerMap();
u32 active_object_count_wider;
{
const MapNode &n = block->getNodeUnsafe(p0);
content_t c = n.getContent();
+ // Cache content types as we go
+ if (!block->contents_cached && !block->do_not_cache_contents) {
+ block->contents.insert(c);
+ if (block->contents.size() > 64) {
+ // Too many different nodes... don't try to cache
+ block->do_not_cache_contents = true;
+ block->contents.clear();
+ }
+ }
if (c >= m_aabms.size() || !m_aabms[c])
continue;
v3s16 p = p0 + block->getPosRelative();
for (ActiveABM &aabm : *m_aabms[c]) {
+ if ((p.Y < aabm.min_y) || (p.Y > aabm.max_y))
+ continue;
+
if (myrand() % aabm.chance != 0)
continue;
c = n.getContent();
} else {
// otherwise consult the map
- MapNode n = map->getNodeNoEx(p1 + block->getPosRelative());
+ MapNode n = map->getNode(p1 + block->getPosRelative());
c = n.getContent();
}
if (CONTAINS(aabm.required_neighbors, c))
}
neighbor_found:
+ abms_run++;
// Call all the trigger variations
aabm.abm->trigger(m_env, p, n);
aabm.abm->trigger(m_env, p, n,
}
}
}
+ block->contents_cached = !block->do_not_cache_contents;
}
};
bool ServerEnvironment::setNode(v3s16 p, const MapNode &n)
{
const NodeDefManager *ndef = m_server->ndef();
- MapNode n_old = m_map->getNodeNoEx(p);
+ MapNode n_old = m_map->getNode(p);
const ContentFeatures &cf_old = ndef->get(n_old);
bool ServerEnvironment::removeNode(v3s16 p)
{
const NodeDefManager *ndef = m_server->ndef();
- MapNode n_old = m_map->getNodeNoEx(p);
+ MapNode n_old = m_map->getNode(p);
// Call destructor
if (ndef->get(n_old).has_on_destruct)
return true;
}
-void ServerEnvironment::getObjectsInsideRadius(std::vector<u16> &objects, v3f pos,
- float radius)
+u8 ServerEnvironment::findSunlight(v3s16 pos) const
{
- for (auto &activeObject : m_active_objects) {
- ServerActiveObject* obj = activeObject.second;
- u16 id = activeObject.first;
- v3f objectpos = obj->getBasePosition();
- if (objectpos.getDistanceFrom(pos) > radius)
- continue;
- objects.push_back(id);
+ // Directions for neighbouring nodes with specified order
+ static const v3s16 dirs[] = {
+ v3s16(-1, 0, 0), v3s16(1, 0, 0), v3s16(0, 0, -1), v3s16(0, 0, 1),
+ v3s16(0, -1, 0), v3s16(0, 1, 0)
+ };
+
+ const NodeDefManager *ndef = m_server->ndef();
+
+ // found_light remembers the highest known sunlight value at pos
+ u8 found_light = 0;
+
+ struct stack_entry {
+ v3s16 pos;
+ s16 dist;
+ };
+ std::stack<stack_entry> stack;
+ stack.push({pos, 0});
+
+ std::unordered_map<s64, s8> dists;
+ dists[MapDatabase::getBlockAsInteger(pos)] = 0;
+
+ while (!stack.empty()) {
+ struct stack_entry e = stack.top();
+ stack.pop();
+
+ v3s16 currentPos = e.pos;
+ s8 dist = e.dist + 1;
+
+ for (const v3s16& off : dirs) {
+ v3s16 neighborPos = currentPos + off;
+ s64 neighborHash = MapDatabase::getBlockAsInteger(neighborPos);
+
+ // Do not walk neighborPos multiple times unless the distance to the start
+ // position is shorter
+ auto it = dists.find(neighborHash);
+ if (it != dists.end() && dist >= it->second)
+ continue;
+
+ // Position to walk
+ bool is_position_ok;
+ MapNode node = m_map->getNode(neighborPos, &is_position_ok);
+ if (!is_position_ok) {
+ // This happens very rarely because the map at currentPos is loaded
+ m_map->emergeBlock(neighborPos, false);
+ node = m_map->getNode(neighborPos, &is_position_ok);
+ if (!is_position_ok)
+ continue; // not generated
+ }
+
+ const ContentFeatures &def = ndef->get(node);
+ if (!def.sunlight_propagates) {
+ // Do not test propagation here again
+ dists[neighborHash] = -1;
+ continue;
+ }
+
+ // Sunlight could have come from here
+ dists[neighborHash] = dist;
+ u8 daylight = node.param1 & 0x0f;
+
+ // In the special case where sunlight shines from above and thus
+ // does not decrease with upwards distance, daylight is always
+ // bigger than nightlight, which never reaches 15
+ int possible_finlight = daylight - dist;
+ if (possible_finlight <= found_light) {
+ // Light from here cannot make a brighter light at currentPos than
+ // found_light
+ continue;
+ }
+
+ u8 nightlight = node.param1 >> 4;
+ if (daylight > nightlight) {
+ // Found a valid daylight
+ found_light = possible_finlight;
+ } else {
+ // Sunlight may be darker, so walk the neighbours
+ stack.push({neighborPos, dist});
+ }
+ }
}
+ return found_light;
}
void ServerEnvironment::clearObjects(ClearObjectsMode mode)
{
infostream << "ServerEnvironment::clearObjects(): "
<< "Removing all active objects" << std::endl;
- std::vector<u16> objects_to_remove;
- for (auto &it : m_active_objects) {
- u16 id = it.first;
- ServerActiveObject* obj = it.second;
+ auto cb_removal = [this] (ServerActiveObject *obj, u16 id) {
if (obj->getType() == ACTIVEOBJECT_TYPE_PLAYER)
- continue;
+ return false;
// Delete static object if block is loaded
deleteStaticFromBlock(obj, id, MOD_REASON_CLEAR_ALL_OBJECTS, true);
// If known by some client, don't delete immediately
if (obj->m_known_by_count > 0) {
- obj->m_pending_removal = true;
- continue;
+ obj->markForRemoval();
+ return false;
}
// Tell the object about removal
// Delete active object
if (obj->environmentDeletes())
delete obj;
- // Id to be removed from m_active_objects
- objects_to_remove.push_back(id);
- }
- // Remove references from m_active_objects
- for (u16 i : objects_to_remove) {
- m_active_objects.erase(i);
- }
+ return true;
+ };
+
+ m_ao_manager.clear(cb_removal);
// Get list of loaded blocks
std::vector<v3s16> loaded_blocks;
void ServerEnvironment::step(float dtime)
{
+ ScopeProfiler sp2(g_profiler, "ServerEnv::step()", SPT_AVG);
+ const auto start_time = porting::getTimeUs();
+
/* Step time of day */
stepTimeOfDay(dtime);
Handle players
*/
{
- ScopeProfiler sp(g_profiler, "SEnv: handle players avg", SPT_AVG);
+ ScopeProfiler sp(g_profiler, "ServerEnv: move players", SPT_AVG);
for (RemotePlayer *player : m_players) {
// Ignore disconnected players
if (player->getPeerId() == PEER_ID_INEXISTENT)
/*
Manage active block list
*/
- if (m_active_blocks_management_interval.step(dtime, m_cache_active_block_mgmt_interval)) {
- ScopeProfiler sp(g_profiler, "SEnv: manage act. block list avg per interval", SPT_AVG);
+ if (m_active_blocks_mgmt_interval.step(dtime, m_cache_active_block_mgmt_interval) ||
+ m_force_update_active_blocks) {
+ ScopeProfiler sp(g_profiler, "ServerEnv: update active blocks", SPT_AVG);
+
/*
Get player block positions
*/
std::vector<PlayerSAO*> players;
- for (RemotePlayer *player: m_players) {
+ players.reserve(m_players.size());
+ for (RemotePlayer *player : m_players) {
// Ignore disconnected players
if (player->getPeerId() == PEER_ID_INEXISTENT)
continue;
for (const v3s16 &p: blocks_added) {
MapBlock *block = m_map->getBlockOrEmerge(p);
if (!block) {
- m_active_blocks.m_list.erase(p);
- m_active_blocks.m_abm_list.erase(p);
+ // TODO: The blocks removed here will only be picked up again
+ // on the next cycle. To minimize the latency of objects being
+ // activated we could remember the blocks pending activating
+ // and activate them instantly as soon as they're loaded.
+ m_active_blocks.remove(p);
continue;
}
activateBlock(block);
}
+
+ // Some blocks may be removed again by the code above so do this here
+ m_active_block_gauge->set(m_active_blocks.size());
}
+ m_force_update_active_blocks = false;
/*
Mess around in active blocks
*/
if (m_active_blocks_nodemetadata_interval.step(dtime, m_cache_nodetimer_interval)) {
- ScopeProfiler sp(g_profiler, "SEnv: mess in act. blocks avg per interval", SPT_AVG);
+ ScopeProfiler sp(g_profiler, "ServerEnv: Run node timers", SPT_AVG);
float dtime = m_cache_nodetimer_interval;
}
}
- if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval))
- do { // breakable
- if (m_active_block_interval_overload_skip > 0) {
- ScopeProfiler sp(g_profiler, "SEnv: ABM overload skips");
- m_active_block_interval_overload_skip--;
- break;
- }
- ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
- TimeTaker timer("modify in active blocks per interval");
+ if (m_active_block_modifier_interval.step(dtime, m_cache_abm_interval)) {
+ ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg per interval", SPT_AVG);
+ TimeTaker timer("modify in active blocks per interval");
- // Initialize handling of ActiveBlockModifiers
- ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
+ // Initialize handling of ActiveBlockModifiers
+ ABMHandler abmhandler(m_abms, m_cache_abm_interval, this, true);
- for (const v3s16 &p : m_active_blocks.m_abm_list) {
- MapBlock *block = m_map->getBlockNoCreateNoEx(p);
- if (!block)
- continue;
+ int blocks_scanned = 0;
+ int abms_run = 0;
+ int blocks_cached = 0;
- // Set current time as timestamp
- block->setTimestampNoChangedFlag(m_game_time);
+ std::vector<v3s16> output(m_active_blocks.m_abm_list.size());
- /* Handle ActiveBlockModifiers */
- abmhandler.apply(block);
- }
+ // Shuffle the active blocks so that each block gets an equal chance
+ // of having its ABMs run.
+ std::copy(m_active_blocks.m_abm_list.begin(), m_active_blocks.m_abm_list.end(), output.begin());
+ std::shuffle(output.begin(), output.end(), m_rgen);
+
+ int i = 0;
+ // determine the time budget for ABMs
+ u32 max_time_ms = m_cache_abm_interval * 1000 * m_cache_abm_time_budget;
+ for (const v3s16 &p : output) {
+ MapBlock *block = m_map->getBlockNoCreateNoEx(p);
+ if (!block)
+ continue;
+
+ i++;
+
+ // Set current time as timestamp
+ block->setTimestampNoChangedFlag(m_game_time);
+
+ /* Handle ActiveBlockModifiers */
+ abmhandler.apply(block, blocks_scanned, abms_run, blocks_cached);
+
+ u32 time_ms = timer.getTimerTime();
- u32 time_ms = timer.stop(true);
- u32 max_time_ms = 200;
if (time_ms > max_time_ms) {
- warningstream<<"active block modifiers took "
- <<time_ms<<"ms (longer than "
- <<max_time_ms<<"ms)"<<std::endl;
- m_active_block_interval_overload_skip = (time_ms / max_time_ms) + 1;
+ warningstream << "active block modifiers took "
+ << time_ms << "ms (processed " << i << " of "
+ << output.size() << " active blocks)" << std::endl;
+ break;
}
- }while(0);
+ }
+ g_profiler->avg("ServerEnv: active blocks", m_active_blocks.m_abm_list.size());
+ g_profiler->avg("ServerEnv: active blocks cached", blocks_cached);
+ g_profiler->avg("ServerEnv: active blocks scanned for ABMs", blocks_scanned);
+ g_profiler->avg("ServerEnv: ABMs run", abms_run);
+
+ timer.stop(true);
+ }
/*
Step script environment (run global on_step())
*/
m_script->environment_Step(dtime);
+ m_script->stepAsync();
+
/*
Step active objects
*/
{
- ScopeProfiler sp(g_profiler, "SEnv: step act. objs avg", SPT_AVG);
- //TimeTaker timer("Step active objects");
-
- g_profiler->avg("SEnv: num of objects", m_active_objects.size());
+ ScopeProfiler sp(g_profiler, "ServerEnv: Run SAO::step()", SPT_AVG);
// This helps the objects to send data at the same time
bool send_recommended = false;
m_send_recommended_timer += dtime;
- if(m_send_recommended_timer > getSendRecommendedInterval())
- {
+ if (m_send_recommended_timer > getSendRecommendedInterval()) {
m_send_recommended_timer -= getSendRecommendedInterval();
send_recommended = true;
}
- for (auto &ao_it : m_active_objects) {
- ServerActiveObject* obj = ao_it.second;
+ u32 object_count = 0;
+
+ auto cb_state = [&] (ServerActiveObject *obj) {
if (obj->isGone())
- continue;
+ return;
+ object_count++;
// Step object
obj->step(dtime, send_recommended);
// Read messages from object
- while (!obj->m_messages_out.empty()) {
- m_active_object_messages.push(obj->m_messages_out.front());
- obj->m_messages_out.pop();
- }
- }
+ obj->dumpAOMessagesToQueue(m_active_object_messages);
+ };
+ m_ao_manager.step(dtime, cb_state);
+
+ m_active_object_gauge->set(object_count);
}
/*
Manage active objects
*/
if (m_object_management_interval.step(dtime, 0.5)) {
- ScopeProfiler sp(g_profiler, "SEnv: remove removed objs avg /.5s", SPT_AVG);
removeRemovedObjects();
}
++i;
}
}
+
+ // Send outdated player inventories
+ for (RemotePlayer *player : m_players) {
+ if (player->getPeerId() == PEER_ID_INEXISTENT)
+ continue;
+
+ PlayerSAO *sao = player->getPlayerSAO();
+ if (sao && player->inventory.checkModified())
+ m_server->SendInventory(sao, true);
+ }
+
+ // Send outdated detached inventories
+ m_server->sendDetachedInventories(PEER_ID_INEXISTENT, true);
+
+ const auto end_time = porting::getTimeUs();
+ m_step_time_counter->increment(end_time - start_time);
+}
+
+ServerEnvironment::BlockStatus ServerEnvironment::getBlockStatus(v3s16 blockpos)
+{
+ if (m_active_blocks.contains(blockpos))
+ return BS_ACTIVE;
+
+ const MapBlock *block = m_map->getBlockNoCreateNoEx(blockpos);
+ if (block && !block->isDummy())
+ return BS_LOADED;
+
+ if (m_map->isBlockInQueue(blockpos))
+ return BS_EMERGING;
+
+ return BS_UNKNOWN;
}
u32 ServerEnvironment::addParticleSpawner(float exptime)
}
}
-ServerActiveObject* ServerEnvironment::getActiveObject(u16 id)
-{
- ServerActiveObjectMap::const_iterator n = m_active_objects.find(id);
- return (n != m_active_objects.end() ? n->second : NULL);
-}
-
-/**
- * Verify if id is a free active object id
- * @param id
- * @return true if slot is free
- */
-bool ServerEnvironment::isFreeServerActiveObjectId(u16 id) const
-{
- if (id == 0)
- return false;
-
- return m_active_objects.find(id) == m_active_objects.end();
-}
-
-/**
- * Retrieve the first free ActiveObject ID
- * @return free activeobject ID or 0 if none was found
- */
-u16 ServerEnvironment::getFreeServerActiveObjectId()
-{
- // try to reuse id's as late as possible
- static u16 last_used_id = 0;
- u16 startid = last_used_id;
- for (;;) {
- last_used_id++;
- if (isFreeServerActiveObjectId(last_used_id))
- return last_used_id;
-
- if (last_used_id == startid)
- return 0;
- }
-}
-
u16 ServerEnvironment::addActiveObject(ServerActiveObject *object)
{
assert(object); // Pre-condition
f32 radius_f = radius * BS;
f32 player_radius_f = player_radius * BS;
- if (player_radius_f < 0)
- player_radius_f = 0;
- /*
- Go through the object list,
- - discard removed/deactivated objects,
- - discard objects that are too far away,
- - discard objects that are found in current_objects.
- - add remaining objects to added_objects
- */
- for (auto &ao_it : m_active_objects) {
- u16 id = ao_it.first;
+ if (player_radius_f < 0.0f)
+ player_radius_f = 0.0f;
- // Get object
- ServerActiveObject *object = ao_it.second;
- if (object == NULL)
- continue;
-
- if (object->isGone())
- continue;
-
- f32 distance_f = object->getBasePosition().
- getDistanceFrom(playersao->getBasePosition());
- if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
- // Discard if too far
- if (distance_f > player_radius_f && player_radius_f != 0)
- continue;
- } else if (distance_f > radius_f)
- continue;
-
- // Discard if already on current_objects
- std::set<u16>::iterator n;
- n = current_objects.find(id);
- if(n != current_objects.end())
- continue;
- // Add to added_objects
- added_objects.push(id);
- }
+ m_ao_manager.getAddedActiveObjectsAroundPos(playersao->getBasePosition(), radius_f,
+ player_radius_f, current_objects, added_objects);
}
/*
for (auto &so_it : block->m_static_objects.m_active) {
// Get the ServerActiveObject counterpart to this StaticObject
- ServerActiveObjectMap::const_iterator ao_it = m_active_objects.find(so_it.first);
- if (ao_it == m_active_objects.end()) {
+ ServerActiveObject *sao = m_ao_manager.getActiveObject(so_it.first);
+ if (!sao) {
// If this ever happens, there must be some kind of nasty bug.
errorstream << "ServerEnvironment::setStaticForObjectsInBlock(): "
"Object from MapBlock::m_static_objects::m_active not found "
continue;
}
- ServerActiveObject *sao = ao_it->second;
sao->m_static_exists = static_exists;
sao->m_static_block = static_block;
}
}
-ActiveObjectMessage ServerEnvironment::getActiveObjectMessage()
+bool ServerEnvironment::getActiveObjectMessage(ActiveObjectMessage *dest)
{
if(m_active_object_messages.empty())
- return ActiveObjectMessage(0);
+ return false;
- ActiveObjectMessage message = m_active_object_messages.front();
+ *dest = std::move(m_active_object_messages.front());
m_active_object_messages.pop();
- return message;
+ return true;
}
void ServerEnvironment::getSelectedActiveObjects(
const core::line3d<f32> &shootline_on_map,
std::vector<PointedThing> &objects)
{
- std::vector<u16> objectIds;
- getObjectsInsideRadius(objectIds, shootline_on_map.start,
- shootline_on_map.getLength() + 10.0f);
+ std::vector<ServerActiveObject *> objs;
+ getObjectsInsideRadius(objs, shootline_on_map.start,
+ shootline_on_map.getLength() + 10.0f, nullptr);
const v3f line_vector = shootline_on_map.getVector();
- for (u16 objectId : objectIds) {
- ServerActiveObject* obj = getActiveObject(objectId);
-
+ for (auto obj : objs) {
+ if (obj->isGone())
+ continue;
aabb3f selection_box;
if (!obj->getSelectionBox(&selection_box))
continue;
if (boxLineCollision(offsetted_box, shootline_on_map.start, line_vector,
¤t_intersection, ¤t_normal)) {
objects.emplace_back(
- (s16) objectId, current_intersection, current_normal,
+ (s16) obj->getId(), current_intersection, current_normal,
(current_intersection - shootline_on_map.start).getLengthSQ());
}
}
u16 ServerEnvironment::addActiveObjectRaw(ServerActiveObject *object,
bool set_changed, u32 dtime_s)
{
- assert(object); // Pre-condition
- if(object->getId() == 0){
- u16 new_id = getFreeServerActiveObjectId();
- if(new_id == 0)
- {
- errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
- <<"no free ids available"<<std::endl;
- if(object->environmentDeletes())
- delete object;
- return 0;
- }
- object->setId(new_id);
- }
- else{
- verbosestream<<"ServerEnvironment::addActiveObjectRaw(): "
- <<"supplied with id "<<object->getId()<<std::endl;
- }
-
- if(!isFreeServerActiveObjectId(object->getId())) {
- errorstream<<"ServerEnvironment::addActiveObjectRaw(): "
- <<"id is not free ("<<object->getId()<<")"<<std::endl;
- if(object->environmentDeletes())
- delete object;
- return 0;
- }
-
- if (objectpos_over_limit(object->getBasePosition())) {
- v3f p = object->getBasePosition();
- warningstream << "ServerEnvironment::addActiveObjectRaw(): "
- << "object position (" << p.X << "," << p.Y << "," << p.Z
- << ") outside maximum range" << std::endl;
- if (object->environmentDeletes())
- delete object;
+ if (!m_ao_manager.registerObject(object)) {
return 0;
}
- /*infostream<<"ServerEnvironment::addActiveObjectRaw(): "
- <<"added (id="<<object->getId()<<")"<<std::endl;*/
-
- m_active_objects[object->getId()] = object;
-
- verbosestream<<"ServerEnvironment::addActiveObjectRaw(): "
- <<"Added id="<<object->getId()<<"; there are now "
- <<m_active_objects.size()<<" active objects."
- <<std::endl;
-
// Register reference in scripting api (must be done before post-init)
m_script->addObjectReference(object);
// Post-initialize object
object->addedToEnvironment(dtime_s);
// Add static data to block
- if(object->isStaticAllowed())
- {
+ if (object->isStaticAllowed()) {
// Add static object to active static list of the block
v3f objectpos = object->getBasePosition();
StaticObject s_obj(object, objectpos);
*/
void ServerEnvironment::removeRemovedObjects()
{
- std::vector<u16> objects_to_remove;
- for (auto &ao_it : m_active_objects) {
- u16 id = ao_it.first;
- ServerActiveObject* obj = ao_it.second;
+ ScopeProfiler sp(g_profiler, "ServerEnvironment::removeRemovedObjects()", SPT_AVG);
+ auto clear_cb = [this] (ServerActiveObject *obj, u16 id) {
// This shouldn't happen but check it
if (!obj) {
errorstream << "ServerEnvironment::removeRemovedObjects(): "
<< "NULL object found. id=" << id << std::endl;
- objects_to_remove.push_back(id);
- continue;
+ return true;
}
/*
We will handle objects marked for removal or deactivation
*/
if (!obj->isGone())
- continue;
+ return false;
/*
Delete static data from block if removed
*/
- if (obj->m_pending_removal)
+ if (obj->isPendingRemoval())
deleteStaticFromBlock(obj, id, MOD_REASON_REMOVE_OBJECTS_REMOVE, false);
// If still known by clients, don't actually remove. On some future
// invocation this will be 0, which is when removal will continue.
if(obj->m_known_by_count > 0)
- continue;
+ return false;
/*
Move static data from active to stored if deactivated
*/
- if (!obj->m_pending_removal && obj->m_static_exists) {
+ if (!obj->isPendingRemoval() && obj->m_static_exists) {
MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
if (block) {
- std::map<u16, StaticObject>::iterator i =
- block->m_static_objects.m_active.find(id);
+ const auto i = block->m_static_objects.m_active.find(id);
if (i != block->m_static_objects.m_active.end()) {
block->m_static_objects.m_stored.push_back(i->second);
block->m_static_objects.m_active.erase(id);
m_script->removeObjectReference(obj);
// Delete
- if(obj->environmentDeletes())
+ if (obj->environmentDeletes())
delete obj;
- objects_to_remove.push_back(id);
- }
- // Remove references from m_active_objects
- for (u16 i : objects_to_remove) {
- m_active_objects.erase(i);
- }
+ return true;
+ };
+
+ m_ao_manager.clear(clear_cb);
}
static void print_hexdump(std::ostream &o, const std::string &data)
int i = i0 + di;
char buf[4];
if(di<thislinelength)
- snprintf(buf, 4, "%.2x ", data[i]);
+ porting::mt_snprintf(buf, sizeof(buf), "%.2x ", data[i]);
else
- snprintf(buf, 4, " ");
+ porting::mt_snprintf(buf, sizeof(buf), " ");
o<<buf;
}
o<<" ";
}
}
+ServerActiveObject* ServerEnvironment::createSAO(ActiveObjectType type, v3f pos,
+ const std::string &data)
+{
+ switch (type) {
+ case ACTIVEOBJECT_TYPE_LUAENTITY:
+ return new LuaEntitySAO(this, pos, data);
+ default:
+ warningstream << "ServerActiveObject: No factory for type=" << type << std::endl;
+ }
+ return nullptr;
+}
+
/*
Convert stored objects from blocks near the players to active.
*/
std::vector<StaticObject> new_stored;
for (const StaticObject &s_obj : block->m_static_objects.m_stored) {
// Create an active object from the data
- ServerActiveObject *obj = ServerActiveObject::create
- ((ActiveObjectType) s_obj.type, this, 0, s_obj.pos, s_obj.data);
+ ServerActiveObject *obj = createSAO((ActiveObjectType) s_obj.type, s_obj.pos,
+ s_obj.data);
// If couldn't create object, store static data back.
- if(obj == NULL) {
+ if (!obj) {
errorstream<<"ServerEnvironment::activateObjects(): "
<<"failed to create active object from static object "
<<"in block "<<PP(s_obj.pos/BS)
*/
void ServerEnvironment::deactivateFarObjects(bool _force_delete)
{
- std::vector<u16> objects_to_remove;
- for (auto &ao_it : m_active_objects) {
+ auto cb_deactivate = [this, _force_delete] (ServerActiveObject *obj, u16 id) {
// force_delete might be overriden per object
bool force_delete = _force_delete;
- ServerActiveObject* obj = ao_it.second;
- assert(obj);
-
- // Do not deactivate if static data creation not allowed
- if(!force_delete && !obj->isStaticAllowed())
- continue;
+ // Do not deactivate if disallowed
+ if (!force_delete && !obj->shouldUnload())
+ return false;
// removeRemovedObjects() is responsible for these
- if(!force_delete && obj->isGone())
- continue;
+ if (!force_delete && obj->isGone())
+ return false;
- u16 id = ao_it.first;
- v3f objectpos = obj->getBasePosition();
+ const v3f &objectpos = obj->getBasePosition();
// The block in which the object resides in
v3s16 blockpos_o = getNodeBlockPos(floatToInt(objectpos, BS));
// If object's static data is stored in a deactivated block and object
// is actually located in an active block, re-save to the block in
// which the object is actually located in.
- if(!force_delete &&
- obj->m_static_exists &&
- !m_active_blocks.contains(obj->m_static_block) &&
- m_active_blocks.contains(blockpos_o))
- {
+ if (!force_delete && obj->m_static_exists &&
+ !m_active_blocks.contains(obj->m_static_block) &&
+ m_active_blocks.contains(blockpos_o)) {
+
// Delete from block where object was located
deleteStaticFromBlock(obj, id, MOD_REASON_STATIC_DATA_REMOVED, false);
// Save to block where object is located
saveStaticToBlock(blockpos_o, id, obj, s_obj, MOD_REASON_STATIC_DATA_ADDED);
- continue;
+ return false;
}
// If block is still active, don't remove
- if(!force_delete && m_active_blocks.contains(blockpos_o))
- continue;
+ bool still_active = obj->isStaticAllowed() ?
+ m_active_blocks.contains(blockpos_o) :
+ getMap().getBlockNoCreateNoEx(blockpos_o) != nullptr;
+ if (!force_delete && still_active)
+ return false;
verbosestream << "ServerEnvironment::deactivateFarObjects(): "
- << "deactivating object id=" << id << " on inactive block "
- << PP(blockpos_o) << std::endl;
+ << "deactivating object id=" << id << " on inactive block "
+ << PP(blockpos_o) << std::endl;
// If known by some client, don't immediately delete.
bool pending_delete = (obj->m_known_by_count > 0 && !force_delete);
MapBlock *block = m_map->emergeBlock(obj->m_static_block, false);
if (block) {
- std::map<u16, StaticObject>::iterator n =
- block->m_static_objects.m_active.find(id);
+ const auto n = block->m_static_objects.m_active.find(id);
if (n != block->m_static_objects.m_active.end()) {
StaticObject static_old = n->second;
force_delete = true;
}
+ // Regardless of what happens to the object at this point, deactivate it first.
+ // This ensures that LuaEntity on_deactivate is always called.
+ obj->markForDeactivation();
+
/*
If known by some client, set pending deactivation.
Otherwise delete it immediately.
*/
- if(pending_delete && !force_delete)
- {
+ if (pending_delete && !force_delete) {
verbosestream << "ServerEnvironment::deactivateFarObjects(): "
- << "object id=" << id << " is known by clients"
- << "; not deleting yet" << std::endl;
+ << "object id=" << id << " is known by clients"
+ << "; not deleting yet" << std::endl;
- obj->m_pending_deactivation = true;
- continue;
+ return false;
}
+
verbosestream << "ServerEnvironment::deactivateFarObjects(): "
- << "object id=" << id << " is not known by clients"
- << "; deleting" << std::endl;
+ << "object id=" << id << " is not known by clients"
+ << "; deleting" << std::endl;
// Tell the object about removal
obj->removingFromEnvironment();
m_script->removeObjectReference(obj);
// Delete active object
- if(obj->environmentDeletes())
+ if (obj->environmentDeletes())
delete obj;
- // Id to be removed from m_active_objects
- objects_to_remove.push_back(id);
- }
- // Remove references from m_active_objects
- for (u16 i : objects_to_remove) {
- m_active_objects.erase(i);
- }
+ return true;
+ };
+
+ m_ao_manager.clear(cb_deactivate);
}
void ServerEnvironment::deleteStaticFromBlock(
if (name == "dummy")
return new Database_Dummy();
+
#if USE_POSTGRESQL
if (name == "postgresql") {
std::string connect_string;
return new PlayerDatabasePostgreSQL(connect_string);
}
#endif
+
+#if USE_LEVELDB
+ if (name == "leveldb")
+ return new PlayerDatabaseLevelDB(savedir);
+#endif
+
if (name == "files")
return new PlayerDatabaseFiles(savedir + DIR_DELIM + "players");
if (!world_mt.exists("player_backend")) {
errorstream << "Please specify your current backend in world.mt:"
<< std::endl
- << " player_backend = {files|sqlite3|postgresql}"
+ << " player_backend = {files|sqlite3|leveldb|postgresql}"
<< std::endl;
return false;
}
delete dstdb;
} catch (BaseException &e) {
- errorstream << "An error occured during migration: " << e.what() << std::endl;
+ errorstream << "An error occurred during migration: " << e.what() << std::endl;
+ return false;
+ }
+ return true;
+}
+
+AuthDatabase *ServerEnvironment::openAuthDatabase(
+ const std::string &name, const std::string &savedir, const Settings &conf)
+{
+ if (name == "sqlite3")
+ return new AuthDatabaseSQLite3(savedir);
+
+#if USE_POSTGRESQL
+ if (name == "postgresql") {
+ std::string connect_string;
+ conf.getNoEx("pgsql_auth_connection", connect_string);
+ return new AuthDatabasePostgreSQL(connect_string);
+ }
+#endif
+
+ if (name == "files")
+ return new AuthDatabaseFiles(savedir);
+
+#if USE_LEVELDB
+ if (name == "leveldb")
+ return new AuthDatabaseLevelDB(savedir);
+#endif
+
+ throw BaseException(std::string("Database backend ") + name + " not supported.");
+}
+
+bool ServerEnvironment::migrateAuthDatabase(
+ const GameParams &game_params, const Settings &cmd_args)
+{
+ std::string migrate_to = cmd_args.get("migrate-auth");
+ Settings world_mt;
+ std::string world_mt_path = game_params.world_path + DIR_DELIM + "world.mt";
+ if (!world_mt.readConfigFile(world_mt_path.c_str())) {
+ errorstream << "Cannot read world.mt!" << std::endl;
+ return false;
+ }
+
+ std::string backend = "files";
+ if (world_mt.exists("auth_backend"))
+ backend = world_mt.get("auth_backend");
+ else
+ warningstream << "No auth_backend found in world.mt, "
+ "assuming \"files\"." << std::endl;
+
+ if (backend == migrate_to) {
+ errorstream << "Cannot migrate: new backend is same"
+ << " as the old one" << std::endl;
+ return false;
+ }
+
+ try {
+ const std::unique_ptr<AuthDatabase> srcdb(ServerEnvironment::openAuthDatabase(
+ backend, game_params.world_path, world_mt));
+ const std::unique_ptr<AuthDatabase> dstdb(ServerEnvironment::openAuthDatabase(
+ migrate_to, game_params.world_path, world_mt));
+
+ std::vector<std::string> names_list;
+ srcdb->listNames(names_list);
+ for (const std::string &name : names_list) {
+ actionstream << "Migrating auth entry for " << name << std::endl;
+ bool success;
+ AuthEntry authEntry;
+ success = srcdb->getAuth(name, authEntry);
+ success = success && dstdb->createAuth(authEntry);
+ if (!success)
+ errorstream << "Failed to migrate " << name << std::endl;
+ }
+
+ actionstream << "Successfully migrated " << names_list.size()
+ << " auth entries" << std::endl;
+ world_mt.set("auth_backend", migrate_to);
+ if (!world_mt.updateConfigFile(world_mt_path.c_str()))
+ errorstream << "Failed to update world.mt!" << std::endl;
+ else
+ actionstream << "world.mt updated" << std::endl;
+
+ if (backend == "files") {
+ // special-case files migration:
+ // move auth.txt to auth.txt.bak if possible
+ std::string auth_txt_path =
+ game_params.world_path + DIR_DELIM + "auth.txt";
+ std::string auth_bak_path = auth_txt_path + ".bak";
+ if (!fs::PathExists(auth_bak_path))
+ if (fs::Rename(auth_txt_path, auth_bak_path))
+ actionstream << "Renamed auth.txt to auth.txt.bak"
+ << std::endl;
+ else
+ errorstream << "Could not rename auth.txt to "
+ "auth.txt.bak" << std::endl;
+ else
+ warningstream << "auth.txt.bak already exists, auth.txt "
+ "not renamed" << std::endl;
+ }
+
+ } catch (BaseException &e) {
+ errorstream << "An error occurred during migration: " << e.what()
+ << std::endl;
return false;
}
return true;