mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-13 11:12:14 +00:00
## About The Pull Request Instead of iterating over an assoc list and doing a list access, which has a complexity of O(nlog(n)), it is better to just store a 2 tuple and access that to get a complexity of O(n) Check code to see what I mean. ## Why It's Good For The Game  This is a very hot proc and it's worth micro-optimizing where we can. The speed increase in doing the following code can be seen here:  Higher is better. The code that was benchmarked: ```dm var/list/target = list() /proc/testa() var/list/queued_calls = list() for(var/i in 1 to length(target)) var/data = target[i] queued_calls.Add(data, 1) for(var/i in 1 to (length(queued_calls) / 2)) var/a = queued_calls[i*2-1] var/b = queued_calls[i*2] /proc/testb() var/list/queued_calls = list() for(var/data in target) queued_calls[data] = 1 for(var/data in queued_calls) var/a = data var/b = queued_calls[data] MAIN for(var/i in 1 to 100) target.Add("[i]") BEGIN_BENCH(2) BENCH_PHASE("New code", testa()) BENCH_PHASE("Old code", testb()) END_BENCH ``` ## Changelog --------- Co-authored-by: Watermelon914 <3052169-Watermelon914@users.noreply.gitlab.com> Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
129 lines
4.9 KiB
Plaintext
129 lines
4.9 KiB
Plaintext
/**
|
|
* Register to listen for a signal from the passed in target
|
|
*
|
|
* This sets up a listening relationship such that when the target object emits a signal
|
|
* the source datum this proc is called upon, will receive a callback to the given proctype
|
|
* Use PROC_REF(procname), TYPE_PROC_REF(type,procname) or GLOBAL_PROC_REF(procname) macros to validate the passed in proc at compile time.
|
|
* PROC_REF for procs defined on current type or it's ancestors, TYPE_PROC_REF for procs defined on unrelated type and GLOBAL_PROC_REF for global procs.
|
|
* Return values from procs registered must be a bitfield
|
|
*
|
|
* Arguments:
|
|
* * datum/target The target to listen for signals from
|
|
* * signal_type A signal name
|
|
* * proctype The proc to call back when the signal is emitted
|
|
* * override If a previous registration exists you must explicitly set this
|
|
*/
|
|
/datum/proc/RegisterSignal(datum/target, signal_type, proctype, override = FALSE)
|
|
if(QDELETED(src) || QDELETED(target))
|
|
return
|
|
|
|
if (islist(signal_type))
|
|
var/static/list/known_failures = list()
|
|
var/list/signal_type_list = signal_type
|
|
var/message = "([target.type]) is registering [signal_type_list.Join(", ")] as a list, the older method. Change it to RegisterSignals."
|
|
|
|
if (!(message in known_failures))
|
|
known_failures[message] = TRUE
|
|
stack_trace("[target] [message]")
|
|
|
|
RegisterSignals(target, signal_type, proctype, override)
|
|
return
|
|
|
|
var/list/procs = (_signal_procs ||= list())
|
|
var/list/target_procs = (procs[target] ||= list())
|
|
var/list/lookup = (target._listen_lookup ||= list())
|
|
|
|
var/exists = target_procs[signal_type]
|
|
target_procs[signal_type] = proctype
|
|
|
|
if(exists)
|
|
if(!override)
|
|
var/override_message = "[signal_type] overridden. Use override = TRUE to suppress this warning.\nTarget: [target] ([target.type]) Existing Proc: [exists] New Proc: [proctype]"
|
|
log_signal(override_message)
|
|
stack_trace(override_message)
|
|
return
|
|
|
|
var/list/looked_up = lookup[signal_type]
|
|
|
|
if(isnull(looked_up)) // Nothing has registered here yet
|
|
lookup[signal_type] = src
|
|
else if(!islist(looked_up)) // One other thing registered here
|
|
lookup[signal_type] = list(looked_up, src)
|
|
else // Many other things have registered here
|
|
looked_up += src
|
|
|
|
/// Registers multiple signals to the same proc.
|
|
/datum/proc/RegisterSignals(datum/target, list/signal_types, proctype, override = FALSE)
|
|
for (var/signal_type in signal_types)
|
|
RegisterSignal(target, signal_type, proctype, override)
|
|
|
|
/**
|
|
* Stop listening to a given signal from target
|
|
*
|
|
* Breaks the relationship between target and source datum, removing the callback when the signal fires
|
|
*
|
|
* Doesn't care if a registration exists or not
|
|
*
|
|
* Arguments:
|
|
* * datum/target Datum to stop listening to signals from
|
|
* * sig_typeor_types Signal string key or list of signal keys to stop listening to specifically
|
|
*/
|
|
/datum/proc/UnregisterSignal(datum/target, sig_type_or_types)
|
|
var/list/lookup = target._listen_lookup
|
|
if(!_signal_procs || !_signal_procs[target] || !lookup)
|
|
return
|
|
if(!islist(sig_type_or_types))
|
|
sig_type_or_types = list(sig_type_or_types)
|
|
for(var/sig in sig_type_or_types)
|
|
if(!_signal_procs[target][sig])
|
|
if(!istext(sig))
|
|
stack_trace("We're unregistering with something that isn't a valid signal \[[sig]\], you fucked up")
|
|
continue
|
|
switch(length(lookup[sig]))
|
|
if(2)
|
|
lookup[sig] = (lookup[sig]-src)[1]
|
|
if(1)
|
|
stack_trace("[target] ([target.type]) somehow has single length list inside _listen_lookup")
|
|
if(src in lookup[sig])
|
|
lookup -= sig
|
|
if(!length(lookup))
|
|
target._listen_lookup = null
|
|
break
|
|
if(0)
|
|
if(lookup[sig] != src)
|
|
continue
|
|
lookup -= sig
|
|
if(!length(lookup))
|
|
target._listen_lookup = null
|
|
break
|
|
else
|
|
lookup[sig] -= src
|
|
|
|
_signal_procs[target] -= sig_type_or_types
|
|
if(!_signal_procs[target].len)
|
|
_signal_procs -= target
|
|
|
|
/**
|
|
* Internal proc to handle most all of the signaling procedure
|
|
*
|
|
* Will runtime if used on datums with an empty lookup list
|
|
*
|
|
* Use the [SEND_SIGNAL] define instead
|
|
*/
|
|
/datum/proc/_SendSignal(sigtype, list/arguments)
|
|
var/target = _listen_lookup[sigtype]
|
|
if(!length(target))
|
|
var/datum/listening_datum = target
|
|
return NONE | call(listening_datum, listening_datum._signal_procs[src][sigtype])(arglist(arguments))
|
|
. = NONE
|
|
// This exists so that even if one of the signal receivers unregisters the signal,
|
|
// all the objects that are receiving the signal get the signal this final time.
|
|
// AKA: No you can't cancel the signal reception of another object by doing an unregister in the same signal.
|
|
var/list/queued_calls = list()
|
|
// This should be faster than doing `var/datum/listening_datum as anything in target` as it does not implicitly copy the list
|
|
for(var/i in 1 to length(target))
|
|
var/datum/listening_datum = target[i]
|
|
queued_calls.Add(listening_datum, listening_datum._signal_procs[src][sigtype])
|
|
for(var/i in 1 to length(queued_calls) step 2)
|
|
. |= call(queued_calls[i], queued_calls[i + 1])(arglist(arguments))
|