But what are scripts, anyway? Everyone knows that Smalltalk is all about objects and messages, so what the heck are scripts?
Well, Croquet and Tweak are not just using Smalltalk as you know it (and the underlying Squeak still is pretty much vanilla Smalltalk-80), but instead improve on it by implementing a new enriched object system. There still are objects (the entities of the system) and messages (their means of communication). But where in Smalltalk methods are invoked synchronously by a message send, we now have asynchronous method invocations as well, which are called "scripts".
Synchronous in this context means that the sender sends a message, which invokes a method in the receiver that is immediately processed. Only after finishing the method the control returns to the sender. In contrast, an asynchronous send only schedules a method invocation for later processing, control is immediately returned to the sender. Here is an example (#perform: is synchronous, #startScript: asynchronous):
Transcript perform: #show: withArguments: #('1').If you execute this snippet from inside Tweak, it prints "132". Outside of it just "13" is printed because the second invocation is only started but never executed. There is no ScriptScheduler running to manage the execution of scripts. Inside Tweak there is one, and Croquet does it in a similar fashion.
Transcript startScript: #show: withArguments: #('2').
Transcript perform: #show: withArguments: #('3').
Historically the term script in Squeak comes from the Etoys environment, where kids make objects and specify their behavior using scripts. One can have multiple objects and multiple scripts for each. All these scripts are running in parallel, at least from the user's point of view. Surprisingly enough kids don't have any problem with that, whereas concurrency normally is a hard problem even for seasoned programmers.
One reason why the parallel execution of interacting scripts is no problem in practice is the underlying scheduling policy: Scripts are never interrupted by other scripts. Other scripts are only executed when your script finished, or when it gives up control explicitly by waiting for an event. You can write your script almost as if it was the only process on the machine. Here's an example script:
| p |Looks just like how we did animations back when, right? Move a bit, wait for vsync, repeat. But the best part is this: While the loop is executing, you can perfectly well use anything else in the world! It's running "in parallel" to everything else on screen.
p := CRectanglePlayer open.
1 to: 500 do: [:i |
p x: i.
Another advantage is that you can easily have thread-local storage that way. Say, on mouse click you want to change an object's color to red, and on mouse up set it back to what the color was before. In most GUI frameworks you would have to implement both a mouse down and mouse up handler, and use an instance variable to store the previous color on mouse down. Not so in Tweak. The mouse down handler would look like this:
| oldColor |Much cleaner, in my book. Anyway, to get back to the original problem: So scripts are run as separate processes, but MessageTally's spyOn: method normally only takes samples in the process it was run in, which is the main UI process. Fortunately, there is a new method spyOnScript: which does the Right Thing. When starting the message tally from the Tweak project builder's debug menu, this new method is used, and indeed, we got a much more meaningful tally, and could spot the performance hog immediately.
oldColor := self color.
self color: Color red.
self waitUntil: #mouseUp.
self color: oldColor.