Merge branch 'master' of https://github.com/PolarisSS13/Polaris into polaris-sync-20170609

# Conflicts:
#	code/_macros.dm
#	code/controllers/master_controller.dm
#	vorestation.dme
This commit is contained in:
Leshana
2017-06-09 22:54:16 -04:00
42 changed files with 1914 additions and 82 deletions

View File

@@ -599,3 +599,66 @@ proc/dd_sortedTextList(list/incoming)
for(var/path in subtypesof(prototype))
L += new path()
return L
//creates every subtype of prototype (excluding prototype) and adds it to list L as a type/instance pair.
//if no list/L is provided, one is created.
/proc/init_subtypes_assoc(prototype, list/L)
if(!istype(L)) L = list()
for(var/path in subtypesof(prototype))
L[path] = new path()
return L
//Move a single element from position fromIndex within a list, to position toIndex
//All elements in the range [1,toIndex) before the move will be before the pivot afterwards
//All elements in the range [toIndex, L.len+1) before the move will be after the pivot afterwards
//In other words, it's as if the range [fromIndex,toIndex) have been rotated using a <<< operation common to other languages.
//fromIndex and toIndex must be in the range [1,L.len+1]
//This will preserve associations ~Carnie
/proc/moveElement(list/L, fromIndex, toIndex)
if(fromIndex == toIndex || fromIndex+1 == toIndex) //no need to move
return
if(fromIndex > toIndex)
++fromIndex //since a null will be inserted before fromIndex, the index needs to be nudged right by one
L.Insert(toIndex, null)
L.Swap(fromIndex, toIndex)
L.Cut(fromIndex, fromIndex+1)
//Move elements [fromIndex,fromIndex+len) to [toIndex-len, toIndex)
//Same as moveElement but for ranges of elements
//This will preserve associations ~Carnie
/proc/moveRange(list/L, fromIndex, toIndex, len=1)
var/distance = abs(toIndex - fromIndex)
if(len >= distance) //there are more elements to be moved than the distance to be moved. Therefore the same result can be achieved (with fewer operations) by moving elements between where we are and where we are going. The result being, our range we are moving is shifted left or right by dist elements
if(fromIndex <= toIndex)
return //no need to move
fromIndex += len //we want to shift left instead of right
for(var/i=0, i<distance, ++i)
L.Insert(fromIndex, null)
L.Swap(fromIndex, toIndex)
L.Cut(toIndex, toIndex+1)
else
if(fromIndex > toIndex)
fromIndex += len
for(var/i=0, i<len, ++i)
L.Insert(toIndex, null)
L.Swap(fromIndex, toIndex)
L.Cut(fromIndex, fromIndex+1)
//replaces reverseList ~Carnie
/proc/reverseRange(list/L, start=1, end=0)
if(L.len)
start = start % L.len
end = end % (L.len+1)
if(start <= 0)
start += L.len
if(end <= 0)
end += L.len + 1
--end
while(start < end)
L.Swap(start++,end--)
return L

View File

@@ -0,0 +1,17 @@
//TimSort interface
/proc/sortTim(list/L, cmp=/proc/cmp_numeric_asc, associative, fromIndex=1, toIndex=0)
if(L && L.len >= 2)
fromIndex = fromIndex % L.len
toIndex = toIndex % (L.len+1)
if(fromIndex <= 0)
fromIndex += L.len
if(toIndex <= 0)
toIndex += L.len + 1
sortInstance.L = L
sortInstance.cmp = cmp
sortInstance.associative = associative
sortInstance.timSort(fromIndex, toIndex)
return L

View File

@@ -0,0 +1,656 @@
//These are macros used to reduce on proc calls
#define fetchElement(L, i) (associative) ? L[L[i]] : L[i]
//Minimum sized sequence that will be merged. Anything smaller than this will use binary-insertion sort.
//Should be a power of 2
#define MIN_MERGE 32
//When we get into galloping mode, we stay there until both runs win less often than MIN_GALLOP consecutive times.
#define MIN_GALLOP 7
//This is a global instance to allow much of this code to be reused. The interfaces are kept separately
var/datum/sortInstance/sortInstance = new()
/datum/sortInstance
//The array being sorted.
var/list/L
//The comparator proc-reference
var/cmp = /proc/cmp_numeric_asc
//whether we are sorting list keys (0: L[i]) or associated values (1: L[L[i]])
var/associative = 0
//This controls when we get *into* galloping mode. It is initialized to MIN_GALLOP.
//The mergeLo and mergeHi methods nudge it higher for random data, and lower for highly structured data.
var/minGallop = MIN_GALLOP
//Stores information regarding runs yet to be merged.
//Run i starts at runBase[i] and extends for runLen[i] elements.
//runBase[i] + runLen[i] == runBase[i+1]
//var/stackSize
var/list/runBases = list()
var/list/runLens = list()
/datum/sortInstance/proc/timSort(start, end)
runBases.Cut()
runLens.Cut()
var/remaining = end - start
//If array is small, do a 'mini-TimSort' with no merges
if(remaining < MIN_MERGE)
var/initRunLen = countRunAndMakeAscending(start, end)
binarySort(start, end, start+initRunLen)
return
//March over the array finding natural runs
//Extend any short natural runs to runs of length minRun
var/minRun = minRunLength(remaining)
do
//identify next run
var/runLen = countRunAndMakeAscending(start, end)
//if run is short, extend to min(minRun, remaining)
if(runLen < minRun)
var/force = (remaining <= minRun) ? remaining : minRun
binarySort(start, start+force, start+runLen)
runLen = force
//add data about run to queue
runBases.Add(start)
runLens.Add(runLen)
//maybe merge
mergeCollapse()
//Advance to find next run
start += runLen
remaining -= runLen
while(remaining > 0)
//Merge all remaining runs to complete sort
//ASSERT(start == end)
mergeForceCollapse();
//ASSERT(runBases.len == 1)
//reset minGallop, for successive calls
minGallop = MIN_GALLOP
return L
/*
Sorts the specified portion of the specified array using a binary
insertion sort. This is the best method for sorting small numbers
of elements. It requires O(n log n) compares, but O(n^2) data
movement (worst case).
If the initial part of the specified range is already sorted,
this method can take advantage of it: the method assumes that the
elements in range [lo,start) are already sorted
lo the index of the first element in the range to be sorted
hi the index after the last element in the range to be sorted
start the index of the first element in the range that is not already known to be sorted
*/
/datum/sortInstance/proc/binarySort(lo, hi, start)
//ASSERT(lo <= start && start <= hi)
if(start <= lo)
start = lo + 1
for(,start < hi, ++start)
var/pivot = fetchElement(L,start)
//set left and right to the index where pivot belongs
var/left = lo
var/right = start
//ASSERT(left <= right)
//[lo, left) elements <= pivot < [right, start) elements
//in other words, find where the pivot element should go using bisection search
while(left < right)
var/mid = (left + right) >> 1 //round((left+right)/2)
if(call(cmp)(fetchElement(L,mid), pivot) > 0)
right = mid
else
left = mid+1
//ASSERT(left == right)
moveElement(L, start, left) //move pivot element to correct location in the sorted range
/*
Returns the length of the run beginning at the specified position and reverses the run if it is back-to-front
A run is the longest ascending sequence with:
a[lo] <= a[lo + 1] <= a[lo + 2] <= ...
or the longest descending sequence with:
a[lo] > a[lo + 1] > a[lo + 2] > ...
For its intended use in a stable mergesort, the strictness of the
definition of "descending" is needed so that the call can safely
reverse a descending sequence without violating stability.
*/
/datum/sortInstance/proc/countRunAndMakeAscending(lo, hi)
//ASSERT(lo < hi)
var/runHi = lo + 1
if(runHi >= hi)
return 1
var/last = fetchElement(L,lo)
var/current = fetchElement(L,runHi++)
if(call(cmp)(current, last) < 0)
while(runHi < hi)
last = current
current = fetchElement(L,runHi)
if(call(cmp)(current, last) >= 0)
break
++runHi
reverseRange(L, lo, runHi)
else
while(runHi < hi)
last = current
current = fetchElement(L,runHi)
if(call(cmp)(current, last) < 0)
break
++runHi
return runHi - lo
//Returns the minimum acceptable run length for an array of the specified length.
//Natural runs shorter than this will be extended with binarySort
/datum/sortInstance/proc/minRunLength(n)
//ASSERT(n >= 0)
var/r = 0 //becomes 1 if any bits are shifted off
while(n >= MIN_MERGE)
r |= (n & 1)
n >>= 1
return n + r
//Examines the stack of runs waiting to be merged and merges adjacent runs until the stack invariants are reestablished:
// runLen[i-3] > runLen[i-2] + runLen[i-1]
// runLen[i-2] > runLen[i-1]
//This method is called each time a new run is pushed onto the stack.
//So the invariants are guaranteed to hold for i<stackSize upon entry to the method
/datum/sortInstance/proc/mergeCollapse()
while(runBases.len >= 2)
var/n = runBases.len - 1
if(n > 1 && runLens[n-1] <= runLens[n] + runLens[n+1])
if(runLens[n-1] < runLens[n+1])
--n
mergeAt(n)
else if(runLens[n] <= runLens[n+1])
mergeAt(n)
else
break //Invariant is established
//Merges all runs on the stack until only one remains.
//Called only once, to finalise the sort
/datum/sortInstance/proc/mergeForceCollapse()
while(runBases.len >= 2)
var/n = runBases.len - 1
if(n > 1 && runLens[n-1] < runLens[n+1])
--n
mergeAt(n)
//Merges the two consecutive runs at stack indices i and i+1
//Run i must be the penultimate or antepenultimate run on the stack
//In other words, i must be equal to stackSize-2 or stackSize-3
/datum/sortInstance/proc/mergeAt(i)
//ASSERT(runBases.len >= 2)
//ASSERT(i >= 1)
//ASSERT(i == runBases.len - 1 || i == runBases.len - 2)
var/base1 = runBases[i]
var/base2 = runBases[i+1]
var/len1 = runLens[i]
var/len2 = runLens[i+1]
//ASSERT(len1 > 0 && len2 > 0)
//ASSERT(base1 + len1 == base2)
//Record the legth of the combined runs. If i is the 3rd last run now, also slide over the last run
//(which isn't involved in this merge). The current run (i+1) goes away in any case.
runLens[i] += runLens[i+1]
runLens.Cut(i+1, i+2)
runBases.Cut(i+1, i+2)
//Find where the first element of run2 goes in run1.
//Prior elements in run1 can be ignored (because they're already in place)
var/k = gallopRight(fetchElement(L,base2), base1, len1, 0)
//ASSERT(k >= 0)
base1 += k
len1 -= k
if(len1 == 0)
return
//Find where the last element of run1 goes in run2.
//Subsequent elements in run2 can be ignored (because they're already in place)
len2 = gallopLeft(fetchElement(L,base1 + len1 - 1), base2, len2, len2-1)
//ASSERT(len2 >= 0)
if(len2 == 0)
return
//Merge remaining runs, using tmp array with min(len1, len2) elements
if(len1 <= len2)
mergeLo(base1, len1, base2, len2)
else
mergeHi(base1, len1, base2, len2)
/*
Locates the position to insert key within the specified sorted range
If the range contains elements equal to key, this will return the index of the LEFTMOST of those elements
key the element to be inserted into the sorted range
base the index of the first element of the sorted range
len the length of the sorted range, must be greater than 0
hint the offset from base at which to begin the search, such that 0 <= hint < len; i.e. base <= hint < base+hint
Returns the index at which to insert element 'key'
*/
/datum/sortInstance/proc/gallopLeft(key, base, len, hint)
//ASSERT(len > 0 && hint >= 0 && hint < len)
var/lastOffset = 0
var/offset = 1
if(call(cmp)(key, fetchElement(L,base+hint)) > 0)
var/maxOffset = len - hint
while(offset < maxOffset && call(cmp)(key, fetchElement(L,base+hint+offset)) > 0)
lastOffset = offset
offset = (offset << 1) + 1
if(offset > maxOffset)
offset = maxOffset
lastOffset += hint
offset += hint
else
var/maxOffset = hint + 1
while(offset < maxOffset && call(cmp)(key, fetchElement(L,base+hint-offset)) <= 0)
lastOffset = offset
offset = (offset << 1) + 1
if(offset > maxOffset)
offset = maxOffset
var/temp = lastOffset
lastOffset = hint - offset
offset = hint - temp
//ASSERT(-1 <= lastOffset && lastOffset < offset && offset <= len)
//Now L[base+lastOffset] < key <= L[base+offset], so key belongs somewhere to the right of lastOffset but no farther than
//offset. Do a binary search with invariant L[base+lastOffset-1] < key <= L[base+offset]
++lastOffset
while(lastOffset < offset)
var/m = lastOffset + ((offset - lastOffset) >> 1)
if(call(cmp)(key, fetchElement(L,base+m)) > 0)
lastOffset = m + 1
else
offset = m
//ASSERT(lastOffset == offset)
return offset
/**
* Like gallopLeft, except that if the range contains an element equal to
* key, gallopRight returns the index after the rightmost equal element.
*
* @param key the key whose insertion point to search for
* @param a the array in which to search
* @param base the index of the first element in the range
* @param len the length of the range; must be > 0
* @param hint the index at which to begin the search, 0 <= hint < n.
* The closer hint is to the result, the faster this method will run.
* @param c the comparator used to order the range, and to search
* @return the int k, 0 <= k <= n such that a[b + k - 1] <= key < a[b + k]
*/
/datum/sortInstance/proc/gallopRight(key, base, len, hint)
//ASSERT(len > 0 && hint >= 0 && hint < len)
var/offset = 1
var/lastOffset = 0
if(call(cmp)(key, fetchElement(L,base+hint)) < 0) //key <= L[base+hint]
var/maxOffset = hint + 1 //therefore we want to insert somewhere in the range [base,base+hint] = [base+,base+(hint+1))
while(offset < maxOffset && call(cmp)(key, fetchElement(L,base+hint-offset)) < 0) //we are iterating backwards
lastOffset = offset
offset = (offset << 1) + 1 //1 3 7 15
//if(offset <= 0) //int overflow, not an issue here since we are using floats
// offset = maxOffset
if(offset > maxOffset)
offset = maxOffset
var/temp = lastOffset
lastOffset = hint - offset
offset = hint - temp
else //key > L[base+hint]
var/maxOffset = len - hint //therefore we want to insert somewhere in the range (base+hint,base+len) = [base+hint+1, base+hint+(len-hint))
while(offset < maxOffset && call(cmp)(key, fetchElement(L,base+hint+offset)) >= 0)
lastOffset = offset
offset = (offset << 1) + 1
//if(offset <= 0) //int overflow, not an issue here since we are using floats
// offset = maxOffset
if(offset > maxOffset)
offset = maxOffset
lastOffset += hint
offset += hint
//ASSERT(-1 <= lastOffset && lastOffset < offset && offset <= len)
++lastOffset
while(lastOffset < offset)
var/m = lastOffset + ((offset - lastOffset) >> 1)
if(call(cmp)(key, fetchElement(L,base+m)) < 0) //key <= L[base+m]
offset = m
else //key > L[base+m]
lastOffset = m + 1
//ASSERT(lastOffset == offset)
return offset
//Merges two adjacent runs in-place in a stable fashion.
//For performance this method should only be called when len1 <= len2!
/datum/sortInstance/proc/mergeLo(base1, len1, base2, len2)
//ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2)
var/cursor1 = base1
var/cursor2 = base2
//degenerate cases
if(len2 == 1)
moveElement(L, cursor2, cursor1)
return
if(len1 == 1)
moveElement(L, cursor1, cursor2+len2)
return
//Move first element of second run
moveElement(L, cursor2++, cursor1++)
--len2
outer:
while(1)
var/count1 = 0 //# of times in a row that first run won
var/count2 = 0 // " " " " " " second run won
//do the straightfoward thin until one run starts winning consistently
do
//ASSERT(len1 > 1 && len2 > 0)
if(call(cmp)(fetchElement(L,cursor2), fetchElement(L,cursor1)) < 0)
moveElement(L, cursor2++, cursor1++)
--len2
++count2
count1 = 0
if(len2 == 0)
break outer
else
++cursor1
++count1
count2 = 0
if(--len1 == 1)
break outer
while((count1 | count2) < minGallop)
//one run is winning consistently so galloping may provide huge benifits
//so try galloping, until such time as the run is no longer consistently winning
do
//ASSERT(len1 > 1 && len2 > 0)
count1 = gallopRight(fetchElement(L,cursor2), cursor1, len1, 0)
if(count1)
cursor1 += count1
len1 -= count1
if(len1 <= 1)
break outer
moveElement(L, cursor2, cursor1)
++cursor2
++cursor1
if(--len2 == 0)
break outer
count2 = gallopLeft(fetchElement(L,cursor1), cursor2, len2, 0)
if(count2)
moveRange(L, cursor2, cursor1, count2)
cursor2 += count2
cursor1 += count2
len2 -= count2
if(len2 == 0)
break outer
++cursor1
if(--len1 == 1)
break outer
--minGallop
while((count1|count2) > MIN_GALLOP)
if(minGallop < 0)
minGallop = 0
minGallop += 2; // Penalize for leaving gallop mode
if(len1 == 1)
//ASSERT(len2 > 0)
moveElement(L, cursor1, cursor2+len2)
//else
//ASSERT(len2 == 0)
//ASSERT(len1 > 1)
/datum/sortInstance/proc/mergeHi(base1, len1, base2, len2)
//ASSERT(len1 > 0 && len2 > 0 && base1 + len1 == base2)
var/cursor1 = base1 + len1 - 1 //start at end of sublists
var/cursor2 = base2 + len2 - 1
//degenerate cases
if(len2 == 1)
moveElement(L, base2, base1)
return
if(len1 == 1)
moveElement(L, base1, cursor2+1)
return
moveElement(L, cursor1--, cursor2-- + 1)
--len1
outer:
while(1)
var/count1 = 0 //# of times in a row that first run won
var/count2 = 0 // " " " " " " second run won
//do the straightfoward thing until one run starts winning consistently
do
//ASSERT(len1 > 0 && len2 > 1)
if(call(cmp)(fetchElement(L,cursor2), fetchElement(L,cursor1)) < 0)
moveElement(L, cursor1--, cursor2-- + 1)
--len1
++count1
count2 = 0
if(len1 == 0)
break outer
else
--cursor2
--len2
++count2
count1 = 0
if(len2 == 1)
break outer
while((count1 | count2) < minGallop)
//one run is winning consistently so galloping may provide huge benifits
//so try galloping, until such time as the run is no longer consistently winning
do
//ASSERT(len1 > 0 && len2 > 1)
count1 = len1 - gallopRight(fetchElement(L,cursor2), base1, len1, len1-1) //should cursor1 be base1?
if(count1)
cursor1 -= count1
moveRange(L, cursor1+1, cursor2+1, count1) //cursor1+1 == cursor2 by definition
cursor2 -= count1
len1 -= count1
if(len1 == 0)
break outer
--cursor2
if(--len2 == 1)
break outer
count2 = len2 - gallopLeft(fetchElement(L,cursor1), cursor1+1, len2, len2-1)
if(count2)
cursor2 -= count2
len2 -= count2
if(len2 <= 1)
break outer
moveElement(L, cursor1--, cursor2-- + 1)
--len1
if(len1 == 0)
break outer
--minGallop
while((count1|count2) > MIN_GALLOP)
if(minGallop < 0)
minGallop = 0
minGallop += 2 // Penalize for leaving gallop mode
if(len2 == 1)
//ASSERT(len1 > 0)
cursor1 -= len1
moveRange(L, cursor1+1, cursor2+1, len1)
//else
//ASSERT(len1 == 0)
//ASSERT(len2 > 0)
/datum/sortInstance/proc/mergeSort(start, end)
var/remaining = end - start
//If array is small, do an insertion sort
if(remaining < MIN_MERGE)
//var/initRunLen = countRunAndMakeAscending(start, end)
binarySort(start, end, start/*+initRunLen*/)
return
var/minRun = minRunLength(remaining)
do
var/runLen = (remaining <= minRun) ? remaining : minRun
binarySort(start, start+runLen, start)
//add data about run to queue
runBases.Add(start)
runLens.Add(runLen)
//Advance to find next run
start += runLen
remaining -= runLen
while(remaining > 0)
while(runBases.len >= 2)
var/n = runBases.len - 1
if(n > 1 && runLens[n-1] <= runLens[n] + runLens[n+1])
if(runLens[n-1] < runLens[n+1])
--n
mergeAt2(n)
else if(runLens[n] <= runLens[n+1])
mergeAt2(n)
else
break //Invariant is established
while(runBases.len >= 2)
var/n = runBases.len - 1
if(n > 1 && runLens[n-1] < runLens[n+1])
--n
mergeAt2(n)
return L
/datum/sortInstance/proc/mergeAt2(i)
var/cursor1 = runBases[i]
var/cursor2 = runBases[i+1]
var/end1 = cursor1+runLens[i]
var/end2 = cursor2+runLens[i+1]
var/val1 = fetchElement(L,cursor1)
var/val2 = fetchElement(L,cursor2)
while(1)
if(call(cmp)(val1,val2) < 0)
if(++cursor1 >= end1)
break
val1 = fetchElement(L,cursor1)
else
moveElement(L,cursor2,cursor1)
++cursor2
if(++cursor2 >= end2)
break
++end1
++cursor1
//if(++cursor1 >= end1)
// break
val2 = fetchElement(L,cursor2)
//Record the legth of the combined runs. If i is the 3rd last run now, also slide over the last run
//(which isn't involved in this merge). The current run (i+1) goes away in any case.
runLens[i] += runLens[i+1]
runLens.Cut(i+1, i+2)
runBases.Cut(i+1, i+2)
#undef MIN_GALLOP
#undef MIN_MERGE
#undef fetchElement

View File

@@ -0,0 +1,20 @@
//
// Comparators for use with /datum/sortInstance (or wherever you want)
// They should return negative, zero, or positive numbers for a < b, a == b, and a > b respectively.
//
// Sorts numeric ascending
/proc/cmp_numeric_asc(a,b)
return a - b
// Sorts subsystems alphabetically
/proc/cmp_subsystem_display(datum/controller/subsystem/a, datum/controller/subsystem/b)
return sorttext(b.name, a.name)
// Sorts subsystems by init_order
/proc/cmp_subsystem_init(datum/controller/subsystem/a, datum/controller/subsystem/b)
return b.init_order - a.init_order
// Sorts subsystems by priority
/proc/cmp_subsystem_priority(datum/controller/subsystem/a, datum/controller/subsystem/b)
return a.priority - b.priority

View File

@@ -100,4 +100,24 @@ var/round_start_time = 0
/hook/startup/proc/set_roundstart_hour()
roundstart_hour = pick(2,7,12,17)
return 1
return 1
/var/midnight_rollovers = 0
/var/rollovercheck_last_timeofday = 0
/proc/update_midnight_rollover()
if (world.timeofday < rollovercheck_last_timeofday) //TIME IS GOING BACKWARDS!
return midnight_rollovers++
return midnight_rollovers
//Increases delay as the server gets more overloaded,
//as sleeps aren't cheap and sleeping only to wake up and sleep again is wasteful
#define DELTA_CALC max(((max(world.tick_usage, world.cpu) / 100) * max(Master.sleep_delta,1)), 1)
/proc/stoplag()
. = 0
var/i = 1
do
. += round(i*DELTA_CALC)
sleep(i*world.tick_lag*DELTA_CALC)
i *= 2
while (world.tick_usage > min(TICK_LIMIT_TO_RUN, CURRENT_TICKLIMIT))