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>
This commit is contained in:
Farie82
2023-05-12 14:58:23 -05:00
committed by GitHub
co-authored by S34N Ryan Charlie
parent 4a1873023a
commit 0fedbb9257
12 changed files with 247 additions and 197 deletions
+5
View File
@@ -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)
+2
View File
@@ -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
+1
View File
@@ -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
+24
View File
@@ -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
+78 -52
View File
@@ -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("<span class='danger'>[user] [change_msg] [src]!</span>")
visible_message("<span class='danger'>[user] reactivates [src]!</span>")
add_hiddenprint(user)
else
visible_message("<span class='danger'>\The [src] [change_msg]!</span>")
visible_message("<span class='danger'>\The [src] reactivates!</span>")
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("<span class='danger'>[user] deactivates [src]!</span>")
add_hiddenprint(user)
else
visible_message("<span class='danger'>\The [src] deactivates!</span>")
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)
@@ -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
@@ -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, "<span class='notice'>User scanned as [camera.c_tag]. Camera activated.</span>")
+1 -1
View File
@@ -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
@@ -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.
@@ -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
@@ -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)