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).
This commit is contained in:
Lohikar
2017-01-31 00:37:56 +02:00
committed by skull132
parent 51f9c63589
commit 1d5287dad0
9 changed files with 90 additions and 102 deletions
+3 -3
View File
@@ -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!
+2 -1
View File
@@ -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()
+4 -4
View File
@@ -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)
+1 -1
View File
@@ -540,7 +540,7 @@
update_heat_protection(turf)
air_master.mark_for_update(turf)
T.update_lights_now()
T.reconsider_lights()
return 1
@@ -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
+15 -30
View File
@@ -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()
+12 -10
View File
@@ -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
+48 -42
View File
@@ -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
+2 -8
View File
@@ -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