diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index 334b7c11e1b..b8e386e1136 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -418,8 +418,7 @@ return var/turf/T = get_turf(src) if(T && (isturf(loc) || isturf(src)) && user.TurfAdjacent(T) && !HAS_TRAIT(user, TRAIT_MOVE_VENTCRAWLING)) - user.listed_turf = T - user.client.stat_panel.send_message("create_listedturf", T.name) + user.set_listed_turf(T) ///The base proc of when something is right clicked on when alt is held - generally use alt_click_secondary instead /atom/proc/alt_click_on_secondary(atom/A) @@ -442,8 +441,7 @@ /atom/proc/AltClickNoInteract(mob/user, atom/A) var/turf/T = get_turf(A) if(T && user.TurfAdjacent(T)) - user.listed_turf = T - user.client.stat_panel.send_message("create_listedturf", T.name) + user.set_listed_turf(T) /mob/proc/TurfAdjacent(turf/T) return T.Adjacent(src) diff --git a/code/controllers/subsystem/processing/obj_tab_items.dm b/code/controllers/subsystem/processing/obj_tab_items.dm new file mode 100644 index 00000000000..53786daf011 --- /dev/null +++ b/code/controllers/subsystem/processing/obj_tab_items.dm @@ -0,0 +1,24 @@ +PROCESSING_SUBSYSTEM_DEF(obj_tab_items) + name = "Obj Tab Items" + flags = SS_NO_INIT + runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT + wait = 0.1 SECONDS + +// I know this is mostly copypasta, but I want to change the processing logic +// Sorry bestie :( +/datum/controller/subsystem/processing/obj_tab_items/fire(resumed = FALSE) + if (!resumed) + currentrun = processing.Copy() + //cache for sanic speed (lists are references anyways) + var/list/current_run = currentrun + + while(current_run.len) + var/datum/thing = current_run[current_run.len] + if(QDELETED(thing)) + processing -= thing + else if(thing.process(wait * 0.1) == PROCESS_KILL) + // fully stop so that a future START_PROCESSING will work + STOP_PROCESSING(src, thing) + if (MC_TICK_CHECK) + return + current_run.len-- diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm index 22ec21825c9..e016c293526 100644 --- a/code/controllers/subsystem/statpanel.dm +++ b/code/controllers/subsystem/statpanel.dm @@ -8,7 +8,6 @@ SUBSYSTEM_DEF(statpanels) var/list/currentrun = list() var/list/global_data var/list/mc_data - var/list/cached_images = list() ///how many subsystem fires between most tab updates var/default_wait = 10 @@ -107,14 +106,10 @@ SUBSYSTEM_DEF(statpanels) if(update_actions && num_fires % default_wait == 0) set_action_tabs(target, target_mob) - // Handle the examined turf of the stat panel - - if(target_mob?.listed_turf && num_fires % default_wait == 0) - if(!target_mob.TurfAdjacent(target_mob.listed_turf) || isnull(target_mob.listed_turf)) - target.stat_panel.send_message("remove_listedturf") - target_mob.listed_turf = null - - else if(target.stat_tab == target_mob?.listed_turf.name || !(target_mob?.listed_turf.name in target.panel_tabs)) + // Handle the examined turf of the stat panel, if it's been long enough, or if we've generated new images for it + var/turf/listed_turf = target_mob?.listed_turf + if(listed_turf && num_fires % default_wait == 0) + if(target.stat_tab == listed_turf.name || !(listed_turf.name in target.panel_tabs)) set_turf_examine_tab(target, target_mob) if(MC_TICK_CHECK) @@ -191,14 +186,12 @@ SUBSYSTEM_DEF(statpanels) /datum/controller/subsystem/statpanels/proc/set_turf_examine_tab(client/target, mob/target_mob) var/list/overrides = list() - var/list/turfitems = list() 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 - turfitems[++turfitems.len] = list("[target_mob.listed_turf]", REF(target_mob.listed_turf), icon2html(target_mob.listed_turf, target, sourceonly=TRUE)) - + var/list/atoms_to_display = list(target_mob.listed_turf) for(var/atom/movable/turf_content as anything in target_mob.listed_turf) if(turf_content.mouse_opacity == MOUSE_OPACITY_TRANSPARENT) continue @@ -208,24 +201,54 @@ SUBSYSTEM_DEF(statpanels) continue if(turf_content.IsObscured()) continue + atoms_to_display += turf_content - if(length(turfitems) < 10) // only create images for the first 10 items on the turf, for performance reasons - var/turf_content_ref = REF(turf_content) - if(!(turf_content_ref in cached_images)) - cached_images += turf_content_ref - turf_content.RegisterSignal(turf_content, COMSIG_PARENT_QDELETING, /atom/.proc/remove_from_cache) // we reset cache if anything in it gets deleted + /// Set the atoms we're meant to display + var/datum/object_window_info/obj_window = target.obj_window + obj_window.atoms_to_show = atoms_to_display + START_PROCESSING(SSobj_tab_items, obj_window) + refresh_client_obj_view(target) - if(ismob(turf_content) || length(turf_content.overlays) > 2) - turfitems[++turfitems.len] = list("[turf_content.name]", turf_content_ref, costly_icon2html(turf_content, target, sourceonly=TRUE)) - else - turfitems[++turfitems.len] = list("[turf_content.name]", turf_content_ref, icon2html(turf_content, target, sourceonly=TRUE)) - else - turfitems[++turfitems.len] = list("[turf_content.name]", turf_content_ref) - else - turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content)) +/datum/controller/subsystem/statpanels/proc/refresh_client_obj_view(client/refresh) + var/list/turf_items = return_object_images(refresh) + if(!length(turf_items) || !refresh.mob?.listed_turf) + return + refresh.stat_panel.send_message("update_listedturf", turf_items) - turfitems = turfitems - target.stat_panel.send_message("update_listedturf", turfitems) +#define OBJ_IMAGE_LOADING "statpanels obj loading temporary" +/// Returns all our ready object tab images +/// Returns a list in the form list(list(object_name, object_ref, loaded_image), ...) +/datum/controller/subsystem/statpanels/proc/return_object_images(client/load_from) + // You might be inclined to think that this is a waste of cpu time, since we + // A: Double iterate over atoms in the build case, or + // B: Generate these lists over and over in the refresh case + // It's really not very hot. The hot portion of this code is genuinely mostly in the image generation + // So it's ok to pay a performance cost for cleanliness here + + // No turf? go away + if(!load_from.mob?.listed_turf) + return list() + var/datum/object_window_info/obj_window = load_from.obj_window + var/list/already_seen = obj_window.atoms_to_images + var/list/to_make = obj_window.atoms_to_imagify + var/list/turf_items = list() + for(var/atom/turf_item as anything in obj_window.atoms_to_show) + // 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] + if(existing_image == OBJ_IMAGE_LOADING) + continue + // We already have it. Success! + if(existing_image) + turf_items[++turf_items.len] = list("[turf_item.name]", REF(turf_item), existing_image) + continue + // Now, we're gonna queue image generation out of those refs + to_make += turf_item + already_seen[turf_item] = OBJ_IMAGE_LOADING + obj_window.RegisterSignal(turf_item, COMSIG_PARENT_QDELETING, /datum/object_window_info/proc/viewing_atom_deleted) // we reset cache if anything in it gets deleted + return turf_items + +#undef OBJ_IMAGE_LOADING /datum/controller/subsystem/statpanels/proc/generate_mc_data() mc_data = list( @@ -271,8 +294,7 @@ SUBSYSTEM_DEF(statpanels) if(target_mob?.listed_turf) if(!target_mob.TurfAdjacent(target_mob.listed_turf)) - target.stat_panel.send_message("removed_listedturf") - target_mob.listed_turf = null + target_mob.set_listed_turf(null) else if(target.stat_tab == target_mob?.listed_turf.name || !(target_mob?.listed_turf.name in target.panel_tabs)) set_turf_examine_tab(target, target_mob) @@ -295,9 +317,107 @@ SUBSYSTEM_DEF(statpanels) else if(length(GLOB.sdql2_queries) && target.stat_tab == "SDQL2") set_SDQL2_tab(target) -/atom/proc/remove_from_cache() - SIGNAL_HANDLER - SSstatpanels.cached_images -= REF(src) - /// Stat panel window declaration /client/var/datum/tgui_window/stat_panel + +/// Datum that holds and tracks info about a client's object window +/// Really only exists because I want to be able to do logic with signals +/// And need a safe place to do the registration +/datum/object_window_info + /// list of atoms to show to our client via the object tab, at least currently + var/list/atoms_to_show = list() + /// list of atom -> image string for objects we have had in the right click tab + /// this is our caching + var/list/atoms_to_images = list() + /// list of atoms to turn into images for the object tab + var/list/atoms_to_imagify = list() + /// Our owner client + var/client/parent + /// Are we currently tracking a turf? + var/actively_tracking = FALSE + +/datum/object_window_info/New(client/parent) + . = ..() + src.parent = parent + +/datum/object_window_info/Destroy(force, ...) + atoms_to_show = null + atoms_to_images = null + atoms_to_imagify = null + parent.obj_window = null + parent = null + STOP_PROCESSING(SSobj_tab_items, src) + return ..() + +/// Takes a client, attempts to generate object images for it +/// We will update the client with any improvements we make when we're done +/datum/object_window_info/process(delta_time) + // Cache the datum access for sonic speed + var/list/to_make = atoms_to_imagify + var/list/newly_seen = atoms_to_images + var/index = 0 + for(index in 1 to length(to_make)) + var/atom/thing = to_make[index] + + var/generated_string + if(ismob(thing) || length(thing.overlays) > 2) + generated_string = costly_icon2html(thing, parent, sourceonly=TRUE) + else + generated_string = icon2html(thing, parent, sourceonly=TRUE) + + newly_seen[thing] = generated_string + if(TICK_CHECK) + to_make.Cut(1, index + 1) + index = 0 + break + // If we've not cut yet, do it now + if(index) + to_make.Cut(1, index + 1) + SSstatpanels.refresh_client_obj_view(parent) + if(!length(to_make)) + return PROCESS_KILL + +/datum/object_window_info/proc/start_turf_tracking() + if(actively_tracking) + stop_turf_tracking() + var/static/list/connections = list( + COMSIG_MOVABLE_MOVED = .proc/on_mob_move, + COMSIG_MOB_LOGOUT = .proc/on_mob_logout, + ) + AddComponent(/datum/component/connect_mob_behalf, parent, connections) + actively_tracking = TRUE + +/datum/object_window_info/proc/stop_turf_tracking() + qdel(GetComponent(/datum/component/connect_mob_behalf)) + actively_tracking = FALSE + +/datum/object_window_info/proc/on_mob_move(mob/source) + SIGNAL_HANDLER + var/turf/listed = source.listed_turf + if(!listed || !source.TurfAdjacent(listed)) + source.set_listed_turf(null) + +/datum/object_window_info/proc/on_mob_logout(mob/source) + SIGNAL_HANDLER + on_mob_move(parent.mob) + +/// Clears any cached object window stuff +/// We use hard refs cause we'd need a signal for this anyway. Cleaner this way +/datum/object_window_info/proc/viewing_atom_deleted(atom/deleted) + SIGNAL_HANDLER + atoms_to_show -= deleted + atoms_to_imagify -= deleted + atoms_to_images -= deleted + +/mob/proc/set_listed_turf(turf/new_turf) + listed_turf = new_turf + if(!client) + return + if(!client.obj_window) + client.obj_window = new(client) + if(listed_turf) + client.stat_panel.send_message("create_listedturf", listed_turf.name) + client.obj_window.start_turf_tracking() + else + client.stat_panel.send_message("remove_listedturf") + client.obj_window.stop_turf_tracking() diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index 577b9de0992..10c7125d7fd 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -194,6 +194,8 @@ var/list/spell_tabs = list() ///A lazy list of atoms we've examined in the last RECENT_EXAMINE_MAX_WINDOW (default 2) seconds, so that we will call [/atom/proc/examine_more] instead of [/atom/proc/examine] on them when examining var/list/recent_examines + ///Our object window datum. It stores info about and handles behavior for the object tab + var/datum/object_window_info/obj_window var/list/parallax_layers var/list/parallax_layers_cached diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index dee25f3abdf..0cb6efbbd4c 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -553,6 +553,8 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( SSserver_maint.UpdateHubStatus() if(credits) QDEL_LIST(credits) + if(obj_window) + QDEL_NULL(obj_window) if(holder) adminGreet(1) holder.owner = null diff --git a/tgstation.dme b/tgstation.dme index e4c175e391f..272df2acfd2 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -641,6 +641,7 @@ #include "code\controllers\subsystem\processing\instruments.dm" #include "code\controllers\subsystem\processing\networks.dm" #include "code\controllers\subsystem\processing\obj.dm" +#include "code\controllers\subsystem\processing\obj_tab_items.dm" #include "code\controllers\subsystem\processing\plumbing.dm" #include "code\controllers\subsystem\processing\processing.dm" #include "code\controllers\subsystem\processing\projectiles.dm"