I thought I needed these for this bouncing-ball demo:
def (bounce screen)
with (w (width screen)
h (height screen)
cx 16
cy 16
dx 12
dy 19)
while 1
clear screen
ring screen cx cy 16 3 5
cx += dx
cy += dy
when (or (cx > w) (cx < 0))
set dx 0-dx
when (or (cy > h) (cy < 0))
set dy 0-dy
for _ 0 (< _ 100) ++_ # delay
No matter how I adjusted the delay I couldn't get rid of the jitter. So
I built a double-buffered version:
(bounce2 . [def (bounce2 screen)
with (w (width screen)
h (height screen)
cx 16
cy 16
dx 12
dy 19
screen2 (new_screen (columns screen)
(lines screen)))
while 1
clear screen2
ring screen2 cx cy 16 3 5
cx += dx
cy += dy
when (or (cx > w) (cx < 0))
set dx 0-dx
when (or (cy > h) (cy < 0))
set dy 0-dy
blit screen2 screen
for _ 0 (< _ 100) ++_]) # delay
But it didn't make a difference! Turns out nothing will help you when
successive frames are too far apart. This is the correct tweak to
`bounce`:
- dx 12
- dy 19)
+ dx 1
+ dy (/ 19 12))
Still, we'll keep double-buffering around for the future.
The Mu shell has no string literals, only streams. No random access,
only sequential access. But I've been playing fast and loose with its
read pointer until now. Hopefully things are cleaned up now.
Like parenthesize, I'm copying tests over from https://github.com/akkartik/wart
Unlike parenthesize, though, I can't just transliterate the code itself.
Wart was operating on an intermediate AST representation. Here I'm all
the way down to cells. That seemed like a good idea when I embarked, but
now I'm not so sure. Operating with the right AST data structure allowed
me to more easily iterate over the elements of a list. The natural recursion
for cells is not a good fit.
This patch and the next couple is an interesting case study in what makes
Unix so effective. Yes, you have to play computer, and yes it gets verbose
and ugly. But just diff and patch go surprisingly far in helping build a
picture of the state space in my brain.
Then again, there's a steep gradient of skills here. There are people who
can visualize state spaces using diff and patch far better than me, and
people who can't do it as well as me. Nature, nurture, having different
priorities, whatever the reason. Giving some people just the right crutch
excludes others.
Refreshing the fake screen is still a heavyweight operation. Double-buffering
makes it less obvious but doesn't actually reduce the amount of work. We
need to ensure that we do enough work between refreshes to make them economic.
Font rendering now happens off the real screen, which provides the effect
of double-buffering.
Apps can now also use convert-graphemes-to-pixels for more traditional
double-buffering.
Two interesting things:
- We don't really need double-buffering for rendering the screen on the
sandbox as a progress indicator. Everything else is untouched, and render-screen
should be doing that as well.
- Rendering just pixels of the fake screen is buttery smooth. It's the
_graphemes_ that are slowing things down. Even though there's so many
fewer of them!
As a result, drawing the fake screen less frequently in `evaluate` doesn't
actually help with flicker. Even though it'll make the debug cycle shorter.
So my current plan is to attack flicker in isolation before I mess with
the render frequency.
In this commit I optimized away the cursor handling. Still doesn't seem
to be helping. In fact it actually seems _worse_.
Rename cells containing screens to screen vars because of the ambiguity
that each grapheme in fake screens is represented by a type screen-cell.
While we're at it, we also analogously rename keyboard vars.
Needed because we don't yet have a primitive in the shell to truncate/round
non-integers to integers.
Before:
(nth (/ 31 10) # we don't have float literals yet
'(1 2 3 4))
=> NULL
..with an unpleasant abort likely later on.
Really the correct thing to do is ensure none of my primitives ever returns
NULL. Start with car/cdr.
After all that, I'm not sure this is the desired behavior. If a function
defines multiple bindings, we shouldn't rename all their keys. So how to
choose?
Perhaps it's not so bad to have "symlinks" in this "file system". To unlink
two bindings you now need to define one of them in the sandbox.
All the refactoring is still useful, though.
Save a single trace to potentially multiple globals just like a gap buffer
(if say we have a single let binding defining multiple functions).
I don't have a strong use for this yet, but it seems cleaner. Maybe it's
redundant or wrong.