[MIRROR] Refactors connect_loc_behalf into a component (#7613)

* Refactors connect_loc_behalf into a component (#60678)

See title. Also refactors caltrops into a component because they use connect_loc_behalf which requires them to hold the state.

This also fixes COMPONENT_DUPE_SELECTIVE from just outright not working.

connect_loc_behalf doesn't make sense as an element because it tries to hold states. There is also no way to maintain current behaviour and not have the states that it needs.
Due to the fact that it tries to hold states, it means the code itself is a lot more buggy because it's a lot harder to successfully manage these states without runtimes or bugs. 

On metastation, there is only 2519 connect_loc_behalf components at roundstart. MrStonedOne has told me that datums take up this much space:
image

If we do the (oversimplified) math, there are only ever 5 variables that'll likely be changed on most connect_loc_behalf components at runtime:
connections,
tracked,
signal_atom,
parent,
signal_procs

This means that on metastation at roundstart, we take up this amount: (24 + 16 * 5) * 2519 = 261.97600 kilobytes
This is not really significant and the benefits of moving this to a component greatly outweighs the memory cost.

(Basically the memory cost is outweighed by the maint cost of tracking down issues with the thing. It's too buggy to be viable longterm basically)

* Update glass.dm

Co-authored-by: Watermelon914 <37270891+Watermelon914@users.noreply.github.com>
Co-authored-by: Gandalf <jzo123@hotmail.com>
This commit is contained in:
SkyratBot
2021-08-17 21:29:11 +02:00
committed by GitHub
parent 044cde205f
commit 169c42a262
25 changed files with 164 additions and 158 deletions
-112
View File
@@ -1,112 +0,0 @@
/**
* Caltrop element; for hurting people when they walk over this.
*
* Used for broken glass, cactuses and four sided dice.
*/
/datum/element/caltrop
element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH
id_arg_index = 2
///Minimum damage done when crossed
var/min_damage
///Maximum damage done when crossed
var/max_damage
///Probability of actually "firing", stunning and doing damage
var/probability
///Miscelanous caltrop flags; shoe bypassing, walking interaction, silence
var/flags
///The sound that plays when a caltrop is triggered.
var/soundfile
///given to connect_loc to listen for something moving over target
var/static/list/crossed_connections = list(
COMSIG_ATOM_ENTERED = .proc/on_entered,
)
/datum/element/caltrop/Attach(datum/target, min_damage = 0, max_damage = 0, probability = 100, flags = NONE, soundfile = null)
. = ..()
if(!isatom(target))
return ELEMENT_INCOMPATIBLE
src.min_damage = min_damage
src.max_damage = max(min_damage, max_damage)
src.probability = probability
src.flags = flags
src.soundfile = soundfile
if(ismovable(target))
AddElement(/datum/element/connect_loc_behalf, target, crossed_connections)
else
RegisterSignal(get_turf(target), COMSIG_ATOM_ENTERED, .proc/on_entered)
/datum/element/caltrop/proc/on_entered(datum/source, atom/movable/arrived, atom/old_loc, list/atom/old_locs)
SIGNAL_HANDLER
if(!prob(probability))
return
if(!ishuman(arrived))
return
var/mob/living/carbon/human/H = arrived
if(HAS_TRAIT(H, TRAIT_PIERCEIMMUNE))
return
if((flags & CALTROP_IGNORE_WALKERS) && H.m_intent == MOVE_INTENT_WALK)
return
if(H.movement_type & (FLOATING|FLYING)) //check if they are able to pass over us
//gravity checking only our parent would prevent us from triggering they're using magboots / other gravity assisting items that would cause them to still touch us.
return
if(H.buckled) //if they're buckled to something, that something should be checked instead.
return
if(H.body_position == LYING_DOWN && !(flags & CALTROP_NOCRAWL)) //if we're not standing we cant step on the caltrop
return
var/picked_def_zone = pick(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
var/obj/item/bodypart/O = H.get_bodypart(picked_def_zone)
if(!istype(O))
return
if(O.status == BODYPART_ROBOTIC)
return
if (!(flags & CALTROP_BYPASS_SHOES))
// SKYRAT EDIT ADDITION BEGIN - Hardened Soles Quirk
if(HAS_TRAIT(H, TRAIT_HARD_SOLES))
return
// SKYRAT EDIT ADDITION END
if ((H.wear_suit?.body_parts_covered | H.w_uniform?.body_parts_covered | H.shoes?.body_parts_covered) & FEET)
return
var/damage = rand(min_damage, max_damage)
if(HAS_TRAIT(H, TRAIT_LIGHT_STEP))
damage *= 0.75
if(!(flags & CALTROP_SILENT) && !H.has_status_effect(/datum/status_effect/caltropped))
H.apply_status_effect(/datum/status_effect/caltropped)
H.visible_message(
span_danger("[H] steps on [source]."),
span_userdanger("You step on [source]!")
)
H.apply_damage(damage, BRUTE, picked_def_zone, wound_bonus = CANT_WOUND)
if(!(flags & CALTROP_NOSTUN)) // Won't set off the paralysis.
H.Paralyze(60)
if(!soundfile)
return
playsound(H, soundfile, 15, TRUE, -3)
/datum/element/caltrop/Detach(datum/target)
. = ..()
if(ismovable(target))
RemoveElement(/datum/element/connect_loc_behalf, target, crossed_connections)
-91
View File
@@ -42,94 +42,3 @@
SIGNAL_HANDLER
unregister_signals(listener, old_loc)
update_signals(listener)
/// This element behaves the same as connect_loc, hooking into a signal on a tracked object's turf
/// It has the ability to react to that signal on behalf of a seperate listener however
/// This has great use, primarially for components, but it carries with it some overhead
/// So we do it seperately rather then intigrating the behavior with the main element
/datum/element/connect_loc_behalf
element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH | ELEMENT_COMPLEX_DETACH
id_arg_index = 3
/// An assoc list of signal -> procpath to register to the loc this object is on.
var/list/connections
/// An assoc list of locs that are being occupied and a list of targets that occupy them.
var/list/targets = list()
/datum/element/connect_loc_behalf/Attach(datum/listener, atom/movable/tracked, list/connections)
. = ..()
if (!istype(tracked))
return ELEMENT_INCOMPATIBLE
src.connections = connections
RegisterSignal(tracked, COMSIG_MOVABLE_MOVED, .proc/on_moved, override = TRUE)
update_signals(listener, tracked)
/datum/element/connect_loc_behalf/Detach(datum/listener, atom/movable/tracked, list/connections)
. = ..()
if(!tracked)
unregister_all(listener)
else if(targets[tracked.loc]) // Detach can happen multiple times due to qdel
unregister_signals(listener, tracked, tracked.loc)
UnregisterSignal(tracked, COMSIG_MOVABLE_MOVED)
/datum/element/connect_loc_behalf/proc/update_signals(datum/listener, atom/movable/tracked)
var/existing = length(targets[tracked.loc])
if(!existing)
targets[tracked.loc] = list()
targets[tracked.loc][tracked] = listener
if(isnull(tracked.loc))
return
for (var/signal in connections)
listener.RegisterSignal(tracked.loc, signal, connections[signal], override=TRUE)
//override=TRUE because more than one connect_loc element instance tracked object can be on the same loc
/datum/element/connect_loc_behalf/proc/unregister_all(datum/listener)
for(var/atom/location as anything in targets)
var/list/loc_targets = targets[location]
for(var/atom/movable/tracked as anything in loc_targets)
if(tracked == listener)
unregister_signals(loc_targets[tracked], tracked, location)
else if(loc_targets[tracked] == listener)
unregister_signals(listener, tracked, location)
else
continue
UnregisterSignal(tracked, COMSIG_MOVABLE_MOVED)
/datum/element/connect_loc_behalf/proc/unregister_signals(datum/listener, atom/movable/tracked, atom/old_loc)
if (length(targets[old_loc]) <= 1)
targets -= old_loc
else
targets[old_loc] -= tracked
// Yes this is after the above because we use null as a key when objects are in nullspace
if(isnull(old_loc))
return
for (var/signal in connections)
listener.UnregisterSignal(old_loc, signal)
/datum/element/connect_loc_behalf/proc/on_moved(atom/movable/tracked, atom/old_loc)
SIGNAL_HANDLER
var/list/objects_in_old_loc = targets[old_loc]
//You may ask yourself, isn't this just silencing an error?
//The answer is yes, but there's no good cheap way to fix it
//What happens is the tracked object or hell the listener gets say, deleted, which makes targets[old_loc] return a null
//The null results in a bad index, because of course it does
//It's not a solvable problem though, since both actions, the destroy and the move, are sourced from the same signal send
//And sending a signal should be agnostic of the order of listeners
//So we need to either pick the order agnositic, or destroy safe
//And I picked destroy safe. Let's hope this is the right path!
if(!objects_in_old_loc)
return
var/datum/listener = objects_in_old_loc[tracked]
if(!listener) //See above
return
unregister_signals(listener, tracked, old_loc)
update_signals(listener, tracked)
-75
View File
@@ -1,75 +0,0 @@
///This component allows something to be when crossed, for example for cockroaches.
/datum/component/squashable
///Chance on crossed to be squashed
var/squash_chance = 50
///How much brute is applied when mob is squashed
var/squash_damage = 1
///Squash flags, for extra checks etcetera.
var/squash_flags = NONE
///Special callback to call on squash instead, for things like hauberoach
var/datum/callback/on_squash_callback
///signal list given to connect_loc
var/static/list/loc_connections = list(
COMSIG_ATOM_ENTERED = .proc/on_entered,
)
/datum/component/squashable/Initialize(squash_chance, squash_damage, squash_flags, squash_callback)
. = ..()
if(!isliving(parent))
return COMPONENT_INCOMPATIBLE
if(squash_chance)
src.squash_chance = squash_chance
if(squash_damage)
src.squash_damage = squash_damage
if(squash_flags)
src.squash_flags = squash_flags
if(!src.on_squash_callback && squash_callback)
on_squash_callback = CALLBACK(parent, squash_callback)
AddElement(/datum/element/connect_loc_behalf, parent, loc_connections)
///Handles the squashing of the mob
/datum/component/squashable/proc/on_entered(turf/source_turf, atom/movable/crossing_movable)
SIGNAL_HANDLER
if(parent == crossing_movable)
return
var/mob/living/parent_as_living = parent
if(squash_flags & SQUASHED_SHOULD_BE_DOWN && parent_as_living.body_position != LYING_DOWN)
return
var/should_squash = prob(squash_chance)
if(should_squash && on_squash_callback)
if(on_squash_callback.Invoke(parent_as_living, crossing_movable))
return //Everything worked, we're done!
if(isliving(crossing_movable))
var/mob/living/crossing_mob = crossing_movable
if(crossing_mob.mob_size > MOB_SIZE_SMALL && !(crossing_mob.movement_type & FLYING))
if(HAS_TRAIT(crossing_mob, TRAIT_PACIFISM))
crossing_mob.visible_message(span_notice("[crossing_mob] carefully steps over [parent_as_living]."), span_notice("You carefully step over [parent_as_living] to avoid hurting it."))
return
if(should_squash)
crossing_mob.visible_message(span_notice("[crossing_mob] squashed [parent_as_living]."), span_notice("You squashed [parent_as_living]."))
Squish(parent_as_living)
else
parent_as_living.visible_message(span_notice("[parent_as_living] avoids getting crushed."))
else if(isstructure(crossing_movable))
if(should_squash)
crossing_movable.visible_message(span_notice("[parent_as_living] is crushed under [crossing_movable]."))
Squish(parent_as_living)
else
parent_as_living.visible_message(span_notice("[parent_as_living] avoids getting crushed."))
/datum/component/squashable/proc/Squish(mob/living/target)
if(squash_flags & SQUASHED_SHOULD_BE_GIBBED)
target.gib()
else
target.adjustBruteLoss(squash_damage)
/datum/component/squashable/UnregisterFromParent()
. = ..()
RemoveElement(/datum/element/connect_loc_behalf, parent, loc_connections)