Devlog
Feb 12, 2025
Syndicate-friendly keywords
Originally, the Klee interpreter plan was a very traditional Scheme-like language. The main issue with this is that it misses out on all the lovely features of the Syndicated Actor Model that are particularly useful for rendering responsive graphics. The best example of this is the during
behavior which is a common theme in syndicate implementations. This behavior frames an actor’s action to when a value is being asserted. Adding this type of behavior to Klee would allow for shape definitions like this:
(define (my-square name)
(during (record 'square name)
(stroke #000
(square 0 0 10px))))
This shape labelled with the symbol my-square
takes a name parameter, and only draws the square while a record of name square
with the same name value is being asserted to the Klee dataspace.
I also think a keyword like collect
based on the folk computer “collecting matches” phrase would be useful for rendering things based on dynamic sets of assertions. For example rendering a dynamic point cloud could look like this:
(define (draw-point p)
(stroke #000
(point p)))
(define (my-points)
(collect ('l '(record 'point p))
(map draw-point l)))
Also needed, is the ability for shapes to make assertions
. This will come in two primary ways:
- controllers
- raw assertion keyword
Controllers are shapes that assert, input events like mouse/keyboard to the dataspace when they are interacted with. Quick example would look like:
(define (c-square)
(on-mouse-down 'c-square
(stroke #000
(square 0 0 10))))
When a mouse down event occurs inside the area defined by the square call, the controller will assert a mousedown event with the associated symbol c-square
. Any observing actor in the dataspace can then react to this assertion and possibly update the view.
Raw assertions are exactly what it sounds like, the ability to make assertions using preserves format. These can be combined with the previous keywords.
(define (c-square)
(during <mousepressed>
(during <shift-pressed>
(assert (record 'hello))
(stroke #000
(square 0 0 10)))))
Here, when both the mouspressed and shift-pressed records are asserted in the dataspace, the c-square procedure asserts a hello record to the space. I’m not exactly sure of great uses for this yet, but it might allow for some interesting extensions to the system. In general, once I have the core system working, I want to spend a lot of time working on making the system growable. A big part of this effort might be giving this system some sort of metaobject protocol, something to make it so I’m not having to hard-code every new object I want to add on the rust side, as it feels very fragile.
Feb 13, 2025
Rethinking Top-level evals
In the notes above I wrote:
“This is an important nuance of the klee system, you cannot for example send the expression (+ 1 2)
to the dataspace and expect for the system to assert a response, the only expression which is ever evaluated at the top level is the view
expression.”
We have this wonderful substrate of a dataspace that allows us to communicate between processes really well, limiting the control of the entire display surface to a single assertion greatly hampers the collaborative possibilities. For large swarms of actors contributing to a single klee dataspace, I think this might become an issue where actors might have to “fight” for the single view assertion to make large changes to the client.
I think the better option is to allow for arbitrary top-level execution, and execute each top-level function with the environment of the client surface. So for example:
(define (my-rect)
(fill 'red
(rect 0 0 100% 100%)))
(my-rect)
Will execute my-rect
with the ParentWidth
and ParentHeight
environment variables set to 100% of the window’s dimensions. Thus this will draw a red rectangle that covers the entire window no matter how it is resized!
I think the view
function will still be useful, it’ll still provide the first start point for each frame execution, and it will also be used to set the screen dimensions and other important wayland client properties.
One other thing to consider is that loose top-level evals ruin any concept of proper ordering, so this may create issues with draw ops happening out of intended order. To compensate for this, we can create a surface object that will have a z-index.
(define (my-rect color)
(fill color
(rect 0 0 100% 100%)))
(surface 0 0 50% 50% 1
(my-rect 'blue))
(my-rect 'red)
Let’s say the surface
function takes properties x
, y
, w
, h
, and z-index
in that order, so the surface
function call above will draw with a z-index
of 1. The default z-index
is the “sea-level” of 0. This full set of assertions will end up drawing a full red background that resizes with the window, and then a blue rectangle on top which will take up the top-left quadrant of the window, and responsively resize.
Surfaces can potentially map nicely to wayland subsurfaces under the hood, and we can probably optimize the interpreter’s interaction with the compositor to efficiently manage redraws.
Feb 15, 2025
The Klee Numerical Tower
One important feature of traditional scheme implementations is their use of Numerical Towers, and Klee is no different.
Eventually, I would love to implement the full scheme numerical tower on top of the Klee-specific values, but for now this is the current Rust description of Scalars (e.g. raw values without direction) and 2d vectors:
#[derive(Debug, Clone, PartialEq)]
pub struct Polynomial {
t: f64, //Placeholder
}
#[derive(Debug, Clone, PartialEq)]
pub enum Scalar {
Int(i64),
Float(f64),
Px(i64),
Cm(i64),
ParentWidth(f64),
ParentHeight(f64),
ViewWidth(f64),
ViewHeight(f64),
RootUnit(f64),
Unit(f64),
Polynomial(Polynomial),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Vec2 {
x: Scalar,
y: Scalar,
}
The major takeaway is the inclusion of relative units, and also the Polynomial
definition. Relative units, as mentioned briefly above, allow for the definition of shapes that adapt to changes in their parent container’s shape. They work by “snapping into” absolute values when executed inside an eval environment that has ParentWidth
, ParentHeight
, ViewWidth
and ViewHeight
values. The top-level environment always has these value set based on the current underlying wayland surface dimensions.
Polynomials represent combinations of these various units via +
, -
, *
, /
operations. What is exciting about this is that you can use this to set extremely precise responsive dimensions. For example, you could define the height of a rectangle to be a responsive value of (+ 10vh 20px)
which is equivalent to 20px more than 10% of the current view height. All of this is very similar to the current calc()
function available in modern CSS.
The idea of RootUnit
and Unit
also pull from CSS, these are equivalent to the rem
and em
units, but freed from their inherent ties to font-size. A good example use could be for defining the size, in pixels, of a grid unit for a grid layout, and then defining your layout in terms of these RootUnits
so that everything sits on this grid.
As a note, I would really love to have cm
be the base absolute unit users think in terms of over pixels, especially as higher DPI screens continue to improve. Getting 1cm
to equal 1 IRL cm
will be a challenge to get working reliably but I believe we can figure out a decent system for calculating this using wayland to gather display info.
Feb 16, 2025
We Have Eval!
Got my first custom object to evaluate properly which is exciting!
Here is the first eval test that passed:
#[test]
fn test_rel_len_with_view_dim() {
let v = KleeView { w: 200.0, h: 200.0 };
let mut env = Rc::new(RefCell::new(Env::new_view(v)));
let obj = Object::List(Rc::new(vec![
Object::BinaryOp("+".to_string()),
Object::ParentHeight(1.05),
Object::ParentWidth(2.00),
]));
let result = eval(&obj, &mut env).unwrap();
assert_eq!(result, Object::Float(6.10));
}
The ParentHeight
and ParentWidth
objects correctly get evaluated to be 1.05% and 2% of the containing environments dimensions, which in this case is 200x200. They get eval’d to floats, which then get added together to reach the intended output of 6.10
.
Here’s to many more!