mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-15 20:22:07 +00:00
* Stabilizes code that flicks overlays to view/all clients (#76937) ## About The Pull Request Rather then using images and displaying them with client.images, we can instead simply make an object, give it the passed in image/MA's appearance, and then vis_contents it where we want. If you want to animate things, you can just use the atom we return from the proc call. This ends up costing about 25% of the best case scenario (one guy online) It will save more time with more users, but it also allows us to avoid the hypersuffering that is passing GLOB.clients into the flick proc. So I think I'm happy enough with this. For context, here's average per call cost for flick_overlay_view() right now. It winds between 5e-5 and 1e-4. With these changes we should pretty consistently hit the low end of this, because none of our work really varies all that much.  (I was using sswardrobe for this, but it ends up being a lot slower so like, why yaknow) ``` /atom/movable/flick_visual New: 3.65625ms Provide: 7.4375ms Qdel: 9.4375ms Stash: 9.46875ms ``` ## Why It's Good For The Game Using our tools should not make your code eat cpu time for no reason. Hearers is expensive, iterating clients is expensive, let's not be expensive. * Stabilizes code that flicks overlays to view/all clients * Not every client needs to see this * and these could be using SECONDS * grr DM * Convert these modular files to seconds too * Update dance_machine.dm --------- Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> Co-authored-by: Giz <13398309+vinylspiders@users.noreply.github.com>
347 lines
14 KiB
Plaintext
347 lines
14 KiB
Plaintext
/// This subsystem strives to make loading large amounts of select objects as smooth at execution as possible
|
|
/// It preloads a set of types to store, and caches them until requested
|
|
/// Doesn't catch everything mind, this is intentional. There's many types that expect to either
|
|
/// A: Not sit in a list for 2 hours, or B: have extra context passed into them, or for their parent to be their location
|
|
/// You should absolutely not spam this system, it will break things in new and wonderful ways
|
|
/// S close enough for government work though.
|
|
/// Fuck you goonstation
|
|
SUBSYSTEM_DEF(wardrobe)
|
|
name = "Wardrobe"
|
|
wait = 10 // This is more like a queue then anything else
|
|
flags = SS_BACKGROUND
|
|
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT // We're going to fill up our cache while players sit in the lobby
|
|
/// How much to cache outfit items
|
|
/// Multiplier, 2 would mean cache enough items to stock 1 of each preloaded order twice, etc
|
|
var/cache_intensity = 2
|
|
/// How many more then the template of a type are we allowed to have before we delete applicants?
|
|
var/overflow_lienency = 2
|
|
/// List of type -> list(insertion callback, removal callback) callbacks for insertion/removal to use.
|
|
/// Set in setup_callbacks, used in canonization.
|
|
var/list/initial_callbacks = list()
|
|
/// Canonical list of types required to fill all preloaded stocks once.
|
|
/// Type -> list(count, last inspection timestamp, call on insert, call on removal)
|
|
var/list/canon_minimum = list()
|
|
/// List of types to load. Type -> count //(I'd do a list of lists but this needs to be refillable)
|
|
var/list/order_list = list()
|
|
/// List of lists. Contains our preloaded atoms. Type -> list(last inspect time, list(instances))
|
|
var/list/preloaded_stock = list()
|
|
/// The last time we inspected our stock
|
|
var/last_inspect_time = 0
|
|
/// How often to inspect our stock, in deciseconds
|
|
var/inspect_delay = 30 SECONDS
|
|
/// What we're currently doing
|
|
var/current_task = SSWARDROBE_STOCK
|
|
/// How many times we've had to generate a stock item on request
|
|
var/stock_miss = 0
|
|
/// How many times we've successfully returned a cached item
|
|
var/stock_hit = 0
|
|
/// How many items would we make just by loading the master list once?
|
|
var/one_go_master = 0
|
|
|
|
/datum/controller/subsystem/wardrobe/Initialize()
|
|
setup_callbacks()
|
|
load_outfits()
|
|
load_species()
|
|
load_storage_contents()
|
|
hard_refresh_queue()
|
|
stock_hit = 0
|
|
stock_miss = 0
|
|
return SS_INIT_SUCCESS
|
|
|
|
/// Resets the load queue to the master template, accounting for the existing stock
|
|
/datum/controller/subsystem/wardrobe/proc/hard_refresh_queue()
|
|
for(var/datum/type_to_queue as anything in canon_minimum)
|
|
var/list/master_info = canon_minimum[type_to_queue]
|
|
var/amount_to_load = master_info[WARDROBE_CACHE_COUNT] * cache_intensity
|
|
|
|
var/list/stock_info = preloaded_stock[type_to_queue]
|
|
if(stock_info) // If we already have stuff, reduce the amount we load
|
|
amount_to_load -= length(stock_info[WARDROBE_STOCK_CONTENTS])
|
|
set_queue_item(type_to_queue, amount_to_load)
|
|
|
|
/datum/controller/subsystem/wardrobe/stat_entry(msg)
|
|
var/total_provided = max(stock_hit + stock_miss, 1)
|
|
var/current_max_store = (one_go_master * cache_intensity) + (overflow_lienency * length(canon_minimum))
|
|
msg += " P:[length(canon_minimum)] Q:[length(order_list)] S:[length(preloaded_stock)] I:[cache_intensity] O:[overflow_lienency]"
|
|
msg += " H:[stock_hit] M:[stock_miss] T:[total_provided] H/T:[PERCENT(stock_hit / total_provided)]% M/T:[PERCENT(stock_miss / total_provided)]%"
|
|
msg += " MAX:[current_max_store]"
|
|
msg += " ID:[inspect_delay] NI:[last_inspect_time + inspect_delay]"
|
|
return ..()
|
|
|
|
/datum/controller/subsystem/wardrobe/fire(resumed=FALSE)
|
|
if(current_task != SSWARDROBE_INSPECT && world.time - last_inspect_time >= inspect_delay)
|
|
current_task = SSWARDROBE_INSPECT
|
|
|
|
switch(current_task)
|
|
if(SSWARDROBE_STOCK)
|
|
stock_wardrobe()
|
|
if(SSWARDROBE_INSPECT)
|
|
run_inspection()
|
|
if(state != SS_RUNNING)
|
|
return
|
|
current_task = SSWARDROBE_STOCK
|
|
last_inspect_time = world.time
|
|
|
|
/// Turns the order list into actual loaded items, this is where most work is done
|
|
/datum/controller/subsystem/wardrobe/proc/stock_wardrobe()
|
|
for(var/atom/movable/type_to_stock as anything in order_list)
|
|
var/amount_to_stock = order_list[type_to_stock]
|
|
for(var/i in 1 to amount_to_stock)
|
|
if(MC_TICK_CHECK)
|
|
order_list[type_to_stock] = (amount_to_stock - (i - 1)) // Account for types we've already created
|
|
return
|
|
var/atom/movable/new_member = new type_to_stock()
|
|
stash_object(new_member)
|
|
|
|
order_list -= type_to_stock
|
|
if(MC_TICK_CHECK)
|
|
return
|
|
|
|
/// Once every medium while, go through the current stock and make sure we don't have too much of one thing
|
|
/// Or that we're not too low on some other stock
|
|
/// This exists as a failsafe, so the wardrobe doesn't just end up generating too many items or accidentially running out somehow
|
|
/datum/controller/subsystem/wardrobe/proc/run_inspection()
|
|
for(var/datum/loaded_type as anything in canon_minimum)
|
|
var/list/master_info = canon_minimum[loaded_type]
|
|
var/last_looked_at = master_info[WARDROBE_CACHE_LAST_INSPECT]
|
|
if(last_looked_at == last_inspect_time)
|
|
continue
|
|
|
|
var/list/stock_info = preloaded_stock[loaded_type]
|
|
var/amount_held = 0
|
|
if(stock_info)
|
|
var/list/held_objects = stock_info[WARDROBE_STOCK_CONTENTS]
|
|
amount_held = length(held_objects)
|
|
|
|
var/target_stock = master_info[WARDROBE_CACHE_COUNT] * cache_intensity
|
|
var/target_delta = amount_held - target_stock
|
|
// If we've got too much
|
|
if(target_delta > overflow_lienency)
|
|
unload_stock(loaded_type, target_delta - overflow_lienency)
|
|
if(state != SS_RUNNING)
|
|
return
|
|
|
|
// If we have more then we target, just don't you feel me?
|
|
target_delta = min(target_delta, 0) //I only want negative numbers to matter here
|
|
|
|
// If we don't have enough, queue enough to make up the remainder
|
|
// If we have too much in the queue, cull to 0. We do this so time isn't wasted creating and destroying entries
|
|
set_queue_item(loaded_type, abs(target_delta))
|
|
|
|
master_info[WARDROBE_CACHE_LAST_INSPECT] = last_inspect_time
|
|
|
|
if(MC_TICK_CHECK)
|
|
return
|
|
|
|
/// Takes a path to get the callback owner for
|
|
/// Returns the deepest path in our callback store that matches the input
|
|
/// The hope is this will prevent dumb conflicts, since the furthest down is always going to be the most relevant
|
|
/datum/controller/subsystem/wardrobe/proc/get_callback_type(datum/to_check)
|
|
var/longest_path
|
|
var/longest_path_length = 0
|
|
for(var/datum/path as anything in initial_callbacks)
|
|
if(ispath(to_check, path))
|
|
var/stringpath = "[path]"
|
|
var/pathlength = length(splittext(stringpath, "/")) // We get the "depth" of the path
|
|
if(pathlength < longest_path_length)
|
|
continue
|
|
longest_path = path
|
|
longest_path_length = pathlength
|
|
return longest_path
|
|
|
|
/**
|
|
* Canonizes the type, which means it's now managed by the subsystem, and will be created deleted and passed out to comsumers
|
|
*
|
|
* Arguments:
|
|
* * type to stock - What type exactly do you want us to remember?
|
|
*
|
|
*/
|
|
/datum/controller/subsystem/wardrobe/proc/canonize_type(type_to_stock)
|
|
if(!type_to_stock)
|
|
return
|
|
if(!ispath(type_to_stock))
|
|
stack_trace("Non path [type_to_stock] attempted to canonize itself. Something's fucky")
|
|
var/list/master_info = canon_minimum[type_to_stock]
|
|
if(!master_info)
|
|
master_info = new /list(WARDROBE_CACHE_CALL_REMOVAL)
|
|
master_info[WARDROBE_CACHE_COUNT] = 0
|
|
//Decide on the appropriate callbacks to use
|
|
var/callback_type = get_callback_type(type_to_stock)
|
|
var/list/callback_info = initial_callbacks[callback_type]
|
|
if(callback_info)
|
|
master_info[WARDROBE_CACHE_CALL_INSERT] = callback_info[WARDROBE_CALLBACK_INSERT]
|
|
master_info[WARDROBE_CACHE_CALL_REMOVAL] = callback_info[WARDROBE_CALLBACK_REMOVE]
|
|
canon_minimum[type_to_stock] = master_info
|
|
master_info[WARDROBE_CACHE_COUNT] += 1
|
|
one_go_master++
|
|
|
|
/datum/controller/subsystem/wardrobe/proc/add_queue_item(queued_type, amount)
|
|
var/amount_held = order_list[queued_type] || 0
|
|
set_queue_item(queued_type, amount_held + amount)
|
|
|
|
/datum/controller/subsystem/wardrobe/proc/remove_queue_item(queued_type, amount)
|
|
var/amount_held = order_list[queued_type]
|
|
if(!amount_held)
|
|
return
|
|
set_queue_item(queued_type, amount_held - amount)
|
|
|
|
/datum/controller/subsystem/wardrobe/proc/set_queue_item(queued_type, amount)
|
|
var/list/master_info = canon_minimum[queued_type]
|
|
if(!master_info)
|
|
stack_trace("We just tried to queue a type \[[queued_type]\] that's not stored in the master canon")
|
|
return
|
|
|
|
var/target_amount = master_info[WARDROBE_CACHE_COUNT] * cache_intensity
|
|
var/list/stock_info = preloaded_stock[queued_type]
|
|
if(stock_info)
|
|
target_amount -= length(stock_info[WARDROBE_STOCK_CONTENTS])
|
|
|
|
amount = min(amount, target_amount) // If we're trying to set more then we need, don't!
|
|
|
|
if(amount <= 0) // If we already have all we need, end it
|
|
order_list -= queued_type
|
|
return
|
|
|
|
order_list[queued_type] = amount
|
|
|
|
/// Take an existing object, and insert it into our storage
|
|
/// If we can't or won't take it, it's deleted. You do not own this object after passing it in
|
|
/datum/controller/subsystem/wardrobe/proc/stash_object(atom/movable/object)
|
|
var/object_type = object.type
|
|
var/list/master_info = canon_minimum[object_type]
|
|
// I will not permit objects you didn't reserve ahead of time
|
|
if(!master_info)
|
|
qdel(object)
|
|
return
|
|
|
|
var/stock_target = master_info[WARDROBE_CACHE_COUNT] * cache_intensity
|
|
var/amount_held = 0
|
|
var/list/stock_info = preloaded_stock[object_type]
|
|
if(stock_info)
|
|
amount_held = length(stock_info[WARDROBE_STOCK_CONTENTS])
|
|
|
|
// Doublely so for things we already have too much of
|
|
if(amount_held - stock_target >= overflow_lienency)
|
|
qdel(object)
|
|
return
|
|
// Fuck off
|
|
if(QDELETED(object))
|
|
stack_trace("We tried to stash a qdeleted object, what did you do")
|
|
return
|
|
|
|
if(!stock_info)
|
|
stock_info = new /list(WARDROBE_STOCK_CALL_REMOVAL)
|
|
stock_info[WARDROBE_STOCK_CONTENTS] = list()
|
|
stock_info[WARDROBE_STOCK_CALL_INSERT] = master_info[WARDROBE_CACHE_CALL_INSERT]
|
|
stock_info[WARDROBE_STOCK_CALL_REMOVAL] = master_info[WARDROBE_CACHE_CALL_REMOVAL]
|
|
preloaded_stock[object_type] = stock_info
|
|
|
|
var/datum/callback/do_on_insert = stock_info[WARDROBE_STOCK_CALL_INSERT]
|
|
if(do_on_insert)
|
|
do_on_insert.object = object
|
|
do_on_insert.Invoke()
|
|
do_on_insert.object = null
|
|
|
|
object.moveToNullspace()
|
|
stock_info[WARDROBE_STOCK_CONTENTS] += object
|
|
|
|
/datum/controller/subsystem/wardrobe/proc/provide_type(datum/requested_type, atom/movable/location)
|
|
var/atom/movable/requested_object
|
|
var/list/stock_info = preloaded_stock[requested_type]
|
|
if(!stock_info)
|
|
stock_miss++
|
|
requested_object = new requested_type(location)
|
|
return requested_object
|
|
|
|
var/list/contents = stock_info[WARDROBE_STOCK_CONTENTS]
|
|
var/contents_length = length(contents)
|
|
requested_object = contents[contents_length]
|
|
contents.len--
|
|
|
|
if(QDELETED(requested_object))
|
|
stack_trace("We somehow ended up with a qdeleted or null object in SSwardrobe's stock. Something's weird, likely to do with reinsertion. Typepath of [requested_type]")
|
|
stock_miss++
|
|
requested_object = new requested_type(location)
|
|
return requested_object
|
|
|
|
if(location)
|
|
requested_object.forceMove(location)
|
|
|
|
var/datum/callback/do_on_removal = stock_info[WARDROBE_STOCK_CALL_REMOVAL]
|
|
if(do_on_removal)
|
|
do_on_removal.object = requested_object
|
|
do_on_removal.Invoke()
|
|
do_on_removal.object = null
|
|
|
|
stock_hit++
|
|
add_queue_item(requested_type, 1) // Requeue the item, under the assumption we'll never see it again
|
|
if(!(contents_length - 1))
|
|
preloaded_stock -= requested_type
|
|
|
|
return requested_object
|
|
|
|
/// Unloads an amount of some type we have in stock
|
|
/// Private function, for internal use only
|
|
/datum/controller/subsystem/wardrobe/proc/unload_stock(datum/unload_type, amount, force = FALSE)
|
|
var/list/stock_info = preloaded_stock[unload_type]
|
|
if(!stock_info)
|
|
return
|
|
|
|
var/list/unload_from = stock_info[WARDROBE_STOCK_CONTENTS]
|
|
for(var/i in 1 to min(amount, length(unload_from)))
|
|
var/datum/nuke = unload_from[unload_from.len]
|
|
unload_from.len--
|
|
qdel(nuke)
|
|
if(!force && MC_TICK_CHECK && length(unload_from))
|
|
return
|
|
|
|
if(!length(stock_info[WARDROBE_STOCK_CONTENTS]))
|
|
preloaded_stock -= unload_type
|
|
|
|
/// Sets up insertion and removal callbacks by typepath
|
|
/// We will always use the deepest path. So /obj/item/blade/knife superceeds the entries of /obj/item and /obj/item/blade
|
|
/// Mind this
|
|
/datum/controller/subsystem/wardrobe/proc/setup_callbacks()
|
|
var/list/play_with = new /list(WARDROBE_CALLBACK_REMOVE) // Turns out there's a global list of pdas. Let's work around that yeah?
|
|
|
|
play_with = new /list(WARDROBE_CALLBACK_REMOVE) // Don't want organs rotting on the job
|
|
play_with[WARDROBE_CALLBACK_INSERT] = CALLBACK(null, TYPE_PROC_REF(/obj/item/organ,enter_wardrobe))
|
|
play_with[WARDROBE_CALLBACK_REMOVE] = CALLBACK(null, TYPE_PROC_REF(/obj/item/organ,exit_wardrobe))
|
|
initial_callbacks[/obj/item/organ] = play_with
|
|
|
|
play_with = new /list(WARDROBE_CALLBACK_REMOVE)
|
|
play_with[WARDROBE_CALLBACK_REMOVE] = CALLBACK(null, TYPE_PROC_REF(/obj/item/storage/box/survival, wardrobe_removal))
|
|
initial_callbacks[/obj/item/storage/box/survival] = play_with
|
|
|
|
/datum/controller/subsystem/wardrobe/proc/load_outfits()
|
|
for(var/datum/outfit/to_stock as anything in subtypesof(/datum/outfit))
|
|
if(!initial(to_stock.preload)) // Clearly not interested
|
|
continue
|
|
var/datum/outfit/hang_up = new to_stock()
|
|
for(var/datum/outfit_item as anything in hang_up.get_types_to_preload())
|
|
canonize_type(outfit_item)
|
|
CHECK_TICK
|
|
|
|
/datum/controller/subsystem/wardrobe/proc/load_species()
|
|
for(var/datum/species/to_record as anything in subtypesof(/datum/species))
|
|
if(!initial(to_record.preload))
|
|
continue
|
|
var/datum/species/fossil_record = new to_record()
|
|
for(var/obj/item/species_request as anything in fossil_record.get_types_to_preload())
|
|
for(var/i in 1 to 5) // Store 5 of each species, since that seems on par with 1 of each outfit
|
|
canonize_type(species_request)
|
|
CHECK_TICK
|
|
|
|
/datum/controller/subsystem/wardrobe/proc/load_storage_contents()
|
|
for(var/obj/item/storage/crate as anything in subtypesof(/obj/item/storage))
|
|
if(!initial(crate.preload))
|
|
continue
|
|
var/obj/item/storage/another_crate = new crate()
|
|
//Unlike other uses, I really don't want people being lazy with this one.
|
|
var/list/somehow_more_boxes = another_crate.get_types_to_preload()
|
|
if(!length(somehow_more_boxes))
|
|
stack_trace("You appear to have set preload to true on [crate] without defining get_types_to_preload. Please be more strict about your scope, this stuff is spooky")
|
|
for(var/datum/a_really_small_box as anything in somehow_more_boxes)
|
|
canonize_type(a_really_small_box)
|
|
qdel(another_crate)
|