Next: , Up: Persistence   [Contents][Index]


8.1 Persistence tutorial

When writing Goblins code without considering persistence, we simply define an outer procedure (the constructor, usually ^my-constructor) and an inner procedure (the behavior, most often using an anonymous lambda) that’s called when the object receives a message. Persistence capable objects are a little different; while they still follow this general pattern, they also need to provide a way to describe themselves. Fortunately, Goblins provides the define-actor syntax which makes this easier. For more advanced cases where define-actor is unsuitable, see Persistence capable objects for the lower-level details of creating persistent objects.

Let’s start with the cell example from the Tutorial:

(define* (^cell bcom #:optional (val #f))
  (case-lambda
    (()         ; get
     val)
    ((new-val)  ; set
     (bcom (^cell bcom new-val)))))

To make ^cell persistence capable, define can be changed to define-actor:

(use-modules (goblins))

(define-actor (^cell bcom #:optional (val #f))
  (case-lambda
    (()         ; get
     val)
    ((new-val)  ; set
     (bcom (^cell bcom new-val)))))

And it’s done! Now, let’s try it out. To use persistence, there’s an additional prerequisite: a persistence environment must be defined. “What is this extra thing?”, I hear you ask.

In short, serializing an object graph requires translating each object from a Scheme procedure representing its current behavior (which cannot be serialized) to a serializable self-portrait. Goblins does this by storing each object as a mapping of labels to self-portraits. In the ^cell constructor above, define-actor has taken care of the self-portrait for us.

To restore an object graph from its serialized form, a persistence environment is required. A persistence environment is simply a mapping between labels and constructor procedures. To demonstrate, let’s define a simple persistence environment that only has our shiny new persistent ^cell in it:

(define my-env
  (make-persistence-env
   `((((my-cool-scheme-project cell) ^cell) ,^cell))))

;;    |-----------------------------------| |----|
;;                    label                  proc

Just remember that each label must be unique within the environment, or else you’ll get into quite the pickle (for more information, see Persistence environments).

The persistence tools work with both Vats and the lower-level Actormaps (since vats are built on top of actormaps). Persistent vats require a store: a place for the vat to read and write serialized object graph data. One such store is the Memory store, which is ephemeral and not actually saved to any kind of long-term storage. A persistent vat with a memory store can be spawned like this:

(use-modules (goblins)
             (goblins persistence-store memory))

(define memory-store (make-memory-store))
(define-values (my-vat my-cell)
  (spawn-persistent-vat
    my-env
    (lambda ()
      (spawn ^cell))
    memory-store))

Hold on, did we just spawn a ^cell object while spawning the vat?

Yes, we did! Notably, this is not necessary for all the objects in the graph, just the roots (the objects that spawn all the other objects). When spawn-persistent-vat is called it first checks in the store to see if there’s any saved data. If not then the thunk (the lambda with no arguments above) is called to spawn the root objects for the first time. If there is saved data then it is deserialized and the associated persistence environment is used to restore the object graph.

Since the store is empty initially, the value of my-cell is a reference to a freshly spawned cell:

> ,enter-vat my-vat
goblins[1]> ($ my-cell 'apple)

Now that the persistent vat and its roots have spawned, the object graph has automatically been saved to the store. By default, a persistent vat will continuously update the store upon changes to object state.

When debugging it can be useful to see the serialized form of an object. To do this from the REPL, use the vat-take-object-portrait metacommand:

goblins[1]> ,vat-take-object-portrait my-cell
(apple)

Now, let’s look at a more advanced example of a persistent object. We’ll first build it without persistence in mind and then see some of the problems we might encounter when making an object persistable. Below is an object representing a player ship in a space shooter video game.

(use-modules (goblins actor-lib methods))

(define (^player-ship bcom init-x init-y)
  (define x (spawn ^cell init-x))
  (define y (spawn ^cell init-y))
  (define (dead-beh)
    (error "I'm already dead!"))
  (define active-beh
    (methods
     ((x) ($ x))
     ((y) ($ y))
     ((fire) "pew pew")
     ((kill) (bcom dead-beh))
     ((tick) ($ x (1+ ($ x))))))
  active-beh)

This ship object contains the (x, y) coordinates for its current position. These coordinates are stored in cells so that the position can be changed over time. The ship spawns in the “active” state and responds to several methods:

To make the ship persist, we could try simply changing define to define-actor:

(define-actor (^player-ship bcom init-x init-y)
  (define x (spawn ^cell init-x))
  (define y (spawn ^cell init-y))
  (define (dead-beh)
    (error "I'm already dead!"))
  (define active-beh
    (methods
     ((x) ($ x))
     ((y) ($ y))
     ((fire) "pew pew")
     ((kill) (bcom dead-beh))
     ((tick) ($ x (1+ ($ x))))))
  active-beh)

At first it might seem like that was enough, but there are actually some big problems when we try it out:

goblins[1]> (define ship (spawn ^player-ship 0 0))
goblins[1]> ,vat-take-object-portrait ship
(0 0)

So far, so good. Now let’s try ticking the game loop to see if the X coordinate changes:

goblins[1]> ($ ship 'tick)
goblins[1]> ,vat-take-object-portrait ship
(0 0)

Hmm, that’s not right. Well, what happens if we kill the player?

goblins[1]> ($ ship 'kill)
goblins[1]> ,vat-take-object-portrait ship
(0 0)

Okay, that’s the same as before. Doesn’t seem like the object would be restored in the dead state. What’s happening?

The problem is that the ^player-ship constructor takes two arguments (the initial X and Y coordinates) but then spawns cells internally. These cells live within the closure of the object and are thus inaccessible to the persistence system (and anything else, really). We need a constructor that accepts all of the values that are needed to serialize a complete self-portrait. The new player ship definition below does exactly that:

(define-actor (^player-ship bcom x y #:key (active? #t))
  (define (dead-beh)
    (error "I'm already dead!"))
  (define active-beh
    (methods
     ((x) x)
     ((y) y)
     ((fire) "pew pew")
     ((kill) (bcom (^player-ship bcom x y #:active? #f)))
     ((tick) (bcom (^player-ship bcom (1+ x) y)))))
  (if active?
      active-beh
      dead-beh))

Let’s test it out:

goblins[1]> (define ship (spawn ^player-ship 0 0))
goblins[1]> ,vat-take-object-portrait ship
(0 0 #:active? #t)

Looks good so far. Now let’s try ticking it:

goblins[1]> ($ ship 'tick)
goblins[1]> ,vat-take-object-portrait ship
(1 0 #:active? #t)

Great! Finally, let’s make sure we can kill it:

goblins[1]> ($ ship 'kill)
goblins[1]> ,vat-take-object-portrait ship
(1 0 #:active? #f)

Everything works!

Instead of mutating internal cells, we simply pass updated values to the constructor using bcom and now define-actor’s built-in self-portrait contains all of the necessary data to restore the player ship later. In summary, when using define-actor we need to think of both the behavior and state of an object as being determined by the constructor rather than relying upon internal state.

There are many other ways to implement this object. Let’s look at two more and then put these different ideas together to create a much more advanced player ship.

First, we don’t have to do away with cells. We could pass cells into the constructor directly, but then we would lose some of the ergonomics from the previous version of the constructor; users would have to manually spawn these cells. Here’s a solution that continues to use cells but keeps the old interface:

(use-modules (goblins actor-lib cell))

;; IMPORTANT: note this is now ^player-ship* not ^player-ship.
(define-actor (^player-ship* bcom x y #:key (active? #t))
  (define (dead-beh)
    (error "I'm already dead!"))
  (define active-beh
    (methods
     ((x) ($ x))
     ((y) ($ y))
     ((fire) "pew pew")
     ((kill) (bcom (^player-ship bcom x y #:active? #f)))
     ((tick) ($ x (1+ ($ x))))))
  (if active?
      active-beh
      dead-beh))

(define (^player-ship bcom x y)
  (spawn ^player-ship*
         (spawn ^cell x)
         (spawn ^cell y)))

;; NOTE: Because of the feature of returning an object described
;; below, only ^player-ship* needs to be in the environment.
(define my-env
  (make-persistence-env
    `((((game) ^player-ship) ,^player-ship*))
    #:extends cell-env))

This wrapper pattern involves a public constructor with the desired interface that does some setup and then calls a private constructor. In this case, the public interface both prevents a user from spawning a dead ship and handles the “cellification” of the X and Y coordinates.

A typical constructor returns a procedure that is used as the object’s initial behavior. However, it’s also possible to return a reference to another object! Instead of creating a new object, Goblins will simply return that reference to the caller of the constructor.

Despite ^player-ship being a wrapper around ^player-ship*, both procedures are defined at the top-level. In fact, this pattern would not work if ^player-ship* was encapsulated within ^player-ship. This is because the persistence environment needs access to the private constructor for the purpose of restoration.

The new persistence environment above references only the private constructor, ^player-ship*. This is because, as explained above, the ^player-ship is not a true constructor; it merely delegates to ^player-ship*. Note also that, instead of using the version of ^cell we defined above, the example uses the one from the (goblins actor-lib cell) module. So, our persistence environments needs to extend cell-env provided by that module. Persistence environments can be composed together.

Onto the second variant. Having a sort of “state” flag passed into the constructor allows the ship to decide if the active or dead behavior should be used, but there are other ways to do this. Let’s look at a particularly interesting technique which uses the Swappable module, (goblins actor-lib swappable):

(use-modules (goblins actor-lib swappable))

;; Public constructor again, except this time spawning the swappable
;; object as well as cellifying the x and y coordinates.
(define (^player-ship bcom x y)
  (define-values (beh-proxy beh-swapper)
    ;; NOTE: Due to how swappable works, it must be spawned with an
    ;; initial reference. We'll use a placeholder one which we will
    ;; immediately switch away from.
    (swappable (spawn (lambda _ (lambda _ 'noop)))))
  ;; Swap away from the dummy placeholder above to an alive ship.
  ($ beh-swapper (spawn ^player-ship/alive
			(spawn ^cell x)
			(spawn ^cell y)
			beh-swapper))
  ;; Return the proxy.
  beh-proxy)

;; The behavior and state for the alive player ship.
(define-actor (^player-ship/alive bcom x y swapper)
  (methods
   ((tick) ($ x (1+ ($ x))))
   ((x) ($ x))
   ((y) ($ y))
   ((fire) "pew pew")
   ((kill) ($ swapper (spawn ^player-ship/dead x y swapper)))))

;; The behavior and state for the dead player ship.
(define-actor (^player-ship/dead bcom x y swapper #:optional (countdown 30))
  (methods
   ((tick)
    ;; When dead instead of moving, just wait until the countdown
    ;; has reached zero and then swap to alive.
    (if (zero? countdown)
        ($ swapper (spawn ^player-ship/alive x y swapper))
        (bcom (^player-ship/dead bcom x y swapper (1- countdown)))))
   ((x) ($ x))
   ((y) ($ y))
   ((fire) 'noop)   ; cannot fire when dead
   ((kill) 'noop))) ; cannot be killed when dead

;; Note because the swappable and cell objects are being passed in and
;; thus need to be persisted in the graph, we *MUST* include those in
;; persistence environment.
(define game-env
  (make-persistence-env
   `((((game) ^player-ship/alive) ,^player-ship/alive)
     (((game) ^player-ship/dead) ,^player-ship/dead))
   #:extends (list swappable-env cell-env)))

In the above example, the ^player-ship constructor is returning the proxy from swappable as the reference for the player ship. The proxy will forward messages to the object representing the ship’s current state. The capability to swap the state of the ship is passed to the ^player-ship/alive and ^player-ship/dead constructors. While this is probably overkill for our player ship, this example illustrates that swappable can be a powerful way to implement an object with many different possible behaviors.

It may be tempting to simplify this by removing the swapper and using bcom like (bcom (spawn ^player-ship/dead ...)), but this does not work. bcom accepts a procedure representing the new behavior, not an object reference. In other words, Goblins does not allow an object to become something that it’s not (that would be a security issue).

And that’s it for the tutorial! Now, go out and save the world!


Next: Persistence capable objects, Up: Persistence   [Contents][Index]