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


8.1 Persistence Tutorial

When writing goblins code without considering persistence, we simply define a outer lambda (the constructor, usually ^my-constructor) and an inner lambda (the behavior) that’s invoked when the object is sent messages. Persistence capable objects are a little different; they do still follow this general pattern, but they need to provide a way to describe themselves. Fortunately goblins provides a macro called define-actor which makes this easier (if you’re doing something more advanced where the macro is unsuitable, or simply prefer not using it, See Persistence capable objects).

Lets take the cell example we defined in the tutorial:

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

Assuming we want to upgrade this to be persistence capable, you just change it to define-actor:

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

And it’s done! Lets try it out. To use the persistence tooling, you need to define a persistence environment. What is this extra thing I hear you ask?

In short, when Goblins serializes all the objects in the graph it needs to take them from scheme procedures (which can’t be serialized) to something else. It does this by storing them with a name/label and the data the actor told Goblins it wanted. The persistence environment is simply a mapping between name/label <=> object type.

Lets define one that just has our new shiny persistence ^cell:

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

;;    |-----------------------------------|  |------|
;;            this is the label/name          object

Just remember the label/name should be unique within the object graph, or you’ll get into quite the pickle (for more information, see See Persistence Environments).

The persistence tools work with both actormaps and vats (they use the same tooling even as vats are built on actormaps). When we use vats we need to specify a store which is basically a place for the vat to save the serialized data to or find it when it starts back up.

For this example lets just use the memory store, which is ephemeral and is not actually saved to any long-term storage:

(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 our object alongside spawning the vat?

Yes we did, we don’t need to do it for all the objects in the graph, just the roots. When we call spawn-persistence-vat it checks in our store to see if there’s already persistence data and if there is it doesn’t invoke the thunk we provided, it instead restores the graph from that data and hands us back the objects. In our case though the store is empty so it spawned our cell from the thunk we provided.

Lets enter the vat and use the cell:

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

Okay we stored an apple but how do we persist this? Well, it already is persisted. By default vats are configured to store the changed objects.

An interesting debugging tool we can use however is the vat command to inspect what the persistence data for a given object looks like:

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

So lets take a more advanced example of a persistent object. Lets first build it without persistence in mind and then see some of the problems we might face making it persistence aware. Here’s a basic a player ship, it has some positions, a fire method and it can be killed, once it’s killed it just becomes some dead behavior which errors. It also has this tick that can be hooked up to a game loop and will move to player ship forward in the x axis:

(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
     [(tick) ($ x (+ ($ x) 1))]
     [(x) ($ x)]
     [(y) ($ y)]
     [(fire) "pew pew"]
     [(kill) (bcom dead-beh)]))
  active-beh)

We could just change this to define-actor and at first, maybe it’d seem like it worked, but it actually has some big problems. Let’s actually see this happen:

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

(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
     [(tick) ($ x (+ ($ x) 1))]
     [(x) ($ x)]
     [(y) ($ y)]
     [(fire) "pew pew"]
     [(kill) (bcom dead-beh)]))
  active-beh)

goblins[1]> (define ship (spawn ^player-ship 0 0))
goblins[1]> ,vat-take-object-portrait ship
(0 0)
goblins[1]> ;; Lets tick the game loop and see if x changes
goblins[1]> ($ ship 'tick)
goblins[1]> ,vat-take-object-portrait ship
(0 0)
goblins[1]> ($ ship 'kill)
goblins[1]> ,vat-take-object-portrait ship
(0 0)

As we can see, initially it looked correct. We ticked it once and we checked to see what the persistence system would get and it didn’t update, we then killed the ship and the portrait again didn’t change, how will Goblins know the ship is no longer alive.

The problem we’re facing here is that we take two static initial x, y values into the object and that’s what Goblins is seeing. We then spawn cells internally but these cells are not exposed to goblins and we’re not updating them to bcom.

So how can we write this with persistence aware? This player ship solves these problems:

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

goblins[1]> (define ship (spawn ^player-ship 0 0))
goblins[1]> ,vat-take-object-portrait ship
(0 0 #:active? #t)
goblins[1]> ;; Lets tick the game loop and see if x changes
goblins[1]> ($ ship 'tick)
goblins[1]> ,vat-take-object-portrait ship
(1 0 #:active? #t)

Instead of having internal cells we mutate, we just change the values in the constructor and Goblins is able to see that and ensure the portrait is adjusted correctly, equally switching between the two different states is done by having that be passed in to the constructor too.

In most cases, and usually when using define-actor, we need to think the behavior and state being determined by the constructor, not relying on internal state.

There are many ways to write this object and I’m going to show you two more and then put these different ideas together to create a much more advanced player ship.

We don’t have to do away with cells, we could pass cells into the constructor and then Goblins will be able to see that, but then we lose some of the old ergonomics and interface of the object, users would have to manually spawn these cells.

Here’s a solution to continue to use cells but keep 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
     [(tick) ($ x (+ ($ x) 1))]
     [(x) ($ x)]
     [(y) ($ y)]
     [(fire) "pew pew"]
     [(kill) (bcom (^player-ship bcom x y #:active? #f))]))
  (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 a refr described
;; below, only `^player-ship*` needs to be in the environment
(define my-env
  (make-persistence-env
    `((((my-cool-scheme-project game) ^player-ship) ,player-ship*))
    #:extends cell-env))

This has a pattern of having a sort of “public” constructor which allows for nice ergonomics, and maybe does some setup and then hands off to a “real” or “internal” constructor for the actor. In this case the public version both prevents a user spawning a dead ship to begin with and handles the cellification of the x and y coordinates.

Normally in Goblins an object returns behavior as a procedure from the constructor, however you can return a refr to another object and Goblins will instead of creating a new object with spawn, simply return the refr returned by the object’s constructor.

The new persistence environment provided defines a single object, just the ^player-ship*. This is because, as explained above, the ^player-ship doesn’t remain around. This also instead of using our cell we defined above, uses the one from actor-lib so we need to extend from the cell-env provided by the cell module. Persistence environments can extend from one or a list of several environments as needed.

Okay, so that’s bringing the cells back. Having a sort of “state” flag passed into the object allows us to decide if the active or dead behavior should be returned, but there are other ways to do that. Lets look at an interesting one which uses Goblin’s swappable library (for more information on this library, See 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 a
    ;; refr. use a place holder one which we will immediately switch
    ;; away from.
    (swappable (spawn (lambda _ (lambda _ 'noop)))))
  ;; Swap away from the dummy place-holder above directly to the
  ;; initial, default behavior of the alive player-ship
  ($ beh-swapper (spawn ^player-ship:ALIVE
			(spawn ^cell x)
			(spawn ^cell y)
			beh-swapper))
  ;; Return the proxy we setup.
  beh-proxy)

;; The behavior and state for the alive player ship
(define-actor (^player-ship:ALIVE bcom x y swapper)
  (methods
   [(tick) ($ x (+ ($ x) 1))]
   [(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 (- countdown 1))))]
   [(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)))

So this is returning the proxy object from swappable as the player-ship and then giving the capability to swap to the ships which swap between the alive and dead states. While this is probably overkill for our version of the player ship, we can see how if an object had many different behaviors and those behaviors had their own state associated with them, this could be a powerful way of building objects.

If you’re wondering if you could simplify this by removing the swapper and just doing a (bcom (spawn ^player-ship:DEAD ...)) this does not work, Goblins does not allow the object to become something its not, this would be a security issue.

And that’s it!

Go out and save the world :)


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