Move networking stuff into its own file
[mudsync.git] / mudsync.scm
index cdc03fd3d408f7dc1dc2e164adbba1e46b50fea2..a7226339bb280afec2a3c07d8e04c16d7e4a521b 100644 (file)
 ;;; You should have received a copy of the GNU General Public License
 ;;; along with Mudsync.  If not, see <http://www.gnu.org/licenses/>.
 
-(use-modules (8sync systems actors)
-             (8sync systems actors debug)
-             (8sync agenda)
-             (ice-9 format)
-             (ice-9 match)
-             (gdbm)
-             (oop goops))
-
+(define-module (mudsync)
+  #:use-module (8sync systems actors)
+  #:use-module (8sync agenda)
+  #:use-module (ice-9 format)
+  #:use-module (ice-9 match)
+  #:use-module (oop goops))
 
 \f
-;;; Networking
-;;; ==========
-
-(define %default-server #f)
-(define %default-port 8889)
-
-(define-class <network-manager> (<actor>)
-  (server-socket #:accessor nm-server-socket)
-  ;; mapping of client -> client-id
-  (clients #:accessor nm-clients
-           #:init-thunk make-hash-table)
-  ;; send input to this actor
-  (send-input-to #:getter nm-send-input-to
-                 #:init-keyword #:send-input-to)
-  (message-handler
-   #:init-value
-   (make-action-dispatch
-    ((start-listening actor message)
-     (nm-install-socket actor (message-ref message 'server %default-server)
-                        (message-ref message 'port %default-port)))
-    ((send-to-client actor message client data)
-     (nm-send-to-client-id actor client data)))))
-
-(define-method (nm-close-everything (nm <network-manager>) remove-from-agenda)
-  "Shut it down!"
-  ;; close all clients
-  (hash-for-each
-   (lambda (_ client)
-     (close client)
-     (if remove-from-agenda
-         (8sync-port-remove client)))
-   (nm-clients nm))
-  ;; reset the clients list
-  (set! (nm-clients) (make-hash-table))
-  ;; close the server
-  (close (nm-server-socket nm))
-  (if remove-from-agenda
-      (8sync-port-remove (nm-server-socket nm))))
-
-;; Maximum number of backlogged connections when we listen
-(define %maximum-backlog-conns 128)     ; same as SOMAXCONN on Linux 2.X,
-                                        ; says the intarwebs
-
-(define (nm-install-socket nm server port)
-  "Install socket on SERVER with PORT"
-  (let ((s (socket PF_INET  ; ipv4
-                   SOCK_STREAM  ; two-way connection-based byte stream
-                   0))
-        (addr (if server
-                  (inet-pton AF_INET server)
-                  INADDR_LOOPBACK)))
-    ;; Totally mimed from the Guile manual.  Not sure if we need this, but:
-    ;; http://www.unixguide.net/network/socketfaq/4.5.shtml
-    (setsockopt s SOL_SOCKET SO_REUSEADDR 1) ; reuse port even if port is busy
-    ;; Connecting to a non-specific address:
-    ;;   (bind s AF_INET INADDR_ANY port)
-    ;; Should this be an option?  Guess I don't know why we'd need it
-    ;; @@: If we wanted to support listening on a particular hostname,
-    ;;   could see 8sync's irc.scm...
-    (bind s AF_INET addr port)
-    ;; Listen to connections
-    (listen s %maximum-backlog-conns)
-
-    ;; Throw a system-error rather than block on an (accept)
-    ;; that has nothing to do
-    (fcntl s F_SETFL
-           (logior O_NONBLOCK
-                   (fcntl s F_GETFL)))
-
-    ;; @@: This is used in Guile's http server under the commit:
-    ;;       * module/web/server/http.scm (http-open): Ignore SIGPIPE. Keeps the
-    ;;         server from dying in some circumstances.
-    ;;   (sigaction SIGPIPE SIG_IGN)
-    ;; Will this break other things that use pipes for us though?
-
-    (set! (nm-server-socket nm) s)
-
-    (format #t "Listening for clients in pid: ~s\n" (getpid))
-    (8sync-port s #:read (lambda (s) (nm-new-client nm s)))
-    ;; TODO: set up periodic close of idle connections?
-    ))
-
-(define (nm-new-client nm s)
-  "Handle new client coming in to socket S"
-  (let* ((client-connection (accept s))
-         (client-details (cdr client-connection))
-         (client (car client-connection)))
-    (format #t "New client: ~s\n" client-details)
-    (format #t "Client address: ~s\n"
-            (gethostbyaddr
-             (sockaddr:addr client-details)))
-
-    (let ((client-id (big-random-number)))
-      (hash-set! (nm-clients nm) client-id client)
-      ;; @@: Do we need an 8sync-port-wait here?
-      ;;   Is such a thing even possible? :\
-      (8sync-port client #:read (nm-make-client-receive nm client-id))
-      (<- nm (nm-send-input-to nm) 'new-client #:client client-id))))
-
-(define (nm-make-client-receive nm client-id)
-  "Make a method to receive client data"
-  (let ((buffer '()))
-    (define (reset-buffer)
-      (set! buffer '()))
-    (define (should-read-char client)
-      (and (not (port-closed? client))
-           (char-ready? client)
-           (not (eof-object? (peek-char client)))))
-    (define (receive-handler client)
-      (while (should-read-char client)
-        (set! buffer (cons (read-char client) buffer))
-        (match buffer
-          (;; @@: Do we need the "char?"
-           (#\newline #\return (? char? line-chars) ...)
-           (let ((ready-line (list->string (reverse line-chars))))
-             ;; reset buffer
-             (set! buffer '())
-             ;; run it
-             (nm-handle-line nm client client-id ready-line)))
-          (_ #f)))
-      ;; Shut things down on closed port or EOF object
-      (cond
-       ((port-closed? client)
-        (nm-handle-port-closed nm client client-id))
-       ((and (char-ready? client)
-             (eof-object? (peek-char client)))
-        (nm-handle-port-eof nm client client-id))))
-    receive-handler))
-
-(define (nm-handle-port-closed nm client client-id)
-  "Handle a closed port"
-  (format #t "DEBUG: handled closed port ~x\n" client-id)
-  (8sync-port-remove client)
-  (hash-remove! (nm-clients nm) client-id))
-
-(define-method (nm-handle-port-eof nm client client-id)
-  "Handle seeing an EOF on port"
-  (format #t "DEBUG: handled eof-object on port ~x\n" client-id)
-  (close client)
-  (8sync-port-remove client)
-  (hash-remove! (nm-clients nm) client-id))
-
-(define-method (nm-handle-line nm client client-id line)
-  "Handle an incoming line of input from a client"
-  (<- nm (nm-send-input-to nm) 'client-input
-      #:data line
-      #:client client-id))
-
-(define-method (nm-send-to-client-id nm client-id data)
-  "Send DATA to TO-CLIENT id"
-  (define client-obj (hash-ref (nm-clients nm) client-id))
-  (if (not client-obj)
-      (throw 'no-such-client
-             "Asked to send data to client but that client doesn't exist"
-             #:client-id client-id
-             #:data data))
-  (display data client-obj))
-
 ; (ez-run-hive hive (list (bootstrap-message hive (actor-id nm) 'start-listening)))
 
 
@@ -377,9 +217,37 @@ with an anonymous persona"
 ;;; derive from this class.
 ;;; And all of them need a GM!
 
-(define-class <game-actor> (<actor>)
+(define-class <gameobj> (<actor>)
+  ;; location id
+  (loc #:init-value #f
+       #:accessor gameobj-loc)
+  ;; game master id
   (gm #:init-keyword #:gm
-      #:getter game-actor-gm))
+      #:getter gameobj-gm)
+  ;; a name to be known by
+  (name #:init-keyword #:name
+        #:accessor gameobj-name)
+
+  ;; how to print our name
+  (name-f #:init-keyword #:name-f
+          #:getter gameobj-name-f
+          #:init-value (wrap gameobj-simple-name-f))
+
+  ;; Name aliases
+  (aliases #:init-keyword #:aliases
+           #:init-value '())
+
+  ;; Commands we can handle
+  (dirobj-commands #:init-value '())
+  (indirobj-commands #:init-value '())
+
+  ;; Commands we can handle by being something's container
+  (contain-commands #:init-value #f))
+
+
+(define (gameobj-simple-name-f gameobj)
+  "Simplest version: return ourselves for our name."
+  (gameobj-name gameobj))
 
 
 \f
@@ -425,11 +293,28 @@ with an anonymous persona"
     (reply-message actor message
                    #:val (slot-ref actor slot))))
 
+(define always (const #t))
+
+;; TODO: remove hack
+(define full-command list)
+
+;; TODO: fill these in
+(define cmatch-just-verb #f)
+(define cmatch-direct-verb #f)
+(define cmatch-direct-obj #f)
+
+(define %room-contain-commands
+  (list
+   (full-command "look" cmatch-just-verb always 'look-room)
+   (full-command "look" cmatch-direct-obj always 'look-member)
+   (full-command "go" cmatch-just-verb always 'go-where)
+   (full-command "go" cmatch-direct-obj always 'go-exit)))
 
-(define-class <room> (<game-actor>)
-  (name #:init-keyword #:name)
+;; TODO: Subclass from container?
+(define-class <room> (<gameobj>)
   (desc #:init-value ""
-               #:init-keyword #:desc)
+        #:init-keyword #:desc)
+  ;; TODO: Switch this to be loc based
   ;; Uses a hash table like a set (values ignored)
   (occupants #:init-thunk make-hash-table)
   ;; A list of <exit>
@@ -438,6 +323,9 @@ with an anonymous persona"
   ;; @@: Maybe eventually <room> will inherit from some more general
   ;;  game object class
 
+  (contain-commands
+   #:init-value %room-contain-commands)
+
   (message-handler
    #:allocation #:each-subclass
    #:init-value
@@ -461,7 +349,7 @@ claim to point to."
   (for-each
    (lambda (exit)
      (define new-exit
-       (<-wait room (game-actor-gm room) 'lookup-room
+       (<-wait room (gameobj-gm room) 'lookup-room
                #:symbol (exit-to-symbol exit)))
 
      (set! (exit-to-address exit) new-exit))
@@ -473,15 +361,16 @@ claim to point to."
 ;;; Players
 ;;; =======
 
-(define-class <player> (<game-actor>)
+(define-class <player> (<gameobj>)
   (username #:init-keyword #:username
             #:accessor player-username)
-  ;; location id
-  (loc #:init-value #f
-       #:accessor player-loc)
   ;; Connection id
   (client #:accessor player-client)
 
+  (self-commands
+   #:init-value '()
+   #:accessor player-self-commands)
+
   (message-handler
    #:init-value
    (make-action-dispatch
@@ -491,9 +380,9 @@ claim to point to."
 ;;; player message handlers
 
 (define-mhandler (player-set-loc! player message id)
-  (format #t "DEBUG: Location set to ~s for player ~s"
+  (format #t "DEBUG: Location set to ~s for player ~s\n"
           id (actor-id-actor player))
-  (set! (player-loc player) id))
+  (set! (gameobj-loc player) id))
 
 (define-mhandler (player-init! player message)
   (player-look-around player))
@@ -503,16 +392,16 @@ claim to point to."
 (define (player-look-around player)
   (define room-name
     (message-ref
-     (<-wait player (player-loc player) 'get-name)
+     (<-wait player (gameobj-loc player) 'get-name)
      'val))
   (define room-desc
     (message-ref
-     (<-wait player (player-loc player) 'get-desc)
+     (<-wait player (gameobj-loc player) 'get-desc)
      'val))
   (define message-text
     (format #f "**~a**\n~a\n" room-name room-desc))
 
-  (<- player (game-actor-gm player) 'write-home #:text message-text))
+  (<- player (gameobj-gm player) 'write-home #:text message-text))
 
 \f
 ;;; Debugging stuff