epic/executor/executor.lua
2021-10-29 08:09:44 +02:00

225 lines
5.6 KiB
Lua

-- time in seconds between executor calls
local executor_dtime = 0.1
function epic.execute_player_state(playername, state, recursion_depth)
local pos = state.ip
local player = minetest.get_player_by_name(playername)
if recursion_depth and recursion_depth > 100 then
-- abort execution if the recursion limit is reached
epic.state[playername] = nil
minetest.log("warn", "[epic][executor] max recursion depth exceeded at: " .. minetest.pos_to_string(pos))
return
end
if not player then
minetest.log("warn", "[epic][executor] player not found, aborting: " .. playername)
return
end
epic.debug("[executor] execute_player_state(" .. playername .. "))")
if not pos then
-- invalid state
epic.state[playername] = nil
minetest.log("warn", "[epic][executor] invalid opcode encountered, state-dump: " .. dump(state))
return
end
local node = epic.get_node(pos)
if not epic.is_epic(node) then
-- no more instructions in this branch
epic.debug("[executor] no more instructions in this branch @ " ..
minetest.pos_to_string(pos) .. " node: " .. node.name)
if #state.stack > 0 then
-- pop stack
state.ip = table.remove(state.stack, #state.stack)
state.initialized = false
state.step_data = {}
epic.debug("[executor] pop stack result: " .. minetest.pos_to_string(state.ip))
epic.execute_player_state(playername, state)
else
-- done
epic.state[playername] = nil
epic.run_hook("on_epic_exit", {playername, state})
end
return
end
local result_next = false
local result_next_pos = nil
local abort_flag = state.abort
local ctx = {
-- next step
next = function(_pos)
result_next = true
result_next_pos = _pos
end,
-- abort epic with given reason
abort = function(reason)
abort_flag = reason or "ctx.abort"
end,
-- call another epic block
call = function(_pos)
-- push next ip
local next_pos = epic.get_next_pos(pos)
local next_node = epic.get_node(next_pos)
if epic.is_epic(next_node) then
-- this branch has more instructions, push the next onto the stack
table.insert(state.stack, next_pos)
end
result_next = true
result_next_pos = _pos
end,
-- set the new timeout
settimeout = function(seconds)
state.time = seconds
end,
data = state.data,
step_data = state.step_data
}
local nodedef = minetest.registered_nodes[node.name]
local epicdef = nodedef.epic
local meta = minetest.get_meta(pos)
if not state.initialized then
state.initialized = true
epic.run_hook("on_before_node_enter", { pos, player, ctx })
if epicdef.on_enter then
epicdef.on_enter(pos, meta, player, ctx)
end
end
epic.run_hook("on_before_node_check", { pos, player, ctx })
if epicdef.on_check then
epicdef.on_check(pos, meta, player, ctx)
end
if state.time then
state.time = state.time - executor_dtime
if state.time < 0 then
abort_flag = "epic_timeout"
end
end
if abort_flag or result_next then
epic.run_hook("on_before_node_exit", { pos, player, ctx })
if epicdef.on_exit then
epicdef.on_exit(pos, meta, player, ctx)
end
end
if abort_flag then
epic.state[playername] = nil
epic.run_hook("on_epic_abort", { playername, state, abort_flag })
return
end
if result_next then
local next_pos = result_next_pos or epic.get_next_pos(pos)
state.ip = next_pos
state.initialized = false
state.step_data = {}
-- set the recursion depth if not set
recursion_depth = recursion_depth or 1
epic.execute_player_state(playername, state, recursion_depth + 1)
end
end
local function executor()
local t0 = minetest.get_us_time()
for playername, state in pairs(epic.state) do
if not minetest.get_player_by_name(playername) then
-- player is gone, clear state
epic.state[playername] = nil
else
-- player is still online
epic.execute_player_state(playername, state)
end
end
local t1 = minetest.get_us_time()
local stats = {
time = t1-t0
}
epic.run_hook("globalstep_stats", { stats })
-- restart execution
minetest.after(executor_dtime, executor)
end
-- initial delay
minetest.after(1.0, executor)
-- cleans up the remaining state, if any
local function cleanup_state(player, state)
local pos = state.ip
if pos then
local node = epic.get_node(state.ip)
local nodedef = minetest.registered_nodes[node.name]
local epicdef = nodedef.epic
if type(epicdef.on_exit) == "function" then
local meta = minetest.get_meta(pos)
epicdef.on_exit(pos, meta, player, state)
end
end
end
-- abort player helper function
-- called on leave and shutdown
local function abort_player(player, timed_out)
local playername = player:get_player_name()
local state = epic.state[playername]
if state then
local reason
if timed_out then
reason = "leave_timed_out"
else
reason = "leave"
end
if epic.log_executor then
minetest.log("action", "[epic] player left the game: " .. playername)
end
cleanup_state(player, state)
epic.state[playername] = nil
epic.run_hook("on_epic_abort", { playername, state, reason })
end
end
-- abort epic on leave
-- savepoints are not touched here
minetest.register_on_leaveplayer(abort_player)
minetest.register_on_shutdown(function()
minetest.log("action", "[epic] shutdown detected, aborting all epics")
for _, player in ipairs(minetest.get_connected_players()) do
-- abort every players epics
abort_player(player, false)
end
end)
minetest.register_on_dieplayer(function(player)
local playername = player:get_player_name()
local state = epic.state[playername]
if state then
if epic.log_executor then
minetest.log("action", "[epic] player died: " .. playername)
end
cleanup_state(player, state)
epic.state[playername] = nil
epic.run_hook("on_epic_abort", { playername, state, "died" })
end
end)