Converts hot springs to use proper shared particles (#90359)

## About The Pull Request

Converts hot spring turfs to shared particles instead of pseudo-cached
per-turf holders. This does come with some ugliness in
add/remove_shared_particles code due to /area not having vis_contents,
but that code shouldn't be touched by most developers who don't know
what's going on anyways.

I'm not sure if the "cached" particles even worked in practice, and if
they had any clientside perf improvements.

## Why It's Good For The Game

Better perf, 

## Changelog
🆑
code: Converted hot springs to a new shared particles system for better
clientside performance.
/🆑
This commit is contained in:
SmArtKar
2025-04-29 17:15:52 -06:00
committed by Shadow-Quill
parent 8a91d3a777
commit cf6dbf06aa
3 changed files with 27 additions and 62 deletions
+1 -45
View File
@@ -30,7 +30,7 @@
// Mouse opacity can get set to opaque by some objects when placed into the object's contents (storage containers).
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
src.particle_flags = particle_flags
particles = get_particle_effect(particle_path)
particles = new particle_path()
// /atom doesn't have vis_contents, /turf and /atom/movable do
var/atom/movable/lie_about_areas = parent
lie_about_areas.vis_contents += src
@@ -40,9 +40,6 @@
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_move))
on_move(parent, null, NORTH)
/obj/effect/abstract/particle_holder/proc/get_particle_effect(particle_path)
return new particle_path()
/obj/effect/abstract/particle_holder/Destroy(force)
QDEL_NULL(particles)
parent = null
@@ -74,44 +71,3 @@
/// Sets the particles position to the passed coordinates
/obj/effect/abstract/particle_holder/proc/set_particle_position(x = 0, y = 0, z = 0)
particles.position = list(x, y, z)
/**
* A subtype of particle holder that reuses the same particles to reduce client lag
* when rendering certain atoms, usually found in large quantities and close together.
* Since it reuses the same instances, modifying an instance of particles will affect all atoms
* that show it, therefore procs like set_particle_position() shouldn't be used here.
*/
/obj/effect/abstract/particle_holder/cached
///A static list meant to contain the availables instances of a particle path to use.
var/static/list/particles_by_type
/**
* The length of the pool of particles from which the chosen instance will be picked
* This provides an ever-so-lightly variety to the particles, so they don't all jarringly look EXACTLY the same
*/
var/max_particle_index = 4
/obj/effect/abstract/particle_holder/cached/Initialize(mapload, particle_path = /particles/smoke, particle_flags = NONE, max_particle_index)
src.max_particle_index = max_particle_index
return ..()
/obj/effect/abstract/particle_holder/cached/Destroy(force)
particles = null
return ..()
/obj/effect/abstract/particle_holder/cached/get_particle_effect(particle_path)
LAZYINITLIST(particles_by_type)
LAZYINITLIST(particles_by_type[particle_path])
var/list/particles_list = particles_by_type[particle_path]
var/index = rand(1, max_particle_index)
var/particles/chosen
if(length(particles_list) < index)
chosen = new particle_path()
particles_list += chosen
else
chosen = particles_list[index]
return chosen
/obj/effect/abstract/particle_holder/cached/set_particle_position(x = 0, y = 0, z = 0)
CRASH("[type] doesn't support set_particle_position()")
@@ -33,36 +33,43 @@ GLOBAL_LIST_EMPTY(shared_particles)
* for 400 objects using them. This should be prioritized over normal particles when possible if it is known
* that there will be a lot of objects using certain particles.
* custom_key can be used to create a new pool of already existing particle type in case you're planning to edit holder's color or properties
* pool_size controls how many particle holders per type are created. Any objects over this cap will pick an existing holder from the pool
* pool_size controls how many particle holders per type are created. Any objects over this cap will pick an existing holder from the pool.
*
* Now, this code seems fucked up, that's because this is meant to support both objects (and mobs) and turfs, *however* areas are special
* and don't have vis_contents, so to avoid copypaste code we do this weirdness
*/
/atom/movable/proc/add_shared_particles(particle_type, custom_key = null, particle_flags = NONE, pool_size = 3)
/atom/proc/add_shared_particles(particle_type, custom_key = null, particle_flags = NONE, pool_size = 3)
var/atom/movable/play_pretend = src
var/particle_key = custom_key || "[particle_type]"
if (!GLOB.shared_particles[particle_key])
GLOB.shared_particles[particle_key] = list(list(new /obj/effect/abstract/shared_particle_holder(null, particle_type, particle_flags)), 1)
vis_contents += GLOB.shared_particles[particle_key][SHARED_PARTICLE_HOLDER_INDEX][1]
play_pretend.vis_contents += GLOB.shared_particles[particle_key][SHARED_PARTICLE_HOLDER_INDEX][1]
return GLOB.shared_particles[particle_key][SHARED_PARTICLE_HOLDER_INDEX][1]
var/list/type_holders = GLOB.shared_particles[particle_key][SHARED_PARTICLE_HOLDER_INDEX]
for (var/obj/effect/abstract/shared_particle_holder/particle_holder as anything in type_holders)
if (particle_holder in vis_contents)
if (particle_holder in play_pretend.vis_contents)
return particle_holder
if (length(type_holders) < pool_size)
var/obj/effect/abstract/shared_particle_holder/new_holder = new(null, particle_type, particle_flags)
type_holders += new_holder
vis_contents += new_holder
play_pretend.vis_contents += new_holder
GLOB.shared_particles[particle_key][SHARED_PARTICLE_USER_NUM_INDEX] += 1
return new_holder
var/obj/effect/abstract/shared_particle_holder/particle_holder = pick(type_holders)
vis_contents += particle_holder
play_pretend.vis_contents += particle_holder
GLOB.shared_particles[particle_key][SHARED_PARTICLE_USER_NUM_INDEX] += 1
return particle_holder
/area/add_shared_particles(particle_type, custom_key = null, particle_flags = NONE, pool_size = 3)
CRASH("add_shared_particles was called on an area [src] ([type]) trying to add [particle_type]! Only turfs and movables support shared particles.")
/* Removes shared particles from object's vis_contents and disposes of it if nothing uses that type/key of particle
* particle_key can be either a type (if no custom_key was passed) or said custom_key
*/
/atom/movable/proc/remove_shared_particles(particle_key, delete_on_empty = TRUE)
/atom/proc/remove_shared_particles(particle_key, delete_on_empty = TRUE)
if (!particle_key)
return
@@ -72,12 +79,13 @@ GLOBAL_LIST_EMPTY(shared_particles)
if (!GLOB.shared_particles[particle_key])
return
var/atom/movable/play_pretend = src
var/list/type_holders = GLOB.shared_particles[particle_key][SHARED_PARTICLE_HOLDER_INDEX]
for (var/obj/effect/abstract/shared_particle_holder/particle_holder as anything in type_holders)
if (!(particle_holder in vis_contents))
if (!(particle_holder in play_pretend.vis_contents))
continue
vis_contents -= particle_holder
play_pretend.vis_contents -= particle_holder
GLOB.shared_particles[particle_key][SHARED_PARTICLE_USER_NUM_INDEX] -= 1
if (delete_on_empty && GLOB.shared_particles[particle_key][SHARED_PARTICLE_USER_NUM_INDEX] <= 0)
@@ -85,5 +93,8 @@ GLOBAL_LIST_EMPTY(shared_particles)
GLOB.shared_particles -= particle_key
return
/area/remove_shared_particles(particle_key, delete_on_empty = TRUE)
CRASH("remove_shared_particles was called on an area [src] ([type]) trying to add [particle_key]! Only turfs and movables support shared particles.")
#undef SHARED_PARTICLE_HOLDER_INDEX
#undef SHARED_PARTICLE_USER_NUM_INDEX