Skip to content

Conversation

@Ruzihm
Copy link
Contributor

@Ruzihm Ruzihm commented Dec 20, 2025

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?

Cyberboss and others added 22 commits November 26, 2023 21:21
- Remove `sleep` from DMStandard.
- Two new opcodes Sleep and BackgroundSleep (which is just `sleep -1`, much faster than popping a `float` off for background sleeps).
- Create new lightweight async proc state for sleeping.
- Compiler/runtime adjustments to handle new opcodes.
…ive-delay sleeps are done in the same thread
@boring-cyborg boring-cyborg bot added Compiler Involves the OpenDream compiler Runtime Involves the OpenDream server/runtime labels Dec 20, 2025
Copy link

@github-advanced-security github-advanced-security bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InspectCode found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@Ruzihm Ruzihm marked this pull request as draft December 20, 2025 08:07
@github-actions github-actions bot added size/XL and removed size/L labels Dec 21, 2025
@ZeWaka
Copy link
Collaborator

ZeWaka commented Dec 21, 2025

Haven't tested, but goon definitely uses a lot of background/negative/0 sleeps etc if anyone needs a actual codebase testbench

@Ruzihm Ruzihm marked this pull request as ready for review December 21, 2025 06:06
@Ruzihm
Copy link
Contributor Author

Ruzihm commented Dec 25, 2025

Tested with this code:

proc/background_proc_a()
	set background = TRUE
	var/t = world.timeofday
	var/x = 0
	for(var/o = 1 to 9900000)
		x = o
	var/elapsed = world.timeofday - t
	world.log << "a done after time: [elapsed]"
	return x

proc/background_proc_b()
	set background = TRUE
	var/t = world.timeofday
	var/x = 0
	for(var/o = 1 to 99000)
		x = o
		if (o % 5 == 0)
			background_proc_c()
	var/elapsed = world.timeofday - t
	world.log << "b done after time: [elapsed]"
	return x

proc/background_proc_c()
	set background = TRUE
	var/t = world.timeofday
	var/x = 0
	for(var/o = 1 to 500)
		x = o
	var/elapsed = world.timeofday - t
	return x

/world/New()
	spawn() background_proc_a()
	spawn() background_proc_b()

Byond:

Welcome BYOND! (5.0 Public Version 516.1658)
a done after time: 6
b done after time: 5

OD master:

[INFO] world.log: a done after time: 65
[INFO] world.log: b done after time: 65

OD with PR:

[INFO] world.log: a done after time: 19
[INFO] world.log: b done after time: 19

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Compiler Involves the OpenDream compiler Runtime Involves the OpenDream server/runtime size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants