From 1dc24fdd864cc2c8b02ebff0d99dd26cb31375de Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Fri, 18 Sep 2020 10:53:44 +0200 Subject: [PATCH] [MIRROR] Slightly optimize shroom spreading code (#867) * Slightly optimize shroom spreading code (#53652) There were some recent lag events on the servers caused by the timer subsystem jamming up with glowshroom timers. I whined about this but nobody did anything so (Thanos "Fine... I'll do it myself" GIF) There are a few changes here, first we collect the potential turfs for the view call in a single pass, not once per yield run. Second we don't run a second view(1) on every single potential turf, instead we try to randomly pick a turf 3 times during yield phase and check the view then. If we fail to find a potential location then we bail out of that yield phase. This is a tradeoff between processing time spent finding locations and the chance that the glowshroom fails to spread. Finally, we have maximum limit on how many times a glowshroom fails to spread, if it fails to spread at least 5 times over any iteration, it stops processing for spread completely. As a bonus, the timers have been made unique, so we don't accidentally generate multiple timers for a single shroom, other than the two it already needs. This code would benefit from being a separate subsystem and grouping, generations of the plants together as a single ticking entity and just spreading from selected edge plants. However I don't particularly feel like plumbing that together, so this will suit for now. * Slightly optimize shroom spreading code Co-authored-by: oranges --- code/game/objects/effects/glowshroom.dm | 71 +++++++++++++++++-------- 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/code/game/objects/effects/glowshroom.dm b/code/game/objects/effects/glowshroom.dm index 87bcb854396..ff4ef2489c8 100644 --- a/code/game/objects/effects/glowshroom.dm +++ b/code/game/objects/effects/glowshroom.dm @@ -21,6 +21,9 @@ var/spreadIntoAdjacentChance = 75 /// Internal seed of the glowshroom, stats are stored here var/obj/item/seeds/myseed = /obj/item/seeds/glowshroom + + /// If we fail to spread this many times we stop trying to spread + var/max_failed_spreads = 5 /// Turfs where the glowshroom cannot spread to var/static/list/blacklisted_glowshroom_turfs = typecacheof(list( /turf/open/lava, @@ -92,40 +95,60 @@ else //if on the floor, glowshroom on-floor sprite icon_state = base_icon_state - addtimer(CALLBACK(src, .proc/Spread), delay_spread) - addtimer(CALLBACK(src, .proc/Decay), delay_decay, FALSE) // Start decaying the plant + + addtimer(CALLBACK(src, .proc/Spread), delay_spread, TIMER_UNIQUE|TIMER_NO_HASH_WAIT) + addtimer(CALLBACK(src, .proc/Decay), delay_decay, TIMER_UNIQUE|TIMER_NO_HASH_WAIT) /** * Causes glowshroom spreading across the floor/walls. */ /obj/structure/glowshroom/proc/Spread() + + //We could be deleted at any point and the timers might not be cleaned up + if(QDELETED(src)) + return + var/turf/ownturf = get_turf(src) var/shrooms_planted = 0 + var/list/possibleLocs = list() + //Lets collect a list of possible viewable turfs BEFORE we iterate for yield so we don't call view multiple + //times when there's no real chance of the viewable range changing, really you could do this once on item + //spawn and most people probably would not notice. + for(var/turf/open/floor/earth in view(3,src)) + if(is_type_in_typecache(earth, blacklisted_glowshroom_turfs)) + continue + if(!ownturf.CanAtmosPass(earth)) + continue + possibleLocs += earth + + //Lets not even try to spawn again if somehow we have ZERO possible locations + if(!possibleLocs.len) + return + for(var/i in 1 to myseed.yield) var/chance_stats = ((myseed.potency + myseed.endurance * 2) * 0.2) // Chance of generating a new mushroom based on stats var/chance_generation = (100 / (generation * generation)) // This formula gives you diminishing returns based on generation. 100% with 1st gen, decreasing to 25%, 11%, 6, 4, 2... - if(prob(max(chance_stats, chance_generation))) // Whatever is the higher chance we use it - var/list/possibleLocs = list() - var/spreadsIntoAdjacent = FALSE - if(prob(spreadIntoAdjacentChance)) - spreadsIntoAdjacent = TRUE + // Whatever is the higher chance we use it (this is really stupid as the diminishing returns are effectively pointless???) + if(prob(max(chance_stats, chance_generation))) + var/spreadsIntoAdjacent = prob(spreadIntoAdjacentChance) + var/turf/newLoc = null - for(var/turf/open/floor/earth in view(3,src)) - if(is_type_in_typecache(earth, blacklisted_glowshroom_turfs)) - continue - if(!ownturf.CanAtmosPass(earth)) - continue - if(spreadsIntoAdjacent || !locate(/obj/structure/glowshroom) in view(1,earth)) - possibleLocs += earth - CHECK_TICK + //Try three random locations to spawn before giving up tradeoff + //between running view(1, earth) on every single collected possibleLoc + //and failing to spread if we get 3 bad picks, which should only be a problem + //if there's a lot of glow shroom clustered about + for(var/Potato in 1 to 3) + var/turf/possibleLoc = pick(possibleLocs) + if(spreadsIntoAdjacent || !locate(/obj/structure/glowshroom) in view(1,possibleLoc)) + newLoc = possibleLoc + break - if(!possibleLocs.len) + //We failed to find any location, skip trying to yield + if(newLoc == null) break - var/turf/newLoc = pick(possibleLocs) - var/shroomCount = 0 //hacky var/placeCount = 1 for(var/obj/structure/glowshroom/shroom in newLoc) @@ -144,10 +167,14 @@ child.generation = generation + 1 shrooms_planted++ - CHECK_TICK - if(shrooms_planted <= myseed.yield) //if we didn't get all possible shrooms planted, try again later + if(!shrooms_planted) + max_failed_spreads-- + + //if we didn't get all possible shrooms planted or we haven't failed to spread at least 5 times then try to spread again later + if( (shrooms_planted <= myseed.yield) && (max_failed_spreads >= 0) ) myseed.adjust_yield(-shrooms_planted) - addtimer(CALLBACK(src, .proc/Spread), delay_spread) + //Lets make this a unique hash + addtimer(CALLBACK(src, .proc/Spread), delay_spread, TIMER_UNIQUE|TIMER_NO_HASH_WAIT) /obj/structure/glowshroom/proc/CalcDir(turf/location = loc) var/direction = 16 @@ -194,7 +221,7 @@ else // Timed decay myseed.endurance -= 1 if (myseed.endurance > 0) - addtimer(CALLBACK(src, .proc/Decay), delay_decay, FALSE) // Recall decay timer + addtimer(CALLBACK(src, .proc/Decay), delay_decay, TIMER_UNIQUE|TIMER_NO_HASH_WAIT) // Recall decay timer return if (myseed.endurance < 1) // Plant is gone qdel(src)