mirror of
https://github.com/yogstation13/Yogstation.git
synced 2025-02-26 09:04:50 +00:00
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
222 lines
5.6 KiB
Plaintext
222 lines
5.6 KiB
Plaintext
SUBSYSTEM_DEF(overlays)
|
|
name = "Overlay"
|
|
flags = SS_TICKER
|
|
wait = 1
|
|
priority = FIRE_PRIORITY_OVERLAYS
|
|
init_order = INIT_ORDER_OVERLAY
|
|
|
|
var/list/queue
|
|
var/list/stats
|
|
var/list/overlay_icon_state_caches
|
|
var/list/overlay_icon_cache
|
|
|
|
/datum/controller/subsystem/overlays/PreInit()
|
|
overlay_icon_state_caches = list()
|
|
overlay_icon_cache = list()
|
|
queue = list()
|
|
stats = list()
|
|
|
|
/datum/controller/subsystem/overlays/Initialize()
|
|
initialized = TRUE
|
|
fire(mc_check = FALSE)
|
|
..()
|
|
|
|
|
|
/datum/controller/subsystem/overlays/stat_entry()
|
|
..("Ov:[length(queue)]")
|
|
|
|
|
|
/datum/controller/subsystem/overlays/Shutdown()
|
|
text2file(render_stats(stats), "[GLOB.log_directory]/overlay.log")
|
|
|
|
|
|
/datum/controller/subsystem/overlays/Recover()
|
|
overlay_icon_state_caches = SSoverlays.overlay_icon_state_caches
|
|
overlay_icon_cache = SSoverlays.overlay_icon_cache
|
|
queue = SSoverlays.queue
|
|
|
|
|
|
/datum/controller/subsystem/overlays/fire(resumed = FALSE, mc_check = TRUE)
|
|
var/list/queue = src.queue
|
|
var/static/count = 0
|
|
if (count)
|
|
var/c = count
|
|
count = 0 //so if we runtime on the Cut, we don't try again.
|
|
queue.Cut(1,c+1)
|
|
|
|
for (var/thing in queue)
|
|
count++
|
|
if(thing)
|
|
STAT_START_STOPWATCH
|
|
var/atom/A = thing
|
|
COMPILE_OVERLAYS(A)
|
|
STAT_STOP_STOPWATCH
|
|
STAT_LOG_ENTRY(stats, A.type)
|
|
if(mc_check)
|
|
if(MC_TICK_CHECK)
|
|
break
|
|
else
|
|
CHECK_TICK
|
|
|
|
if (count)
|
|
queue.Cut(1,count+1)
|
|
count = 0
|
|
|
|
/proc/iconstate2appearance(icon, iconstate)
|
|
var/static/image/stringbro = new()
|
|
var/list/icon_states_cache = SSoverlays.overlay_icon_state_caches
|
|
var/list/cached_icon = icon_states_cache[icon]
|
|
if (cached_icon)
|
|
var/cached_appearance = cached_icon["[iconstate]"]
|
|
if (cached_appearance)
|
|
return cached_appearance
|
|
stringbro.icon = icon
|
|
stringbro.icon_state = iconstate
|
|
if (!cached_icon) //not using the macro to save an associated lookup
|
|
cached_icon = list()
|
|
icon_states_cache[icon] = cached_icon
|
|
var/cached_appearance = stringbro.appearance
|
|
cached_icon["[iconstate]"] = cached_appearance
|
|
return cached_appearance
|
|
|
|
/proc/icon2appearance(icon)
|
|
var/static/image/iconbro = new()
|
|
var/list/icon_cache = SSoverlays.overlay_icon_cache
|
|
. = icon_cache[icon]
|
|
if (!.)
|
|
iconbro.icon = icon
|
|
. = iconbro.appearance
|
|
icon_cache[icon] = .
|
|
|
|
/atom/proc/build_appearance_list(old_overlays)
|
|
var/static/image/appearance_bro = new()
|
|
var/list/new_overlays = list()
|
|
if (!islist(old_overlays))
|
|
old_overlays = list(old_overlays)
|
|
for (var/overlay in old_overlays)
|
|
if(!overlay)
|
|
continue
|
|
if (istext(overlay))
|
|
new_overlays += iconstate2appearance(icon, overlay)
|
|
else if(isicon(overlay))
|
|
new_overlays += icon2appearance(overlay)
|
|
else
|
|
if(isloc(overlay))
|
|
var/atom/A = overlay
|
|
if (A.flags_1 & OVERLAY_QUEUED_1)
|
|
COMPILE_OVERLAYS(A)
|
|
appearance_bro.appearance = overlay //this works for images and atoms too!
|
|
if(!ispath(overlay))
|
|
var/image/I = overlay
|
|
appearance_bro.dir = I.dir
|
|
new_overlays += appearance_bro.appearance
|
|
return new_overlays
|
|
|
|
#define NOT_QUEUED_ALREADY (!(flags_1 & OVERLAY_QUEUED_1))
|
|
#define QUEUE_FOR_COMPILE flags_1 |= OVERLAY_QUEUED_1; SSoverlays.queue += src;
|
|
/atom/proc/cut_overlays(priority = FALSE)
|
|
LAZYINITLIST(priority_overlays)
|
|
LAZYINITLIST(remove_overlays)
|
|
LAZYINITLIST(add_overlays)
|
|
remove_overlays = overlays.Copy()
|
|
add_overlays.Cut()
|
|
|
|
if(priority)
|
|
priority_overlays.Cut()
|
|
|
|
//If not already queued for work and there are overlays to remove
|
|
if(NOT_QUEUED_ALREADY && remove_overlays.len)
|
|
QUEUE_FOR_COMPILE
|
|
|
|
/atom/proc/cut_overlay(list/overlays, priority)
|
|
if(!overlays)
|
|
return
|
|
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
|
|
|
|
|
|
if(priority)
|
|
var/list/cached_priority = priority_overlays
|
|
LAZYREMOVE(cached_priority, overlays)
|
|
|
|
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
|
|
|
|
/atom/proc/add_overlay(list/overlays, priority = FALSE)
|
|
if(!overlays)
|
|
return
|
|
|
|
overlays = build_appearance_list(overlays)
|
|
|
|
LAZYINITLIST(add_overlays) //always initialized after this point
|
|
LAZYINITLIST(priority_overlays)
|
|
var/a_len = add_overlays.len
|
|
var/p_len = priority_overlays.len
|
|
|
|
if(priority)
|
|
priority_overlays += overlays //or in the image. Can we use [image] = image?
|
|
var/fp_len = priority_overlays.len
|
|
if(NOT_QUEUED_ALREADY && fp_len != p_len)
|
|
QUEUE_FOR_COMPILE
|
|
else
|
|
add_overlays += overlays
|
|
var/fa_len = add_overlays.len
|
|
if(NOT_QUEUED_ALREADY && fa_len != a_len)
|
|
QUEUE_FOR_COMPILE
|
|
|
|
/atom/proc/copy_overlays(atom/other, cut_old) //copys our_overlays from another atom
|
|
if(!other)
|
|
if(cut_old)
|
|
cut_overlays()
|
|
return
|
|
|
|
var/list/cached_other = other.overlays.Copy()
|
|
if(cached_other)
|
|
if(cut_old || !LAZYLEN(overlays))
|
|
remove_overlays = overlays
|
|
add_overlays = cached_other
|
|
if(NOT_QUEUED_ALREADY)
|
|
QUEUE_FOR_COMPILE
|
|
else if(cut_old)
|
|
cut_overlays()
|
|
|
|
#undef NOT_QUEUED_ALREADY
|
|
#undef QUEUE_FOR_COMPILE
|
|
|
|
//TODO: Better solution for these?
|
|
/image/proc/add_overlay(x)
|
|
overlays |= x
|
|
|
|
/image/proc/cut_overlay(x)
|
|
overlays -= x
|
|
|
|
/image/proc/cut_overlays(x)
|
|
overlays.Cut()
|
|
|
|
/image/proc/copy_overlays(atom/other, cut_old)
|
|
if(!other)
|
|
if(cut_old)
|
|
cut_overlays()
|
|
return
|
|
|
|
var/list/cached_other = other.overlays.Copy()
|
|
if(cached_other)
|
|
if(cut_old || !overlays.len)
|
|
overlays = cached_other
|
|
else
|
|
overlays |= cached_other
|
|
else if(cut_old)
|
|
cut_overlays()
|