Andreas, I'll just cc the list in response, since this may be interesting and related to our writing a squeake processor.
All'y'all, I looked into Croquet and there are in fact two schedulers. Of course, Andreas, is the man with the answers. In a SqueakE vat, I can imagine that there could be multiple stacks pending for some event to occur. Is being uninterruptible the important property of an event-loop?
On Wednesday, February 12, 2003, at 06:22 AM, Andreas Raab wrote:
The real-time scheduling of future messages is the concurrency mechanism of choice, I would think. Your scripts make it an event-loop, although I suspect that the TeaProcessScheduler is also an event-loop. The crucial thing about an event loop is... what? Could it be that they are uninterruptible?
True for the script scheduler (within scripts), not true for the Tea scheduler. scripts do not interrupt each other, they have "stepping semantics". The tea scheduler has earliest deadline first policy which means it _can_ interrupt another process if work with an earlier deadline is being scheduled.
I am not sure, but I am thinking that this interruptability breaks the event-loop model.
TeaProcessScheduler has several structures of processes. There is a heap for timeouts (pending), a heap ordered for deadline (to occur), and for lookup, but only by TeaTime, not symbols. How does it deal with symbols and pattern-matching? .. oh, the times are inserted by the MessageSend, so it does have pattern-matching through sending. How does it support an event system? Well, the appropriate MessageSend could occur.
Like I was saying, much of this is still in flux. We are not quite certain yet, how the earliest deadline first scheduling interacts with the scripts. Part of this is the model of time which David develops. IOW, each process runs "in time", and accesses state "as of" a particular time. If that time hasn't come yet it will block (or alternatively perform an eventual computation which may later be aborted due to changes at "some earlier point in time"). Much of this hasn't been implemented yet. The scheduler (as of now) only garuantuees strict earliest deadline first scheduling.
ScriptProcessor, creates chained handlers and then suspends the process (but not always). The handlers are looked up by pattern matching whenever events occur. So you added yet another event system! ;-)
For the scripts, the policy is really simple: Scripts either run to completion or until they wait for something to happen (this might be an event or a time out or a semaphore signal). Which is exactly what step methods do (in a generalized sense). What you see in some places (the "but not always" part of the above) is mostly implementation detail which ensures that certain critical notions are preserved. Such as (for example) that when we enter a critical section *and* the process entering it can aquire the lock immediately that it will not be suspended. This turned out to be critical for a number of places.
I like the semantics of it. #signal sounds like it is a more fundamental mechanism than dependencies or the multicast events; like exceptions, but without all of the stack manipulation. that would mean that it is an exception capability secure design. :) In fact it uses a pattern-matching mechanism, much Linda-like, to reschedule scripts for execution. So how does it deal with time and time events? Ahh, a tick list.
No, this is only a little optimization to get some stuff more easily out of it. Conceptually, this tick list doesn't matter - it's simply a convenient way of allowing scripts to happen on each "world tick" (simulation cycle, vat turn, you name it).
What are the entry points you use for suspending a script until an event occurs (#waitUntil:), creating and running a new script (#runScript:), triggering an event (#signalEvent:)? I tried following the senders until it got to something that looked like public protocol, but I keep running out of senders. :)
Certainly the pattern of creating a new ProcessScheduler and defining the Processes that use it as non-preemptable and uninterruptable, *at least among each other*, may allow this minimal Squeake engine to be built. My DispatchSecretary is slow and needs replacing. Could we just use the ScriptProcessor for our squeake event-loop or should we roll our own?
cheers! rob
Rob,
Andreas, I'll just cc the list in response, since this may be interesting and related to our writing a squeake processor.
Fine with me.
[TeaProcessScheduler]
I am not sure, but I am thinking that this interruptability breaks the event-loop model.
Yes, most likely so. This is a (continuous) time-driven framework not a (discrete) event-driven one.
What are the entry points you use for suspending a script until an event occurs (#waitUntil:), creating and running a new script (#runScript:), triggering an event (#signalEvent:)? I tried following the senders until it got to something that looked like public protocol, but I keep running out of senders. :)
You're right - this is slightly confusing since I am still experimenting with stuff. The essential entry points are:
* #startScript:[when:[withArguments:]] - this says that we want to start (asynchronously) after some event occured. For example: button startScript: #onMouseDown when:{button. #mouseDown}. The first argument is the "script descriptor" which can be either a symbol or a block. The second one is an event specification consisting of receiver/eventname pairs. Optionally, you can provide additional arguments. Here are some samples: button startScript:[button color: Color red] when:{button. #mouseDown}. button startScript: #color: when:{button. #mouseUp} withArguments:{Color white}.
* waitUntil: - this says we want to wait until the given event is triggered. the argument is the name of the event to trigger, its return value is the event that triggered the wait to complete.
* stopScript: - this says we want to *gracefully* stop a script (which is different from #terminateScript:). The argument is a script descriptor which can be matched against the existing scripts.
That's the essence of it. I have been playing around with more complex control structures at various levels but I am still somewhat unhappy with them. There are also a few support mechanisms of which the most important one is:
* catchAnyOf:during: - which catches the occurance of any events in the spec (specified by the first argument) during the evaluation of some block (second arg). For example: self catchAnyOf:{button. #mouseDown} during:[ self wait: 10. ]. will tell you if the button was pressed while you were waiting.
Note that there are many subtleties in this regime. The most important ones to keep in mind are:
* event masking: Any "inner handler" for an event will mask any "outer handler". This is a non-issue for strict blocking waits but a biggie when you make control structures. For example, a handler like: outer := self catchAnyOf: {foo. #bar} during:[ foo waitUntil: #bar. ]. would return nil to the outer handler since the inner one (established by using #waitUntil:) will catch the event.
* multiple script start: A script cannot be easily "started twice", e.g., if a script is already running it will not be triggered by the occurence of the same event again. For example: foo startScript:[foo waitUntil: #mumble] when: {foo. #bar}. foo signal: #bar. self wait: 1. foo signal: #bar. self wait: 1. foo signal: #mumble. will not run upon the second signal. This is to a large extend for allowing error handling to be more user friendly. For example, something like: self startScript:[self halt] when:{World hand. #mouseMove} will work perfectly fine. Since there is a need for allowing the same script to be triggered multiple times even if running I have made a fallback (which is really a workaround until a proper solution is found) for "special system scripts" (such as Hand>>processEvents ;). See #isSpecialSystemScript:.
* event dropping: If events are triggered multiple times, you will only see the last event by default. So for example, event := self catchAnyOf: {foo. #bar} during:[ foo signal: #bar with: 42. foo signal: #bar. ]. will answer the *last* event triggered. You can obtain all of the events that have been triggered in the mean time by using #withDroppedEventsDo: on the event but the queue size for remembering dropped events is (by default) limited to 100 (I think...).
Certainly the pattern of creating a new ProcessScheduler and defining the Processes that use it as non-preemptable and uninterruptable, *at least among each other*, may allow this minimal Squeake engine to be built. My DispatchSecretary is slow and needs replacing. Could we just use the ScriptProcessor for our squeake event-loop or should we roll our own?
I'd be delighted if you'd use it! That is as long as you keep me in the loop about any "system level changes" to it ;-)
Cheers, - Andreas
Is the Croquet concurrency model explained anywhere? Does anyone have an electronic version of David Reed's thesis they could upload? The only thing I could find is pages 66-68 of http://glab.cs.uni-magdeburg.de/~sqland/Croquet0.1.pdf , which only gives a taste.
Btw, I'll be travelling from now till 21Feb and will be mostly incommunicative while I'm gone.
P.S., I was extremely pleased to see how well my proposed consensus was received! We should now be thinking about how to proceed towards a first draft definition of Squeak-E.
---------------------------------------- Text by me above is hereby placed in the public domain
Cheers, --MarkM
squeak-e@lists.squeakfoundation.org