/
movement.dm
134 lines (118 loc) · 5.4 KB
/
movement.dm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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()