diff --git a/doc/lua_api.md b/doc/lua_api.md index 12b412dbe..3c8368794 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -6636,6 +6636,17 @@ Environment access * mode = `"quick"`: Clear objects immediately in loaded mapblocks, clear objects in unloaded mapblocks only when the mapblocks are next activated. +* `core.blocks_callback(options)` + * Call Lua function over all loaded or loadable blocks + * Takes an table as an argument with the fields: + * `mode` + * mode = "full": Load and go through every mapblock, clearing objects (default). + * mode = "quick": Clear objects immediately in loaded mapblocks, clear objects in unloaded mapblocks only when the mapblocks are next activated. + * `callback`: Lua callback function function(name, staticdata, params. + * `blockpos_hash`: block position hash compatible with `core.hash_node_position` and `core.get_position_from_hash` + * `params`: Some Lua value. + * `params`: Params for Lua callback functions. + * `core.load_area(pos1[, pos2])` * Load the mapblocks containing the area from `pos1` to `pos2`. `pos2` defaults to `pos1` if not specified. diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp index 980ba37cb..babf96c3a 100644 --- a/src/script/cpp_api/s_env.cpp +++ b/src/script/cpp_api/s_env.cpp @@ -419,6 +419,40 @@ bool ScriptApiEnv::has_on_mapblocks_changed() return lua_objlen(L, -1) > 0; } +void ScriptApiEnv::block_callback(const v3s16 blockpos, ScriptCallbackState *state) +{ + Server *server = getServer(); + + // This function should be executed with envlock held. + // The caller (LuaClearObjectback in src/script/lua_api/l_env.cpp) + // should have obtained the lock. + // Note that the order of these locks is important! Envlock must *ALWAYS* + // be acquired before attempting to acquire scriptlock, or else ServerThread + // will try to acquire scriptlock after it already owns envlock, thus + // deadlocking EmergeThread and ServerThread + + SCRIPTAPI_PRECHECKHEADER + + int error_handler = PUSH_ERROR_HANDLER(L); + + lua_rawgeti(L, LUA_REGISTRYINDEX, state->callback_ref); + + lua_pushnumber(L, hash_node_position(blockpos)); + lua_rawgeti(L, LUA_REGISTRYINDEX, state->args_ref); + + setOriginDirect(state->origin.c_str()); + + try { + PCALL_RES(lua_pcall(L, 2, 1, error_handler)); + + lua_pop(L, 2); // Pop callback result and error handler + + } catch (LuaError &e) { + // Note: don't throw here, we still need to run the cleanup code below + server->setAsyncFatalError(e); + } +} + void ScriptApiEnv::triggerABM(int id, v3s16 p, MapNode n, u32 active_object_count, u32 active_object_count_wider) { diff --git a/src/script/cpp_api/s_env.h b/src/script/cpp_api/s_env.h index e32c6bc4c..e7a44058f 100644 --- a/src/script/cpp_api/s_env.h +++ b/src/script/cpp_api/s_env.h @@ -41,6 +41,9 @@ public: // Determines whether there are any on_mapblocks_changed callbacks bool has_on_mapblocks_changed(); + // Calll Lua callback for block + void block_callback(const v3s16 blockpos, ScriptCallbackState *state); + // Initializes environment and loads some definitions from Lua void initializeEnvironment(ServerEnvironment *env); diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 51604fff0..e6d7bb427 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -155,6 +155,19 @@ void LuaEmergeAreaCallback(v3s16 blockpos, EmergeAction action, void *param) delete state; } +void LuaBlockCallback(const v3s16 blockpos, void *param) +{ + ScriptCallbackState *state = static_cast(param); + assert(state != NULL); + assert(state->script != NULL); + + // state must be protected by envlock + //Server *server = state->script->getServer(); + //MutexAutoLock envlock(server->m_env_mutex); + + state->script->block_callback(blockpos, state); +} + /* Exported functions */ // set_node(pos, node) @@ -1092,6 +1105,56 @@ int ModApiEnv::l_clear_objects(lua_State *L) return 0; } +// blocks_callback +int ModApiEnv::l_blocks_callback(lua_State *L) +{ + GET_ENV_PTR; + + ScriptCallbackState *state = NULL; + + BlocksCallbackConfig config; + config.mode = CLEAR_OBJECTS_MODE_QUICK; + config.callback = nullptr; + if (lua_istable(L, 1)) { + lua_getfield(L, 1, "mode"); + config.mode = (ClearObjectsMode)getenumfield(L, 1, "mode", + ModApiEnv::es_ClearObjectsMode, config.mode); + lua_pop(L, 1); + + lua_getfield(L, 1, "callback"); + if (lua_isfunction(L, -1)) { + config.callback = LuaBlockCallback; + + int callback_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + lua_getfield(L, 1, "params"); + int args_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + state = new ScriptCallbackState; + state->script = getServer(L)->getScriptIface(); + state->callback_ref = callback_ref; + state->args_ref = args_ref; + state->refcount = 1; + state->origin = getScriptApiBase(L)->getOrigin(); + + config.param = static_cast(state); + } + else { + lua_pop(L, 1); + } + } + + env->blocksCallback(config); + + if (state) { + luaL_unref(L, LUA_REGISTRYINDEX, state->callback_ref); + luaL_unref(L, LUA_REGISTRYINDEX, state->args_ref); + delete state; + } + + return 0; +} + // line_of_sight(pos1, pos2) -> true/false, pos int ModApiEnv::l_line_of_sight(lua_State *L) { @@ -1419,6 +1482,7 @@ void ModApiEnv::Initialize(lua_State *L, int top) API_FCT(get_perlin_map); API_FCT(get_voxel_manip); API_FCT(clear_objects); + API_FCT(blocks_callback); API_FCT(spawn_tree); API_FCT(find_path); API_FCT(line_of_sight); diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 49b3458a6..a28bbba5b 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -195,6 +195,9 @@ private: // clear all objects in the environment static int l_clear_objects(lua_State *L); + // blocks_callback() + static int l_blocks_callback(lua_State *L); + // spawn_tree(pos, treedef) static int l_spawn_tree(lua_State *L); diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 55306ee59..bfbe0f3d8 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -866,6 +866,90 @@ void ServerEnvironment::clearObjects(ClearObjectsMode mode) << " in " << num_blocks_cleared << " blocks" << std::endl; } +void ServerEnvironment::blocksCallback(BlocksCallbackConfig &config) +{ + infostream << "ServerEnvironment::blocksCallback(): " + << "Call Lua callback over blocks" << std::endl; + + // Get list of loaded blocks + std::vector loaded_blocks; + infostream << "ServerEnvironment::blocksCallback(): " + << "Listing all loaded blocks" << std::endl; + m_map->listAllLoadedBlocks(loaded_blocks); + infostream << "ServerEnvironment::blocksCallback(): " + << "Done listing all loaded blocks: " + << loaded_blocks.size()< loadable_blocks; + if (config.mode == CLEAR_OBJECTS_MODE_FULL) { + infostream << "ServerEnvironment::blocksCallback(): " + << "Listing all loadable blocks" << std::endl; + m_map->listAllLoadableBlocks(loadable_blocks); + infostream << "ServerEnvironment::blocksCallback(): " + << "Done listing all loadable blocks: " + << loadable_blocks.size() << std::endl; + } else { + loadable_blocks = loaded_blocks; + } + + actionstream << "ServerEnvironment::blocksCallback(): " + << "Now processing " << loadable_blocks.size() + << " blocks" << std::endl; + + // Grab a reference on each loaded block to avoid unloading it + for (v3s16 p : loaded_blocks) { + MapBlock *block = m_map->getBlockNoCreateNoEx(p); + assert(block != NULL); + block->refGrab(); + } + + // Remove objects in all loadable blocks + u32 unload_interval = U32_MAX; + if (config.mode == CLEAR_OBJECTS_MODE_FULL) { + unload_interval = g_settings->getS32("max_clearobjects_extra_loaded_blocks"); + unload_interval = MYMAX(unload_interval, 1); + } + u32 report_interval = loadable_blocks.size() / 10; + u32 num_blocks_processed = 0; + for (auto i = loadable_blocks.begin(); + i != loadable_blocks.end(); ++i) { + v3s16 p = *i; + MapBlock *block = m_map->emergeBlock(p, false); + if (!block) { + errorstream << "ServerEnvironment::blocksCallback(): " + << "Failed to emerge block " << p << std::endl; + continue; + } + + config.callback(p, config.param); + num_blocks_processed++; + + if (report_interval != 0 && + num_blocks_processed % report_interval == 0) { + float percent = 100.0 * (float)num_blocks_processed / + loadable_blocks.size(); + actionstream << "ServerEnvironment::blocksCallback(): " + << "Processed" << num_blocks_processed << " blocks (" + << percent << "%)" << std::endl; + } + if (num_blocks_processed % unload_interval == 0) { + m_map->unloadUnreferencedBlocks(); + } + } + m_map->unloadUnreferencedBlocks(); + + // Drop references that were added above + for (v3s16 p : loaded_blocks) { + MapBlock *block = m_map->getBlockNoCreateNoEx(p); + assert(block); + block->refDrop(); + } + + actionstream << "ServerEnvironment::blocksCallback(): " + << "Finished: Processed " << num_blocks_processed << " blocks" << std::endl; +} + void ServerEnvironment::step(float dtime) { ScopeProfiler sp2(g_profiler, "ServerEnv::step()", SPT_AVG); diff --git a/src/serverenvironment.h b/src/serverenvironment.h index 04153e944..79f778b3b 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -101,6 +101,16 @@ enum ClearObjectsMode { CLEAR_OBJECTS_MODE_QUICK, }; +// blocksCallback +typedef void (*BlocksCallback)(const v3s16 blockpos, void *param); + +struct BlocksCallbackConfig { + ClearObjectsMode mode; + + BlocksCallback callback; + void *param; +}; + class ServerEnvironment final : public Environment { public: @@ -242,6 +252,9 @@ public: // Clear objects, loading and going through every MapBlock void clearObjects(ClearObjectsMode mode); + // Call Lua functon for akk loaded or loadable MapBlock + void blocksCallback(BlocksCallbackConfig &config); + // to be called before destructor void deactivateBlocksAndObjects();