mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-29 08:08:49 +01:00
[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:
co-authored by
LemonInTheDark
Gandalf
parent
0828007a1d
commit
a7fcb65332
@@ -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...
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
@@ -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()
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user