mirror of
https://github.com/sbrl/Minetest-WorldEditAdditions.git
synced 2024-11-22 23:42:59 +00:00
run_command: implement support for async functions.
Also update //for to make use of this new functionality. //subdivide is still on the todo list. The new functionality works by adding the new property `async = false` to command definition tables registered via wea_core.register_command()`. When `true`, if and only if the command's MAIN FUNC ONLY returns no values at all then it will consider an async operation to be in progress. This delays `run_command` from emitting the `post-execute` event on `wea_core`. Additionally, all async commands have a callback function injected as the first argument to their main `func` (ref main cmd definition table). This callback function -- if no arguments are returned by the main `func` -- must be called once the async operation is complete with same args you would normally return from `func` -- that is `success, result_message`. These will then be handled as normal and sent to the player as appropriate, as well as finally emitting the `post-execute` event. BUG: There is a potential issue in this implementation, in that if i.e. `minetest.after()` is used to delay async execution then this will break out of the `xpcall()` protection in place to prevent crashes. To this end, if you implement an async function you need to be very careful, and do a manual `wea_core.safe_function()` call yourself!
This commit is contained in:
parent
a06136812f
commit
1ae48f3a52
3 changed files with 84 additions and 27 deletions
|
@ -15,7 +15,7 @@ local wea_c = worldeditadditions_core
|
||||||
-- ?Basename support for values
|
-- ?Basename support for values
|
||||||
-- ?Comma deliniation support for values
|
-- ?Comma deliniation support for values
|
||||||
|
|
||||||
local function step(params)
|
local function step(params, __callback)
|
||||||
-- Initialize additional params on first call
|
-- Initialize additional params on first call
|
||||||
if not params.first then
|
if not params.first then
|
||||||
params.i = 1 -- Iteration number
|
params.i = 1 -- Iteration number
|
||||||
|
@ -37,12 +37,9 @@ local function step(params)
|
||||||
|
|
||||||
if params.i <= #params.values then
|
if params.i <= #params.values then
|
||||||
-- If we haven't run out of values call function again
|
-- If we haven't run out of values call function again
|
||||||
minetest.after(0, step, params)
|
minetest.after(0, step, params, __callback)
|
||||||
else
|
else
|
||||||
wea_c.notify.ok(params.player_name, "For "..
|
__callback(true, "//for completed mapping values ["..table.concat(params.values, ", ").."] over /"..params.cmd_name.." "..tostring(params.args).." in "..wea_c.format.human_time(params.time))
|
||||||
table.concat(params.values,", ")..
|
|
||||||
", /"..params.cmd_name.." completed in " ..
|
|
||||||
wea_c.format.human_time(params.time))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -50,6 +47,7 @@ worldeditadditions_core.register_command("for", {
|
||||||
params = "<value1> <value2> <value3>... do //<command> <arg> %% <arg>",
|
params = "<value1> <value2> <value3>... do //<command> <arg> %% <arg>",
|
||||||
description = "Executes a chat command for each value before \" do \" replacing any instances of \"%%\" with those values. The forward slashes at the beginning of the chat command must be the same as if you were executing it normally.",
|
description = "Executes a chat command for each value before \" do \" replacing any instances of \"%%\" with those values. The forward slashes at the beginning of the chat command must be the same as if you were executing it normally.",
|
||||||
privs = { worldedit = true },
|
privs = { worldedit = true },
|
||||||
|
async = true,
|
||||||
parse = function(params_text)
|
parse = function(params_text)
|
||||||
if not params_text:match("%sdo%s") then
|
if not params_text:match("%sdo%s") then
|
||||||
return false, "Error: \"do\" argument is not present."
|
return false, "Error: \"do\" argument is not present."
|
||||||
|
@ -68,7 +66,9 @@ worldeditadditions_core.register_command("for", {
|
||||||
|
|
||||||
return true, values, command, args
|
return true, values, command, args
|
||||||
end,
|
end,
|
||||||
func = function(name, values, command, args)
|
func = function(__callback, name, values, command, args)
|
||||||
|
print("DEBUG://for __callback", wea_c.inspect(__callback), "name", name)
|
||||||
|
|
||||||
local cmd = minetest.registered_chatcommands[command]
|
local cmd = minetest.registered_chatcommands[command]
|
||||||
if not cmd then
|
if not cmd then
|
||||||
return false, "Error: "..command.." isn't a valid command."
|
return false, "Error: "..command.." isn't a valid command."
|
||||||
|
@ -77,13 +77,17 @@ worldeditadditions_core.register_command("for", {
|
||||||
return false, "Your privileges are insufficient to run /\""..command.."\"."
|
return false, "Your privileges are insufficient to run /\""..command.."\"."
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
step({
|
step({
|
||||||
player_name = name,
|
player_name = name,
|
||||||
cmd_name = command,
|
cmd_name = command,
|
||||||
values = values,
|
values = values,
|
||||||
cmd = cmd,
|
cmd = cmd,
|
||||||
args = args
|
args = args
|
||||||
})
|
}, __callback)
|
||||||
|
|
||||||
|
-- Returning nothing + async = true means we're going async and we'll get back to `run_command` later. Until then, I'm going to have to put you on hold :P
|
||||||
|
-- * cue hold music *
|
||||||
|
-- ref https://youtu.be/3to4vaWl2dY
|
||||||
end
|
end
|
||||||
})
|
})
|
||||||
|
|
|
@ -15,12 +15,20 @@ end
|
||||||
|
|
||||||
|
|
||||||
--- Registers a new WorldEditAdditions chat command.
|
--- Registers a new WorldEditAdditions chat command.
|
||||||
|
--
|
||||||
|
-- **Async commands:** Set `async = true` in the definition table you pass to this function. THEN, to indicate you are doing an async thing return NO VALUES AT ALL from your main `func` that is passed the parsed arguments. When `async = true`, your main `func` will be passed a callback function as the 1st argument BEFORE all other arguments.
|
||||||
|
--
|
||||||
|
-- Call this function when you are done with all async work with the same variables you would return: success: bool, result_message: string.
|
||||||
|
--
|
||||||
|
-- **IMPORTANT:** You MUST NOT return `success, result_message` from the main function AND call the callback function in a single call of a command!
|
||||||
|
-- An example of this in action can be seen in the implementation of `//for`.
|
||||||
-- @param cmdname string The name of the command to register.
|
-- @param cmdname string The name of the command to register.
|
||||||
-- @param options table A table of options for the command:
|
-- @param options table A table of options for the command:
|
||||||
-- - `params` (string) A textual description of the parameters the command takes.
|
-- - `params` (string) A textual description of the parameters the command takes.
|
||||||
-- - `description` (string) A description of the command.
|
-- - `description` (string) A description of the command.
|
||||||
-- - `privs` (`{someprivilege=true, ....}`) The privileges required to use the command.
|
-- - `privs` (`{someprivilege=true, ....}`) The privileges required to use the command.
|
||||||
-- - `require_pos` (number) The number of positions required for the command.
|
-- - `require_pos` (number) The number of positions required for the command.
|
||||||
|
-- - `async=false` (bool) Whether this function is async. See the note in the description of this function for more information.
|
||||||
-- - `parse` (function) A function that parses the raw param_text into proper input arguments to be passed to `nodes_needed` and `func`.
|
-- - `parse` (function) A function that parses the raw param_text into proper input arguments to be passed to `nodes_needed` and `func`.
|
||||||
-- - `nodes_needed` (function) A function that returns the number of nodes the command could potential change given the parsed input arguments.
|
-- - `nodes_needed` (function) A function that returns the number of nodes the command could potential change given the parsed input arguments.
|
||||||
-- - `func` (function) The function to execute when the command is run.
|
-- - `func` (function) The function to execute when the command is run.
|
||||||
|
@ -56,6 +64,7 @@ local function register_command(cmdname, options)
|
||||||
---
|
---
|
||||||
-- 2: Normalisation
|
-- 2: Normalisation
|
||||||
---
|
---
|
||||||
|
if options.async == nil then options.async = false end
|
||||||
if not options.privs then options.privs = {} end
|
if not options.privs then options.privs = {} end
|
||||||
if not options.require_pos then options.require_pos = 0 end
|
if not options.require_pos then options.require_pos = 0 end
|
||||||
if not options.nodes_needed then options.nodes_needed = function() return 0 end end
|
if not options.nodes_needed then options.nodes_needed = function() return 0 end end
|
||||||
|
|
|
@ -7,6 +7,26 @@ local safe_region = dofile(weac.modpath.."/core/safe_region.lua")
|
||||||
local human_size = weac.format.human_size
|
local human_size = weac.format.human_size
|
||||||
local safe_function = weac.safe_function
|
local safe_function = weac.safe_function
|
||||||
|
|
||||||
|
--- Handles the success bool and result message string that a command's main `func` returns.
|
||||||
|
-- @param success bool Whether the command executed successfully or not.
|
||||||
|
-- @param result_message string The message (as a string) to send to the player.
|
||||||
|
local function handle_success_resultmsg(player_name, cmdname, success, result_message)
|
||||||
|
if success then
|
||||||
|
if not result_message then
|
||||||
|
result_message = "//" .. tostring(cmdname) .. " successful"
|
||||||
|
end
|
||||||
|
weac.notify.ok(player_name, result_message)
|
||||||
|
else
|
||||||
|
if not result_message then
|
||||||
|
result_message =
|
||||||
|
"An unspecified (likely user) error was returned by the command. It is a bug that a specific error message is not returned here. It is not necessarily a bug that an error was thrown: your command invocation could have contained invalid syntax, for example."
|
||||||
|
end
|
||||||
|
weac.notify.error(player_name, "[//" .. tostring(cmdname) .. "] " .. result_message)
|
||||||
|
end
|
||||||
|
|
||||||
|
return success, result_message
|
||||||
|
end
|
||||||
|
|
||||||
--- Actually runs the command in question. [HIDDEN]
|
--- Actually runs the command in question. [HIDDEN]
|
||||||
-- Unfortunately needed to keep the codebase clena because Lua sucks.
|
-- Unfortunately needed to keep the codebase clena because Lua sucks.
|
||||||
-- @internal
|
-- @internal
|
||||||
|
@ -17,29 +37,52 @@ local safe_function = weac.safe_function
|
||||||
-- @returns nil
|
-- @returns nil
|
||||||
local function run_command_stage2(player_name, func, parse_result, tbl_event)
|
local function run_command_stage2(player_name, func, parse_result, tbl_event)
|
||||||
weac:emit("pre-execute", tbl_event)
|
weac:emit("pre-execute", tbl_event)
|
||||||
local success_safefn, retvals = safe_function(func, { player_name, weac.table.unpack(parse_result) }, player_name, "The function crashed during execution.", tbl_event.cmdname)
|
|
||||||
|
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~#
|
||||||
|
|
||||||
|
local args = {}
|
||||||
|
-- If we're async, add the callback function as the 1st argument before everything else
|
||||||
|
if tbl_event.cmddef.async then
|
||||||
|
table.insert(args, function(success, result_message)
|
||||||
|
success, result_message = handle_success_resultmsg(player_name, tbl_event.cmdname, success, result_message)
|
||||||
|
|
||||||
|
tbl_event.success = success
|
||||||
|
tbl_event.result = result_message
|
||||||
|
weac:emit("post-execute", tbl_event)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
-- Add player_name, unpack(args_from_parse_func).... afterwards
|
||||||
|
table.insert(args, player_name)
|
||||||
|
for _,value in ipairs(parse_result) do
|
||||||
|
table.insert(args, value)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Run the cmd itself and catch errors
|
||||||
|
local success_safefn, retvals = safe_function(func, args, player_name, "The function crashed during execution.", tbl_event.cmdname)
|
||||||
if not success_safefn then return false end
|
if not success_safefn then return false end
|
||||||
|
|
||||||
if #retvals ~= 2 then
|
-- BELOW: We handle the IMMEDIATE RETURN VALUE. For async commands this requires special handling as the actual exit of async commands is above.
|
||||||
weac.notify.error(player_name, "[//"..tostring(tbl_event.cmdname).."] The main execution function for this chat command returned "..tostring(#retvals).." arguments instead of the expected 2 (success, message), so it is unclear whether it succeeded or not. This is a bug!")
|
|
||||||
|
-- If a function is async (pass `async = true` in the table passed to weac.register_command()`), then if there are no return values then we assume it was successful.
|
||||||
|
if #retvals ~= 2 and not tbl_event.cmddef.async then
|
||||||
|
weac.notify.error(player_name, "[//"..tostring(tbl_event.cmdname).."] This command is not async and the main execution function for it returned "..tostring(#retvals).." arguments instead of the expected 2 (success, message), so it is unclear whether it succeeded or not. This is a bug!")
|
||||||
|
end
|
||||||
|
|
||||||
|
if #retvals == 2 then
|
||||||
|
local success, result_message = retvals[1], retvals[2]
|
||||||
|
success, result_message = handle_success_resultmsg(player_name, tbl_event.cmdname, success, result_message)
|
||||||
|
|
||||||
|
tbl_event.success = success
|
||||||
|
tbl_event.result = result_message
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local success, result_message = retvals[1], retvals[2]
|
-- This is outside the above before we need to fire post-execute even if `#retvals ~= 2` or something else happened
|
||||||
print("DEBUG:run_command_stage2 SUCCESS", success, "RESULT_MESSAGE", result_message)
|
--
|
||||||
if success then
|
-- Don't fire the post-execute event if async = true unless we were explicitly told its fine. If async = false then just go right ahead anyway
|
||||||
if not result_message then
|
if not tbl_event.cmddef.async or (tbl_event.cmddef.async and success) then
|
||||||
result_message = "//"..tostring(tbl_event.cmdname).." successful"
|
weac:emit("post-execute", tbl_event)
|
||||||
end
|
|
||||||
weac.notify.ok(player_name, result_message)
|
|
||||||
else
|
|
||||||
if not result_messasge then
|
|
||||||
result_message = "An unspecified (likely user) error was returned by the command. It is a bug that a specific error message is not returned here. It is not necessarily a bug that an error was thrown: your command invocation could have contained invalid syntax, for example."
|
|
||||||
end
|
|
||||||
weac.notify.error(player_name, "[//"..tostring(tbl_event.cmdname).."] "..result_message)
|
|
||||||
end
|
end
|
||||||
tbl_event.success = success
|
|
||||||
tbl_event.result = result_message
|
|
||||||
weac:emit("post-execute", tbl_event)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -93,7 +136,8 @@ end
|
||||||
-- - `paramargs` (table): The parsed arguments returned by the parsing function. Available in `post-parse` and later.
|
-- - `paramargs` (table): The parsed arguments returned by the parsing function. Available in `post-parse` and later.
|
||||||
-- - `potential_changes` (number): The number of potential nodes that could be changed as a result of running the command. `post-nodesneeded` and later: remember not all commands have an associated `nodesneeded` function.
|
-- - `potential_changes` (number): The number of potential nodes that could be changed as a result of running the command. `post-nodesneeded` and later: remember not all commands have an associated `nodesneeded` function.
|
||||||
-- - `success` (boolean): Whether the command executed successfully or not. Available only in `post-execute`.
|
-- - `success` (boolean): Whether the command executed successfully or not. Available only in `post-execute`.
|
||||||
-- - `result` (any): The `result` value returned by the command function. Value depends on the command executed. Available only in `post-execute`.
|
-- - `result` (any): The `result` value returned by the command function. Value depends on the command executed. Available only in `post-execute`. SHOULD be a string but don't count on it.
|
||||||
|
--
|
||||||
-- @param cmdname string The name of the command to run.
|
-- @param cmdname string The name of the command to run.
|
||||||
-- @param options table The table of options associated with the command. See worldeditadditions_core.register_command for more information.
|
-- @param options table The table of options associated with the command. See worldeditadditions_core.register_command for more information.
|
||||||
-- @param player_name string The name of the player to execute the command for.
|
-- @param player_name string The name of the player to execute the command for.
|
||||||
|
|
Loading…
Reference in a new issue