/
datum.dm
429 lines (377 loc) · 13.7 KB
/
datum.dm
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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
/**
* The absolute base class for everything
*
* A datum instantiated has no physical world prescence, use an atom if you want something
* that actually lives in the world
*
* Be very mindful about adding variables to this class, they are inherited by every single
* thing in the entire game, and so you can easily cause memory usage to rise a lot with careless
* use of variables at this level
*/
/datum
/**
* Tick count time when this object was destroyed.
*
* If this is non zero then the object has been garbage collected and is awaiting either
* a hard del by the GC subsystme, or to be autocollected (if it has no references)
*/
var/gc_destroyed
/// Open uis owned by this datum
/// Lazy, since this case is semi rare
var/list/open_uis
/// Active timers with this datum as the target
var/list/_active_timers
/// Status traits attached to this datum. associative list of the form: list(trait name (string) = list(source1, source2, source3,...))
var/list/_status_traits
/**
* Components attached to this datum
*
* Lazy associated list in the structure of `type -> component/list of components`
*/
var/list/_datum_components
/**
* Any datum registered to receive signals from this datum is in this list
*
* Lazy associated list in the structure of `signal -> registree/list of registrees`
*/
var/list/_listen_lookup
/// Lazy associated list in the structure of `target -> list(signal -> proctype)` that are run when the datum receives that signal
var/list/list/_signal_procs
/// Datum level flags
var/datum_flags = NONE
/// A weak reference to another datum
var/datum/weakref/weak_reference
/*
* Lazy associative list of currently active cooldowns.
*
* cooldowns [ COOLDOWN_INDEX ] = add_timer()
* add_timer() returns the truthy value of -1 when not stoppable, and else a truthy numeric index
*/
var/list/cooldowns
/// List for handling persistent filters.
var/list/filter_data
#ifdef REFERENCE_TRACKING
/// When was this datum last touched by a reftracker?
/// If this value doesn't match with the start of the search
/// We know this datum has never been seen before, and we should check it
var/last_find_references = 0
/// How many references we're trying to find when searching
var/references_to_clear = 0
#ifdef REFERENCE_TRACKING_DEBUG
///Stores info about where refs are found, used for sanity checks and testing
var/list/found_refs
#endif
#endif
// If we have called dump_harddel_info already. Used to avoid duped calls (since we call it immediately in some cases on failure to process)
// Create and destroy is weird and I wanna cover my bases
var/harddel_deets_dumped = FALSE
#ifdef DATUMVAR_DEBUGGING_MODE
var/list/cached_vars
#endif
/**
* Called when a href for this datum is clicked
*
* Sends a [COMSIG_TOPIC] signal
*/
/datum/Topic(href, href_list[])
..()
SEND_SIGNAL(src, COMSIG_TOPIC, usr, href_list)
/**
* Default implementation of clean-up code.
*
* This should be overridden to remove all references pointing to the object being destroyed, if
* you do override it, make sure to call the parent and return it's return value by default
*
* Return an appropriate [QDEL_HINT][QDEL_HINT_QUEUE] to modify handling of your deletion;
* in most cases this is [QDEL_HINT_QUEUE].
*
* The base case is responsible for doing the following
* * Erasing timers pointing to this datum
* * Erasing compenents on this datum
* * Notifying datums listening to signals from this datum that we are going away
*
* Returns [QDEL_HINT_QUEUE]
*/
/datum/proc/Destroy(force = FALSE)
SHOULD_CALL_PARENT(TRUE)
SHOULD_NOT_SLEEP(TRUE)
tag = null
datum_flags &= ~DF_USE_TAG //In case something tries to REF us
weak_reference = null //ensure prompt GCing of weakref.
if(_active_timers)
var/list/timers = _active_timers
_active_timers = null
for(var/datum/timedevent/timer as anything in timers)
if (timer.spent && !(timer.flags & TIMER_DELETE_ME))
continue
qdel(timer)
#ifdef REFERENCE_TRACKING
#ifdef REFERENCE_TRACKING_DEBUG
found_refs = null
#endif
#endif
//BEGIN: ECS SHIT
var/list/dc = _datum_components
if(dc)
for(var/component_key in dc)
var/component_or_list = dc[component_key]
if(islist(component_or_list))
for(var/datum/component/component as anything in component_or_list)
qdel(component, FALSE)
else
var/datum/component/C = component_or_list
qdel(C, FALSE)
dc.Cut()
_clear_signal_refs()
//END: ECS SHIT
return QDEL_HINT_QUEUE
///Only override this if you know what you're doing. You do not know what you're doing
///This is a threat
/datum/proc/_clear_signal_refs()
var/list/lookup = _listen_lookup
if(lookup)
for(var/sig in lookup)
var/list/comps = lookup[sig]
if(length(comps))
for(var/datum/component/comp as anything in comps)
comp.UnregisterSignal(src, sig)
else
var/datum/component/comp = comps
comp.UnregisterSignal(src, sig)
_listen_lookup = lookup = null
for(var/target in _signal_procs)
UnregisterSignal(target, _signal_procs[target])
#ifdef DATUMVAR_DEBUGGING_MODE
/datum/proc/save_vars()
cached_vars = list()
for(var/i in vars)
if(i == "cached_vars")
continue
cached_vars[i] = vars[i]
/datum/proc/check_changed_vars()
. = list()
for(var/i in vars)
if(i == "cached_vars")
continue
if(cached_vars[i] != vars[i])
.[i] = list(cached_vars[i], vars[i])
/datum/proc/txt_changed_vars()
var/list/l = check_changed_vars()
var/t = "[src]([REF(src)]) changed vars:"
for(var/i in l)
t += "\"[i]\" \[[l[i][1]]\] --> \[[l[i][2]]\] "
t += "."
/datum/proc/to_chat_check_changed_vars(target = world)
to_chat(target, txt_changed_vars())
#endif
/// Return a list of data which can be used to investigate the datum, also ensure that you set the semver in the options list
/datum/proc/serialize_list(list/options, list/semvers)
SHOULD_CALL_PARENT(TRUE)
. = list()
.["tag"] = tag
SET_SERIALIZATION_SEMVER(semvers, "1.0.0")
return .
///Accepts a LIST from deserialize_datum. Should return whether or not the deserialization was successful.
/datum/proc/deserialize_list(json, list/options)
SHOULD_CALL_PARENT(TRUE)
return TRUE
///Serializes into JSON. Does not encode type.
/datum/proc/serialize_json(list/options)
. = serialize_list(options)
if(!islist(.))
. = null
else
. = json_encode(.)
///Deserializes from JSON. Does not parse type.
/datum/proc/deserialize_json(list/input, list/options)
var/list/jsonlist = json_decode(input)
. = deserialize_list(jsonlist)
if(!istype(., /datum))
. = null
///Convert a datum into a json blob
/proc/json_serialize_datum(datum/D, list/options)
if(!istype(D))
return
var/list/jsonlist = D.serialize_list(options)
if(islist(jsonlist))
jsonlist["DATUM_TYPE"] = D.type
return json_encode(jsonlist)
/// Convert a list of json to datum
/proc/json_deserialize_datum(list/jsonlist, list/options, target_type, strict_target_type = FALSE)
if(!islist(jsonlist))
if(!istext(jsonlist))
CRASH("Invalid JSON")
jsonlist = json_decode(jsonlist)
if(!islist(jsonlist))
CRASH("Invalid JSON")
if(!jsonlist["DATUM_TYPE"])
return
if(!ispath(jsonlist["DATUM_TYPE"]))
if(!istext(jsonlist["DATUM_TYPE"]))
return
jsonlist["DATUM_TYPE"] = text2path(jsonlist["DATUM_TYPE"])
if(!ispath(jsonlist["DATUM_TYPE"]))
return
if(target_type)
if(!ispath(target_type))
return
if(strict_target_type)
if(target_type != jsonlist["DATUM_TYPE"])
return
else if(!ispath(jsonlist["DATUM_TYPE"], target_type))
return
var/typeofdatum = jsonlist["DATUM_TYPE"] //BYOND won't directly read if this is just put in the line below, and will instead runtime because it thinks you're trying to make a new list?
var/datum/D = new typeofdatum
if(!D.deserialize_list(jsonlist, options))
qdel(D)
else
return D
/**
* Callback called by a timer to end an associative-list-indexed cooldown.
*
* Arguments:
* * source - datum storing the cooldown
* * index - string index storing the cooldown on the cooldowns associative list
*
* This sends a signal reporting the cooldown end.
*/
/proc/end_cooldown(datum/source, index)
if(QDELETED(source))
return
SEND_SIGNAL(source, COMSIG_CD_STOP(index))
TIMER_COOLDOWN_END(source, index)
/**
* Proc used by stoppable timers to end a cooldown before the time has ran out.
*
* Arguments:
* * source - datum storing the cooldown
* * index - string index storing the cooldown on the cooldowns associative list
*
* This sends a signal reporting the cooldown end, passing the time left as an argument.
*/
/proc/reset_cooldown(datum/source, index)
if(QDELETED(source))
return
SEND_SIGNAL(source, COMSIG_CD_RESET(index), S_TIMER_COOLDOWN_TIMELEFT(source, index))
TIMER_COOLDOWN_END(source, index)
///Generate a tag for this /datum, if it implements one
///Should be called as early as possible, best would be in New, to avoid weakref mistargets
///Really just don't use this, you don't need it, global lists will do just fine MOST of the time
///We really only use it for mobs to make id'ing people easier
/datum/proc/GenerateTag()
datum_flags |= DF_USE_TAG
/** Add a filter to the datum.
* This is on datum level, despite being most commonly / primarily used on atoms, so that filters can be applied to images / mutable appearances.
* Can also be used to assert a filter's existence. I.E. update a filter regardless if it exists or not.
*
* Arguments:
* * name - Filter name
* * priority - Priority used when sorting the filter.
* * params - Parameters of the filter.
*/
/datum/proc/add_filter(name, priority, list/params)
LAZYINITLIST(filter_data)
var/list/copied_parameters = params.Copy()
copied_parameters["priority"] = priority
filter_data[name] = copied_parameters
update_filters()
///A version of add_filter that takes a list of filters to add rather than being individual, to limit calls to update_filters().
/datum/proc/add_filters(list/list/filters)
LAZYINITLIST(filter_data)
for(var/list/individual_filter as anything in filters)
var/list/params = individual_filter["params"]
var/list/copied_parameters = params.Copy()
copied_parameters["priority"] = individual_filter["priority"]
filter_data[individual_filter["name"]] = copied_parameters
update_filters()
/// Reapplies all the filters.
/datum/proc/update_filters()
ASSERT(isatom(src) || isimage(src))
var/atom/atom_cast = src // filters only work with images or atoms.
atom_cast.filters = null
filter_data = sortTim(filter_data, GLOBAL_PROC_REF(cmp_filter_data_priority), TRUE)
for(var/filter_raw in filter_data)
var/list/data = filter_data[filter_raw]
var/list/arguments = data.Copy()
arguments -= "priority"
atom_cast.filters += filter(arglist(arguments))
UNSETEMPTY(filter_data)
/obj/item/update_filters()
. = ..()
update_item_action_buttons()
/** Update a filter's parameter to the new one. If the filter doesnt exist we won't do anything.
*
* Arguments:
* * name - Filter name
* * new_params - New parameters of the filter
* * overwrite - TRUE means we replace the parameter list completely. FALSE means we only replace the things on new_params.
*/
/datum/proc/modify_filter(name, list/new_params, overwrite = FALSE)
var/filter = get_filter(name)
if(!filter)
return
if(overwrite)
filter_data[name] = new_params
else
for(var/thing in new_params)
filter_data[name][thing] = new_params[thing]
update_filters()
/** Update a filter's parameter and animate this change. If the filter doesnt exist we won't do anything.
* Basically a [datum/proc/modify_filter] call but with animations. Unmodified filter parameters are kept.
*
* Arguments:
* * name - Filter name
* * new_params - New parameters of the filter
* * time - time arg of the BYOND animate() proc.
* * easing - easing arg of the BYOND animate() proc.
* * loop - loop arg of the BYOND animate() proc.
*/
/datum/proc/transition_filter(name, list/new_params, time, easing, loop)
var/filter = get_filter(name)
if(!filter)
return
// This can get injected by the filter procs, we want to support them so bye byeeeee
new_params -= "type"
animate(filter, new_params, time = time, easing = easing, loop = loop)
modify_filter(name, new_params)
/// Updates the priority of the passed filter key
/datum/proc/change_filter_priority(name, new_priority)
if(!filter_data || !filter_data[name])
return
filter_data[name]["priority"] = new_priority
update_filters()
/// Returns the filter associated with the passed key
/datum/proc/get_filter(name)
ASSERT(isatom(src) || isimage(src))
if(filter_data && filter_data[name])
var/atom/atom_cast = src // filters only work with images or atoms.
return atom_cast.filters[filter_data.Find(name)]
/// Returns the indice in filters of the given filter name.
/// If it is not found, returns null.
/datum/proc/get_filter_index(name)
return filter_data?.Find(name)
/// Removes the passed filter, or multiple filters, if supplied with a list.
/datum/proc/remove_filter(name_or_names)
if(!filter_data)
return
var/list/names = islist(name_or_names) ? name_or_names : list(name_or_names)
for(var/name in names)
if(filter_data[name])
filter_data -= name
update_filters()
/datum/proc/clear_filters()
ASSERT(isatom(src) || isimage(src))
var/atom/atom_cast = src // filters only work with images or atoms.
filter_data = null
atom_cast.filters = null
/// Return text from this proc to provide extra context to hard deletes that happen to it
/// Optional, you should use this for cases where replication is difficult and extra context is required
/// Can be called more then once per object, use harddel_deets_dumped to avoid duplicate calls (I am so sorry)
/datum/proc/dump_harddel_info()
return
///images are pretty generic, this should help a bit with tracking harddels related to them
/image/dump_harddel_info()
if(harddel_deets_dumped)
return
harddel_deets_dumped = TRUE
return "Image icon: [icon] - icon_state: [icon_state] [loc ? "loc: [loc] ([loc.x],[loc.y],[loc.z])" : ""]"