diff --git a/code/controllers/subsystem/SSstatpanel.dm b/code/controllers/subsystem/SSstatpanel.dm index d75e8541ddc..b5b8d1843c7 100644 --- a/code/controllers/subsystem/SSstatpanel.dm +++ b/code/controllers/subsystem/SSstatpanel.dm @@ -112,7 +112,7 @@ SUBSYSTEM_DEF(statpanels) for(var/image/target_image as anything in target.images) if(!target_image.loc || target_image.loc.loc != target_mob.listed_turf || !target_image.override) continue - overrides += target_image.loc + overrides[target_image.loc] = TRUE var/list/atoms_to_display = list(target_mob.listed_turf) for(var/atom/movable/turf_content as anything in target_mob.listed_turf) @@ -120,7 +120,7 @@ SUBSYSTEM_DEF(statpanels) continue if(turf_content.invisibility > target_mob.see_invisible) continue - if(turf_content in overrides) + if(overrides[turf_content]) continue if(turf_content.IsObscured()) continue @@ -129,6 +129,8 @@ SUBSYSTEM_DEF(statpanels) /// Set the atoms we're meant to display var/datum/object_window_info/obj_window = istype(target.obj_window) ? target.obj_window : new(target) obj_window.atoms_to_show = atoms_to_display + // don't generate icons for stuff the user will not see + obj_window.atoms_to_imagify &= atoms_to_display refresh_client_obj_view(target, obj_window.min_index, obj_window.max_index) /datum/controller/subsystem/statpanels/proc/refresh_client_obj_view(client/refresh, min_index = 0, max_index = 30) @@ -160,6 +162,8 @@ SUBSYSTEM_DEF(statpanels) var/list/turf_items = list() var/i = 0 var/client_uid = load_from.UID() + // While an item's icon is being generated, we send no image URL and let the frontend render an empty slot. + var/static/no_image_url = "" for(var/atom/turf_item as anything in obj_window.atoms_to_show) // Limit what we send to the client's rendered section. i++ @@ -169,7 +173,11 @@ SUBSYSTEM_DEF(statpanels) // First, we fill up the list of refs to display // If we already have one, just use that var/existing_image = already_seen[turf_item] + // rows that are still loading are still sent, we just won't send an image, frontend will hotswap it in if(existing_image == OBJ_IMAGE_LOADING) + var/obj_m5_uid_loading = turf_item.MD5_UID() + load_from.m5_uid_cache[obj_m5_uid_loading] = turf_item.unique_datum_id + turf_items["[i]"] = list("[turf_item.name]", obj_m5_uid_loading, no_image_url, "", client_uid) continue // We already have it. Success! @@ -254,6 +262,10 @@ SUBSYSTEM_DEF(statpanels) var/client/parent /// Are we currently tracking a turf? var/actively_tracking = FALSE + /// Turf we are currently listening to for content changes. + var/turf/tracked_turf + /// Debounce flag for immediate refreshes. + var/pending_turf_refresh = FALSE /// The minimum index currently sent to the client. var/min_index = 0 /// The maximum index currently sent to the client. @@ -264,6 +276,9 @@ SUBSYSTEM_DEF(statpanels) src.parent = parent /datum/object_window_info/Destroy(force, ...) + if(tracked_turf) + UnregisterSignal(tracked_turf, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_EXITED, COMSIG_TURF_CHANGE)) + tracked_turf = null atoms_to_show = null atoms_to_images = null atoms_to_imagify = null @@ -303,6 +318,13 @@ SUBSYSTEM_DEF(statpanels) /datum/object_window_info/proc/start_turf_tracking() if(actively_tracking) stop_turf_tracking() + var/turf/new_tracked = parent?.mob?.listed_turf + if(new_tracked) + // We're setting up signals here so updates are instant rather than laggy and slow. + tracked_turf = new_tracked + RegisterSignal(tracked_turf, COMSIG_ATOM_ENTERED, PROC_REF(on_tracked_turf_entered)) + RegisterSignal(tracked_turf, COMSIG_ATOM_EXITED, PROC_REF(on_tracked_turf_exited)) + RegisterSignal(tracked_turf, COMSIG_TURF_CHANGE, PROC_REF(on_tracked_turf_change)) var/static/list/connections = list( COMSIG_MOVABLE_MOVED = PROC_REF(on_mob_move), COMSIG_MOB_LOGOUT = PROC_REF(on_mob_logout), @@ -312,8 +334,38 @@ SUBSYSTEM_DEF(statpanels) /datum/object_window_info/proc/stop_turf_tracking() DeleteComponent(/datum/component/connect_mob_behalf) + if(tracked_turf) + UnregisterSignal(tracked_turf, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_EXITED, COMSIG_TURF_CHANGE)) + tracked_turf = null + pending_turf_refresh = FALSE actively_tracking = FALSE +/datum/object_window_info/proc/queue_listedturf_refresh() + if(pending_turf_refresh) + return + pending_turf_refresh = TRUE + // This accumulates all of our updates and does one refresh rather than calling a dozen refreshes when a dozen things change + addtimer(CALLBACK(src, PROC_REF(do_listedturf_refresh)), 1) + +/datum/object_window_info/proc/do_listedturf_refresh() + pending_turf_refresh = FALSE + if(!parent?.mob?.listed_turf) + return + // If the user moved away, let the existing adjacency logic close the tab. + SSstatpanels.set_turf_examine_tab(parent, parent.mob) + +/datum/object_window_info/proc/on_tracked_turf_entered(atom/source, atom/movable/entered, atom/old_loc) + SIGNAL_HANDLER // COMSIG_ATOM_ENTERED + queue_listedturf_refresh() + +/datum/object_window_info/proc/on_tracked_turf_exited(atom/source, atom/movable/exiting, direction) + SIGNAL_HANDLER // COMSIG_ATOM_EXITED + queue_listedturf_refresh() + +/datum/object_window_info/proc/on_tracked_turf_change(atom/source) + SIGNAL_HANDLER // COMSIG_TURF_CHANGE + queue_listedturf_refresh() + /datum/object_window_info/proc/on_mob_move(mob/source) SIGNAL_HANDLER var/turf/listed = source.listed_turf @@ -330,7 +382,7 @@ SUBSYSTEM_DEF(statpanels) SIGNAL_HANDLER atoms_to_show -= deleted atoms_to_imagify -= deleted - atoms_to_images -= deleted + atoms_to_images[deleted] = null /mob/proc/set_listed_turf(turf/new_turf) listed_turf = new_turf @@ -347,3 +399,6 @@ SUBSYSTEM_DEF(statpanels) else client.stat_panel.send_message("remove_listedturf") client.obj_window.stop_turf_tracking() + // Don't generate icons when the tab's closed. + client.obj_window.atoms_to_show.Cut() + client.obj_window.atoms_to_imagify.Cut() diff --git a/html/statbrowser.js b/html/statbrowser.js index 3647e679ab3..efd3d9f7c76 100644 --- a/html/statbrowser.js +++ b/html/statbrowser.js @@ -381,7 +381,12 @@ function listedturf_add_row(table, table_index, true_index) { turf_incomplete_rows[true_index] = true_index + 1; } -function listedturf_fill_row(row, item_index) { +function listedturf_render_row(row, item_index) { + // Clear any previous contents so we don't mess up list indices + while (row.firstChild) { + row.removeChild(row.firstChild); + } + let object_info = turfcontents['' + item_index]; if (!object_info) { return false; @@ -428,32 +433,46 @@ function listedturf_fill_row(row, item_index) { cell.appendChild(button); let img = document.createElement('img'); - img.id = object_info[1]; - img.src = object_info[2]; - img.style.verticalAlign = 'middle'; - img.style.width = turf_row_inner_height + 'em'; - img.style.height = turf_row_inner_height + 'em'; - img.onerror = (function (object_info) { - return function () { - let delay = imageRetryDelay; - if (!turf_image_errors[object_info[3]]) { - turf_image_errors[object_info[3]] = 0; - delay = imageFirstRetryDelay; - } - turf_image_errors[object_info[3]]++; - if (turf_image_errors[object_info[3]] > imageRetryLimit) { - return; - } + if (object_info[2]) { + img.id = object_info[1]; + img.src = object_info[2]; + img.style.verticalAlign = 'middle'; + img.style.width = turf_row_inner_height + 'em'; + img.style.height = turf_row_inner_height + 'em'; + // Only retry if we got an asset key + if (object_info[3]) { + img.onerror = (function (object_info) { + return function () { + let delay = imageRetryDelay; + if (!turf_image_errors[object_info[3]]) { + turf_image_errors[object_info[3]] = 0; + delay = imageFirstRetryDelay; + } + turf_image_errors[object_info[3]]++; + if (turf_image_errors[object_info[3]] > imageRetryLimit) { + return; + } - Byond.sendMessage('Resend-Asset', object_info[3]); - setTimeout(function () { - // Use the failure count as a cachebreaker to force-reload. - let img = document.getElementById(object_info[1]); - img.src = object_info[2] + '?' + turf_image_errors[object_info[3]]; - }, imageRetryDelay); - }; - })(object_info); - button.appendChild(img); + Byond.sendMessage('Resend-Asset', object_info[3]); + setTimeout(function () { + // Use the failure count as a cachebreaker to force-reload. + let img = document.getElementById(object_info[1]); + if (img) { + img.src = object_info[2] + '?' + turf_image_errors[object_info[3]]; + } + }, delay); + }; + })(object_info); + } + button.appendChild(img); + } else { + // If we're missing an image URL just space out the same amount that the image would take up + let spacer = document.createElement('span'); + spacer.style.display = 'inline-block'; + spacer.style.width = turf_row_inner_height + 'em'; + spacer.style.height = turf_row_inner_height + 'em'; + button.appendChild(spacer); + } var label = document.createElement('span'); label.style.marginLeft = '0.5em'; @@ -463,6 +482,22 @@ function listedturf_fill_row(row, item_index) { return true; } +function listedturf_fill_row(row, item_index) { + return listedturf_render_row(row, item_index); +} + +function listedturf_refresh_visible_rows() { + if (!turf_rows || !turf_rows.initialized) { + return; + } + for (let i = turf_rows.min_row; i < turf_rows.max_row; i++) { + if (turf_rows[i]) { + listedturf_render_row(turf_rows[i], i + 1); + delete turf_incomplete_rows[i]; + } + } +} + function listedturf_fill_all() { for (let i in turf_incomplete_rows) { let item_index = turf_incomplete_rows[i]; @@ -915,6 +950,7 @@ Byond.subscribeTo('update_listedturf', function (TC) { turf_size = TC['total']; if (current_tab == turfname) { draw_listedturf(); + listedturf_refresh_visible_rows(); } });