diff --git a/code/__defines/items_clothing.dm b/code/__defines/items_clothing.dm index c33d92609c..8cb0d04bab 100644 --- a/code/__defines/items_clothing.dm +++ b/code/__defines/items_clothing.dm @@ -36,7 +36,7 @@ #define ACCESSORY_SLOT_ARMOR_M "Misc armor" #define ACCESSORY_SLOT_HELM_C "Helmet cover" -// Flags bitmasks. +// Flags bitmasks. - Used in /atom/var/flags #define NOBLUDGEON 0x1 // When an item has this it produces no "X has been hit by Y with Z" message with the default handler. #define CONDUCT 0x2 // Conducts electricity. (metal etc.) #define ON_BORDER 0x4 // Item has priority to check when entering or leaving. @@ -45,8 +45,9 @@ #define PHORONGUARD 0x20 // Does not get contaminated by phoron. #define NOREACT 0x40 // Reagents don't react inside this container. #define PROXMOVE 0x80 // Does this object require proximity checking in Enter()? +#define OVERLAY_QUEUED 0x100 // Atom queued to SSoverlay for COMPILE_OVERLAYS -//Flags for items (equipment) +//Flags for items (equipment) - Used in /obj/item/var/item_flags #define THICKMATERIAL 0x1 // Prevents syringes, parapens and hyposprays if equipped to slot_suit or slot_head. #define STOPPRESSUREDAMAGE 0x2 // Counts towards pressure protection. Note that like temperature protection, body_parts_covered is considered here as well. #define AIRTIGHT 0x4 // Functions with internals. @@ -54,13 +55,13 @@ #define BLOCK_GAS_SMOKE_EFFECT 0x10 // Blocks the effect that chemical clouds would have on a mob -- glasses, mask and helmets ONLY! (NOTE: flag shared with ONESIZEFITSALL) #define FLEXIBLEMATERIAL 0x20 // At the moment, masks with this flag will not prevent eating even if they are covering your face. -// Flags for pass_flags. +// Flags for pass_flags. - Used in /atom/var/pass_flags #define PASSTABLE 0x1 #define PASSGLASS 0x2 #define PASSGRILLE 0x4 #define PASSBLOB 0x8 -// Bitmasks for the flags_inv variable. These determine when a piece of clothing hides another, i.e. a helmet hiding glasses. +// Bitmasks for the /obj/item/var/flags_inv variable. These determine when a piece of clothing hides another, i.e. a helmet hiding glasses. // WARNING: The following flags apply only to the external suit! #define HIDEGLOVES 0x1 #define HIDESUITSTORAGE 0x2 diff --git a/code/__defines/math.dm b/code/__defines/math.dm index 79dc1b4d24..4245b5b564 100644 --- a/code/__defines/math.dm +++ b/code/__defines/math.dm @@ -9,6 +9,8 @@ #define REALTIMEOFDAY (world.timeofday + (MIDNIGHT_ROLLOVER * MIDNIGHT_ROLLOVER_CHECK)) #define MIDNIGHT_ROLLOVER_CHECK ( rollovercheck_last_timeofday != world.timeofday ? update_midnight_rollover() : midnight_rollovers ) +#define SHORT_REAL_LIMIT 16777216 // 2^24 - Maximum integer that can be exactly represented in a float (BYOND num var) + #define CEILING(x, y) ( -round(-(x) / (y)) * (y) ) // round() acts like floor(x, 1) by default but can't handle other values #define FLOOR(x, y) ( round((x) / (y)) * (y) ) diff --git a/code/__defines/stat_tracking.dm b/code/__defines/stat_tracking.dm new file mode 100644 index 0000000000..79337bda5c --- /dev/null +++ b/code/__defines/stat_tracking.dm @@ -0,0 +1,17 @@ +// +// Defines used for advanced performance profiling of subsystems. +// Currently used only by SSoverlays (2018-02-24 ~Leshana) +// +#define STAT_ENTRY_TIME 1 +#define STAT_ENTRY_COUNT 2 +#define STAT_ENTRY_LENGTH 2 + +#define STAT_START_STOPWATCH var/STAT_STOP_WATCH = TICK_USAGE +#define STAT_STOP_STOPWATCH var/STAT_TIME = TICK_USAGE_TO_MS(STAT_STOP_WATCH) +#define STAT_LOG_ENTRY(entrylist, entryname) \ + var/list/STAT_ENTRY = entrylist[entryname] || (entrylist[entryname] = new /list(STAT_ENTRY_LENGTH));\ + STAT_ENTRY[STAT_ENTRY_TIME] += STAT_TIME;\ + var/STAT_INCR_AMOUNT = min(1, 2**round((STAT_ENTRY[STAT_ENTRY_COUNT] || 0)/SHORT_REAL_LIMIT));\ + if (STAT_INCR_AMOUNT == 1 || prob(100/STAT_INCR_AMOUNT)) {\ + STAT_ENTRY[STAT_ENTRY_COUNT] += STAT_INCR_AMOUNT;\ + };\ diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm index ccbb9306ed..5b459f73ff 100644 --- a/code/__defines/subsystems.dm +++ b/code/__defines/subsystems.dm @@ -29,5 +29,35 @@ var/global/list/runlevel_flags = list(RUNLEVEL_LOBBY, RUNLEVEL_SETUP, RUNLEVEL_G #define INIT_ORDER_MACHINES 10 #define INIT_ORDER_SHUTTLES 3 #define INIT_ORDER_LIGHTING 0 -#define INIT_ORDER_AIR -1 +#define INIT_ORDER_AIR -1 +#define INIT_ORDER_OVERLAY -6 #define INIT_ORDER_XENOARCH -20 + + +// Subsystem fire priority, from lowest to highest priority +// If the subsystem isn't listed here it's either DEFAULT or PROCESS (if it's a processing subsystem child) + + #define FIRE_PRIORITY_OVERLAYS 500 + +// Macro defining the actual code applying our overlays lists to the BYOND overlays list. (I guess a macro for speed) +// TODO - I don't really like the location of this macro define. Consider it. ~Leshana +#define COMPILE_OVERLAYS(A)\ + if (TRUE) {\ + var/list/oo = A.our_overlays;\ + var/list/po = A.priority_overlays;\ + if(LAZYLEN(po)){\ + if(LAZYLEN(oo)){\ + A.overlays = oo + po;\ + }\ + else{\ + A.overlays = po;\ + }\ + }\ + else if(LAZYLEN(oo)){\ + A.overlays = oo;\ + }\ + else{\ + A.overlays.Cut();\ + }\ + A.flags &= ~OVERLAY_QUEUED;\ + } diff --git a/code/_helpers/sorts/comparators.dm b/code/_helpers/sorts/comparators.dm index 1b4b834692..7fd07ec711 100644 --- a/code/_helpers/sorts/comparators.dm +++ b/code/_helpers/sorts/comparators.dm @@ -36,3 +36,9 @@ . = b.head_position - a.head_position if (. == 0) //Already in head/nothead spot, sort by name . = sorttext(b.title, a.title) + +// Sorts entries in a performance stats list. +/proc/cmp_generic_stat_item_time(list/A, list/B) + . = B[STAT_ENTRY_TIME] - A[STAT_ENTRY_TIME] + if (!.) + . = B[STAT_ENTRY_COUNT] - A[STAT_ENTRY_COUNT] diff --git a/code/controllers/subsystems/overlays.dm b/code/controllers/subsystems/overlays.dm new file mode 100644 index 0000000000..6d7581e126 --- /dev/null +++ b/code/controllers/subsystems/overlays.dm @@ -0,0 +1,253 @@ +SUBSYSTEM_DEF(overlays) + name = "Overlay" + flags = SS_TICKER + wait = 1 + priority = FIRE_PRIORITY_OVERLAYS + init_order = INIT_ORDER_OVERLAY + + var/initialized = FALSE + var/list/queue // Queue of atoms needing overlay compiling (TODO-VERIFY!) + var/list/stats + var/list/overlay_icon_state_caches // Cache thing + var/list/overlay_icon_cache // Cache thing + +/datum/controller/subsystem/overlays/PreInit() + overlay_icon_state_caches = list() + overlay_icon_cache = list() + queue = list() + stats = list() + +/datum/controller/subsystem/overlays/Initialize() + initialized = TRUE + fire(mc_check = FALSE) + ..() + +/datum/controller/subsystem/overlays/stat_entry() + ..("Ov:[length(queue)]") + + +/datum/controller/subsystem/overlays/Shutdown() + //text2file(render_stats(stats), "[GLOB.log_directory]/overlay.log") + var/date_string = time2text(world.realtime, "YYYY/MM-Month/DD-Day") + text2file(render_stats(stats), "data/logs/[date_string]-overlay.log") + +/datum/controller/subsystem/overlays/Recover() + overlay_icon_state_caches = SSoverlays.overlay_icon_state_caches + overlay_icon_cache = SSoverlays.overlay_icon_cache + 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) + STAT_START_STOPWATCH + var/atom/A = thing + COMPILE_OVERLAYS(A) + 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 + +/proc/iconstate2appearance(icon, iconstate) + var/static/image/stringbro = new() + var/list/icon_states_cache = SSoverlays.overlay_icon_state_caches + var/list/cached_icon = icon_states_cache[icon] + if (cached_icon) + var/cached_appearance = cached_icon["[iconstate]"] + if (cached_appearance) + return cached_appearance + stringbro.icon = icon + stringbro.icon_state = iconstate + if (!cached_icon) //not using the macro to save an associated lookup + cached_icon = list() + icon_states_cache[icon] = cached_icon + var/cached_appearance = stringbro.appearance + cached_icon["[iconstate]"] = cached_appearance + return cached_appearance + +/proc/icon2appearance(icon) + var/static/image/iconbro = new() + var/list/icon_cache = SSoverlays.overlay_icon_cache + . = icon_cache[icon] + if (!.) + iconbro.icon = icon + . = iconbro.appearance + icon_cache[icon] = . + +/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) + if(!overlay) + continue + if (istext(overlay)) + new_overlays += iconstate2appearance(icon, overlay) + else if(isicon(overlay)) + new_overlays += icon2appearance(overlay) + else + if(isloc(overlay)) + var/atom/A = overlay + if (A.flags & OVERLAY_QUEUED) + 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 + +#define NOT_QUEUED_ALREADY (!(flags & OVERLAY_QUEUED)) +#define QUEUE_FOR_COMPILE flags |= OVERLAY_QUEUED; SSoverlays.queue += src; + +/** + * Cut all of atom's normal overlays. Usually leaves "priority" overlays untouched. + * + * @param priority If true, also will cut priority overlays. + */ +/atom/proc/cut_overlays(priority = FALSE) + var/list/cached_overlays = our_overlays + var/list/cached_priority = priority_overlays + + var/need_compile = FALSE + + if(LAZYLEN(cached_overlays)) //don't queue empty lists, don't cut priority overlays + cached_overlays.Cut() //clear regular overlays + need_compile = TRUE + + if(priority && LAZYLEN(cached_priority)) + cached_priority.Cut() + need_compile = TRUE + + if(NOT_QUEUED_ALREADY && need_compile) + QUEUE_FOR_COMPILE + +/** + * Removes specific overlay(s) from the atom. Usually does not remove them from "priority" overlays. + * + * @param overlays The overlays to removed, type can be anything that is allowed for add_overlay(). + * @param priority If true, also will remove them from the "priority" overlays. + */ +/atom/proc/cut_overlay(list/overlays, priority) + if(!overlays) + return + + overlays = build_appearance_list(overlays) + + var/list/cached_overlays = our_overlays //sanic + var/list/cached_priority = priority_overlays + var/init_o_len = LAZYLEN(cached_overlays) + var/init_p_len = LAZYLEN(cached_priority) //starter pokemon + + LAZYREMOVE(cached_overlays, overlays) + if(priority) + LAZYREMOVE(cached_priority, overlays) + + if(NOT_QUEUED_ALREADY && ((init_o_len != LAZYLEN(cached_overlays)) || (init_p_len != LAZYLEN(cached_priority)))) + QUEUE_FOR_COMPILE + +/** + * Adds specific overlay(s) to the atom. + * It is designed so any of the types allowed to be added to /atom/overlays can be added here too. More details below. + * + * @param overlays The overlay(s) to add. These may be + * - A string: In which case it is treated as an icon_state of the atom's icon. + * - An icon: It is treated as an icon. + * - An atom: Its own overlays are compiled and then it's appearance is added. (Meaning its current apperance is frozen). + * - An image: Image's apperance is added (i.e. subsequently editing the image will not edit the overlay) + * - A type path: Added to overlays as is. Does whatever it is BYOND does when you add paths to overlays. + * - Or a list containing any of the above. + * @param priority The overlays are added to the "priority" list istead of the normal one. + */ +/atom/proc/add_overlay(list/overlays, priority = FALSE) + if(!overlays) + return + + overlays = build_appearance_list(overlays) + + LAZYINITLIST(our_overlays) //always initialized after this point + LAZYINITLIST(priority_overlays) + + var/list/cached_overlays = our_overlays //sanic + var/list/cached_priority = priority_overlays + var/init_o_len = cached_overlays.len + var/init_p_len = cached_priority.len //starter pokemon + var/need_compile + + if(priority) + cached_priority += overlays //or in the image. Can we use [image] = image? + need_compile = init_p_len != cached_priority.len + else + cached_overlays += overlays + need_compile = init_o_len != cached_overlays.len + + if(NOT_QUEUED_ALREADY && need_compile) //have we caught more pokemon? + QUEUE_FOR_COMPILE + +/** + * Copy the overlays from another atom, either replacing all of ours or appending to our existing overlays. + * Note: This copies only the normal overlays, not the "priority" overlays. + * + * @param other The atom to copy overlays from. + * @param cut_old If true, all of our overlays will be *replaced* by the other's. If other is null, that means cutting all ours. + */ +/atom/proc/copy_overlays(atom/other, cut_old) //copys our_overlays from another atom + if(!other) + if(cut_old) + cut_overlays() + return + + var/list/cached_other = other.our_overlays + if(cached_other) + if(cut_old || !LAZYLEN(our_overlays)) + our_overlays = cached_other.Copy() + else + our_overlays |= cached_other + if(NOT_QUEUED_ALREADY) + QUEUE_FOR_COMPILE + else if(cut_old) + cut_overlays() + +#undef NOT_QUEUED_ALREADY +#undef QUEUE_FOR_COMPILE + +//TODO: Better solution for these? +/image/proc/add_overlay(x) + overlays += x + +/image/proc/cut_overlay(x) + overlays -= x + +/image/proc/cut_overlays(x) + overlays.Cut() + +/image/proc/copy_overlays(atom/other, cut_old) + if(!other) + if(cut_old) + cut_overlays() + return + + var/list/cached_other = other.our_overlays + if(cached_other) + if(cut_old || !overlays.len) + overlays = cached_other.Copy() + else + overlays |= cached_other + else if(cut_old) + cut_overlays() diff --git a/code/game/atoms.dm b/code/game/atoms.dm index d23f53c23a..dba0ec67ab 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -22,6 +22,10 @@ // replaced by OPENCONTAINER flags and atom/proc/is_open_container() ///Chemistry. + // Overlays + var/list/our_overlays //our local copy of (non-priority) overlays without byond magic. Use procs in SSoverlays to manipulate + var/list/priority_overlays //overlays that should remain on top and not normally removed when using cut_overlay functions, like c4. + //Detective Work, used for the duplicate data points kept in the scanners var/list/original_atom // Track if we are already had initialize() called to prevent double-initialization. diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 9fcac1a398..bbd10a20dc 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -195,6 +195,7 @@ var/list/admin_verbs_debug = list( /client/proc/cmd_debug_tog_aliens, /client/proc/cmd_display_del_log, /client/proc/cmd_display_init_log, + /client/proc/cmd_display_overlay_log, /client/proc/air_report, /client/proc/reload_admins, /client/proc/reload_eventMs, diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 291b1aee0c..a7b966447d 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -335,6 +335,28 @@ if(!check_rights(R_DEBUG)) return src << browse(replacetext(SSatoms.InitLog(), "\n", "
"), "window=initlog") +/client/proc/cmd_display_overlay_log() + set category = "Debug" + set name = "Display overlay Log" + set desc = "Display SSoverlays log of everything that's passed through it." + + if(!check_rights(R_DEBUG)) return + render_stats(SSoverlays.stats, src) + +// Render stats list for round-end statistics. +/proc/render_stats(list/stats, user, sort = /proc/cmp_generic_stat_item_time) + sortTim(stats, sort, TRUE) + + var/list/lines = list() + for (var/entry in stats) + var/list/data = stats[entry] + lines += "[entry] => [num2text(data[STAT_ENTRY_TIME], 10)]ms ([data[STAT_ENTRY_COUNT]]) (avg:[num2text(data[STAT_ENTRY_TIME]/(data[STAT_ENTRY_COUNT] || 1), 99)])" + + if (user) + user << browse("
  1. [lines.Join("
  2. ")]
", "window=[url_encode("stats:\ref[stats]")]") + else + . = lines.Join("\n") + /client/proc/cmd_admin_grantfullaccess(var/mob/M in mob_list) set category = "Admin" set name = "Grant Full Access" diff --git a/vorestation.dme b/vorestation.dme index fbba9124a5..95ed5cd4cc 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -56,6 +56,7 @@ #include "code\__defines\roguemining_vr.dm" #include "code\__defines\species_languages.dm" #include "code\__defines\species_languages_vr.dm" +#include "code\__defines\stat_tracking.dm" #include "code\__defines\subsystems.dm" #include "code\__defines\targeting.dm" #include "code\__defines\turfs.dm" @@ -202,6 +203,7 @@ #include "code\controllers\subsystems\mapping_vr.dm" #include "code\controllers\subsystems\mobs.dm" #include "code\controllers\subsystems\orbits.dm" +#include "code\controllers\subsystems\overlays.dm" #include "code\controllers\subsystems\persist_vr.dm" #include "code\controllers\subsystems\shuttles.dm" #include "code\controllers\subsystems\transcore_vr.dm"