From f88edef0fb0c6988de0381d7c29e967ce04310dd Mon Sep 17 00:00:00 2001 From: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> Date: Mon, 6 Feb 2023 20:04:50 -0800 Subject: [PATCH] Space/Changeturf fixes and optimizations (#73261) ## About The Pull Request We've got a few space related things that are busted, and shuttle movement is slow. I'd like to try to improve these things, if just a bit. Long list of only tenuously related topics. Sorry for the shotgun blast #### [Fixes lazyloaded stuff having bad space](https://github.com/tgstation/tgstation/commit/d4de176a63f87d0f820e0cb610cd192750c897d6) We need to handle area transferring in maploading code under niche cases, and we also need to actually init reservation spaces we create. It's also redundant and potentially dupe creating to do area lighting handling in changeturf, because it gets touched in turf init anyway. Old me is stupid. #### [Adds some doc comments, yeets ssmappping/transit](https://github.com/tgstation/tgstation/commit/269717145d70a4a73198791ca50253c708ee3ac1) We had a reserved space for just shuttles to use, except it wasn't for just shuttles. So in theory if the space got clogged with other shit, the shuttles could have nowhere to actually use. It's better to just have the two groups share real estate. More sane ### The "Starlight is Slow" Block #### [Starlight optimization part one (don't check config for each individual turf you check for activity)](https://github.com/tgstation/tgstation/commit/7312a314bef281c1b85a377cf2dcb647a2045050) #### [Starlight optimization part two (infer context)](https://github.com/tgstation/tgstation/commit/be94c422ed76aa3f07b43cad4d1dc6b6148f135f) Starlight was causing each space turf to cause itself and its neighbor to constantly recheck if they had starlight off changeturf. The exact same effect can be had by taking advantage of some pre-existing information, namely if the space turf is gaining or losing a source of starlight. Essentially, instead of telling a turf to check all adjacent turfs to see if it's got starlight, we tell the turf if WE are a source of starlight, or if we might be taking something away from it. There's a bit of wasted cpu here but not much, if it's worth doing a register signal pattern for clearing depends on the case we're working with. Being intelligent about this makes things much faster, something in the neighborhood of 4 to 3 fold. I've also made openspace's starlight work better, cause the old pattern was a bit silly. ### Changeturf is Annoying (Microops) #### [Micro ops changeturf and turf deletion a bit](https://github.com/tgstation/tgstation/commit/386b3ab7fc2a820a9ffe3d2e39d78f96dc562d64) Don't do work if the thing you're working on doesn't exist, don't check every adjacent turf for firelocks on turf change (just have thefirelocks manage that), don't check all atoms on the turf for decals on turf change, similar. Also moves visibility changes from camera code into changeturf, to avoid unneeded work. Needs some extra work to optimize the guts for this path but I can do that! #### [Micros camera vis changes](https://github.com/tgstation/tgstation/commit/ebab69e9ea4adffd8787671f309f4ba27756c82e) We should only update vis when our opacity changes. In addition, we don't need all the camera handling fluff if we only want to update our turf's static groups. Also micros a camera net helper to be less crap for non multiz maps #### [Micros some open space atmos cases, alongside avoiding a for(null) in opacity handling](https://github.com/tgstation/tgstation/commit/72ae07ba1db1fb1c4434a4cdaecc78ea6a2864fc) #### [Ensures space_lit tiles never accidentially inherit lighting objects](https://github.com/tgstation/tgstation/commit/a99ff2265a4d1b157849fb7485adee17a3250df5) S dumb, and leads to space turfs having two sources of lighting, which looks wrong. This was invisible when their lighting was fullbright, but it sucks now. ### Misc Stuff #### [Cleans up stat tracking a bit to avoid collisions](https://github.com/tgstation/tgstation/commit/40fb8f21e20d5bd9ef2f989eb166e03b30d66b3d) #### [Cleans up a turf helper to not be stupid](https://github.com/tgstation/tgstation/commit/bf4ee6710026e6ca9922d0f1fa49020ebde8cd6f) WHY ARE YOU USING THE RANGED TURF HELPER IF YOU GO ONE TILE #### [Moves transit turf signal cleanup to destroy, I named this proc wrong](https://github.com/tgstation/tgstation/commit/c85c2cfc86f3b2dd224cae6b12e2fc428846c30b) I'm sorry @Time-Green #### [Adds better transit caching to shuttles](https://github.com/tgstation/tgstation/commit/35e85334c4f815da0cadd8172e9908267a01d334) Adds a max reserved transit size to the shuttle subsystem, to keep things in bounds. In addition, adds a soft cap under which existing transit space will get hold onto, to make repeated non escape/arrive shuttle movements faster Hopefully this makes common shuttle moves less bad. ## Why It's Good For The Game Speed --- code/__DEFINES/stat_tracking.dm | 13 ++-- code/__HELPERS/spatial_info.dm | 24 +++++--- code/controllers/subsystem/mapping.dm | 35 +++++++---- code/controllers/subsystem/shuttle.dm | 30 +++++++++- code/datums/datum.dm | 13 ++-- code/game/atoms.dm | 16 ++--- code/game/machinery/doors/firedoor.dm | 13 +++- code/game/objects/effects/decals/decal.dm | 13 +++- code/game/turfs/change_turf.dm | 60 ++++++++++++------- code/game/turfs/open/openspace.dm | 2 + code/game/turfs/open/space/space.dm | 53 ++++++++++------ code/game/turfs/open/space/transit.dm | 3 +- code/game/turfs/turf.dm | 18 +++--- code/modules/lighting/lighting_atom.dm | 4 +- code/modules/lighting/lighting_object.dm | 2 +- code/modules/lighting/lighting_setup.dm | 2 +- code/modules/lighting/lighting_turf.dm | 21 ++++--- code/modules/mapping/reader.dm | 9 ++- .../space_management/zlevel_manager.dm | 2 + .../living/silicon/ai/freelook/cameranet.dm | 19 +++++- 20 files changed, 231 insertions(+), 121 deletions(-) diff --git a/code/__DEFINES/stat_tracking.dm b/code/__DEFINES/stat_tracking.dm index 810a313ac31..d143e5df4c7 100644 --- a/code/__DEFINES/stat_tracking.dm +++ b/code/__DEFINES/stat_tracking.dm @@ -15,7 +15,7 @@ #define INIT_COST(costs, counting) \ var/list/_costs = costs; \ var/list/_counting = counting; \ - var/usage = TICK_USAGE; + var/_usage = TICK_USAGE; // STATIC cost tracking macro. Uses static lists instead of the normal global ones // Good for debug stuff, and for running before globals init @@ -31,16 +31,16 @@ costs = hidden_static_list_for_fun1; \ counting = hidden_static_list_for_fun2 ; \ } \ - usage = TICK_USAGE; + _usage = TICK_USAGE; #define SET_COST(category) \ do { \ - var/cost = TICK_USAGE; \ - _costs[category] += TICK_DELTA_TO_MS(cost - usage);\ + var/_cost = TICK_USAGE; \ + _costs[category] += TICK_DELTA_TO_MS(_cost - _usage);\ _counting[category] += 1; \ } while(FALSE); \ - usage = TICK_USAGE; + _usage = TICK_USAGE; #define SET_COST_LINE(...) SET_COST("[__LINE__]") @@ -62,4 +62,5 @@ ##proc(filename, costs, counts); \ } \ } \ - } while (FALSE); + } while (FALSE); \ + _usage = TICK_USAGE; diff --git a/code/__HELPERS/spatial_info.dm b/code/__HELPERS/spatial_info.dm index cbaa1b906d8..7cc953691e7 100644 --- a/code/__HELPERS/spatial_info.dm +++ b/code/__HELPERS/spatial_info.dm @@ -348,19 +348,27 @@ ///Returns the open turf next to the center in a specific direction /proc/get_open_turf_in_dir(atom/center, dir) - var/turf/open/get_turf = get_ranged_target_turf(center, dir, 1) + var/turf/open/get_turf = get_step(center, dir) if(istype(get_turf)) return get_turf ///Returns a list with all the adjacent open turfs. Clears the list of nulls in the end. /proc/get_adjacent_open_turfs(atom/center) - . = list( - get_open_turf_in_dir(center, NORTH), - get_open_turf_in_dir(center, SOUTH), - get_open_turf_in_dir(center, EAST), - get_open_turf_in_dir(center, WEST) - ) - list_clear_nulls(.) + var/list/hand_back = list() + // Inlined get_open_turf_in_dir, just to be fast + var/turf/open/new_turf = get_step(center, NORTH) + if(istype(new_turf)) + hand_back += new_turf + new_turf = get_step(center, SOUTH) + if(istype(new_turf)) + hand_back += new_turf + new_turf = get_step(center, EAST) + if(istype(new_turf)) + hand_back += new_turf + new_turf = get_step(center, WEST) + if(istype(new_turf)) + hand_back += new_turf + return hand_back ///Returns a list with all the adjacent areas by getting the adjacent open turfs /proc/get_adjacent_open_areas(atom/center) diff --git a/code/controllers/subsystem/mapping.dm b/code/controllers/subsystem/mapping.dm index d10724a36af..b53fd6b2a80 100644 --- a/code/controllers/subsystem/mapping.dm +++ b/code/controllers/subsystem/mapping.dm @@ -148,13 +148,13 @@ SUBSYSTEM_DEF(mapping) #endif // Run map generation after ruin generation to prevent issues run_map_generation() - // Add the transit level - transit = add_new_zlevel("Transit/Reserved", list(ZTRAIT_RESERVED = TRUE)) + // Add the first transit level + var/datum/space_level/base_transit = add_reservation_zlevel() require_area_resort() // Set up Z-level transitions. setup_map_transitions() generate_station_area_list() - initialize_reserved_level(transit.z_value) + initialize_reserved_level(base_transit.z_value) calculate_default_z_level_gravities() return SS_INIT_SUCCESS @@ -171,7 +171,8 @@ SUBSYSTEM_DEF(mapping) var/packetlen = length(packet) while(packetlen) if(MC_TICK_CHECK) - lists_to_reserve.Cut(1, index) + if(index) + lists_to_reserve.Cut(1, index) return var/turf/T = packet[packetlen] T.empty(RESERVED_TURF_TYPE, RESERVED_TURF_TYPE, null, TRUE) @@ -328,7 +329,6 @@ Used by the AI doomsday and the self-destruct nuke. turf_reservations = SSmapping.turf_reservations used_turfs = SSmapping.used_turfs holodeck_templates = SSmapping.holodeck_templates - transit = SSmapping.transit areas_in_z = SSmapping.areas_in_z config = SSmapping.config @@ -647,6 +647,12 @@ GLOBAL_LIST_EMPTY(the_station_areas) message_admins("Loading [away_name] failed!") return +/// Adds a new reservation z level. A bit of space that can be handed out on request +/// Of note, reservations default to transit turfs, to make their most common use, shuttles, faster +/datum/controller/subsystem/mapping/proc/add_reservation_zlevel(for_shuttles) + num_of_res_levels++ + return add_new_zlevel("Transit/Reserved #[num_of_res_levels]", list(ZTRAIT_RESERVED = TRUE)) + /datum/controller/subsystem/mapping/proc/RequestBlockReservation(width, height, z, type = /datum/turf_reservation, turf_type_override) UNTIL((!z || reservation_ready["[z]"]) && !clearing_reserved_turfs) var/datum/turf_reservation/reserve = new type @@ -657,8 +663,7 @@ GLOBAL_LIST_EMPTY(the_station_areas) if(reserve.Reserve(width, height, i)) return reserve //If we didn't return at this point, theres a good chance we ran out of room on the exisiting reserved z levels, so lets try a new one - num_of_res_levels += 1 - var/datum/space_level/newReserved = add_new_zlevel("Transit/Reserved [num_of_res_levels]", list(ZTRAIT_RESERVED = TRUE)) + var/datum/space_level/newReserved = add_reservation_zlevel() initialize_reserved_level(newReserved.z_value) if(reserve.Reserve(width, height, newReserved.z_value)) return reserve @@ -671,7 +676,9 @@ GLOBAL_LIST_EMPTY(the_station_areas) return reserve QDEL_NULL(reserve) -//This is not for wiping reserved levels, use wipe_reservations() for that. +///Sets up a z level as reserved +///This is not for wiping reserved levels, use wipe_reservations() for that. +///If this is called after SSatom init, it will call Initialize on all turfs on the passed z, as its name promises /datum/controller/subsystem/mapping/proc/initialize_reserved_level(z) UNTIL(!clearing_reserved_turfs) //regardless, lets add a check just in case. clearing_reserved_turfs = TRUE //This operation will likely clear any existing reservations, so lets make sure nothing tries to make one while we're doing it. @@ -681,11 +688,15 @@ GLOBAL_LIST_EMPTY(the_station_areas) var/turf/A = get_turf(locate(SHUTTLE_TRANSIT_BORDER,SHUTTLE_TRANSIT_BORDER,z)) var/turf/B = get_turf(locate(world.maxx - SHUTTLE_TRANSIT_BORDER,world.maxy - SHUTTLE_TRANSIT_BORDER,z)) var/block = block(A, B) - for(var/t in block) - // No need to empty() these, because it's world init and they're - // already /turf/open/space/basic. - var/turf/T = t + for(var/turf/T as anything in block) + // No need to empty() these, because they just got created and are already /turf/open/space/basic. T.flags_1 |= UNUSED_RESERVATION_TURF + CHECK_TICK + + // Gotta create these suckers if we've not done so already + if(SSatoms.initialized) + SSatoms.InitializeAtoms(Z_TURFS(z)) + unused_turfs["[z]"] = block reservation_ready["[z]"] = TRUE clearing_reserved_turfs = FALSE diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm index 783915b2569..0e9d4fdce03 100644 --- a/code/controllers/subsystem/shuttle.dm +++ b/code/controllers/subsystem/shuttle.dm @@ -1,4 +1,10 @@ #define MAX_TRANSIT_REQUEST_RETRIES 10 +/// How many turfs to allow before we stop blocking transit requests +#define MAX_TRANSIT_TILE_COUNT (150 ** 2) +/// How many turfs to allow before we start freeing up existing "soft reserved" transit docks +/// If we're under load we want to allow for cycling, but if not we want to preserve already generated docks for use +#define SOFT_TRANSIT_RESERVATION_THRESHOLD (100 ** 2) + SUBSYSTEM_DEF(shuttle) name = "Shuttle" @@ -25,6 +31,8 @@ SUBSYSTEM_DEF(shuttle) var/list/transit_requesters = list() /// An associative list of the mobile docking ports that have failed a transit request, with the amount of times they've actually failed that transit request, up to MAX_TRANSIT_REQUEST_RETRIES var/list/transit_request_failures = list() + /// How many turfs our shuttles are currently utilizing in reservation space + var/transit_utilized = 0 /** * Emergency shuttle stuff @@ -199,6 +207,11 @@ SUBSYSTEM_DEF(shuttle) // This next one removes transit docks/zones that aren't // immediately being used. This will mean that the zone creation // code will be running a lot. + + // If we're below the soft reservation threshold, don't clear the old space + // We're better off holding onto it for now + if(transit_utilized < SOFT_TRANSIT_RESERVATION_THRESHOLD) + continue var/obj/docking_port/mobile/owner = T.owner if(owner) var/idle = owner.mode == SHUTTLE_IDLE @@ -211,7 +224,10 @@ SUBSYSTEM_DEF(shuttle) if(!SSmapping.clearing_reserved_turfs) while(transit_requesters.len) var/requester = popleft(transit_requesters) - var/success = generate_transit_dock(requester) + var/success = null + // Do not try and generate any transit if we're using more then our max already + if(transit_utilized < MAX_TRANSIT_TILE_COUNT) + success = generate_transit_dock(requester) if(!success) // BACK OF THE QUEUE transit_request_failures[requester]++ if(transit_request_failures[requester] < MAX_TRANSIT_REQUEST_RETRIES) @@ -614,6 +630,7 @@ SUBSYSTEM_DEF(shuttle) var/turf/midpoint = locate(transit_x, transit_y, bottomleft.z) if(!midpoint) + qdel(proposal) return FALSE var/area/old_area = midpoint.loc old_area.turfs_to_uncontain += proposal.reserved_turfs @@ -630,9 +647,18 @@ SUBSYSTEM_DEF(shuttle) // Add 180, because ports point inwards, rather than outwards new_transit_dock.setDir(angle2dir(dock_angle)) + // Proposals use 2 extra hidden tiles of space, from the cordons that surround them + transit_utilized += (proposal.width + 2) * (proposal.height + 2) M.assigned_transit = new_transit_dock + RegisterSignal(proposal, COMSIG_PARENT_QDELETING, PROC_REF(transit_space_clearing)) + return new_transit_dock +/// Gotta manage our space brother +/datum/controller/subsystem/shuttle/proc/transit_space_clearing(datum/turf_reservation/source) + SIGNAL_HANDLER + transit_utilized -= (source.width + 2) * (source.height + 2) + /datum/controller/subsystem/shuttle/Recover() initialized = SSshuttle.initialized if (istype(SSshuttle.mobile_docking_ports)) @@ -851,7 +877,7 @@ SUBSYSTEM_DEF(shuttle) /datum/controller/subsystem/shuttle/proc/load_template(datum/map_template/shuttle/loading_template) . = FALSE // Load shuttle template to a fresh block reservation. - preview_reservation = SSmapping.RequestBlockReservation(loading_template.width, loading_template.height, SSmapping.transit.z_value, /datum/turf_reservation/transit) + preview_reservation = SSmapping.RequestBlockReservation(loading_template.width, loading_template.height, type = /datum/turf_reservation/transit) if(!preview_reservation) CRASH("failed to reserve an area for shuttle template loading") var/turf/bottom_left = TURF_FROM_COORDS_LIST(preview_reservation.bottom_left_coords) diff --git a/code/datums/datum.dm b/code/datums/datum.dm index 13d44e4afaa..9e42adcda2b 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -105,12 +105,13 @@ datum_flags &= ~DF_USE_TAG //In case something tries to REF us weak_reference = null //ensure prompt GCing of weakref. - var/list/timers = active_timers - active_timers = null - for(var/datum/timedevent/timer as anything in timers) - if (timer.spent && !(timer.flags & TIMER_DELETE_ME)) - continue - qdel(timer) + if(active_timers) + var/list/timers = active_timers + active_timers = null + for(var/datum/timedevent/timer as anything in timers) + if (timer.spent && !(timer.flags & TIMER_DELETE_ME)) + continue + qdel(timer) #ifdef REFERENCE_TRACKING #ifdef REFERENCE_TRACKING_DEBUG diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 9ac44b4460d..884f87c7190 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -322,9 +322,10 @@ overlays.Cut() LAZYNULL(managed_overlays) - - QDEL_NULL(ai_controller) - QDEL_NULL(light) + if(ai_controller) + QDEL_NULL(ai_controller) + if(light) + QDEL_NULL(light) if (length(light_sources)) light_sources.Cut() @@ -1044,15 +1045,6 @@ /atom/proc/handle_atom_del(atom/deleting_atom) SEND_SIGNAL(src, COMSIG_ATOM_CONTENTS_DEL, deleting_atom) -/** - * called when the turf the atom resides on is ChangeTurfed - * - * Default behaviour is to loop through atom contents and call their HandleTurfChange() proc - */ -/atom/proc/HandleTurfChange(turf/changing_turf) - for(var/atom/current_atom as anything in src) - current_atom.HandleTurfChange(changing_turf) - /** * the vision impairment to give to the mob whose perspective is set to that atom * diff --git a/code/game/machinery/doors/firedoor.dm b/code/game/machinery/doors/firedoor.dm index 68f9da9f83c..e01aab37dd7 100644 --- a/code/game/machinery/doors/firedoor.dm +++ b/code/game/machinery/doors/firedoor.dm @@ -244,11 +244,12 @@ if(!checked_turf) continue - if(isclosedturf(checked_turf)) + + RegisterSignal(checked_turf, COMSIG_TURF_CHANGE, PROC_REF(adjacent_change)) + RegisterSignal(checked_turf, COMSIG_TURF_EXPOSE, PROC_REF(process_results)) + if(!isopenturf(checked_turf)) continue process_results(checked_turf) - RegisterSignal(checked_turf, COMSIG_TURF_EXPOSE, PROC_REF(process_results)) - /obj/machinery/door/firedoor/proc/unregister_adjacent_turfs(atom/old_loc) if(!loc) @@ -262,8 +263,14 @@ if(!checked_turf) continue + UnregisterSignal(checked_turf, COMSIG_TURF_CHANGE) UnregisterSignal(checked_turf, COMSIG_TURF_EXPOSE) +// If a turf adjacent to us changes, recalc our affecting areas when it's done yeah? +/obj/machinery/door/firedoor/proc/adjacent_change(turf/changed, path, list/new_baseturfs, flags, list/post_change_callbacks) + SIGNAL_HANDLER + post_change_callbacks += CALLBACK(src, PROC_REF(CalculateAffectingAreas)) + /obj/machinery/door/firedoor/proc/check_atmos(turf/checked_turf) var/datum/gas_mixture/environment = checked_turf.return_air() if(!environment) diff --git a/code/game/objects/effects/decals/decal.dm b/code/game/objects/effects/decals/decal.dm index 40e622b5225..399c81455d0 100644 --- a/code/game/objects/effects/decals/decal.dm +++ b/code/game/objects/effects/decals/decal.dm @@ -9,6 +9,10 @@ . = ..() if(turf_loc_check && (!isturf(loc) || NeverShouldHaveComeHere(loc))) return INITIALIZE_HINT_QDEL + var/static/list/loc_connections = list( + COMSIG_TURF_CHANGED = PROC_REF(handle_turf_change), + ) + AddElement(/datum/element/connect_loc, loc_connections) /obj/effect/decal/blob_act(obj/structure/blob/B) if(B && B.loc == loc) @@ -24,9 +28,12 @@ if(!(resistance_flags & FIRE_PROOF)) //non fire proof decal or being burned by lava qdel(src) -/obj/effect/decal/HandleTurfChange(turf/T) - ..() - if(T == loc && NeverShouldHaveComeHere(T)) +/obj/effect/decal/proc/handle_turf_change(turf/source, path, list/new_baseturfs, flags, list/post_change_callbacks) + SIGNAL_HANDLER + post_change_callbacks += CALLBACK(src, PROC_REF(sanity_check_self)) + +/obj/effect/decal/proc/sanity_check_self(turf/changed) + if(changed == loc && NeverShouldHaveComeHere(changed)) qdel(src) ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/code/game/turfs/change_turf.dm b/code/game/turfs/change_turf.dm index 3a1cc8aaac3..b5ccc15b3db 100644 --- a/code/game/turfs/change_turf.dm +++ b/code/game/turfs/change_turf.dm @@ -77,8 +77,11 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( var/old_directional_opacity = directional_opacity var/old_dynamic_lumcount = dynamic_lumcount var/old_rcd_memory = rcd_memory - var/old_space_lit = space_lit var/old_explosion_throw_details = explosion_throw_details + var/old_opacity = opacity + // I'm so sorry brother + // This is used for a starlight optimization + var/old_light_range = light_range // We get just the bits of explosive_resistance that aren't the turf var/old_explosive_resistance = explosive_resistance - get_explosive_block() var/old_lattice_underneath = lattice_underneath @@ -132,17 +135,13 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( lattice_underneath = old_lattice_underneath - var/area/our_area = new_turf.loc - if(new_turf.space_lit && !our_area.area_has_base_lighting) - // We are guarenteed to have these overlays because of how generation works - var/mutable_appearance/overlay = GLOB.fullbright_overlays[GET_TURF_PLANE_OFFSET(src) + 1] - new_turf.add_overlay(overlay) - else if (old_space_lit && !our_area.area_has_base_lighting) - var/mutable_appearance/overlay = GLOB.fullbright_overlays[GET_TURF_PLANE_OFFSET(src) + 1] - new_turf.cut_overlay(overlay) - if(SSlighting.initialized) - new_turf.lighting_object = old_lighting_object + // Space tiles should never have lighting objects + if(!space_lit) + // Should have a lighting object if we never had one + lighting_object = old_lighting_object || new /datum/lighting_object(src) + else if (old_lighting_object) + qdel(old_lighting_object, force = TRUE) directional_opacity = old_directional_opacity recalculate_directional_opacity() @@ -150,15 +149,40 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( if(lighting_object && !lighting_object.needs_update) lighting_object.update() + // If we're space, then we're either lit, or not, and impacting our neighbors, or not + if(isspaceturf(src) && CONFIG_GET(flag/starlight)) + var/turf/open/space/lit_turf = src + // This also counts as a removal, so we need to do a full rebuild + if(!ispath(old_type, /turf/open/space)) + lit_turf.update_starlight() + for(var/turf/open/space/space_tile in RANGE_TURFS(1, src) - src) + space_tile.update_starlight() + else if(old_light_range) + lit_turf.enable_starlight() + + // If we're a cordon we count against a light, but also don't produce any ourselves + else if (istype(src, /turf/cordon) && CONFIG_GET(flag/starlight)) + // This counts as removing a source of starlight, so we need to update the space tile to inform it + if(!ispath(old_type, /turf/open/space)) + for(var/turf/open/space/space_tile in RANGE_TURFS(1, src)) + space_tile.update_starlight() + + // If we're not either, but were formerly a space turf, then we want light + else if(ispath(old_type, /turf/open/space) && CONFIG_GET(flag/starlight)) for(var/turf/open/space/space_tile in RANGE_TURFS(1, src)) - space_tile.update_starlight() + space_tile.enable_starlight() + + if(old_opacity != opacity && SSticker) + GLOB.cameranet.bareMajorChunkChange(src) // We will only run this logic if the tile is not on the prime z layer, since we use area overlays to cover that if(SSmapping.z_level_to_plane_offset[z]) + var/area/our_area = new_turf.loc if(our_area.lighting_effects) new_turf.add_overlay(our_area.lighting_effects[SSmapping.z_level_to_plane_offset[z] + 1]) - if(flags_1 & INITIALIZED_1) // only queue for smoothing if SSatom initialized us + // only queue for smoothing if SSatom initialized us, and we'd be changing smoothing state + if(flags_1 & INITIALIZED_1) QUEUE_SMOOTH_NEIGHBORS(src) QUEUE_SMOOTH(src) @@ -185,7 +209,8 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( if(stashed_group.should_display || SSair.display_all_groups) stashed_group.display_turf(new_turf) else - SSair.remove_from_active(src) //Clean up wall excitement, and refresh excited groups + if(excited || excited_group) + SSair.remove_from_active(src) //Clean up wall excitement, and refresh excited groups if(ispath(path,/turf/closed) || ispath(path,/turf/cordon)) flags |= CHANGETURF_RECALC_ADJACENT return ..() @@ -199,13 +224,6 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( SSair.add_to_active(src) else //In effect, I want closed turfs to make their tile active when sheered, but we need to queue it since they have no adjacent turfs CALCULATE_ADJACENT_TURFS(src, (!(ispath(oldType, /turf/closed) && isopenturf(src)) ? NORMAL_TURF : MAKE_ACTIVE)) - //update firedoor adjacency - var/list/turfs_to_check = get_adjacent_open_turfs(src) | src - for(var/turf/check_turf in turfs_to_check) - for(var/obj/machinery/door/firedoor/FD in check_turf) - FD.CalculateAffectingAreas() - - HandleTurfChange(src) /turf/open/AfterChange(flags, oldType) ..() diff --git a/code/game/turfs/open/openspace.dm b/code/game/turfs/open/openspace.dm index f24d5d8fb52..ba16fc0872c 100644 --- a/code/game/turfs/open/openspace.dm +++ b/code/game/turfs/open/openspace.dm @@ -16,6 +16,8 @@ /turf/open/openspace/airless/planetary planetary_atmos = TRUE +// Reminder, any behavior code written here needs to be duped to /turf/open/space/openspace +// I am so sorry /turf/open/openspace/Initialize(mapload) // handle plane and layer here so that they don't cover other obs/turfs in Dream Maker . = ..() RegisterSignal(src, COMSIG_ATOM_INITIALIZED_ON, PROC_REF(on_atom_created)) diff --git a/code/game/turfs/open/space/space.dm b/code/game/turfs/open/space/space.dm index f2ae490bb0a..0a9202ba00c 100644 --- a/code/game/turfs/open/space/space.dm +++ b/code/game/turfs/open/space/space.dm @@ -8,6 +8,7 @@ temperature = TCMB thermal_conductivity = OPEN_HEAT_TRANSFER_COEFFICIENT heat_capacity = 700000 + var/starlight_source_count = 0 var/destination_z var/destination_x @@ -101,15 +102,23 @@ /turf/open/space/remove_air(amount) return null +/// Updates starlight. Called when we're unsure of a turf's starlight state +/// Returns TRUE if we succeed, FALSE otherwise /turf/open/space/proc/update_starlight() - if(CONFIG_GET(flag/starlight)) - for(var/t in RANGE_TURFS(1,src)) //RANGE_TURFS is in code\__HELPERS\game.dm - if(isspaceturf(t)) - //let's NOT update this that much pls - continue - set_light(2) - return - set_light(0) + for(var/t in RANGE_TURFS(1,src)) //RANGE_TURFS is in code\__HELPERS\game.dm + // I've got a lot of cordons near spaceturfs, be good kids + if(isspaceturf(t) || istype(t, /turf/cordon)) + //let's NOT update this that much pls + continue + enable_starlight() + return TRUE + set_light(0) + return FALSE + +/// Turns on the stars, if they aren't already +/turf/open/space/proc/enable_starlight() + if(!light_range) + set_light(2) /turf/open/space/attack_paw(mob/user, list/modifiers) return attack_hand(user, modifiers) @@ -243,7 +252,6 @@ /turf/open/space/openspace/Initialize(mapload) // handle plane and layer here so that they don't cover other obs/turfs in Dream Maker . = ..() icon_state = "invisible" - update_starlight() return INITIALIZE_HINT_LATELOAD /turf/open/space/openspace/LateInitialize() @@ -284,16 +292,25 @@ return TRUE return FALSE -/turf/open/space/openspace/update_starlight() - if(!CONFIG_GET(flag/starlight)) - return +/turf/open/space/openspace/enable_starlight() var/turf/below = SSmapping.get_turf_below(src) + // Override = TRUE beacuse we could have our starlight updated many times without a failure, which'd trigger this + RegisterSignal(below, COMSIG_TURF_CHANGE, PROC_REF(on_below_change), override = TRUE) if(!isspaceturf(below)) return - for(var/t in RANGE_TURFS(1,src)) //RANGE_TURFS is in code\__HELPERS\game.dm - if(isspaceturf(t)) - //let's NOT update this that much pls - continue - set_light(2) + set_light(2) + +/turf/open/space/openspace/update_starlight() + . = ..() + if(.) return - set_light(0) + // If we're here, the starlight is not to be + var/turf/below = SSmapping.get_turf_below(src) + UnregisterSignal(below, COMSIG_TURF_CHANGE) + +/turf/open/space/openspace/proc/on_below_change(turf/source, path, list/new_baseturfs, flags, list/post_change_callbacks) + SIGNAL_HANDLER + if(isspaceturf(source) && !ispath(path, /turf/open/space)) + set_light(2) + else if(!isspaceturf(source) && ispath(path, /turf/open/space)) + set_light(0) diff --git a/code/game/turfs/open/space/transit.dm b/code/game/turfs/open/space/transit.dm index 74ba0e6fb0a..2f4c49fe5b1 100644 --- a/code/game/turfs/open/space/transit.dm +++ b/code/game/turfs/open/space/transit.dm @@ -15,9 +15,10 @@ for(var/atom/movable/movable in src) throw_atom(movable) -/turf/open/space/transit/clear_signal_refs() +/turf/open/space/transit/Destroy() //Signals are NOT removed from turfs upon replacement, and we get replaced ALOT, so unregister our signal UnregisterSignal(src, COMSIG_TURF_RESERVATION_RELEASED) + return ..() /turf/open/space/transit/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir) . = ..() diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 4bbe3f8f2cc..17da71ea408 100755 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -147,10 +147,6 @@ GLOBAL_LIST_EMPTY(station_turfs) if (smoothing_flags & (SMOOTH_CORNERS|SMOOTH_BITMASK)) QUEUE_SMOOTH(src) - // visibilityChanged() will never hit any path with side effects during mapload - if (!mapload) - visibilityChanged() - for(var/atom/movable/content as anything in src) Entered(content, null) @@ -186,12 +182,13 @@ GLOBAL_LIST_EMPTY(station_turfs) if(!changing_turf) stack_trace("Incorrect turf deletion") changing_turf = FALSE - var/turf/T = SSmapping.get_turf_above(src) - if(T) - T.multiz_turf_del(src, DOWN) - T = SSmapping.get_turf_below(src) - if(T) - T.multiz_turf_del(src, UP) + if(GET_LOWEST_STACK_OFFSET(z)) + var/turf/T = SSmapping.get_turf_above(src) + if(T) + T.multiz_turf_del(src, DOWN) + T = SSmapping.get_turf_below(src) + if(T) + T.multiz_turf_del(src, UP) if(force) ..() //this will completely wipe turf state @@ -199,7 +196,6 @@ GLOBAL_LIST_EMPTY(station_turfs) for(var/A in B.contents) qdel(A) return - visibilityChanged() QDEL_LIST(blueprint_data) flags_1 &= ~INITIALIZED_1 requires_activation = FALSE diff --git a/code/modules/lighting/lighting_atom.dm b/code/modules/lighting/lighting_atom.dm index f0487b86b1a..e11981aacee 100644 --- a/code/modules/lighting/lighting_atom.dm +++ b/code/modules/lighting/lighting_atom.dm @@ -28,9 +28,7 @@ /// 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() - set waitfor = FALSE - if (QDELETED(src)) - return + SHOULD_NOT_SLEEP(TRUE) if(light_system != STATIC_LIGHT) CRASH("update_light() for [src] with following light_system value: [light_system]") diff --git a/code/modules/lighting/lighting_object.dm b/code/modules/lighting/lighting_object.dm index cd590008720..5f7b16eb7d1 100644 --- a/code/modules/lighting/lighting_object.dm +++ b/code/modules/lighting/lighting_object.dm @@ -33,7 +33,7 @@ GLOBAL_LIST_EMPTY(default_lighting_underlays_by_z) // 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() + space_tile.enable_starlight() needs_update = TRUE SSlighting.objects_queue += src diff --git a/code/modules/lighting/lighting_setup.dm b/code/modules/lighting/lighting_setup.dm index 541569be0fb..d6bf19711ef 100644 --- a/code/modules/lighting/lighting_setup.dm +++ b/code/modules/lighting/lighting_setup.dm @@ -7,6 +7,6 @@ for(var/turf/T as anything in A.get_contained_turfs()) if(T.space_lit) continue - new/datum/lighting_object(T) + new /datum/lighting_object(T) CHECK_TICK CHECK_TICK diff --git a/code/modules/lighting/lighting_turf.dm b/code/modules/lighting/lighting_turf.dm index 8692fc475dc..d093961411e 100644 --- a/code/modules/lighting/lighting_turf.dm +++ b/code/modules/lighting/lighting_turf.dm @@ -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 /datum/lighting_object(src) // Used to get a scaled lumcount. /turf/proc/get_lumcount(minlum = 0, maxlum = 1) @@ -81,19 +81,20 @@ reconsider_lights() return directional_opacity = NONE - for(var/atom/movable/opacity_source as anything in opacity_sources) - if(opacity_source.flags_1 & ON_BORDER_1) - directional_opacity |= opacity_source.dir - else //If fulltile and opaque, then the whole tile blocks view, no need to continue checking. - directional_opacity = ALL_CARDINALS - break + if(opacity_sources) + for(var/atom/movable/opacity_source as anything in opacity_sources) + if(opacity_source.flags_1 & ON_BORDER_1) + directional_opacity |= opacity_source.dir + else //If fulltile and opaque, then the whole tile blocks view, no need to continue checking. + directional_opacity = ALL_CARDINALS + break if(. != directional_opacity && (. == ALL_CARDINALS || directional_opacity == ALL_CARDINALS)) reconsider_lights() //The lighting system only cares whether the tile is fully concealed from all directions or not. ///Transfer the lighting of one area to another /turf/proc/transfer_area_lighting(area/old_area, area/new_area) - if(SSlighting.initialized) + if(SSlighting.initialized && !space_lit) if (new_area.static_lighting != old_area.static_lighting) if (new_area.static_lighting) lighting_build_overlay() @@ -109,3 +110,7 @@ cut_overlay(old_area.lighting_effects[index]) if(new_area.lighting_effects) add_overlay(new_area.lighting_effects[index]) + + // If we're changing into an area with no lighting, and we're lit, light ourselves + if(!new_area.lighting_effects && old_area.lighting_effects && space_lit) + overlays += GLOB.fullbright_overlays[GET_TURF_PLANE_OFFSET(src) + 1] diff --git a/code/modules/mapping/reader.dm b/code/modules/mapping/reader.dm index 4eb1512191a..f6538d4ec1d 100644 --- a/code/modules/mapping/reader.dm +++ b/code/modules/mapping/reader.dm @@ -800,11 +800,11 @@ GLOBAL_LIST_EMPTY(map_model_default) //The next part of the code assumes there's ALWAYS an /area AND a /turf on a given tile //first instance the /area and remove it from the members list index = members.len + var/area/old_area if(members[index] != /area/template_noop) - var/area/area_instance if(members_attributes[index] != default_list) world.preloader_setup(members_attributes[index], members[index])//preloader for assigning set variables on atom creation - area_instance = loaded_areas[members[index]] + var/area/area_instance = loaded_areas[members[index]] if(!area_instance) var/area_type = members[index] // If this parsed map doesn't have that area already, we check the global cache @@ -817,7 +817,7 @@ GLOBAL_LIST_EMPTY(map_model_default) loaded_areas[area_type] = area_instance if(!new_z) - var/area/old_area = crds.loc + old_area = crds.loc old_area.turfs_to_uncontain += crds area_instance.contained_turfs.Add(crds) area_instance.contents.Add(crds) @@ -845,6 +845,9 @@ GLOBAL_LIST_EMPTY(map_model_default) if(GLOB.use_preloader && instance)//second preloader pass, for those atoms that don't ..() in New() world.preloader_load(instance) + // If this isn't template work, we didn't change our turf and we changed area, then we've gotta handle area lighting transfer + else if(!no_changeturf && old_area) + crds.transfer_area_lighting(old_area, crds.loc) MAPLOADING_CHECK_TICK //finally instance all remainings objects/mobs diff --git a/code/modules/mapping/space_management/zlevel_manager.dm b/code/modules/mapping/space_management/zlevel_manager.dm index 1c2e73ab003..b7664f09ff2 100644 --- a/code/modules/mapping/space_management/zlevel_manager.dm +++ b/code/modules/mapping/space_management/zlevel_manager.dm @@ -19,6 +19,8 @@ manage_z_level(S, filled_with_space = FALSE) generate_z_level_linkages() // Default Zs don't use add_new_zlevel() so they don't automatically generate z-linkages. +/// Generates a real, honest to god new z level. Will create the actual space, and also generate a datum that holds info about the new plot of land +/// Accepts the name, traits list, datum type, and if we should manage the turfs we create /datum/controller/subsystem/mapping/proc/add_new_zlevel(name, traits = list(), z_type = /datum/space_level, contain_turfs = TRUE) UNTIL(!adding_new_zlevel) adding_new_zlevel = TRUE diff --git a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm index 27920dc2513..facffbc5cce 100644 --- a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm +++ b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm @@ -42,8 +42,11 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new) /datum/cameranet/proc/chunkGenerated(x, y, z) x = GET_CHUNK_COORD(x) y = GET_CHUNK_COORD(y) - var/turf/lowest = get_lowest_turf(locate(x, y, z)) - return chunks["[x],[y],[lowest.z]"] + if(GET_LOWEST_STACK_OFFSET(z) != 0) + var/turf/lowest = get_lowest_turf(locate(x, y, z)) + return chunks["[x],[y],[lowest.z]"] + + return chunks["[x],[y],[z]"] // Returns the chunk in the x, y, z. // If there is no chunk, it creates a new chunk and returns that. @@ -150,6 +153,18 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new) chunk.cameras["[T.z]"] |= c chunk.hasChanged(update_delay_buffer = update_delay_buffer) +/// A faster, turf only version of [/datum/cameranet/proc/majorChunkChange] +/// For use in sensitive code, be careful with it +/datum/cameranet/proc/bareMajorChunkChange(turf/changed) + var/x1 = max(1, changed.x - (CHUNK_SIZE / 2)) + var/y1 = max(1, changed.y - (CHUNK_SIZE / 2)) + var/x2 = min(world.maxx, changed.x + (CHUNK_SIZE / 2)) + var/y2 = min(world.maxy, changed.y + (CHUNK_SIZE / 2)) + for(var/x = x1; x <= x2; x += CHUNK_SIZE) + for(var/y = y1; y <= y2; y += CHUNK_SIZE) + var/datum/camerachunk/chunk = chunkGenerated(x, y, changed.z) + chunk?.hasChanged() + /// Will check if a mob is on a viewable turf. Returns 1 if it is, otherwise returns 0. /datum/cameranet/proc/checkCameraVis(mob/living/target) var/turf/position = get_turf(target)