Files
VMSolidus 1daf9000f0 Fixed Spammy Pointless Signals Runtime Error (#22204)
<img width="1233" height="377" alt="image"
src="https://github.com/user-attachments/assets/6e267bec-cf6a-4c8b-8371-e0347068d7eb"
/>

Literally pointless runtime error, except this time I'm disabling it
without making it harder to port stuff from TG.
2026-04-12 22:19:42 +00:00

196 lines
6.5 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 its 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 = TRUE)
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 = TRUE)
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
var/list/procs = _signal_procs
// If neither side has anything, bail.
if(!lookup && (!procs || !procs[target]))
return
if(!islist(sig_type_or_types))
sig_type_or_types = list(sig_type_or_types)
var/list/target_procs = procs ? procs[target] : null
for(var/sig in sig_type_or_types)
var/has_proc = target_procs && target_procs[sig]
// Always try to clean the emitter side if lookup exists
if(lookup && (sig in lookup))
var/current = lookup[sig]
switch(length(current))
if(2)
// 2-element list: remove src, collapse to single datum
var/list/new_list = current - src
if(new_list.len)
lookup[sig] = new_list.len == 1 ? new_list[1] : new_list
else
// if somehow both gone, just drop the key
lookup -= sig
if(1)
// Single-length list (weird state, but handle it)
if(islist(current) && (src in current))
lookup -= sig
if(0)
// Non-list: either src or somebody else
if(current == src)
lookup -= sig
else
// >2 listeners
current -= src
if(lookup && !lookup.len)
target._listen_lookup = null
// Listener side cleanup, only if we actually had a proc
if(has_proc)
target_procs -= sig
// Final tidy: if target_procs is empty, remove that target from _signal_procs
if(target_procs && !target_procs.len)
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]
// No listeners at all for this signal
if(!target)
return NONE
. = NONE
// SINGLE LISTENER
if(!islist(target))
var/datum/listening_datum = target
if(!listening_datum)
return NONE
var/list/proc_map = listening_datum?._signal_procs?[src]
if(!proc_map)
// Listener has completely forgotten this source;
// clean the emitter side and log.
stack_trace("Signal mismatch (single): [src] has [sigtype] for [listening_datum], but listener has no _signal_procs entry")
if(_listen_lookup[sigtype] == listening_datum)
_listen_lookup -= sigtype
return NONE
var/proc_name = proc_map[sigtype]
if(!proc_name)
// Listener still has a table for this source, but not this signal.
stack_trace("Signal mismatch (single): [src] has [sigtype] for [listening_datum], but listener has no matching proc entry")
if(_listen_lookup[sigtype] == listening_datum)
_listen_lookup -= sigtype
return NONE
// Happy path
return NONE | call(listening_datum, proc_name)(arglist(arguments))
// MULTIPLE LISTENERS
var/list/listeners = target
var/list/queued_calls = list()
for(var/i in 1 to listeners.len)
var/datum/listening_datum = listeners[i]
if(!listening_datum)
continue
var/list/proc_map = listening_datum?._signal_procs?[src]
if(!proc_map)
// Listener forgot this source, but the source still remembers the listener.
stack_trace("Signal mismatch: [src] has [sigtype] in _listen_lookup but [listening_datum] has no _signal_procs entry for it")
// Self-heal: remove stale listener from src's listener list.
listeners.Cut(i, i + 1)
i--
continue
var/proc_name = proc_map[sigtype]
if(!proc_name)
// Listener still has a table for this source, but not this signal.
stack_trace("Signal mismatch: [src] has [sigtype] in _listen_lookup but [listening_datum] has no proc entry for it")
listeners.Cut(i, i + 1)
i--
continue
queued_calls += list(listening_datum, proc_name)
for(var/i in 1 to queued_calls.len step 2)
. |= call(queued_calls[i], queued_calls[i + 1])(arglist(arguments))