;;;; cl-scopes/core - here comes the real action (defpackage :scopes/core (:use :common-lisp) (:local-nicknames (:actor :scopes/core/actor) (:async :scopes/util/async) (:config :scopes/config) (:message :scopes/core/message) (:shape :scopes/shape) (:util :scopes/util) (:alx :alexandria) (:lp :lparallel) (:lpq :lparallel.queue)) (:export #:action-spec #:define-actions #:*root* #:default-setup #:actions #:find-service #:run-services #:setup-services #:shutdown #:base-context #:context #:add-action #:config #:name #:handle-message #:do-print #:echo)) (in-package :scopes/core) ;;;; actions (defclass action-spec () ((pattern :reader pattern :initarg :pattern :initform nil) (handlers :accessor handlers :initarg :handlers))) (defun define-action (pattern &rest handlers) (make-instance 'action-spec :pattern pattern :handlers handlers)) (defun define-actions (&rest acts) (mapcar #'(lambda (act) (apply #'define-action (car act) (cdr act))) acts)) (defun select (msg acts) (let ((h (shape:head msg))) ;(hdlrs nil)) (dolist (a acts) (when (match (pattern a) h) (return-from select (handlers a)))))) #| (dolist (hdlr (handlers a)) (push hdlr hdlrs)))) hdlrs))|# ;(nreverse hdlrs))) (defun match (pat h) (dolist (item pat) (if (and item (not (eq item (pop h)))) (return-from match nil))) t) (defun find-action (pat acts) (dolist (a acts) (if (equal (pattern a) pat) (return-from find-action a)))) ;;;; context, services (defvar *root* nil) (defclass base-context (actor:actor) ((actions :accessor actions :initform nil))) (defclass context (base-context) ((config :reader config :initarg :config) (name :reader name :initarg :name) (services :reader services :initform (make-hash-table)))) (defclass service (context actor:bg-actor) ((task :accessor task :initform nil))) (defclass root-service (service actor:fg-actor) ()) (defun default-setup (cfg &optional (cls 'context) &rest args &key &allow-other-keys) (apply #'actor:create #'handle-message cls :config cfg :name (config:name cfg) args)) (defun find-service (name &optional (parent *root*)) (with-slots (services) parent (when services (gethash name services)))) (defun setup-services (&optional (cfg config:*root*)) (async:init) (setf *root* (actor:make-actor #'handle-message 'root-service :config cfg)) (dolist (c (reverse (config:children cfg))) (add-service *root* c))) (defun run-services (&optional (cfg config:*root*)) (unwind-protect (progn (setup-services cfg) (actor:start *root*)) (shutdown))) (defun shutdown () (dolist (ctx (alx:hash-table-values (services *root*))) (funcall (config:shutdown (config ctx)) ctx)) (if (task *root*) (actor:stop *root*)) (async:finish)) (defun add-action (ctx pat hdlr) (let* ((acts (actions ctx)) (act (find-action pat acts))) (if act (push hdlr (handlers act)) (push (make-instance 'action-spec :pattern pat :handlers (list hdlr)) (actions ctx))))) (defun add-service (ctx cfg) (with-slots (services) ctx (let ((child (funcall (config:setup cfg) cfg))) (when child (dolist (a (config:actions cfg)) (add-action child (car a) (cadr a))) (setf (gethash (config:name cfg) services) child))))) (defgeneric handle-message (ctx msg) (:method ((ctx base-context) msg) (do-actions ctx msg))) (defun do-actions (ctx msg &optional (acts #'actions)) (let ((hdlrs (select msg (funcall acts ctx)))) (if hdlrs (mapcar #'(lambda (hdlr) (funcall hdlr ctx msg)) hdlrs) (util:lgw "no action selected" msg)))) ;;;; some simple predefined actions (defun echo (ctx msg &key (domain :scopes) (action :echo)) (let ((cust (actor:customer msg))) (if cust (let* ((h (shape:head msg)) (new-msg (message:create `(,domain ,action ,@(cddr h)) :data (shape:data msg)))) (actor:send cust new-msg)) (util:lgw "customer missing" msg)))) (defun do-print (ctx msg) (declare (ignore ctx)) (format t "~&~s~%" msg))