225 lines
5.6 KiB
Lua
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)
|