From 17a5454fbc1cce63133f0dcdc70ba117b71a7a6b Mon Sep 17 00:00:00 2001 From: variableundefined <40092670+variableundefined@users.noreply.github.com> Date: Tue, 4 Sep 2018 23:19:30 +0800 Subject: [PATCH] Port over three PRs from TG about component. --- code/__DEFINES/components.dm | 3 + code/_onclick/hud/screen_objects.dm | 2 +- code/_onclick/item_attack.dm | 2 +- code/controllers/subsystem/garbage.dm | 3 +- code/datums/components/README.md | 19 +++-- code/datums/components/_component.dm | 88 ++++++++++++++------ code/datums/components/material_container.dm | 8 +- code/datums/datum.dm | 14 ++++ code/game/atoms.dm | 2 +- 9 files changed, 99 insertions(+), 42 deletions(-) diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm index 6f14b75b2ec..235071549bd 100644 --- a/code/__DEFINES/components.dm +++ b/code/__DEFINES/components.dm @@ -1,3 +1,5 @@ +#define SEND_SIGNAL(target, sigtype, arguments...) ( !target.comp_lookup || !target.comp_lookup[sigtype] ? NONE : target._SendSignal(sigtype, list(target, ##arguments)) ) + //shorthand #define GET_COMPONENT_FROM(varname, path, target) var##path/##varname = ##target.GetComponent(##path) #define GET_COMPONENT(varname, path) GET_COMPONENT_FROM(varname, path, src) @@ -13,6 +15,7 @@ // All signals. Format: // When the signal is called: (signal arguments) +// All signals send the source datum of the signal as the first argument // /datum signals #define COMSIG_COMPONENT_ADDED "component_added" //when a component is added to a datum: (/datum/component) diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index b61cb69e0e4..74080a908a5 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -167,7 +167,7 @@ if(master) var/obj/item/I = usr.get_active_hand() if(I) - master.attackby(I, usr, params) + master.attackby(null, I, usr, params) return 1 /obj/screen/zone_sel diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index 0299a7ec2fc..78dde4e95f9 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -14,7 +14,7 @@ // No comment /atom/proc/attackby(obj/item/W, mob/user, params) - return SendSignal(COMSIG_PARENT_ATTACKBY, W, user, params) + return SEND_SIGNAL(src, COMSIG_PARENT_ATTACKBY, W, user, params) /obj/attackby(obj/item/I, mob/living/user, params) return ..() || (can_be_hit && I.attack_obj(src, user)) diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm index 823294e4d4e..e81e90dd974 100644 --- a/code/controllers/subsystem/garbage.dm +++ b/code/controllers/subsystem/garbage.dm @@ -295,11 +295,12 @@ SUBSYSTEM_DEF(garbage) if(isnull(D.gc_destroyed)) - D.SendSignal(COMSIG_PARENT_QDELETED) + SEND_SIGNAL(D, COMSIG_PARENT_QDELETED, force) // Give the components a chance to prevent their parent from being deleted D.gc_destroyed = GC_CURRENTLY_BEING_QDELETED var/start_time = world.time var/start_tick = world.tick_usage var/hint = D.Destroy(arglist(args.Copy(2))) // Let our friend know they're about to get fucked up. + SEND_SIGNAL(D, COMSIG_PARENT_QDELETED, force, hint) // Let the (remaining) components know about the result of Destroy if(world.time != start_time) I.slept_destroy++ else diff --git a/code/datums/components/README.md b/code/datums/components/README.md index ee156e45525..4eecdce18ca 100644 --- a/code/datums/components/README.md +++ b/code/datums/components/README.md @@ -2,7 +2,7 @@ ## Concept -Loosely adapted from /vg/. This is an entity component system for adding behaviours to datums when inheritance doesn't quite cut it. By using signals and events instead of direct inheritance, you can inject behaviours without hacky overloads. It requires a different method of thinking, but is not hard to use correctly. If a behaviour can have application across more than one thing. Make it generic, make it a component. Atom/mob/obj event? Give it a signal, and forward it's arguments with a `SendSignal()` call. Now every component that want's to can also know about this happening. +Loosely adapted from /vg/. This is an entity component system for adding behaviours to datums when inheritance doesn't quite cut it. By using signals and events instead of direct inheritance, you can inject behaviours without hacky overloads. It requires a different method of thinking, but is not hard to use correctly. If a behaviour can have application across more than one thing. Make it generic, make it a component. Atom/mob/obj event? Give it a signal, and forward it's arguments with a `SEND_SIGNAL` call. Now every component that want's to can also know about this happening. ### In the code @@ -28,6 +28,7 @@ Stands have a lot of procs which mimic mob procs. Rather than inserting hooks fo 1. `COMPONENT_INCOMPATIBLE` Return this from `/datum/component/Initialize` or `datum/component/OnTransfer` to have the component be deleted if it's applied to an incorrect type. `parent` must not be modified if this is to be returned. + ### Vars 1. `/datum/var/list/datum_components` (private) @@ -61,6 +62,11 @@ Stands have a lot of procs which mimic mob procs. Rather than inserting hooks fo * Returns a reference to a component whose type MATCHES component_type if that component exists in the datum, null otherwise 1. `GET_COMPONENT(varname, component_type)` OR `GET_COMPONENT_FROM(varname, component_type, src)` * Shorthand for `var/component_type/varname = src.GetComponent(component_type)` +1. `SEND_SIGNAL(target, sigtype, ...)` (public, final) + * Use to send signals to target datum + * Extra arguments are to be specified in the signal definition + * Returns a bitflag with signal specific information assembled from all activated components + * Arguments are packaged in a list and handed off to _SendSignal() 1. `/datum/proc/AddComponent(component_type(type), ...) -> datum/component` (public, final) * Creates an instance of `component_type` in the datum and passes `...` to its `Initialize()` call * Sends the `COMSIG_COMPONENT_ADDED` signal to the datum @@ -77,10 +83,9 @@ Stands have a lot of procs which mimic mob procs. Rather than inserting hooks fo * Properly transfers ownership of a component from one datum to another * Signals `COMSIG_COMPONENT_REMOVING` on the parent * Called on the datum you want to own the component with another datum's component -1. `/datum/proc/SendSignal(signal, ...)` (public, final) - * Call to send a signal to the components of the target datum - * Extra arguments are to be specified in the signal definition - * Returns a bitflag with signal specific information assembled from all activated components +1. `/datum/proc/_SendSignal(signal, list/arguments)` (private, final) + * Handles most of the actual signaling procedure + * Will runtime if used on datums with an empty component list 1. `/datum/component/New(datum/parent, ...)` (private, final) * Runs internal setup for the component * Extra arguments are passed to `Initialize()` @@ -108,13 +113,13 @@ Stands have a lot of procs which mimic mob procs. Rather than inserting hooks fo * Clears `parent` and removes the component from it's component list 1. `/datum/component/proc/_JoinParent` (private, final) * Tries to add the component to it's `parent`s `datum_components` list -1. `/datum/component/proc/RegisterSignal(signal(string/list of strings), proc_ref(type), override(boolean))` (protected, final) (Consider removing for performance gainz) +1. `/datum/component/proc/RegisterSignal(datum/target, signal(string/list of strings), proc_ref(type), override(boolean))` (protected, final) * If signal is a list it will be as if RegisterSignal was called for each of the entries with the same following arguments * Makes a component listen for the specified `signal` on it's `parent` datum. * When that signal is recieved `proc_ref` will be called on the component, along with associated arguments * Example proc ref: `.proc/OnEvent` * If a previous registration is overwritten by the call, a runtime occurs. Setting `override` to TRUE prevents this * These callbacks run asyncronously - * Returning `TRUE` from these callbacks will trigger a `TRUE` return from the `SendSignal()` that initiated it + * Returning `TRUE` from these callbacks will trigger a `TRUE` return from the `_SendSignal()` that initiated it ### See/Define signals and their arguments in __DEFINES\components.dm diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm index 775a8667b3f..2b12d92937e 100644 --- a/code/datums/components/_component.dm +++ b/code/datums/components/_component.dm @@ -56,9 +56,10 @@ if(!force) _RemoveFromParent() if(!silent) - P.SendSignal(COMSIG_COMPONENT_REMOVING, src) + SEND_SIGNAL(P, COMSIG_COMPONENT_REMOVING, src) parent = null - LAZYCLEARLIST(signal_procs) + for(var/target in signal_procs) + UnregisterSignal(target, signal_procs[target]) return ..() /datum/component/proc/_RemoveFromParent() @@ -77,26 +78,67 @@ if(!dc.len) P.datum_components = null -/datum/component/proc/RegisterSignal(sig_type_or_types, proc_or_callback, override = FALSE) - if(QDELETED(src)) +/datum/component/proc/RegisterSignal(datum/target, sig_type_or_types, proc_or_callback, override = FALSE) + if(QDELETED(src) || QDELETED(target)) return + var/list/procs = signal_procs if(!procs) - procs = list() - signal_procs = procs + signal_procs = procs = list() + if(!procs[target]) + procs[target] = list() + var/list/lookup = target.comp_lookup + if(!lookup) + target.comp_lookup = lookup = list() + + if(!istype(proc_or_callback, /datum/callback)) //if it wasnt a callback before, it is now + proc_or_callback = CALLBACK(src, proc_or_callback) var/list/sig_types = islist(sig_type_or_types) ? sig_type_or_types : list(sig_type_or_types) for(var/sig_type in sig_types) - if(!override) - . = procs[sig_type] - if(.) - stack_trace("[sig_type] overridden. Use override = TRUE to suppress this warning") + if(!override && procs[target][sig_type]) + stack_trace("[sig_type] overridden. Use override = TRUE to suppress this warning") - if(!istype(proc_or_callback, /datum/callback)) //if it wasnt a callback before, it is now - proc_or_callback = CALLBACK(src, proc_or_callback) - procs[sig_type] = proc_or_callback + procs[target][sig_type] = proc_or_callback + if(!lookup[sig_type]) // Nothing has registered here yet + lookup[sig_type] = src + else if(lookup[sig_type] == src) // We already registered here + continue + else if(!length(lookup[sig_type])) // One other thing registered here + lookup[sig_type] = list(lookup[sig_type]=TRUE) + lookup[sig_type][src] = TRUE + else // Many other things have registered here + lookup[sig_type][src] = TRUE enabled = TRUE +/datum/component/proc/UnregisterSignal(datum/target, sig_type_or_types) + var/list/lookup = target.comp_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) + 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 comp_lookup") + if(src in lookup[sig]) + lookup -= sig + if(!length(lookup)) + target.comp_lookup = null + break + if(0) + lookup -= sig + if(!length(lookup)) + target.comp_lookup = null + break + else + lookup[sig] -= src + + signal_procs[target] -= sig_type_or_types + if(!signal_procs[target].len) + signal_procs -= target /datum/component/proc/InheritComponent(datum/component/C, i_am_original) return @@ -121,28 +163,20 @@ current_type = type2parent(current_type) . += current_type -/datum/proc/SendSignal(sigtype, ...) - var/list/comps = datum_components - if(!comps) - return NONE - var/list/arguments = args.Copy(2) - var/target = comps[/datum/component] +/datum/proc/_SendSignal(sigtype, list/arguments) + var/target = comp_lookup[sigtype] if(!length(target)) var/datum/component/C = target if(!C.enabled) return NONE - var/datum/callback/CB = C.signal_procs[sigtype] - if(!CB) - return NONE + var/datum/callback/CB = C.signal_procs[src][sigtype] return CB.InvokeAsync(arglist(arguments)) . = NONE for(var/I in target) var/datum/component/C = I if(!C.enabled) continue - var/datum/callback/CB = C.signal_procs[sigtype] - if(!CB) - continue + var/datum/callback/CB = C.signal_procs[src][sigtype] . |= CB.InvokeAsync(arglist(arguments)) /datum/proc/GetComponent(c_type) @@ -224,7 +258,7 @@ new_comp = new nt(arglist(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 - SendSignal(COMSIG_COMPONENT_ADDED, new_comp) + SEND_SIGNAL(src, COMSIG_COMPONENT_ADDED, new_comp) return new_comp return old_comp @@ -244,7 +278,7 @@ qdel(C) return C._RemoveFromParent() - helicopter.SendSignal(COMSIG_COMPONENT_REMOVING, C) + SEND_SIGNAL(helicopter, COMSIG_COMPONENT_REMOVING, C) C.parent = src if(C == AddComponent(C)) C._JoinParent() diff --git a/code/datums/components/material_container.dm b/code/datums/components/material_container.dm index f08181cb5f4..4ae7d08f3ea 100644 --- a/code/datums/components/material_container.dm +++ b/code/datums/components/material_container.dm @@ -30,8 +30,8 @@ precondition = _precondition after_insert = _after_insert - RegisterSignal(COMSIG_PARENT_ATTACKBY, .proc/OnAttackBy) - RegisterSignal(COMSIG_PARENT_EXAMINE, .proc/OnExamine) + RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/OnAttackBy) + RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/OnExamine) var/list/possible_mats = list() for(var/mat_type in subtypesof(/datum/material)) @@ -42,14 +42,14 @@ var/mat_path = possible_mats[id] materials[id] = new mat_path() -/datum/component/material_container/proc/OnExamine(mob/user) +/datum/component/material_container/proc/OnExamine(datum/source, mob/user) for(var/I in materials) var/datum/material/M = materials[I] var/amt = amount(M.id) if(amt) to_chat(user, "It has [amt] units of [lowertext(M.name)] stored.") -/datum/component/material_container/proc/OnAttackBy(obj/item/I, mob/living/user) +/datum/component/material_container/proc/OnAttackBy(datum/source, obj/item/I, mob/living/user) var/list/tc = allowed_typecache if(user.a_intent != INTENT_HELP) return diff --git a/code/datums/datum.dm b/code/datums/datum.dm index 70c783d634e..2db8c0cad89 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -2,6 +2,7 @@ var/gc_destroyed //Time when this object was destroyed. var/list/active_timers //for SStimer var/list/datum_components //for /datum/components + var/list/comp_lookup var/var_edited = FALSE //Warranty void if seal is broken var/tmp/unique_datum_id = null @@ -37,6 +38,19 @@ qdel(C, FALSE, TRUE) dc.Cut() + var/list/lookup = comp_lookup + if(lookup) + for(var/sig in lookup) + var/list/comps = lookup[sig] + if(length(comps)) + for(var/i in comps) + var/datum/component/comp = i + comp.UnregisterSignal(src, sig) + else + var/datum/component/comp = comps + comp.UnregisterSignal(src, sig) + comp_lookup = lookup = null + return QDEL_HINT_QUEUE diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 3c541a308ff..c0da13073fd 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -283,7 +283,7 @@ else to_chat(user, "Nothing.") - SendSignal(COMSIG_PARENT_EXAMINE, user) + SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user) return distance == -1 || (get_dist(src, user) <= distance) || isobserver(user) //observers do not have a range limit