Sleeping negative delay
I wanted to take a shot at continuing the work by @Cyberboss in #1539
After making adjustments to fit the latest codebase, I also made additional changes:
- Changed
forto accept expressions OR certain statements as its incrementor. (apparently you can havefor(var/i=0, i<1, sleep(0)). neat!) - Put back in the background attribute check for putting in the background sleep (which is now an opcode)
- Changes proc/delay scheduling to track number of negative sleep attempts per thread/proc to approximate sharing in byond based on some very basic testing (see below)
- Changed the YieldOrder unit test to match the behavior of this byond script (based on @Cyberboss 's version ) :
#define MODE_INLINE 0 // spawn
#define MODE_BACKGROUND 1 // set waitfor = FALSE + sleep
#define MODE_RAND 2 // random seeded
#define TestSleep(delay, expect) if(mode == MODE_INLINE || (mode == MODE_RAND && prob(50))){ spawn(##delay) { ExpectOrder(##expect); } } else { BackgroundSleep(##delay, ##expect); }
#define ExpectOrder(n) ASSERT(++counter == ##n)
/world/New()
world.log << "Inline:"
TestSequence(MODE_INLINE)
world.log << "Background:"
TestSequence(MODE_BACKGROUND)
rand_seed(22475)
for(var/i in 1 to 10000)
world.log << "Rand-[i]:"
TestSequence(MODE_RAND)
/proc/BackgroundSleep(delay, expect)
set waitfor = FALSE
sleep(delay)
world.log << "Expect: [expect]"
ExpectOrder(expect)
var/counter
/proc/TestSequence(mode)
counter = 0
var/start_tick = world.time
TestSleep(0, 2)
ExpectOrder(1)
sleep(0)
ExpectOrder(3)
TestSleep(-1, 4)
ExpectOrder(5)
TestSleep(0, 11)
sleep(-1)
ExpectOrder(6)
TestSleep(-1, 7)
ExpectOrder(8)
sleep(-1)
ExpectOrder(9)
TestSleep(1, 13)
sleep(-1)
ExpectOrder(10)
sleep(0)
ExpectOrder(12)
ASSERT(world.time == start_tick)
sleep(1)
ExpectOrder(14)
(^ Which breaks in current OD, running as a server)
I based the 10/20 sleep scheduling proc/thread thresholds on this test:
proc/background_proc_a()
set background = TRUE
for(var/o = 1 to 200)
//sleep(-1)
world.log << "a at [o]"
world.log << "a done"
proc/background_proc_b()
set background = TRUE
for(var/o = 1 to 200)
//sleep(-1)
world.log << "b at [o]"
if (o % 5 == 0)
background_proc_c()
world.log << "b done"
proc/background_proc_c()
set background = TRUE
for(var/o = 1 to 5)
//sleep(-1)
world.log << "c at [o]"
world.log << "c done"
/world/New()
spawn() background_proc_a()
spawn() background_proc_b()
Which produces results like this in Byond:
a at 1
a at 2
a at 3
a at 4
a at 5
a at 6
a at 7
a at 8
a at 9
a at 10
b at 1
b at 2
b at 3
b at 4
b at 5
c at 1
c at 2
c at 3
c at 4
c at 5
c done
b at 6
b at 7
b at 8
b at 9
b at 10
c at 1
c at 2
c at 3
c at 4
c at 5
c done
a at 11
a at 12
a at 13
a at 14
a at 15
a at 16
a at 17
a at 18
a at 19
a at 20
b at 11
b at 12
b at 13
b at 14
b at 15
c at 1
c at 2
c at 3
c at 4
c at 5
c done
b at 16
...
b at 195
c at 1
c at 2
c at 3
c at 4
c at 5
c done
b at 196
b at 197
b at 198
b at 199
b at 200
c at 1
c at 2
c at 3
c at 4
c at 5
c done
a done
b done
And with this patch you get:
[INFO] world.log: a at 1
[INFO] world.log: a at 2
[INFO] world.log: a at 3
[INFO] world.log: a at 4
[INFO] world.log: a at 5
[INFO] world.log: a at 6
[INFO] world.log: a at 7
[INFO] world.log: a at 8
[INFO] world.log: a at 9
[INFO] world.log: a at 10
[INFO] world.log: b at 1
[INFO] world.log: b at 2
[INFO] world.log: b at 3
[INFO] world.log: b at 4
[INFO] world.log: b at 5
[INFO] world.log: c at 1
[INFO] world.log: c at 2
[INFO] world.log: c at 3
[INFO] world.log: c at 4
[INFO] world.log: c at 5
[INFO] world.log: c done
[INFO] world.log: b at 6
[INFO] world.log: b at 7
[INFO] world.log: b at 8
[INFO] world.log: b at 9
[INFO] world.log: b at 10
[INFO] world.log: c at 1
[INFO] world.log: c at 2
[INFO] world.log: c at 3
[INFO] world.log: c at 4
[INFO] world.log: c at 5
[INFO] world.log: a at 11
[INFO] world.log: a at 12
[INFO] world.log: a at 13
[INFO] world.log: a at 14
[INFO] world.log: a at 15
[INFO] world.log: a at 16
[INFO] world.log: a at 17
[INFO] world.log: a at 18
[INFO] world.log: a at 19
[INFO] world.log: a at 20
[INFO] world.log: c done
[INFO] world.log: b at 11
[INFO] world.log: b at 12
[INFO] world.log: b at 13
[INFO] world.log: b at 14
[INFO] world.log: b at 15
[INFO] world.log: c at 1
[INFO] world.log: c at 2
[INFO] world.log: c at 3
[INFO] world.log: c at 4
[INFO] world.log: c at 5
[INFO] world.log: c done
[INFO] world.log: b at 16
...
[INFO] world.log: b at 195
[INFO] world.log: c at 1
[INFO] world.log: c at 2
[INFO] world.log: c at 3
[INFO] world.log: c at 4
[INFO] world.log: c at 5
[INFO] world.log: c done
[INFO] world.log: b at 196
[INFO] world.log: b at 197
[INFO] world.log: b at 198
[INFO] world.log: b at 199
[INFO] world.log: b at 200
[INFO] world.log: c at 1
[INFO] world.log: c at 2
[INFO] world.log: c at 3
[INFO] world.log: c at 4
[INFO] world.log: c at 5
[INFO] world.log: c done
[INFO] world.log: b done
Which is a bit different around when the c loop ends the 2nd time in a row, but is overall more similar compared to the version on master:
[INFO] world.log: a at 1
[INFO] world.log: b at 1
[INFO] world.log: a at 2
[INFO] world.log: b at 2
[INFO] world.log: a at 3
[INFO] world.log: b at 3
[INFO] world.log: a at 4
[INFO] world.log: b at 4
[INFO] world.log: a at 5
[INFO] world.log: b at 5
[INFO] world.log: c at 1
[INFO] world.log: a at 6
[INFO] world.log: c at 2
[INFO] world.log: a at 7
[INFO] world.log: c at 3
[INFO] world.log: a at 8
[INFO] world.log: c at 4
[INFO] world.log: a at 9
[INFO] world.log: c at 5
[INFO] world.log: a at 10
[INFO] world.log: c done
[INFO] world.log: a at 11
[INFO] world.log: b at 6
[INFO] world.log: a at 12
[INFO] world.log: b at 7
[INFO] world.log: a at 13
[INFO] world.log: b at 8
[INFO] world.log: a at 14
[INFO] world.log: b at 9
[INFO] world.log: a at 15
[INFO] world.log: b at 10
[INFO] world.log: c at 1
[INFO] world.log: a at 16
[INFO] world.log: c at 2
[INFO] world.log: a at 17
[INFO] world.log: c at 3
[INFO] world.log: a at 18
[INFO] world.log: c at 4
[INFO] world.log: a at 19
[INFO] world.log: c at 5
[INFO] world.log: a at 20
[INFO] world.log: c done
[INFO] world.log: a at 21
[INFO] world.log: b at 11
...
[INFO] world.log: b at 195
[INFO] world.log: c at 1
[INFO] world.log: c at 2
[INFO] world.log: c at 3
[INFO] world.log: c at 4
[INFO] world.log: c at 5
[INFO] world.log: c done
[INFO] world.log: b at 196
[INFO] world.log: b at 197
[INFO] world.log: b at 198
[INFO] world.log: b at 199
[INFO] world.log: b at 200
[INFO] world.log: c at 1
[INFO] world.log: c at 2
[INFO] world.log: c at 3
[INFO] world.log: c at 4
[INFO] world.log: c at 5
[INFO] world.log: c done
[INFO] world.log: b done
I'm not terribly happy with using hashmaps to lookup the thread/proc info by thread/proc id but maybe it's better than storing the negative sleep counts in the procs and thread classes. As for speed, I wasn't able to reproduce the slow startup as described in the OP of #1262 but the idea is to close that issue. Maybe it helps Goon?