diff --git a/async.lua b/async.lua new file mode 100644 index 0000000..3e313e7 --- /dev/null +++ b/async.lua @@ -0,0 +1,23 @@ +areas = rawget(_G, "areas") or {} + +local safe_file_write = core.safe_file_write +if safe_file_write == nil then + safe_file_write = function(path, content) + local file, err = io.open(path, "w") + if err then + return err + end + file:write(content) + file:close() + end +end + +-- Save the areas table to a file +function areas._internal_do_save(areas_tb, filename) + local datastr = core.write_json(areas_tb) + if not datastr then + core.log("error", "[areas] Failed to serialize area data!") + return + end + return safe_file_write(filename, datastr) +end diff --git a/init.lua b/init.lua index 4be95d9..c4e57b4 100644 --- a/init.lua +++ b/init.lua @@ -7,11 +7,15 @@ areas = {} areas.factions_available = minetest.get_modpath("playerfactions") and true areas.adminPrivs = {areas=true} -areas.startTime = os.clock() +local startTime = os.clock() areas.modpath = minetest.get_modpath("areas") dofile(areas.modpath.."/settings.lua") dofile(areas.modpath.."/api.lua") + +local async_dofile = core.register_async_dofile or dofile +async_dofile(areas.modpath.."/async.lua") + dofile(areas.modpath.."/internal.lua") dofile(areas.modpath.."/chatcommands.lua") dofile(areas.modpath.."/pos.lua") @@ -39,6 +43,6 @@ if not minetest.registered_privileges[areas.config.self_protection_privilege] th end if minetest.settings:get_bool("log_mods") then - local diffTime = os.clock() - areas.startTime + local diffTime = os.clock() - startTime minetest.log("action", "areas loaded in "..diffTime.."s.") end diff --git a/internal.lua b/internal.lua index f018084..700af0f 100644 --- a/internal.lua +++ b/internal.lua @@ -4,26 +4,38 @@ function areas:player_exists(name) return minetest.get_auth_handler().get_auth(name) ~= nil end -local safe_file_write = minetest.safe_file_write -if safe_file_write == nil then - function safe_file_write(path, content) - local file, err = io.open(path, "w") - if err then - return err +-- When saving is done in an async thread, the function will not be present in this global namespace. +if not areas._internal_do_save then + local saving_requested = false + local saving_locked = false + + -- Required cuz we are referring to _G.areas._internal_do_save *inside* + -- async env (it does not exist in the main thread) + local function async_func(...) + return areas._internal_do_save(...) + end + + local function done_callback() + saving_locked = false + if saving_requested == true then + saving_requested = false + return areas:save() end - file:write(content) - file:close() end -end --- Save the areas table to a file -function areas:save() - local datastr = minetest.write_json(self.areas) - if not datastr then - minetest.log("error", "[areas] Failed to serialize area data!") - return + function areas:save() + if saving_locked == true then + saving_requested = true + else + saving_locked = true + return core.handle_async(async_func, done_callback, self.areas, self.config.filename) + end + end +else + -- Save the areas table to a file + function areas:save() + return areas._internal_do_save(self.areas, self.config.filename) end - return safe_file_write(self.config.filename, datastr) end -- Load the areas table from the save file @@ -83,19 +95,19 @@ function areas:populateStore() self.store_ids = store_ids end --- Finds the first usable index in a table --- Eg: {[1]=false,[4]=true} -> 2 -local function findFirstUnusedIndex(t) - local i = 0 - repeat i = i + 1 - until t[i] == nil - return i +-- Guarentees returning an unused index in areas.areas +local index_cache = 0 +local function findFirstUnusedIndex() + local t = areas.areas + repeat index_cache = index_cache + 1 + until t[index_cache] == nil + return index_cache end --- Add an area. -- @return The new area's ID. function areas:add(owner, name, pos1, pos2, parent) - local id = findFirstUnusedIndex(self.areas) + local id = findFirstUnusedIndex() self.areas[id] = { name = name, pos1 = pos1, diff --git a/pos.lua b/pos.lua index a90e194..c24c3d6 100644 --- a/pos.lua +++ b/pos.lua @@ -6,8 +6,6 @@ local S = minetest.get_translator("areas") -- Since this is mostly copied from WorldEdit it is mostly -- licensed under the AGPL. (select_area is an exception) -areas.marker1 = {} -areas.marker2 = {} areas.set_pos = {} areas.pos1 = {} areas.pos2 = {} @@ -181,41 +179,67 @@ function areas:getPos(playerName) return areas:sortPos(pos1, pos2) end -function areas:setPos1(playerName, pos) - areas.pos1[playerName] = posLimit(pos) - areas.markPos1(playerName) -end +function areas:setPos1(name, pos) + local old_pos = areas.pos1[name] + pos = posLimit(pos) + areas.pos1[name] = pos -function areas:setPos2(playerName, pos) - areas.pos2[playerName] = posLimit(pos) - areas.markPos2(playerName) + local entity = minetest.add_entity(pos, "areas:pos1") + if entity then + local luaentity = entity:get_luaentity() + if luaentity then + luaentity.player = name + end + end + + if old_pos then + for object in core.objects_inside_radius(old_pos, 0.01) do + local luaentity = object:get_luaentity() + if luaentity and luaentity.name == "areas:pos1" and luaentity.player == name then + object:remove() + end + end + end end +function areas:setPos2(name, pos) + local old_pos = areas.pos2[name] + pos = posLimit(pos) + areas.pos2[name] = pos + + local entity = minetest.add_entity(pos, "areas:pos2") + if entity then + local luaentity = entity:get_luaentity() + if luaentity then + luaentity.player = name + end + end + + if old_pos then + for object in core.objects_inside_radius(old_pos, 0.01) do + local luaentity = object:get_luaentity() + if luaentity and luaentity.name == "areas:pos2" and luaentity.player == name then + object:remove() + end + end + end +end minetest.register_on_punchnode(function(pos, node, puncher) local name = puncher:get_player_name() -- Currently setting position if name ~= "" and areas.set_pos[name] then - if areas.set_pos[name] == "pos1" then - areas.pos1[name] = pos - areas.markPos1(name) - areas.set_pos[name] = "pos2" - minetest.chat_send_player(name, - S("Position @1 set to @2", "1", - minetest.pos_to_string(pos))) - elseif areas.set_pos[name] == "pos1only" then - areas.pos1[name] = pos - areas.markPos1(name) + if areas.set_pos[name] == "pos2" then + areas:setPos2(name, pos) areas.set_pos[name] = nil minetest.chat_send_player(name, - S("Position @1 set to @2", "1", + S("Position @1 set to @2", "2", minetest.pos_to_string(pos))) - elseif areas.set_pos[name] == "pos2" then - areas.pos2[name] = pos - areas.markPos2(name) - areas.set_pos[name] = nil + else + areas:setPos1(name, pos) + areas.set_pos[name] = areas.set_pos[name] == "pos1" and "pos2" or nil minetest.chat_send_player(name, - S("Position @1 set to @2", "2", + S("Position @1 set to @2", "1", minetest.pos_to_string(pos))) end end @@ -237,32 +261,6 @@ function areas:sortPos(pos1, pos2) return pos1, pos2 end --- Marks area position 1 -areas.markPos1 = function(name) - local pos = areas.pos1[name] - if areas.marker1[name] ~= nil then -- Marker already exists - areas.marker1[name]:remove() -- Remove marker - areas.marker1[name] = nil - end - if pos ~= nil then -- Add marker - areas.marker1[name] = minetest.add_entity(pos, "areas:pos1") - areas.marker1[name]:get_luaentity().active = true - end -end - --- Marks area position 2 -areas.markPos2 = function(name) - local pos = areas.pos2[name] - if areas.marker2[name] ~= nil then -- Marker already exists - areas.marker2[name]:remove() -- Remove marker - areas.marker2[name] = nil - end - if pos ~= nil then -- Add marker - areas.marker2[name] = minetest.add_entity(pos, "areas:pos2") - areas.marker2[name]:get_luaentity().active = true - end -end - minetest.register_entity("areas:pos1", { initial_properties = { visual = "cube", @@ -271,17 +269,10 @@ minetest.register_entity("areas:pos1", { "areas_pos1.png", "areas_pos1.png", "areas_pos1.png", "areas_pos1.png"}, collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55}, + hp_max = 1, + armor_groups = {fleshy=100}, + static_save = false, }, - on_step = function(self, dtime) - if self.active == nil then - self.object:remove() - end - end, - on_punch = function(self, hitter) - self.object:remove() - local name = hitter:get_player_name() - areas.marker1[name] = nil - end, }) minetest.register_entity("areas:pos2", { @@ -292,15 +283,8 @@ minetest.register_entity("areas:pos2", { "areas_pos2.png", "areas_pos2.png", "areas_pos2.png", "areas_pos2.png"}, collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55}, + hp_max = 1, + armor_groups = {fleshy=100}, + static_save = false, }, - on_step = function(self, dtime) - if self.active == nil then - self.object:remove() - end - end, - on_punch = function(self, hitter) - self.object:remove() - local name = hitter:get_player_name() - areas.marker2[name] = nil - end, })