Files
Bubberstation/code/datums/components/aquarium_content.dm
SkyratBot 067188d366 [MIRROR] Micro-optimize qdel by only permitting one parameter [MDB IGNORE] (#25889)
* Micro-optimize qdel by only permitting one parameter (#80628)

Productionizes #80615.

The core optimization is this:

```patch
-	var/hint = to_delete.Destroy(arglist(args.Copy(2))) // Let our friend know they're about to get fucked up.
+	var/hint = to_delete.Destroy(force) // Let our friend know they're about to get fucked up.
```

We avoid a heap allocation in the form of copying the args over to a new
list. A/B testing shows this results in 33% better overtime, and in a
real round shaving off a full second of self time and 0.4 seconds of
overtime--both of these would be doubled in the event this is merged as
the new proc was only being run 50% of the time.

* Micro-optimize qdel by only permitting one parameter

---------

Co-authored-by: Mothblocks <35135081+Mothblocks@users.noreply.github.com>
2023-12-29 14:41:12 +00:00

306 lines
11 KiB
Plaintext

/// Allows movables to be inserted/displayed in aquariums.
/datum/component/aquarium_content
/// Keeps track of our current aquarium.
var/obj/structure/aquarium/current_aquarium
//This is visual effect holder that will end up in aquarium's vis_contents
var/obj/effect/vc_obj
/// Base px offset of the visual object in current aquarium aka current base position
var/base_px = 0
/// Base px offset of the visual object in current aquarium aka current base position
var/base_py = 0
//Current layer for the visual object
var/base_layer
/**
* Fish sprite how to:
* Need to be centered on 16,16 in the dmi and facing left by default.
* sprite_height/sprite_width is the size it will have in aquarium and used to control animation boundaries.
* source_height/source_width is the size of the original icon (ideally only the non-empty parts)
*/
/// Icon used for in aquarium sprite
var/icon = 'icons/obj/aquarium.dmi'
/// If this is set this icon state will be used for the holder while icon_state will only be used for item/catalog. Transformation from source_width/height WON'T be applied.
var/icon_state
/// Applied to vc object only for use with greyscaled icons.
var/aquarium_vc_color
/// Transformation applied to the visual holder - used when scaled down sprites are used as in aquarium visual
var/matrix/base_transform
/// How the thing will be layered
var/layer_mode = AQUARIUM_LAYER_MODE_AUTO
/// If the starting position is randomised within bounds when inserted into aquarium.
var/randomize_position = FALSE
//Target sprite size for path/position calculations.
var/sprite_height = 3
var/sprite_width = 3
//This is the size of the source sprite. This will be used to calculate scale down factor.
var/source_width = 32
var/source_height = 32
/// Currently playing animation
var/current_animation
/// Does this behviour need additional processing in aquarium, will be added to SSobj processing on insertion
var/processing = FALSE
/// TODO: Change this into trait checked on aquarium insertion
var/unique = FALSE
/// Proc used to retrieve current animation state from the parent, optional
var/animation_getter
/// Signals of the parent that will trigger animation update
var/animation_update_signals
/datum/component/aquarium_content/Initialize(animation_getter, animation_update_signals)
if(!ismovable(parent))
return COMPONENT_INCOMPATIBLE
src.animation_getter = animation_getter
src.animation_update_signals = animation_update_signals
if(animation_update_signals)
RegisterSignals(parent, animation_update_signals, PROC_REF(generate_animation))
if(istype(parent,/obj/item/fish))
InitializeFromFish()
else if(istype(parent,/obj/item/aquarium_prop))
InitializeFromProp()
else
InitializeOther()
ADD_TRAIT(parent, TRAIT_FISH_CASE_COMPATIBILE, REF(src))
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(enter_aquarium))
//If component is added to something already in aquarium at the time initialize it properly.
var/atom/movable/movable_parent = parent
if(istype(movable_parent.loc, /obj/structure/aquarium))
on_inserted(movable_parent.loc)
/// Sets visuals properties for fish
/datum/component/aquarium_content/proc/InitializeFromFish()
var/obj/item/fish/fish = parent
icon = fish.icon
sprite_height = fish.sprite_height
sprite_width = fish.sprite_width
aquarium_vc_color = fish.aquarium_vc_color
if(fish.dedicated_in_aquarium_icon_state)
if(fish.dedicated_in_aquarium_icon)
icon = fish.dedicated_in_aquarium_icon
icon_state = fish.dedicated_in_aquarium_icon_state
base_transform = matrix()
else
icon_state = fish.icon_state
var/matrix/matrix = matrix()
var/x_scale = fish.sprite_width / fish.source_width
var/y_scale = fish.sprite_height / fish.source_height
matrix.Scale(x_scale, y_scale)
base_transform = matrix
randomize_position = TRUE
/// Sets visuals properties for fish
/datum/component/aquarium_content/proc/InitializeFromProp()
var/obj/item/aquarium_prop/prop = parent
icon = prop.icon
icon_state = prop.icon_state
layer_mode = prop.layer_mode
sprite_height = 32
sprite_width = 32
base_transform = matrix()
unique = TRUE
/// Mostly for admin abuse
/datum/component/aquarium_content/proc/InitializeOther()
sprite_width = 8
sprite_height = 8
var/matrix/matrix = matrix()
var/x_scale = sprite_width / 32
var/y_scale = sprite_height / 32
matrix.Scale(x_scale, y_scale)
base_transform = matrix
/datum/component/aquarium_content/PreTransfer()
. = ..()
REMOVE_TRAIT(parent, TRAIT_FISH_CASE_COMPATIBILE, REF(src))
/datum/component/aquarium_content/Destroy(force)
if(current_aquarium)
remove_from_aquarium()
QDEL_NULL(vc_obj)
return ..()
/datum/component/aquarium_content/proc/enter_aquarium(datum/source, OldLoc, Dir, Forced)
SIGNAL_HANDLER
var/atom/movable/movable_parent = parent
if(istype(movable_parent.loc, /obj/structure/aquarium))
on_inserted(movable_parent.loc)
/datum/component/aquarium_content/proc/is_ready_to_insert(obj/structure/aquarium/aquarium)
//This is kinda awful but we're unaware of other fish
if(unique)
for(var/atom/movable/fish_or_prop in aquarium)
if(fish_or_prop == parent)
continue
if(fish_or_prop.type == parent.type)
return FALSE
return TRUE
/datum/component/aquarium_content/proc/on_inserted(atom/aquarium)
current_aquarium = aquarium
RegisterSignal(current_aquarium, COMSIG_ATOM_EXITED, PROC_REF(on_removed))
RegisterSignal(current_aquarium, COMSIG_AQUARIUM_SURFACE_CHANGED, PROC_REF(on_surface_changed))
RegisterSignal(current_aquarium, COMSIG_AQUARIUM_FLUID_CHANGED, PROC_REF(on_fluid_changed))
if(processing)
START_PROCESSING(SSobj, src)
//If we don't have vc object yet build it
if(!vc_obj)
vc_obj = generate_base_vc()
//Set default position and layer
set_vc_base_position()
generate_animation(reset = TRUE)
//Finally add it to to objects vis_contents
current_aquarium.vis_contents |= vc_obj
/// Aquarium surface changed in some way, we need to recalculate base position and aninmation
/datum/component/aquarium_content/proc/on_surface_changed()
SIGNAL_HANDLER
set_vc_base_position()
generate_animation(reset = TRUE) //our animation start point changed, gotta redo
/datum/component/aquarium_content/proc/on_fluid_changed()
SIGNAL_HANDLER
generate_animation()
/datum/component/aquarium_content/proc/remove_visual_from_aquarium()
current_aquarium.vis_contents -= vc_obj
if(base_layer)
current_aquarium.free_layer(base_layer)
/// Generates common visual object, propeties that don't depend on aquarium surface
/datum/component/aquarium_content/proc/generate_base_vc()
var/obj/effect/visual = new
apply_appearance(visual)
visual.vis_flags |= VIS_INHERIT_ID | VIS_INHERIT_PLANE //plane so it shows properly in containers on inventory ui for handheld cases
return visual
/// Applies icon,color and base scaling to our visual holder
/datum/component/aquarium_content/proc/apply_appearance(obj/effect/holder)
holder.icon = icon
holder.icon_state = icon_state
holder.transform = matrix(base_transform)
if(aquarium_vc_color)
holder.color = aquarium_vc_color
/// Actually animates the vc holder
/datum/component/aquarium_content/proc/generate_animation(reset=FALSE)
if(!current_aquarium)
return
var/next_animation = animation_getter ? call(parent,animation_getter)() : null
if(current_animation == next_animation && !reset)
return
current_animation = next_animation
switch(current_animation)
if(AQUARIUM_ANIMATION_FISH_SWIM)
swim_animation()
return
if(AQUARIUM_ANIMATION_FISH_DEAD)
dead_animation()
return
/// Create looping random path animation, pixel offsets parameters include offsets already
/datum/component/aquarium_content/proc/swim_animation()
var/avg_width = round(sprite_width / 2)
var/avg_height = round(sprite_height / 2)
var/list/aq_properties = current_aquarium.get_surface_properties()
var/px_min = aq_properties[AQUARIUM_PROPERTIES_PX_MIN] + avg_width - 16
var/px_max = aq_properties[AQUARIUM_PROPERTIES_PX_MAX] - avg_width - 16
var/py_min = aq_properties[AQUARIUM_PROPERTIES_PY_MIN] + avg_height - 16
var/py_max = aq_properties[AQUARIUM_PROPERTIES_PY_MAX] - avg_width - 16
var/origin_x = base_px
var/origin_y = base_py
var/prev_x = origin_x
var/prev_y = origin_y
animate(vc_obj, pixel_x = origin_x, time = 0, loop = -1) //Just to start the animation
var/move_number = rand(3, 5) //maybe unhardcode this
for(var/i in 1 to move_number)
//If it's last movement, move back to start otherwise move to some random point
var/target_x = i == move_number ? origin_x : rand(px_min,px_max) //could do with enforcing minimal delta for prettier zigzags
var/target_y = i == move_number ? origin_y : rand(py_min,py_max)
var/dx = prev_x - target_x
var/dy = prev_y - target_y
prev_x = target_x
prev_y = target_y
var/dist = abs(dx) + abs(dy)
var/eyeballed_time = dist * 2 //2ds per px
//Face the direction we're going
var/matrix/dir_mx = matrix(base_transform)
if(dx <= 0) //assuming default sprite is facing left here
dir_mx.Scale(-1, 1)
animate(transform = dir_mx, time = 0, loop = -1)
animate(pixel_x = target_x, pixel_y = target_y, time = eyeballed_time, loop = -1)
/datum/component/aquarium_content/proc/dead_animation()
//Set base_py to lowest possible value
var/avg_height = round(sprite_height / 2)
var/list/aq_properties = current_aquarium.get_surface_properties()
var/py_min = aq_properties[AQUARIUM_PROPERTIES_PY_MIN] + avg_height - 16
base_py = py_min
animate(vc_obj, pixel_y = py_min, time = 1) //flop to bottom and end current animation.
/datum/component/aquarium_content/proc/set_vc_base_position()
if(randomize_position)
randomize_base_position()
if(base_layer)
current_aquarium.free_layer(base_layer)
base_layer = current_aquarium.request_layer(layer_mode)
vc_obj.layer = base_layer
/datum/component/aquarium_content/proc/randomize_base_position()
var/list/aq_properties = current_aquarium.get_surface_properties()
var/avg_width = round(sprite_width / 2)
var/avg_height = round(sprite_height / 2)
var/px_min = aq_properties[AQUARIUM_PROPERTIES_PX_MIN] + avg_width - 16
var/px_max = aq_properties[AQUARIUM_PROPERTIES_PX_MAX] - avg_width - 16
var/py_min = aq_properties[AQUARIUM_PROPERTIES_PY_MIN] + avg_height - 16
var/py_max = aq_properties[AQUARIUM_PROPERTIES_PY_MAX] - avg_width - 16
base_px = rand(px_min,px_max)
base_py = rand(py_min,py_max)
vc_obj.pixel_x = base_px
vc_obj.pixel_y = base_py
/datum/component/aquarium_content/proc/on_removed(datum/source, atom/movable/gone, direction)
SIGNAL_HANDLER
if(parent != gone)
return
remove_from_aquarium()
/datum/component/aquarium_content/proc/remove_from_aquarium()
UnregisterSignal(current_aquarium, list(COMSIG_AQUARIUM_SURFACE_CHANGED, COMSIG_AQUARIUM_FLUID_CHANGED, COMSIG_ATOM_ATTACKBY, COMSIG_ATOM_EXITED))
remove_visual_from_aquarium()
current_aquarium = null