From cf6dbf06aa5da182d224d50127d8922450904a0c Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Sun, 6 Apr 2025 00:24:50 +0200 Subject: [PATCH] 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 :cl: code: Converted hot springs to a new shared particles system for better clientside performance. /:cl: --- code/game/objects/effects/particle_holder.dm | 46 +------------------ .../objects/effects/shared_particle_holder.dm | 29 ++++++++---- code/game/turfs/open/water.dm | 14 +++--- 3 files changed, 27 insertions(+), 62 deletions(-) diff --git a/code/game/objects/effects/particle_holder.dm b/code/game/objects/effects/particle_holder.dm index 46048c136d1..374b8a83f2e 100644 --- a/code/game/objects/effects/particle_holder.dm +++ b/code/game/objects/effects/particle_holder.dm @@ -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()") diff --git a/code/game/objects/effects/shared_particle_holder.dm b/code/game/objects/effects/shared_particle_holder.dm index c4b3a21af3c..01c3955a377 100644 --- a/code/game/objects/effects/shared_particle_holder.dm +++ b/code/game/objects/effects/shared_particle_holder.dm @@ -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 diff --git a/code/game/turfs/open/water.dm b/code/game/turfs/open/water.dm index ec990e51a6d..9e1f25ac7cb 100644 --- a/code/game/turfs/open/water.dm +++ b/code/game/turfs/open/water.dm @@ -109,8 +109,6 @@ immerse_overlay_color = "#A0E2DE" immerse_overlay_alpha = 190 fishing_datum = /datum/fish_source/hot_spring - /// Holder for the steam particles - var/obj/effect/abstract/particle_holder/cached/particle_effect /turf/open/water/hot_spring/Initialize(mapload) . = ..() @@ -122,18 +120,18 @@ AddElement(/datum/element/immerse, icon, icon_state, "immerse", immerse_overlay_color, alpha = immerse_overlay_alpha) immerse_added = TRUE icon_state = "pool_[rand(1, 4)]" - particle_effect = new(src, /particles/hotspring_steam, 4) - //render the steam over mobs and objects on the game plane - particle_effect.vis_flags &= ~VIS_INHERIT_PLANE - //And be unaffected by ambient occlusions, which would render the steam grey - particle_effect.plane = MUTATE_PLANE(MASSIVE_OBJ_PLANE, src) + var/obj/effect/abstract/shared_particle_holder/holder = add_shared_particles(/particles/hotspring_steam, "hot_springs_[GET_TURF_PLANE_OFFSET(src)]", pool_size = 4) + // Render the steam over mobs and objects on the game plane + holder.vis_flags &= ~VIS_INHERIT_PLANE + // And be unaffected by ambient occlusions, which would render the steam grey + holder.plane = MUTATE_PLANE(MASSIVE_OBJ_PLANE, src) add_filter("hot_spring_waves", 1, wave_filter(y = 1, size = 1, offset = 0, flags = WAVE_BOUNDED)) var/filter = get_filter("hot_spring_waves") animate(filter, offset = 1, time = 3 SECONDS, loop = -1, easing = SINE_EASING|EASE_IN|EASE_OUT) animate(offset = 0, time = 3 SECONDS, easing = SINE_EASING|EASE_IN|EASE_OUT) /turf/open/water/hot_spring/Destroy() - QDEL_NULL(particle_effect) + remove_shared_particles("hot_springs_[GET_TURF_PLANE_OFFSET(src)]") remove_filter("hot_spring_waves") for(var/atom/movable/movable as anything in contents) exit_hot_spring(movable)