/** * CarnMC * * Simplified MC; designed to fail fast and respawn. * This ensures Master.process() never doubles up. * It will kill itself and any sleeping procs if needed. * * All core systems are subsystems. * They are process()'d by this Master Controller. **/ var/global/datum/controller/master/Master = new() /datum/controller/master name = "Master" // The minimum length of time between MC ticks (in deciseconds). // The highest this can be without affecting schedules is the GCD of all subsystem waits. // Set to 0 to disable all processing. var/processing_interval = 1 // The iteration of the MC. var/iteration = 0 // The cost (in deciseconds) of the MC loop. var/cost = 0 #if DM_VERSION < 510 // The old fps when we slow it down to prevent lag. var/old_fps #endif // A list of subsystems to process(). var/list/subsystems = list() // The cost of running the subsystems (in deciseconds). var/subsystem_cost = 0 // The type of the last subsystem to be process()'d. var/last_type_processed /datum/controller/master/New() // Highlander-style: there can only be one! Kill off the old and replace it with the new. if(Master != src) if(istype(Master)) Recover() qdel(Master) else init_subtypes(/datum/subsystem, subsystems) Master = src /datum/controller/master/Destroy() ..() // Tell qdel() to Del() this object. return QDEL_HINT_HARDDEL_NOW /datum/controller/master/proc/Recover() var/msg = "## DEBUG: [time2text(world.timeofday)] MC restarted. Reports:\n" for(var/varname in Master.vars) switch(varname) if("name", "tag", "bestF", "type", "parent_type", "vars", "statclick") // Built-in junk. continue else var/varval = Master.vars[varname] if(istype(varval, /datum)) // Check if it has a type var. var/datum/D = varval msg += "\t [varname] = [D.type]\n" else msg += "\t [varname] = [varval]\n" world.log << msg subsystems = Master.subsystems /datum/controller/master/proc/Setup(zlevel) // Per-Z-level subsystems. if(zlevel && zlevel > 0 && zlevel <= world.maxz) for(var/datum/subsystem/SS in subsystems) SS.Initialize(world.timeofday, zlevel) sleep(-1) return world << "Initializing subsystems..." preloadTemplates() // Pick a random away mission. createRandomZlevel() // Generate asteroid. make_mining_asteroid_secrets() // Set up Z-level transistions. setup_map_transitions() // Sort subsystems by priority, so they initialize in the correct order. sortTim(subsystems, /proc/cmp_subsystem_priority) // Initialize subsystems. for(var/datum/subsystem/SS in subsystems) SS.Initialize(world.timeofday, zlevel) sleep(-1) world << "Initializations complete!" // Sort subsystems by display setting for easy access. sortTim(subsystems, /proc/cmp_subsystem_display) // Set world options. world.sleep_offline = 1 world.fps = config.fps sleep(-1) // Loop. Master.process() // Notify the MC that the round has started. /datum/controller/master/proc/RoundStart() for(var/datum/subsystem/SS in subsystems) SS.can_fire = 1 SS.next_fire = world.time + rand(0, SS.wait) // Stagger subsystems. // Used to smooth out costs to try and avoid oscillation. #define MC_AVERAGE_FAST(average, current) (0.7 * (average) + 0.3 * (current)) #define MC_AVERAGE(average, current) (0.8 * (average) + 0.2 * (current)) #define MC_AVERAGE_SLOW(average, current) (0.9 * (average) + 0.1 * (current)) /datum/controller/master/process() if(!Failsafe) new/datum/controller/failsafe() // (re)Start the failsafe. spawn(0) // Schedule the first run of the Subsystems. var/timer = world.time for(var/datum/subsystem/SS in subsystems) timer += processing_interval SS.next_fire = timer var/start_time while(1) // More efficient than recursion, 1 to avoid an infinite loop. if(processing_interval > 0) ++iteration var/startingtick = world.time // Store when we started this iteration. start_time = world.timeofday var/ran_subsystems = 0 for(var/datum/subsystem/SS in subsystems) #if DM_VERSION >= 510 if (world.tick_usage > 80) #else if(world.cpu >= 100) #endif break if(SS.can_fire > 0) if(SS.next_fire <= world.time && SS.last_fire + (SS.wait * 0.75) <= world.time) // Check if it's time. #if DM_VERSION >= 510 if (world.tick_usage + SS.tick_usage > 80 && SS.last_fire + (SS.wait*1.25) > world.time) continue #endif ran_subsystems = 1 timer = world.timeofday last_type_processed = SS.type SS.last_fire = world.time #if DM_VERSION >= 510 var/tick_usage = world.tick_usage #endif SS.fire() // Fire the subsystem and record the cost. #if DM_VERSION >= 510 var/newusage = max(world.tick_usage - tick_usage, 0) if (newusage < SS.tick_usage) SS.tick_usage = MC_AVERAGE_SLOW(SS.tick_usage,world.tick_usage - tick_usage) else SS.tick_usage = MC_AVERAGE_FAST(SS.tick_usage,world.tick_usage - tick_usage) #endif SS.cost = max(MC_AVERAGE(SS.cost, world.timeofday - timer), 0) if(SS.dynamic_wait) // Adjust wait depending on lag. var/oldwait = SS.wait var/global_delta = (subsystem_cost - (SS.cost / (SS.wait / 10))) - 1 var/newwait = (SS.cost - SS.dwait_buffer + global_delta) * SS.dwait_delta newwait = newwait * (world.cpu / 100 + 1) //smooth out wait changes, but only if going down if(newwait < oldwait) newwait = MC_AVERAGE(oldwait, newwait) SS.wait = Clamp(newwait, SS.dwait_lower, SS.dwait_upper) SS.next_fire = world.time + SS.wait else SS.next_fire += SS.wait ++SS.times_fired // If we caused BYOND to miss a tick, stop processing for a bit... if(startingtick < world.time || start_time + 1 < world.timeofday) break sleep(0) cost = max(MC_AVERAGE(cost, world.timeofday - start_time), 0) if(ran_subsystems) var/oldcost = subsystem_cost var/newcost = 0 for(var/datum/subsystem/SS in subsystems) if (!SS.can_fire) continue newcost += SS.cost / (SS.wait / 10) subsystem_cost = MC_AVERAGE(oldcost, newcost) var/extrasleep = 0 // If we are loading the server too much, sleep a bit extra... if(world.cpu >= 75) extrasleep += (extrasleep + processing_interval) * ((world.cpu-50)/10) #if DM_VERSION < 510 if(world.cpu >= 100) if(!old_fps) old_fps = world.fps //byond bug, if we go over 120 fps and world.fps is higher then 10, the bad things that happen are made worst. world.fps = 10 else if(old_fps && world.cpu < 50) world.fps = old_fps old_fps = null #endif sleep(processing_interval + extrasleep) else sleep(50) #undef MC_AVERAGE /datum/controller/master/proc/stat_entry() if(!statclick) statclick = new/obj/effect/statclick/debug("Initializing...", src) stat("Master Controller:", statclick.update("[round(Master.cost, 0.01)]ds (Interval: [Master.processing_interval] | Iteration:[Master.iteration])"))