Skip to content

Commit

Permalink
Add /emergeblocks command and core.emerge_area() Lua API
Browse files Browse the repository at this point in the history
  • Loading branch information
kwolekr committed Sep 23, 2015
1 parent 596484d commit f062bbd
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 49 deletions.
30 changes: 30 additions & 0 deletions builtin/common/misc_helpers.lua
Expand Up @@ -554,6 +554,36 @@ assert(core.string_to_pos("10.0, 5, -2").x == 10)
assert(core.string_to_pos("( 10.0, 5, -2)").z == -2)
assert(core.string_to_pos("asd, 5, -2)") == nil)

--------------------------------------------------------------------------------
function core.string_to_area(value)
local p1, p2 = unpack(value:split(") ("))
if p1 == nil or p2 == nil then
return nil
end

p1 = core.string_to_pos(p1 .. ")")
p2 = core.string_to_pos("(" .. p2)
if p1 == nil or p2 == nil then
return nil
end

return p1, p2
end

local function test_string_to_area()
local p1, p2 = core.string_to_area("(10.0, 5, -2) ( 30.2, 4, -12.53)")
assert(p1.x == 10.0 and p1.y == 5 and p1.z == -2)
assert(p2.x == 30.2 and p2.y == 4 and p2.z == -12.53)

p1, p2 = core.string_to_area("(10.0, 5, -2 30.2, 4, -12.53")
assert(p1 == nil and p2 == nil)

p1, p2 = core.string_to_area("(10.0, 5,) -2 fgdf2, 4, -12.53")
assert(p1 == nil and p2 == nil)
end

test_string_to_area()

--------------------------------------------------------------------------------
function table.copy(t, seen)
local n = {}
Expand Down
70 changes: 41 additions & 29 deletions builtin/game/chatcommands.lua
Expand Up @@ -51,6 +51,27 @@ core.register_on_chat_message(function(name, message)
return true -- Handled chat message
end)

-- Parses a "range" string in the format of "here (number)" or
-- "(x1, y1, z1) (x2, y2, z2)", returning two position vectors
local function parse_range_str(player_name, str)
local p1, p2
local args = str:split(" ")

if args[1] == "here" then
p1, p2 = core.get_player_radius_area(player_name, tonumber(args[2]))
if p1 == nil then
return false, "Unable to get player " .. player_name .. " position"
end
else
p1, p2 = core.string_to_area(str)
if p1 == nil then
return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)"
end
end

return p1, p2
end

--
-- Chat commands
--
Expand Down Expand Up @@ -415,40 +436,31 @@ core.register_chatcommand("set", {
end,
})

core.register_chatcommand("deleteblocks", {
core.register_chatcommand("emergeblocks", {
params = "(here [radius]) | (<pos1> <pos2>)",
description = "delete map blocks contained in area pos1 to pos2",
description = "starts loading (or generating, if inexistent) map blocks "
.. "contained in area pos1 to pos2",
privs = {server=true},
func = function(name, param)
local p1 = {}
local p2 = {}
local args = param:split(" ")
if args[1] == "here" then
local player = core.get_player_by_name(name)
if player == nil then
core.log("error", "player is nil")
return false, "Unable to get current position; player is nil"
end
p1 = player:getpos()
p2 = p1

if #args >= 2 then
local radius = tonumber(args[2]) or 0
p1 = vector.add(p1, radius)
p2 = vector.subtract(p2, radius)
end
else
local pos1, pos2 = unpack(param:split(") ("))
if pos1 == nil or pos2 == nil then
return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)"
end
local p1, p2 = parse_range_str(name, param)
if p1 == false then
return false, p2
end

p1 = core.string_to_pos(pos1 .. ")")
p2 = core.string_to_pos("(" .. pos2)
core.emerge_area(p1, p2)
return true, "Started emerge of area ranging from " ..
core.pos_to_string(p1, 1) .. " to " .. core.pos_to_string(p2, 1)
end,
})

if p1 == nil or p2 == nil then
return false, "Incorrect area format. Expected: (x1,y1,z1) (x2,y2,z2)"
end
core.register_chatcommand("deleteblocks", {
params = "(here [radius]) | (<pos1> <pos2>)",
description = "delete map blocks contained in area pos1 to pos2",
privs = {server=true},
func = function(name, param)
local p1, p2 = parse_range_str(name, param)
if p1 == false then
return false, p2
end

if core.delete_area(p1, p2) then
Expand Down
19 changes: 19 additions & 0 deletions builtin/game/misc.lua
Expand Up @@ -109,6 +109,25 @@ function core.get_connected_players()
return temp_table
end

-- Returns two position vectors representing a box of `radius` in each
-- direction centered around the player corresponding to `player_name`
function core.get_player_radius_area(player_name, radius)
local player = core.get_player_by_name(player_name)
if player == nil then
return nil
end

local p1 = player:getpos()
local p2 = p1

if radius then
p1 = vector.subtract(p1, radius)
p2 = vector.add(p2, radius)
end

return p1, p2
end

function core.hash_node_position(pos)
return (pos.z+32768)*65536*65536 + (pos.y+32768)*65536 + pos.x+32768
end
Expand Down
5 changes: 5 additions & 0 deletions doc/lua_api.txt
Expand Up @@ -1708,6 +1708,8 @@ Helper functions
* Convert position to a printable string
* `minetest.string_to_pos(string)`: returns a position
* Same but in reverse. Returns `nil` if the string can't be parsed to a position.
* `minetest.string_to_area("(X1, Y1, Z1) (X2, Y2, Z2)")`: returns two positions
* Converts a string representing an area box into two positions
* `minetest.formspec_escape(string)`: returns a string
* escapes the characters "[", "]", "\", "," and ";", which can not be used in formspecs
* `minetest.is_yes(arg)`
Expand Down Expand Up @@ -2020,6 +2022,9 @@ 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

This comment has been minimized.

Copy link
@HybridDog

HybridDog Sep 24, 2015

Contributor

Can l use this before using minetest.get_node in unloaded chunks?

This comment has been minimized.

Copy link
@est31

est31 Sep 24, 2015

Contributor

Nope, because of two insecurities:

  1. It doesn't wait until the area is emerged, but simply tells the emergethread: "if you have time, please emerge those"
  2. after the area has been emerged, you can't be sure how long it is that way, because the server will unload unused areas.

Therefore, the only legal use case is for calling the mapgen.

This comment has been minimized.

Copy link
@est31

This comment has been minimized.

Copy link
@HybridDog

HybridDog Sep 25, 2015

Contributor

ok, thanks

This comment has been minimized.

Copy link
@bendeutsch

bendeutsch Sep 26, 2015

Contributor

I'm planning on calling emerge_area one second before I teleport the player (to a generated location, I should add), and not relying on the destination already / still being loaded when I teleport. I'm assuming this still counts as a "legal case"? Or am I missing something?

This comment has been minimized.

Copy link
@RobertZenz

RobertZenz Sep 26, 2015

Contributor

From what I understood so far, no that's not a good use case. The block will be queued for loading/generating, there is no guarantee that this will happen within that second (might also take 10 seconds). So it doesn't really help you, as sending a player to that block would basically do the same. What you most likely want is minetest.forceload_block(pos).

This comment has been minimized.

Copy link
@rubenwardy

rubenwardy Sep 26, 2015

Member

This function should be called minetest.emerge_area_async()

This comment has been minimized.

Copy link
@est31

est31 Sep 26, 2015

Contributor

@rubenwardy +1
@bendeutsch While the command in fact does load the area, you can't be sure that the area gets unloaded before the player gets there. But I think if the time is inside server_unload_unused_data_timeout, it should still be kept loaded. In that case, the use of minetest.emerge_area is effective.

This comment has been minimized.

Copy link
@kwolekr

kwolekr Oct 18, 2015

Author Contributor

I'll add a note in the API that this is asynchronous. Going to also add callbacks to the Lua API for emerged blocks soon, so there's another way to synchronize this.

* `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`
Expand Down
40 changes: 30 additions & 10 deletions src/emerge.cpp
Expand Up @@ -235,11 +235,13 @@ void EmergeManager::stopThreads()
}


bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate)
bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p,
bool allow_generate, bool force_queue_block)
{
std::map<v3s16, BlockEmergeData *>::const_iterator iter;
BlockEmergeData *bedata;
u16 count;
u16 count_global = 0;
u16 count_peer = 0;
u8 flags = 0;
int idx = 0;

Expand All @@ -249,14 +251,17 @@ bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate
{
MutexAutoLock queuelock(queuemutex);

count = blocks_enqueued.size();
if (count >= qlimit_total)
return false;
count_global = blocks_enqueued.size();
count_peer = peer_queue_count[peer_id];

count = peer_queue_count[peer_id];
u16 qlimit_peer = allow_generate ? qlimit_generate : qlimit_diskonly;
if (count >= qlimit_peer)
return false;
if (!force_queue_block) {
if (count_global >= qlimit_total)
return false;

u16 qlimit_peer = allow_generate ? qlimit_generate : qlimit_diskonly;
if (count_peer >= qlimit_peer)
return false;
}

iter = blocks_enqueued.find(p);
if (iter != blocks_enqueued.end()) {
Expand All @@ -270,7 +275,7 @@ bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate
bedata->peer_requested = peer_id;
blocks_enqueued.insert(std::make_pair(p, bedata));

peer_queue_count[peer_id] = count + 1;
peer_queue_count[peer_id] = count_peer + 1;

// insert into the EmergeThread queue with the least items
int lowestitems = emergethread[0]->blockqueue.size();
Expand All @@ -289,6 +294,21 @@ bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate
return true;
}

v3s16 EmergeManager::getContainingChunk(v3s16 blockpos)
{
return getContainingChunk(blockpos, params.chunksize);
}


v3s16 EmergeManager::getContainingChunk(v3s16 blockpos, s16 chunksize)
{
s16 coff = -chunksize / 2;
v3s16 chunk_offset(coff, coff, coff);

return getContainerPos(blockpos - chunk_offset, chunksize)
* chunksize + chunk_offset;
}


int EmergeManager::getGroundLevelAtPoint(v2s16 p)
{
Expand Down
8 changes: 6 additions & 2 deletions src/emerge.h
Expand Up @@ -109,9 +109,13 @@ class EmergeManager {
static void getMapgenNames(std::list<const char *> &mgnames);
void startThreads();
void stopThreads();
bool enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate);
bool enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate,
bool force_queue_block=false);

//mapgen helper methods
v3s16 getContainingChunk(v3s16 blockpos);
static v3s16 getContainingChunk(v3s16 blockpos, s16 chunksize);

// mapgen helper methods
Biome *getBiomeAtPoint(v3s16 p);
int getGroundLevelAtPoint(v2s16 p);
bool isBlockUnderground(v3s16 blockpos);
Expand Down
11 changes: 3 additions & 8 deletions src/map.cpp
Expand Up @@ -2259,14 +2259,9 @@ bool ServerMap::initBlockMake(BlockMakeData *data, v3s16 blockpos)
bool enable_mapgen_debug_info = m_emerge->mapgen_debug_info;
EMERGE_DBG_OUT("initBlockMake(): " PP(blockpos) " - " PP(blockpos));

s16 chunksize = m_emerge->params.chunksize;
s16 coffset = -chunksize / 2;
v3s16 chunk_offset(coffset, coffset, coffset);
v3s16 blockpos_div = getContainerPos(blockpos - chunk_offset, chunksize);
v3s16 blockpos_min = blockpos_div * chunksize;
v3s16 blockpos_max = blockpos_div * chunksize + v3s16(1,1,1)*(chunksize-1);
blockpos_min += chunk_offset;
blockpos_max += chunk_offset;
s16 csize = m_emerge->params.chunksize;
v3s16 blockpos_min = EmergeManager::getContainingChunk(blockpos, csize);
v3s16 blockpos_max = blockpos_min + v3s16(1, 1, 1) * (csize - 1);

v3s16 extra_borders(1,1,1);

Expand Down
25 changes: 25 additions & 0 deletions src/script/lua_api/l_env.cpp
Expand Up @@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/pointedthing.h"
#include "content_sao.h"
#include "treegen.h"
#include "emerge.h"
#include "pathfinder.h"

#define GET_ENV_PTR ServerEnvironment* env = \
Expand Down Expand Up @@ -751,6 +752,29 @@ int ModApiEnvMod::l_line_of_sight(lua_State *L)
return 1;
}


// emerge_area(p1, p2)
// emerge mapblocks in area p1..p2
int ModApiEnvMod::l_emerge_area(lua_State *L)
{
GET_ENV_PTR;

EmergeManager *emerge = getServer(L)->getEmergeManager();

v3s16 bpmin = getNodeBlockPos(read_v3s16(L, 1));
v3s16 bpmax = getNodeBlockPos(read_v3s16(L, 2));
sortBoxVerticies(bpmin, bpmax);

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);

This comment has been minimized.

Copy link
@RobertZenz

RobertZenz Sep 24, 2015

Contributor

Shouldn't the third parameter here be true, because that is allow_generate and so the command can't be used to pre-generate the map. If this would be flipped to true this would satisfy #3183.

This comment has been minimized.

Copy link
@est31

est31 Sep 24, 2015

Contributor

Ah yes, I've pointed out @kwolekr to fix that, but both he and me read the implementation of the method wrong, and we thought the param doesnt influence generation, and only is used for queues. But it does:

f062bbd#diff-1dc3934293fad6b8769890134ac51c21R248

This comment has been minimized.

Copy link
@kwolekr

kwolekr Oct 18, 2015

Author Contributor

Oh, crap, I didn't see this, sorry. Will fix.

This comment has been minimized.

Copy link
@est31

est31 Oct 19, 2015

Contributor

@kwolekr if you're at it, can you rename /emergeblocks to /emerge_blocks ? @ShadowNinja will like it.

}

return 0;
}

// delete_area(p1, p2)
// delete mapblocks in area p1..p2
int ModApiEnvMod::l_delete_area(lua_State *L)
Expand Down Expand Up @@ -954,6 +978,7 @@ void ModApiEnvMod::Initialize(lua_State *L, int top)
API_FCT(find_node_near);
API_FCT(find_nodes_in_area);
API_FCT(find_nodes_in_area_under_air);
API_FCT(emerge_area);
API_FCT(delete_area);
API_FCT(get_perlin);
API_FCT(get_perlin_map);
Expand Down
3 changes: 3 additions & 0 deletions src/script/lua_api/l_env.h
Expand Up @@ -125,6 +125,9 @@ class ModApiEnvMod : public ModApiBase {
// nodenames: eg. {"ignore", "group:tree"} or "default:dirt"
static int l_find_nodes_in_area_under_air(lua_State *L);

// emerge_area(p1, p2)
static int l_emerge_area(lua_State *L);

// delete_area(p1, p2) -> true/false
static int l_delete_area(lua_State *L);

Expand Down

0 comments on commit f062bbd

Please sign in to comment.