143 lines
4.5 KiB
Clojure
143 lines
4.5 KiB
Clojure
;;; ChatRoom — process-based chat room using spawn/send/receive
|
|
;;;
|
|
;;; This is a single-VM demo showing BEAM concurrency primitives.
|
|
;;; Run with: mix clje.run examples/chat_room.clje
|
|
;;;
|
|
;;; For a multi-terminal chat experience, see:
|
|
;;; examples/tcp_chat_server.clje and examples/tcp_chat_client.clje
|
|
|
|
(ns ChatRoom
|
|
(:require [clje.core :refer :all]
|
|
[Enum] [IO] [Map] [Process] [String]))
|
|
|
|
;; ── Room loop ─────────────────────────────────────────────────────
|
|
;; Manages members map of {username → pid} and broadcasts messages.
|
|
|
|
(defn run-loop [state]
|
|
(receive
|
|
[:join username pid]
|
|
(let [members (assoc (:members state) username pid)]
|
|
(send pid #el[:welcome username (count members)])
|
|
;; Notify existing members
|
|
(doseq [[_name member-pid] (:members state)]
|
|
(send member-pid #el[:system (str username " joined the room")]))
|
|
(recur (assoc state :members members)))
|
|
|
|
[:message from body] :guard [(is-binary body)]
|
|
(do
|
|
(doseq [[_name pid] (:members state)]
|
|
(send pid #el[:chat from body]))
|
|
(recur state))
|
|
|
|
[:leave username]
|
|
(let [new-members (dissoc (:members state) username)]
|
|
(doseq [[_name pid] new-members]
|
|
(send pid #el[:system (str username " left the room")]))
|
|
(recur (assoc state :members new-members)))
|
|
|
|
[:who reply-pid]
|
|
(do
|
|
(send reply-pid #el[:members (Map/keys (:members state))])
|
|
(recur state))
|
|
|
|
:shutdown
|
|
(do
|
|
(doseq [[_name pid] (:members state)]
|
|
(send pid :room-closed))
|
|
:ok)
|
|
|
|
:after 5000
|
|
(if (== (count (:members state)) 0)
|
|
:empty-timeout
|
|
(recur state))))
|
|
|
|
;; ── User listener ─────────────────────────────────────────────────
|
|
;; Collects messages received by a user and prints them.
|
|
|
|
(defn listen [username messages]
|
|
(receive
|
|
[:welcome _name member-count]
|
|
(do
|
|
(println (str " [" username " sees: Welcome! " member-count " user(s) here]"))
|
|
(ChatRoom/listen username messages))
|
|
|
|
[:chat from body]
|
|
(do
|
|
(println (str " [" username " sees: " from "> " body "]"))
|
|
(ChatRoom/listen username (cons #el[from body] messages)))
|
|
|
|
[:system text]
|
|
(do
|
|
(println (str " [" username " sees: * " text "]"))
|
|
(ChatRoom/listen username messages))
|
|
|
|
:room-closed
|
|
(println (str " [" username " sees: room closed]"))
|
|
|
|
:dump
|
|
messages
|
|
|
|
:after 2000
|
|
(do
|
|
(println (str " [" username " done listening]"))
|
|
messages)))
|
|
|
|
;; ── Run the demo ────────────────────────────────────────────────────
|
|
|
|
(println "=== ChatRoom Demo ===\n")
|
|
|
|
;; Start the room
|
|
(let [room (spawn (fn [] (ChatRoom/run-loop {:owner "system" :members {}})))
|
|
alice-listener (spawn (fn [] (ChatRoom/listen "alice" (list))))
|
|
bob-listener (spawn (fn [] (ChatRoom/listen "bob" (list))))
|
|
carol-listener (spawn (fn [] (ChatRoom/listen "carol" (list))))]
|
|
|
|
;; Alice joins
|
|
(println "Alice joins...")
|
|
(send room #el[:join "alice" alice-listener])
|
|
(Process/sleep 100)
|
|
|
|
;; Bob joins
|
|
(println "\nBob joins...")
|
|
(send room #el[:join "bob" bob-listener])
|
|
(Process/sleep 100)
|
|
|
|
;; Alice sends a message
|
|
(println "\nAlice sends a message...")
|
|
(send room #el[:message "alice" "Hello everyone!"])
|
|
(Process/sleep 100)
|
|
|
|
;; Carol joins
|
|
(println "\nCarol joins...")
|
|
(send room #el[:join "carol" carol-listener])
|
|
(Process/sleep 100)
|
|
|
|
;; Bob sends a message
|
|
(println "\nBob sends a message...")
|
|
(send room #el[:message "bob" "Hey Alice! Welcome Carol!"])
|
|
(Process/sleep 100)
|
|
|
|
;; Carol sends a message
|
|
(println "\nCarol sends a message...")
|
|
(send room #el[:message "carol" "Thanks Bob!"])
|
|
(Process/sleep 100)
|
|
|
|
;; Check who's online
|
|
(println "\nWho's online?")
|
|
(send room #el[:who *self*])
|
|
(receive
|
|
[:members names]
|
|
(println (str " Online: " (Enum/join names ", "))))
|
|
|
|
;; Bob leaves
|
|
(println "\nBob leaves...")
|
|
(send room #el[:leave "bob"])
|
|
(Process/sleep 100)
|
|
|
|
;; Shutdown the room
|
|
(println "\nShutting down room...")
|
|
(send room :shutdown)
|
|
(Process/sleep 200)
|
|
|
|
(println "\n=== Demo complete ==="))
|