From cf02f622986932af8fb09e48cbdf5ec0ac567cf5 Mon Sep 17 00:00:00 2001 From: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> Date: Tue, 20 Dec 2022 00:51:52 -0800 Subject: [PATCH] useless update_appearance reduction, emissive_blocker micro optimization (saves a second of init) (#71658) ## About The Pull Request [Saves 0.2 seconds of init time. 50% of emissive blockers](https://github.com/tgstation/tgstation/commit/8318b648f6d32844aacbfb4c309152cd45801f5c) Emissive blockers are a decent expense during init, even these, which are the ones that update outside of initialize. I've inlined them, removed some redundant vars and checks, reduced the arg count, and shifted some things around. This ends up saving 200ms, or 50% of its total cost. I also shifted mutable_appearance about a bit. it's not a massive saving, but it is technically faster [Prevents a few redundant appearance_updates, saves 0.8 seconds of init](https://github.com/tgstation/tgstation/commit/5475cd778b66b22b1e2c8d86b2c6d59fb84f219a) Prequisit info: update_appearance is decently expensive It's good then to only do it if we have a reason to, right? Me and moth were shooting the shit about just general init time, and we came up with the idea of tracking which update_appearances actually "worked" and which didn't. That bit comes later, let's enjoy the fruits of that work first First, holograms were calling update_appearance on process, for almost no reason. I patched the one event they don't already "react" to, and then locked it behind a change decection if. good for live, doesn't impact init. Next, decals. If you add a decal to something before it inits, it'll react to the after successful init signal. The trouble is the same atom could have its appearance updated from this MORE then once, since decals can be stacked on tiles, and signal unregisters don't work once the signal is sent. So we add a flag to track if we've had this happen to us or not, so it only happens once. saves 80 ms Power! lots of things call power_change on init, often more then once. We'll update appearance for each of those calls, even if only one is an actual change. That's silly, better to track what sort of power we're using for our appearance and go off that changing This was taking about 300ms. Really stupid Icon smoothing. After emissive blockers were added, any change to something's icon smoothing would lead to an update_appearance call. Nasty shit, specially cause of walls, which don't even use emissive blockers. Ok then, so we'll always update appearance for movables, and will allow turfs that are interested to hook it manually. Not many of those anyhow This is slightly a dev ux thing, but it saves 600ms so I think it's worth it. Rare case anyway Telecomms: telecomm machines were updating appearance on process. This is to cover for them turning on/off on process. Better then to just check if on actually changed. This cost adds up midgame, doesn't impact init tho Materials: There's this update_appearance call in material on_apply. it doesn't do anything. The logs will lie to you and say it does, but it's just like reapplying emissives. It doesn't need to exist Saves like 50ms Canisters: Live thing, lots of time wasted updating appearance for no reason, lets see if we change anything first yes? [Uses defines to wrap update_appearance for tracking](https://github.com/tgstation/tgstation/commit/4fa82e1c9d93577aadb3c743f17196331f62e67c) [Undoes _update_appearance changes, instead reccomends 2 regexes to use](https://github.com/tgstation/tgstation/commit/a8c8fec57a4e43d1fa636b5ac68459903faa9fc5) I need file and line number for my tracking, so I need to override update_appearance calls, and also preferably not require every override of update_appearance to handle dummy file + line args. So instead, I created a wrapper proc that checks to see if appearanaces match (they're unique remember, the two of the same visual appearance will be equivalent) The trouble is I can't intercept JUST proc calls, or JUST function definitions with defines. it needs to be both. So I renamed the /update_appearance proc to /_update_appearance this way I can capture old uses, and don't need to worry about merge/dev brain skew ~~It does mean that all update_appearance proc definitions now look weird tho. My profiling is leaking into dev ux. I wish I had better templating.~~ **The above is no longer being pr'd**, it's instead just recommended via a few regexes adjacent to the define. Smelled wrong anyhow [Adds a setter for panel_open, so I can update_appearance on it](https://github.com/tgstation/tgstation/pull/71658/commits/cf1df8a69fc1a816391d085ee7419b14f9fe9167) ## Why It's Good For The Game Speed --- code/__DEFINES/appearance.dm | 7 +++++ code/__DEFINES/flags.dm | 7 ++++- code/__DEFINES/stat_tracking.dm | 25 +++++++++++------ code/__HELPERS/_planes.dm | 3 ++ code/__HELPERS/icon_smoothing.dm | 6 ++++ code/__HELPERS/lighting.dm | 22 ++++++++++++++- code/__HELPERS/stat_tracking.dm | 16 ++++++++++- code/_compile_options.dm | 8 ++++++ code/datums/elements/decals/_decal.dm | 4 +-- code/datums/materials/_material.dm | 2 -- code/datums/mutable_appearance.dm | 28 +++++++++---------- code/game/atoms.dm | 18 ++++++++++++ code/game/atoms_movable.dm | 4 +-- code/game/machinery/_machinery.dm | 25 ++++++++++++++--- code/game/machinery/announcement_system.dm | 2 +- code/game/machinery/buttons.dm | 2 +- code/game/machinery/camera/camera.dm | 2 +- code/game/machinery/doors/airlock.dm | 5 ++-- code/game/machinery/doors/airlock_types.dm | 3 +- code/game/machinery/doors/windowdoor.dm | 2 +- code/game/machinery/firealarm.dm | 4 +-- code/game/machinery/hologram.dm | 9 ++++-- code/game/machinery/shieldgen.dm | 2 +- code/game/machinery/spaceheater.dm | 7 +++-- code/game/machinery/status_display.dm | 6 +++- .../machinery/telecomms/telecomunications.dm | 11 +++++--- code/game/objects/structures/barsigns.dm | 5 ++-- code/game/turfs/closed/wall/reinf_walls.dm | 1 + code/game/turfs/open/floor/fancy_floor.dm | 4 +++ .../atmospherics/machinery/airalarm.dm | 4 +-- .../machinery/bluespace_vendor.dm | 2 +- .../components/binary_devices/circulator.dm | 6 ++-- .../components/electrolyzer/electrolyzer.dm | 2 +- .../machinery/portable/canister.dm | 12 ++++++-- code/modules/mapping/mapping_helpers.dm | 4 +-- code/modules/power/apc/apc_tool_act.dm | 2 +- code/modules/power/cable.dm | 1 + code/modules/power/generator.dm | 2 +- code/modules/power/port_gen.dm | 2 +- code/modules/power/power.dm | 11 ++++++-- code/modules/power/turbine/turbine.dm | 5 ++-- code/modules/recycling/disposal/bin.dm | 2 +- code/modules/vending/_vending.dm | 2 +- tgstation.dme | 1 + 44 files changed, 214 insertions(+), 84 deletions(-) create mode 100644 code/__DEFINES/appearance.dm diff --git a/code/__DEFINES/appearance.dm b/code/__DEFINES/appearance.dm new file mode 100644 index 00000000000..4c0e05db9b3 --- /dev/null +++ b/code/__DEFINES/appearance.dm @@ -0,0 +1,7 @@ +// wrapper for update_appearance that lets me capture it in debug mode +// Does not work for callbacks unfortunately, not sure how to feel about that +// This only works if definitions of the proc get changed to _update_appearance with REGEX +// See the _compile_options comment for the regexes to use for that +#ifdef APPEARANCE_SUCCESS_TRACKING +#define update_appearance(arguments...) wrap_update_appearance(__FILE__, __LINE__, ##arguments) +#endif diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index bae3c9e7174..6cbc9310355 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -56,8 +56,13 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 #define IS_PLAYER_COLORABLE_1 (1<<17) /// Whether or not this atom has contextual screentips when hovered OVER #define HAS_CONTEXTUAL_SCREENTIPS_1 (1<<18) -// Whether or not this atom is storing contents for a disassociated storage object +/// Whether or not this atom is storing contents for a disassociated storage object #define HAS_DISASSOCIATED_STORAGE_1 (1<<19) +/// If this atom has experienced a decal element "init finished" sourced appearance update +/// We use this to ensure stacked decals don't double up appearance updates for no rasin +/// Flag as an optimization, don't make this a trait without profiling +/// Yes I know this is a stupid flag, no you can't take him from me +#define DECAL_INIT_UPDATE_EXPERIENCED_1 (1<<20) // Update flags for [/atom/proc/update_appearance] /// Update the atom's name diff --git a/code/__DEFINES/stat_tracking.dm b/code/__DEFINES/stat_tracking.dm index 63874d74f9e..25eb910a9d1 100644 --- a/code/__DEFINES/stat_tracking.dm +++ b/code/__DEFINES/stat_tracking.dm @@ -10,23 +10,29 @@ STAT_ENTRY[STAT_ENTRY_TIME] += STAT_TIME;\ STAT_ENTRY[STAT_ENTRY_COUNT] += 1; -// Cost tracking macros, to be used in one proc -// The static lists are under the assumption that costs and counting are global lists, and will therefor -// Break during world init +// Cost tracking macros, to be used in one proc. If you're using this raw you'll want to use global lists +// If you don't you'll need another way of reading it #define INIT_COST(costs, counting) \ var/list/_costs = costs; \ var/list/_counting = counting; \ var/usage = TICK_USAGE; -// Cost tracking macro for global lists, prevents erroring if GLOB has not yet been initialized -#define INIT_COST_GLOBAL(costs, counting) \ +// STATIC cost tracking macro. Uses static lists instead of the normal global ones +// Good for debug stuff, and for running before globals init +#define INIT_COST_STATIC(...) \ var/static/list/hidden_static_list_for_fun1 = list(); \ var/static/list/hidden_static_list_for_fun2 = list(); \ + INIT_COST(hidden_static_list_for_fun1, hidden_static_list_for_fun2) + +// Cost tracking macro for global lists, prevents erroring if GLOB has not yet been initialized +#define INIT_COST_GLOBAL(costs, counting) \ + INIT_COST_STATIC() \ if(GLOB){\ costs = hidden_static_list_for_fun1; \ counting = hidden_static_list_for_fun2 ; \ } \ - INIT_COST(hidden_static_list_for_fun1, hidden_static_list_for_fun2) + usage = TICK_USAGE; + #define SET_COST(category) \ do { \ @@ -38,7 +44,10 @@ #define SET_COST_LINE(...) SET_COST("[__LINE__]") -#define EXPORT_STATS_TO_FILE_LATER(filename, costs, counts) \ +#define EXPORT_STATS_TO_JSON_LATER(filename, costs, counts) EXPORT_STATS_TO_FILE_LATER(filename, costs, counts, stat_tracking_export_to_json_later) +#define EXPORT_STATS_TO_CSV_LATER(filename, costs, counts) EXPORT_STATS_TO_FILE_LATER(filename, costs, counts, stat_tracking_export_to_csv_later) + +#define EXPORT_STATS_TO_FILE_LATER(filename, costs, counts, proc) \ do { \ var/static/last_export = 0; \ if (world.time - last_export > 1.1 SECONDS) { \ @@ -46,7 +55,7 @@ /* spawn() is used here because this is often used to track init times, where timers act oddly. */ \ /* I was making timers and even after init times were complete, the timers didn't run :shrug: */ \ spawn (1 SECONDS) { \ - stat_tracking_export_to_file_later(filename, costs, counts); \ + ##proc(filename, costs, counts); \ } \ } \ } while (FALSE); diff --git a/code/__HELPERS/_planes.dm b/code/__HELPERS/_planes.dm index a7fc3fc7db5..20f2f69ec9a 100644 --- a/code/__HELPERS/_planes.dm +++ b/code/__HELPERS/_planes.dm @@ -47,6 +47,9 @@ if(_our_turf){\ thing.plane = GET_NEW_PLANE(_cached_plane, GET_Z_PLANE_OFFSET(_our_turf.z));\ }\ + else {\ + thing.plane = new_value;\ + }\ }\ else {\ thing.plane = new_value;\ diff --git a/code/__HELPERS/icon_smoothing.dm b/code/__HELPERS/icon_smoothing.dm index cbe3fc8ca6a..5e0d1b83b27 100644 --- a/code/__HELPERS/icon_smoothing.dm +++ b/code/__HELPERS/icon_smoothing.dm @@ -127,6 +127,12 @@ DEFINE_BITFIELD(smoothing_junction, list( CRASH("smooth_icon called for [src] with smoothing_flags == [smoothing_flags]") SEND_SIGNAL(src, COMSIG_ATOM_SMOOTHED_ICON) +// As a rule, movables will most always care about smoothing changes +// Turfs on the other hand, don't, so we don't do the update for THEM unless they explicitly request it +/atom/movable/smooth_icon() + . = ..() + update_appearance(~UPDATE_SMOOTHING) + /atom/proc/corners_diagonal_smooth(adjacencies) switch(adjacencies) if(NORTH_JUNCTION|WEST_JUNCTION) diff --git a/code/__HELPERS/lighting.dm b/code/__HELPERS/lighting.dm index efde257218c..4d0105532e4 100644 --- a/code/__HELPERS/lighting.dm +++ b/code/__HELPERS/lighting.dm @@ -1,9 +1,29 @@ /// Produces a mutable appearance glued to the [EMISSIVE_PLANE] dyed to be the [EMISSIVE_COLOR]. /proc/emissive_appearance(icon, icon_state = "", atom/offset_spokesman, layer = FLOAT_LAYER, alpha = 255, appearance_flags = NONE, offset_const) - var/mutable_appearance/appearance = mutable_appearance(icon, icon_state, layer, offset_spokesman, EMISSIVE_PLANE, alpha, appearance_flags | EMISSIVE_APPEARANCE_FLAGS, offset_const) + // Note: alpha doesn't "do" anything, since it's overriden by the color set shortly after + // Consider removing it someday? (I wonder if we made emissives blend right we could make alpha actually matter. dreams man, dreams) + var/mutable_appearance/appearance = mutable_appearance(icon, icon_state, layer, offset_spokesman, EMISSIVE_PLANE, 255, appearance_flags | EMISSIVE_APPEARANCE_FLAGS, offset_const) appearance.color = GLOB.emissive_color return appearance +// This is a semi hot proc, so we micro it. saves maybe 150ms +// sorry :) +/proc/fast_emissive_blocker(atom/make_blocker) + // Note: alpha doesn't "do" anything, since it's overriden by the color set shortly after + // Consider removing it someday? + var/mutable_appearance/blocker = new() + blocker.icon = make_blocker.icon + blocker.icon_state = make_blocker.icon_state + blocker.layer = make_blocker.layer + blocker.appearance_flags |= make_blocker.appearance_flags | EMISSIVE_APPEARANCE_FLAGS + blocker.dir = make_blocker.dir + blocker.color = GLOB.em_block_color + + // Note, we are ok with null turfs, that's not an error condition we'll just default to 0, the error would be + // Not passing ANYTHING in, key difference + SET_PLANE_EXPLICIT(blocker, EMISSIVE_PLANE, make_blocker) + return blocker + /// Produces a mutable appearance glued to the [EMISSIVE_PLANE] dyed to be the [EM_BLOCK_COLOR]. /proc/emissive_blocker(icon, icon_state = "", atom/offset_spokesman, layer = FLOAT_LAYER, alpha = 255, appearance_flags = NONE, offset_const) // Note: alpha doesn't "do" anything, since it's overriden by the color set shortly after diff --git a/code/__HELPERS/stat_tracking.dm b/code/__HELPERS/stat_tracking.dm index 1fda8abdc83..3b8f82ede4e 100644 --- a/code/__HELPERS/stat_tracking.dm +++ b/code/__HELPERS/stat_tracking.dm @@ -1,3 +1,4 @@ +// For use with the stopwatch defines /proc/render_stats(list/stats, user, sort = GLOBAL_PROC_REF(cmp_generic_stat_item_time)) sortTim(stats, sort, TRUE) @@ -12,7 +13,8 @@ . = lines.Join("\n") -/proc/stat_tracking_export_to_file_later(filename, costs, counts) +// For use with the set_cost defines +/proc/stat_tracking_export_to_json_later(filename, costs, counts) if (IsAdminAdvancedProcCall()) return @@ -25,3 +27,15 @@ ) rustg_file_write(json_encode(output), "[GLOB.log_directory]/[filename]") + +/proc/stat_tracking_export_to_csv_later(filename, costs, counts) + if (IsAdminAdvancedProcCall()) + return + + var/list/output = list() + + output += "key, cost, count" + for (var/key in costs) + output += "[replacetext(key, ",", "")], [replacetext(costs[key], ",", "")], [replacetext(counts[key], ",", "")]" + + rustg_file_write(output.Join("\n"), "[GLOB.log_directory]/[filename]") diff --git a/code/_compile_options.dm b/code/_compile_options.dm index f4ce1f7f94d..7a6680dc4b2 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -11,6 +11,14 @@ #ifdef TESTING #define DATUMVAR_DEBUGGING_MODE +/// Enables update_appearance "relevence" tracking +/// This allows us to check which update_appearance procs are actually doing anything. Good thing to look in on once a year or so +/// You'll need to run a two regexes/search and replaces to make it work +/// First, one to convert type refs (PROC_REF.*)(update_appearance\)) -> $1_$2 +/// Second, one to convert definitions /update_appearance\( -> /_update_appearance( +/// We'll use another define to convert uses of the proc over. That'll be all +// #define APPEARANCE_SUCCESS_TRACKING + ///Used to find the sources of harddels, quite laggy, don't be surpised if it freezes your client for a good while //#define REFERENCE_TRACKING #ifdef REFERENCE_TRACKING diff --git a/code/datums/elements/decals/_decal.dm b/code/datums/elements/decals/_decal.dm index f5fd4a02ead..a15136eb260 100644 --- a/code/datums/elements/decals/_decal.dm +++ b/code/datums/elements/decals/_decal.dm @@ -128,11 +128,11 @@ /datum/element/decal/proc/late_update_icon(atom/source) SIGNAL_HANDLER - if(source && istype(source)) + if(istype(source) && !(source.flags_1 & DECAL_INIT_UPDATE_EXPERIENCED_1)) + source.flags_1 |= DECAL_INIT_UPDATE_EXPERIENCED_1 // I am so sorry, but it saves like 80ms I gotta source.update_appearance(UPDATE_OVERLAYS) UnregisterSignal(source, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE) - /datum/element/decal/proc/apply_overlay(atom/source, list/overlay_list) SIGNAL_HANDLER diff --git a/code/datums/materials/_material.dm b/code/datums/materials/_material.dm index 0d13fd761f9..777af6bd4f2 100644 --- a/code/datums/materials/_material.dm +++ b/code/datums/materials/_material.dm @@ -92,8 +92,6 @@ Simple datum which is instanced once per type and is used for every object of sa else if(istype(source, /turf)) //turfs on_applied_turf(source, amount, material_flags) - source.update_appearance() - source.mat_update_desc(src) ///This proc is called when a material updates an object's description diff --git a/code/datums/mutable_appearance.dm b/code/datums/mutable_appearance.dm index e1080c06a70..c066b96cea4 100644 --- a/code/datums/mutable_appearance.dm +++ b/code/datums/mutable_appearance.dm @@ -24,27 +24,25 @@ * offset_const - A constant to offset our plane by, so it renders on the right "z layer" **/ /proc/mutable_appearance(icon, icon_state = "", layer = FLOAT_LAYER, atom/offset_spokesman, plane = FLOAT_PLANE, alpha = 255, appearance_flags = NONE, offset_const) + var/mutable_appearance/appearance = new() + appearance.icon = icon + appearance.icon_state = icon_state + appearance.layer = layer + appearance.alpha = alpha + appearance.appearance_flags |= appearance_flags if(plane != FLOAT_PLANE) - // Essentially, we allow users that only want one static offset to pass one in - if(!isnull(offset_const)) - plane = GET_NEW_PLANE(plane, offset_const) - // That, or you need to pass in some non null object to reference - else if(isatom(offset_spokesman)) + // You need to pass in some non null object to reference + if(isatom(offset_spokesman)) // Note, we are ok with null turfs, that's not an error condition we'll just default to 0, the error would be // Not passing ANYTHING in, key difference - var/turf/our_turf = get_turf(offset_spokesman) - plane = MUTATE_PLANE(plane, our_turf) + SET_PLANE_EXPLICIT(appearance, plane, offset_spokesman) + // That or I'll let you pass in a static offset. Don't be stupid now + else if(!isnull(offset_const)) + SET_PLANE_W_SCALAR(appearance, plane, offset_const) // otherwise if you're setting plane you better have the guts to back it up else stack_trace("No plane offset passed in as context for a non floating mutable appearance, things are gonna go to hell on multiz maps") else if(!isnull(offset_spokesman) && !isatom(offset_spokesman)) stack_trace("Why did you pass in offset_spokesman as [offset_spokesman]? We need an atom to properly offset planes") - var/mutable_appearance/MA = new() - MA.icon = icon - MA.icon_state = icon_state - MA.layer = layer - MA.plane = plane - MA.alpha = alpha - MA.appearance_flags |= appearance_flags - return MA + return appearance diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 0e38a6710e9..cb60659e925 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -740,6 +740,24 @@ . = list() SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE_MORE, user, .) +/// Wrapper for _update_appearance that is only called when APPEARANCE_SUCCESS_TRACKING is defined +#ifdef APPEARANCE_SUCCESS_TRACKING +/atom/proc/wrap_update_appearance(file, line, updates) + INIT_COST_STATIC() + EXPORT_STATS_TO_CSV_LATER("appearance_efficency.csv", _costs, _counting) + var/old_appearance = appearance + + _update_appearance(updates) + // We're checking to see if update_appearance DID anything to our appearance + // If it didn't, or it produced the same thing, we'll mark it as such so it can potentially be opitmized + if(old_appearance == appearance) + SET_COST("SAME [file] [line]") + return FALSE + else + SET_COST("DIFFERENT [file] [line]") + return TRUE +#endif + /** * Updates the appearence of the icon * diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 88b0387548b..852c60a2cf5 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -204,9 +204,7 @@ if(!blocks_emissive) return else if (blocks_emissive == EMISSIVE_BLOCK_GENERIC) - var/mutable_appearance/gen_emissive_blocker = emissive_blocker(icon, icon_state, src, alpha = src.alpha, appearance_flags = src.appearance_flags) - gen_emissive_blocker.dir = dir - return gen_emissive_blocker + return fast_emissive_blocker(src) else if(blocks_emissive == EMISSIVE_BLOCK_UNIQUE) if(!em_block && !QDELETED(src)) render_target = ref(src) diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index 625a653e3a8..9fc13ed0c60 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -135,7 +135,7 @@ var/market_verb = "Customer" var/payment_department = ACCOUNT_ENG - // For storing and overriding ui id + /// For storing and overriding ui id var/tgui_id // ID of TGUI interface ///Is this machine currently in the atmos machinery queue? var/atmos_processing = FALSE @@ -148,6 +148,9 @@ var/always_area_sensitive = FALSE ///Multiplier for power consumption. var/machine_power_rectifier = 1 + /// What was our power state the last time we updated its appearance? + /// TRUE for on, FALSE for off, -1 for never checked + var/appearance_power_state = -1 /obj/machinery/Initialize(mapload) if(!armor) @@ -542,6 +545,21 @@ /obj/machinery/proc/on_set_is_operational(old_value) return +///Called when we want to change the value of the `panel_open` variable. Boolean. +/obj/machinery/proc/set_panel_open(new_value) + if(panel_open == new_value) + return + var/old_value = panel_open + panel_open = new_value + on_set_panel_open(old_value) + +///Called when the value of `panel_open` changes, so we can react to it. +/obj/machinery/proc/on_set_panel_open(old_value) + return + +/// Toggles the panel_open var. Defined for convienience +/obj/machinery/proc/toggle_panel_open() + set_panel_open(!panel_open) /obj/machinery/can_interact(mob/user) if((machine_stat & (NOPOWER|BROKEN)) && !(interaction_flags_machine & INTERACT_MACHINE_OFFLINE)) // Check if the machine is broken, and if we can still interact with it if so @@ -849,12 +867,11 @@ return FALSE screwdriver.play_tool_sound(src, 50) - if(!panel_open) - panel_open = TRUE + toggle_panel_open() + if(panel_open) icon_state = icon_state_open to_chat(user, span_notice("You open the maintenance hatch of [src].")) else - panel_open = FALSE icon_state = icon_state_closed to_chat(user, span_notice("You close the maintenance hatch of [src].")) return TRUE diff --git a/code/game/machinery/announcement_system.dm b/code/game/machinery/announcement_system.dm index dd8f1b4d1f9..3474c5e4cb6 100644 --- a/code/game/machinery/announcement_system.dm +++ b/code/game/machinery/announcement_system.dm @@ -54,7 +54,7 @@ GLOBAL_LIST_EMPTY(announcement_systems) /obj/machinery/announcement_system/screwdriver_act(mob/living/user, obj/item/tool) tool.play_tool_sound(src) - panel_open = !panel_open + toggle_panel_open() to_chat(user, span_notice("You [panel_open ? "open" : "close"] the maintenance hatch of [src].")) update_appearance() return TRUE diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm index 0c2c80b9a94..3e8c293ad86 100644 --- a/code/game/machinery/buttons.dm +++ b/code/game/machinery/buttons.dm @@ -22,7 +22,7 @@ . = ..() if(built) setDir(ndir) - panel_open = TRUE + set_panel_open(TRUE) update_appearance() if(!built && !device && device_type) diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index 1e82ebf66f5..f41b3b74770 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -216,7 +216,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/camera/xray, 0) /obj/machinery/camera/screwdriver_act(mob/living/user, obj/item/I) if(..()) return TRUE - panel_open = !panel_open + toggle_panel_open() to_chat(user, span_notice("You screw the camera's panel [panel_open ? "open" : "closed"].")) I.play_tool_sound(src) update_appearance() diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index c25e31f1db5..b30f8c38cae 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -735,7 +735,7 @@ if(panel_open && detonated) to_chat(user, span_warning("[src] has no maintenance panel!")) return TOOL_ACT_TOOLTYPE_SUCCESS - panel_open = !panel_open + toggle_panel_open() to_chat(user, span_notice("You [panel_open ? "open":"close"] the maintenance panel of the airlock.")) tool.play_tool_sound(src) update_appearance() @@ -1295,8 +1295,7 @@ /obj/machinery/door/airlock/proc/on_break() SIGNAL_HANDLER - if(!panel_open) - panel_open = TRUE + set_panel_open(TRUE) wires.cut_all() /obj/machinery/door/airlock/emp_act(severity) diff --git a/code/game/machinery/doors/airlock_types.dm b/code/game/machinery/doors/airlock_types.dm index 871d9722890..8c4cfafa8e4 100644 --- a/code/game/machinery/doors/airlock_types.dm +++ b/code/game/machinery/doors/airlock_types.dm @@ -503,8 +503,7 @@ return (IS_CULTIST(user) && !isAllPowerCut()) /obj/machinery/door/airlock/cult/on_break() - if(!panel_open) - panel_open = TRUE + set_panel_open(TRUE) /obj/machinery/door/airlock/cult/isElectrified() return FALSE diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index 4346272b2a3..411847a0fe8 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -293,7 +293,7 @@ return add_fingerprint(user) tool.play_tool_sound(src) - panel_open = !panel_open + toggle_panel_open() to_chat(user, span_notice("You [panel_open ? "open" : "close"] the maintenance panel.")) return TRUE diff --git a/code/game/machinery/firealarm.dm b/code/game/machinery/firealarm.dm index 547b59f5cd6..43c2ecbbfb8 100644 --- a/code/game/machinery/firealarm.dm +++ b/code/game/machinery/firealarm.dm @@ -42,7 +42,7 @@ . = ..() if(building) buildstage = 0 - panel_open = TRUE + set_panel_open(TRUE) if(name == initial(name)) name = "[get_area_name(src)] [initial(name)]" update_appearance() @@ -256,7 +256,7 @@ if(tool.tool_behaviour == TOOL_SCREWDRIVER && buildstage == 2) tool.play_tool_sound(src) - panel_open = !panel_open + toggle_panel_open() to_chat(user, span_notice("The wires have been [panel_open ? "exposed" : "unexposed"].")) update_appearance() return diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm index 5a634edf780..51f8014026d 100644 --- a/code/game/machinery/hologram.dm +++ b/code/game/machinery/hologram.dm @@ -438,6 +438,7 @@ Possible to do for anyone motivated enough: if(!LAZYLEN(holo_calls)) set_can_hear_flags(CAN_HEAR_ACTIVE_HOLOCALLS, FALSE) + update_appearance(UPDATE_ICON_STATE) return TRUE /** @@ -487,7 +488,7 @@ Possible to do for anyone motivated enough: if(outgoing_call) outgoing_call.Check() - ringing = FALSE + var/are_ringing = FALSE for(var/datum/holocall/holocall as anything in holo_calls) if(holocall.connected_holopad == src) @@ -503,9 +504,11 @@ Possible to do for anyone motivated enough: holocall.Disconnect(src)//can't answer calls while calling else playsound(src, 'sound/machines/twobeep.ogg', 100) //bring, bring! - ringing = TRUE + are_ringing = TRUE - update_appearance(UPDATE_ICON_STATE) + if(ringing != are_ringing) + update_appearance(UPDATE_ICON_STATE) + ringing = are_ringing /obj/machinery/holopad/proc/activate_holo(mob/living/user) var/mob/living/silicon/ai/AI = user diff --git a/code/game/machinery/shieldgen.dm b/code/game/machinery/shieldgen.dm index 9bde27f4c2e..c8412dd3a80 100644 --- a/code/game/machinery/shieldgen.dm +++ b/code/game/machinery/shieldgen.dm @@ -187,7 +187,7 @@ /obj/machinery/shieldgen/screwdriver_act(mob/living/user, obj/item/tool) tool.play_tool_sound(src, 100) - panel_open = !panel_open + toggle_panel_open() if(panel_open) to_chat(user, span_notice("You open the panel and expose the wiring.")) else diff --git a/code/game/machinery/spaceheater.dm b/code/game/machinery/spaceheater.dm index d4bb0038f22..572caa738b7 100644 --- a/code/game/machinery/spaceheater.dm +++ b/code/game/machinery/spaceheater.dm @@ -96,6 +96,10 @@ if(panel_open && display_panel) . += "[base_icon_state]-open" +/obj/machinery/space_heater/on_set_panel_open() + update_appearance() + return ..() + /obj/machinery/space_heater/process_atmos() if(!on || !is_operational) if (on) // If it's broken, turn it off too @@ -275,8 +279,7 @@ /obj/machinery/space_heater/constructed/Initialize(mapload) . = ..() - panel_open = TRUE - update_appearance() + set_panel_open(TRUE) /obj/machinery/space_heater/proc/toggle_power(user) on = !on diff --git a/code/game/machinery/status_display.dm b/code/game/machinery/status_display.dm index 361a4a752c6..f332a293d19 100644 --- a/code/game/machinery/status_display.dm +++ b/code/game/machinery/status_display.dm @@ -97,13 +97,17 @@ line1 = uppertext(line1) line2 = uppertext(line2) + var/message_changed = FALSE if(line1 != message1) message1 = line1 + message_changed = TRUE if(line2 != message2) message2 = line2 + message_changed = TRUE - update_appearance() + if(message_changed) + update_appearance() /** * Remove both message objs and null the fields. diff --git a/code/game/machinery/telecomms/telecomunications.dm b/code/game/machinery/telecomms/telecomunications.dm index 6f3f886b91e..679bbd1eafb 100644 --- a/code/game/machinery/telecomms/telecomunications.dm +++ b/code/game/machinery/telecomms/telecomunications.dm @@ -141,8 +141,12 @@ GLOBAL_LIST_EMPTY(telecomms_list) icon_state = "[initial(icon_state)][panel_open ? "_o" : null][on ? null : "_off"]" return ..() -/obj/machinery/telecomms/proc/update_power() +/obj/machinery/telecomms/on_set_panel_open(old_value) + update_appearance() + return ..() +/obj/machinery/telecomms/proc/update_power() + var/old_on = on if(toggled) if(machine_stat & (BROKEN|NOPOWER|EMPED)) // if powered, on. if not powered, off. if too damaged, off on = FALSE @@ -153,13 +157,12 @@ GLOBAL_LIST_EMPTY(telecomms_list) else on = FALSE soundloop.stop() + if(old_on != on) + update_appearance() /obj/machinery/telecomms/process(delta_time) update_power() - // Update the icon - update_appearance() - if(traffic > 0) traffic -= netspeed * delta_time diff --git a/code/game/objects/structures/barsigns.dm b/code/game/objects/structures/barsigns.dm index 9b1891546fa..9ba4bafa62c 100644 --- a/code/game/objects/structures/barsigns.dm +++ b/code/game/objects/structures/barsigns.dm @@ -76,10 +76,10 @@ /obj/structure/sign/barsign/screwdriver_act(mob/living/user, obj/item/tool) tool.play_tool_sound(src) - if(!panel_open) + panel_open = !panel_open + if(panel_open) to_chat(user, span_notice("You open the maintenance panel.")) set_sign(new /datum/barsign/hiddensigns/signoff) - panel_open = TRUE return TOOL_ACT_TOOLTYPE_SUCCESS to_chat(user, span_notice("You close the maintenance panel.")) @@ -89,7 +89,6 @@ set_sign(new /datum/barsign/hiddensigns/signoff) else set_sign(chosen_sign) - panel_open = FALSE return TOOL_ACT_TOOLTYPE_SUCCESS /obj/structure/sign/barsign/attackby(obj/item/I, mob/user) diff --git a/code/game/turfs/closed/wall/reinf_walls.dm b/code/game/turfs/closed/wall/reinf_walls.dm index 9b507daf912..f41e21be848 100644 --- a/code/game/turfs/closed/wall/reinf_walls.dm +++ b/code/game/turfs/closed/wall/reinf_walls.dm @@ -197,6 +197,7 @@ QUEUE_SMOOTH_NEIGHBORS(src) QUEUE_SMOOTH(src) +// We don't react to smoothing changing here because this else exists only to "revert" intact changes /turf/closed/wall/r_wall/update_icon_state() if(d_state != INTACT) icon_state = "r_wall-[d_state]" diff --git a/code/game/turfs/open/floor/fancy_floor.dm b/code/game/turfs/open/floor/fancy_floor.dm index b3c7cfaf0bd..ca630953726 100644 --- a/code/game/turfs/open/floor/fancy_floor.dm +++ b/code/game/turfs/open/floor/fancy_floor.dm @@ -423,6 +423,10 @@ desc = "A glow-in-the-dark floor used to test emissive turfs." floor_tile = /obj/item/stack/tile/emissive_test +/turf/open/floor/emissive_test/smooth_icon() + . = ..() + update_appearance(~UPDATE_SMOOTHING) + /turf/open/floor/emissive_test/update_overlays() . = ..() . += emissive_appearance(icon, icon_state, src, alpha = src.alpha) diff --git a/code/modules/atmospherics/machinery/airalarm.dm b/code/modules/atmospherics/machinery/airalarm.dm index 9d145f05baa..afcc8063dfe 100644 --- a/code/modules/atmospherics/machinery/airalarm.dm +++ b/code/modules/atmospherics/machinery/airalarm.dm @@ -131,7 +131,7 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm) if(nbuild) buildstage = AIRALARM_BUILD_NO_CIRCUIT - panel_open = TRUE + set_panel_open(TRUE) if(name == initial(name)) name = "[get_area_name(src)] Air Alarm" @@ -723,7 +723,7 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm) if(buildstage != AIRALARM_BUILD_COMPLETE) return tool.play_tool_sound(src) - panel_open = !panel_open + toggle_panel_open() to_chat(user, span_notice("The wires have been [panel_open ? "exposed" : "unexposed"].")) update_appearance() return TRUE diff --git a/code/modules/atmospherics/machinery/bluespace_vendor.dm b/code/modules/atmospherics/machinery/bluespace_vendor.dm index 30b8951e3aa..5929ec70290 100644 --- a/code/modules/atmospherics/machinery/bluespace_vendor.dm +++ b/code/modules/atmospherics/machinery/bluespace_vendor.dm @@ -57,7 +57,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/bluespace_vendor, 30) . = ..() if(nbuild) - panel_open = TRUE + set_panel_open(TRUE) update_appearance() diff --git a/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm b/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm index af52c1a78a8..6151abc6520 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm @@ -147,15 +147,15 @@ if(generator) disconnectFromGenerator() mode = !mode - to_chat(user, span_notice("You set [src] to [mode?"cold":"hot"] mode.")) + to_chat(user, span_notice("You set [src] to [mode ? "cold" : "hot"] mode.")) return TRUE /obj/machinery/atmospherics/components/binary/circulator/screwdriver_act(mob/user, obj/item/I) if(..()) return TRUE - panel_open = !panel_open + toggle_panel_open() I.play_tool_sound(src) - to_chat(user, span_notice("You [panel_open?"open":"close"] the panel on [src].")) + to_chat(user, span_notice("You [panel_open ? "open" : "close"] the panel on [src].")) return TRUE /obj/machinery/atmospherics/components/binary/circulator/crowbar_act(mob/user, obj/item/I) diff --git a/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm index 6cafb58e25b..ac6e129db28 100644 --- a/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm +++ b/code/modules/atmospherics/machinery/components/electrolyzer/electrolyzer.dm @@ -153,7 +153,7 @@ /obj/machinery/electrolyzer/screwdriver_act(mob/living/user, obj/item/tool) tool.play_tool_sound(src, 50) - panel_open = !panel_open + toggle_panel_open() user.visible_message(span_notice("\The [user] [panel_open ? "opens" : "closes"] the hatch on \the [src]."), span_notice("You [panel_open ? "open" : "close"] the hatch on \the [src].")) update_appearance() return TRUE diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index 27bd8f2a8a3..aee0513a7f9 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -579,19 +579,25 @@ GLOBAL_LIST_INIT(gas_id_to_canister, init_gas_id_to_canister()) valve_open = !valve_open timing = FALSE + var/visual_update = FALSE // Handle gas transfer. if(valve_open) var/turf/location = get_turf(src) var/datum/gas_mixture/target_air = holding?.return_air() || location.return_air() excited = TRUE - if(air_contents.release_gas_to(target_air, release_pressure) && !holding) - air_update_turf(FALSE, FALSE) + if(air_contents.release_gas_to(target_air, release_pressure)) + visual_update = TRUE + if(!holding) + air_update_turf(FALSE, FALSE) // A bit different than other atmos devices. Wont stop if currently taking damage. if(take_atmos_damage()) + visual_update = TRUE excited = TRUE - update_appearance() + + if(visual_update) + update_appearance() return ..() /obj/machinery/portable_atmospherics/canister/ui_state(mob/user) diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm index 867d305b343..b2f40ba68fc 100644 --- a/code/modules/mapping/mapping_helpers.dm +++ b/code/modules/mapping/mapping_helpers.dm @@ -161,12 +161,12 @@ if(16 to 23) airlock.welded = TRUE if(24 to 30) - airlock.panel_open = TRUE + airlock.set_panel_open(TRUE) if(airlock.cutAiWire) airlock.wires.cut(WIRE_AI) if(airlock.autoname) airlock.name = get_area_name(src, TRUE) - update_appearance() + airlock.update_appearance() qdel(src) /obj/effect/mapping_helpers/airlock/proc/payload(obj/machinery/door/airlock/payload) diff --git a/code/modules/power/apc/apc_tool_act.dm b/code/modules/power/apc/apc_tool_act.dm index 62d3b100850..543a75d7e87 100644 --- a/code/modules/power/apc/apc_tool_act.dm +++ b/code/modules/power/apc/apc_tool_act.dm @@ -62,7 +62,7 @@ if(obj_flags & EMAGGED) balloon_alert(user, "the interface is broken!") return - panel_open = !panel_open + toggle_panel_open() balloon_alert(user, "wires are [panel_open ? "exposed" : "unexposed"]") update_appearance() return diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm index fe40682a1ab..768d391ccc8 100644 --- a/code/modules/power/cable.dm +++ b/code/modules/power/cable.dm @@ -151,6 +151,7 @@ GLOBAL_LIST_INIT(wire_node_generating_types, typecacheof(list(/obj/structure/gri icon_state = "l[cable_layer]-noconnection" return ..() + // TODO: stop doing this shit in update_icon_state, this should be event based for the love of all that is holy var/list/dir_icon_list = list() for(var/check_dir in GLOB.cardinals) if(linked_dirs & check_dir) diff --git a/code/modules/power/generator.dm b/code/modules/power/generator.dm index e3e84e04511..28913ed2c4b 100644 --- a/code/modules/power/generator.dm +++ b/code/modules/power/generator.dm @@ -206,7 +206,7 @@ /obj/machinery/power/generator/screwdriver_act(mob/user, obj/item/I) if(..()) return TRUE - panel_open = !panel_open + toggle_panel_open() I.play_tool_sound(src) to_chat(user, span_notice("You [panel_open?"open":"close"] the panel on [src].")) return TRUE diff --git a/code/modules/power/port_gen.dm b/code/modules/power/port_gen.dm index 6d0913ad782..56f278bdaa1 100644 --- a/code/modules/power/port_gen.dm +++ b/code/modules/power/port_gen.dm @@ -198,7 +198,7 @@ playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE) return else if(O.tool_behaviour == TOOL_SCREWDRIVER) - panel_open = !panel_open + toggle_panel_open() O.play_tool_sound(src) if(panel_open) to_chat(user, span_notice("You open the access panel.")) diff --git a/code/modules/power/power.dm b/code/modules/power/power.dm index c6c9e477b9e..3029558d167 100644 --- a/code/modules/power/power.dm +++ b/code/modules/power/power.dm @@ -188,7 +188,14 @@ if(!(initial_stat & NOPOWER)) SEND_SIGNAL(src, COMSIG_MACHINERY_POWER_LOST) . = TRUE - update_appearance() + + if(appearance_power_state != (machine_stat & NOPOWER)) + update_appearance() + +// Saves like 300ms of init by not duping calls in the above proc +/obj/machinery/update_appearance(updates) + . = ..() + appearance_power_state = machine_stat & NOPOWER // connect the machine to a powernet if a node cable or a terminal is present on the turf /obj/machinery/power/proc/connect_to_network() @@ -426,6 +433,6 @@ return null for(var/obj/structure/cable/C in src) if(C.machinery_layer & machinery_layer) - C.update_appearance() + C.update_appearance() // I hate this. it's here because update_icon_state SCANS nearby turfs for objects to connect to. Wastes cpu time return C return null diff --git a/code/modules/power/turbine/turbine.dm b/code/modules/power/turbine/turbine.dm index 521dc342972..2ec930d36d4 100644 --- a/code/modules/power/turbine/turbine.dm +++ b/code/modules/power/turbine/turbine.dm @@ -95,13 +95,12 @@ return TOOL_ACT_TOOLTYPE_SUCCESS tool.play_tool_sound(src, 50) - panel_open = !panel_open + toggle_panel_open() if(panel_open) disable_parts(user) else enable_parts(user) - var/descriptor = panel_open ? "open" : "close" - balloon_alert(user, "you [descriptor] the maintenance hatch of [src]") + balloon_alert(user, "you [panel_open ? "open" : "close"] the maintenance hatch of [src]") update_appearance() return TOOL_ACT_TOOLTYPE_SUCCESS diff --git a/code/modules/recycling/disposal/bin.dm b/code/modules/recycling/disposal/bin.dm index 3aa8eb8db25..116d387e973 100644 --- a/code/modules/recycling/disposal/bin.dm +++ b/code/modules/recycling/disposal/bin.dm @@ -91,7 +91,7 @@ add_fingerprint(user) if(!pressure_charging && !full_pressure && !flush) if(I.tool_behaviour == TOOL_SCREWDRIVER) - panel_open = !panel_open + toggle_panel_open() I.play_tool_sound(src) to_chat(user, span_notice("You [panel_open ? "remove":"attach"] the screws around the power connection.")) return diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index 2cfd88c019b..cc9d96ca8b9 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -1438,7 +1438,7 @@ GLOBAL_LIST_EMPTY(vending_products) /obj/machinery/vending/custom/greed/Initialize(mapload) . = ..() //starts in a state where you can move it - panel_open = TRUE + set_panel_open(TRUE) set_anchored(FALSE) add_overlay(panel_type) //and references the deity diff --git a/tgstation.dme b/tgstation.dme index ec287eb316f..e00f911502b 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -38,6 +38,7 @@ #include "code\__DEFINES\alerts.dm" #include "code\__DEFINES\antagonists.dm" #include "code\__DEFINES\apc_defines.dm" +#include "code\__DEFINES\appearance.dm" #include "code\__DEFINES\aquarium.dm" #include "code\__DEFINES\art.dm" #include "code\__DEFINES\assemblies.dm"