-- Notifications provide easy way to make lazy updates after certain changes are complete. -- Multiple notifications within the same millisecond are treated as one. --- Maintains a mapping of delayed call objects and their corresponding threads. --- `DelayedCallObjects` is a table that stores the objects to be called when a delayed call is triggered. --- `DelayedCallThreads` is a table that stores the game time threads that will execute the delayed calls. --- `NotifyLastTimeCalled` is a variable that stores the last time a notification was triggered. MapVar("DelayedCallObjects", {}) MapVar("DelayedCallThreads", {}) MapVar("NotifyLastTimeCalled", 0) --- --- Executes the delayed notifications for the specified method. --- This function is called by the game time thread created in the `Notify` function. --- It iterates through the list of objects associated with the method and calls the corresponding method or function on each valid object. --- After all notifications have been executed, the `DelayedCallObjects` and `DelayedCallThreads` tables are cleared for the method. --- --- @param method string|function The method or function to be called for the delayed notifications. --- @return function The function that will be executed by the game time thread. --- local function DoNotify(method) return function() Sleep(0) Sleep(0) local objs = DelayedCallObjects[method] if objs then local i = 1 if type(method) == "function" then while true do local obj = objs[i] -- This check is purposedly not re-phrased to "if not obj", -- because obj may well be "false" in a valid situation(when a notification has been cancelled). if obj == nil then break end if IsValid(obj) then procall(method, obj) end i = i + 1 end else while true do local obj = objs[i] -- This check is purposedly not re-phrased to "if not obj". Please, don't change. if obj == nil then break end if IsValid(obj) then procall(obj[method], obj) end i = i + 1 end end else assert(false, "Missing notify list for method " .. tostring(method)) end DelayedCallObjects[method] = nil DelayedCallThreads[method] = nil end end --- --- Recreates the `DelayedCallThreads` table by iterating through the `DelayedCallObjects` table and creating a new game time thread for each method that has associated objects. --- This function is called when the `NotifyLastTimeCalled` variable is updated, indicating that a new frame has started. --- --- @internal function RecreateNotifyStructures() assert(not next(DelayedCallThreads)) for k, v in pairs(DelayedCallThreads) do DelayedCallThreads[k] = DelayedCallObjects[k] and CreateGameTimeThread(DoNotify(k)) or nil end end -- calls the func or the obj method when the current thread completes (but within the same millisecond) -- multiple calls with the same obj/method pair result in one call only --- --- Notifies the specified object using the given method. --- --- If the `NotifyLastTimeCalled` variable indicates that a new frame has started, the `RecreateNotifyStructures()` function is called to recreate the `DelayedCallThreads` table. --- --- If a thread for the given method does not exist, a new game time thread is created using the `DoNotify(method)` function and stored in the `DelayedCallThreads` table. The object is also added to the `DelayedCallObjects` table. --- --- If a thread for the given method already exists, the object is added to the `DelayedCallObjects` table. --- --- @param obj table The object to notify --- @param method string|function The method to call on the object, or a function to call with the object as the argument --- function Notify(obj, method) if not obj then return end local now = GameTime() if NotifyLastTimeCalled ~= now then RecreateNotifyStructures() NotifyLastTimeCalled = now end local thread = DelayedCallThreads[method] if not thread then thread = CreateGameTimeThread(DoNotify(method)) DelayedCallThreads[method] = thread DelayedCallObjects[method] = {obj, [obj]=true} else local objs = DelayedCallObjects[method] if not objs[obj] then objs[#objs + 1] = obj objs[obj] = true end end end --- notifies all objects in --- --- Notifies all objects in the given list using the specified method. --- --- If the `NotifyLastTimeCalled` variable indicates that a new frame has started, the `RecreateNotifyStructures()` function is called to recreate the `DelayedCallThreads` table. --- --- For each object in the list, if a thread for the given method does not exist, a new game time thread is created using the `DoNotify(method)` function and stored in the `DelayedCallThreads` table. The object is also added to the `DelayedCallObjects` table. --- --- If a thread for the given method already exists, the object is added to the `DelayedCallObjects` table. --- --- @param objects_to_call table The list of objects to notify --- @param method string|function The method to call on the objects, or a function to call with each object as the argument --- function ListNotify(objects_to_call, method) if #objects_to_call < 1 then return end Notify(objects_to_call[1], method) local objs = DelayedCallObjects[method] assert(objs) for i = 2, #objects_to_call do local obj = objects_to_call[i] if not objs[obj] then objs[#objs + 1] = obj objs[obj] = true end end end --- Cancels a notification for the given object and method. --- --- If the object is registered to receive notifications for the given method, this function removes the object from the list of objects to notify. --- --- @param obj table The object to cancel the notification for --- @param method string|function The method to cancel the notification for function CancelNotify(obj, method) local objs = DelayedCallObjects[method] if objs[obj] then objs[obj] = nil for i = 1, #objs do if objs[i] == obj then objs[i] = false return end end end end