#lang racket/base (provide ide-dataspace install-ide-dataspace!) (require racket/async-channel) (require (only-in racket/list flatten)) (require racket/match) (require (only-in racket/math pi sqr)) (require racket/set) (require 2htdp/image) (require (only-in syndicate seal process-name process-behavior process)) (require (only-in syndicate/dataspace dataspace?)) (require (only-in syndicate/relay relay)) (require syndicate/actor) (require (only-in syndicate/lang current-ground-dataspace)) (require syndicate/patch) (require syndicate/protocol/standard-relay) (require syndicate/ground) (require syndicate/trace) (require syndicate/store) (require syndicate-gl/2d) (require "hsv.rkt") (struct view-position (pid pos) #:prefab) ;; assertion (struct influence (subject object) #:prefab) ;; message ;; A SpringType is one of ;; - tension - only pulls the ends together when stretched ;; - compression - only pushes the ends apart when compressed ;; - both - both tension and compression (struct spring (type subject object strength length) #:prefab) ;; assertion (struct push-view (pid delta) #:prefab) ;; message (define (random-in-range lo hi) (+ lo (* (random) (- hi lo)))) (define (tooltip touching? x y w h label-strings0) (define label-strings (flatten label-strings0)) (define label-text (apply above (map (lambda (s) (text s 22 "black")) label-strings))) (define label (overlay label-text (empty-scene (+ (image-width label-text) 10) (+ (image-height label-text) 10)))) (define (x-pos window-width) (define left (- (x) (image-width label) 10)) (define right (+ (x) w 10)) (cond [(not (negative? left)) left] [(<= (+ right (image-width label)) window-width) right] [else (+ (x) (* 1/2 w) (* -1/2 (image-width label)))])) (react (define/query-value window-width 0 (inbound (window $w _)) w) (assert #:when (touching?) (outbound (simple-sprite -10 (x-pos (window-width)) (+ (y) (* 1/2 h) (- (* 1/2 (image-height label)))) (image-width label) (image-height label) label))))) (define (actor-view name parent-pid pid is-dataspace?) (spawn #:name (list 'actor-view name pid) (field [pos #f]) (define/query-value win #f (inbound (window $w $h)) (window w h) #:on-add (when (not (pos)) (pos (make-rectangular (random-in-range 0 w) (random-in-range 0 h))))) (define color (color-by-hash pid)) (define costume (circle (if is-dataspace? 40 20) "solid" color)) (define extent (make-rectangular (image-width costume) (image-height costume))) (define (x) (real-part (pos))) (define (y) (imag-part (pos))) (define/query-value touching? #f (inbound (touching pid)) #t) (on-start (tooltip touching? x y (real-part extent) (imag-part extent) (list (format "~a" name) (format "~a" pid)))) (assert #:when (pos) (view-position pid (+ (pos) (* 1/2 extent)))) (assert #:when (pos) (outbound (simple-sprite #:touchable-id pid #:touchable-predicate in-unit-circle? 0 (x) (y) (real-part extent) (imag-part extent) costume))) (on (message (push-view pid $delta)) (when (pos) (pos (+ (pos) delta)))) (on (message (trace-notification pid _ 'action _)) (log-info "~v acting" pid) (void)) (on (message (influence pid _)) (log-info "~v influencing" pid) (void)) (on (message (influence _ pid)) (log-info "~v influenced" pid) (void)) (stop-when (message (trace-notification _ pid 'exit _))))) (define 1x1-white-rectangle (rectangle 1 1 "solid" "white")) (define (compute-link-line start-pos end-pos width) (define delta (- end-pos start-pos)) (define heading (angle delta)) (define displacement (make-polar 10 (- heading (* pi 1/2)))) (outbound (simple-sprite 1 #:rotation (* -180 (/ heading pi)) (real-part (+ start-pos displacement)) (imag-part (+ start-pos displacement)) (magnitude delta) width 1x1-white-rectangle))) (define (spawn-influence-view-factory) (spawn #:name 'influence-view-factory (field [pairs (set)]) (on (message (influence $subject $object)) (define entry (cons subject object)) (when (not (set-member? (pairs) entry)) (pairs (set-add (pairs) entry)) (react (stop-when (retracted (view-position subject _)) (pairs (set-remove (pairs) entry))) (stop-when (retracted (view-position object _)) (pairs (set-remove (pairs) entry)))) (spawn #:name (list 'influence-view entry) (field [strength 1] [line-width 0]) (define/query-value subject-pos #f (view-position subject $p) p) (define/query-value object-pos #f (view-position object $p) p) (stop-when (retracted (view-position subject _))) (stop-when (retracted (view-position object _))) (assert #:when (and (subject-pos) (object-pos) (>= (strength) 0.1)) (compute-link-line (subject-pos) (object-pos) (line-width))) (assert #:when (>= (strength) 0.1) (spring 'tension subject object 1 (+ 50 (/ 100 (strength))))) (begin/dataflow (define new-width (min 10 (floor (strength)))) (when (not (= new-width (line-width))) (line-width new-width))) (on (message (inbound (frame-event $counter _ _ $target-frame-rate))) (define update-every (inexact->exact (floor (/ target-frame-rate 3)))) (when (zero? (modulo counter update-every)) (strength (/ (strength) (expt 2 (* update-every (/ target-frame-rate))))))) (on (message (influence subject object)) (strength (+ (strength) 1)))))))) (define (spawn-layout-engine) (spawn #:name 'layout-engine (define/query-value win #f (inbound (window $w $h)) (window w h)) (define/query-hash positions (view-position $pid $pos) pid pos) (define/query-set springs ($ s (spring _ _ _ _ _)) s) (on #:when (win) (message (inbound (frame-event _ _ _ _))) (define midpoint (/ (make-rectangular (window-width (win)) (window-height (win))) 2)) (for [((this-pid pos) (in-hash (positions)))] (send! (push-view this-pid (* 1/10 (+ (- midpoint pos) (for/sum [((other-pid other-pos) (in-hash (positions))) #:when (not (equal? other-pid this-pid))] (define delta (- pos other-pos)) (when (< (magnitude delta) 10) (set! delta (make-polar 10 (random-in-range (- pi) pi)))) (make-polar (/ 1000000 (sqr (magnitude delta))) (angle delta))) (for/sum [(s (in-set (springs)))] (define other-pid (match s [(spring _ p (== this-pid) _ _) p] [(spring _ (== this-pid) p _ _) p] [_ #f])) (or (and other-pid (not (equal? this-pid other-pid)) (let ((other-pos (hash-ref (positions) other-pid #f))) (and other-pos (let () (define heading (angle (- other-pos pos))) (define current-length (magnitude (- other-pos pos))) (define ideal-length (spring-length s)) (define force (* (spring-strength s) (- current-length ideal-length))) (make-polar ((case (spring-type s) [(both) (lambda (z v) v)] [(tension) max] [(compression) min]) 0 force) heading))))) 0)))))))) )) (define (process-is-dataspace? p) (match p [(process _name _beh (? dataspace? _)) #t] [(process _name _beh (relay _ _ _ _ _ (process _inner-name _inner-beh (? dataspace? _)))) #t] [_ #f])) (define ((ide-dataspace #:exit? [exit? #t]) . boot-actions) (define from-user-thread-ch (make-async-channel)) (define user-thread (thread (lambda () (with-store ((current-trace-procedures (cons (lambda (n) (async-channel-put from-user-thread-ch n)) (current-trace-procedures)))) (run-ground boot-actions))))) (signal-background-activity! #t) (with-store ((current-trace-procedures '())) ((2d-dataspace #:label "Syndicate IDE" #:exit? exit?) (spawn #:name 'user-thread-death-monitor (field [user-thread-running? #t]) (assert #:when (user-thread-running?) 'user-thread-running) (on (message (inbound (? frame-event? _))) (when (thread-dead? user-thread) (user-thread-running? #f)))) ;; Ground dataspace (actor-view 'ground #f '() #t) (spawn #:name 'notification-relay (on (message (inbound (? frame-event? _))) (let loop () (define n (async-channel-try-get from-user-thread-ch)) (match n [#f (void)] [(trace-notification _ new-pid 'spawn (list parent-pid p)) (actor-view (process-name p) (match parent-pid [(cons 'meta _) #f] [_ parent-pid]) new-pid (process-is-dataspace? p))] [(trace-notification s o 'influence (? patch? p)) (for [(source-pid (in-set (extract-patch-pids p)))] (send! (influence (cons source-pid (cdr s)) o)))] [(trace-notification s o 'influence (message _)) (send! (influence s o))] [(? trace-notification? n) (send! n)]) (when n (loop))))) (spawn #:name 'quit-listener (on (message (inbound (key-event #\q #t _))) (assert! (outbound 'stop)))) (spawn-influence-view-factory) (spawn-layout-engine) ;; (spawn #:name 'debug ;; (on (message (? trace-notification? $n)) ;; (log-info "INBOUND: ~v --~v--> ~v" ;; (trace-notification-source n) ;; (trace-notification-type n) ;; (trace-notification-sink n)))) ))) (define install-ide-dataspace! (make-keyword-procedure (lambda (ks vs . positionals) (define installed-dataspace (current-ground-dataspace)) (current-ground-dataspace (keyword-apply ide-dataspace ks vs positionals)))))