Files
2026-03-09 23:09:46 -04:00

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