From 0fedbb92572d5930d2b137b8e951e53026e54998 Mon Sep 17 00:00:00 2001 From: Farie82 Date: Fri, 12 May 2023 21:58:23 +0200 Subject: [PATCH] Camera net/chunk refactor. Slight performance improvements and makes it GC properly (#20302) * Add SScamera * Refactor camera * Refactor how camera chunks add/remove cams * Use CAMERA_VIEW_DISTANCE to determine max view dist of cam * Fix movable cameras and optimise them * Optimise update() * Ensure aiEyes get removed properly when deleted * Final optimisations and refactoring * Fix cameras being EMPed * Shits fixed * AA review * Fix merge mistake * Add comment about early return * Seans review * the it the Co-authored-by: S34N <12197162+S34NW@users.noreply.github.com> * Update code/game/machinery/camera/camera.dm Co-authored-by: Ryan <80364400+Sirryan2002@users.noreply.github.com> * Ensure the robit has a camera before updating it --------- Co-authored-by: S34N <12197162+S34NW@users.noreply.github.com> Co-authored-by: Charlie <69320440+hal9000PR@users.noreply.github.com> Co-authored-by: Ryan <80364400+Sirryan2002@users.noreply.github.com> --- code/__DEFINES/dcs/signals.dm | 5 + code/__DEFINES/misc_defines.dm | 2 + code/__DEFINES/subsystems.dm | 1 + code/controllers/subsystem/camera.dm | 24 +++ code/game/machinery/camera/camera.dm | 130 ++++++++------ code/game/machinery/camera/camera_assembly.dm | 9 +- .../clothing/spacesuits/ert_hardsuits.dm | 3 +- code/modules/mob/living/silicon/ai/ai_mob.dm | 2 +- .../living/silicon/ai/freelook/cameranet.dm | 78 ++++---- .../mob/living/silicon/ai/freelook/chunk.dm | 169 +++++++++--------- .../mob/living/silicon/robot/robot_mob.dm | 20 +-- paradise.dme | 1 + 12 files changed, 247 insertions(+), 197 deletions(-) create mode 100644 code/controllers/subsystem/camera.dm diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index acc8cd6ffbe..259bcf07500 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -504,6 +504,11 @@ ///from base power_change() when power is restored #define COMSIG_MACHINERY_POWER_RESTORED "machinery_power_restored" +// /obj/machinery/camera signals +#define COMSIG_CAMERA_ON "camera_on" +#define COMSIG_CAMERA_OFF "camera_off" +#define COMSIG_CAMERA_MOVED "camera_moved" + // /obj/item signals ///from base of obj/item/attack(): (/mob/living/target, /mob/living/user) diff --git a/code/__DEFINES/misc_defines.dm b/code/__DEFINES/misc_defines.dm index 8f1dc6098f5..fd6abbb1f44 100644 --- a/code/__DEFINES/misc_defines.dm +++ b/code/__DEFINES/misc_defines.dm @@ -153,6 +153,8 @@ #define MIDNIGHT_ROLLOVER 864000 //number of deciseconds in a day +#define CAMERA_VIEW_DISTANCE 7 +#define CAMERA_CHUNK_SIZE 16 // Only chunk sizes that are to the power of 2. E.g: 2, 4, 8, 16, etc.. //Turf wet states diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 56b05d60411..8b2ceed2ef8 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -97,6 +97,7 @@ #define FIRE_PRIORITY_GARBAGE 15 #define FIRE_PRIORITY_AIR 20 #define FIRE_PRIORITY_NPC 20 +#define FIRE_PRIORITY_CAMERA 20 #define FIRE_PRIORITY_PROCESS 25 #define FIRE_PRIORITY_THROWING 25 #define FIRE_PRIORITY_SPACEDRIFT 30 diff --git a/code/controllers/subsystem/camera.dm b/code/controllers/subsystem/camera.dm new file mode 100644 index 00000000000..5369c9b9c37 --- /dev/null +++ b/code/controllers/subsystem/camera.dm @@ -0,0 +1,24 @@ +// Subsystem that controls the camera vision tiles +SUBSYSTEM_DEF(camera) + name = "Camera" + flags = SS_BACKGROUND | SS_NO_INIT + priority = FIRE_PRIORITY_CAMERA + runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + wait = 2.5 SECONDS + offline_implications = "AI camera view won't update. No immediate action is needed." + var/list/chunk_queue = list() + +/datum/controller/subsystem/camera/fire(resumed) + for(var/datum/camerachunk/chunk as anything in chunk_queue) + chunk.update() + chunk_queue -= chunk + if(MC_TICK_CHECK) + return + + +/datum/controller/subsystem/camera/proc/queue(datum/camerachunk/chunk) + if(!chunk_queue[chunk]) + chunk_queue[chunk] = chunk + +/datum/controller/subsystem/camera/proc/remove_from_queue(datum/camerachunk/chunk) + chunk_queue -= chunk diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index 52d13d45ca0..2953fbf5133 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -14,6 +14,7 @@ max_integrity = 100 integrity_failure = 50 var/list/network = list("SS13") + var/list/previous_network var/c_tag = null var/c_tag_order = 999 var/status = TRUE @@ -21,10 +22,12 @@ var/start_active = FALSE //If it ignores the random chance to start broken on round start var/invuln = null var/obj/item/camera_assembly/assembly = null + /// If this camera should be added to the camera network and update the camera network when it moves around + var/part_of_camera_network //OTHER - var/view_range = 7 + var/view_range = CAMERA_VIEW_DISTANCE var/short_range = 2 var/alarm_on = FALSE @@ -34,7 +37,7 @@ var/toggle_sound = 'sound/items/wirecutter.ogg' blocks_emissive = EMISSIVE_BLOCK_GENERIC -/obj/machinery/camera/Initialize(mapload) +/obj/machinery/camera/Initialize(mapload, should_add_to_cameranet = TRUE) . = ..() wires = new(src) assembly = new(src) @@ -43,22 +46,27 @@ assembly.update_icon() GLOB.cameranet.cameras += src - GLOB.cameranet.addCamera(src) + part_of_camera_network = should_add_to_cameranet + if(part_of_camera_network) + GLOB.cameranet.addCamera(src) if(isturf(loc)) LAZYADD(get_area(src).cameras, UID()) if(is_station_level(z) && prob(3) && !start_active) - toggle_cam(null, FALSE) + turn_off(null, FALSE) wires.cut_all() /obj/machinery/camera/proc/set_area_motion(area/A) area_motion = A +/obj/machinery/camera/Moved(atom/OldLoc, Dir, Forced) + . = ..() + SEND_SIGNAL(src, COMSIG_CAMERA_MOVED, OldLoc) + /obj/machinery/camera/Destroy() SStgui.close_uis(wires) - toggle_cam(null, FALSE) //kick anyone viewing out + kick_out_watchers() QDEL_NULL(assembly) QDEL_NULL(wires) - GLOB.cameranet.removeCamera(src) //Will handle removal from the camera network and the chunks, so we don't need to worry about that GLOB.cameranet.cameras -= src if(isarea(get_area(src))) LAZYREMOVE(get_area(src).cameras, UID()) @@ -85,36 +93,26 @@ return if(!isEmpProof()) if(prob(150/severity)) - update_icon(UPDATE_ICON_STATE) - var/list/previous_network = network - network = list() - GLOB.cameranet.removeCamera(src) - stat |= EMPED - set_light(0) - emped = emped+1 //Increase the number of consecutive EMP's - update_icon(UPDATE_ICON_STATE) - var/thisemp = emped //Take note of which EMP this proc is for - spawn(900) - if(!QDELETED(src)) - if(emped == thisemp) //Only fix it if the camera hasn't been EMP'd again - network = previous_network - stat &= ~EMPED - update_icon(UPDATE_ICON_STATE) - if(can_use()) - GLOB.cameranet.addCamera(src) - emped = 0 //Resets the consecutive EMP count - for(var/mob/M in GLOB.player_list) - if(M.client && M.client.eye == src) - M.reset_perspective(null) - to_chat(M, "The screen bursts into static.") + if(!(stat & EMPED)) + previous_network = network + network = list() + stat |= EMPED + turn_off(null, FALSE, TRUE) + addtimer(CALLBACK(src, PROC_REF(reactivate_after_emp)), 90 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE) ..() +/obj/machinery/camera/proc/reactivate_after_emp() + network = previous_network + previous_network = null + stat &= ~EMPED + turn_on(null, FALSE, TRUE) + /obj/machinery/camera/ex_act(severity) if(invuln) return ..() -/obj/machinery/camera/proc/setViewRange(num = 7) +/obj/machinery/camera/proc/setViewRange(num = CAMERA_VIEW_DISTANCE) view_range = num GLOB.cameranet.updateVisibility(src, 0) @@ -261,34 +259,56 @@ else icon_state = "[initial(icon_state)]" -/obj/machinery/camera/proc/toggle_cam(mob/user, displaymessage = TRUE) - status = !status - if(can_use()) - GLOB.cameranet.addCamera(src) - if(isturf(loc)) - LAZYADD(get_area(src).cameras, UID()) - else - set_light(0) - GLOB.cameranet.removeCamera(src) - if(isarea(get_area(src))) - LAZYREMOVE(get_area(src).cameras, UID()) - GLOB.cameranet.updateChunk(x, y, z) - var/change_msg = "deactivates" +/obj/machinery/camera/proc/toggle_cam(mob/user, display_message = TRUE) if(status) - change_msg = "reactivates" - if(displaymessage) + turn_off(user, display_message) + return + + turn_on(user, display_message) + +/obj/machinery/camera/proc/turn_on(mob/user, display_message = TRUE, emp_recover = FALSE) + if(status && !emp_recover) + return + status = TRUE + if(!emp_recover && isturf(loc)) + LAZYADD(get_area(src).cameras, UID()) + + if(display_message) if(user) - visible_message("[user] [change_msg] [src]!") + visible_message("[user] reactivates [src]!") add_hiddenprint(user) else - visible_message("\The [src] [change_msg]!") + visible_message("\The [src] reactivates!") + playsound(loc, toggle_sound, 100, TRUE) + update_icon(UPDATE_ICON_STATE) + SEND_SIGNAL(src, COMSIG_CAMERA_ON, user, display_message) +/obj/machinery/camera/proc/turn_off(mob/user, display_message = TRUE, emped = FALSE) + if(!status && !emped) + return + + if(!emped) + status = FALSE + if(isarea(get_area(src))) + LAZYREMOVE(get_area(src).cameras, UID()) + + set_light(0) + + if(display_message) + if(user) + visible_message("[user] deactivates [src]!") + add_hiddenprint(user) + else + visible_message("\The [src] deactivates!") playsound(loc, toggle_sound, 100, 1) + update_icon(UPDATE_ICON_STATE) - // now disconnect anyone using the camera - //Apparently, this will disconnect anyone even if the camera was re-activated. - //I guess that doesn't matter since they can't use it anyway? + kick_out_watchers() + + SEND_SIGNAL(src, COMSIG_CAMERA_OFF, user, display_message, emped) + +/obj/machinery/camera/proc/kick_out_watchers() for(var/mob/O in GLOB.player_list) if(O.client && O.client.eye == src) O.reset_perspective(null) @@ -395,6 +415,12 @@ assembly.update_icon() /obj/machinery/camera/portable/process() //Updates whenever the camera is moved. - if(GLOB.cameranet && get_turf(src) != prev_turf) - GLOB.cameranet.updatePortableCamera(src) - prev_turf = get_turf(src) + if(!part_of_camera_network) + return PROCESS_KILL // Stop wasting performance + + if(get_turf(src) == prev_turf) + return + + SEND_SIGNAL(src, COMSIG_CAMERA_MOVED, prev_turf) + GLOB.cameranet.updatePortableCamera(src, prev_turf) + prev_turf = get_turf(src) diff --git a/code/game/machinery/camera/camera_assembly.dm b/code/game/machinery/camera/camera_assembly.dm index a6b3faff944..64e88550121 100644 --- a/code/game/machinery/camera/camera_assembly.dm +++ b/code/game/machinery/camera/camera_assembly.dm @@ -96,16 +96,15 @@ var/temptag = "[sanitize(camera_area.name)] ([rand(1, 999)])" input = strip_html(input(usr, "How would you like to name the camera?", "Set Camera Name", temptag)) state = ASSEMBLY_BUILT - var/obj/machinery/camera/C = new(loc) + var/list/network_list = uniquelist(tempnetwork) + var/list/visible_networks = difflist(network_list, GLOB.restricted_camera_networks) + var/obj/machinery/camera/C = new(loc, length(visible_networks) > 0) loc = C C.assembly = src C.auto_turn() - C.network = uniquelist(tempnetwork) - tempnetwork = difflist(C.network,GLOB.restricted_camera_networks) - if(!tempnetwork.len) // Camera isn't on any open network - remove its chunk from AI visibility. - GLOB.cameranet.removeCamera(C) + C.network = network_list C.c_tag = input diff --git a/code/modules/clothing/spacesuits/ert_hardsuits.dm b/code/modules/clothing/spacesuits/ert_hardsuits.dm index d40357dbb62..15ecefa978c 100644 --- a/code/modules/clothing/spacesuits/ert_hardsuits.dm +++ b/code/modules/clothing/spacesuits/ert_hardsuits.dm @@ -37,9 +37,8 @@ /obj/item/clothing/head/helmet/space/hardsuit/ert/proc/register_camera(mob/wearer) if(camera || !has_camera) return - camera = new /obj/machinery/camera/portable(src) + camera = new /obj/machinery/camera/portable(src, FALSE) camera.network = list("ERT") - GLOB.cameranet.removeCamera(camera) camera.c_tag = wearer.name to_chat(wearer, "User scanned as [camera.c_tag]. Camera activated.") diff --git a/code/modules/mob/living/silicon/ai/ai_mob.dm b/code/modules/mob/living/silicon/ai/ai_mob.dm index a1c531f25b4..581742834ec 100644 --- a/code/modules/mob/living/silicon/ai/ai_mob.dm +++ b/code/modules/mob/living/silicon/ai/ai_mob.dm @@ -1222,7 +1222,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list( var/list/obj/machinery/camera/remove = list() var/list/obj/machinery/camera/visible = list() for(var/datum/camerachunk/CC in eyeobj.visibleCameraChunks) - for(var/obj/machinery/camera/C in CC.cameras) + for(var/obj/machinery/camera/C in CC.active_cameras) if(!C.can_use() || get_dist(C, eyeobj) > 7) continue visible |= C diff --git a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm index e1056df48d2..9e5341f93ee 100644 --- a/code/modules/mob/living/silicon/ai/freelook/cameranet.dm +++ b/code/modules/mob/living/silicon/ai/freelook/cameranet.dm @@ -1,9 +1,6 @@ // CAMERA NET // // The datum containing all the chunks. - -#define CHUNK_SIZE 16 // Only chunk sizes that are to the power of 2. E.g: 2, 4, 8, 16, etc.. - GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new()) /datum/cameranet @@ -20,16 +17,16 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new()) // Checks if a chunk has been Generated in x, y, z. /datum/cameranet/proc/chunkGenerated(x, y, z) - x &= ~(CHUNK_SIZE - 1) - y &= ~(CHUNK_SIZE - 1) + x &= ~(CAMERA_CHUNK_SIZE - 1) + y &= ~(CAMERA_CHUNK_SIZE - 1) var/key = "[x],[y],[z]" return (chunks[key]) // Returns the chunk in the x, y, z. // If there is no chunk, it creates a new chunk and returns that. /datum/cameranet/proc/getCameraChunk(x, y, z) - x &= ~(CHUNK_SIZE - 1) - y &= ~(CHUNK_SIZE - 1) + x &= ~(CAMERA_CHUNK_SIZE - 1) + y &= ~(CAMERA_CHUNK_SIZE - 1) var/key = "[x],[y],[z]" if(!chunks[key]) chunks[key] = new /datum/camerachunk(null, x, y, z) @@ -55,15 +52,15 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new()) chunks_pre_seen |= eye.visibleCameraChunks // 0xf = 15 var/static_range = eye.static_visibility_range - var/x1 = max(0, eye.x - static_range) & ~(CHUNK_SIZE - 1) - var/y1 = max(0, eye.y - static_range) & ~(CHUNK_SIZE - 1) - var/x2 = min(world.maxx, eye.x + static_range) & ~(CHUNK_SIZE - 1) - var/y2 = min(world.maxy, eye.y + static_range) & ~(CHUNK_SIZE - 1) + var/x1 = max(0, eye.x - static_range) & ~(CAMERA_CHUNK_SIZE - 1) + var/y1 = max(0, eye.y - static_range) & ~(CAMERA_CHUNK_SIZE - 1) + var/x2 = min(world.maxx, eye.x + static_range) & ~(CAMERA_CHUNK_SIZE - 1) + var/y2 = min(world.maxy, eye.y + static_range) & ~(CAMERA_CHUNK_SIZE - 1) var/list/visibleChunks = list() - for(var/x = x1; x <= x2; x += CHUNK_SIZE) - for(var/y = y1; y <= y2; y += CHUNK_SIZE) + for(var/x = x1; x <= x2; x += CAMERA_CHUNK_SIZE) + for(var/y = y1; y <= y2; y += CAMERA_CHUNK_SIZE) visibleChunks |= getCameraChunk(x, y, eye.z) var/list/remove = eye.visibleCameraChunks - visibleChunks @@ -115,22 +112,17 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new()) // Removes a camera from a chunk. /datum/cameranet/proc/removeCamera(obj/machinery/camera/c) - if(c.can_use()) - majorChunkChange(c, 0) + majorChunkChange(c, 0) // Add a camera to a chunk. /datum/cameranet/proc/addCamera(obj/machinery/camera/c) - if(c.can_use()) - majorChunkChange(c, 1) + majorChunkChange(c, 1) // Used for Cyborg cameras. Since portable cameras can be in ANY chunk. -/datum/cameranet/proc/updatePortableCamera(obj/machinery/camera/c) - if(c.can_use()) - majorChunkChange(c, 1) - //else - // majorChunkChange(c, 0) +/datum/cameranet/proc/updatePortableCamera(obj/machinery/camera/c, turf/old_loc) + majorChunkChange(c, 1, old_loc) // Never access this proc directly!!!! // This will update the chunk and all the surrounding chunks. @@ -138,31 +130,37 @@ GLOBAL_DATUM_INIT(cameranet, /datum/cameranet, new()) // Setting the choice to 0 will remove the camera from the chunks. // If you want to update the chunks around an object, without adding/removing a camera, use choice 2. -/datum/cameranet/proc/majorChunkChange(atom/c, choice) +/datum/cameranet/proc/majorChunkChange(atom/c, choice, turf/old_loc = null) // 0xf = 15 if(!c) return var/turf/T = get_turf(c) - if(T) - var/x1 = max(0, T.x - (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1) - var/y1 = max(0, T.y - (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1) - var/x2 = min(world.maxx, T.x + (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1) - var/y2 = min(world.maxy, T.y + (CHUNK_SIZE / 2)) & ~(CHUNK_SIZE - 1) + if(!T) + return -// to_chat(world, "X1: [x1] - Y1: [y1] - X2: [x2] - Y2: [y2]") + if(old_loc) + // Check if the current turf falls in the same chunka as the old_loc. If so, don't do anything + if(T.x & ~(CAMERA_CHUNK_SIZE - 1) == old_loc.x & ~(CAMERA_CHUNK_SIZE - 1) && T.y & ~(CAMERA_CHUNK_SIZE - 1) == old_loc.y & ~(CAMERA_CHUNK_SIZE - 1)) + return - for(var/x = x1; x <= x2; x += CHUNK_SIZE) - for(var/y = y1; y <= y2; y += CHUNK_SIZE) - if(chunkGenerated(x, y, T.z)) - var/datum/camerachunk/chunk = getCameraChunk(x, y, T.z) - if(choice == 0) - // Remove the camera. - chunk.cameras -= c - else if(choice == 1) - // You can't have the same camera in the list twice. - chunk.cameras |= c - chunk.hasChanged() + // Use camera view distance here to actually know how far a camera can max watch + var/x1 = max(0, T.x - CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1) + var/y1 = max(0, T.y - CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1) + var/x2 = min(world.maxx, T.x + CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1) + var/y2 = min(world.maxy, T.y + CAMERA_VIEW_DISTANCE) & ~(CAMERA_CHUNK_SIZE - 1) + + for(var/x = x1; x <= x2; x += CAMERA_CHUNK_SIZE) + for(var/y = y1; y <= y2; y += CAMERA_CHUNK_SIZE) + if(chunkGenerated(x, y, T.z)) + var/datum/camerachunk/chunk = getCameraChunk(x, y, T.z) + if(choice == 0) + // Remove the camera. + chunk.remove_camera(c) + else if(choice == 1) + // You can't have the same camera in the list twice. + chunk.add_camera(c) + chunk.hasChanged() // Will check if a mob is on a viewable turf. Returns 1 if it is, otherwise returns 0. diff --git a/code/modules/mob/living/silicon/ai/freelook/chunk.dm b/code/modules/mob/living/silicon/ai/freelook/chunk.dm index a72ddf20067..651f09b718d 100644 --- a/code/modules/mob/living/silicon/ai/freelook/chunk.dm +++ b/code/modules/mob/living/silicon/ai/freelook/chunk.dm @@ -1,5 +1,3 @@ -#define UPDATE_BUFFER 25 // 2.5 seconds - // CAMERA CHUNK // // A 16x16 grid of the map with a list of turfs that can be seen, are visible and are dimmed. @@ -9,31 +7,68 @@ var/list/obscuredTurfs = list() var/list/visibleTurfs = list() var/list/obscured = list() - var/list/cameras = list() + var/list/active_cameras = list() + var/list/inactive_cameras = list() var/list/turfs = list() var/list/seenby = list() - var/visible = FALSE var/changed = FALSE var/updating = FALSE var/x = 0 var/y = 0 var/z = 0 -// Add an AI eye to the chunk, then update if changed. +/datum/camerachunk/proc/add_camera(obj/machinery/camera/cam) + if(active_cameras[cam] || inactive_cameras[cam]) + return + // Register all even though it is active/inactive. Won't get called incorrectly + RegisterSignal(cam, COMSIG_CAMERA_OFF, PROC_REF(deactivate_camera)) + RegisterSignal(cam, COMSIG_CAMERA_ON, PROC_REF(activate_camera)) + RegisterSignal(cam, COMSIG_PARENT_QDELETING, PROC_REF(remove_camera)) + RegisterSignal(cam, COMSIG_CAMERA_MOVED, PROC_REF(camera_moved)) + if(cam.can_use()) + active_cameras[cam] = cam + else + inactive_cameras[cam] = cam +/datum/camerachunk/proc/camera_moved(obj/machinery/camera/cam, atom/old_loc) + var/turf/T = get_turf(cam) + + // Falls outside of the chunk view distance + if(T.x + CAMERA_VIEW_DISTANCE < x || T.x - CAMERA_VIEW_DISTANCE >= x + CAMERA_CHUNK_SIZE || T.y + CAMERA_VIEW_DISTANCE < y || T.y - CAMERA_VIEW_DISTANCE >= y + CAMERA_CHUNK_SIZE || T.z != z) + remove_camera(cam) + +/datum/camerachunk/proc/remove_camera(obj/machinery/camera/cam) + UnregisterSignal(cam, list(COMSIG_CAMERA_OFF, COMSIG_CAMERA_ON, COMSIG_PARENT_QDELETING, COMSIG_CAMERA_MOVED)) + active_cameras -= cam + inactive_cameras -= cam + SScamera.queue(src) + +/datum/camerachunk/proc/activate_camera(obj/machinery/camera/cam) + inactive_cameras -= cam + active_cameras += cam + SScamera.queue(src) + +/datum/camerachunk/proc/deactivate_camera(obj/machinery/camera/cam) + inactive_cameras += cam + active_cameras -= cam + SScamera.queue(src) + +// Add an AI eye to the chunk, then update if changed. /datum/camerachunk/proc/add(mob/camera/aiEye/eye, add_images = TRUE) if(add_images) var/client/client = eye.GetViewerClient() if(client) client.images += obscured eye.visibleCameraChunks += src - visible++ seenby += eye + RegisterSignal(eye, COMSIG_PARENT_QDELETING, PROC_REF(aiEye_destroyed)) if(changed) - update() + SScamera.queue(src) + +/datum/camerachunk/proc/aiEye_destroyed(mob/camera/aiEye/eye) + remove(eye, FALSE) // Remove an AI eye from the chunk, then update if changed. - /datum/camerachunk/proc/remove(mob/camera/aiEye/eye, remove_images = TRUE) if(remove_images) var/client/client = eye.GetViewerClient() @@ -41,8 +76,7 @@ client.images -= obscured eye.visibleCameraChunks -= src seenby -= eye - if(visible > 0) - visible-- + UnregisterSignal(eye, COMSIG_PARENT_QDELETING) // Called when a chunk has changed. I.E: A wall was deleted. @@ -55,12 +89,12 @@ // instead be flagged to update the next time an AI Eye moves near it. /datum/camerachunk/proc/hasChanged(update_now = 0) - if(visible || update_now) - if(!updating) - updating = TRUE - spawn(UPDATE_BUFFER) // Batch large changes, such as many doors opening or closing at once - update() - updating = FALSE + if(update_now) + update() + SScamera.remove_from_queue(src) + + if(length(seenby)) + SScamera.queue(src) else changed = TRUE @@ -69,106 +103,69 @@ /datum/camerachunk/proc/update() var/list/newVisibleTurfs = list() - for(var/camera in cameras) - var/obj/machinery/camera/c = camera - - if(!c) - continue - - if(!c.can_use()) - continue - - var/turf/point = locate(src.x + (CHUNK_SIZE / 2), src.y + (CHUNK_SIZE / 2), src.z) - if(get_dist(point, c) > CHUNK_SIZE + (CHUNK_SIZE / 2)) - continue + for(var/obj/machinery/camera/c as anything in active_cameras) + var/turf/point = locate(src.x + (CAMERA_CHUNK_SIZE / 2), src.y + (CAMERA_CHUNK_SIZE / 2), src.z) + var/turf/T = get_turf(c) + if(get_dist(point, T) > CAMERA_VIEW_DISTANCE + (CAMERA_CHUNK_SIZE / 2)) + continue // Still needed for Ais who get created on Z level 1 on the spot of the new player for(var/turf/t in c.can_see()) - // Possible optimization: if(turfs[t]) here, rather than &= turfs afterwards. - // List associations use a tree or hashmap of some sort (alongside the list itself) - // so are surprisingly fast. (significantly faster than var/thingy/x in list, in testing) - newVisibleTurfs[t] = t - - // Removes turf that isn't in turfs. - newVisibleTurfs &= turfs + if(turfs[t]) + newVisibleTurfs[t] = t var/list/visAdded = newVisibleTurfs - visibleTurfs var/list/visRemoved = visibleTurfs - newVisibleTurfs visibleTurfs = newVisibleTurfs obscuredTurfs = turfs - newVisibleTurfs - - for(var/turf in visAdded) - var/turf/t = turf + var/list/images_to_remove = list() + var/list/images_to_add = list() + for(var/turf/t as anything in visAdded) if(t.obscured) obscured -= t.obscured - for(var/eye in seenby) - var/mob/camera/aiEye/m = eye - if(!m) - continue - var/client/client = m.GetViewerClient() - if(client) - client.images -= t.obscured + images_to_remove += t.obscured - for(var/turf in visRemoved) - var/turf/t = turf - if(obscuredTurfs[t]) - if(!t.obscured) - t.obscured = image('icons/effects/cameravis.dmi', t, null, BYOND_LIGHTING_LAYER + 0.1) - t.obscured.plane = BYOND_LIGHTING_PLANE + 1 + for(var/turf/t as anything in visRemoved) + if(!t.obscured) + t.obscured = image('icons/effects/cameravis.dmi', t, null, BYOND_LIGHTING_LAYER + 0.1) + t.obscured.plane = BYOND_LIGHTING_PLANE + 1 + obscured += t.obscured + images_to_add += t.obscured - obscured += t.obscured - for(var/eye in seenby) - var/mob/camera/aiEye/m = eye - if(!m) - seenby -= m - continue - var/client/client = m.GetViewerClient() - if(client) - client.images += t.obscured + for(var/mob/camera/aiEye/eye as anything in seenby) + var/client/client = eye.GetViewerClient() + if(client) + client.images -= images_to_remove + client.images += images_to_add + changed = FALSE // Create a new camera chunk, since the chunks are made as they are needed. - /datum/camerachunk/New(loc, x, y, z) // 0xf = 15 - x &= ~(CHUNK_SIZE - 1) - y &= ~(CHUNK_SIZE - 1) + x &= ~(CAMERA_CHUNK_SIZE - 1) + y &= ~(CAMERA_CHUNK_SIZE - 1) src.x = x src.y = y src.z = z - for(var/obj/machinery/camera/c in urange(CHUNK_SIZE, locate(x + (CHUNK_SIZE / 2), y + (CHUNK_SIZE / 2), z))) - if(c.can_use()) - cameras += c + var/half_chunk = CAMERA_CHUNK_SIZE / 2 + for(var/obj/machinery/camera/c in urange(half_chunk + CAMERA_VIEW_DISTANCE, locate(x + half_chunk, y + half_chunk, z))) + add_camera(c) - for(var/turf/t in block(locate(max(x, 1), max(y, 1), max(z, 1)), locate(min(x + CHUNK_SIZE - 1, world.maxx), min(y + CHUNK_SIZE - 1, world.maxy), z))) + for(var/turf/t in block(locate(max(x, 1), max(y, 1), max(z, 1)), locate(min(x + CAMERA_CHUNK_SIZE - 1, world.maxx), min(y + CAMERA_CHUNK_SIZE - 1, world.maxy), z))) turfs[t] = t - for(var/camera in cameras) - var/obj/machinery/camera/c = camera - if(!c) - continue - - if(!c.can_use()) - continue - + for(var/obj/machinery/camera/c as anything in active_cameras) for(var/turf/t in c.can_see()) - // Possible optimization: if(turfs[t]) here, rather than &= turfs afterwards. - // List associations use a tree or hashmap of some sort (alongside the list itself) - // so are surprisingly fast. (significantly faster than var/thingy/x in list, in testing) - visibleTurfs[t] = t - - // Removes turf that isn't in turfs. - visibleTurfs &= turfs + if(turfs[t]) + visibleTurfs[t] = t obscuredTurfs = turfs - visibleTurfs - for(var/turf in obscuredTurfs) - var/turf/t = turf + for(var/turf/t as anything in obscuredTurfs) if(!t.obscured) t.obscured = image('icons/effects/cameravis.dmi', t, "black", BYOND_LIGHTING_LAYER + 0.1) t.obscured.plane = BYOND_LIGHTING_PLANE + 1 obscured += t.obscured - -#undef UPDATE_BUFFER diff --git a/code/modules/mob/living/silicon/robot/robot_mob.dm b/code/modules/mob/living/silicon/robot/robot_mob.dm index 268fac6e099..4072ced048d 100644 --- a/code/modules/mob/living/silicon/robot/robot_mob.dm +++ b/code/modules/mob/living/silicon/robot/robot_mob.dm @@ -99,7 +99,8 @@ GLOBAL_LIST_INIT(robot_verbs_default, list( var/lamp_intensity = 0 //Luminosity of the headlamp. 0 is off. Higher settings than the minimum require power. var/lamp_recharging = FALSE //Flag for if the lamp is on cooldown after being forcibly disabled. - var/updating = FALSE //portable camera camerachunk update + /// When the camera moved signal was send last. Avoid overdoing it + var/last_camera_update hud_possible = list(SPECIALROLE_HUD, DIAG_STAT_HUD, DIAG_HUD, DIAG_BATT_HUD) @@ -1225,19 +1226,16 @@ GLOBAL_LIST_INIT(robot_verbs_default, list( cell = null qdel(src) -#define BORG_CAMERA_BUFFER 3 SECONDS +#define CAMERA_UPDATE_COOLDOWN 2.5 SECONDS -/mob/living/silicon/robot/Move(atom/newloc, direct, movetime) - var/oldLoc = loc +/mob/living/silicon/robot/Moved(atom/OldLoc, Dir, Forced) . = ..() - if(. && !updating && camera) - updating = TRUE - spawn(BORG_CAMERA_BUFFER) - if(camera && oldLoc != loc) - GLOB.cameranet.updatePortableCamera(camera) - updating = FALSE + if(camera && last_camera_update + CAMERA_UPDATE_COOLDOWN < world.time) + last_camera_update = world.time + GLOB.cameranet.updatePortableCamera(camera, OldLoc) + SEND_SIGNAL(camera, COMSIG_CAMERA_MOVED, OldLoc) -#undef BORG_CAMERA_BUFFER +#undef CAMERA_UPDATE_COOLDOWN /mob/living/silicon/robot/proc/self_destruct() if(emagged) diff --git a/paradise.dme b/paradise.dme index 3646c398a74..c4344b48316 100644 --- a/paradise.dme +++ b/paradise.dme @@ -240,6 +240,7 @@ #include "code\controllers\subsystem\air.dm" #include "code\controllers\subsystem\ambience.dm" #include "code\controllers\subsystem\blackbox.dm" +#include "code\controllers\subsystem\camera.dm" #include "code\controllers\subsystem\chat_pings.dm" #include "code\controllers\subsystem\cleanup.dm" #include "code\controllers\subsystem\dbcore.dm"