Games
Breakout, and a shooter on a canvas. Nothing was added to the language for either of them.
A frame is a message
requestAnimationFrame is reached through Extern. Four lines.
Extern subclass: #Win global: 'window'.
Win class >> requestAnimationFrame: Block[Float Unit] -> Int.
Screen >> tick = (
Win requestAnimationFrame: [:t | me ref tell tick].
...build the next state... ).
The callback does nothing but send a message. After that it is a business screen: the handler answers the next state, and the state moving is what draws.
Measured in a browser: 60 frames a second, with the actor redrawing on every one. The counting was done by the page's own requestAnimationFrame and a MutationObserver.
A key is held, not counted
Move on each keydown and holding the arrow gives one step, a pause while the operating system decides the key is repeating, and then jumps.
So the actor remembers whether the key is down, and the frame does the moving.
Screen >> pressed: e = self with: { goLeft: ((e key) = 'ArrowLeft') or: [goLeft] }.
Screen >> released: e = self with: { goLeft: ((e key) = 'ArrowLeft') ifTrue: [false] ifFalse: [goLeft] }.
A business screen reads a key the same way. How many times it was pressed and how long it is held are different questions.
Breakout is DOM
The ball, the paddle and the bricks are clones of samples in the designer's page, moved by their style attribute. The bricks carry key:, so one breaking leaves the others' nodes alone.
The shooter is canvas
Canvas is reached through Extern as well.
Extern subclass: #Ctx.
Ctx >> fillRect: Int y: Int w: Int h: Int -> Unit.
Ctx >> fillStyle := Str.
Here view goes unused. A canvas keeps what was drawn on it, so each frame clears and redraws, and "draw only when the state moved" has nothing to act on. A game redraws every frame anyway.
The same approach moving 10,000 dots also held 60 frames a second. This game moves about a hundred things.
One thing it taught us
While the score label was being told the score every frame, the worst frame gap was 48ms: the label answered a new state each time, so the DOM was written sixty times a second to say the same thing.
Making show: answer self when the text has not changed brought it back to 18ms. A handler that answers self draws nothing.