From e5a2b0f16e2bb47b0ed60e487e3c6b2dd32f81dc Mon Sep 17 00:00:00 2001 From: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> Date: Mon, 26 Sep 2022 13:56:35 -0700 Subject: [PATCH] Micros the lighting subsystem (Saves a second of init) (#69838) About The Pull Request Micros lighting objects, and their creation We save a good bit of time by not walking space turfs adjacent to new objects. We also save some time with micros in the actual underlay update logic. I swear dude we spend like 0.8 seconds of init applying the underlay. I want threaded maptick already Micros lighting sources, and corner creation A: Corners were being passed just A turf, and then expected to generatecorners based on that. This is pointless. It is better to instead pass in the coords of the bottom left turf, and then build in a circle. This saves like 0.3 seconds B: We use so many damn datum vars in corner application that we just do not need to. This resolves that, since it pissed me off. It's pointless. Lets cache em instead There's some misc datum var caching going on here too. Lemme see... Oh and a bit of shortcutting for a for loop, since it was a tad expensive on its own. Also I removed the turfs list, because it does fucking nothing. Why is this still here. All my little optimizations save about 1 second of init I think Not great, but not bad, and plus actual lighting work is faster now too Why It's Good For The Game Speed --- code/__DEFINES/stat_tracking.dm | 8 +- code/controllers/subsystem/lighting.dm | 2 +- code/datums/mutable_appearance.dm | 8 +- code/modules/lighting/lighting_corner.dm | 74 ++++++------- code/modules/lighting/lighting_object.dm | 53 ++++----- code/modules/lighting/lighting_setup.dm | 1 + code/modules/lighting/lighting_source.dm | 104 +++++++++++++----- code/modules/lighting/lighting_turf.dm | 14 --- .../mapping/space_management/space_level.dm | 4 + 9 files changed, 146 insertions(+), 122 deletions(-) diff --git a/code/__DEFINES/stat_tracking.dm b/code/__DEFINES/stat_tracking.dm index 3936c5451ea..00b2d2c7b18 100644 --- a/code/__DEFINES/stat_tracking.dm +++ b/code/__DEFINES/stat_tracking.dm @@ -36,10 +36,4 @@ } while(FALSE); \ usage = TICK_USAGE; -#define SET_COST_LINE(...) \ - do { \ - var/cost = TICK_USAGE; \ - _costs["[__LINE__ ]"] += TICK_DELTA_TO_MS(cost - usage); \ - _counting["[__LINE__ ]"] += 1; \ - usage = TICK_USAGE; \ - } while(FALSE) +#define SET_COST_LINE(...) SET_COST("[__LINE__]") diff --git a/code/controllers/subsystem/lighting.dm b/code/controllers/subsystem/lighting.dm index 1c4884b3b06..f79136f678b 100644 --- a/code/controllers/subsystem/lighting.dm +++ b/code/controllers/subsystem/lighting.dm @@ -29,13 +29,13 @@ SUBSYSTEM_DEF(lighting) MC_SPLIT_TICK_INIT(3) if(!init_tick_checks) MC_SPLIT_TICK + var/list/queue = sources_queue var/i = 0 for (i in 1 to length(queue)) var/datum/light_source/L = queue[i] L.update_corners() - L.needs_update = LIGHTING_NO_UPDATE if(init_tick_checks) diff --git a/code/datums/mutable_appearance.dm b/code/datums/mutable_appearance.dm index 4abaa9e640e..5fb2cdf2a3c 100644 --- a/code/datums/mutable_appearance.dm +++ b/code/datums/mutable_appearance.dm @@ -4,10 +4,12 @@ // Mutable appearances are children of images, just so you know. -/mutable_appearance/New() +// Mutable appearances erase template vars on new, because they accept an appearance to copy as an arg +// If we have nothin to copy, we set the float plane +/mutable_appearance/New(mutable_appearance/to_copy) ..() - plane = FLOAT_PLANE // No clue why this is 0 by default yet images are on FLOAT_PLANE - // And yes this does have to be in the constructor, BYOND ignores it if you set it as a normal var + if(!to_copy) + plane = FLOAT_PLANE /// Helper similar to image() /proc/mutable_appearance(icon, icon_state = "", layer = FLOAT_LAYER, plane = FLOAT_PLANE, alpha = 255, appearance_flags = NONE) diff --git a/code/modules/lighting/lighting_corner.dm b/code/modules/lighting/lighting_corner.dm index 572cd8b50d7..a72d1c5f5cc 100644 --- a/code/modules/lighting/lighting_corner.dm +++ b/code/modules/lighting/lighting_corner.dm @@ -29,50 +29,48 @@ ///whether we are to be added to SSlighting's corners_queue list for an update var/needs_update = FALSE -/datum/lighting_corner/New(turf/new_turf, diagonal) +// Takes as an argument the coords to use as the bottom left (south west) of our corner +/datum/lighting_corner/New(x, y, z) . = ..() - save_master(new_turf, turn(diagonal, 180)) - var/vertical = diagonal & ~(diagonal - 1) // The horizontal directions (4 and 8) are bigger than the vertical ones (1 and 2), so we can reliably say the lsb is the horizontal direction. - var/horizontal = diagonal & ~vertical // Now that we know the horizontal one we can get the vertical one. + src.x = x + 0.5 + src.y = y + 0.5 - x = new_turf.x + (horizontal == EAST ? 0.5 : -0.5) - y = new_turf.y + (vertical == NORTH ? 0.5 : -0.5) + // Alright. We're gonna take a set of coords, and from them do a loop clockwise + // To build out the turfs adjacent to us. This is pretty fast + var/turf/process_next = locate(x, y, z) + if(process_next) + master_SW = process_next + process_next.lighting_corner_NE = src + // Now, we go north! + process_next = get_step(process_next, NORTH) + else + // Yes this is slightly slower then having a guarenteeed turf, but there aren't many null turfs + // So this is pretty damn fast + process_next = locate(x, y + 1, z) - // My initial plan was to make this loop through a list of all the dirs (horizontal, vertical, diagonal). - // Issue being that the only way I could think of doing it was very messy, slow and honestly overengineered. - // So we'll have this hardcode instead. - var/turf/new_master_turf + // Ok, if we have a north turf, go there. otherwise, onto the next + if(process_next) + master_NW = process_next + process_next.lighting_corner_SE = src + // Now, TO THE EAST + process_next = get_step(process_next, EAST) + else + process_next = locate(x + 1, y + 1, z) - // Diagonal one is easy. - new_master_turf = get_step(new_turf, diagonal) - if (new_master_turf) // In case we're on the map's border. - save_master(new_master_turf, diagonal) + // Etc etc + if(process_next) + master_NE = process_next + process_next.lighting_corner_SW = src + // Now, TO THE SOUTH AGAIN (SE) + process_next = get_step(process_next, SOUTH) + else + process_next = locate(x + 1, y, z) - // Now the horizontal one. - new_master_turf = get_step(new_turf, horizontal) - if (new_master_turf) // Ditto. - save_master(new_master_turf, ((new_master_turf.x > x) ? EAST : WEST) | ((new_master_turf.y > y) ? NORTH : SOUTH)) // Get the dir based on coordinates. - - // And finally the vertical one. - new_master_turf = get_step(new_turf, vertical) - if (new_master_turf) - save_master(new_master_turf, ((new_master_turf.x > x) ? EAST : WEST) | ((new_master_turf.y > y) ? NORTH : SOUTH)) // Get the dir based on coordinates. - -/datum/lighting_corner/proc/save_master(turf/master, dir) - switch (dir) - if (NORTHEAST) - master_NE = master - master.lighting_corner_SW = src - if (SOUTHEAST) - master_SE = master - master.lighting_corner_NW = src - if (SOUTHWEST) - master_SW = master - master.lighting_corner_NE = src - if (NORTHWEST) - master_NW = master - master.lighting_corner_SE = src + // anddd the last tile + if(process_next) + master_SE = process_next + process_next.lighting_corner_NW = src /datum/lighting_corner/proc/self_destruct_if_idle() if (!LAZYLEN(affecting)) diff --git a/code/modules/lighting/lighting_object.dm b/code/modules/lighting/lighting_object.dm index 9004e35b204..050072f5e78 100644 --- a/code/modules/lighting/lighting_object.dm +++ b/code/modules/lighting/lighting_object.dm @@ -8,14 +8,18 @@ ///the turf that our light is applied to var/turf/affected_turf +// Global list of lighting underlays, indexed by z level +GLOBAL_LIST_EMPTY(default_lighting_underlays_by_z) + /datum/lighting_object/New(turf/source) if(!isturf(source)) qdel(src, force=TRUE) stack_trace("a lighting object was assigned to [source], a non turf! ") return + . = ..() - current_underlay = mutable_appearance(LIGHTING_ICON, "transparent", source.z, LIGHTING_PLANE, 255, RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM) + current_underlay = new(GLOB.default_lighting_underlays_by_z[source.z]) affected_turf = source if (affected_turf.lighting_object) @@ -25,8 +29,11 @@ affected_turf.lighting_object = src affected_turf.luminosity = 0 - for(var/turf/open/space/space_tile in RANGE_TURFS(1, affected_turf)) - space_tile.update_starlight() + // This path is really hot. this is faster + // Really this should be a global var or something, but lets not think about that yes? + if(CONFIG_GET(flag/starlight)) + for(var/turf/open/space/space_tile in RANGE_TURFS(1, affected_turf)) + space_tile.update_starlight() needs_update = TRUE SSlighting.objects_queue += src @@ -59,6 +66,7 @@ var/static/datum/lighting_corner/dummy/dummy_lighting_corner = new + var/turf/affected_turf = src.affected_turf var/datum/lighting_corner/red_corner = affected_turf.lighting_corner_SW || dummy_lighting_corner var/datum/lighting_corner/green_corner = affected_turf.lighting_corner_SE || dummy_lighting_corner var/datum/lighting_corner/blue_corner = affected_turf.lighting_corner_NW || dummy_lighting_corner @@ -66,21 +74,6 @@ var/max = max(red_corner.largest_color_luminosity, green_corner.largest_color_luminosity, blue_corner.largest_color_luminosity, alpha_corner.largest_color_luminosity) - var/rr = red_corner.cache_r - var/rg = red_corner.cache_g - var/rb = red_corner.cache_b - - var/gr = green_corner.cache_r - var/gg = green_corner.cache_g - var/gb = green_corner.cache_b - - var/br = blue_corner.cache_r - var/bg = blue_corner.cache_g - var/bb = blue_corner.cache_b - - var/ar = alpha_corner.cache_r - var/ag = alpha_corner.cache_g - var/ab = alpha_corner.cache_b #if LIGHTING_SOFT_THRESHOLD != 0 var/set_luminosity = max > LIGHTING_SOFT_THRESHOLD @@ -90,28 +83,28 @@ var/set_luminosity = max > 1e-6 #endif - if((rr & gr & br & ar) && (rg + gg + bg + ag + rb + gb + bb + ab == 8)) + var/mutable_appearance/current_underlay = src.current_underlay + affected_turf.underlays -= current_underlay + if(red_corner.cache_r & green_corner.cache_r & blue_corner.cache_r & alpha_corner.cache_r && \ + (red_corner.cache_g + green_corner.cache_g + blue_corner.cache_g + alpha_corner.cache_g + \ + red_corner.cache_b + green_corner.cache_b + blue_corner.cache_b + alpha_corner.cache_b == 8)) //anything that passes the first case is very likely to pass the second, and addition is a little faster in this case - affected_turf.underlays -= current_underlay current_underlay.icon_state = "lighting_transparent" current_underlay.color = null - affected_turf.underlays += current_underlay else if(!set_luminosity) - affected_turf.underlays -= current_underlay current_underlay.icon_state = "lighting_dark" current_underlay.color = null - affected_turf.underlays += current_underlay else - affected_turf.underlays -= current_underlay current_underlay.icon_state = null current_underlay.color = list( - rr, rg, rb, 00, - gr, gg, gb, 00, - br, bg, bb, 00, - ar, ag, ab, 00, + red_corner.cache_r, red_corner.cache_g, red_corner.cache_b, 00, + green_corner.cache_r, green_corner.cache_g, green_corner.cache_b, 00, + blue_corner.cache_r, blue_corner.cache_g, blue_corner.cache_b, 00, + alpha_corner.cache_r, alpha_corner.cache_g, alpha_corner.cache_b, 00, 00, 00, 00, 01 ) - affected_turf.underlays += current_underlay - + // Of note. Most of the cost in this proc is here, I think because color matrix'd underlays DO NOT cache well, which is what adding to underlays does + // We use underlays because objects on each tile would fuck with maptick. if that ever changes, use an object for this instead + affected_turf.underlays += current_underlay affected_turf.luminosity = set_luminosity diff --git a/code/modules/lighting/lighting_setup.dm b/code/modules/lighting/lighting_setup.dm index 78fde88899e..ff716ca4bdd 100644 --- a/code/modules/lighting/lighting_setup.dm +++ b/code/modules/lighting/lighting_setup.dm @@ -4,6 +4,7 @@ if(!A.static_lighting) continue + // I hate this so much dude. why do areas not track their turfs lummyyyyyyy for(var/turf/T in A) if(T.always_lit) continue diff --git a/code/modules/lighting/lighting_source.dm b/code/modules/lighting/lighting_source.dm index f5a349a7807..69cefd48d14 100644 --- a/code/modules/lighting/lighting_source.dm +++ b/code/modules/lighting/lighting_source.dm @@ -140,31 +140,53 @@ // If you're wondering what's with the backslashes, the backslashes cause BYOND to not automatically end the line. // 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 LUM_FALLOFF(C, T) (1 - CLAMP01(sqrt((C.x - T.x) ** 2 + (C.y - T.y) ** 2 + LIGHTING_HEIGHT) / max(1, light_range))) + +// This exists so we can cache the vars used in this macro, and save MASSIVE time :) +// Most of this is saving off datum var accesses, tho some of it does actually cache computation +// You will NEED to call this before you call APPLY_CORNER +#define SETUP_CORNERS_CACHE(lighting_source) \ + var/_turf_x = lighting_source.pixel_turf.x; \ + var/_turf_y = lighting_source.pixel_turf.y; \ + var/_range_divisor = max(1, lighting_source.light_range); \ + var/_light_power = lighting_source.light_power; \ + var/_applied_lum_r = lighting_source.applied_lum_r; \ + var/_applied_lum_g = lighting_source.applied_lum_g; \ + var/_applied_lum_b = lighting_source.applied_lum_b; \ + var/_lum_r = lighting_source.lum_r; \ + var/_lum_g = lighting_source.lum_g; \ + var/_lum_b = lighting_source.lum_b; \ + +#define SETUP_CORNERS_REMOVAL_CACHE(lighting_source) \ + var/_applied_lum_r = lighting_source.applied_lum_r; \ + var/_applied_lum_g = lighting_source.applied_lum_g; \ + var/_applied_lum_b = lighting_source.applied_lum_b; + +#define LUM_FALLOFF(C) (1 - CLAMP01(sqrt((C.x - _turf_x) ** 2 + (C.y - _turf_y) ** 2 + LIGHTING_HEIGHT) / _range_divisor)) #define APPLY_CORNER(C) \ - . = LUM_FALLOFF(C, pixel_turf); \ - . *= light_power; \ + . = LUM_FALLOFF(C); \ + . *= _light_power; \ var/OLD = effect_str[C]; \ \ C.update_lumcount \ ( \ - (. * lum_r) - (OLD * applied_lum_r), \ - (. * lum_g) - (OLD * applied_lum_g), \ - (. * lum_b) - (OLD * applied_lum_b) \ + (. * _lum_r) - (OLD * _applied_lum_r), \ + (. * _lum_g) - (OLD * _applied_lum_g), \ + (. * _lum_b) - (OLD * _applied_lum_b) \ ); \ #define REMOVE_CORNER(C) \ . = -effect_str[C]; \ C.update_lumcount \ ( \ - . * applied_lum_r, \ - . * applied_lum_g, \ - . * applied_lum_b \ + . * _applied_lum_r, \ + . * _applied_lum_g, \ + . * _applied_lum_b \ ); /// This is the define used to calculate falloff. /datum/light_source/proc/remove_lum() + SETUP_CORNERS_REMOVAL_CACHE(src) applied = FALSE for (var/datum/lighting_corner/corner as anything in effect_str) REMOVE_CORNER(corner) @@ -173,6 +195,7 @@ effect_str = null /datum/light_source/proc/recalc_corner(datum/lighting_corner/corner) + SETUP_CORNERS_CACHE(src) LAZYINITLIST(effect_str) if (effect_str[corner]) // Already have one. REMOVE_CORNER(corner) @@ -182,6 +205,22 @@ effect_str[corner] = . +// Keep in mind. Lighting corners accept the bottom left (northwest) set of cords to them as input +#define GENERATE_MISSING_CORNERS(gen_for) \ + if (!gen_for.lighting_corner_NE) { \ + gen_for.lighting_corner_NE = new /datum/lighting_corner(gen_for.x, gen_for.y, gen_for.z); \ + } \ + if (!gen_for.lighting_corner_SE) { \ + gen_for.lighting_corner_SE = new /datum/lighting_corner(gen_for.x, gen_for.y - 1, gen_for.z); \ + } \ + if (!gen_for.lighting_corner_SW) { \ + gen_for.lighting_corner_SW = new /datum/lighting_corner(gen_for.x - 1, gen_for.y - 1, gen_for.z); \ + } \ + if (!gen_for.lighting_corner_NW) { \ + gen_for.lighting_corner_NW = new /datum/lighting_corner(gen_for.x - 1, gen_for.y, gen_for.z); \ + } \ + gen_for.lighting_corners_initialised = TRUE; + /datum/light_source/proc/update_corners() var/update = FALSE var/atom/source_atom = src.source_atom @@ -244,24 +283,27 @@ return //nothing's changed var/list/datum/lighting_corner/corners = list() - var/list/turf/turfs = list() if (source_turf) var/oldlum = source_turf.luminosity source_turf.luminosity = CEILING(light_range, 1) for(var/turf/T in view(CEILING(light_range, 1), source_turf)) - if(!IS_OPAQUE_TURF(T)) - if (!T.lighting_corners_initialised) - T.generate_missing_corners() - corners[T.lighting_corner_NE] = 0 - corners[T.lighting_corner_SE] = 0 - corners[T.lighting_corner_SW] = 0 - corners[T.lighting_corner_NW] = 0 - turfs += T + if(IS_OPAQUE_TURF(T)) + continue + if (!T.lighting_corners_initialised) + GENERATE_MISSING_CORNERS(T) + + corners[T.lighting_corner_NE] = 0 + corners[T.lighting_corner_SE] = 0 + corners[T.lighting_corner_SW] = 0 + corners[T.lighting_corner_NW] = 0 source_turf.luminosity = oldlum - var/list/datum/lighting_corner/new_corners = (corners - effect_str) - LAZYINITLIST(effect_str) + SETUP_CORNERS_CACHE(src) + + var/list/datum/lighting_corner/new_corners = (corners - src.effect_str) + LAZYINITLIST(src.effect_str) + var/list/effect_str = src.effect_str if (needs_update == LIGHTING_VIS_UPDATE) for (var/datum/lighting_corner/corner as anything in new_corners) APPLY_CORNER(corner) @@ -274,14 +316,15 @@ if (. != 0) LAZYADD(corner.affecting, src) effect_str[corner] = . - - for (var/datum/lighting_corner/corner as anything in corners - new_corners) // Existing corners - APPLY_CORNER(corner) - if (. != 0) - effect_str[corner] = . - else - LAZYREMOVE(corner.affecting, src) - effect_str -= corner + // New corners are a subset of corners. so if they're both the same length, there are NO old corners! + if(length(corners) != length(new_corners)) + for (var/datum/lighting_corner/corner as anything in corners - new_corners) // Existing corners + APPLY_CORNER(corner) + if (. != 0) + effect_str[corner] = . + else + LAZYREMOVE(corner.affecting, src) + effect_str -= corner var/list/datum/lighting_corner/gone_corners = effect_str - corners for (var/datum/lighting_corner/corner as anything in gone_corners) @@ -293,9 +336,12 @@ applied_lum_g = lum_g applied_lum_b = lum_b - UNSETEMPTY(effect_str) + UNSETEMPTY(src.effect_str) #undef EFFECT_UPDATE #undef LUM_FALLOFF #undef REMOVE_CORNER #undef APPLY_CORNER +#undef SETUP_CORNERS_REMOVAL_CACHE +#undef SETUP_CORNERS_CACHE +#undef GENERATE_MISSING_CORNERS diff --git a/code/modules/lighting/lighting_turf.dm b/code/modules/lighting/lighting_turf.dm index a953bc45f94..f9aba7eadd5 100644 --- a/code/modules/lighting/lighting_turf.dm +++ b/code/modules/lighting/lighting_turf.dm @@ -100,17 +100,3 @@ else lighting_clear_overlay() -/turf/proc/generate_missing_corners() - if (!lighting_corner_NE) - lighting_corner_NE = new/datum/lighting_corner(src, NORTH|EAST) - - if (!lighting_corner_SE) - lighting_corner_SE = new/datum/lighting_corner(src, SOUTH|EAST) - - if (!lighting_corner_SW) - lighting_corner_SW = new/datum/lighting_corner(src, SOUTH|WEST) - - if (!lighting_corner_NW) - lighting_corner_NW = new/datum/lighting_corner(src, NORTH|WEST) - - lighting_corners_initialised = TRUE diff --git a/code/modules/mapping/space_management/space_level.dm b/code/modules/mapping/space_management/space_level.dm index 5e555ebb41a..6be4f5b8f55 100644 --- a/code/modules/mapping/space_management/space_level.dm +++ b/code/modules/mapping/space_management/space_level.dm @@ -18,4 +18,8 @@ else // in case a single trait is passed in SSmapping.z_trait_levels[new_traits] += list(new_z) + if(length(GLOB.default_lighting_underlays_by_z) < z_value) + GLOB.default_lighting_underlays_by_z.len = z_value + GLOB.default_lighting_underlays_by_z[z_value] = mutable_appearance(LIGHTING_ICON, "transparent", new_z, LIGHTING_PLANE, 255, RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM) + set_linkage(new_traits[ZTRAIT_LINKAGE])