* 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>
In-code keypress handling system
This whole system is heavily based off of forum_account's keyboard library. Thanks to forum_account for saving the day, the library can be found here!
.dmf macros have some very serious shortcomings. For example, they do not allow reusing parts
of one macro in another, so giving cyborgs their own shortcuts to swap active module couldn't
inherit the movement that all mobs should have anyways. The webclient only supports one macro,
so having more than one was problematic. Additionally each keybind has to call an actual
verb, which meant a lot of hidden verbs that just call one other proc. Also our existing
macro was really bad and tied unrelated behavior into Northeast(), Southeast(), Northwest(),
and Southwest().
The basic premise of this system is to not screw with .dmf macro setup at all and handle
pressing those keys in the code instead. We have every key call client.keyDown()
or client.keyUp() with the pressed key as an argument. Certain keys get processed
directly by the client because they should be doable at any time, then we call
keyDown() or keyUp() on the client's holder and the client's mob's focus.
By default mob.focus is the mob itself, but you can set it to any datum to give control of a
client's keypresses to another object. This would be a good way to handle a menu or driving
a mech. You can also set it to null to disregard input from a certain user.
Movement is handled by having each client call client.keyLoop() every game tick.
As above, this calls holder and focus.keyLoop(). atom/movable/keyLoop() handles movement
Try to keep the calculations in this proc light. It runs every tick for every client after all!
You can also tell which keys are being held down now. Each client a list of keys pressed called
keys_held. Each entry is a key as a text string associated with the world.time when it was
pressed.
No client-set keybindings at this time, but it shouldn't be too hard if someone wants.
Notes about certain keys:
Tabhas client-sided behavior but acts normallyT,O, andMmove focus to the input when pressed. This fires the keyUp macro right away.\needs to be escaped in the dmf so any usage is\\
You cannot TICK_CHECK or check world.tick_usage inside of procs called by key down and up
events. They happen outside of a byond tick and have no meaning there. Key looping
works correctly since it's part of a subsystem, not direct input.