Files
Bubberstation/code/__DEFINES/verb_manager.dm
SkyratBot d8da1153b7 [MIRROR] Biddle Verbs: Queues the Most Expensive Verbs for the Next Tick if the Server Is Overloaded [MDB IGNORE] (#15329)
* Biddle Verbs: Queues the Most Expensive Verbs for the Next Tick if the Server Is Overloaded (#65589)

This pr goes through: /client/Click(), /client/Topic(), /mob/living/verb/resist(), /mob/verb/quick_equip(), /mob/verb/examinate(), and /mob/verb/mode() and makes them queue their functionality to a subsystem to execute in the next tick if the server is overloaded. To do this a new subsystem is made to handle most verbs called SSverb_manager, if the server is overloaded the verb queues itself in the subsystem and returns, then near the start of the next tick that verb is resumed with the provided callback. The verbs are called directly after SSinput, and the subsystem does not yield until its queue is completely finished.

The exception are clicks from player input since they are extremely important for the feeling of responsiveness. I considered not queuing them but theyre too expensive not to, suffering from a death of a thousand cuts performance wise from many many things in the process adding up. Instead clicks are executed at the very start of the next tick, as the first action that SSinput completes, before player movement is processed even.

A few months ago, before I died I was trying to figure out why games at midpop (40-50 people) had non zero and consistent time dilation without maptick being consistently above 28% (which is when the MC stops yielding for maptick if its overloaded). I found it out, started working on this pr, then promptly died. luckily im a bit less dead now

the current MC has a problem: the cost of verbs is completely and totally invisible to it, it cannot account for them. Why is this bad? because verbs are the last thing to execute in the tick, after the MC and SendMaps have finished executing.
tick diagram2
If the MC is overloaded and uses 100% of the time it allots itself this means that if SendMaps uses the amount its expected to take, verbs have at most 2% of the tick to execute in before they are overtiming and thus delaying the start of the next tick. This is bad, and im 99% sure this is the majority of our overtime.

Take Click() for example. Click isnt listed as a verb but since its called as a result of client commands its executed at the end of the tick like other verbs. in this random 80 pop sybil round profile i had saved on my computer sybil 80 pop (2).txt /client/Click() has an overtime of only 1.8 seconds, which isnt that bad. however it has a self cpu of 2.5 seconds meaning 1.8/2.5 = 72% of its time is overtiming, and it also is calling 80.2 seconds worth of total cpu, which means that more than 57.7 seconds of overtime is attributed to just /client/Click() executing at the very end of a tick. the reason why this isnt obvious is just because the verbs themselves typically dont have high enough self cpu to get high enough on the rankings of overtiming procs to be noticed, all of their overtime is distributed among a ton of procs they call in the chain.

Since i cant guarantee the MC resumes at the very start of the next tick due to other sleeping procs almost always resuming first: I time the duration between clicks being queued up for the next tick and when theyre actually executed. if it exceeds 20 milliseconds of added latency (less than one tenth the average human reaction time) clicks will execute immediately instead of queuing, this should make instances where a player can notice the added latency a vanishingly small minority of cases. still, this should be tm'd

* Biddle Verbs: Queues the Most Expensive Verbs for the Next Tick if the Server Is Overloaded

Co-authored-by: Kylerace <kylerlumpkin1@gmail.com>
2022-07-31 22:03:59 +00:00

37 lines
3.2 KiB
Plaintext

/**
* verb queuing thresholds. remember that since verbs execute after SendMaps the player wont see the effects of the verbs on the game world
* until SendMaps executes next tick, and then when that later update reaches them. thus most player input has a minimum latency of world.tick_lag + player ping.
* however thats only for the visual effect of player input, when a verb processes the actual latency of game state changes or semantic latency is effectively 1/2 player ping,
* unless that verb is queued for the next tick in which case its some number probably smaller than world.tick_lag.
* so some verbs that represent player input are important enough that we only introduce semantic latency if we absolutely need to.
* its for this reason why player clicks are handled in SSinput before even movement - semantic latency could cause someone to move out of range
* when the verb finally processes but it was in range if the verb had processed immediately and overtimed.
*/
///queuing tick_usage threshold for verbs that are high enough priority that they only queue if the server is overtiming.
///ONLY use for critical verbs
#define VERB_OVERTIME_QUEUE_THRESHOLD 100
///queuing tick_usage threshold for verbs that need lower latency more than most verbs.
#define VERB_HIGH_PRIORITY_QUEUE_THRESHOLD 95
///default queuing tick_usage threshold for most verbs which can allow a small amount of latency to be processed in the next tick
#define VERB_DEFAULT_QUEUE_THRESHOLD 85
///attempt to queue this verb process if the server is overloaded. evaluates to FALSE if queuing isnt necessary or if it failed.
///_verification_args... are only necessary if the verb_manager subsystem youre using checks them in can_queue_verb()
///if you put anything in _verification_args that ISNT explicitely put in the can_queue_verb() override of the subsystem youre using,
///it will runtime.
#define TRY_QUEUE_VERB(_verb_callback, _tick_check, _subsystem_to_use, _verification_args...) (_queue_verb(_verb_callback, _tick_check, _subsystem_to_use, _verification_args))
///queue wrapper for TRY_QUEUE_VERB() when you want to call the proc if the server isnt overloaded enough to queue
#define QUEUE_OR_CALL_VERB(_verb_callback, _tick_check, _subsystem_to_use, _verification_args...) \
if(!TRY_QUEUE_VERB(_verb_callback, _tick_check, _subsystem_to_use, _verification_args)) {\
_verb_callback:InvokeAsync() \
};
//goes straight to SSverb_manager with default tick threshold
#define DEFAULT_TRY_QUEUE_VERB(_verb_callback, _verification_args...) (TRY_QUEUE_VERB(_verb_callback, VERB_DEFAULT_QUEUE_THRESHOLD, null, _verification_args))
#define DEFAULT_QUEUE_OR_CALL_VERB(_verb_callback, _verification_args...) QUEUE_OR_CALL_VERB(_verb_callback, VERB_DEFAULT_QUEUE_THRESHOLD, null, _verification_args)
//default tick threshold but nondefault subsystem
#define TRY_QUEUE_VERB_FOR(_verb_callback, _subsystem_to_use, _verification_args...) (TRY_QUEUE_VERB(_verb_callback, VERB_DEFAULT_QUEUE_THRESHOLD, _subsystem_to_use, _verification_args))
#define QUEUE_OR_CALL_VERB_FOR(_verb_callback, _subsystem_to_use, _verification_args...) QUEUE_OR_CALL_VERB(_verb_callback, VERB_DEFAULT_QUEUE_THRESHOLD, _subsystem_to_use, _verification_args)