File size: 6,700 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
-- 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 <objlist>
---
--- 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