2018-04-06 10:37:59 +00:00
|
|
|
#lang racket/base
|
|
|
|
|
|
|
|
(require syndicate/functional-queue)
|
|
|
|
(require syndicate/dataflow)
|
|
|
|
(require racket/match)
|
|
|
|
(require racket/set)
|
|
|
|
|
|
|
|
(module+ test (require rackunit))
|
|
|
|
|
|
|
|
(require "skeleton.rkt")
|
|
|
|
(require "pattern.rkt")
|
|
|
|
(require "bag.rkt")
|
|
|
|
|
|
|
|
;; A `FID` is a Facet ID, uniquely identifying both a facet and its
|
|
|
|
;; ancestry in a `Dataspace`.
|
|
|
|
;;
|
|
|
|
;; FID = (Listof Nat)
|
|
|
|
;;
|
|
|
|
;; The first `Nat` in the list is the unique identifier for this
|
|
|
|
;; facet; the `cdr` of the list is the FID of its parent facet. No two
|
|
|
|
;; `FID`s in a `Dataspace` have the same first `Nat`.
|
|
|
|
|
|
|
|
;; A `Dataspace` is a ... TODO
|
|
|
|
|
|
|
|
;; An `Action` is either `(patch (Deltaof Assertion))` or `(message
|
|
|
|
;; Assertion)` or `(spawn BootProc)`.
|
|
|
|
(struct patch (changes) #:prefab)
|
|
|
|
(struct message (body) #:prefab)
|
|
|
|
(struct spawn (boot-proc) #:prefab)
|
|
|
|
|
|
|
|
(struct dataspace ([next-id #:mutable] ;; Nat
|
|
|
|
routing-table ;; Skeleton
|
|
|
|
facets ;; (MutableHash FID Facet)
|
|
|
|
actors ;; (MutableSetof FID)
|
|
|
|
assertions ;; (Bagof Assertion)
|
|
|
|
dataflow ;; DataflowGraph
|
|
|
|
pending-scripts ;; (MutableVectorof (Queueof (-> Any)))
|
|
|
|
[pending-actions #:mutable] ;; (Queueof Action)
|
|
|
|
)
|
|
|
|
#:transparent)
|
|
|
|
|
|
|
|
(struct facet (id ;; FID
|
|
|
|
endpoints ;; (MutableHash EID Endpoint)
|
|
|
|
[stop-scripts #:mutable] ;; (Listof Script) -- IN REVERSE ORDER
|
|
|
|
[children #:mutable] ;; (Setof FID)
|
|
|
|
)
|
|
|
|
#:transparent)
|
|
|
|
|
|
|
|
(struct endpoint (id ;; EID
|
|
|
|
[assertion #:mutable] ;; Assertion
|
|
|
|
assertion-fn ;; (-> Assertion)
|
|
|
|
handler ;; (Option SkInterest)
|
|
|
|
)
|
|
|
|
#:transparent)
|
|
|
|
|
|
|
|
;; TODO: field ownership: record actor (root facet) ID in field, check
|
|
|
|
;; it on access.
|
|
|
|
(struct field-handle (name ;; Symbol
|
|
|
|
id ;; Nat
|
|
|
|
owner ;; FID
|
|
|
|
[value #:mutable] ;; Any
|
|
|
|
)
|
|
|
|
#:methods gen:custom-write
|
|
|
|
[(define (write-proc f port mode)
|
|
|
|
(fprintf port "#<field-handle:~a:~a>" (field-handle-name f) (field-handle-id f)))]
|
|
|
|
#:property prop:procedure
|
|
|
|
(case-lambda
|
|
|
|
[(f)
|
|
|
|
(dataflow-record-observation! (dataspace-dataflow (current-dataspace)) f)
|
|
|
|
(field-handle-value f)]
|
|
|
|
[(f v)
|
|
|
|
(dataflow-record-damage! (dataspace-dataflow (current-dataspace)) f)
|
|
|
|
(set-field-handle-value! f v)]))
|
|
|
|
|
|
|
|
;; Parameterof Dataspace
|
|
|
|
(define current-dataspace (make-parameter #f))
|
|
|
|
|
|
|
|
;; Parameterof FID
|
|
|
|
(define current-facet-id (make-parameter #f))
|
|
|
|
|
|
|
|
;; Parameterof Boolean
|
|
|
|
(define in-script? (make-parameter #t))
|
|
|
|
|
|
|
|
;;---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
;; Script priorities. These are used to ensure that the results of
|
|
|
|
;; some *side effects* are visible to certain pieces of code.
|
|
|
|
|
|
|
|
(module priorities racket/base
|
|
|
|
(require (for-syntax racket/base))
|
|
|
|
|
|
|
|
(define-syntax (define-priority-levels stx)
|
|
|
|
(let loop ((counter 0) (stx (syntax-case stx ()
|
|
|
|
[(_ level ...) #'(level ...)])))
|
|
|
|
(syntax-case stx ()
|
|
|
|
[()
|
|
|
|
#'(void)]
|
|
|
|
[(#:count c)
|
|
|
|
#`(begin (define c #,counter)
|
|
|
|
(provide c))]
|
|
|
|
[(this-level more ...)
|
|
|
|
#`(begin (define this-level #,counter)
|
|
|
|
(provide this-level)
|
|
|
|
#,(loop (+ counter 1) #'(more ...)))])))
|
|
|
|
|
|
|
|
(define-priority-levels ;; highest-priority to lowest-priority
|
|
|
|
*query-priority-high*
|
|
|
|
*query-priority*
|
|
|
|
*query-handler-priority*
|
|
|
|
*normal-priority*
|
|
|
|
*gc-priority*
|
|
|
|
*idle-priority*
|
|
|
|
#:count priority-count))
|
|
|
|
|
|
|
|
(require (submod "." priorities))
|
|
|
|
|
|
|
|
;;---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
(define (make-dataspace)
|
|
|
|
(dataspace 0
|
|
|
|
(make-empty-skeleton)
|
|
|
|
(make-hash)
|
|
|
|
(mutable-set)
|
|
|
|
(make-bag)
|
|
|
|
(make-dataflow-graph)
|
|
|
|
(make-vector priority-count (make-queue))
|
|
|
|
(make-queue)))
|
|
|
|
|
|
|
|
(define (generate-id! ds)
|
|
|
|
(let ((id (dataspace-next-id ds)))
|
|
|
|
(set-dataspace-next-id! ds (+ id 1))
|
|
|
|
id))
|
|
|
|
|
|
|
|
(define (fid-parent fid)
|
|
|
|
(cdr fid))
|
|
|
|
|
|
|
|
(define (generate-fid! ds parent-fid)
|
|
|
|
(cons (generate-id! ds) parent-fid))
|
|
|
|
|
|
|
|
(define (actor-fid? fid)
|
|
|
|
(null? (fid-parent fid)))
|
|
|
|
|
|
|
|
(define (add-actor! ds boot-proc)
|
|
|
|
(define actor-fid (generate-fid! ds '()))
|
|
|
|
(set-add! (dataspace-actors ds) actor-fid)
|
2018-04-06 10:58:49 +00:00
|
|
|
(add-facet! ds actor-fid boot-proc))
|
2018-04-06 10:37:59 +00:00
|
|
|
|
|
|
|
(define (lookup-facet ds fid)
|
|
|
|
(hash-ref (dataspace-facets ds) fid #f))
|
|
|
|
|
2018-04-06 10:58:49 +00:00
|
|
|
(define-syntax-rule (with-current-facet [ds fid script?] body ...)
|
2018-04-06 10:37:59 +00:00
|
|
|
(parameterize ((current-dataspace ds)
|
|
|
|
(current-facet-id fid)
|
|
|
|
(in-script? script?))
|
|
|
|
body ...))
|
|
|
|
|
|
|
|
(define (capture-facet-context proc)
|
|
|
|
(let ((ds (current-dataspace))
|
2018-04-06 10:58:49 +00:00
|
|
|
(fid (current-facet-id)))
|
2018-04-06 10:37:59 +00:00
|
|
|
(lambda args
|
2018-04-06 10:58:49 +00:00
|
|
|
(with-current-facet [ds fid #t]
|
2018-04-06 10:37:59 +00:00
|
|
|
(apply proc args)))))
|
|
|
|
|
|
|
|
(define (pop-next-script! ds)
|
|
|
|
(define priority-levels (dataspace-pending-scripts ds))
|
|
|
|
(let loop ((level 0))
|
|
|
|
(and (< level (vector-length priority-levels))
|
|
|
|
(let ((q (vector-ref priority-levels level)))
|
|
|
|
(if (queue-empty? q)
|
|
|
|
(loop (+ level 1))
|
|
|
|
(let-values (((script q) (dequeue q)))
|
|
|
|
(vector-set! priority-levels level q)
|
|
|
|
script))))))
|
|
|
|
|
|
|
|
(define (run-all-pending-scripts! ds)
|
|
|
|
(define script (pop-next-script! ds))
|
|
|
|
(and script
|
|
|
|
(begin (script)
|
|
|
|
(dataflow-repair-damage! (dataspace-dataflow ds)
|
|
|
|
(lambda (subject-id)
|
|
|
|
(match-define (list fid eid) subject-id)
|
|
|
|
(define f (lookup-facet ds fid))
|
|
|
|
(when f
|
2018-04-06 10:58:49 +00:00
|
|
|
(with-current-facet [ds fid #f]
|
|
|
|
(define ep (hash-ref (facet-endpoints f) eid))
|
|
|
|
(define old-assertion (endpoint-assertion ep))
|
|
|
|
(define new-assertion ((endpoint-assertion-fn ep)))
|
|
|
|
(when (not (equal? old-assertion new-assertion))
|
|
|
|
(set-endpoint-assertion! ep new-assertion)
|
|
|
|
(dataspace-retract! ds old-assertion)
|
|
|
|
(dataspace-assert! ds new-assertion)
|
|
|
|
(define h (endpoint-handler ep))
|
|
|
|
(when h
|
|
|
|
(dataspace-unsubscribe! ds h)
|
|
|
|
(dataspace-subscribe! ds h)))))))
|
2018-04-06 10:37:59 +00:00
|
|
|
(run-all-pending-scripts! ds))))
|
|
|
|
|
|
|
|
(define (perform-pending-actions! ds)
|
|
|
|
(define actions (queue->list (dataspace-pending-actions ds)))
|
|
|
|
(set-dataspace-pending-actions! ds (make-queue))
|
|
|
|
(for [(action actions)]
|
|
|
|
(match action
|
|
|
|
[(patch delta)
|
|
|
|
(for [((a count) (in-bag delta))]
|
|
|
|
(match (bag-change! (dataspace-assertions ds) a count)
|
|
|
|
['present->absent (remove-assertion! (dataspace-routing-table ds) a)]
|
|
|
|
['absent->present (add-assertion! (dataspace-routing-table ds) a)]
|
|
|
|
;; 'absent->absent absurd
|
|
|
|
['present->present (void)]))] ;; i.e. no visible change
|
|
|
|
[(message body)
|
|
|
|
(send-assertion! (dataspace-routing-table ds) body)]
|
|
|
|
[(spawn boot-proc)
|
|
|
|
(add-actor! ds boot-proc)]))
|
|
|
|
(not (null? actions)))
|
|
|
|
|
|
|
|
(define (run-scripts! ds)
|
|
|
|
(define ran-a-script (run-all-pending-scripts! ds))
|
|
|
|
(define performed-an-action (perform-pending-actions! ds))
|
|
|
|
;; TODO: figure out when a dataspace should quit itself. Given the
|
|
|
|
;; mutable nature of the implementation, maybe never? It might be
|
|
|
|
;; being held elsewhere!
|
|
|
|
(or ran-a-script performed-an-action))
|
|
|
|
|
2018-04-06 10:58:49 +00:00
|
|
|
(define (add-facet! ds fid boot-proc)
|
2018-04-06 10:37:59 +00:00
|
|
|
(define parent-fid (fid-parent fid))
|
|
|
|
(define f (facet fid
|
|
|
|
(make-hash)
|
|
|
|
'()
|
|
|
|
(set)))
|
|
|
|
(hash-set! (dataspace-facets ds) fid f)
|
|
|
|
(when (pair? parent-fid)
|
|
|
|
(define pf (lookup-facet ds parent-fid))
|
|
|
|
(when pf (set-facet-children! pf (set-add (facet-children pf) fid))))
|
2018-04-06 10:58:49 +00:00
|
|
|
(with-current-facet [ds fid #f]
|
2018-04-06 10:37:59 +00:00
|
|
|
(boot-proc))
|
|
|
|
(schedule-script! ds (lambda ()
|
|
|
|
(when (and (facet-live? ds fid)
|
|
|
|
(or (and (pair? parent-fid) (not (facet-live? ds parent-fid)))
|
|
|
|
(facet-live-but-inert? ds fid)))
|
2018-04-06 10:58:49 +00:00
|
|
|
(terminate-facet! ds fid)))))
|
2018-04-06 10:37:59 +00:00
|
|
|
|
|
|
|
(define (facet-live? ds fid)
|
|
|
|
(hash-has-key? (dataspace-facets ds) fid))
|
|
|
|
|
|
|
|
(define (facet-live-but-inert? ds fid)
|
|
|
|
(define f (lookup-facet ds fid))
|
|
|
|
(and f
|
|
|
|
(hash-empty? (facet-endpoints f))
|
|
|
|
(set-empty? (facet-children f))))
|
|
|
|
|
|
|
|
(define (schedule-script! #:priority [priority *normal-priority*] ds thunk)
|
|
|
|
(define v (dataspace-pending-scripts ds))
|
2018-04-06 10:58:49 +00:00
|
|
|
(vector-set! v priority (enqueue (vector-ref v priority) (capture-facet-context thunk))))
|
2018-04-06 10:37:59 +00:00
|
|
|
|
2018-04-06 10:58:49 +00:00
|
|
|
(define (terminate-facet! ds fid)
|
2018-04-06 10:37:59 +00:00
|
|
|
(define f (lookup-facet ds fid))
|
|
|
|
(when f
|
|
|
|
(define parent-fid (fid-parent fid))
|
|
|
|
|
|
|
|
(when (pair? parent-fid)
|
|
|
|
(define pf (lookup-facet ds parent-fid))
|
|
|
|
(when pf (set-facet-children! (set-remove (facet-children pf) fid))))
|
|
|
|
|
|
|
|
(hash-remove! (dataspace-facets ds) fid)
|
|
|
|
|
|
|
|
(for [(child-fid (in-set (facet-children f)))]
|
2018-04-06 10:58:49 +00:00
|
|
|
(terminate-facet! ds child-fid))
|
2018-04-06 10:37:59 +00:00
|
|
|
|
|
|
|
;; Run stop-scripts after terminating children. This means that
|
|
|
|
;; children's stop-scripts run before ours.
|
|
|
|
(for [(script (reverse (facet-stop-scripts f)))]
|
|
|
|
(schedule-script! ds script))
|
|
|
|
|
|
|
|
(schedule-script!
|
|
|
|
ds
|
|
|
|
(lambda ()
|
|
|
|
(for [((eid ep) (in-hash (facet-endpoints f)))]
|
|
|
|
(dataflow-forget-subject! (dataspace-dataflow ds) (list fid eid))
|
|
|
|
(dataspace-retract! ds (endpoint-assertion ep))
|
|
|
|
(define h (endpoint-handler ep))
|
|
|
|
(when h (dataspace-unsubscribe! ds h)))))
|
|
|
|
|
|
|
|
(schedule-script!
|
|
|
|
#:priority *gc-priority*
|
|
|
|
ds
|
|
|
|
(lambda ()
|
|
|
|
(when (and (pair? parent-fid) (facet-live-but-inert? ds parent-fid))
|
|
|
|
(log-info "terminating ~v because it's dead and child ~v terminated" parent-fid fid)
|
2018-04-06 10:58:49 +00:00
|
|
|
(terminate-facet! ds parent-fid))))))
|
|
|
|
|
|
|
|
(define (stop-facet! ds fid stop-script)
|
|
|
|
(with-current-facet [ds (fid-parent fid) #t] ;; run in parent context wrt terminating facet
|
|
|
|
(schedule-script! ds (lambda ()
|
|
|
|
(terminate-facet! ds fid)
|
|
|
|
(schedule-script! ds stop-script)))))
|
2018-04-06 10:37:59 +00:00
|
|
|
|
|
|
|
(define (add-endpoint! ds where assertion-fn handler)
|
|
|
|
(when (in-script?)
|
|
|
|
(error 'add-endpoint!
|
|
|
|
"~a: Cannot add endpoint in script; are you missing a (react ...)?"
|
|
|
|
where))
|
2018-04-06 10:58:49 +00:00
|
|
|
(define fid (current-facet-id))
|
2018-04-06 10:37:59 +00:00
|
|
|
(define eid (generate-id! ds))
|
|
|
|
(define assertion
|
2018-04-06 10:58:49 +00:00
|
|
|
(parameterize ((current-dataflow-subject-id (list fid eid)))
|
2018-04-06 10:37:59 +00:00
|
|
|
(assertion-fn)))
|
|
|
|
(define ep (endpoint eid assertion assertion-fn handler))
|
|
|
|
(dataspace-assert! ds assertion)
|
|
|
|
(when handler (dataspace-subscribe! ds handler))
|
2018-04-06 10:58:49 +00:00
|
|
|
(hash-set! (facet-endpoints (lookup-facet ds fid)) eid ep))
|
2018-04-06 10:37:59 +00:00
|
|
|
|
|
|
|
(define (ensure-patch-action! ds)
|
|
|
|
(define old-q (dataspace-pending-actions ds))
|
|
|
|
(when (or (queue-empty? old-q) (not (patch? (queue-last old-q))))
|
|
|
|
(set-dataspace-pending-actions! ds (enqueue old-q (patch (make-bag)))))
|
|
|
|
(patch-changes (queue-last (dataspace-pending-actions ds))))
|
|
|
|
|
|
|
|
(define (dataspace-retract! ds assertion)
|
|
|
|
(when (not (void? assertion))
|
|
|
|
(bag-change! (ensure-patch-action! ds) assertion -1)))
|
|
|
|
|
|
|
|
(define (dataspace-assert! ds assertion)
|
|
|
|
(when (not (void? assertion))
|
|
|
|
(bag-change! (ensure-patch-action! ds) assertion +1)))
|
|
|
|
|
|
|
|
(define (dataspace-unsubscribe! ds h)
|
|
|
|
(remove-interest! (dataspace-routing-table ds) h))
|
|
|
|
|
|
|
|
(define (dataspace-subscribe! ds h)
|
|
|
|
(add-interest! (dataspace-routing-table ds) h))
|
|
|
|
|
|
|
|
(define (dataspace-send! ds body)
|
|
|
|
(set-dataspace-pending-actions! ds (enqueue (dataspace-pending-actions ds) (message body))))
|
|
|
|
|
|
|
|
(module+ test
|
|
|
|
;; TODO: move somewhere sensible
|
|
|
|
;; Thin veneers over `struct` for declaring intent.
|
|
|
|
(define-syntax-rule (assertion-struct item ...) (struct item ... #:prefab))
|
|
|
|
(define-syntax-rule (message-struct item ...) (struct item ... #:prefab))
|
|
|
|
|
|
|
|
(message-struct set-box (new-value))
|
|
|
|
(assertion-struct box-state (value))
|
|
|
|
|
|
|
|
;; TODO: move somewhere sensible
|
|
|
|
(assertion-struct observe (specification))
|
|
|
|
|
|
|
|
(define ds (make-dataspace))
|
|
|
|
(add-actor! ds
|
|
|
|
(lambda ()
|
|
|
|
(define current-value (field-handle 'current-value
|
|
|
|
(generate-id! (current-dataspace))
|
|
|
|
(current-facet-id)
|
|
|
|
0))
|
|
|
|
(add-endpoint! (current-dataspace)
|
|
|
|
'stop-when-ten
|
2018-04-06 10:58:49 +00:00
|
|
|
(lambda ()
|
|
|
|
(when (= (current-value) 10)
|
|
|
|
(stop-facet! (current-dataspace)
|
|
|
|
(current-facet-id)
|
|
|
|
(lambda ()
|
|
|
|
(printf "box: terminating\n"))))
|
|
|
|
(void))
|
2018-04-06 10:37:59 +00:00
|
|
|
#f)
|
|
|
|
(add-endpoint! (current-dataspace)
|
|
|
|
'assert-box-state
|
2018-04-06 10:58:49 +00:00
|
|
|
(lambda () (box-state (current-value)))
|
2018-04-06 10:37:59 +00:00
|
|
|
#f)
|
|
|
|
(add-endpoint! (current-dataspace)
|
|
|
|
'on-message-set-box
|
2018-04-06 10:58:49 +00:00
|
|
|
(lambda () (observe (set-box (capture (discard)))))
|
2018-04-06 10:37:59 +00:00
|
|
|
(skeleton-interest (list struct:set-box #f)
|
|
|
|
'()
|
|
|
|
'()
|
|
|
|
'((0 0))
|
|
|
|
(capture-facet-context
|
|
|
|
(lambda (op new-value)
|
|
|
|
(when (eq? '! op)
|
|
|
|
(schedule-script!
|
|
|
|
(current-dataspace)
|
2018-04-06 10:58:49 +00:00
|
|
|
(lambda ()
|
|
|
|
(printf "new-value ~a ~v\n" op new-value)
|
|
|
|
(current-value new-value))))))))))
|
2018-04-06 10:37:59 +00:00
|
|
|
(add-actor! ds
|
|
|
|
(lambda ()
|
|
|
|
(add-endpoint! (current-dataspace)
|
|
|
|
'stop-when-retracted-observe-set-box
|
2018-04-06 10:58:49 +00:00
|
|
|
(lambda () (observe (observe (set-box (discard)))))
|
2018-04-06 10:37:59 +00:00
|
|
|
(skeleton-interest (list struct:observe (list struct:set-box #f))
|
|
|
|
'()
|
|
|
|
'()
|
|
|
|
'()
|
|
|
|
(capture-facet-context
|
|
|
|
(lambda (op)
|
|
|
|
(when (eq? '- op)
|
2018-04-06 10:58:49 +00:00
|
|
|
(stop-facet!
|
2018-04-06 10:37:59 +00:00
|
|
|
(current-dataspace)
|
2018-04-06 10:58:49 +00:00
|
|
|
(current-facet-id)
|
|
|
|
(lambda ()
|
|
|
|
(printf "client: box has gone\n"))))))))
|
2018-04-06 10:37:59 +00:00
|
|
|
(add-endpoint! (current-dataspace)
|
|
|
|
'on-asserted-box-state
|
2018-04-06 10:58:49 +00:00
|
|
|
(lambda () (observe (box-state (capture (discard)))))
|
2018-04-06 10:37:59 +00:00
|
|
|
(skeleton-interest (list struct:box-state #f)
|
|
|
|
'()
|
|
|
|
'()
|
|
|
|
'((0 0))
|
|
|
|
(capture-facet-context
|
|
|
|
(lambda (op v)
|
|
|
|
(when (eq? '+ op)
|
|
|
|
(schedule-script!
|
|
|
|
(current-dataspace)
|
2018-04-06 10:58:49 +00:00
|
|
|
(lambda ()
|
|
|
|
(printf "v ~a ~v\n" op v)
|
|
|
|
(dataspace-send! (current-dataspace)
|
|
|
|
(set-box (+ v 1))))))))))))
|
2018-04-06 10:37:59 +00:00
|
|
|
|
|
|
|
(require racket/pretty)
|
|
|
|
(pretty-print ds)
|
|
|
|
(let loop ((i 0))
|
|
|
|
(printf "--- i = ~v\n" i)
|
|
|
|
(when (run-scripts! ds)
|
|
|
|
(pretty-print ds)
|
|
|
|
(loop (+ i 1))))
|
|
|
|
(pretty-print ds)
|
|
|
|
)
|