Refactors how overlays handle the compile step (#38002)

Robustin says:

The very heart of our overlay code, a single line that basically boiled
down to overlays = new_overlays, was the cause of so much overlay lag.
Human overlay code was by far the biggest culprit. Most objects have 0-2
overlays but humans are marching around with 20+ most of the time and
the current system was spending a LOT of effort comparing 20+ image with
20+ other images and then apparently rendering them all anyway. Human
overlays are at least 10x the cost of any other overlay process and on a
busy server the overlay compiling was 2x the cost of any other system.

I compared the cost of overlay changes by picking up/dropping a PDA in
the dorms 250 times, with a 50% chance to use our current overlay
compiler and a 50% chance to use a "direct addition/removal (+=, -=)
approach:
CURRENT         1120ms  133     (avg:8.4210529327392578)
SCRAPS  6ms     112     (avg:0.0535714291036129)

Now this PR makes our whole overlay subsystem use that approach for
overlay compiling and the early results look incredible. The best part
is this is just the START of improvements. Humans benefits the most
because their icon system was already designed for small, incremental
overlay updates. By moving other code from "Cut everything, then put it
all back" to only updating the necessary overlays (cough, APC's), we can
see similar improvements.

oranges says:
I've picked up this PR because robustin vanished, but I do see the value
in the approach, only things I added were the checks for the overlay
list len changing

I also retabbed the entire file because I am a brainlet and did it
without thinking
This commit is contained in:
oranges
2018-05-31 12:39:28 +12:00
committed by yogstation13-bot
parent 64d9cb643a
commit 2c0b76f0b8
8 changed files with 61 additions and 64 deletions

View File

@@ -124,21 +124,19 @@
#define COMPILE_OVERLAYS(A)\ #define COMPILE_OVERLAYS(A)\
if (TRUE) {\ if (TRUE) {\
var/list/oo = A.our_overlays;\ var/list/ad = A.add_overlays;\
var/list/rm = A.remove_overlays;\
var/list/po = A.priority_overlays;\ var/list/po = A.priority_overlays;\
if(LAZYLEN(rm)){\
A.overlays -= rm;\
rm.Cut();\
}\
if(LAZYLEN(ad)){\
A.overlays |= ad;\
ad.Cut();\
}\
if(LAZYLEN(po)){\ if(LAZYLEN(po)){\
if(LAZYLEN(oo)){\ A.overlays |= po;\
A.overlays = oo + po;\
}\
else{\
A.overlays = po;\
}\
}\
else if(LAZYLEN(oo)){\
A.overlays = oo;\
}\
else{\
A.overlays.Cut();\
}\ }\
A.flags_1 &= ~OVERLAY_QUEUED_1;\ A.flags_1 &= ~OVERLAY_QUEUED_1;\
} }

View File

@@ -115,38 +115,43 @@ SUBSYSTEM_DEF(overlays)
#define NOT_QUEUED_ALREADY (!(flags_1 & OVERLAY_QUEUED_1)) #define NOT_QUEUED_ALREADY (!(flags_1 & OVERLAY_QUEUED_1))
#define QUEUE_FOR_COMPILE flags_1 |= OVERLAY_QUEUED_1; SSoverlays.queue += src; #define QUEUE_FOR_COMPILE flags_1 |= OVERLAY_QUEUED_1; SSoverlays.queue += src;
/atom/proc/cut_overlays(priority = FALSE) /atom/proc/cut_overlays(priority = FALSE)
var/list/cached_overlays = our_overlays LAZYINITLIST(priority_overlays)
var/list/cached_priority = priority_overlays LAZYINITLIST(remove_overlays)
LAZYINITLIST(add_overlays)
remove_overlays = overlays.Copy()
add_overlays.Cut()
var/need_compile = FALSE if(priority)
priority_overlays.Cut()
if(LAZYLEN(cached_overlays)) //don't queue empty lists, don't cut priority overlays //If not already queued for work and there are overlays to remove
cached_overlays.Cut() //clear regular overlays if(NOT_QUEUED_ALREADY && remove_overlays.len)
need_compile = TRUE
if(priority && LAZYLEN(cached_priority))
cached_priority.Cut()
need_compile = TRUE
if(NOT_QUEUED_ALREADY && need_compile)
QUEUE_FOR_COMPILE QUEUE_FOR_COMPILE
/atom/proc/cut_overlay(list/overlays, priority) /atom/proc/cut_overlay(list/overlays, priority)
if(!overlays) if(!overlays)
return return
overlays = build_appearance_list(overlays) overlays = build_appearance_list(overlays)
LAZYINITLIST(add_overlays) //always initialized after this point
LAZYINITLIST(priority_overlays)
LAZYINITLIST(remove_overlays)
var/a_len = add_overlays.len
var/r_len = remove_overlays.len
var/p_len = priority_overlays.len
remove_overlays += overlays
add_overlays -= overlays
var/list/cached_overlays = our_overlays //sanic
var/list/cached_priority = priority_overlays
var/init_o_len = LAZYLEN(cached_overlays)
var/init_p_len = LAZYLEN(cached_priority) //starter pokemon
LAZYREMOVE(cached_overlays, overlays)
if(priority) if(priority)
var/list/cached_priority = priority_overlays
LAZYREMOVE(cached_priority, overlays) LAZYREMOVE(cached_priority, overlays)
if(NOT_QUEUED_ALREADY && ((init_o_len != LAZYLEN(cached_overlays)) || (init_p_len != LAZYLEN(cached_priority)))) var/fa_len = add_overlays.len
var/fr_len = remove_overlays.len
var/fp_len = priority_overlays.len
//If not already queued and there is work to be done
if(NOT_QUEUED_ALREADY && (fa_len != a_len || fr_len != r_len || fp_len != p_len))
QUEUE_FOR_COMPILE QUEUE_FOR_COMPILE
/atom/proc/add_overlay(list/overlays, priority = FALSE) /atom/proc/add_overlay(list/overlays, priority = FALSE)
@@ -155,24 +160,21 @@ SUBSYSTEM_DEF(overlays)
overlays = build_appearance_list(overlays) overlays = build_appearance_list(overlays)
LAZYINITLIST(our_overlays) //always initialized after this point LAZYINITLIST(add_overlays) //always initialized after this point
LAZYINITLIST(priority_overlays) LAZYINITLIST(priority_overlays)
var/a_len = add_overlays.len
var/list/cached_overlays = our_overlays //sanic var/p_len = priority_overlays.len
var/list/cached_priority = priority_overlays
var/init_o_len = cached_overlays.len
var/init_p_len = cached_priority.len //starter pokemon
var/need_compile
if(priority) if(priority)
cached_priority += overlays //or in the image. Can we use [image] = image? priority_overlays += overlays //or in the image. Can we use [image] = image?
need_compile = init_p_len != cached_priority.len var/fp_len = priority_overlays.len
if(NOT_QUEUED_ALREADY && fp_len != p_len)
QUEUE_FOR_COMPILE
else else
cached_overlays += overlays add_overlays += overlays
need_compile = init_o_len != cached_overlays.len var/fa_len = add_overlays.len
if(NOT_QUEUED_ALREADY && fa_len != a_len)
if(NOT_QUEUED_ALREADY && need_compile) //have we caught more pokemon? QUEUE_FOR_COMPILE
QUEUE_FOR_COMPILE
/atom/proc/copy_overlays(atom/other, cut_old) //copys our_overlays from another atom /atom/proc/copy_overlays(atom/other, cut_old) //copys our_overlays from another atom
if(!other) if(!other)
@@ -180,12 +182,11 @@ SUBSYSTEM_DEF(overlays)
cut_overlays() cut_overlays()
return return
var/list/cached_other = other.our_overlays var/list/cached_other = other.overlays.Copy()
if(cached_other) if(cached_other)
if(cut_old || !LAZYLEN(our_overlays)) if(cut_old || !LAZYLEN(overlays))
our_overlays = cached_other.Copy() remove_overlays = overlays
else add_overlays = cached_other
our_overlays |= cached_other
if(NOT_QUEUED_ALREADY) if(NOT_QUEUED_ALREADY)
QUEUE_FOR_COMPILE QUEUE_FOR_COMPILE
else if(cut_old) else if(cut_old)
@@ -196,7 +197,7 @@ SUBSYSTEM_DEF(overlays)
//TODO: Better solution for these? //TODO: Better solution for these?
/image/proc/add_overlay(x) /image/proc/add_overlay(x)
overlays += x overlays |= x
/image/proc/cut_overlay(x) /image/proc/cut_overlay(x)
overlays -= x overlays -= x
@@ -210,10 +211,10 @@ SUBSYSTEM_DEF(overlays)
cut_overlays() cut_overlays()
return return
var/list/cached_other = other.our_overlays var/list/cached_other = other.overlays.Copy()
if(cached_other) if(cached_other)
if(cut_old || !overlays.len) if(cut_old || !overlays.len)
overlays = cached_other.Copy() overlays = cached_other
else else
overlays |= cached_other overlays |= cached_other
else if(cut_old) else if(cut_old)

View File

@@ -19,8 +19,9 @@
var/list/atom_colours //used to store the different colors on an atom var/list/atom_colours //used to store the different colors on an atom
//its inherent color, the colored paint applied on it, special color effect etc... //its inherent color, the colored paint applied on it, special color effect etc...
var/list/our_overlays //our local copy of (non-priority) overlays without byond magic. Use procs in SSoverlays to manipulate
var/list/priority_overlays //overlays that should remain on top and not normally removed when using cut_overlay functions, like c4. var/list/priority_overlays //overlays that should remain on top and not normally removed when using cut_overlay functions, like c4.
var/list/remove_overlays // a very temporary list of overlays to remove
var/list/add_overlays // a very temporary list of overlays to add
var/datum/proximity_monitor/proximity_monitor var/datum/proximity_monitor/proximity_monitor
var/buckle_message_cooldown = 0 var/buckle_message_cooldown = 0

View File

@@ -127,7 +127,7 @@
// Deny placing posters on currently-diagonal walls, although the wall may change in the future. // Deny placing posters on currently-diagonal walls, although the wall may change in the future.
if (smooth & SMOOTH_DIAGONAL) if (smooth & SMOOTH_DIAGONAL)
for (var/O in our_overlays) for (var/O in overlays)
var/image/I = O var/image/I = O
if (copytext(I.icon_state, 1, 3) == "d-") if (copytext(I.icon_state, 1, 3) == "d-")
return return

View File

@@ -118,8 +118,8 @@
if(INGREDIENTS_STACKPLUSTOP) if(INGREDIENTS_STACKPLUSTOP)
filling.pixel_x = rand(-1,1) filling.pixel_x = rand(-1,1)
filling.pixel_y = 2 * ingredients.len - 1 filling.pixel_y = 2 * ingredients.len - 1
if(our_overlays) if(overlays)
our_overlays.Cut(ingredients.len) //???, add overlay calls later in this proc will queue the compile if necessary overlays -= overlays[ingredients.len]
var/mutable_appearance/TOP = mutable_appearance(icon, "[icon_state]_top") var/mutable_appearance/TOP = mutable_appearance(icon, "[icon_state]_top")
TOP.pixel_y = 2 * ingredients.len + 3 TOP.pixel_y = 2 * ingredients.len + 3
add_overlay(filling) add_overlay(filling)

View File

@@ -406,8 +406,8 @@
name = "stack of pancakes" name = "stack of pancakes"
else else
name = initial(name) name = initial(name)
if(contents.len < LAZYLEN(our_overlays)) if(contents.len < LAZYLEN(overlays))
cut_overlay(our_overlays[our_overlays.len]) overlays-=overlays[overlays.len]
/obj/item/reagent_containers/food/snacks/pancakes/examine(mob/user) /obj/item/reagent_containers/food/snacks/pancakes/examine(mob/user)
var/ingredients_listed = "" var/ingredients_listed = ""

View File

@@ -297,7 +297,6 @@ GLOBAL_LIST_EMPTY(roundstart_races)
/datum/species/proc/handle_hair(mob/living/carbon/human/H, forced_colour) /datum/species/proc/handle_hair(mob/living/carbon/human/H, forced_colour)
H.remove_overlay(HAIR_LAYER) H.remove_overlay(HAIR_LAYER)
var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD) var/obj/item/bodypart/head/HD = H.get_bodypart(BODY_ZONE_HEAD)
if(!HD) //Decapitated if(!HD) //Decapitated
return return

View File

@@ -567,9 +567,7 @@ generate/load female uniform sprites matching all previously decided variables
if(4) //even = right hands if(4) //even = right hands
return list("x" = 0, "y" = 16) return list("x" = 0, "y" = 16)
else //No offsets or Unwritten number of hands else //No offsets or Unwritten number of hands
return list("x" = 0, "y" = 0) return list("x" = 0, "y" = 0)//Handle held offsets
//produces a key based on the human's limbs //produces a key based on the human's limbs
/mob/living/carbon/human/generate_icon_render_key() /mob/living/carbon/human/generate_icon_render_key()