diff --git a/code/__DEFINES/_flags/_flags.dm b/code/__DEFINES/_flags/_flags.dm index cd38c9ad5b..8ffbde37af 100644 --- a/code/__DEFINES/_flags/_flags.dm +++ b/code/__DEFINES/_flags/_flags.dm @@ -26,32 +26,30 @@ GLOBAL_LIST_INIT(bitflags, list( //FLAGS BITMASK ///This flag is what recursive_hear_check() uses to determine wether to add an item to the hearer list or not. -#define HEAR_1 (1<<3) +#define HEAR_1 (1<<0) ///Projectiles will use default chance-based ricochet handling on things with this. -#define DEFAULT_RICOCHET_1 (1<<4) +#define DEFAULT_RICOCHET_1 (1<<1) ///Conducts electricity (metal etc.). -#define CONDUCT_1 (1<<5) +#define CONDUCT_1 (1<<2) ///For machines and structures that should not break into parts, eg, holodeck stuff. -#define NODECONSTRUCT_1 (1<<7) -///Atom queued to SSoverlay. -#define OVERLAY_QUEUED_1 (1<<8) +#define NODECONSTRUCT_1 (1<<3) ///Item has priority to check when entering or leaving. -#define ON_BORDER_1 (1<<9) +#define ON_BORDER_1 (1<<4) ///Whether or not this atom shows screentips when hovered over -#define NO_SCREENTIPS_1 (1<<10) +#define NO_SCREENTIPS_1 (1<<5) ///Prevent clicking things below it on the same turf eg. doors/ fulltile windows. -#define PREVENT_CLICK_UNDER_1 (1<<11) -#define HOLOGRAM_1 (1<<12) +#define PREVENT_CLICK_UNDER_1 (1<<6) +#define HOLOGRAM_1 (1<<7) ///Prevents mobs from getting chainshocked by teslas and the supermatter. -#define SHOCKED_1 (1<<13) +#define SHOCKED_1 (1<<8) ///Whether /atom/Initialize() has already run for the object. -#define INITIALIZED_1 (1<<14) +#define INITIALIZED_1 (1<<9) ///was this spawned by an admin? used for stat tracking stuff. -#define ADMIN_SPAWNED_1 (1<<15) +#define ADMIN_SPAWNED_1 (1<<10) /// should not get harmed if this gets caught by an explosion? -#define PREVENT_CONTENTS_EXPLOSION_1 (1<<16) +#define PREVENT_CONTENTS_EXPLOSION_1 (1<<11) /// Early returns mob.face_atom() -#define BLOCK_FACE_ATOM_1 (1<<17) +#define BLOCK_FACE_ATOM_1 (1<<12) //turf-only flags #define NOJAUNT_1 (1<<0) diff --git a/code/__DEFINES/overlays.dm b/code/__DEFINES/overlays.dm new file mode 100644 index 0000000000..3e5959a680 --- /dev/null +++ b/code/__DEFINES/overlays.dm @@ -0,0 +1,24 @@ +// A reasonable number of maximum overlays an object needs +// If you think you need more, rethink it +#define MAX_ATOM_OVERLAYS 100 + +/// Checks if an atom has reached the overlay limit, and make a loud error if it does. +#define VALIDATE_OVERLAY_LIMIT(changed_on) \ + if(length(changed_on.overlays) >= MAX_ATOM_OVERLAYS) { \ + var/text_lays = overlays2text(changed_on.overlays); \ + stack_trace("Too many overlays on [changed_on.type] - [length(changed_on.overlays)], refusing to update and cutting.\ + \n What follows is a printout of all existing overlays at the time of the overflow \n[text_lays]"); \ + changed_on.overlays.Cut(); \ + changed_on.add_overlay(mutable_appearance('icons/testing/greyscale_error.dmi')); \ + } \ + +/// Performs any operations that ought to run after an appearance change +/* #define POST_OVERLAY_CHANGE(changed_on) \ + if(alternate_appearances) { \ + for(var/I in changed_on.alternate_appearances){\ + var/datum/atom_hud/alternate_appearance/AA = changed_on.alternate_appearances[I];\ + if(AA.transfer_overlays){\ + AA.copy_overlays(changed_on, TRUE);\ + }\ + } \ + } */ diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 49bad31a60..a66ec3ca61 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -228,25 +228,6 @@ #define SSMOBS_DT (SSmobs.wait/10) #define SSOBJ_DT (SSobj.wait/10) -//! ## Overlays subsystem - -///Compile all the overlays for an atom from the cache lists -// |= on overlays is not actually guaranteed to not add same appearances but we're optimistically using it anyway. -#define COMPILE_OVERLAYS(A)\ - do {\ - var/list/ad = A.add_overlays;\ - var/list/rm = A.remove_overlays;\ - if(LAZYLEN(rm)){\ - A.overlays -= rm;\ - rm.Cut();\ - }\ - if(LAZYLEN(ad)){\ - A.overlays |= ad;\ - ad.Cut();\ - }\ - A.flags_1 &= ~OVERLAY_QUEUED_1;\ - } while(FALSE) - /** Create a new timer and add it to the queue. * Arguments: diff --git a/code/__HELPERS/custom_holoforms.dm b/code/__HELPERS/custom_holoforms.dm index 87f3bfbe8b..4761c1f160 100644 --- a/code/__HELPERS/custom_holoforms.dm +++ b/code/__HELPERS/custom_holoforms.dm @@ -25,7 +25,6 @@ var/icon/combined = new for(var/d in GLOB.cardinals) mannequin.setDir(d) - COMPILE_OVERLAYS(mannequin) CHECK_TICK var/icon/capture = getFlatIcon(mannequin) CHECK_TICK diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm index eb0aa448d3..e5aff01eef 100644 --- a/code/__HELPERS/icons.dm +++ b/code/__HELPERS/icons.dm @@ -1065,7 +1065,6 @@ GLOBAL_LIST_EMPTY(friendly_animal_types) var/icon/out_icon = icon('icons/effects/effects.dmi', "nothing") - COMPILE_OVERLAYS(body) for(var/D in showDirs) var/icon/partial = getFlatIcon(body, defdir = D, no_anim = no_anim) out_icon.Insert(partial,dir=D) diff --git a/code/_compile_options.dm b/code/_compile_options.dm index aef0022c4f..1ed2075537 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -79,10 +79,6 @@ #define CBT #endif -// A reasonable number of maximum overlays an object needs -// If you think you need more, rethink it -#define MAX_ATOM_OVERLAYS 100 - #if !defined(CBT) && !defined(SPACEMAN_DMM) #warn Building with Dream Maker is no longer supported and will result in errors. #warn In order to build, run BUILD.bat in the root directory. diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index d16c24be44..e4ac2fe140 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -164,7 +164,6 @@ DEFINE_BITFIELD(flags_1, list( "NO_LAVA_GEN_1" = NO_LAVA_GEN_1, "NO_RUINS_1" = NO_RUINS_1, "NO_SCREENTIPS_1" = NO_SCREENTIPS_1, - "OVERLAY_QUEUED_1" = OVERLAY_QUEUED_1, "ON_BORDER_1" = ON_BORDER_1, "PREVENT_CLICK_UNDER_1" = PREVENT_CLICK_UNDER_1, "PREVENT_CONTENTS_EXPLOSION_1" = PREVENT_CONTENTS_EXPLOSION_1, diff --git a/code/controllers/subsystem/overlays.dm b/code/controllers/subsystem/overlays.dm index f7b1be0fcf..d5712273ea 100644 --- a/code/controllers/subsystem/overlays.dm +++ b/code/controllers/subsystem/overlays.dm @@ -1,68 +1,30 @@ SUBSYSTEM_DEF(overlays) name = "Overlay" - flags = SS_TICKER - wait = 1 - priority = FIRE_PRIORITY_OVERLAYS - init_order = INIT_ORDER_OVERLAY - - var/list/queue + flags = SS_NO_FIRE|SS_NO_INIT var/list/stats /datum/controller/subsystem/overlays/PreInit() - queue = list() stats = list() -/datum/controller/subsystem/overlays/Initialize() - initialized = TRUE - fire(mc_check = FALSE) - return ..() - - -/datum/controller/subsystem/overlays/stat_entry(msg) - msg = "Ov:[length(queue)]" - return ..() - - /datum/controller/subsystem/overlays/Shutdown() text2file(render_stats(stats), "[GLOB.log_directory]/overlay.log") - /datum/controller/subsystem/overlays/Recover() - queue = SSoverlays.queue - - -/datum/controller/subsystem/overlays/fire(resumed = FALSE, mc_check = TRUE) - var/list/queue = src.queue - var/static/count = 0 - if (count) - var/c = count - count = 0 //so if we runtime on the Cut, we don't try again. - queue.Cut(1,c+1) - - for (var/thing in queue) - count++ - if(thing) - var/atom/A = thing - if(A.overlays.len >= MAX_ATOM_OVERLAYS) - //Break it real GOOD - stack_trace("Too many overlays on [A.type] - [A.overlays.len], refusing to update and cutting") - A.overlays.Cut() - continue - STAT_START_STOPWATCH - COMPILE_OVERLAYS(A) - UNSETEMPTY(A.add_overlays) - UNSETEMPTY(A.remove_overlays) - STAT_STOP_STOPWATCH - STAT_LOG_ENTRY(stats, A.type) - if(mc_check) - if(MC_TICK_CHECK) - break - else - CHECK_TICK - if (count) - queue.Cut(1,count+1) - count = 0 + stats = SSoverlays.stats +/// Converts an overlay list into text for debug printing +/// Of note: overlays aren't actually mutable appearances, they're just appearances +/// Don't have access to that type tho, so this is the best you're gonna get +/proc/overlays2text(list/overlays) + var/list/unique_overlays = list() + // As anything because we're basically doing type coerrsion, rather then actually filtering for mutable apperances + for(var/mutable_appearance/overlay as anything in overlays) + var/key = "[overlay.icon]-[overlay.icon_state]-[overlay.dir]" + unique_overlays[key] += 1 + var/list/output_text = list() + for(var/key in unique_overlays) + output_text += "([key]) = [unique_overlays[key]]" + return output_text.Join("\n") /proc/iconstate2appearance(icon, iconstate) var/static/image/stringbro = new() @@ -75,81 +37,54 @@ SUBSYSTEM_DEF(overlays) iconbro.icon = icon return iconbro.appearance -/atom/proc/build_appearance_list(old_overlays) - var/static/image/appearance_bro = new() - var/list/new_overlays = list() - if (!islist(old_overlays)) - old_overlays = list(old_overlays) - for (var/overlay in old_overlays) +/atom/proc/build_appearance_list(list/build_overlays) + if (!islist(build_overlays)) + build_overlays = list(build_overlays) + for (var/overlay in build_overlays) if(!overlay) + build_overlays -= overlay continue if (istext(overlay)) -#ifdef UNIT_TESTS - // This is too expensive to run normally but running it during CI is a good test - var/list/icon_states_available = icon_states(icon) - if(!(overlay in icon_states_available)) - var/icon_file = "[icon]" || "Unknown Generated Icon" - stack_trace("Invalid overlay: Icon object '[icon_file]' [REF(icon)] used in '[src]' [type] is missing icon state [overlay].") - continue -#endif - new_overlays += iconstate2appearance(icon, overlay) + // This is too expensive to run normally but running it during CI is a good test - Unexistant test + /* if (PERFORM_ALL_TESTS(focus_only/invalid_overlays)) + var/list/icon_states_available = icon_states(icon) + if(!(overlay in icon_states_available)) + var/icon_file = "[icon]" || "Unknown Generated Icon" + stack_trace("Invalid overlay: Icon object '[icon_file]' [REF(icon)] used in '[src]' [type] is missing icon state [overlay].") + continue */ + + var/index = build_overlays.Find(overlay) + build_overlays[index] = iconstate2appearance(icon, overlay) else if(isicon(overlay)) - new_overlays += icon2appearance(overlay) - else - if(isloc(overlay)) - var/atom/A = overlay - if (A.flags_1 & OVERLAY_QUEUED_1) - COMPILE_OVERLAYS(A) - appearance_bro.appearance = overlay //this works for images and atoms too! - if(!ispath(overlay)) - var/image/I = overlay - appearance_bro.dir = I.dir - new_overlays += appearance_bro.appearance - return new_overlays + var/index = build_overlays.Find(overlay) + build_overlays[index] = icon2appearance(overlay) + return build_overlays -#define NOT_QUEUED_ALREADY (!(flags_1 & OVERLAY_QUEUED_1)) -#define QUEUE_FOR_COMPILE flags_1 |= OVERLAY_QUEUED_1; SSoverlays.queue += src; /atom/proc/cut_overlays() - LAZYINITLIST(remove_overlays) - remove_overlays = overlays.Copy() - add_overlays = null + STAT_START_STOPWATCH + overlays = null +// POST_OVERLAY_CHANGE(src) + STAT_STOP_STOPWATCH + STAT_LOG_ENTRY(SSoverlays.stats, type) - //If not already queued for work and there are overlays to remove - if(NOT_QUEUED_ALREADY && remove_overlays.len) - QUEUE_FOR_COMPILE - -/atom/proc/cut_overlay(list/overlays) +/atom/proc/cut_overlay(list/remove_overlays) if(!overlays) return - overlays = build_appearance_list(overlays) - LAZYINITLIST(add_overlays) - LAZYINITLIST(remove_overlays) - var/a_len = add_overlays.len - var/r_len = remove_overlays.len - remove_overlays += overlays - add_overlays -= overlays + STAT_START_STOPWATCH + overlays -= build_appearance_list(remove_overlays) +// POST_OVERLAY_CHANGE(src) + STAT_STOP_STOPWATCH + STAT_LOG_ENTRY(SSoverlays.stats, type) - var/fa_len = add_overlays.len - var/fr_len = remove_overlays.len - - //If not already queued and there is work to be done - if(NOT_QUEUED_ALREADY && (fa_len != a_len || fr_len != r_len )) - QUEUE_FOR_COMPILE - UNSETEMPTY(add_overlays) - -/atom/proc/add_overlay(list/overlays) +/atom/proc/add_overlay(list/add_overlays) if(!overlays) return - - overlays = build_appearance_list(overlays) - - LAZYINITLIST(add_overlays) //always initialized after this point - var/a_len = add_overlays.len - - add_overlays += overlays - var/fa_len = add_overlays.len - if(NOT_QUEUED_ALREADY && fa_len != a_len) - QUEUE_FOR_COMPILE + STAT_START_STOPWATCH + overlays += build_appearance_list(add_overlays) + VALIDATE_OVERLAY_LIMIT(src) +// POST_OVERLAY_CHANGE(src) + STAT_STOP_STOPWATCH + STAT_LOG_ENTRY(SSoverlays.stats, type) /atom/proc/copy_overlays(atom/other, cut_old) //copys our_overlays from another atom if(!other) @@ -157,18 +92,23 @@ SUBSYSTEM_DEF(overlays) cut_overlays() return + STAT_START_STOPWATCH var/list/cached_other = other.overlays.Copy() - if(cached_other) - if(cut_old || !LAZYLEN(overlays)) - remove_overlays = overlays - add_overlays = cached_other - if(NOT_QUEUED_ALREADY) - QUEUE_FOR_COMPILE - else if(cut_old) - cut_overlays() - -#undef NOT_QUEUED_ALREADY -#undef QUEUE_FOR_COMPILE + if(cut_old) + if(cached_other) + overlays = cached_other + else + overlays = null + VALIDATE_OVERLAY_LIMIT(src) +// POST_OVERLAY_CHANGE(src) + STAT_STOP_STOPWATCH + STAT_LOG_ENTRY(SSoverlays.stats, type) + else if(cached_other) + overlays += cached_other + VALIDATE_OVERLAY_LIMIT(src) +// POST_OVERLAY_CHANGE(src) + STAT_STOP_STOPWATCH + STAT_LOG_ENTRY(SSoverlays.stats, type) //TODO: Better solution for these? /image/proc/add_overlay(x) @@ -194,3 +134,100 @@ SUBSYSTEM_DEF(overlays) overlays |= cached_other else if(cut_old) cut_overlays() + +// Debug procs + +/atom + /// List of overlay "keys" (info about the appearance) -> mutable versions of static appearances + /// Drawn from the overlays list + var/list/realized_overlays + /// List of underlay "keys" (info about the appearance) -> mutable versions of static appearances + /// Drawn from the underlays list + var/list/realized_underlays + +/image + /// List of overlay "keys" (info about the appearance) -> mutable versions of static appearances + /// Drawn from the overlays list + var/list/realized_overlays + /// List of underlay "keys" (info about the appearance) -> mutable versions of static appearances + /// Drawn from the underlays list + var/list/realized_underlays + +/// Takes the atoms's existing overlays and underlays, and makes them mutable so they can be properly vv'd in the realized_overlays/underlays list +/atom/proc/realize_overlays() + realized_overlays = realize_appearance_queue(overlays) + realized_underlays = realize_appearance_queue(underlays) + +/// Takes the image's existing overlays, and makes them mutable so they can be properly vv'd in the realized_overlays list +/image/proc/realize_overlays() + realized_overlays = realize_appearance_queue(overlays) + realized_underlays = realize_appearance_queue(underlays) + +/// Takes a list of appearnces, makes them mutable so they can be properly vv'd and inspected +/proc/realize_appearance_queue(list/appearances) + var/list/real_appearances = list() + var/list/queue = appearances.Copy() + var/queue_index = 0 + while(queue_index < length(queue)) + queue_index++ + // If it's not a command, we assert that it's an appearance + var/mutable_appearance/appearance = queue[queue_index] + if(!appearance) // Who fucking adds nulls to their sublists god you people are the worst + continue + + var/mutable_appearance/new_appearance = new /mutable_appearance() + new_appearance.appearance = appearance + var/key = "[appearance.icon]-[appearance.icon_state]-[appearance.plane]-[appearance.layer]-[appearance.dir]-[appearance.color]" + var/tmp_key = key + var/appearance_indx = 1 + while(real_appearances[tmp_key]) + tmp_key = "[key]-[appearance_indx]" + appearance_indx++ + + real_appearances[tmp_key] = new_appearance + var/add_index = queue_index + // Now check its children + for(var/mutable_appearance/child_appearance as anything in appearance.overlays) + add_index++ + queue.Insert(add_index, child_appearance) + for(var/mutable_appearance/child_appearance as anything in appearance.underlays) + add_index++ + queue.Insert(add_index, child_appearance) + return real_appearances + +/// Takes two appearances as args, prints out, logs, and returns a text representation of their differences +/// Including suboverlays +/proc/diff_appearances(mutable_appearance/first, mutable_appearance/second, iter = 0) + var/list/diffs = list() + var/list/firstdeet = first.vars + var/list/seconddeet = second.vars + var/diff_found = FALSE + for(var/name in first.vars) + var/firstv = firstdeet[name] + var/secondv = seconddeet[name] + if(firstv ~= secondv) + continue + if((islist(firstv) || islist(secondv)) && length(firstv) == 0 && length(secondv) == 0) + continue + if(name == "vars") // Go away + continue + if(name == "_listen_lookup") // This is just gonna happen with marked datums, don't care + continue + if(name == "overlays") + first.realize_overlays() + second.realize_overlays() + var/overlays_differ = FALSE + for(var/i in 1 to length(first.realized_overlays)) + if(diff_appearances(first.realized_overlays[i], second.realized_overlays[i], iter + 1)) + overlays_differ = TRUE + + if(!overlays_differ) + continue + + diff_found = TRUE + diffs += "Diffs detected at [name]: First ([firstv]), Second ([secondv])" + + var/text = "Depth of: [iter]\n\t[diffs.Join("\n\t")]" + message_admins(text) + log_world(text) + return diff_found diff --git a/code/datums/holocall.dm b/code/datums/holocall.dm index aeb810cfdd..bb033c10c9 100644 --- a/code/datums/holocall.dm +++ b/code/datums/holocall.dm @@ -317,7 +317,6 @@ if(outfit_type) mannequin.equipOutfit(outfit_type,TRUE) mannequin.setDir(SOUTH) - COMPILE_OVERLAYS(mannequin) . = image(mannequin) unset_busy_human_dummy("HOLODISK_PRESET") diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm index 89c0e0210e..9e570608f2 100644 --- a/code/modules/admin/verbs/mapping.dm +++ b/code/modules/admin/verbs/mapping.dm @@ -307,7 +307,6 @@ GLOBAL_VAR_INIT(say_disabled, FALSE) qdel(I) randomize_human(D) JB.equip(D, TRUE, FALSE) - COMPILE_OVERLAYS(D) var/icon/I = icon(getFlatIcon(D), frame = 1) final.Insert(I, JB.title) qdel(D) diff --git a/code/modules/admin/verbs/one_click_antag.dm b/code/modules/admin/verbs/one_click_antag.dm index e7fc2c3f94..292c0564b0 100644 --- a/code/modules/admin/verbs/one_click_antag.dm +++ b/code/modules/admin/verbs/one_click_antag.dm @@ -314,7 +314,6 @@ equipAntagOnDummy(mannequin, ert) - COMPILE_OVERLAYS(mannequin) CHECK_TICK var/icon/preview_icon = icon('icons/effects/effects.dmi', "nothing") preview_icon.Scale(48+32, 16+32) diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm index 136a4c6e69..9f3556dbb6 100644 --- a/code/modules/antagonists/_common/antag_datum.dm +++ b/code/modules/antagonists/_common/antag_datum.dm @@ -391,7 +391,6 @@ GLOBAL_LIST_EMPTY(antagonists) /datum/antagonist/proc/render_preview_outfit(datum/outfit/outfit, mob/living/carbon/human/dummy) dummy = dummy || new /mob/living/carbon/human/dummy/consistent dummy.equipOutfit(outfit, visualsOnly = TRUE) - COMPILE_OVERLAYS(dummy) var/icon = getFlatIcon(dummy) // We don't want to qdel the dummy right away, since its items haven't initialized yet. diff --git a/code/modules/flufftext/Hallucination.dm b/code/modules/flufftext/Hallucination.dm index 479a80b0f6..d7d53ba691 100644 --- a/code/modules/flufftext/Hallucination.dm +++ b/code/modules/flufftext/Hallucination.dm @@ -1342,7 +1342,6 @@ GLOBAL_LIST_INIT(hallucination_list, list( var/datum/preferences/prefs = C.client.prefs var/mob/living/carbon/human/dummy/M = generate_or_wait_for_human_dummy(DUMMY_HUMAN_SLOT_HALLUCINATION) prefs.copy_to(M) - COMPILE_OVERLAYS(M) CHECK_TICK image = image(M,C) unset_busy_human_dummy(DUMMY_HUMAN_SLOT_HALLUCINATION) diff --git a/code/modules/mob/dead/new_player/preferences_setup.dm b/code/modules/mob/dead/new_player/preferences_setup.dm index a978e541bf..aaa5e04c0e 100644 --- a/code/modules/mob/dead/new_player/preferences_setup.dm +++ b/code/modules/mob/dead/new_player/preferences_setup.dm @@ -70,7 +70,6 @@ mannequin.regenerate_icons() - COMPILE_OVERLAYS(mannequin) parent.show_character_previews(new /mutable_appearance(mannequin)) unset_busy_human_dummy(DUMMY_HUMAN_SLOT_PREFERENCES) diff --git a/icons/Testing/greyscale_error.dmi b/icons/Testing/greyscale_error.dmi new file mode 100644 index 0000000000..6c781a70ad Binary files /dev/null and b/icons/Testing/greyscale_error.dmi differ diff --git a/tgstation.dme b/tgstation.dme index b2aa6b767e..06a91c87d0 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -90,6 +90,7 @@ #include "code\__DEFINES\movespeed_modification.dm" #include "code\__DEFINES\nanites.dm" #include "code\__DEFINES\networks.dm" +#include "code\__DEFINES\overlays.dm" #include "code\__DEFINES\pinpointers.dm" #include "code\__DEFINES\pipe_construction.dm" #include "code\__DEFINES\plumbing.dm"