|
1 | | -/// Controls how many buckets should be kept, each representing a tick. (30 seconds worth) |
2 | | -#define BUCKET_LEN (world.fps * 1 * 30) |
3 | | -/// Helper for getting the correct bucket for a given chatmessage |
4 | | -#define BUCKET_POS(scheduled_destruction) (((round((scheduled_destruction - SSrunechat.head_offset) / world.tick_lag) + 1) % BUCKET_LEN) || BUCKET_LEN) |
5 | | -/// Gets the maximum time at which messages will be handled in buckets, used for deferring to secondary queue |
6 | | -#define BUCKET_LIMIT (world.time + TICKS2DS(min(BUCKET_LEN - (SSrunechat.practical_offset - DS2TICKS(world.time - SSrunechat.head_offset)) - 1, BUCKET_LEN - 1))) |
7 | | - |
8 | | -/** |
9 | | - * # Runechat Subsystem |
10 | | - * |
11 | | - * Maintains a timer-like system to handle destruction of runechat messages. Much of this code is modeled |
12 | | - * after or adapted from the timer subsystem. |
13 | | - * |
14 | | - * Note that this has the same structure for storing and queueing messages as the timer subsystem does |
15 | | - * for handling timers: the bucket_list is a list of chatmessage datums, each of which are the head |
16 | | - * of a circularly linked list. Any given index in bucket_list could be null, representing an empty bucket. |
17 | | - */ |
18 | | -SUBSYSTEM_DEF(runechat) |
| 1 | +TIMER_SUBSYSTEM_DEF(runechat) |
19 | 2 | name = "Runechat" |
20 | | - flags = SS_TICKER | SS_NO_INIT |
21 | | - wait = 1 |
22 | 3 | priority = FIRE_PRIORITY_RUNECHAT |
23 | 4 |
|
24 | | - /// world.time of the first entry in the bucket list, effectively the 'start time' of the current buckets |
25 | | - var/head_offset = 0 |
26 | | - /// Index of the first non-empty bucket |
27 | | - var/practical_offset = 1 |
28 | | - /// world.tick_lag the bucket was designed for |
29 | | - var/bucket_resolution = 0 |
30 | | - /// How many messages are in the buckets |
31 | | - var/bucket_count = 0 |
32 | | - /// List of buckets, each bucket holds every message that has to be killed that byond tick |
33 | | - var/list/bucket_list = list() |
34 | | - /// Queue used for storing messages that are scheduled for deletion too far in the future for the buckets |
35 | | - var/list/datum/chatmessage/second_queue = list() |
36 | | - |
37 | | -/datum/controller/subsystem/runechat/PreInit() |
38 | | - bucket_list.len = BUCKET_LEN |
39 | | - head_offset = world.time |
40 | | - bucket_resolution = world.tick_lag |
41 | | - |
42 | | -/datum/controller/subsystem/runechat/stat_entry(msg) |
43 | | - msg += "ActMsgs:[bucket_count] SecQueue:[length(second_queue)]" |
44 | | - return msg |
45 | | - |
46 | | -/datum/controller/subsystem/runechat/get_metrics() |
47 | | - . = ..() |
48 | | - .["buckets"] = bucket_count |
49 | | - .["second_queue"] = length(second_queue) |
50 | | - |
51 | | -/datum/controller/subsystem/runechat/fire(resumed = FALSE) |
52 | | - // Store local references to datum vars as it is faster to access them this way |
53 | | - var/list/bucket_list = src.bucket_list |
54 | | - |
55 | | - if (MC_TICK_CHECK) |
56 | | - return |
57 | | - |
58 | | - // Check for when we need to loop the buckets, this occurs when |
59 | | - // the head_offset is approaching BUCKET_LEN ticks in the past |
60 | | - if (practical_offset > BUCKET_LEN) |
61 | | - head_offset += TICKS2DS(BUCKET_LEN) |
62 | | - practical_offset = 1 |
63 | | - resumed = FALSE |
64 | | - |
65 | | - // Check for when we have to reset buckets, typically from auto-reset |
66 | | - if ((length(bucket_list) != BUCKET_LEN) || (world.tick_lag != bucket_resolution)) |
67 | | - reset_buckets() |
68 | | - bucket_list = src.bucket_list |
69 | | - resumed = FALSE |
70 | | - |
71 | | - // Store a reference to the 'working' chatmessage so that we can resume if the MC |
72 | | - // has us stop mid-way through processing |
73 | | - var/static/datum/chatmessage/cm |
74 | | - if (!resumed) |
75 | | - cm = null |
76 | | - |
77 | | - // Iterate through each bucket starting from the practical offset |
78 | | - while (practical_offset <= BUCKET_LEN && head_offset + ((practical_offset - 1) * world.tick_lag) <= world.time) |
79 | | - var/datum/chatmessage/bucket_head = bucket_list[practical_offset] |
80 | | - if (!cm || !bucket_head || cm == bucket_head) |
81 | | - bucket_head = bucket_list[practical_offset] |
82 | | - cm = bucket_head |
83 | | - |
84 | | - while (cm) |
85 | | - // If the chatmessage hasn't yet had its life ended then do that now |
86 | | - var/datum/chatmessage/next = cm.next |
87 | | - if (!cm.eol_complete) |
88 | | - cm.end_of_life() |
89 | | - else if (!QDELETED(cm)) // otherwise if we haven't deleted it yet, do so (this is after EOL completion) |
90 | | - qdel(cm) |
91 | | - |
92 | | - if (MC_TICK_CHECK) |
93 | | - return |
94 | | - |
95 | | - // Break once we've processed the entire bucket |
96 | | - cm = next |
97 | | - if (cm == bucket_head) |
98 | | - break |
99 | | - |
100 | | - // Empty the bucket, check if anything in the secondary queue should be shifted to this bucket |
101 | | - bucket_list[practical_offset++] = null |
102 | | - var/i = 0 |
103 | | - for (i in 1 to length(second_queue)) |
104 | | - cm = second_queue[i] |
105 | | - if (cm.scheduled_destruction >= BUCKET_LIMIT) |
106 | | - i-- |
107 | | - break |
108 | | - |
109 | | - // Transfer the message into the bucket, performing necessary circular doubly-linked list operations |
110 | | - bucket_count++ |
111 | | - var/bucket_pos = max(1, BUCKET_POS(cm.scheduled_destruction)) |
112 | | - var/datum/timedevent/head = bucket_list[bucket_pos] |
113 | | - if (!head) |
114 | | - bucket_list[bucket_pos] = cm |
115 | | - cm.next = null |
116 | | - cm.prev = null |
117 | | - continue |
118 | | - |
119 | | - if (!head.prev) |
120 | | - head.prev = head |
121 | | - cm.next = head |
122 | | - cm.prev = head.prev |
123 | | - cm.next.prev = cm |
124 | | - cm.prev.next = cm |
125 | | - if (i) |
126 | | - second_queue.Cut(1, i + 1) |
127 | | - cm = null |
128 | | - |
129 | | -/datum/controller/subsystem/runechat/Recover() |
130 | | - bucket_list |= SSrunechat.bucket_list |
131 | | - second_queue |= SSrunechat.second_queue |
132 | | - |
133 | | -/datum/controller/subsystem/runechat/proc/reset_buckets() |
134 | | - bucket_list.len = BUCKET_LEN |
135 | | - head_offset = world.time |
136 | | - bucket_resolution = world.tick_lag |
137 | | - |
138 | | -/** |
139 | | - * Enters the runechat subsystem with this chatmessage, inserting it into the end-of-life queue |
140 | | - * |
141 | | - * This will also account for a chatmessage already being registered, and in which case |
142 | | - * the position will be updated to remove it from the previous location if necessary |
143 | | - * |
144 | | - * Arguments: |
145 | | - * * new_sched_destruction Optional, when provided is used to update an existing message with the new specified time |
146 | | - */ |
147 | | -/datum/chatmessage/proc/enter_subsystem(new_sched_destruction = 0) |
148 | | - // Get local references from subsystem as they are faster to access than the datum references |
149 | | - var/list/bucket_list = SSrunechat.bucket_list |
150 | | - var/list/second_queue = SSrunechat.second_queue |
151 | | - |
152 | | - // When necessary, de-list the chatmessage from its previous position |
153 | | - if (new_sched_destruction) |
154 | | - if (scheduled_destruction >= BUCKET_LIMIT) |
155 | | - second_queue -= src |
156 | | - else |
157 | | - SSrunechat.bucket_count-- |
158 | | - var/bucket_pos = BUCKET_POS(scheduled_destruction) |
159 | | - if (bucket_pos > 0) |
160 | | - var/datum/chatmessage/bucket_head = bucket_list[bucket_pos] |
161 | | - if (bucket_head == src) |
162 | | - bucket_list[bucket_pos] = next |
163 | | - if (prev != next) |
164 | | - prev.next = next |
165 | | - next.prev = prev |
166 | | - else |
167 | | - prev?.next = null |
168 | | - next?.prev = null |
169 | | - prev = next = null |
170 | | - scheduled_destruction = new_sched_destruction |
171 | | - |
172 | | - // Ensure the scheduled destruction time is properly bound to avoid missing a scheduled event |
173 | | - scheduled_destruction = max(CEILING(scheduled_destruction, world.tick_lag), world.time + world.tick_lag) |
174 | | - |
175 | | - // Handle insertion into the secondary queue if the required time is outside our tracked amounts |
176 | | - if (scheduled_destruction >= BUCKET_LIMIT) |
177 | | - BINARY_INSERT(src, SSrunechat.second_queue, /datum/chatmessage, src, scheduled_destruction, COMPARE_KEY) |
178 | | - return |
179 | | - |
180 | | - // Get bucket position and a local reference to the datum var, it's faster to access this way |
181 | | - var/bucket_pos = BUCKET_POS(scheduled_destruction) |
182 | | - |
183 | | - // Get the bucket head for that bucket, increment the bucket count |
184 | | - var/datum/chatmessage/bucket_head = bucket_list[bucket_pos] |
185 | | - SSrunechat.bucket_count++ |
186 | | - |
187 | | - // If there is no existing head of this bucket, we can set this message to be that head |
188 | | - if (!bucket_head) |
189 | | - bucket_list[bucket_pos] = src |
190 | | - return |
191 | | - |
192 | | - // Otherwise it's a simple insertion into the circularly doubly-linked list |
193 | | - if (!bucket_head.prev) |
194 | | - bucket_head.prev = bucket_head |
195 | | - next = bucket_head |
196 | | - prev = bucket_head.prev |
197 | | - next.prev = src |
198 | | - prev.next = src |
199 | | - |
200 | | - |
201 | | -/** |
202 | | - * Removes this chatmessage datum from the runechat subsystem |
203 | | - */ |
204 | | -/datum/chatmessage/proc/leave_subsystem() |
205 | | - // Attempt to find the bucket that contains this chat message |
206 | | - var/bucket_pos = BUCKET_POS(scheduled_destruction) |
207 | | - |
208 | | - // Get local references to the subsystem's vars, faster than accessing on the datum |
209 | | - var/list/bucket_list = SSrunechat.bucket_list |
210 | | - var/list/second_queue = SSrunechat.second_queue |
211 | | - |
212 | | - // Attempt to get the head of the bucket |
213 | | - var/datum/chatmessage/bucket_head |
214 | | - if (bucket_pos > 0) |
215 | | - bucket_head = bucket_list[bucket_pos] |
216 | | - |
217 | | - // Decrement the number of messages in buckets if the message is |
218 | | - // the head of the bucket, or has a SD less than BUCKET_LIMIT implying it fits |
219 | | - // into an existing bucket, or is otherwise not present in the secondary queue |
220 | | - if(bucket_head == src) |
221 | | - bucket_list[bucket_pos] = next |
222 | | - SSrunechat.bucket_count-- |
223 | | - else if(scheduled_destruction < BUCKET_LIMIT) |
224 | | - SSrunechat.bucket_count-- |
225 | | - else |
226 | | - var/l = length(second_queue) |
227 | | - second_queue -= src |
228 | | - if(l == length(second_queue)) |
229 | | - SSrunechat.bucket_count-- |
230 | | - |
231 | | - // Remove the message from the bucket, ensuring to maintain |
232 | | - // the integrity of the bucket's list if relevant |
233 | | - if(prev != next) |
234 | | - prev.next = next |
235 | | - next.prev = prev |
236 | | - else |
237 | | - prev?.next = null |
238 | | - next?.prev = null |
239 | | - prev = next = null |
| 5 | + var/list/datum/callback/message_queue = list() |
240 | 6 |
|
241 | | -#undef BUCKET_LEN |
242 | | -#undef BUCKET_POS |
243 | | -#undef BUCKET_LIMIT |
| 7 | +/datum/controller/subsystem/timer/runechat/fire(resumed) |
| 8 | + . = ..() //poggers |
| 9 | + while(message_queue.len) |
| 10 | + var/datum/callback/queued_message = message_queue[message_queue.len] |
| 11 | + queued_message.Invoke() |
| 12 | + message_queue.len-- |
| 13 | + if(MC_TICK_CHECK) |
| 14 | + return |
0 commit comments