DisplayScript

State and Identity

Your program runs from scratch each time it needs to redraw. Values stored in state variables persist across runs.

State variables are created with an initial value:

state counter = 0
draw centered(text("Counter: \(counter)"))

Putting a value in a state variable lets you read it during the next run.

state counter = 0
draw centered(text("Counter: \(counter)"))
draw clickHandler(function () {
    next counter = counter + 1
})

Note that we assign to next counter instead of counter directly. Changes to state variables are not applied until the next frame. This sidesteps many of the problems with shared mutable state: for example, you can call functions at any time without worrying about whether you've left state variables in a "good state".

Once you've assigned to a state variable, a next binding is added to the current scope. You can use it in expressions like a normal binding:

state counter = 0
draw centered(text("Counter: \(counter)"))
draw clickHandler(function () {
    next counter = counter + 1
    next counter = next counter * 2
})

Persistent Identifiers

State variables can appear inside functions and drawables:

drawable counter() {
    state value = 0
    draw centered(text("Counter: \(value)"))
    draw clickHandler(function () {
        next value = value + 1
    })
}
draw at(0, 0, 100, 100, counter())
draw at(100, 0, 100, 100, counter())

Notice how each counter maintains separate state. State variables each have a persistent identifier that identifies the variable across frames. Calling a function adds its source location to the current persistent identifier. Since counter is called in two different places, we get two distinct source locations, and therefore two distinct state variables.

If we only call counter once:

drawable counter() {
    state value = 0
    draw centered(text("Counter: \(value)"))
    draw clickHandler(function () {
        next value = value + 1
    })
}
var c = counter()
draw at(0, 0, 100, 100, c)
draw at(100, 0, 100, 100, c)

then both state variables use the same persistent identifier. That means clicking in one counter increments the other one, too.

Unlike function calls, drawing a drawable doesn't add anything to the persistent identifier. Instead, drawables capture the current persistent identifier when they are created. They use this captured identifier wherever they are drawn, regardless of what the current persistent identifier happens to be.

drawable counter() {
    state value = 0
    draw centered(text("Counter: \(value)"))
    draw clickHandler(function () {
        next value = value + 1
    })
}
var c = counter()
drawable box1 {
    draw fill(srgb(1, 1, 0.5))
    draw c
}
drawable box2 {
    draw fill(gray(0.7))
    draw c
}
draw at(0, 0, 100, 100, box1)
draw at(100, 0, 100, 100, box2)

Notice how the two counters are still linked, despite being drawn from different places.

You can add your own values to the persistent identifier using a ~ statement.

drawable button(title, action) {
    draw centered(text(title))
    draw clickHandler(action)
}
state page = 0
~page {
    state counter = 0
    draw at(100, 0, 100, 100, button("Page \(page) counter: \(counter)", function () {
        next counter = counter + 1
    }))
}
draw at(0, 0, 100, 100, button("prev page", function () {
    next page = page - 1
}))
draw at(200, 0, 100, 100, button("next page", function () {
    next page = page + 1
}))

This code makes a new counter for each page using ~page. Switching pages resets the counter.

If a state variable is not accessed, it will reset to its starting value. That's why switching away and then back to a page resets the counter to zero.

You can get a persistent identifier value directly using the persistentIdentifier function.

function f() {
    return persistentIdentifier()
}
show(f() == f())
var x = f()
show(x == x)

Identity in for Loops

Using only source location as identity doesn't work well with loops. Since the loop runs the same source code repeatedly, the loop body will have the same identity each time through. To provide a better default, for loops add the current index to the persistent identifier on each iteration.

drawable counter() {
    state value = 0
    draw centered(text("Counter: \(value)"))
    draw clickHandler(function () {
        next value = value + 1
    })
}
var y = 0
for i in range(0, 5) {
    draw at(0, y, 100, 100, counter())
    y = y + 100
}

Notice how each counter is independent despite sharing a source location. It's as if the loop body were wrapped in a ~i { } statement.

This can lead to strange behavior with deletion, however:

drawable counter(name) {
    state value = 0
    draw centered(text("\(name) Counter: \(value)"))
    draw clickHandler(function () {
        next value = value + 1
    })
}
state counterNames = ["Widget", "Sprocket", "Gizmo"]
drawable deleteButton(name) {
    draw centered(text("Delete"))
    draw clickHandler(function () {
        next counterNames = remove(counterNames, name)
    })
}
var y = 0
for name in counterNames {
    draw at(0, y, 100, 100, counter(name))
    draw at(100, y, 100, 100, deleteButton(name))
    y = y + 100
}

Try incrementing the counters a bit, then deleting the "widget" counter. Note how the "sprocket" counter takes the value the "widget" counter used to have: each counter's identity depends only on the loop index.

We want each counter's identity to be based on its name, not where it appears. The for loop lets us specify a identity expression using ~:

drawable counter(name) {
    state value = 0
    draw centered(text("\(name) Counter: \(value)"))
    draw clickHandler(function () {
        next value = value + 1
    })
}
state counterNames = ["Widget", "Sprocket", "Gizmo"]
drawable deleteButton(name) {
    draw centered(text("Delete"))
    draw clickHandler(function () {
        next counterNames = remove(counterNames, name)
    })
}
var y = 0
for name in counterNames ~name {
    draw at(0, y, 100, 100, counter(name))
    draw at(100, y, 100, 100, deleteButton(name))
    y = y + 100
}

Now each counter keeps its state when the array of counters changes. As a shortcut, you can write the loop as for ~name in counterNames: this keeps you from having to type name twice.

Special State Variables and Explicit Updates

Nothing in this section is required to write DisplayScript programs, but it might be helpful if you're trying to read some of the code in the prelude or editor.

DisplayScript uses state variables to communicate with other code not written in DisplayScript. Loading an image, for example, uses an imageLoader state variable internally:

state(imageLoader) loader = __unit
state loaderState = #init
if loaderState == #init {
    next loaderState = #loading
    loader <- {
        url: "http://i.imgur.com/n7rt6cK.jpg",
        handler: function (result) {
            next loaderState = result
        }
    }
}
show(loaderState)

Putting an identifer in parentheses after state indicates that the state variable is special. Special state variables have built-in behavior beyond just persisting their value, like the imageLoader which loads an image in this example.

The <- you see is called the explicit update operator. Updating a state variable with <- is equivalent to using next value except that no next binding is created. Many special state variables don't update their value, so the next value form doesn't make much sense.

Here's another special state variable which gives us the size of the canvas we're drawing into:

state(canvasSize, __unit) canvasSize = {width: 0, height: 0}
show(canvasSize.width)

The second thing in the parentheses is an explicit state identifier. Normally state variables use the current persistent identifier, but you can change the identifier by specifying a value here. In this case, the DisplayScript runtime uses the unit value as an identifier for the canvas size, so we must use the unit value here to look it up.

Using state variables for communication means that DisplayScript does not need a C stack or FFI. Using readiness (as described in Asynchronous Operations and Readiness) lets us present this communication as a synchronous function call.