Files
Bubberstation/code/controllers/master_controller.dm
MrStonedOne c784ca902e Ports /vg/'s asset cache system; lowers interface lag when clients connect.
Byond will queue all browse(), browse_rsc(), and winset() calls to the client, to ensure they load in order, and ensure any resources from a browse_rsc call (css/html/js) are already at the client by the time any html windows loads.

How ever, each file is processed separately, and byond will wait for the reply from the client before it will send the next file.

Our current system sends all of these html resource files to the client at once when they connect, this is the sole cause of the lag when a client connects. Byond will not send the client a file it already has, but it has to ask the client first, and it does so one file at a time, waiting for a reply from the client before sending the next one down the pipe.

This system fixes that.

Basically it works like this:
Client connects: nothing happens, no massive queuing of browse_rsc() calls, so no interface delay
Client opens a asset_cache controlled html based interface
Asset cache gets notified by the html based interface what assets the client needs to have.
Asset cache checks to see if it's sent that client those files.
Asset cache sends the missing ones, adding them to the list of assets the client has.

This basically spreads out the delay to when you first open a window that uses resources, where it is much more manageable.

I've kinda done a halfass port without too much thought, I see some room for improvement to better fit /tg/'s coding style and make the system more flexible. I'm PRing this because if I don't, it will never get finished.

PDAs and html_interface has been imported in to the new system lazily to test. at 100ms connection start interface lag went from 35 seconds to 16 seconds. Nanoui hasn't been imported, and once it is, that should drop down to almost nothing.

I'll work on this some more after some sleep.
2015-11-15 23:49:05 -08:00

158 lines
5.0 KiB
Plaintext

//simplified MC that is designed to fail when procs 'break'. When it fails it's just replaced with a new one.
//It ensures master_controller.process() is never doubled up by killing the MC (hence terminating any of its sleeping procs)
//Update: all core-systems are now placed inside subsystem datums. This makes them highly configurable and easy to work with.
var/global/datum/controller/game_controller/master_controller = new()
/datum/controller/game_controller
var/processing_interval = 1 //The minimum length of time between MC ticks (in deciseconds). The highest this can be without affecting schedules, is the GCD of all subsystem var/wait. Set to 0 to disable all processing.
var/iteration = 0
var/cost = 0
var/SSCostPerSecond = 0
var/last_thing_processed
var/list/subsystems = list()
/datum/controller/game_controller/New()
//There can be only one master_controller. Out with the old and in with the new.
if(master_controller != src)
if(istype(master_controller))
Recover()
qdel(master_controller)
else
init_subtypes(/datum/subsystem, subsystems)
master_controller = src
calculateGCD()
/datum/controller/game_controller/Destroy()
..()
return QDEL_HINT_HARDDEL_NOW
/*
calculate the longest number of ticks the MC can wait between each cycle without causing subsystems to not fire on schedule
*/
/datum/controller/game_controller/proc/calculateGCD()
var/GCD
for(var/datum/subsystem/SS in subsystems)
if(SS.wait)
GCD = Gcd(round(SS.wait*10), GCD)
GCD = round(GCD)
if(GCD < world.tick_lag*10)
GCD = world.tick_lag*10
processing_interval = GCD/10
/datum/controller/game_controller/proc/setup(zlevel)
if (zlevel && zlevel > 0 && zlevel <= world.maxz)
for(var/datum/subsystem/S in subsystems)
S.Initialize(world.timeofday, zlevel)
sleep(-1)
return
world << "<span class='boldannounce'>Initializing Subsystems...</span>"
//sort subsystems by priority, so they initialize in the correct order
sortTim(subsystems, /proc/cmp_subsystem_priority)
createRandomZlevel() //gate system
setup_map_transitions()
for(var/i=0, i<max_secret_rooms, i++)
make_mining_asteroid_secret()
//Eventually all this other setup stuff should be contained in subsystems and done in subsystem.Initialize()
for(var/datum/subsystem/S in subsystems)
S.Initialize(world.timeofday, zlevel)
sleep(-1)
crewmonitor.generateMiniMaps()
populate_asset_cache()
world << "<span class='boldannounce'>Initializations complete</span>"
world.sleep_offline = 1
world.fps = config.fps
sleep(-1)
process()
//used for smoothing out the cost values so they don't fluctuate wildly
#define MC_AVERAGE(average, current) (0.8*(average) + 0.2*(current))
/datum/controller/game_controller/process()
if(!Failsafe) new /datum/controller/failsafe()
spawn(0)
var/timer = world.time
for(var/datum/subsystem/SS in subsystems)
timer += processing_interval
SS.next_fire = timer
var/start_time
while(1) //far more efficient than recursively calling ourself
if(processing_interval > 0)
++iteration
start_time = world.timeofday
var/SubSystemRan = 0
for(var/datum/subsystem/SS in subsystems)
if(SS.can_fire > 0)
if(SS.next_fire <= world.time)
SubSystemRan = 1
timer = world.timeofday
last_thing_processed = SS.type
SS.last_fire = world.time
SS.fire()
SS.cost = MC_AVERAGE(SS.cost, world.timeofday - timer)
if (SS.dynamic_wait)
var/oldwait = SS.wait
var/GlobalCostDelta = (SSCostPerSecond-(SS.cost/(SS.wait/10)))-1
var/NewWait = MC_AVERAGE(oldwait,(SS.cost-SS.dwait_buffer+GlobalCostDelta)*SS.dwait_delta)
SS.wait = Clamp(NewWait,SS.dwait_lower,SS.dwait_upper)
if (oldwait != SS.wait)
calculateGCD()
SS.next_fire += SS.wait
++SS.times_fired
sleep(-1)
cost = MC_AVERAGE(cost, world.timeofday - start_time)
if (SubSystemRan)
calculateSScost()
sleep(processing_interval)
else
sleep(50)
/datum/controller/game_controller/proc/calculateSScost()
var/newcost = 0
for(var/datum/subsystem/SS in subsystems)
if (!SS.can_fire)
continue
newcost += SS.cost/(SS.wait/10)
SSCostPerSecond = MC_AVERAGE(SSCostPerSecond,newcost)
#undef MC_AVERAGE
/datum/controller/game_controller/proc/roundHasStarted()
for(var/datum/subsystem/SS in subsystems)
SS.can_fire = 1
SS.next_fire = world.time + rand(0,SS.wait)
/datum/controller/game_controller/proc/Recover()
var/msg = "## DEBUG: [time2text(world.timeofday)] MC restarted. Reports:\n"
for(var/varname in master_controller.vars)
switch(varname)
if("tag","bestF","type","parent_type","vars") continue
else
var/varval = master_controller.vars[varname]
if(istype(varval,/datum))
var/datum/D = varval
msg += "\t [varname] = [D.type]\n"
else
msg += "\t [varname] = [varval]\n"
world.log << msg
subsystems = master_controller.subsystems