Skip to main content


Showing posts from 2005

Simplified TeaTime

The next Croquet release, code-named "Hedgehog", will be centered around a real replicated object model, rather than the ad-hoc meta sends in Jasmine. See Andreas' and David's OOPSLA presentation (PDF) for an overview.

Connecting fields

Quick Recipe To connect field a in obj1 to field b in obj2 , use this: obj2 startScript: #b: when: {obj1. #aChanged} Now for the whole story ... Problem A colleague of mine wanted to make a drop-down list, where the options are not just set once, but provided and updated by the application. So, of course, when the options in the application changes, the items of the list widget have to be set to this new value. Nothing easier than that, just write a handler: onOptionsChanged     <on: optionsChanged in: app>     listWidget items: app options HOWEVER, he wanted to build this programmatically, not using a separate method. So, he easily came up with the following: listWidget startScript : #items: withArguments: {app options} when: {app. #optionsChanged} HOWEVER, this does not work as intended because the arguments to the script are evaluated only once, rather than every time the script is triggered. Well, this is what blocks are for, right? So this indeed works as intended:

Scripts in Croquet

Tao asked for a "Tweak & Croquet" tutorial. I don't have time right now to actually write one, but here's some sample code anyway. Using scripts is easy and useful, even without the Tweak GUI. Just use #startScript: to run some method as a script. Inside a script, you can use loops and anything you like, just throw in a wait to account for time. Like, to animate the color of a frame, you could use this method (just add it to your TeapotMorph ): animateColorFor: aFrame [ 0 to: 360 do: [:hue | aFrame material color: (Color h: hue s: 1.0 v: 1.0). self wait: 0.01] ] repeat This changes the color every 10 ms, and you can start it from the initializeDefaultSpace method: self startScript: #animateColorFor: withArguments: {someFrame}. Here is something that does not loop forever, but finishes after one cycle: jump: aFrame | v g | v := 0@1@0. g := 0@-0.05@0. [aFrame translation y >= 0] whileTrue: [

Tweak Tutorial

Andreas posted a BankAccount and ATM tutorial. This nicely demonstrates some of the basic Tweak concepts such as fields, events, triggers, and handlers, as well as introducing UI aspects like players, costumes, updating etc.

Print-Quality Screenshots

For high-quality prints you need high-quality screenshots. This means very high resolution, and nice anti-aliasing. Just grabbing the screen produces rather unpleasant results ( screenshot , 80 KB, 800x600 pixels). With normal OpenGL rendering you get rarely more than screen resolution, and anti-aliasing quality very much depends on your graphics board. So what to do? Tiled Rendering comes to the rescue. Instead of rendering the whole image at once, we render smaller portions of the scene, and then arrange the tiles into a large picture. However, just pointing the camera at each tile will not work as intended, the perspective would change from tile to tile. What is needed instead is to construct partial viewing frustums that together exactly recreate the whole frustum. This sounds like a lot of math, but actually it is quite simple: gluPerspective: fov aspect: aspect zNear: near zFar: far tile: rect | cotangent radians w h | radians := (fov/2.0) degreesToRadians. cotangent := radia

Change events are special

I just spent a few hours chasing a very mysterious bug, where an event handler was properly called the first time, but never again. This was a rather unspecial on:in: handler: onFooInBar <on: foo in: bar> ... After initialization it was properly registered in the event map, but at some point the event map entry just vanished. There also was another on: handler which did some stuff whenever bar changed. To "call" that stuff, we just signaled a barChanged event somewhere else: self signal: #barChanged. It turned out this innocuously looking line was responsible for the trashed on:in: handler! What happened? Well, an on:in: handler must always be registered to the object that currently is occupying the bar field. But what if bar changes? Then we must unregister the foo handler in the old bar , and re-register it with the new bar . That's why the system installs a barChanged handler behind the scenes, which normally receives the new and old values of ba

About Scripts

We were looking into another Croquet performance problem the other day so we fired up a message tally (world menu - debug - start MessageTally). Curiously enough, 70 percent was taken by ScriptScheduler>>runActiveScripts ! Unfortunately, the tally did not further differentiate this item. 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 rec

Lend me a Hand

The last Tweak updates broke our Tweak-based Croquet application, objects did not respond to clicks anymore. I first suspected changes in Croquet, but the latest updates there didn't look suspicious. But neither did the last couple of Tweak updates. Investigating further and sprinkling debug output here and there I discovered that pointer events were offset. Moving the Croquet window to the upper left corner confirmed this, all of a sudden everything worked normally. What was happening? Well, living in a shared world isn't easy. Morphic, Croquet, and Tweak interact very closely in the TeapotMorph , which bridges between those three worlds. Without Tweak (that is, without calling initializeTweakWorld in your initializeDefaultSpace method), the TeapotMorph uses the regular Morphic event dispatching mechanism which leads to calling the event handling methods keyDown: , mouseDown: etc. which in turn dispatch the events to the activeCamera , that is, into the Croquet world. Wh

Dynamic Textures

We've been wondering for a while, why screen updates are more expensive than expected in the Tweak overlay. Now I debugged into this and it turns out we're uploading the whole texture even if only a small part was changed. The relevant code is in OGLTextureManager>>uploadTexture:dirtyRect: . A partial upload using glTexSubImage2D() is only performed if the texture in use is not static. Having found out what's going on it's easy to fix, a one-line change in TeapotMorph>>glRenderTweakCostume:on: does the trick: texCache isStatic: false. As simple as that - I just posted the update.

Profiling + Vector Math = Performance

So there was a student who implemented a flocking simulation for a virtual fishtank using Croquet. Worked fine with 20 fishes. With 50 fishes it became rather sluggish. Putting in more than 100 fishes the framerate could be measured in seconds per frame. So the rendering in Croquet is too slow, right? Not quite. There are two things you have to keep in mind when it comes to performance: When in doubt, measure. You are in doubt. So we fired up a message tally (world menu - debug - start MessageTally). Turns out only 12 percent of the time were spent in rendering. So it's not the rendering at all. A whopping 80 percent was taken by the flock's step methods. The leaves of the message tally showed that 15 percent of the time went into each of the methods #x , #y , and #z ! So we looked at the code. Every fish was told to swim in the main direction of its neighbors, that is the fishes within a certain radius. Processing continued only for those fishes in range. Sounds quite r