[MIRROR] GAGS fix + debug improvements I needed to find it (#8219)

* GAGS fix + debug improvements I needed to find it

* Feex

Co-authored-by: Emmett Gaines <ninjanomnom@protonmail.com>
Co-authored-by: GoldenAlpharex <jerego1234@hotmail.com>
This commit is contained in:
SkyratBot
2021-09-17 21:02:48 +01:00
committed by GitHub
co-authored by Emmett Gaines GoldenAlpharex
parent 6caab7f61c
commit 95ac28a6b0
7 changed files with 214 additions and 56 deletions
+1
View File
@@ -1477,3 +1477,4 @@
///Called when the ticker sets up the game for start
#define COMSIG_TICKER_ENTER_SETTING_UP "comsig_ticker_enter_setting_up"
#define COMSIG_GREYSCALE_CONFIG_REFRESHED "greyscale_config_refreshed"
@@ -1,12 +1,13 @@
SUBSYSTEM_DEF(greyscale)
PROCESSING_SUBSYSTEM_DEF(greyscale)
name = "Greyscale"
flags = SS_NO_FIRE
flags = SS_BACKGROUND
init_order = INIT_ORDER_GREYSCALE
wait = 3 SECONDS
var/list/datum/greyscale_config/configurations = list()
var/list/datum/greyscale_layer/layer_types = list()
/datum/controller/subsystem/greyscale/Initialize(start_timeofday)
/datum/controller/subsystem/processing/greyscale/Initialize(start_timeofday)
for(var/datum/greyscale_layer/fake_type as anything in subtypesof(/datum/greyscale_layer))
layer_types[initial(fake_type.layer_type)] = fake_type
@@ -28,11 +29,11 @@ SUBSYSTEM_DEF(greyscale)
return ..()
/datum/controller/subsystem/greyscale/proc/RefreshConfigsFromFile()
/datum/controller/subsystem/processing/greyscale/proc/RefreshConfigsFromFile()
for(var/i in configurations)
configurations[i].Refresh(TRUE)
/datum/controller/subsystem/greyscale/proc/GetColoredIconByType(type, list/colors)
/datum/controller/subsystem/processing/greyscale/proc/GetColoredIconByType(type, list/colors)
if(!ispath(type, /datum/greyscale_config))
CRASH("An invalid greyscale configuration was given to `GetColoredIconByType()`: [type]")
type = "[type]"
@@ -42,7 +43,7 @@ SUBSYSTEM_DEF(greyscale)
CRASH("Invalid colors were given to `GetColoredIconByType()`: [colors]")
return configurations[type].Generate(colors)
/datum/controller/subsystem/greyscale/proc/ParseColorString(color_string)
/datum/controller/subsystem/processing/greyscale/proc/ParseColorString(color_string)
. = list()
var/list/split_colors = splittext(color_string, "#")
for(var/color in 2 to length(split_colors))
+85 -9
View File
@@ -27,12 +27,24 @@
/// String path to the json file, used for reloading
var/string_json_config
/// The md5 file hash for the json configuration. Used to check if the file has changed
var/json_config_hash
/// String path to the icon file, used for reloading
var/string_icon_file
/// The md5 file hash for the icon file. Used to check if the file has changed
var/icon_file_hash
/// A list of icon states and their layers
var/list/icon_states
/// A list of all layers irrespective of nesting
var/list/flat_all_layers
/// A list of types to update in the world whenever a config changes
var/list/live_edit_types
/// How many colors are expected to be given when building the sprite
var/expected_colors = 0
@@ -51,11 +63,63 @@
if(!name)
stack_trace("Greyscale config object [DebugName()] is missing a name, make sure `name` has been assigned a value.")
/datum/greyscale_config/Destroy(force, ...)
if(!force)
return QDEL_HINT_LETMELIVE
return ..()
/datum/greyscale_config/process(delta_time)
if(!Refresh(loadFromDisk=TRUE))
return
if(!live_edit_types)
return
for(var/atom/thing in world)
if(live_edit_types[thing.type])
thing.update_greyscale()
/datum/greyscale_config/proc/EnableAutoRefresh(live_type)
message_admins("Config auto refresh has been enabled for '[live_type]' with configuration [DebugName()]. Expect heavy lag.")
if(live_type)
if(!live_edit_types)
live_edit_types = list()
live_edit_types += typecacheof(live_type)
START_PROCESSING(SSgreyscale, src)
/datum/greyscale_config/proc/DisableAutoRefresh(live_type, remove_all=FALSE)
if(!remove_all && !(live_type in live_edit_types))
return
message_admins("Config auto refresh has been disabled for '[live_type]' with configuration [DebugName()]")
if(remove_all)
live_edit_types = null
else if(live_type && live_edit_types)
live_edit_types -= typecacheof(live_type)
if(!length(live_edit_types))
live_edit_types = null
STOP_PROCESSING(SSgreyscale, src)
/// Call this proc to handle all the data extraction from the json configuration. Can be forced to load values from disk instead of memory.
/datum/greyscale_config/proc/Refresh(loadFromDisk=FALSE)
if(loadFromDisk)
var/changed = FALSE
json_config = file(string_json_config)
var/json_hash = md5asfile(json_config)
if(json_config_hash != json_hash)
json_config_hash = json_hash
changed = TRUE
icon_file = file(string_icon_file)
var/icon_hash = md5asfile(icon_file)
if(icon_file_hash != icon_hash)
icon_file_hash = icon_hash
changed = TRUE
for(var/datum/greyscale_layer/layer as anything in flat_all_layers)
if(layer.DiskRefresh())
changed = TRUE
if(!changed)
return FALSE
var/list/raw = json_decode(file2text(json_config))
ReadIconStateConfiguration(raw)
@@ -67,6 +131,10 @@
ReadMetadata()
SEND_SIGNAL(src, COMSIG_GREYSCALE_CONFIG_REFRESHED)
return TRUE
/// Called after every config has refreshed, this proc handles data verification that depends on multiple entwined configurations.
/datum/greyscale_config/proc/CrossVerify()
for(var/icon_state in icon_states)
@@ -139,9 +207,11 @@
if(length(state_layers) > MAX_SANE_LAYERS)
stack_trace("[DebugName()] icon state '[state]' has [length(state_layers)] layers which is larger than the max of [MAX_SANE_LAYERS].")
flat_all_layers = list()
var/list/color_groups = list()
var/largest_id = 0
for(var/datum/greyscale_layer/layer as anything in all_layers)
flat_all_layers += layer
for(var/id in layer.color_ids)
if(!isnum(id))
continue
@@ -155,6 +225,11 @@
expected_colors = length(color_groups)
/// For saving a dmi to disk, useful for debug mainly
/datum/greyscale_config/proc/SaveOutput(color_string)
var/icon/icon_output = GenerateBundle(color_string)
fcopy(icon_output, "tmp/gags_debug_output.dmi")
/// Actually create the icon and color it in, handles caching
/datum/greyscale_config/proc/Generate(color_string)
var/key = color_string
@@ -163,13 +238,7 @@
return icon(new_icon)
var/icon/icon_bundle = GenerateBundle(color_string)
// This block is done like this because generated icons are unable to be scaled before getting added to the rsc
icon_bundle = fcopy_rsc(icon_bundle)
icon_bundle = icon(icon_bundle)
icon_bundle.Scale(width, height)
icon_bundle = fcopy_rsc(icon_bundle)
icon_cache[key] = icon_bundle
var/icon/output = icon(icon_bundle)
return output
@@ -183,13 +252,19 @@
var/list/generated_icons = list()
for(var/icon_state in icon_states)
var/icon/generated_icon = GenerateLayerGroup(colors, icon_states[icon_state], render_steps)
var/list/icon_state_steps
if(render_steps)
icon_state_steps = render_steps[icon_state] = list()
var/icon/generated_icon = GenerateLayerGroup(colors, icon_states[icon_state], icon_state_steps)
// We read a pixel to force the icon to be fully generated before we let it loose into the world
// I hate this
generated_icon.GetPixel(1, 1)
generated_icons[icon_state] = generated_icon
var/icon/icon_bundle = icon('icons/testing/greyscale_error.dmi')
var/icon/icon_bundle = generated_icons[""] || icon('icons/testing/greyscale_error.dmi')
icon_bundle.Scale(width, height)
generated_icons -= ""
for(var/icon_state in generated_icons)
icon_bundle.Insert(generated_icons[icon_state], icon_state)
@@ -214,8 +289,9 @@
// These are so we can see the result of every step of the process in the preview ui
if(render_steps)
var/list/icon_data = list()
render_steps[icon(layer_icon)] = icon_data
render_steps += list(icon_data)
icon_data["config_name"] = name
icon_data["step"] = icon(layer_icon)
icon_data["result"] = icon(new_icon)
return new_icon
+14 -2
View File
@@ -20,6 +20,10 @@
/datum/greyscale_layer/proc/Initialize(icon_file)
return
/// Override this if you need to do something during a full config refresh from disk, return TRUE if something was changed
/datum/greyscale_layer/proc/DiskRefresh()
return FALSE
/// Handles the processing of the json data and conversion to correct value types.
/// Will error on incorrect, missing, or unexpected values.
/datum/greyscale_layer/proc/ReadJsonData(list/json_data)
@@ -117,13 +121,21 @@
optional_values[NAMEOF(src, icon_state)] = /datum/json_reader/text
required_values[NAMEOF(src, reference_type)] = /datum/json_reader/greyscale_config
/datum/greyscale_layer/reference/DiskRefresh()
. = ..()
return reference_type.Refresh(loadFromDisk=TRUE)
/datum/greyscale_layer/reference/CrossVerify()
. = ..()
if(!reference_type.icon_states[icon_state])
CRASH("[src] expects icon_state '[icon_state]' but referenced configuration '[reference_type]' does not have it.")
/datum/greyscale_layer/reference/InternalGenerate(list/colors, list/render_steps)
var/icon/generated_icon
if(render_steps)
return reference_type.GenerateBundle(colors, render_steps)
var/list/reference_data = list()
generated_icon = reference_type.GenerateBundle(colors, reference_data)
render_steps += reference_data[icon_state]
else
return reference_type.Generate(colors.Join())
generated_icon = reference_type.Generate(colors.Join())
return icon(generated_icon, icon_state)
+63 -23
View File
@@ -17,6 +17,9 @@
/// A list of colors currently selected
var/list/split_colors
/// The type that the configuration file was assigned at
var/config_owner_type
/// Collection of data for tgui to use in displaying everything
var/list/sprite_data
/// The sprite dir currently being shown
@@ -38,15 +41,18 @@
src.apply_callback = apply_callback || CALLBACK(src, .proc/DefaultApply)
icon_state = starting_icon_state
SetupConfigOwner()
var/current_config = "[starting_config]" || "[target?.greyscale_config]"
config = SSgreyscale.configurations[current_config]
var/datum/greyscale_config/new_config = SSgreyscale.configurations[current_config]
if(!(current_config in allowed_configs))
config = SSgreyscale.configurations["[allowed_configs[pick(allowed_configs)]]"]
new_config = SSgreyscale.configurations["[allowed_configs[pick(allowed_configs)]]"]
change_config(new_config)
var/list/config_choices = list()
for(var/config_string in allowed_configs)
var/datum/greyscale_config/config = text2path("[config_string]")
config_choices[initial(config.name)] = config_string
var/datum/greyscale_config/allowed_config = text2path("[config_string]")
config_choices[initial(allowed_config.name)] = config_string
src.allowed_configs = config_choices
ReadColorsFromString(starting_colors || target?.greyscale_colors)
@@ -88,6 +94,7 @@
data["generate_full_preview"] = generate_full_preview
data["unlocked"] = unlocked
data["refreshing"] = refreshing
data["monitoring_files"] = !!(config.datum_flags & DF_ISPROCESSING)
data["sprites_dir"] = dir2text(sprite_dir)
data["icon_state"] = icon_state
data["sprites"] = sprite_data
@@ -108,7 +115,7 @@
new_config = allowed_configs[new_config]
new_config = SSgreyscale.configurations[new_config] || new_config
if(!isnull(new_config) && config != new_config)
config = new_config
change_config(new_config)
queue_refresh()
if("load_config_from_string")
@@ -116,7 +123,7 @@
return
var/datum/greyscale_config/new_config = SSgreyscale.configurations[params["config_string"]]
if(!isnull(new_config) && config != new_config)
config = new_config
change_config(new_config)
queue_refresh()
if("toggle_full_preview")
@@ -171,7 +178,7 @@
apply_callback.Invoke(src)
if("refresh_file")
if(!unlocked)
if(!unlocked || !check_rights(R_DEBUG))
return
if(length(GLOB.player_list) > 1)
var/check = alert(
@@ -184,13 +191,36 @@ This is highly likely to cause a lag spike for a few seconds."},
)
if(check != "Yes")
return
SSgreyscale.RefreshConfigsFromFile()
queue_refresh()
config.Refresh(loadFromDisk=TRUE)
if("save_dmi")
if(!unlocked)
return
config.SaveOutput(split_colors.Copy(1, config.expected_colors+1))
if("change_dir")
sprite_dir = text2dir(params["new_sprite_dir"])
queue_refresh()
if("toggle_mass_refresh")
if(!unlocked || !check_rights(R_DEBUG))
return
if(config.datum_flags & DF_ISPROCESSING)
config.DisableAutoRefresh(remove_all=TRUE)
return
if(length(GLOB.player_list) > 1)
var/check = alert(
user,
{"Other players are connected to the server, are you sure you want to automatically refresh all greyscale configurations?\n
This is highly likely to cause massive amounts of lag as every object in the game will be iterated over every few seconds."},
"Auto-Refresh Greyscale Configurations",
"Yes",
"Cancel"
)
if(check != "Yes")
return
config.EnableAutoRefresh(config_owner_type)
/datum/greyscale_modify_menu/proc/ReadColorsFromString(colorString)
var/list/raw_colors = splittext(colorString, "#")
split_colors = list()
@@ -203,7 +233,14 @@ This is highly likely to cause a lag spike for a few seconds."},
new_color += num2hex(rand(0, 255), 2)
split_colors[color_index] = new_color
/datum/greyscale_modify_menu/proc/change_config(datum/greyscale_config/new_config)
if(config)
UnregisterSignal(config, COMSIG_GREYSCALE_CONFIG_REFRESHED)
config = new_config
RegisterSignal(config, COMSIG_GREYSCALE_CONFIG_REFRESHED, .proc/queue_refresh)
/datum/greyscale_modify_menu/proc/queue_refresh()
SIGNAL_HANDLER
refreshing = TRUE
addtimer(CALLBACK(src, .proc/refresh_preview), 1 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE)
@@ -235,25 +272,17 @@ This is highly likely to cause a lag spike for a few seconds."},
finished = image(data["icon"], icon_state=icon_state)
var/list/steps = list()
sprite_data["steps"] = steps
for(var/step in data["steps"])
var/list/icon_state_data = data["steps"][icon_state]
for(var/list/step as anything in icon_state_data)
CHECK_TICK
var/list/step_data = data["steps"][step]
var/image/layer = image(step)
var/image/result = step_data["result"]
// SKYRAT EDIT BEGIN - Bringing back the GAGS coloring menu - ORIGINAL:
/*
var/image/layer = image(step["step"])
var/image/result = image(step["result"])
// SKYRAT EDIT BEGIN - Bringing back the GAGS coloring menu
steps += list(
list(
"layer"=icon2html(layer, user, dir=sprite_dir, sourceonly=TRUE, override_skyrat = TRUE),
"result"=icon2html(result, user, dir=sprite_dir, sourceonly=TRUE, override_skyrat = TRUE),
"config_name"=step_data["config_name"]
)
)*/
steps += list(
list(
"layer"=icon2html(layer, user, dir=sprite_dir, sourceonly=TRUE, override_skyrat = TRUE),
"result"=icon2html(result, user, dir=sprite_dir, sourceonly=TRUE, override_skyrat = TRUE),
"config_name"=step_data["config_name"]
"config_name"=step["config_name"]
)
)
// SKYRAT EDIT END
@@ -268,3 +297,14 @@ This is highly likely to cause a lag spike for a few seconds."},
/datum/greyscale_modify_menu/proc/DefaultApply()
target.set_greyscale(split_colors, config.type)
/// Gets the top level type that first uses the configuration in this type path
/datum/greyscale_modify_menu/proc/SetupConfigOwner()
var/atom/current = target.type
var/atom/parent = target.parent_type
if(!initial(current.greyscale_config))
return
while(initial(current.greyscale_config) == initial(parent.greyscale_config))
current = parent
parent = type2parent(current)
config_owner_type = current
+1 -1
View File
@@ -374,7 +374,6 @@
#include "code\controllers\subsystem\explosions.dm"
#include "code\controllers\subsystem\fire_burning.dm"
#include "code\controllers\subsystem\garbage.dm"
#include "code\controllers\subsystem\greyscale.dm"
#include "code\controllers\subsystem\icon_smooth.dm"
#include "code\controllers\subsystem\id_access.dm"
#include "code\controllers\subsystem\idlenpcpool.dm"
@@ -432,6 +431,7 @@
#include "code\controllers\subsystem\processing\fastprocess.dm"
#include "code\controllers\subsystem\processing\fields.dm"
#include "code\controllers\subsystem\processing\fluids.dm"
#include "code\controllers\subsystem\processing\greyscale.dm"
#include "code\controllers\subsystem\processing\instruments.dm"
#include "code\controllers\subsystem\processing\networks.dm"
#include "code\controllers\subsystem\processing\obj.dm"
@@ -26,6 +26,7 @@ type GreyscaleMenuData = {
sprites: SpriteData;
generate_full_preview: boolean;
unlocked: boolean;
monitoring_files: boolean;
sprites_dir: string;
icon_state: string;
refreshing: boolean;
@@ -292,21 +293,48 @@ export const GreyscaleModifyMenu = (props, context) => {
<ConfigDisplay />
<ColorDisplay />
<IconStatesDisplay />
{
!!data.unlocked
&& <Button content="Refresh Icon File" onClick={() => act("refresh_file")} />
}
<Button
content="Apply"
onClick={() => act("apply")}
mx={1}
/>
<Button.Checkbox
content="Full Preview"
disabled={!data.generate_full_preview && !data.unlocked}
checked={data.generate_full_preview}
onClick={() => act("toggle_full_preview")}
/>
<Flex direction="column">
{
!!data.unlocked
&& (
<Flex.Item justify="flex-start">
<Button
content={<Icon name="file-image-o" spin={data.monitoring_files} />}
tooltip="Continuously checks files for changes and reloads when necessary. WARNING: Very expensive"
selected={data.monitoring_files}
onClick={() => act("toggle_mass_refresh")}
width={1.9}
mr={-0.2}
/>
<Button
content="Refresh Icon File"
tooltip="Loads the json configuration and icon file fresh from disk. This is useful to avoid restarting the server to see changes. WARNING: Expensive"
onClick={() => act("refresh_file")}
/>
<Button
content="Save Icon File"
tooltip="Saves the icon to a temp file in tmp/. This is useful if you want to use a generated icon elsewhere or just view a more accurate representation"
onClick={() => act("save_dmi")}
/>
</Flex.Item>
)
}
<Flex.Item>
<Button
content="Apply"
tooltip="Applies changes made to the object this menu was created from."
color="red"
onClick={() => act("apply")}
/>
<Button.Checkbox
content="Full Preview"
tooltip="Generates and displays the full sprite generation process instead of just the final output."
disabled={!data.generate_full_preview && !data.unlocked}
checked={data.generate_full_preview}
onClick={() => act("toggle_full_preview")}
/>
</Flex.Item>
</Flex>
<PreviewDisplay />
{
!!data.refreshing && <LoadingAnimation />