File size: 10,110 Bytes
b6a38d7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 |
--- Holds a flag indicating whether there are any pending reasons to suspend the process.
--- Holds a flag indicating whether there are any pending reasons to suspend the process.
---
--- @type boolean
SuspendProcessReasons = nil
--- Holds a flag indicating whether the process is currently suspended.
---
--- @type boolean
SuspendedProcessing = nil
--- A function that checks the current execution timestamp.
---
--- @type function
CheckExecutionTimestamp = empty_func
--- A function that checks for any remaining reasons to suspend the process.
---
--- @type function
CheckRemainingReason = empty_func
--- A function that unpacks a table.
---
--- @type function
table_unpack = table.unpack
--- A function that checks if two tables are equal.
---
--- @type function
table_iequal = table.iequal
local SuspendProcessReasons
local SuspendedProcessing
local CheckExecutionTimestamp = empty_func
local CheckRemainingReason = empty_func
local table_unpack = table.unpack
local table_iequal = table.iequal
if FirstLoad then
__process_params_meta = {
__eq = function(t1, t2)
if type(t1) ~= type(t2) or not rawequal(getmetatable(t1), getmetatable(t2)) then
return false
end
local count = t1[1]
if count ~= t2[1] then
return false
end
for i=2,count do
if t1[i] ~= t2[i] then
return false
end
end
return true
end
}
end
---
--- Packs the provided parameters into a table with a metatable that allows for efficient equality comparison.
---
--- @param obj any The first parameter to be packed.
--- @param ... any Additional parameters to be packed.
--- @return table A table containing the packed parameters, with a metatable that allows for efficient equality comparison.
---
local function PackProcessParams(obj, ...)
local count = select("#", ...)
if count == 0 then
return obj or false
end
return setmetatable({count + 2, obj, ...}, __process_params_meta)
end
---
--- Unpacks the parameters from a table that was packed using the `PackProcessParams` function.
---
--- @param params table A table containing the packed parameters, with a metatable that allows for efficient equality comparison.
--- @return any The unpacked parameters.
---
function UnpackProcessParams(params)
if type(params) ~= "table" or getmetatable(params) ~= __process_params_meta then
return params
end
return table_unpack(params, 2, params[1])
end
function OnMsg.DoneMap()
CheckRemainingReason()
SuspendProcessReasons = false
SuspendedProcessing = false
end
---
--- Executes any suspended functions for the given process.
---
--- @param process string The name of the process for which to execute suspended functions.
---
function ExecuteSuspended(process)
-- Implementation details omitted for brevity
end
local function ExecuteSuspended(process)
local delayed = SuspendedProcessing
local funcs_to_params = delayed and delayed[process]
if not funcs_to_params then
return
end
delayed[process] = nil
local procall = procall
for _, funcname in ipairs(funcs_to_params) do
local func = _G[funcname]
for _, params in ipairs(funcs_to_params[funcname]) do
dbg(CheckExecutionTimestamp(process, funcname, params, true))
procall(func, UnpackProcessParams(params))
end
end
end
---
--- Cancels the processing of routines from a named process.
---
--- @param process string The name of the process for which to cancel processing.
---
function CancelProcessing(process)
if not SuspendProcessReasons or not SuspendProcessReasons[process] then
return
end
if SuspendedProcessing then
SuspendedProcessing[process] = nil
end
SuspendProcessReasons[process] = nil
Msg("ProcessingResumed", process, "cancel")
end
--[[@@@
Checks if the processing of routines from a named process is currently suspended
@function bool IsProcessingSuspended(string process)
--]]
function IsProcessingSuspended(process)
local process_to_reasons = SuspendProcessReasons
return process_to_reasons and next(process_to_reasons[process])
end
--[[@@@
Suspends the processing of routines from a named process. Multiple suspending with the same reason would lead to an error.
@function void SuspendProcessing(string process, type reason, bool ignore_errors)
@param string process - the name of the process, which routines should be suspended.
@param type reason - the reason to be used in order to resume the processing later. Could be any type.
@param bool ignore_errors - ignore suspending errors (e.g. process already suspended).
--]]
function SuspendProcessing(process, reason, ignore_errors)
reason = reason or ""
local reasons = SuspendProcessReasons and SuspendProcessReasons[process]
if reasons and reasons[reason] then
assert(ignore_errors)
return
end
local now = GameTime()
if reasons then
reasons[reason] = now
return
end
SuspendProcessReasons = table.set(SuspendProcessReasons, process, reason, now)
Msg("ProcessingSuspended", process)
end
--[[@@@
Resumes the processing of routines from a named process. Resuming an already resumed process, or resuming it with time delay, would lead to an error.
@function void ResumeProcessing(string process, type reason, bool ignore_errors)
@param string process - the name of the process, which routines should be suspended.
@param type reason - the reason to be used in order to resume the processing later. Could be any type.
@param bool ignore_errors - ignore resume errors (e.g. process already resumed).
--]]
function ResumeProcessing(process, reason, ignore_errors)
reason = reason or ""
local reasons = SuspendProcessReasons and SuspendProcessReasons[process]
local suspended = reasons and reasons[reason]
if not suspended then
return
end
assert(ignore_errors or suspended == GameTime())
local now = GameTime()
reasons[reason] = nil
if next(reasons) ~= nil then
return
end
assert(not IsProcessingSuspended(process))
ExecuteSuspended(process)
Msg("ProcessingResumed", process)
end
--[[@@@
Execute a routine from a named process. If the process is currently suspended, the call will be registered in ordered to be executed once the process is resumed. Multiple calls with the same context will be registered as one.
@function void ExecuteProcess(string process, function func, table obj)
@param string process - the name of the process, which routines should be suspended.
@param function func - the function to be executed.
@param table obj - optional function context.
--]]
function ExecuteProcess(process, funcname, obj, ...)
if not IsProcessingSuspended(process) then
dbg(CheckExecutionTimestamp(process, funcname, obj))
return procall(_G[funcname], obj, ...)
end
local params = PackProcessParams(obj, ...)
local suspended = SuspendedProcessing
if not suspended then
suspended = {}
SuspendedProcessing = suspended
end
local funcs_to_params = suspended[process]
if not funcs_to_params then
suspended[process] = { funcname, [funcname] = {params} }
return
end
local objs = funcs_to_params[funcname]
if not objs then
funcs_to_params[#funcs_to_params + 1] = funcname
funcs_to_params[funcname] = {params}
return
end
table.insert_unique(objs, params)
end
----
if Platform.asserts then
local ExecutionTimestamps
function OnMsg.DoneMap()
ExecutionTimestamps = false
end
-- Rise an error if a routine from a process is executed twice in the same time
--[[@@@
Checks if a process routine has been executed more than once in the same time frame.
@function void CheckExecutionTimestamp(string process, string funcname, table obj, boolean delayed)
@param string process - the name of the process
@param string funcname - the name of the function being executed
@param table obj - the object context of the function being executed
@param boolean delayed - whether the function call is delayed
@return boolean - true if the function has been executed more than once, false otherwise
--]]
CheckExecutionTimestamp = function(process, funcname, obj, delayed)
if not config.DebugSuspendProcess then
return
end
if not ExecutionTimestamps then
ExecutionTimestamps = {}
CreateRealTimeThread(function()
Sleep(1)
ExecutionTimestamps = false
end)
end
local func_to_objs = ExecutionTimestamps[process]
if not func_to_objs then
func_to_objs = {}
ExecutionTimestamps[process] = func_to_objs
end
local objs_to_timestamp = func_to_objs[funcname]
if not objs_to_timestamp then
objs_to_timestamp = {}
func_to_objs[funcname] = objs_to_timestamp
end
obj = obj or false
local rtime, gtime = RealTime(), GameTime()
local timestamp = xxhash(rtime, gtime)
if timestamp == objs_to_timestamp[obj] then
print("Duplicated processing:", process, funcname, "time:", gtime, "obj:", obj and obj.class,
obj and obj.handle)
assert(false, string.format("Duplicated process routine: %s.%s", process, funcname))
else
objs_to_timestamp[obj] = timestamp
--[[
if IsValid(obj) then
local pos = obj:GetVisualPos()
local seed = xxhash(obj and obj.handle)
local len = 5*guim + BraidRandom(seed, 10*guim)
DbgAddVector(pos, len, RandColor(seed))
DbgAddText(funcname, pos + point(0, 0, len), RandColor(obj and obj.handle))
end
--]]
end
end
---
--- Checks if there are any remaining reasons for suspending a process.
--- If there are any remaining reasons, an assertion is triggered with the process name and reason.
---
--- @function CheckRemainingReason
--- @return nil
CheckRemainingReason = function()
local process = next(SuspendProcessReasons)
local reason = process and next(SuspendProcessReasons[process])
if reason then
assert(false, string.format("Process '%s' not resumed: %s", process, ValueToStr(reason)))
end
end
end -- Platform.asserts |