User Tools

Site Tools


newsletters:2026-05

This is an old revision of the document!


May 2026 newsletter

Our next Folk open house will be on Tuesday, June 30th, 2026.

What we've been up to

General system improvements

  • Added column numbers to Jim Tcl stack traces, so we can get the exact position of an expression like [Field ___] in both x and y (until now, Jim only recorded line numbers, so this change pervades the whole interpreter)
  • Made fn-created procs get destroyed at end of scope, so you can hot-reload fns (they no longer stick around in global scope when the new code is injected)
  • Changed calibrate tag size to be the same as printed tag size in the print/calibration changes, which should help most tracking be more accurate even if you mis-measure tags (note: you need to recalibrate)
  • Added support for array length as a virtual parameter in C calls, which lets us get rid of a lot of extra parameters/cruft at the caller where we were passing pointer + array length separately
  • Fixed a big memory leak where every block execution was allocating new Jim channels for the stdout/stderr files
    • Helpful: this RAM checker program:
      • img_4796.jpeg
  • Fixed epoch thread slot cleanup so you can keep churning threads without running out of epoch slots in the epoch-based reclamation system
  • Debounced/reduced canvas creation churn when the statement's parent goes away and comes back a lot
  • Fixed RAM exhaustion check which had been broken by using meminfo file instead of API
  • Fixed -atomically flag for When which wasn't actually getting the When pattern to distinguish between blocks on the same origin, so you'd get unnecessary blinking
    • Also now use atomicallyWithKey to control blinking on titles/footnotes/etc
    • Hoping now that we know how to work it, we can use -atomically more aggressively to stomp out blinking + start thinking about how to automatically introduce it so Folk code 'does the right thing' out of the box most of the time

Fix match destruction and insertion race/crash

A pretty tricky and subtle bug in how we dealt with match slots: we were marking slots as free by setting childStatements to NULL, but then disposing the slot's destructor set later, so there was a chance that the slot got reused in that middle period. Then the destructor set is destroyed while the match slot is used for another match, meaning that you have a null pointer that crops up when you try to destroy the new match / clear its destructors, or you lose the new destructors (which means stuff sticks around forever, I guess).

Fixed by adding a third state for childStatements that indicates they're in the middle of removal process and you shouldn't reuse the slot (that field is atomic already anyway).

Fix texture exhaustion crash

Omar: We were getting crashes when we tried to, say, display a really long string of text (or other complicated graphics).

The problem was that we were actually running out of texture slots, which caused a panic.

Texture reclamation runs on the GPU thread alongside the parsing and rendering of all the glyph instance objects for that really long string of text. So if you're drawing something complicated such that the parse/render work takes too long (see the draw-lists-construction section here, which is 50-100+ ms), you stop freeing old texture slots before the next frame starts:

Then you run out of texture slots, which caused the whole system to abort.

Excess unreclaimed textures:

Solution for now: don't abort when out of texture slots! Just delay and try again later.

(in general, it feels like we should very rarely actually panic; most problems in the system should be recoverable. Panic was maybe good for an earlier phase of system development when it was more monolithic anyway, to make sure invariants were maintained, but it's pretty drastic now.)

(A panic using straight C exit(1) is also problematic because it may not show up properly in the logs, because it starts invalidating data structures that may cause other Folk threads [especially Vulkan validator work] to crash before printing the log/panic message, so it can be very confusing to debug.)

Fix Vulkan texture publication ordering (magenta flashing)

  • Andrés: I worked on "Fix Vulkan texture publication ordering" because we noticed at the SVA demo that the animation program was frequently resulting in our magenta “missing content” texture displaying after final frame of animation. While this was a pretty involved change (see the code below), it can be summed up as: instead of textures getting deleted while the GPU is still trying to draw them, we introduced a “grace period” to keep them alive for a few frames. We also tightened up the order of operations to ensure new textures are fully loaded before the canvas actually tries to display them.
Dive into the technical details
  • Graceful Retirement: Instead of instantly deleting a texture when it's no longer needed (which caused the GPU to draw a deleted texture, hence the magenta flash), the PR adds a 2-frame grace period (TEXTURE_RETIRE_GRACE_FRAMES). Textures are marked as “retiring” and kept alive just long enough for the GPU to finish its in-flight renders. (gpu/textures.folk#L126..127, gpu/textures.folk#L768..774)
#define TEXTURE_RETIRE_GRACE_FRAMES 2
int textureFrameEpoch = 0;
 
// ...
 
static void retireGpuTexture(GpuTextureHandle gim) {
    GpuTextureBlock* block = &gpuTextures[gim];
    if (gim == 0 || !block->alive || block->retiring) return;
 
    block->retiring = true;
    block->retireAfterFrame = textureFrameEpoch + TEXTURE_RETIRE_GRACE_FRAMES;
}
  • Order of Operations: Here we force the system to finish processing pending texture updates (drainDeferredTextureOps) before it records the drawing commands for the canvas.
    # Make textures published while building the display list drawable
    # before we record display command buffers.
    $gpuTextureLib drainDeferredTextureOps

    # Draw to each active display.
    dict for {displayName ds} $displays {
        if {$ds eq "missing"} { continue }
    VkSemaphore imageAvailableSemaphore;
    VkSemaphore* renderFinishedSemaphores;
    VkFence inFlightFence;
 
// ...
 
    ds->renderFinishedSemaphores = calloc(ds->swapchainImageCount, sizeof(VkSemaphore));
    for (uint32_t i = 0; i < ds->swapchainImageCount; i++) {
        $[vktry {vkCreateSemaphore(device, &semaphoreInfo, NULL, &ds->renderFinishedSemaphores[i])}]
    }
 
// ...
 
    VkSemaphore signalSemaphores[] = {ds->renderFinishedSemaphores[ds->imageIndex]};

Handle HTTPS proxies

Andrés: Handle WebSocket header behind HTTPS proxies (PR #260) fixes an edge case where Folk's /new endpoint was crashing behind a Tailscale reverse proxy. Previously we used case-sensitive dictionary lookups (e.g., dict get $headers Content-Length). This would throw an error if headers were missing or had unexpected capitalization.

To fix this we now have a new headerGet proc:

proc headerGet {headers name args} {
    foreach {k v} $headers {
        if {[string equal -nocase $k $name]} { return $v }
    }
    if {[llength $args] > 0} { return [lindex $args 0] }
    error "missing HTTP header $name"
}

This helper adds support for case-insensitive matching and default fallback values. For example, instead of crashing when a client attempts a WebSocket connection without a Sec-WebSocket-Key, the server can now safely detect the missing key and return a clean HTTP 400 response:

- WsConnection upgrade $chan [dict get $headers Sec-WebSocket-Key]
+ set clientKey [headerGet $headers Sec-WebSocket-Key ""]
+ if {$clientKey eq ""} {
+     puts -nonewline $chan "HTTP/1.1 400 Bad Request\r\n...Missing Sec-WebSocket-Key\r\n"
+     close $chan
+     return
+ }

QueryOne! changes and new Expect! construct

Omar: Added support for ''-default'' option and 'nameless return value binding' ''/./'' to ''QueryOne!''. Also added ''Expect!'' which directly makes bindings in the caller scope.

As I've been working on calibration, editing, and printing, I've been using Query! and QueryOne! a lot to imperatively grab 'whatever the current state is' during a point action, in these very repetitive patterns:

so I finally bit the bullet to clean all of these up and replace them with Expect! wherever I can:

I think it looks great – much more readable and straightforward, harder to mess up as a user.

Thanks to Mason Jones for design input on defaults and s-ol for suggesting the direct-binding Expect! syntax.

Shapes, new and improved

  • Andrés: Now that the semester is over and I'm done teaching at Parsons and SVA part time I've been able to go to the studio nearly every day. Most days in May I was working on Revive drawing capabilities (new and improved!) (PR #261)|, which has resulted in enabling a much more consistent and capable set of shape drawing wishes, e.g.:
    • In order, left to right, we added the ability to draw:
      • filled shapes
      • line caps (e.g. flat (on top) or round (on the bottom))
      • circles (not new)
      • arbirary shapes by name (e.g. octagon)
      • text (new: now with full support for options)
      • dashed lines (not new)
      • images (not new)
      • AprilTags (not new)
      • draw Bezier curves
      • GIFs (not new)
    • The PR needs a bit of refactoring but I expect it to be merged by the end of the first week of June.

Screen sharing experiments

Andrés: Brian Lee and I were pairing on enabling screen sharing via WebRTC. We got it working on a branch (ac/screenshare) and tried out sharing Google Maps and the news on the table:

There's something really magical about seeing your screen as a little, live texture you can control, Daily Prophet-like, even! Brian's really interested in using newspaper-sized sheets of paper to project large, movable versions of screens as a kind of presenter mode. We'll prototype that + try to land this PR next month!

Editor-based print typesetting and layout

Omar: I've changed around the editor and print logic so that printouts come out looking exactly like the editor (and line up perfectly when calibration and pose estimation are good enough):

img_5303.jpeg img_5266.jpeg

so typeface, margins, line spacing, text size, etc are all aligned.

You may remember that I've been interested in fields on the printed code for a while:

I think it would be cool to provide an API like this:

20250131-202950.jpeg

20250131-202955.jpeg

where you get "X" as a string as the return value of Blank2. This would be great instead of (or in addition to) a more explicit API where you have to like Wish $this prints rectangle with width 4cm height 2cm x 0cm y 10cm and When query for the camera slice of the field and run OCR on that etc etc. You'd be able to use handwriting input out of the box, as easily as you read from the terminal in basic programming on a laptop.

(more generally, want to make the printed code into a space for interaction, not just documentation for a human reader. you could also imagine checkboxes, sliders, display windows that you can project into, color pickers, etc as elements that you can put into your code text)

I started working on this Field construct, but quickly realized that I wanted it to be editable and previewable in-system, so the in-system editor would need to be at-scale to how it'd actually print out (which is also something we've wanted for a while). So I went on this side quest of making a 'WYSIWYG' to-scale tabletop editor, where the text layout and typesetting in the editor is exactly what you get when you print the program.

Had to fix a bunch of bugs, like tags and lines at top/bottom of pages getting cut off:

Also got rid of word wrap (you can choose how to wrap it yourself by hand, since you see how it'll get cut off – I like giving these presentational decisions to the user) and made the editing happen on the page itself, which has downsides.

(It's also a good way to motivate making the calibration really good, since it's obvious when it's off by more than a millimeter or so)

The new print system emits a much richer geometry that says where the paper edges, the tag, and the code margins are.

It also requires you to print a new calibration board and recalibrate so the system can understand the print properties of your printer and print stuff at exact metric positions on the page (which is an important advance for us). Here's how the new calibration looks:

Experimental Tcl interpreter update (Zicl)

Mason: I've had some time open up over the past month, so I've cracked back open my experimental interpreter. I've made a lot of progress with reworking the internals (improved shimmering API, reworked memory allocators, and lots and lots of crash fixes). I've also gotten probably 30% of the standard library implemented now, with most of the core commands working. IO and others are forthcoming. I think the most interesting things though have been redesigning dictionary lookup, method dispatch, functions with lexical scope capture, linked dictionaries, and hashes as first class objects.

Dictionary lookup

Conventional Tcl dictionary lookup is kind of a pain. You have to do [dict get $dictionary foo] for a key, or [dict get $dictionary foo bar] for nested keys. Jimtcl makes this a bit better with “$dictionary(foo)”, but that still doesn't allow for nested key lookup. I decided to steal Tcl's namespace design, except that it does dictionary lookup instead. Getting a value looks like “$dictionary::foo”, and setting a value is just “set dictionary::foo value”. This also allows for nested keys, like “$dictionary::foo::bar”. Now you might be wondering, “what about Tcl namespaces then?” Well, I removed them. Functions are now first-class in Zicl, which means to create a “library”, you'd just do

set namespace {}
fn namespace::foo {x y} {
  return [+ $x $y]
}

Speaking of functions as first-class concepts, that leads us to

Functions

Tcl is designed by separating the procedure and variable scopes. You can have a procedure called “foo” and a variable called “foo”, and they exist side-by-side. This is because the way procedures are looked up is different than variables. I don't particularily like this, so I've decided to take a page from Scheme's book and have everything in a unified space. This means that code such as

set foo {fn impl {{x y} { return [+ $x $y] }}}
foo

actually looks up “foo” in the local scope, parses it, and evaluates it as a function. This is great, because then you can put functions in dictionaries, and call them directly:

set namespace {}
fn namespace::foo {x y} {
  return [+ $x $y]
}
puts "Result: [namespace::foo 5 10]"

You can also redeclare them to “import”:

set foo $namespace::foo
puts "Result: [foo 5 10]"

Now this is great and all, but there's one glaring issue: Jim's OO system is very dependent on namespaces, particularly the fact that namespaces can mutate. With this new design for “namespaces”, they're an immutable dictionary, so the OO system wouldn't really work. Or could it? I figured if we're gonna lean all in on immutability, we might as well steal from one of the most famous immutable languages: Haskell. Enter Monads. Monads allow you to do an operation on a dictionary, while preserving the fact that it's a dictionary:

set counterObj {value 5}
fn incrCounter {counterObj} {
  set counterObj::value [+ $counterObj::value 1]
  return $counterObj
}
set counterObj [incrCounter $counterObj]
puts $counterObj ;# {value 6}

This does work, but it's a lot of boilerplate. In addition, it can't return a value alongside the object. So I've added some sugary machinery that makes it much easier to work with these quasi-monads. Enter methods. Here's the same as above, except with a method:

set counterObj {value 5}
method counterObj::incr {self} {
  set self::value [+ $self::value 1]
}
counterObj::incr
puts $counterObj ;# {value 6 incr {method ...}}

This way you get all the goodies of OO, while still being transparent to what's actually stored in the object at any time. This way objects should sync much nicer across threads and computers, since the values are stored directly in the object.

However, there's one big disadvantage: each object lugs around all of its objects. This is a good way to explode the size of all of your objects and grind object transferring to a halt. So what to do? Well, be inspired by Self, of course, and by extension JavaScript and Lua. If you're familiar with JavaScript, I'm talking about prototype lookup, and if you're familiar with Lua, I'm talking about metatables. The beauty of prototype lookup is that you have one core object/table/dictionary that implements all of the object's methods, and then you “link” a new object to those methods. In Zicl, this looks something like

set methods {}
method methods::add {self amount} {
  incr self::count $amount
}
set newObject [dict link $methods {count 0}]
newObject::add 1
puts $newObject ;# {count 1 ^parent blake3^W9aTJiCMFQKJXFqkFxyx5am1XFJ16ctLUeQ8auJFbts}

You'll notice that there's a “^parent” field. This contains a hash of the linked dictionary. This is very important: it's not a pointer, it's a hash. That means it's content based, and so it's stable across systems. It's also stable when saved to disk. The biggest downside to this is you have to keep the hash's value alive. So how do we keep hash values alive?

Hashes as first-class concepts

The hard thing about introducing hashes is you need to keep their corresponding value alive. You also don't want hash values to leak if nothing refers to them. LRU caches are right out because you can't guarantee that a value exists, as it might be ejected. So, in Zicl, whenever a string is created, the string is scanned for hashes, and the hash value is ref counted by that string. If another string is created referencing an unresolved hash, it'll look it up in the central table and borrow the value. This also means that when syncing between systems, you could do something like a Git-style sync where only missing hashes are synced to the other computer. Hash values could also be persisted to the hard drive by having a disk-level ref counting system, though that would be a lot trickier to pull off.

Scope capture

Now that we've covered linked dictionaries, I can finally explain how function scope capture happens. When you create a function, it captures the immediate scope. That scope may in turn reference a higher up scope. Well, that sounds an awful lot like linked dictionaries, doesn't it? So, scope capture works by getting a hash to the immediate scope, and that immediate scope in turn has parent links going all the way back to the highest lexical scope. So now closures can by synced! In fact, I'm curious what continuation-style programming would by like, where you evaluate the first step of a function, send it to a different computer that does the next step, and so on. Because all variables are captured, it should hopefully transparently move around.

DrawTalking experiments

We met Karl Rosenberg from NYU at the SVA talk and have since been working a bit with him on integrating his DrawTalking project into Folk. It's exciting to have an external collaborator and to be doing more drawing and external-computer interactions (again, not just moving pieces of paper around).

His external system (iPad app + server) is sort of stateful and opinionated, so we want to use it to drive story/behavior and use Folk as a 'view' at first. Eventually, it would be great to do storyboard interactions to draw on physical paper, and also have the animation/game interact with real-world objects on the table (beyond just tagged pieces of paper, too).

Outreach

SVA talk

Setup

Omar, Andrés, and Brian worked on installing a Folk system permanently in a small cubby/study area at School of Visual Arts in Manhattan. It might be our cleanest setup yet – we like this method of hanging from a dowel and drilled into the ceiling – it is easily able to support the weight, and it's adjustable.

img_4477.jpeg img_4485.jpeg img_4486.jpeg img_4493.jpeg

img_4495.jpeg img_4499.jpeg

Andrés working on the final setup of the table:

The talk

img_4599.jpeg

folk-recurse

The Folk instance at the Recurse Center is back up, thanks to Audrey Gu and Jaza Syed:

Audrey and other Recursers have been playing with the system and poking at Jessie Grosen's SuperCollider programs:

img_3069.jpg

Open house

We had a sizable open house in May. A lot of people from Recurse Center came by. We showed off the new drawing, video, and printing/editing improvements.

img_5378.jpeg img_5376.jpeg img_5393.jpeg img_5385.jpeg img_5368.jpeg c9b4836b-d723-42ae-9017-d00c5ca78e07_1_105_c.jpeg

Our friends Audrey Gu and Jerry Hong worked on making a 'generic file' icon with the new drawing primitives:

img_5398.jpeg img_5402.jpeg 65029691-e716-42e9-9314-512b68879db6_1_105_c.jpeg

Danny made a 'kaleidoscope':

img_5388.jpeg

What we'll be up to in June

  • Andrés: landing the shapes PR
  • Andrés: working on getting HTTPS and screensharing working
  • Omar: fix crashes, improve performance
  • Omar: go back to working on SAM2 demos and object tracking
  • Omar: work with Karl on DrawTalking demo
  • Omar: continue working on new editor (needs better user experience, calibration accuracy) + fields interactions (hand-writing, drawing, getting those as objects in Folk)
  • Help tune up the Recurse Center Folk system

Andrés

Omar

newsletters/2026-05.1780525659.txt.gz · Last modified: by osnr

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki