;;; 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 ==="))