mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-01-02 13:42:32 +00:00
135 lines
5.4 KiB
Plaintext
135 lines
5.4 KiB
Plaintext
SUBSYSTEM_DEF(movement)
|
|
name = "Movement Loops"
|
|
flags = SS_NO_INIT|SS_BACKGROUND|SS_TICKER
|
|
wait = 1 //Fire each tick
|
|
/*
|
|
A breif aside about the bucketing system here
|
|
|
|
The goal is to allow for higher loads of semi long delays while reducing cpu usage
|
|
Bucket insertion and management are much less complex then what you might see in SStimer
|
|
This is intentional, as we loop our delays much more often then that ss is designed for
|
|
We also have much shorter term timers, so we need to worry about redundant buckets much less
|
|
*/
|
|
///Assoc list of "target time" -> list(things to process). Used for quick lookup
|
|
var/list/buckets = list()
|
|
///Sorted list of list(target time, bucket to process)
|
|
var/list/sorted_buckets = list()
|
|
///The time we started our last fire at
|
|
var/canonical_time = 0
|
|
///The visual delay of the subsystem
|
|
var/visual_delay = 1
|
|
|
|
/datum/controller/subsystem/movement/stat_entry(msg)
|
|
var/total_len = 0
|
|
for(var/list/bucket as anything in sorted_buckets)
|
|
total_len += length(bucket[MOVEMENT_BUCKET_LIST])
|
|
msg = "B:[length(sorted_buckets)] E:[total_len]"
|
|
return ..()
|
|
|
|
/datum/controller/subsystem/movement/Recover()
|
|
//Get ready this is gonna be horrible
|
|
//We need to do this to support subtypes by the by
|
|
var/list/typenames = return_typenames(src.type)
|
|
var/our_name = typenames[length(typenames)] //Get the last name in the list, IE the subsystem identifier
|
|
|
|
var/datum/controller/subsystem/movement/old_version = global.vars["SS[our_name]"]
|
|
buckets = old_version.buckets
|
|
sorted_buckets = old_version.sorted_buckets
|
|
|
|
/datum/controller/subsystem/movement/fire(resumed)
|
|
if(!resumed)
|
|
canonical_time = world.time
|
|
|
|
for(var/list/bucket_info as anything in sorted_buckets)
|
|
var/time = bucket_info[MOVEMENT_BUCKET_TIME]
|
|
if(time > canonical_time || MC_TICK_CHECK)
|
|
return
|
|
pour_bucket(bucket_info)
|
|
|
|
/// Processes a bucket of movement loops (This should only ever be called by fire(), it exists to prevent runtime fuckery)
|
|
/datum/controller/subsystem/movement/proc/pour_bucket(list/bucket_info)
|
|
var/list/processing = bucket_info[MOVEMENT_BUCKET_LIST] // Cache for lookup speed
|
|
while(processing.len)
|
|
var/datum/move_loop/loop = processing[processing.len]
|
|
processing.len--
|
|
// No longer queued since we just got removed from the loop
|
|
loop.queued_time = null
|
|
loop.process() //This shouldn't get nulls, if it does, runtime
|
|
if(!QDELETED(loop) && loop.status & MOVELOOP_STATUS_QUEUED) //Re-Insert the loop
|
|
loop.status &= ~MOVELOOP_STATUS_QUEUED
|
|
loop.timer = world.time + loop.delay
|
|
queue_loop(loop)
|
|
if(MC_TICK_CHECK)
|
|
break
|
|
|
|
if(length(processing))
|
|
return // Still work to be done
|
|
var/bucket_time = bucket_info[MOVEMENT_BUCKET_TIME]
|
|
smash_bucket(1, bucket_time) // We assume we're the first bucket in the queue right now
|
|
visual_delay = MC_AVERAGE_FAST(visual_delay, max((world.time - canonical_time) / wait, 1))
|
|
|
|
/// Removes a bucket from our system. You only need to pass in the time, but if you pass in the index of the list you save us some work
|
|
/datum/controller/subsystem/movement/proc/smash_bucket(index, bucket_time)
|
|
var/sorted_length = length(sorted_buckets)
|
|
if(!index)
|
|
index = sorted_length + 1 // let's setup the failure condition
|
|
for(var/i in 1 to sorted_length)
|
|
var/list/bucket_info = sorted_buckets[i]
|
|
if(bucket_info[MOVEMENT_BUCKET_TIME] != bucket_time)
|
|
continue
|
|
index = i
|
|
break
|
|
//This is technically possible, if our bucket is smashed inside the loop's process
|
|
//Let's be nice, the cost of doing it is cheap
|
|
if(index > sorted_length || !buckets["[bucket_time]"])
|
|
return
|
|
|
|
sorted_buckets.Cut(index, index + 1) //Removes just this list
|
|
//Removes the assoc lookup too
|
|
buckets -= "[bucket_time]"
|
|
|
|
/datum/controller/subsystem/movement/proc/queue_loop(datum/move_loop/loop)
|
|
if(loop.status & MOVELOOP_STATUS_QUEUED)
|
|
stack_trace("A move loop attempted to queue while already queued")
|
|
return
|
|
loop.queued_time = loop.timer
|
|
loop.status |= MOVELOOP_STATUS_QUEUED
|
|
var/list/our_bucket = buckets["[loop.queued_time]"]
|
|
// If there's no bucket for this, lets set them up
|
|
if(!our_bucket)
|
|
buckets["[loop.queued_time]"] = list()
|
|
our_bucket = buckets["[loop.queued_time]"]
|
|
// This makes assoc buckets and sorted buckets point to the same place, allowing for quicker inserts
|
|
var/list/new_bucket = list(list(loop.queued_time, our_bucket))
|
|
var/list/compare_item = list(loop.queued_time)
|
|
BINARY_INSERT_DEFINE(new_bucket, sorted_buckets, SORT_VAR_NO_TYPE, compare_item, SORT_FIRST_INDEX, COMPARE_KEY)
|
|
|
|
our_bucket |= loop
|
|
|
|
/datum/controller/subsystem/movement/proc/dequeue_loop(datum/move_loop/loop)
|
|
// Go home, you're not here anyway
|
|
if(!(loop.status & MOVELOOP_STATUS_QUEUED))
|
|
return
|
|
if(isnull(loop.queued_time)) // This happens if a moveloop is dequeued while handling process()
|
|
loop.status &= ~MOVELOOP_STATUS_QUEUED
|
|
return
|
|
var/list/our_entries = buckets["[loop.queued_time]"]
|
|
our_entries -= loop
|
|
if(!length(our_entries))
|
|
smash_bucket(bucket_time = loop.queued_time) // We can't pass an index in for context because we don't know our position
|
|
loop.queued_time = null
|
|
loop.status &= ~MOVELOOP_STATUS_QUEUED
|
|
|
|
/datum/controller/subsystem/movement/proc/add_loop(datum/move_loop/add)
|
|
if(add.status & MOVELOOP_STATUS_QUEUED)
|
|
CRASH("Loop being added that is already queued.")
|
|
add.loop_started()
|
|
if(QDELETED(add) || add.status & MOVELOOP_STATUS_QUEUED)
|
|
return
|
|
queue_loop(add)
|
|
|
|
/datum/controller/subsystem/movement/proc/remove_loop(datum/move_loop/remove)
|
|
dequeue_loop(remove)
|
|
remove.loop_stopped()
|
|
|