Modernizes components (#17260)

* Part 1

* Refactor signals into different files

* Remove redundant file

* Add missing movable signals

* Add signals log

* Split signal registering with list into new proc

* Add comments to component.dm and remove signal_enabled

* Fix yogs code

* Not this one

* Hopefully make linter happy

* Remove duplicate file

* More duplicates signals
This commit is contained in:
Ling
2023-01-04 23:29:34 +01:00
committed by GitHub
parent bc61c15b41
commit c7226a179b
115 changed files with 2612 additions and 699 deletions

View File

@@ -1,3 +1,14 @@
/**
* # Component
*
* The component datum
*
* A component should be a single standalone unit
* of functionality, that works by receiving signals from it's parent
* object to provide some single functionality (i.e a slippery component)
* that makes the object it's attached to cause people to slip over.
* Useful when you want shared behaviour independent of type inheritance
*/
/datum/component
var/dupe_mode = COMPONENT_DUPE_HIGHLANDER
var/dupe_type
@@ -7,15 +18,51 @@
//Make sure you also implement PostTransfer for any post transfer handling
var/can_transfer = FALSE
/datum/component/New(datum/P, ...)
parent = P
var/list/arguments = args.Copy(2)
/**
* Create a new component.
*
* Additional arguments are passed to [Initialize()][/datum/component/proc/Initialize]
*
* Arguments:
* * datum/P the parent datum this component reacts to signals from
*/
/datum/component/New(list/raw_args)
parent = raw_args[1]
var/list/arguments = raw_args.Copy(2)
if(Initialize(arglist(arguments)) == COMPONENT_INCOMPATIBLE)
qdel(src, TRUE, TRUE)
CRASH("Incompatible [type] assigned to a [P.type]! args: [json_encode(arguments)]")
CRASH("Incompatible [type] assigned to a [parent.type]! args: [json_encode(arguments)]")
_JoinParent(P)
_JoinParent(parent)
/**
* Called during component creation with the same arguments as in new excluding parent.
*
* Do not call `qdel(src)` from this function, `return COMPONENT_INCOMPATIBLE` instead
*/
/datum/component/proc/Initialize(...)
return
/**
* Properly removes the component from `parent` and cleans up references
*
* Arguments:
* * force - makes it not check for and remove the component from the parent
* * silent - deletes the component without sending a [COMSIG_COMPONENT_REMOVING] signal
*/
/datum/component/Destroy(force=FALSE, silent=FALSE)
if(!parent)
return ..()
if (!force)
_RemoveFromParent()
if(!silent)
SEND_SIGNAL(parent, COMSIG_COMPONENT_REMOVING, src)
parent = null
return ..()
/**
* Internal proc to handle behaviour of components when joining a parent
*/
/datum/component/proc/_JoinParent()
var/datum/P = parent
//lazy init the parent's dc list
@@ -51,21 +98,19 @@
RegisterWithParent()
// If you want/expect to be moving the component around between parents, use this to register on the parent for signals
/**
* Register the component with the parent object
*
* Use this proc to register with your parent object
*
* Overridable proc that's called when added to a new parent
*/
/datum/component/proc/RegisterWithParent()
return
/datum/component/proc/Initialize(...)
return
/datum/component/Destroy(force=FALSE, silent=FALSE)
if(!force && parent)
_RemoveFromParent()
if(!silent && parent)
SEND_SIGNAL(parent, COMSIG_COMPONENT_REMOVING, src)
parent = null
return ..()
/**
* Internal proc to handle behaviour when being removed from a parent
*/
/datum/component/proc/_RemoveFromParent()
var/datum/P = parent
var/list/dc = P.datum_components
@@ -84,39 +129,83 @@
UnregisterFromParent()
/**
* Unregister from our parent object
*
* Use this proc to unregister from your parent object
*
* Overridable proc that's called when removed from a parent
* *
*/
/datum/component/proc/UnregisterFromParent()
return
/datum/proc/RegisterSignal(datum/target, sig_type_or_types, proctype, override = FALSE)
/**
* 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
var/list/procs = signal_procs
if(!procs)
signal_procs = procs = list()
var/list/target_procs = procs[target] || (procs[target] = list())
var/list/lookup = target.comp_lookup
if(!lookup)
target.comp_lookup = lookup = list()
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."
for(var/sig_type in (islist(sig_type_or_types) ? sig_type_or_types : list(sig_type_or_types)))
if(!override && target_procs[sig_type])
stack_trace("[sig_type] overridden. Use override = TRUE to suppress this warning")
if (!(message in known_failures))
known_failures[message] = TRUE
stack_trace("[target] [message]")
RegisterSignals(target, signal_type, proctype, override)
return
target_procs[sig_type] = proctype
var/list/looked_up = lookup[sig_type]
var/list/procs = (signal_procs ||= list())
var/list/target_procs = (procs[target] ||= list())
var/list/lookup = (target.comp_lookup ||= list())
if(!looked_up) // Nothing has registered here yet
lookup[sig_type] = src
else if(looked_up == src) // We already registered here
continue
else if(!length(looked_up)) // One other thing registered here
lookup[sig_type] = list((looked_up) = TRUE, (src) = TRUE)
else // Many other things have registered here
looked_up[src] = TRUE
if(!override && target_procs[signal_type])
log_signal("[signal_type] overridden. Use override = TRUE to suppress this warning.\nTarget: [target] ([target.type]) Proc: [proctype]")
signal_enabled = TRUE
target_procs[signal_type] = proctype
var/list/looked_up = lookup[signal_type]
if(isnull(looked_up)) // Nothing has registered here yet
lookup[signal_type] = src
else if(looked_up == src) // We already registered here
return
else if(!length(looked_up)) // One other thing registered here
lookup[signal_type] = list((looked_up) = TRUE, (src) = TRUE)
else // Many other things have registered here
looked_up[src] = TRUE
/// 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)
/**
* 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)
if(!target)
return
@@ -148,15 +237,49 @@
if(!signal_procs[target].len)
signal_procs -= target
/**
* Called on a component when a component of the same type was added to the same parent
*
* See [/datum/component/var/dupe_mode]
*
* `C`'s type will always be the same of the called component
*/
/datum/component/proc/InheritComponent(datum/component/C, i_am_original)
return
/**
* Called on a component when a component of the same type was added to the same parent with [COMPONENT_DUPE_SELECTIVE]
*
* See [/datum/component/var/dupe_mode]
*
* `C`'s type will always be the same of the called component
*
* return TRUE if you are absorbing the component, otherwise FALSE if you are fine having it exist as a duplicate component
*/
/datum/component/proc/CheckDupeComponent(datum/component/C, ...)
return
/**
* Callback Just before this component is transferred
*
* Use this to do any special cleanup you might need to do before being deregged from an object
*/
/datum/component/proc/PreTransfer()
return
/**
* Callback Just after a component is transferred
*
* Use this to do any special setup you need to do after being moved to a new object
*
* Do not call `qdel(src)` from this function, `return COMPONENT_INCOMPATIBLE` instead
*/
/datum/component/proc/PostTransfer()
return COMPONENT_INCOMPATIBLE //Do not support transfer by default as you must properly support it
/**
* Internal proc to create a list of our type and all parent types
*/
/datum/component/proc/_GetInverseTypeList(our_type = type)
//we can do this one simple trick
var/current_type = parent_type
@@ -166,26 +289,40 @@
current_type = type2parent(current_type)
. += current_type
/**
* Internal proc to handle most all of the signaling procedure
*
* Will runtime if used on datums with an empty component list
*
* Use the [SEND_SIGNAL] define instead
*/
/datum/proc/_SendSignal(sigtype, list/arguments)
var/target = comp_lookup[sigtype]
if(!length(target))
var/datum/C = target
if(!C.signal_enabled)
return NONE
var/proctype = C.signal_procs[src][sigtype]
return NONE | CallAsync(C, proctype, arguments)
var/datum/listening_datum = target
return NONE | call(listening_datum, listening_datum.signal_procs[src][sigtype])(arglist(arguments))
. = NONE
for(var/I in target)
var/datum/C = I
if(!C.signal_enabled)
continue
var/proctype = C.signal_procs[src][sigtype]
. |= CallAsync(C, proctype, arguments)
// 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()
for(var/datum/listening_datum as anything in target)
queued_calls[listening_datum] = listening_datum.signal_procs[src][sigtype]
for(var/datum/listening_datum as anything in queued_calls)
. |= call(listening_datum, queued_calls[listening_datum])(arglist(arguments))
// The type arg is casted so initial works, you shouldn't be passing a real instance into this
/**
* Return any component assigned to this datum of the given type
*
* This will throw an error if it's possible to have more than one component of that type on the parent
*
* Arguments:
* * datum/component/c_type The typepath of the component you want to get a reference to
*/
/datum/proc/GetComponent(datum/component/c_type)
RETURN_TYPE(c_type)
if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED)
if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED || initial(c_type.dupe_mode) == COMPONENT_DUPE_SELECTIVE)
stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]")
var/list/dc = datum_components
if(!dc)
@@ -194,7 +331,19 @@
if(length(.))
return .[1]
/datum/proc/GetExactComponent(c_type)
// The type arg is casted so initial works, you shouldn't be passing a real instance into this
/**
* Return any component assigned to this datum of the exact given type
*
* This will throw an error if it's possible to have more than one component of that type on the parent
*
* Arguments:
* * datum/component/c_type The typepath of the component you want to get a reference to
*/
/datum/proc/GetExactComponent(datum/component/c_type)
RETURN_TYPE(c_type)
if(initial(c_type.dupe_mode) == COMPONENT_DUPE_ALLOWED || initial(c_type.dupe_mode) == COMPONENT_DUPE_SELECTIVE)
stack_trace("GetComponent was called to get a component of which multiple copies could be on an object. This can easily break and should be changed. Type: \[[c_type]\]")
var/list/dc = datum_components
if(!dc)
return null
@@ -206,6 +355,12 @@
return C
return null
/**
* Get all components of a given type that are attached to this datum
*
* Arguments:
* * c_type The component type path
*/
/datum/proc/GetComponents(c_type)
var/list/dc = datum_components
if(!dc)
@@ -214,7 +369,19 @@
if(!length(.))
return list(.)
/datum/proc/AddComponent(new_type, ...)
/**
* Creates an instance of `new_type` in the datum and attaches to it as parent
*
* Sends the [COMSIG_COMPONENT_ADDED] signal to the datum
*
* Returns the component that was created. Or the old component in a dupe situation where [COMPONENT_DUPE_UNIQUE] was set
*
* If this tries to add a component to an incompatible type, the component will be deleted and the result will be `null`. This is very unperformant, try not to do it
*
* Properly handles duplicate situations based on the `dupe_mode` var
*/
/datum/proc/_AddComponent(list/raw_args)
var/new_type = raw_args[1]
var/datum/component/nt = new_type
var/dm = initial(nt.dupe_mode)
var/dt = initial(nt.dupe_type)
@@ -229,7 +396,7 @@
new_comp = nt
nt = new_comp.type
args[1] = src
raw_args[1] = src
if(dm != COMPONENT_DUPE_ALLOWED)
if(!dt)
@@ -240,37 +407,62 @@
switch(dm)
if(COMPONENT_DUPE_UNIQUE)
if(!new_comp)
new_comp = new nt(arglist(args))
new_comp = new nt(raw_args)
if(!QDELETED(new_comp))
old_comp.InheritComponent(new_comp, TRUE)
QDEL_NULL(new_comp)
if(COMPONENT_DUPE_HIGHLANDER)
if(!new_comp)
new_comp = new nt(arglist(args))
new_comp = new nt(raw_args)
if(!QDELETED(new_comp))
new_comp.InheritComponent(old_comp, FALSE)
QDEL_NULL(old_comp)
if(COMPONENT_DUPE_UNIQUE_PASSARGS)
if(!new_comp)
var/list/arguments = args.Copy(2)
old_comp.InheritComponent(null, TRUE, arguments)
var/list/arguments = raw_args.Copy(2)
arguments.Insert(1, null, TRUE)
old_comp.InheritComponent(arglist(arguments))
else
old_comp.InheritComponent(new_comp, TRUE)
if(COMPONENT_DUPE_SELECTIVE)
var/list/arguments = raw_args.Copy()
arguments[1] = new_comp
var/make_new_component = TRUE
for(var/datum/component/existing_component as anything in GetComponents(new_type))
if(existing_component.CheckDupeComponent(arglist(arguments)))
make_new_component = FALSE
QDEL_NULL(new_comp)
break
if(!new_comp && make_new_component)
new_comp = new nt(raw_args)
else if(!new_comp)
new_comp = new nt(arglist(args)) // There's a valid dupe mode but there's no old component, act like normal
new_comp = new nt(raw_args) // There's a valid dupe mode but there's no old component, act like normal
else if(!new_comp)
new_comp = new nt(arglist(args)) // Dupes are allowed, act like normal
new_comp = new nt(raw_args) // Dupes are allowed, act like normal
if(!old_comp && !QDELETED(new_comp)) // Nothing related to duplicate components happened and the new component is healthy
SEND_SIGNAL(src, COMSIG_COMPONENT_ADDED, new_comp)
return new_comp
return old_comp
/datum/proc/LoadComponent(component_type, ...)
. = GetComponent(component_type)
/**
* Get existing component of type, or create it and return a reference to it
*
* Use this if the item needs to exist at the time of this call, but may not have been created before now
*
* Arguments:
* * component_type The typepath of the component to create or return
* * ... additional arguments to be passed when creating the component if it does not exist
*/
/datum/proc/_LoadComponent(list/arguments)
. = GetComponent(arguments[1])
if(!.)
return AddComponent(arglist(args))
return _AddComponent(arguments)
/**
* Removes the component from parent, ends up with a null parent
* Used as a helper proc by the component transfer proc, does not clean up the component like Destroy does
*/
/datum/component/proc/RemoveComponent()
if(!parent)
return
@@ -280,6 +472,14 @@
parent = null
SEND_SIGNAL(old_parent, COMSIG_COMPONENT_REMOVING, src)
/**
* Transfer this component to another parent
*
* Component is taken from source datum
*
* Arguments:
* * datum/component/target Target datum to transfer to
*/
/datum/proc/TakeComponent(datum/component/target)
if(!target || target.parent == src)
return
@@ -296,6 +496,14 @@
if(target == AddComponent(target))
target._JoinParent()
/**
* Transfer all components to target
*
* All components from source datum are taken
*
* Arguments:
* * /datum/target the target to move the components to
*/
/datum/proc/TransferComponents(datum/target)
var/list/dc = datum_components
if(!dc)
@@ -310,5 +518,8 @@
if(C.can_transfer)
target.TakeComponent(comps)
/**
* Return the object that is the host of any UI's that this component has
*/
/datum/component/ui_host()
return parent