Makes lighting objects objects again (#95332)

## About The Pull Request

Continuation of https://github.com/tgstation/tgstation/pull/78857

FOR TOOO LONG WE HAVE SUFF-
Ok yeah so like, we made them overlays to save on maptick, but with
threaded maptick that is potentially not an issue anymore.
I'm opening this pr so I can tm it and see. If it works, it'll save
about 50% of lighting object update costs, which is pretty damn good.

I'm also removing the fullbright light icon state, since it is barely
ever used (it was added as a clientside op, but we don't hit fullbright
very much at all so it does nothing but eat my cpu time)

Also also changing how SSlighting does its resolution. Rather then
waiting for all sources to process, then working on corners and objects,
instead we will do all the sources we had at the start, then all the
sources after, and so on.
The goal is to avoid churn causing the system to constantly choke. it is
better to potentially double operate then it is for things to feel
horrifically slow.

## Why It's Good For The Game

Faster. Also you won't be able to see lights through walls anymore, so
mesons will be less dumb. Can maybe bump their nightvision a bit idk
we'll see.

## Current Issues

to be found

## Changelog
🆑 Absolucy, LemonInTheDark
fix: You can no longer see lights through walls when using mesons or
when ventcrawling.
/🆑

---------

Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
This commit is contained in:
Lucy
2026-04-23 08:55:26 +12:00
committed by GitHub
co-authored by LemonInTheDark
parent c048aa41fb
commit 9b6e752f3e
15 changed files with 114 additions and 74 deletions
+1 -1
View File
@@ -39,7 +39,7 @@
#define POINT_PLANE 5
//---------- LIGHTING -------------
/// Normal 1 per turf dynamic lighting underlays
/// Normal 1 per turf dynamic lighting objects
#define LIGHTING_PLANE 10
/// Lighting objects that are "free floating"
+14 -3
View File
@@ -4,7 +4,7 @@ SUBSYSTEM_DEF(lighting)
/datum/controller/subsystem/atoms,
/datum/controller/subsystem/mapping,
)
wait = 2
wait = 1
ss_flags = SS_TICKER
var/static/list/sources_queue = list() // List of lighting sources queued for update.
var/static/list/corners_queue = list() // List of lighting corners queued for update.
@@ -38,7 +38,7 @@ SUBSYSTEM_DEF(lighting)
for(var/turf/area_turf as anything in zlevel_turfs)
if(area_turf.space_lit)
continue
new /datum/lighting_object(area_turf)
new /atom/movable/lighting_object(null, area_turf)
CHECK_TICK
CHECK_TICK
@@ -53,6 +53,7 @@ SUBSYSTEM_DEF(lighting)
// UPDATE SOURCE QUEUE
var/i = 0
// something something cache locally for sonic speed
var/list/queue = current_sources
while(i < length(queue)) //we don't use for loop here because i cannot be changed during an iteration
i += 1
@@ -110,7 +111,7 @@ SUBSYSTEM_DEF(lighting)
while(i < length(queue)) //we don't use for loop here because i cannot be changed during an iteration
i += 1
var/datum/lighting_object/O = queue[i]
var/atom/movable/lighting_object/O = queue[i]
if(QDELETED(O))
continue
O.update()
@@ -132,3 +133,13 @@ SUBSYSTEM_DEF(lighting)
/datum/controller/subsystem/lighting/Recover()
initialized = SSlighting.initialized
..()
/// Takes a list of turfs in, and sets up static lighting for them as needed.
/// Exactly what it says on the tin.
/datum/controller/subsystem/lighting/proc/setup_static_lighting_if_needed(list/turfs)
for(var/turf/unlit as anything in turfs)
if(unlit.space_lit)
continue
var/area/loc_area = unlit.loc
if(loc_area.static_lighting)
unlit.lighting_build_overlay()
-15
View File
@@ -744,11 +744,6 @@ ADMIN_VERB(load_away_mission, R_FUN, "Load Away Mission", "Load a specific away
if(contain_turfs)
build_area_turfs(z_value, filled_with_space)
// And finally, misc global generation
// We'll have to update this if offsets change, because we load lowest z to highest z
generate_lighting_appearance_by_z(z_value)
/datum/controller/subsystem/mapping/proc/build_area_turfs(z_level, space_guaranteed)
// If we know this is filled with default tiles, we can use the default area
// Faster
@@ -785,11 +780,6 @@ ADMIN_VERB(load_away_mission, R_FUN, "Load Away Mission", "Load a specific away
z_level_to_lowest_plane_offset[level_to_update.z_value] = plane_offset
z_level_to_stack[level_to_update.z_value] = z_stack
// This can be affected by offsets, so we need to update it
// PAIN
for(var/i in 1 to length(z_list))
generate_lighting_appearance_by_z(i)
var/old_max = max_plane_offset
max_plane_offset = max(max_plane_offset, plane_offset)
if(max_plane_offset == old_max)
@@ -875,11 +865,6 @@ ADMIN_VERB(load_away_mission, R_FUN, "Load Away Mission", "Load a specific away
CRASH("Attempted to lazy load a template key that does not exist: '[template_key]'")
return target.lazy_load()
/proc/generate_lighting_appearance_by_z(z_level)
if(length(GLOB.default_lighting_underlays_by_z) < z_level)
GLOB.default_lighting_underlays_by_z.len = z_level
GLOB.default_lighting_underlays_by_z[z_level] = mutable_appearance(LIGHTING_ICON, "transparent", z_level * 0.01, null, LIGHTING_PLANE, 255, RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM, offset_const = GET_Z_PLANE_OFFSET(z_level))
/// Returns true if the map we're playing on is on a planet
/datum/controller/subsystem/mapping/proc/is_planetary()
return current_map.planetary
+1
View File
@@ -104,6 +104,7 @@
loaded_atom_movables |= thing
SSatoms.InitializeAtoms(loaded_areas + loaded_atom_movables + loaded_turfs)
SSlighting.setup_static_lighting_if_needed(loaded_turfs)
SSmachines.setup_template_powernets(loaded_cables)
SSair.setup_template_machinery(loaded_atmospherics)
+5 -1
View File
@@ -158,8 +158,12 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
if(SSlighting.initialized)
// Space tiles should never have lighting objects
if(!space_lit)
if(old_lighting_object)
lighting_object = old_lighting_object
vis_contents += lighting_object
// Should have a lighting object if we never had one
lighting_object = old_lighting_object || new /datum/lighting_object(src)
else
new /atom/movable/lighting_object(null, src)
else if (old_lighting_object)
qdel(old_lighting_object, force = TRUE)
+1 -1
View File
@@ -67,7 +67,7 @@ GLOBAL_LIST_EMPTY(station_turfs)
var/tmp/lighting_corners_initialised = FALSE
///Our lighting object.
var/tmp/datum/lighting_object/lighting_object
var/tmp/atom/movable/lighting_object/lighting_object
///Lighting Corner datums.
var/tmp/datum/lighting_corner/lighting_corner_NE
var/tmp/datum/lighting_corner/lighting_corner_SE
@@ -19,6 +19,7 @@ GLOBAL_ALIST_EMPTY(reftracker_skip_typecache_b)
/icon,
/matrix,
/regex,
/atom/movable/lighting_object, // only contains turf and MA refs
/atom/movable/mirage_holder,
/atom/movable/render_step/emissive_blocker,
/datum/armor,
@@ -31,7 +32,6 @@ GLOBAL_ALIST_EMPTY(reftracker_skip_typecache_b)
/datum/greyscale_layer,
/datum/icon_transformer,
/datum/instrument_key,
/datum/lighting_object, // only contains turf and MA refs
/datum/movespeed_modifier,
/datum/painting,
/datum/paper_input,
+1 -5
View File
@@ -141,7 +141,7 @@
return
#endif
var/datum/lighting_object/lighting_object = master_NE?.lighting_object
var/atom/movable/lighting_object/lighting_object = master_NE?.lighting_object
if (lighting_object && !lighting_object.needs_update)
lighting_object.needs_update = TRUE
SSlighting.objects_queue += lighting_object
@@ -163,10 +163,6 @@
self_destruct_if_idle()
/datum/lighting_corner/dummy/New()
return
/datum/lighting_corner/Destroy(force)
if (!force)
return QDEL_HINT_LETMELIVE
+73 -36
View File
@@ -1,32 +1,50 @@
/datum/lighting_object
///the underlay we are currently applying to our turf to apply light
var/mutable_appearance/current_underlay
/atom/movable/lighting_object
name = ""
anchored = TRUE
plane = LIGHTING_PLANE
icon = LIGHTING_ICON
icon_state = null
color = null //we manually set color in init instead
appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
invisibility = INVISIBILITY_LIGHTING
move_resist = INFINITY
///whether we are already in the SSlighting.objects_queue list
var/needs_update = FALSE
///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
/atom/movable/lighting_object/Initialize(mapload, turf/affected_turf)
if(!isnull(loc))
if(isturf(loc))
affected_turf = loc
moveToNullspace()
stack_trace("a lighting object was improperly initialized - they should have a null loc, with the affected turf being the second argument")
else
qdel(src, force = TRUE)
CRASH("a lighting object tried to be spawned for a non-turf!")
if(!isturf(affected_turf))
qdel(src, force = TRUE)
CRASH("a lighting object was assigned to [affected_turf], a non turf!")
. = ..()
current_underlay = new(GLOB.default_lighting_underlays_by_z[source.z])
verbs.Cut()
affected_turf = source
src.affected_turf = affected_turf
layer = affected_turf.z * 0.01
if(SSmapping.max_plane_offset)
// generates the offset lighting plane to use. NOTE: this assumes the turf lighting
// plane is ALWAYS offsettable which is technically dependent on a plane master var.
// checking for that is slower and this is hot enough that this is a worthwhile risk to take
plane = LIGHTING_PLANE - (PLANE_RANGE * GET_Z_PLANE_OFFSET(affected_turf.z))
if (affected_turf.lighting_object)
qdel(affected_turf.lighting_object, force = TRUE)
stack_trace("a lighting object was assigned to a turf that already had a lighting object!")
affected_turf.lighting_object = src
affected_turf.vis_contents += src
// Default to fullbright, so things can "see" if they use view() before we update
affected_turf.luminosity = 1
@@ -38,18 +56,20 @@ GLOBAL_LIST_EMPTY(default_lighting_underlays_by_z)
needs_update = TRUE
SSlighting.objects_queue += src
/datum/lighting_object/Destroy(force)
/atom/movable/lighting_object/Destroy(force)
if (!force)
return QDEL_HINT_LETMELIVE
SSlighting.objects_queue -= src
if (isturf(affected_turf))
affected_turf.vis_contents -= src
affected_turf.lighting_object = null
affected_turf.luminosity = 1
affected_turf.underlays -= current_underlay
affected_turf = null
return ..()
/datum/lighting_object/proc/update()
/atom/movable/lighting_object/proc/update()
var/turf/affected_turf = src.affected_turf
// To the future coder who sees this and thinks
// "Why didn't he just use a loop?"
// Well my man, it's because the loop performed like shit.
@@ -60,8 +80,6 @@ GLOBAL_LIST_EMPTY(default_lighting_underlays_by_z)
var/static/datum/lighting_corner/dummy/dummy_lighting_corner = new
var/turf/affected_turf = src.affected_turf
#ifdef VISUALIZE_LIGHT_UPDATES
affected_turf.add_atom_colour(COLOR_BLUE_LIGHT, ADMIN_COLOUR_PRIORITY)
animate(affected_turf, 10, color = null)
@@ -84,20 +102,12 @@ GLOBAL_LIST_EMPTY(default_lighting_underlays_by_z)
var/set_luminosity = max > 1e-6
#endif
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
current_underlay.icon_state = "lighting_transparent"
current_underlay.color = null
else if(!set_luminosity)
current_underlay.icon_state = "lighting_dark"
current_underlay.color = null
if(!set_luminosity)
icon_state = "lighting_dark"
color = null
else
current_underlay.icon_state = null
current_underlay.color = list(
icon_state = null
color = list(
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,
@@ -105,7 +115,34 @@ GLOBAL_LIST_EMPTY(default_lighting_underlays_by_z)
00, 00, 00, 01
)
// 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
luminosity = set_luminosity
// Variety of overrides so the overlays don't get affected by weird things.
/atom/movable/lighting_object/ex_act(severity)
return FALSE
/atom/movable/lighting_object/singularity_act()
return
/atom/movable/lighting_object/singularity_pull()
return
/atom/movable/lighting_object/blob_act()
return
/atom/movable/lighting_object/on_changed_z_level(turf/old_turf, turf/new_turf, same_z_layer, notify_contents = TRUE)
SHOULD_CALL_PARENT(FALSE)
return
/atom/movable/lighting_object/wash(clean_types)
SHOULD_CALL_PARENT(FALSE) // lighting objects are dirty, confirmed
return
// Override here to prevent things accidentally moving around overlays.
/atom/movable/lighting_object/forceMove(atom/destination, no_tp = FALSE, harderforce = FALSE)
if(harderforce)
return ..()
/atom/movable/lighting_object/ref_search_details()
return "[text_ref(src)] (turf: [affected_turf ? "[affected_turf.type] @ [AREACOORD(affected_turf)]" : "null"] needs_update: [needs_update ? "true" : "false"])"
+1 -1
View File
@@ -14,7 +14,7 @@
if (lighting_object)
qdel(lighting_object, force=TRUE) //Shitty fix for lighting objects persisting after death
new /datum/lighting_object(src)
new /atom/movable/lighting_object(null, src)
// Used to get a scaled lumcount.
/turf/proc/get_lumcount(minlum = 0, maxlum = 1)
+1 -8
View File
@@ -90,14 +90,7 @@
return
SSatoms.InitializeAtoms(areas + turfs + movables, returns_created_atoms ? created_atoms : null)
for(var/turf/unlit as anything in turfs)
if(unlit.space_lit)
continue
var/area/loc_area = unlit.loc
if(!loc_area.static_lighting)
continue
unlit.lighting_build_overlay()
SSlighting.setup_static_lighting_if_needed(turfs)
// NOTE, now that Initialize and LateInitialize run correctly, do we really
// need these two below?
@@ -41,7 +41,7 @@
atoms += new /obj/effect/appearance_clone(newT, T.loc)
if(T.lighting_object)
var/obj/effect/appearance_clone/lighting_overlay = new(newT)
lighting_overlay.appearance = T.lighting_object.current_underlay
lighting_overlay.appearance = T.lighting_object.appearance
lighting_overlay.underlays += backdrop
lighting_overlay.blend_mode = BLEND_MULTIPLY
lighting += lighting_overlay
@@ -61,7 +61,7 @@
atoms += T
if(T.lighting_object)
var/obj/effect/appearance_clone/lighting_overlay = new(T)
lighting_overlay.appearance = T.lighting_object.current_underlay
lighting_overlay.appearance = T.lighting_object.appearance
lighting_overlay.underlays += backdrop
lighting_overlay.blend_mode = BLEND_MULTIPLY
lighting += lighting_overlay
@@ -78,6 +78,13 @@ All ShuttleMove procs go here
if(rotation)
shuttleRotate(rotation, params = ALL) //see shuttle_rotate.dm
// if we have a lighting object that needs to be updated
if(lighting_object?.needs_update)
lighting_object.update()
lighting_object.needs_update = FALSE
SSlighting.objects_queue -= lighting_object
SEND_SIGNAL(src, COMSIG_TURF_AFTER_SHUTTLE_MOVE, oldT)
return TRUE
@@ -402,6 +409,9 @@ All ShuttleMove procs go here
/************************************Misc move procs************************************/
/atom/movable/lighting_object/onShuttleMove()
return FALSE
/obj/docking_port/mobile/hypotheticalShuttleMove(rotation, move_mode, obj/docking_port/mobile/moving_dock)
. = ..()
if(moving_dock == src)
+3
View File
@@ -279,6 +279,9 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests())
/obj/effect/decal/cleanable/blood/trail,
//Should not exist outside of ethereals
/obj/item/stock_parts/power_store/cell/ethereal,
// Abstract type, controlled by turfs
// Literally errors on creation/deletion
/atom/movable/lighting_object,
)
// Everything that follows is a typesof() check.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB