Folks -
I had an interesting thought today that I'd like to run by you because I think it might just work. I have been thinking for a long time how to make the Squeak VM be "truly event driven" that is invoke it in response to OS or other events instead of having the VM poll. There are lots of good reasons for this starting from not blocking when popping up an OS context menu or standard dialog, over being able to embed the VM into other apps (browser plugin etc), up to properly dealing with suspend/resume. There are various problems that could be dealt with more easily if the VM would be truly event driven.
Today it occurred to me that there might be a relatively simple way to deal with that problem merely by having interpret() run until "there is no more work to do" and return from interpret() when it's all said and done. The trick is that instead of running the idle loop, the VM would determine that it has no more work to do when there is no runnable process, so when it finds that there is no runnable process it would return from interpret saying "my work's done here, there is no more code to run at this point, ask me again when an external event comes in".
The changes would be fairly straight forward: First, nuke the idle loop and allow wakeHighestPriority to return nil when there's no runnable process. Second, have transferTo: do a longjmp to the registered vmExitBuf to leave interpret(). Third, have interpret register the vmExitBuf and wake up the highest priorty process like here:
interpret "install jmpbuf for main interpreter" (self setjmp: vmExitBuf) == 0 ifTrue:[ self checkForInterrupts. "timers etc" "transferTo: longjmps if arg is nil so no need to check" self transferTo: self wakeHighestPriority.
"this is the current interpret() implementation" self internalizeIPandSP. self fetchNextBytecode. [true] whileTrue: [self dispatchOn: currentBytecode in: BytecodeTable].
].
At this point we can write a client loop that effectively looks like:
/* run the interpreter */ while(!done) { /* check for new events */ ioProcessEvents(); /* run processes resulting from the events */ interpret(); }
Now, obviously this is inefficient, we'd want to replace the ioProcessEvents() call with something more elaborate that reacts to the incoming OS events, takes the next scheduled delay into account, checks for socket handles etc. But I'm sure you're getting the idea. Instead of wasting our time in the idleProcess, we just return when there's no more work to do and it's up to the caller to run interpret() as often or as rarely as desired.
I also think that this scheme could be made backwards compatible by ensuring that we never call interpret() recursively. In this case an "old" image with the idle process would run the way it does today, and a "new" image without the idle process would live in the shiny new event driven world and return as needed.
What do you think? Any reasons why this wouldn't work?
Cheers, - Andreas