diff --git a/builtin/game/chatcommands.lua b/builtin/game/chatcommands.lua index 5f9fcfc7b..6a35c034e 100644 --- a/builtin/game/chatcommands.lua +++ b/builtin/game/chatcommands.lua @@ -436,6 +436,31 @@ core.register_chatcommand("set", { end, }) +local function emergeblocks_callback(pos, action, num_calls_remaining, ctx) + if ctx.total_blocks == 0 then + ctx.total_blocks = num_calls_remaining + 1 + ctx.current_blocks = 0 + end + ctx.current_blocks = ctx.current_blocks + 1 + + if ctx.current_blocks == ctx.total_blocks then + core.chat_send_player(ctx.requestor_name, + string.format("Finished emerging %d blocks in %.2fms.", + ctx.total_blocks, (os.clock() - ctx.start_time) * 1000)) + end +end + +local function emergeblocks_progress_update(ctx) + if ctx.current_blocks ~= ctx.total_blocks then + core.chat_send_player(ctx.requestor_name, + string.format("emergeblocks update: %d/%d blocks emerged (%.1f%%)", + ctx.current_blocks, ctx.total_blocks, + (ctx.current_blocks / ctx.total_blocks) * 100)) + + core.after(2, emergeblocks_progress_update, ctx) + end +end + core.register_chatcommand("emergeblocks", { params = "(here [radius]) | ( )", description = "starts loading (or generating, if inexistent) map blocks " @@ -447,7 +472,16 @@ core.register_chatcommand("emergeblocks", { return false, p2 end - core.emerge_area(p1, p2) + local context = { + current_blocks = 0, + total_blocks = 0, + start_time = os.clock(), + requestor_name = name + } + + core.emerge_area(p1, p2, emergeblocks_callback, context) + core.after(2, emergeblocks_progress_update, context) + return true, "Started emerge of area ranging from " .. core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1) end, diff --git a/builtin/game/constants.lua b/builtin/game/constants.lua new file mode 100644 index 000000000..ea3644cfb --- /dev/null +++ b/builtin/game/constants.lua @@ -0,0 +1,12 @@ +-- Minetest: builtin/constants.lua + +-- +-- Constants values for use with the Lua API +-- + +-- Block emerge status constants (for use with core.emerge_area) +core.EMERGE_CANCELLED = 0 +core.EMERGE_ERRORED = 1 +core.EMERGE_FROM_MEMORY = 2 +core.EMERGE_FROM_DISK = 3 +core.EMERGE_GENERATED = 4 diff --git a/builtin/game/init.lua b/builtin/game/init.lua index 72e3f009c..a6cfa3bf8 100644 --- a/builtin/game/init.lua +++ b/builtin/game/init.lua @@ -5,6 +5,7 @@ local gamepath = scriptpath.."game"..DIR_DELIM dofile(commonpath.."vector.lua") +dofile(gamepath.."constants.lua") dofile(gamepath.."item.lua") dofile(gamepath.."register.lua") @@ -25,4 +26,3 @@ dofile(gamepath.."features.lua") dofile(gamepath.."voxelarea.lua") dofile(gamepath.."forceloading.lua") dofile(gamepath.."statbars.lua") - diff --git a/doc/lua_api.txt b/doc/lua_api.txt index 5ad88b121..57940a5dd 100644 --- a/doc/lua_api.txt +++ b/doc/lua_api.txt @@ -2039,9 +2039,19 @@ and `minetest.auth_reload` call the authetification handler. * `pos1` and `pos2` are optional and default to mapchunk minp and maxp. * `minetest.clear_objects()` * clear all objects in the environments -* `minetest.emerge_area(pos1, pos2)` - * queues all mapblocks in the area from pos1 to pos2, inclusive, for emerge - * i.e. asynchronously loads blocks from disk, or if inexistent, generates them +* `minetest.emerge_area(pos1, pos2, [callback], [param])` + * Queue all blocks in the area from `pos1` to `pos2`, inclusive, to be asynchronously + * fetched from memory, loaded from disk, or if inexistent, generates them. + * If `callback` is a valid Lua function, this will be called for each block emerged. + * The function signature of callback is: + * `function EmergeAreaCallback(blockpos, action, calls_remaining, param)` + * - `blockpos` is the *block* coordinates of the block that had been emerged + * - `action` could be one of the following constant values: + * `core.EMERGE_CANCELLED`, `core.EMERGE_ERRORED`, `core.EMERGE_FROM_MEMORY`, + * `core.EMERGE_FROM_DISK`, `core.EMERGE_GENERATED` + * - `calls_remaining` is the number of callbacks to be expected after this one + * - `param` is the user-defined parameter passed to emerge_area (or nil if the + * parameter was absent) * `minetest.delete_area(pos1, pos2)` * delete all mapblocks in the area from pos1 to pos2, inclusive * `minetest.line_of_sight(pos1, pos2, stepsize)`: returns `boolean, pos` diff --git a/src/script/cpp_api/s_env.cpp b/src/script/cpp_api/s_env.cpp index b8717597a..a1b11bfe1 100644 --- a/src/script/cpp_api/s_env.cpp +++ b/src/script/cpp_api/s_env.cpp @@ -157,3 +157,42 @@ void ScriptApiEnv::initializeEnvironment(ServerEnvironment *env) } lua_pop(L, 1); } + +void ScriptApiEnv::on_emerge_area_completion( + v3s16 blockpos, int action, ScriptCallbackState *state) +{ + Server *server = getServer(); + + // 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 + MutexAutoLock envlock(server->m_env_mutex); + + SCRIPTAPI_PRECHECKHEADER + + int error_handler = PUSH_ERROR_HANDLER(L); + + lua_rawgeti(L, LUA_REGISTRYINDEX, state->callback_ref); + luaL_checktype(L, -1, LUA_TFUNCTION); + + push_v3s16(L, blockpos); + lua_pushinteger(L, action); + lua_pushinteger(L, state->refcount); + lua_rawgeti(L, LUA_REGISTRYINDEX, state->args_ref); + + setOriginDirect(state->origin.c_str()); + + try { + PCALL_RES(lua_pcall(L, 4, 0, error_handler)); + } catch (LuaError &e) { + server->setAsyncFatalError(e.what()); + } + + lua_pop(L, 1); // Pop error handler + + if (state->refcount == 0) { + luaL_unref(L, LUA_REGISTRYINDEX, state->callback_ref); + luaL_unref(L, LUA_REGISTRYINDEX, state->args_ref); + } +} diff --git a/src/script/cpp_api/s_env.h b/src/script/cpp_api/s_env.h index 180cfabd0..0d98e627f 100644 --- a/src/script/cpp_api/s_env.h +++ b/src/script/cpp_api/s_env.h @@ -24,19 +24,22 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irr_v3d.h" class ServerEnvironment; -struct MapgenParams; +struct ScriptCallbackState; -class ScriptApiEnv - : virtual public ScriptApiBase -{ +class ScriptApiEnv : virtual public ScriptApiBase { public: - // On environment step + // Called on environment step void environment_Step(float dtime); - // After generating a piece of map - void environment_OnGenerated(v3s16 minp, v3s16 maxp,u32 blockseed); - //called on player event - void player_event(ServerActiveObject* player, std::string type); + // Called after generating a piece of map + void environment_OnGenerated(v3s16 minp, v3s16 maxp, u32 blockseed); + + // Called on player event + void player_event(ServerActiveObject *player, std::string type); + + // Called after emerge of a block queued from core.emerge_area() + void on_emerge_area_completion(v3s16 blockpos, int action, + ScriptCallbackState *state); void initializeEnvironment(ServerEnvironment *env); }; diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 6ffca6f0f..084b1b440 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -83,6 +83,21 @@ void LuaABM::trigger(ServerEnvironment *env, v3s16 p, MapNode n, lua_pop(L, 1); // Pop error handler } +void LuaEmergeAreaCallback(v3s16 blockpos, EmergeAction action, void *param) +{ + ScriptCallbackState *state = (ScriptCallbackState *)param; + assert(state != NULL); + assert(state->script != NULL); + assert(state->refcount > 0); + + state->refcount--; + + state->script->on_emerge_area_completion(blockpos, action, state); + + if (state->refcount == 0) + delete state; +} + // Exported functions // set_node(pos, node) @@ -748,24 +763,46 @@ int ModApiEnvMod::l_line_of_sight(lua_State *L) return 1; } - -// emerge_area(p1, p2) -// emerge mapblocks in area p1..p2 +// emerge_area(p1, p2, [callback, context]) +// emerge mapblocks in area p1..p2, calls callback with context upon completion int ModApiEnvMod::l_emerge_area(lua_State *L) { GET_ENV_PTR; + EmergeCompletionCallback callback = NULL; + ScriptCallbackState *state = NULL; + EmergeManager *emerge = getServer(L)->getEmergeManager(); v3s16 bpmin = getNodeBlockPos(read_v3s16(L, 1)); v3s16 bpmax = getNodeBlockPos(read_v3s16(L, 2)); sortBoxVerticies(bpmin, bpmax); + size_t num_blocks = VoxelArea(bpmin, bpmax).getVolume(); + assert(num_blocks != 0); + + if (lua_isfunction(L, 3)) { + callback = LuaEmergeAreaCallback; + + lua_pushvalue(L, 3); + int callback_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + lua_pushvalue(L, 4); + 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 = num_blocks; + state->origin = getScriptApiBase(L)->getOrigin(); + } + for (s16 z = bpmin.Z; z <= bpmax.Z; z++) for (s16 y = bpmin.Y; y <= bpmax.Y; y++) for (s16 x = bpmin.X; x <= bpmax.X; x++) { - v3s16 chunkpos(x, y, z); - emerge->enqueueBlockEmerge(PEER_ID_INEXISTENT, chunkpos, false, true); + emerge->enqueueBlockEmergeEx(v3s16(x, y, z), PEER_ID_INEXISTENT, + BLOCK_EMERGE_ALLOW_GEN | BLOCK_EMERGE_FORCE_QUEUE, callback, state); } return 0; diff --git a/src/script/lua_api/l_env.h b/src/script/lua_api/l_env.h index 8354379f3..424556d4b 100644 --- a/src/script/lua_api/l_env.h +++ b/src/script/lua_api/l_env.h @@ -172,8 +172,7 @@ public: static void Initialize(lua_State *L, int top); }; -class LuaABM : public ActiveBlockModifier -{ +class LuaABM : public ActiveBlockModifier { private: int m_id; @@ -219,4 +218,12 @@ public: u32 active_object_count, u32 active_object_count_wider); }; +struct ScriptCallbackState { + GameScripting *script; + int callback_ref; + int args_ref; + unsigned int refcount; + std::string origin; +}; + #endif /* L_ENV_H_ */ diff --git a/src/server.h b/src/server.h index a4be7d3fb..3ec889971 100644 --- a/src/server.h +++ b/src/server.h @@ -378,6 +378,9 @@ public: // Bind address Address m_bind_addr; + // Environment mutex (envlock) + Mutex m_env_mutex; + private: friend class EmergeThread; @@ -518,7 +521,6 @@ private: // Environment ServerEnvironment *m_env; - Mutex m_env_mutex; // server connection con::Connection m_con;