[MIRROR] Adds moveloop bucketing, uses queues for the singulo rather then sleeps [MDB IGNORE] (#11257)

* Adds moveloop bucketing, uses queues for the singulo rather then sleeps

* Update singularity.dm

Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: Gandalf <9026500+Gandalf2k15@users.noreply.github.com>
This commit is contained in:
SkyratBot
2022-02-08 01:21:56 +00:00
committed by GitHub
co-authored by LemonInTheDark Gandalf
parent 0828007a1d
commit a7fcb65332
8 changed files with 186 additions and 53 deletions
+4
View File
@@ -35,6 +35,10 @@ GLOBAL_VAR_INIT(glide_size_multiplier, 1.0)
///Should we override the loop's glide?
#define MOVEMENT_LOOP_IGNORE_GLIDE (1<<2)
//Index defines for movement bucket data packets
#define MOVEMENT_BUCKET_TIME 1
#define MOVEMENT_BUCKET_LIST 2
/**
* currently_z_moving defines. Higher numbers mean higher priority.
* This one is for falling down open space from stuff such as deleted tile, pit grate...
+37
View File
@@ -133,6 +133,43 @@
};\
} while(FALSE)
#define SORT_FIRST_INDEX(list) (list[1])
#define SORT_VAR_NO_TYPE(varname) var/varname
/****
* Even more custom binary search sorted insert, using defines instead of vars
* INPUT: Item to be inserted
* LIST: List to insert INPUT into
* TYPECONT: A define setting the var to the typepath of the contents of the list
* COMPARE: The item to compare against, usualy the same as INPUT
* COMPARISON: A define that takes an item to compare as input, and returns their comparable value
* COMPTYPE: How should the list be compared? Either COMPARE_KEY or COMPARE_VALUE.
*/
#define BINARY_INSERT_DEFINE(INPUT, LIST, TYPECONT, COMPARE, COMPARISON, COMPTYPE) \
do {\
var/list/__BIN_LIST = LIST;\
var/__BIN_CTTL = length(__BIN_LIST);\
if(!__BIN_CTTL) {\
__BIN_LIST += INPUT;\
} else {\
var/__BIN_LEFT = 1;\
var/__BIN_RIGHT = __BIN_CTTL;\
var/__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\
##TYPECONT(__BIN_ITEM);\
while(__BIN_LEFT < __BIN_RIGHT) {\
__BIN_ITEM = COMPTYPE;\
if(##COMPARISON(__BIN_ITEM) <= ##COMPARISON(COMPARE)) {\
__BIN_LEFT = __BIN_MID + 1;\
} else {\
__BIN_RIGHT = __BIN_MID;\
};\
__BIN_MID = (__BIN_LEFT + __BIN_RIGHT) >> 1;\
};\
__BIN_ITEM = COMPTYPE;\
__BIN_MID = ##COMPARISON(__BIN_ITEM) > ##COMPARISON(COMPARE) ? __BIN_MID : __BIN_MID + 1;\
__BIN_LIST.Insert(__BIN_MID, INPUT);\
};\
} while(FALSE)
///Returns a list in plain english as a string
/proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" )
var/total = length(input)
+74 -18
View File
@@ -2,17 +2,28 @@ SUBSYSTEM_DEF(movement)
name = "Movement Loops"
flags = SS_NO_INIT|SS_BACKGROUND|SS_TICKER
wait = 1 //Fire each tick
///The list of datums we're processing
var/list/processing = list()
///Used to make pausing possible
var/list/currentrun = list()
/*
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)
msg = "P:[length(processing)]"
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()
@@ -22,30 +33,75 @@ SUBSYSTEM_DEF(movement)
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]"]
processing = old_version.processing
currentrun = old_version.currentrun
buckets = old_version.buckets
sorted_buckets = old_version.sorted_buckets
/datum/controller/subsystem/movement/fire(resumed)
if(!resumed)
canonical_time = world.time
currentrun = processing.Copy()
var/list/running = currentrun //Cache for... you've heard this before
while(running.len)
var/datum/move_loop/loop = running[running.len]
running.len--
if(loop.timer <= canonical_time)
loop.process() //This shouldn't get nulls, if it does, runtime
if (MC_TICK_CHECK)
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--
loop.process() //This shouldn't get nulls, if it does, runtime
if(!QDELETED(loop)) //Re-Insert the loop
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)
if(!index)
for(var/i in 1 to length(sorted_buckets))
var/list/bucket_info = sorted_buckets[i]
if(bucket_info[MOVEMENT_BUCKET_TIME] != bucket_time)
continue
index = i
break
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)
var/target_time = loop.timer
var/string_time = "[target_time]"
if(buckets[string_time])
buckets[string_time] += loop
else
buckets[string_time] = list(loop)
// This makes buckets and sorted buckets point to the same place, allowing for quicker inserts
var/list/new_bucket = list(list(target_time, buckets[string_time]))
BINARY_INSERT_DEFINE(new_bucket, sorted_buckets, SORT_VAR_NO_TYPE, list(target_time), SORT_FIRST_INDEX, COMPARE_KEY)
/datum/controller/subsystem/movement/proc/dequeue_loop(datum/move_loop/loop)
var/list/our_entries = buckets["[loop.timer]"]
our_entries -= loop
if(!our_entries)
smash_bucket(bucket_time = loop.timer) // We can't pass an index in for context because we don't know our position
/datum/controller/subsystem/movement/proc/add_loop(datum/move_loop/add)
processing += add
add.start_loop()
if(QDELETED(add))
return
queue_loop(add)
/datum/controller/subsystem/movement/proc/remove_loop(datum/move_loop/remove)
processing -= remove
currentrun -= remove
dequeue_loop(remove)
remove.stop_loop()
@@ -20,6 +20,8 @@
///Delay between each move in deci-seconds
var/delay = 1
///The next time we should process
///Used primarially as a hint to be reasoned about by our [controller], and as the id of our bucket
///Should not be modified directly outside of [start_loop]
var/timer = 0
///Is this loop running or not
var/running = FALSE
@@ -71,10 +73,9 @@
extra_info = null
return ..()
///Exists as a helper so outside code can modify delay while also modifying timer
///Exists as a helper so outside code can modify delay in a sane way
/datum/move_loop/proc/set_delay(new_delay)
delay = max(new_delay, world.tick_lag)
timer = world.time + delay
/datum/move_loop/process()
var/old_delay = delay //The signal can sometimes change delay
@@ -93,7 +94,6 @@
SEND_SIGNAL(src, COMSIG_MOVELOOP_POSTPROCESS, success, delay * visual_delay)
timer = world.time + delay
if(QDELETED(src) || !success) //Can happen
return
@@ -399,7 +399,6 @@
/datum/move_loop/has_target/dist_bound/move()
if(!check_dist()) //If we're too close don't do the move
timer = world.time //Make sure to move as soon as possible
return FALSE
return TRUE
@@ -0,0 +1,6 @@
/// Very rare subsystem, provides any active singularities with the timings and seclusion they need to succeed
PROCESSING_SUBSYSTEM_DEF(singuloprocess)
name = "Singularity"
wait = 0.5
priority = FIRE_PRIORITY_DEFAULT
stat_tag = "SIN"
+53 -29
View File
@@ -40,7 +40,11 @@
/// If specified, the singularity will slowly move to this target
var/atom/target
/// List of turfs we have yet to consume, but need to
var/list/turf/turfs_to_consume = list()
/// The time that has elapsed since our last move/eat call
var/time_since_last_eat
/datum/component/singularity/Initialize(
bsa_targetable = TRUE,
@@ -65,7 +69,7 @@
src.singularity_size = singularity_size
/datum/component/singularity/RegisterWithParent()
START_PROCESSING(SSdcs, src)
START_PROCESSING(SSsinguloprocess, src)
// The singularity stops drifting for no man!
parent.AddElement(/datum/element/forced_gravity, FALSE)
@@ -104,7 +108,7 @@
return ..()
/datum/component/singularity/UnregisterFromParent()
STOP_PROCESSING(SSdcs, src)
STOP_PROCESSING(SSsinguloprocess, src)
parent.RemoveElement(/datum/element/bsa_blocker)
parent.RemoveElement(/datum/element/forced_gravity)
@@ -122,9 +126,17 @@
))
/datum/component/singularity/process(delta_time)
if (roaming)
move()
eat()
// We want to move and eat once a second, but want to process our turf consume queue the rest of the time
time_since_last_eat += delta_time
digest()
if(TICK_CHECK)
return
if(time_since_last_eat > 1) // Delta time is in seconds for "reasons"
time_since_last_eat = 0
if (roaming)
move()
eat()
digest() // Try and process as much as you can with the time we have left
/datum/component/singularity/proc/block_blob()
SIGNAL_HANDLER
@@ -166,38 +178,50 @@
thing.singularity_act(singularity_size, parent)
/datum/component/singularity/proc/eat()
turfs_to_consume |= spiral_range_turfs(grav_pull, parent)
/datum/component/singularity/proc/digest()
var/atom/atom_parent = parent
for (var/_tile in spiral_range_turfs(grav_pull, parent))
var/turf/tile = _tile
if (!tile || !isturf(atom_parent.loc))
if(!isturf(atom_parent.loc))
return
// We use a static index for this to prevent infinite runtimes.
// Maybe a might overengineered, but let's be safe yes?
var/static/cached_index = 0
if(cached_index)
var/old_index = cached_index
cached_index = 0 // Prevents infinite Cut() runtimes. Sorry MSO
turfs_to_consume.Cut(1, old_index + 1)
for (cached_index in 1 to length(turfs_to_consume))
var/turf/tile = turfs_to_consume[cached_index]
var/dist_to_tile = get_dist(tile, parent)
if(grav_pull < dist_to_tile) //If we've exited the singulo's range already, just skip us
continue
if (get_dist(tile, parent) > consume_range)
tile.singularity_pull(src, singularity_size)
else
var/in_consume_range = (dist_to_tile <= consume_range)
if (in_consume_range)
consume(src, tile)
else
tile.singularity_pull(parent, singularity_size)
for (var/_thing in tile)
var/atom/thing = _thing
// Because we can possibly yield in the middle of iteration, let's make sure what were looking at is still there
// Without this, you get "Qdeleted thing being thrown around"
if (QDELETED(thing))
for (var/atom/movable/thing as anything in tile)
if(thing == parent)
continue
if (in_consume_range)
consume(src, thing)
else
thing.singularity_pull(parent, singularity_size)
if (isturf(atom_parent.loc) && thing != parent)
var/atom/movable/movable_thing = thing
if (get_dist(movable_thing, parent) > consume_range)
movable_thing.singularity_pull(parent, singularity_size)
//SKYRAT EDIT CHANGE
else if(istype(movable_thing, /obj/machinery/field/containment))
if(singularity_size > STAGE_FOUR)
consume(src, movable_thing)
// SKYRAT EDIT END
else
consume(src, movable_thing)
if(TICK_CHECK) //Yes this means the singulo can eat all of its host subsystem's cpu, but like it's the singulo, and it was gonna do that anyway
turfs_to_consume.Cut(1, cached_index + 1)
cached_index = 0
return
CHECK_TICK
turfs_to_consume.Cut()
cached_index = 0
/datum/component/singularity/proc/move()
var/drifting_dir = pick(GLOB.alldirs - last_failed_movement)
@@ -38,6 +38,8 @@
var/move_self = TRUE
///If the singularity has eaten a supermatter shard and can go to stage six
var/consumed_supermatter = FALSE
/// How long it's been since the singulo last acted, in seconds
var/time_since_act = 0
flags_1 = SUPERMATTER_IGNORES_1
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF
@@ -53,7 +55,7 @@
energy = starting_energy
//SKYRAT EDIT END
START_PROCESSING(SSobj, src)
START_PROCESSING(SSsinguloprocess, src)
SSpoints_of_interest.make_point_of_interest(src)
var/datum/component/singularity/new_component = AddComponent(
@@ -83,7 +85,7 @@
/obj/singularity/Destroy()
STOP_PROCESSING(SSobj, src)
STOP_PROCESSING(SSsinguloprocess, src)
return ..()
/obj/singularity/attack_tk(mob/user)
@@ -153,6 +155,10 @@
energy -= round(((energy + 1) / 4), 1)
/obj/singularity/process(delta_time)
time_since_act += delta_time
if(time_since_act < 2)
return
time_since_act = 0
if(current_size >= STAGE_TWO)
if(prob(event_chance))
event()
+1
View File
@@ -564,6 +564,7 @@
#include "code\controllers\subsystem\processing\projectiles.dm"
#include "code\controllers\subsystem\processing\quirks.dm"
#include "code\controllers\subsystem\processing\reagents.dm"
#include "code\controllers\subsystem\processing\singulo.dm"
#include "code\controllers\subsystem\processing\station.dm"
#include "code\controllers\subsystem\processing\tramprocess.dm"
#include "code\controllers\subsystem\processing\wet_floors.dm"