From 1d5287dad0e691dfde715a2c76017d80a1dcd774 Mon Sep 17 00:00:00 2001 From: Lohikar Date: Mon, 30 Jan 2017 16:37:56 -0600 Subject: [PATCH] Adaptive lighting updates (#1679) This PR simplifies the lighting update system (removes update_type) and instead makes the lighting system decide which update method to use based on server load. Instant updates are used during low-load, scheduled during high. Also removes: update_lights_now() (redundant), diff_light() (unused). --- code/ZAS/Fire.dm | 6 +- code/controllers/Processes/lighting.dm | 3 +- code/game/machinery/doors/airlock.dm | 8 +- code/game/machinery/doors/door.dm | 2 +- code/game/objects/items/devices/flashlight.dm | 6 +- code/modules/lighting/lighting_atom.dm | 45 ++++------ code/modules/lighting/lighting_corner.dm | 22 ++--- code/modules/lighting/lighting_source.dm | 90 ++++++++++--------- code/modules/lighting/lighting_turf.dm | 10 +-- 9 files changed, 90 insertions(+), 102 deletions(-) diff --git a/code/ZAS/Fire.dm b/code/ZAS/Fire.dm index ff12697c9db..99174e5ec97 100644 --- a/code/ZAS/Fire.dm +++ b/code/ZAS/Fire.dm @@ -137,13 +137,13 @@ turf/proc/hotspot_expose(exposed_temperature, exposed_volume, soh = 0) if(firelevel > 6) icon_state = "3" - set_light(7, FIRE_LIGHT_3, update_type = UPDATE_NONE) // We set color later in the proc, that should trigger an update. + set_light(7, FIRE_LIGHT_3, no_update = TRUE) // We set color later in the proc, that should trigger an update. else if(firelevel > 2.5) icon_state = "2" - set_light(5, FIRE_LIGHT_2, update_type = UPDATE_NONE) + set_light(5, FIRE_LIGHT_2, no_update = TRUE) else icon_state = "1" - set_light(3, FIRE_LIGHT_1, update_type = UPDATE_NONE) + set_light(3, FIRE_LIGHT_1, no_update = TRUE) for(var/mob/living/L in loc) L.FireBurn(firelevel, air_contents.temperature, air_contents.return_pressure()) //Burn the mobs! diff --git a/code/controllers/Processes/lighting.dm b/code/controllers/Processes/lighting.dm index 5b849a612dd..ff32733424d 100644 --- a/code/controllers/Processes/lighting.dm +++ b/code/controllers/Processes/lighting.dm @@ -26,6 +26,7 @@ /datum/controller/process/lighting/statProcess() ..() + stat(null, "Current server tick usage is [world.tick_usage], threshold is [TICK_LIMIT].") stat(null, "[all_lighting_overlays.len] overlays ([all_lighting_corners.len] corners)") stat(null, "Lights: [lighting_update_lights.len] queued, [curr_lights.len] processing") stat(null, "Corners: [lighting_update_corners.len] queued, [curr_corners.len] processing") @@ -43,7 +44,7 @@ var/datum/light_source/L = curr_lights[curr_lights.len] curr_lights.len-- - if(L.check() || L.destroyed || L.force_update) + if(L.destroyed || L.check() || L.force_update) L.remove_lum() if(!L.destroyed) L.apply_lum() diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 824e8f40eee..d3368bd4fb2 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -565,12 +565,12 @@ About the new airlock wires panel: if(locked && lights && src.arePowerSystemsOn()) icon_state = "door_locked" if (!has_set_boltlight) - set_light(2, 0.75, COLOR_RED_LIGHT, update_type = UPDATE_NONE) + set_light(2, 0.75, COLOR_RED_LIGHT, no_update = TRUE) has_set_boltlight = TRUE else icon_state = "door_closed" if (has_set_boltlight) - set_light(0, update_type = UPDATE_NONE) + set_light(0, no_update = TRUE) has_set_boltlight = FALSE if(p_open || welded) overlays = list() @@ -597,13 +597,13 @@ About the new airlock wires panel: if((stat & BROKEN) && !(stat & NOPOWER)) overlays += image(icon, "sparks_open") if (has_set_boltlight) - set_light(0, update_type = UPDATE_NONE) + set_light(0, no_update = TRUE) has_set_boltlight = FALSE if (src) var/turf/T = get_turf(src) if (T) - T.update_lights_now() + T.reconsider_lights() return /obj/machinery/door/airlock/do_animate(animation) diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 3d4d087ab43..51563dfc162 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -540,7 +540,7 @@ update_heat_protection(turf) air_master.mark_for_update(turf) - T.update_lights_now() + T.reconsider_lights() return 1 diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm index bda57dd9cff..01fa9e447d1 100644 --- a/code/game/objects/items/devices/flashlight.dm +++ b/code/game/objects/items/devices/flashlight.dm @@ -24,10 +24,10 @@ /obj/item/device/flashlight/update_icon() if(on) icon_state = "[initial(icon_state)]-on" - set_light(brightness_on, update_type = UPDATE_NOW) + set_light(brightness_on) else icon_state = "[initial(icon_state)]" - set_light(0, update_type = UPDATE_NOW) + set_light(0) /obj/item/device/flashlight/attack_self(mob/user) if(!isturf(user.loc)) @@ -231,7 +231,7 @@ /obj/item/device/flashlight/slime/New() ..() - set_light(brightness_on, update_type = UPDATE_NOW) + set_light(brightness_on) /obj/item/device/flashlight/slime/update_icon() return diff --git a/code/modules/lighting/lighting_atom.dm b/code/modules/lighting/lighting_atom.dm index 17d2eb7868d..7f973942674 100644 --- a/code/modules/lighting/lighting_atom.dm +++ b/code/modules/lighting/lighting_atom.dm @@ -12,24 +12,8 @@ // Nonesensical value for l_color default, so we can detect if it gets set to null. #define NONSENSICAL_VALUE -99999 -#define SET_LIGHT set_light(l_range,l_power,l_color,uv,update_type);return; -// Same as set_light(), but only does something if there's actually a change in state. -/atom/proc/diff_light(/var/l_range, var/l_power, var/l_color = NONSENSICAL_VALUE, var/uv = NONSENSICAL_VALUE, var/update_type = UPDATE_SCHEDULE) - if (l_range != light_range) - SET_LIGHT - if (l_power && l_power != light_power) - SET_LIGHT - if (l_color != NONSENSICAL_VALUE && l_color != light_color) - SET_LIGHT - if (uv != NONSENSICAL_VALUE) - SET_LIGHT - if (update_type != UPDATE_SCHEDULE) - SET_LIGHT - -#undef SET_LIGHT - // The proc you should always use to set the light of this atom. -/atom/proc/set_light(var/l_range, var/l_power, var/l_color = NONSENSICAL_VALUE, var/uv = NONSENSICAL_VALUE, var/update_type = UPDATE_SCHEDULE) +/atom/proc/set_light(var/l_range, var/l_power, var/l_color = NONSENSICAL_VALUE, var/uv = NONSENSICAL_VALUE, var/no_update = FALSE) lprof_write(src, "atom_setlight") if(l_range > 0 && l_range < MINIMUM_USEFUL_LIGHT_RANGE) @@ -44,28 +28,29 @@ light_color = l_color if (uv != NONSENSICAL_VALUE) - set_uv(uv, update_type = UPDATE_NONE) + set_uv(uv, no_update = TRUE) - switch (update_type) - if (UPDATE_SCHEDULE) - update_light() - if (UPDATE_NOW) - update_light(TRUE) + if (no_update) + return + + update_light() #undef NONSENSICAL_VALUE -/atom/proc/set_uv(var/intensity, var/update_type = UPDATE_SCHEDULE) +/atom/proc/set_uv(var/intensity, var/no_update) if (intensity < 0 || intensity > 255) intensity = min(max(intensity, 255), 0) uv_intensity = intensity - if (update_type != UPDATE_NONE) - update_light(update_type) + if (no_update) + return + + update_light() // Will update the light (duh). // Creates or destroys it if needed, makes it update values, makes sure it's got the correct source turf... -/atom/proc/update_light(var/update_type = UPDATE_SCHEDULE) +/atom/proc/update_light() set waitfor = FALSE if (gcDestroyed) return @@ -83,7 +68,7 @@ . = loc if (light) // Update the light or create it if it does not exist. - light.update(., update_type) + light.update(.) else light = new/datum/light_source(src, .) @@ -143,11 +128,11 @@ if (Obj && OldLoc != src) for (var/datum/light_source/L in Obj.light_sources) // Cycle through the light sources on this atom and tell them to update. - L.source_atom.update_light(update_type = UPDATE_NOW) + L.source_atom.update_light() /atom/Exited(var/atom/movable/Obj, var/atom/newloc) . = ..() if (!newloc && Obj && newloc != src) // Incase the atom is being moved to nullspace, we handle queuing for a lighting update here. for (var/datum/light_source/L in Obj.light_sources) // Cycle through the light sources on this atom and tell them to update. - L.source_atom.update_light(update_type = UPDATE_NOW) + L.source_atom.update_light() diff --git a/code/modules/lighting/lighting_corner.dm b/code/modules/lighting/lighting_corner.dm index 05ba71d9af1..d80c8bda3c2 100644 --- a/code/modules/lighting/lighting_corner.dm +++ b/code/modules/lighting/lighting_corner.dm @@ -90,20 +90,23 @@ active = TRUE // God that was a mess, now to do the rest of the corner code! Hooray! -/datum/lighting_corner/proc/update_lumcount(var/delta_r, var/delta_g, var/delta_b, var/delta_u, var/update_type = UPDATE_SCHEDULE) +/datum/lighting_corner/proc/update_lumcount(var/delta_r, var/delta_g, var/delta_b, var/delta_u, var/now = FALSE) lum_r += delta_r lum_g += delta_g lum_b += delta_b lum_u += delta_u - if (update_type == UPDATE_SCHEDULE && !needs_update) - needs_update = TRUE - update_overlays(update_type) - lighting_update_corners += src - else if (!needs_update) - update_overlays(UPDATE_NOW) + if (needs_update) + return -/datum/lighting_corner/proc/update_overlays(var/update_type = UPDATE_SCHEDULE) + if (!now) + needs_update = TRUE + update_overlays(FALSE) + lighting_update_corners += src + else + update_overlays(TRUE) + +/datum/lighting_corner/proc/update_overlays(var/now = FALSE) // Cache these values a head of time so 4 individual lighting overlays don't all calculate them individually. var/mx = max(lum_r, lum_g, lum_b) // Scale it so 1 is the strongest lum, if it is above 1. @@ -123,9 +126,8 @@ for (var/TT in masters) var/turf/T = TT if (T.lighting_overlay) - if (update_type == UPDATE_NOW) // UPDATE_NONE is meaningless here. + if (now) T.lighting_overlay.update_overlay() - else if (!T.lighting_overlay.needs_update) T.lighting_overlay.needs_update = TRUE lighting_update_overlays += T.lighting_overlay diff --git a/code/modules/lighting/lighting_source.dm b/code/modules/lighting/lighting_source.dm index f3e13daaf77..c130f5806ca 100644 --- a/code/modules/lighting/lighting_source.dm +++ b/code/modules/lighting/lighting_source.dm @@ -33,7 +33,7 @@ var/destroyed // Whether we are destroyed and need to stop emitting light. var/force_update -/datum/light_source/New(var/atom/owner, var/atom/top, var/update_t = UPDATE_SCHEDULE) +/datum/light_source/New(var/atom/owner, var/atom/top) source_atom = owner // Set our new owner. if (!source_atom.light_sources) source_atom.light_sources = list() @@ -57,7 +57,7 @@ effect_str = list() affecting_turfs = list() - update(update_type = update_t) + update() lprof_write(src, "source_new") @@ -73,32 +73,40 @@ if (top_atom && top_atom.light_sources) top_atom.light_sources -= src -/datum/light_source/proc/effect_update_now() - lprof_write(src, "source_updatenow") - if (check() || destroyed || force_update) - remove_lum(TRUE) - if (!destroyed) - apply_lum(update_type = UPDATE_NOW) +// Process the light RIGHT NOW. +#define DO_UPDATE \ + if (destroyed || check() || force_update) { \ + remove_lum(TRUE); \ + if (!destroyed) { \ + apply_lum(TRUE); \ + } \ + } \ + else if (vis_update) { \ + smart_vis_update(TRUE); \ + } \ + vis_update = FALSE; \ + force_update = FALSE; \ + needs_update = FALSE; - else if (vis_update) // We smartly update only tiles that became (in) visible to use. - smart_vis_update(update_type = UPDATE_NOW) - - vis_update = FALSE - force_update = FALSE - needs_update = FALSE - -// Call it dirty, I don't care. -// This is here so there's no performance loss on non-instant updates from the fact that the engine can also do instant updates. -// If you're wondering what's with the "BYOND" argument: BYOND won't let me have a () macro that has no arguments :|. -#define effect_update(BYOND) \ +// Queue an update. +#define QUEUE_UPDATE \ if (!needs_update) \ { \ lighting_update_lights += src; \ needs_update = TRUE; \ } +// Picks either scheduled or instant updates based on current server load. +#define INTELLIGENT_UPDATE \ + if (world.tick_usage > TICK_LIMIT || !ticker) { \ + QUEUE_UPDATE; \ + } \ + else { \ + DO_UPDATE; \ + } + // This proc will cause the light source to update the top atom, and add itself to the update queue. -/datum/light_source/proc/update(var/atom/new_top_atom, var/update_type = UPDATE_SCHEDULE) +/datum/light_source/proc/update(var/atom/new_top_atom) // This top atom is different. if (new_top_atom && new_top_atom != top_atom) if(top_atom != source_atom) // Remove ourselves from the light sources of that top atom. @@ -114,23 +122,20 @@ lprof_write(src, "source_update") - if (update_type == UPDATE_NOW) - effect_update_now() - else if (update_type == UPDATE_SCHEDULE) // I don't know why you would call this with UPDATE_NONE, but hey. - effect_update(null) + INTELLIGENT_UPDATE // Will force an update without checking if it's actually needed. /datum/light_source/proc/force_update() lprof_write(src, "source_forceupdate") force_update = 1 - effect_update(null) + INTELLIGENT_UPDATE // Will cause the light source to recalculate turfs that were removed or added to visibility only. /datum/light_source/proc/vis_update() vis_update = 1 - effect_update(null) + INTELLIGENT_UPDATE // Will check if we actually need to update, and update any variables that may need to be updated. /datum/light_source/proc/check() @@ -188,7 +193,7 @@ // As such this all gets counted as a single line. // The braces and semicolons are there to be able to do this on a single line. -#define APPLY_CORNER(C) \ +#define APPLY_CORNER(C,now) \ . = LUM_FALLOFF(C, source_turf); \ \ . *= light_power; \ @@ -201,11 +206,11 @@ . * applied_lum_g, \ . * applied_lum_b, \ . * applied_lum_u, \ - update_type \ + now \ ); // I don't need to explain what this does, do I? -#define REMOVE_CORNER(C) \ +#define REMOVE_CORNER(C,now) \ . = -effect_str[C]; \ C.update_lumcount \ ( \ @@ -213,13 +218,13 @@ . * applied_lum_g, \ . * applied_lum_b, \ . * applied_lum_u, \ - update_type \ + now \ ); // This is the define used to calculate falloff. #define LUM_FALLOFF(C, T) (1 - CLAMP01(sqrt((C.x - T.x) ** 2 + (C.y - T.y) ** 2 + LIGHTING_HEIGHT) / max(1, light_range))) -/datum/light_source/proc/apply_lum(var/update_type = UPDATE_SCHEDULE) +/datum/light_source/proc/apply_lum(var/now = FALSE) var/static/update_gen = 1 applied = 1 @@ -244,7 +249,7 @@ effect_str[C] = 0 continue - APPLY_CORNER(C) + APPLY_CORNER(C, now) if (!T.affecting_lights) T.affecting_lights = list() @@ -254,7 +259,7 @@ update_gen++ -/datum/light_source/proc/remove_lum(var/update_type = UPDATE_SCHEDULE) +/datum/light_source/proc/remove_lum(var/now = FALSE) applied = FALSE for (var/turf/T in affecting_turfs) @@ -266,20 +271,19 @@ affecting_turfs.Cut() for (var/datum/lighting_corner/C in effect_str) - REMOVE_CORNER(C) + REMOVE_CORNER(C,now) C.affecting -= src effect_str.Cut() -/datum/light_source/proc/recalc_corner(var/datum/lighting_corner/C) - var/update_type = UPDATE_SCHEDULE // for (APPLY|REMOVE)_CORNER. +/datum/light_source/proc/recalc_corner(var/datum/lighting_corner/C, var/now = FALSE) if (effect_str.Find(C)) // Already have one. - REMOVE_CORNER(C) + REMOVE_CORNER(C,now) - APPLY_CORNER(C) + APPLY_CORNER(C,now) -/datum/light_source/proc/smart_vis_update(var/update_type = UPDATE_SCHEDULE) +/datum/light_source/proc/smart_vis_update(var/now = FALSE) var/list/datum/lighting_corner/corners = list() var/list/turf/turfs = list() FOR_DVIEW(var/turf/T, light_range, source_turf, 0) @@ -307,14 +311,16 @@ effect_str[C] = 0 continue - APPLY_CORNER(C) + APPLY_CORNER(C,now) for (var/datum/lighting_corner/C in effect_str - corners) // Old, now gone, corners. - REMOVE_CORNER(C) + REMOVE_CORNER(C,now) C.affecting -= src effect_str -= C -#undef effect_update +#undef QUEUE_UPDATE +#undef DO_UPDATE +#undef INTELLIGENT_UPDATE #undef LUM_FALLOFF #undef REMOVE_CORNER #undef APPLY_CORNER diff --git a/code/modules/lighting/lighting_turf.dm b/code/modules/lighting/lighting_turf.dm index 0d2887699be..83376867b6e 100644 --- a/code/modules/lighting/lighting_turf.dm +++ b/code/modules/lighting/lighting_turf.dm @@ -21,12 +21,6 @@ for (var/datum/light_source/L in affecting_lights) L.vis_update() -// Avoid calling this if you can, bypasses the lighting scheduler (potentially creating lag). -/turf/proc/update_lights_now() - lprof_write(src, "turf_updatenow") - for (var/datum/light_source/L in affecting_lights) - L.update(update_type = UPDATE_NOW) - /turf/proc/lighting_clear_overlay() if (lighting_overlay) returnToPool(lighting_overlay) @@ -50,7 +44,7 @@ if (!C.active) // We would activate the corner, calculate the lighting for it. for (var/L in C.affecting) var/datum/light_source/S = L - S.recalc_corner(C) + S.recalc_corner(C, TRUE) C.active = TRUE @@ -71,7 +65,7 @@ return CLAMP01(totallums) -// Gets the current UV illumination of the turf. Always 100% for space. +// Gets the current UV illumination of the turf. Always 100% for space & other static-lit tiles. /turf/proc/get_uv_lumcount(var/minlum = 0, var/maxlum = 1) if (!lighting_overlay) return 1