OpenDream icon indicating copy to clipboard operation
OpenDream copied to clipboard

Sleeping negative delay

Open Ruzihm opened this issue 2 months ago • 1 comments

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 for to accept expressions OR certain statements as its incrementor. (apparently you can have for(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?

Ruzihm avatar Dec 20 '25 08:12 Ruzihm