diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index cae09f6ee50..9c4cc3eafa8 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -197,6 +197,7 @@ } while(FALSE) #define SORT_FIRST_INDEX(list) (list[1]) +#define SORT_PRIORITY_INDEX(list) (list["priority"]) #define SORT_COMPARE_DIRECTLY(thing) (thing) #define SORT_VAR_NO_TYPE(varname) var/varname /**** diff --git a/code/_onclick/hud/rendering/plane_master_controller.dm b/code/_onclick/hud/rendering/plane_master_controller.dm index f4b71611716..2d2d5c54bd6 100644 --- a/code/_onclick/hud/rendering/plane_master_controller.dm +++ b/code/_onclick/hud/rendering/plane_master_controller.dm @@ -31,21 +31,21 @@ INITIALIZE_IMMEDIATE(/atom/movable/plane_master_controller) return returned_planes ///Full override so we can just use filterrific -/atom/movable/plane_master_controller/add_filter(name, priority, list/params) +/atom/movable/plane_master_controller/add_filter(name, priority, list/params, update = TRUE) . = ..() for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes()) - pm_iterator.add_filter(name, priority, params) + pm_iterator.add_filter(name, priority, params, update) ///Full override so we can just use filterrific -/atom/movable/plane_master_controller/remove_filter(name_or_names) +/atom/movable/plane_master_controller/remove_filter(name_or_names, update = TRUE) . = ..() for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes()) - pm_iterator.remove_filter(name_or_names) + pm_iterator.remove_filter(name_or_names, update) -/atom/movable/plane_master_controller/update_filters() +/atom/movable/plane_master_controller/update_filters(start_index = null) . = ..() for(var/atom/movable/screen/plane_master/pm_iterator as anything in get_planes()) - pm_iterator.update_filters() + pm_iterator.update_filters(start_index) ///Gets all filters for this controllers plane masters /atom/movable/plane_master_controller/proc/get_filters(name) diff --git a/code/datums/datum.dm b/code/datums/datum.dm index dcf1772f54f..fe69f6d6cc3 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -55,9 +55,12 @@ */ var/list/cooldowns - /// List for handling persistent filters. var/list/filter_data + /// An accursed beast of a list that contains our filters. Why? Because var/list/filters on atoms/images isn't actually a list + /// but a snowflaked skinwalker pretending to be one, which doesn't support half the list procs/operations and the other half behaves weirdly + /// so we cut down on filter creation and appearance update costs by editing *this* list, and then assigning ours to it + var/list/filter_cache #ifdef REFERENCE_TRACKING /// When was this datum last touched by a reftracker? @@ -321,38 +324,67 @@ * * name - Filter name * * priority - Priority used when sorting the filter. * * params - Parameters of the filter. + * * update - If we should update our actual filters list, or wait until something updates it later */ -/datum/proc/add_filter(name, priority, list/params) - LAZYINITLIST(filter_data) - var/list/copied_parameters = params.Copy() - copied_parameters["priority"] = priority - filter_data[name] = copied_parameters - update_filters() - -///A version of add_filter that takes a list of filters to add rather than being individual, to limit calls to update_filters(). -/datum/proc/add_filters(list/list/filters) - LAZYINITLIST(filter_data) - for(var/list/individual_filter as anything in filters) - var/list/params = individual_filter["params"] - var/list/copied_parameters = params.Copy() - copied_parameters["priority"] = individual_filter["priority"] - filter_data[individual_filter["name"]] = copied_parameters - update_filters() - -/// Reapplies all the filters. -/datum/proc/update_filters() +/datum/proc/add_filter(name, priority, list/params, update = TRUE) ASSERT(isatom(src) || isimage(src)) var/atom/atom_cast = src // filters only work with images or atoms. - atom_cast.filters = null - sortTim(filter_data, GLOBAL_PROC_REF(cmp_filter_data_priority), TRUE) - for(var/filter_raw in filter_data) - var/list/data = filter_data[filter_raw] - var/list/arguments = data.Copy() + LAZYINITLIST(filter_data) + LAZYINITLIST(filter_cache) + var/list/copied_parameters = params.Copy() + copied_parameters["name"] = name + copied_parameters["priority"] = priority + for (var/list/filter_info as anything in filter_data) + if (filter_info["name"] == name) + filter_data -= filter_info + filter_cache -= name + break + + BINARY_INSERT_DEFINE(list(copied_parameters), filter_data, SORT_VAR_NO_TYPE, copied_parameters, SORT_PRIORITY_INDEX, COMPARE_KEY) + + for (var/index in 1 to length(filter_data)) + var/list/filter_info = filter_data[index] + if (filter_info["name"] != name) + continue + var/list/arguments = filter_info.Copy() arguments -= "priority" - atom_cast.filters += filter(arglist(arguments)) + filter_cache.Insert(index, filter(arglist(arguments))) + break + + if (update) + atom_cast.filters = filter_cache + +/// A version of add_filter that takes a list of filters to add rather than being individual, to limit appearance updates +/datum/proc/add_filters(list/list/filters, update = TRUE) + ASSERT(isatom(src) || isimage(src)) + var/atom/atom_cast = src // filters only work with images or atoms. + for (var/list/individual_filter as anything in filters) + add_filter(individual_filter["name"], individual_filter["priority"], individual_filter["params"], update = FALSE) + if (update) + atom_cast.filters = filter_cache + +/// Reapplies all the filters. If start_index is passed, only a portion of all filters are reapplied starting from said index +/datum/proc/update_filters(start_index = null) + ASSERT(isatom(src) || isimage(src)) + var/atom/atom_cast = src // filters only work with images or atoms. + if (start_index) + filter_cache.Cut(start_index) + else + atom_cast.filters = null + filter_cache.Cut() + + for (var/index in start_index || 1 to length(filter_data)) + var/list/filter_info = filter_data[index] + var/list/arguments = filter_info.Copy() + arguments -= "priority" + if (start_index) // See https://www.byond.com/forum/post/2980598 as to why we cannot just override the existing filter + atom_cast.filters -= filter_info["name"] // We're trapped in the belly of this horrible machine + filter_cache += filter(arglist(arguments)) // And the machine is bleeding to death + + atom_cast.filters = filter_cache UNSETEMPTY(filter_data) -/obj/item/update_filters() +/obj/item/update_filters(start_index = null) . = ..() update_item_action_buttons() @@ -362,17 +394,29 @@ * * name - Filter name * * new_params - New parameters of the filter * * overwrite - TRUE means we replace the parameter list completely. FALSE means we only replace the things on new_params. + * * update - If we should apply our filter cache to our actual filters */ -/datum/proc/modify_filter(name, list/new_params, overwrite = FALSE) - var/filter = get_filter(name) - if(!filter) +/datum/proc/modify_filter(name, list/new_params, overwrite = FALSE, update = TRUE) + ASSERT(isatom(src) || isimage(src)) + var/atom/atom_cast = src // filters only work with images or atoms. + for (var/index in 1 to length(filter_data)) + var/list/filter_info = filter_data[index] + if (filter_info["name"] != name) + continue + + if (overwrite) + filter_data[index] = new_params + else + for (var/thing in new_params) + filter_info[thing] = new_params[thing] + + var/list/arguments = filter_info.Copy() + arguments -= "priority" + filter_cache[index] = filter(arglist(arguments)) + + if (update) + atom_cast.filters = filter_cache return - if(overwrite) - filter_data[name] = new_params - else - for(var/thing in new_params) - filter_data[name][thing] = new_params[thing] - update_filters() /** Update a filter's parameter and animate this change. If the filter doesn't exist we won't do anything. * Basically a [datum/proc/modify_filter] call but with animations. Unmodified filter parameters are kept. @@ -386,7 +430,7 @@ */ /datum/proc/transition_filter(name, list/new_params, time, easing, loop) var/filter = get_filter(name) - if(!filter) + if (!filter) return // This can get injected by the filter procs, we want to support them so bye byeeeee new_params -= "type" @@ -399,75 +443,75 @@ * * duration - the time it takes to animate this step * * easing - the type of easing this step has */ -/proc/FilterChainStep(params, duration, easing) +/proc/filter_chain_step(params, duration, easing, flags) params -= "type" - return list("params"= params, "duration"=duration, "easing"=easing) + return list("params" = params, "duration" = duration, "easing" = easing, "flags" = flags) /** Similar to transition_filter(), except it creates an animation chain that moves between a list of states. * Arguments: * * name - Filter name * * num_loops - Amount of times the chain loops. INDEFINITE = Infinite - * * ... - a list of each link in the animation chain. Use FilterChainStep(params, duration, easing) for each link + * * ... - a list of each link in the animation chain. Use filter_chain_step(params, duration, easing) for each link * Example use: * * add_filter("blue_pulse", 1, color_matrix_filter(COLOR_WHITE)) * * transition_filter_chain(src, "blue_pulse", INDEFINITE,\ - * * FilterChainStep(color_matrix_filter(COLOR_BLUE), 10 SECONDS, CUBIC_EASING),\ - * * FilterChainStep(color_matrix_filter(COLOR_WHITE), 10 SECONDS, CUBIC_EASING)) + * * filter_chain_step(color_matrix_filter(COLOR_BLUE), 10 SECONDS, CUBIC_EASING),\ + * * filter_chain_step(color_matrix_filter(COLOR_WHITE), 10 SECONDS, CUBIC_EASING)) * The above code would edit a color_matrix_filter() to slowly turn blue over 10 seconds before returning back to white 10 seconds after, repeating this chain forever. */ /datum/proc/transition_filter_chain(name, num_loops, ...) var/list/transition_steps = args.Copy(3) var/filter = get_filter(name) - if(!filter) + if (!filter) return var/list/first_step = transition_steps[1] - animate(filter, first_step["params"], time = first_step["duration"], easing = first_step["easing"], loop = num_loops) - for(var/transition_step in 2 to length(transition_steps)) + animate(filter, first_step["params"], time = first_step["duration"], easing = first_step["easing"], flags = first_step["flags"], loop = num_loops) + for (var/transition_step in 2 to length(transition_steps)) var/list/this_step = transition_steps[transition_step] - animate(this_step["params"], time = this_step["duration"], easing = this_step["easing"]) - + animate(this_step["params"], time = this_step["duration"], easing = this_step["easing"], flags = this_step["flags"]) /// Updates the priority of the passed filter key /datum/proc/change_filter_priority(name, new_priority) - if(!filter_data || !filter_data[name]) - return + for (var/list/filter_info as anything in filter_data) + if (filter_info["name"] != name) + continue - filter_data[name]["priority"] = new_priority - update_filters() + remove_filter(name, update = FALSE) + add_filter(name, new_priority, filter_info) + return /// Returns the filter associated with the passed key /datum/proc/get_filter(name) ASSERT(isatom(src) || isimage(src)) - if(filter_data && filter_data[name]) - var/atom/atom_cast = src // filters only work with images or atoms. - return atom_cast.filters[filter_data.Find(name)] - -/// Returns the indice in filters of the given filter name. -/// If it is not found, returns null. -/datum/proc/get_filter_index(name) - return filter_data?.Find(name) + var/atom/atom_cast = src // filters only work with images or atoms. + return atom_cast.filters[name] /// Removes the passed filter, or multiple filters, if supplied with a list. -/datum/proc/remove_filter(name_or_names) +/datum/proc/remove_filter(name_or_names, update = TRUE) + ASSERT(isatom(src) || isimage(src)) if(!filter_data) return - + var/atom/atom_cast = src // filters only work with images or atoms. var/list/names = islist(name_or_names) ? name_or_names : list(name_or_names) - . = FALSE - for(var/name in names) - if(filter_data[name]) - filter_data -= name - . = TRUE - - if(.) - update_filters() + var/list/new_data = list() + var/list/new_cache = list() + for (var/index in 1 to length(filter_data)) + var/list/filter_info = filter_data[index] + if (!(filter_info["name"] in names)) + new_data += list(filter_info) + new_cache += filter_cache[index] + filter_data = new_data + filter_cache = new_cache + if (update) + atom_cast.filters = filter_cache return . /datum/proc/clear_filters() ASSERT(isatom(src) || isimage(src)) var/atom/atom_cast = src // filters only work with images or atoms. filter_data = null + filter_cache = null atom_cast.filters = null /// Calls qdel on itself, because signals dont allow callbacks diff --git a/code/modules/admin/view_variables/filterrific.dm b/code/modules/admin/view_variables/filterrific.dm index 3034c092dc4..0ddd07aec4f 100644 --- a/code/modules/admin/view_variables/filterrific.dm +++ b/code/modules/admin/view_variables/filterrific.dm @@ -74,8 +74,7 @@ if("modify_icon_value") var/icon/new_icon = input("Pick icon:", "Icon") as null|icon if(new_icon) - target.filter_data[params["name"]]["icon"] = new_icon - target.update_filters() + target.modify_filter(params["name"], list("icon" = new_icon)) . = TRUE if("mass_apply") if(!check_rights_for(usr.client, R_FUN)) diff --git a/code/modules/antagonists/cult/cult_items.dm b/code/modules/antagonists/cult/cult_items.dm index dacd9836fa7..ca4bb86cc97 100644 --- a/code/modules/antagonists/cult/cult_items.dm +++ b/code/modules/antagonists/cult/cult_items.dm @@ -472,25 +472,23 @@ Striking a noncultist, however, will tear their flesh."} // on bound if(bound) + remove_filter("unbound_ray", update = FALSE) add_filter("bind_glow", 2, list("type" = "outline", "color" = h_color, "size" = 0.1)) - remove_filter("unbound_ray") - update_filters() - // on unbound - else - // we re-add this every time it's picked up or dropped - remove_filter("unbound_ray") - add_filter(name = "unbound_ray", priority = 1, params = list( - type = "rays", - size = 16, - color = COLOR_HERETIC_GREEN, // the sickly green of the heretic leaking through - density = 16, - )) - // because otherwise the animations stack and it looks ridiculous - var/ray_filter = get_filter("unbound_ray") - animate(ray_filter, offset = 100, time = 2 MINUTES, loop = -1, flags = ANIMATION_PARALLEL) // Absurdly long animate so nobody notices it hitching when it loops - animate(offset = 0, time = 2 MINUTES) // I sure hope duration of animate doesnt have any performance effect + return - update_filters() + // on unbound + // we re-add this every time it's picked up or dropped + remove_filter("bind_glow", update = FALSE) + add_filter(name = "unbound_ray", priority = 1, params = list( + type = "rays", + size = 16, + color = COLOR_HERETIC_GREEN, // the sickly green of the heretic leaking through + density = 16, + )) + // because otherwise the animations stack and it looks ridiculous + var/ray_filter = get_filter("unbound_ray") + animate(ray_filter, offset = 100, time = 2 MINUTES, loop = -1, flags = ANIMATION_PARALLEL) // Absurdly long animate so nobody notices it hitching when it loops + animate(offset = 0, time = 2 MINUTES) // I sure hope duration of animate doesnt have any performance effect /obj/item/melee/cultblade/haunted/proc/start_glow_loop() var/filter = get_filter("bind_glow") diff --git a/code/modules/point/point.dm b/code/modules/point/point.dm index ae0fc64cac9..adb539ad3b9 100644 --- a/code/modules/point/point.dm +++ b/code/modules/point/point.dm @@ -48,10 +48,7 @@ pointed_atom_appearance.pixel_x = 0 pointed_atom_appearance.pixel_y = 0 thought_bubble.overlays += pointed_atom_appearance - - var/hover_outline_index = pointed_atom.get_filter_index(HOVER_OUTLINE_FILTER) - if (!isnull(hover_outline_index)) - pointed_atom_appearance.filters.Cut(hover_outline_index, hover_outline_index + 1) + pointed_atom_appearance.remove_filter(HOVER_OUTLINE_FILTER) thought_bubble.pixel_w = 16 thought_bubble.pixel_z = 32 diff --git a/code/modules/unit_tests/plane_double_transform.dm b/code/modules/unit_tests/plane_double_transform.dm index a56b38edad7..3379f751e11 100644 --- a/code/modules/unit_tests/plane_double_transform.dm +++ b/code/modules/unit_tests/plane_double_transform.dm @@ -33,8 +33,7 @@ consider making a new render plate that they can both draw to instead, or something of that nature.") // Now we walk for filters that take from us - for(var/filter_id in plane.filter_data) - var/list/filter = plane.filter_data[filter_id] + for(var/list/filter in plane.filter_data) if(!filter["render_source"]) continue var/atom/movable/screen/plane_master/target = render_target_to_plane[filter["render_source"]]