From 8f98047bfeba471f6c5532d46583f1c6415d6d9a Mon Sep 17 00:00:00 2001 From: RikuTheKiller <88713943+RikuTheKiller@users.noreply.github.com> Date: Sat, 17 Jan 2026 00:18:41 +0200 Subject: [PATCH] [TM FIRST] Literally just halves the performance cost of cameras (#94522) ## About The Pull Request Title, I used an alist and some inlined list operation tricks to make camera chunk updates twice as fast. The data in the screenshots is from me updating every single camera chunk on Meta. I did also test on Icebox and it scaled just as well there as it did on Meta, which means that multi-z doesn't cause any issues for these changes. The correct way to compare these costs is to take the total cost, divide it by the count, and then compare them relatively to see which is higher/lower and by how much. Before: image After (combined keys): The value of "update visible turfs" here is just a merged version of "collect turfs" + "the & operator" + "update visible turfs" image After 2 (separate keys, but fucked up counts): Not as directly comparable cuz the counts are fucked up, gotta do some math to figure it out. image The biggest remaining performance bottleneck, which now accounts for ~70% of all the performance cost of camera chunks, is camera.can_see() being slow as balls. You can see this in the After 2 image. The only cost of doing this is that the format of the list is now "alist[turf] = null" instead of "list[i] = turf", which makes checking for individual turfs cumbersome. Entirely possible and and still relatively performant, but the syntax is just cumbersome. Said single checks are only used by SyndEye as of now. ## Why It's Good For The Game On live, it seems that camera chunks are consistently the biggest and fattest source of performance loss and overtime in the entire game. Obviously halving that cost is good for the game. ## Changelog :cl: fix: Camera chunks, the biggest source of lag in the entire game, now run twice as fast. /:cl: --- code/game/machinery/camera/camera.dm | 16 +++++++++++----- .../mob/living/silicon/ai/freelook/chunk.dm | 6 +++--- .../file_system/programs/secureye.dm | 2 +- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index b88acdcbc01..38bdd0e2f00 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -401,19 +401,24 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera/xray, 0) return FALSE return TRUE -/// Returns a list of turfs in this camera's view. -/// This includes turfs that are "obscured by darkness" from the camera's POV. +/// Returns an alist of turfs in this camera's view. This includes turfs that are "obscured by darkness" from the camera's POV. +/// Format is "alist[turf] = null", if you need to check individual objects use "length(can_see & list(turf))". Only "&" and "in" work for checking contents, but "in" is much slower. +/// Always have the return value of can_see as the left-hand operand, otherwise it uses list checks instead of alist checks and your CPU time gets thrown in a blender. /obj/machinery/camera/proc/can_see() - var/list/see = null + var/alist/see = alist() var/turf/pos = get_turf(src) var/turf/directly_above = GET_TURF_ABOVE(pos) var/check_lower = pos != get_lowest_turf(pos) var/check_higher = directly_above && istransparentturf(directly_above) && (pos != get_highest_turf(pos)) if(isXRay()) - see = RANGE_TURFS(view_range, pos) + see += RANGE_TURFS(view_range, pos) else - see = get_hear_turfs(view_range, pos) + var/lum = pos.luminosity + pos.luminosity = 6 + for(var/turf/turf in view(view_range, pos)) + see += turf + pos.luminosity = lum if(check_lower || check_higher) // Haha datum var access KILL ME @@ -428,6 +433,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera/xray, 0) while(above && istransparentturf(above)) see += RANGE_TURFS(1, above) above = GET_TURF_ABOVE(above) + return see /obj/machinery/camera/proc/Togglelight(on=0) diff --git a/code/modules/mob/living/silicon/ai/freelook/chunk.dm b/code/modules/mob/living/silicon/ai/freelook/chunk.dm index 23098ab15e9..5a9c093a31b 100644 --- a/code/modules/mob/living/silicon/ai/freelook/chunk.dm +++ b/code/modules/mob/living/silicon/ai/freelook/chunk.dm @@ -121,7 +121,8 @@ if(get_dist(point, current_camera) > CHUNK_SIZE + (CHUNK_SIZE / 2)) continue - for(var/turf/vis_turf as anything in turfs & current_camera.can_see()) + // The return value of can_see being the left-hand operand here is a load-bearing performance pillar + for(var/turf/vis_turf as anything in current_camera.can_see() & turfs) updated_visible_turfs[vis_turf] = vis_turf ///new turfs that we couldnt see last update but can now @@ -164,7 +165,6 @@ client.images += active_static_images - /// Create a new camera chunk, since the chunks are made as they are needed. /datum/camerachunk/New(x, y, lower_z) x = GET_CHUNK_COORD(x) @@ -206,7 +206,7 @@ if(!camera.can_use()) continue - for(var/turf/vis_turf as anything in turfs & camera.can_see()) + for(var/turf/vis_turf as anything in camera.can_see() & turfs) visibleTurfs[vis_turf] = vis_turf for(var/turf/obscured_turf as anything in turfs - visibleTurfs) diff --git a/code/modules/modular_computers/file_system/programs/secureye.dm b/code/modules/modular_computers/file_system/programs/secureye.dm index 7a91b9f00aa..491d1402512 100644 --- a/code/modules/modular_computers/file_system/programs/secureye.dm +++ b/code/modules/modular_computers/file_system/programs/secureye.dm @@ -174,7 +174,7 @@ CRASH("[src] was able to track [target] through /datum/trackable, but was not on a visible turf to cameras.") for(var/obj/machinery/camera/cameras as anything in target_camerachunk.cameras[target.z]) // We need to find a particular camera that can see this turf - if(!(target_turf in cameras.can_see())) + if(length(cameras.can_see() & list(target_turf))) continue var/new_camera = WEAKREF(cameras) if(camera_ref == new_camera)