jim800121chen c54f16fca0 Initial commit: visionA monorepo with local-tool subproject
local-tool/: visionA-local desktop app
- M1: Wails shell + Go server + Next.js UI + Mock mode (macOS dmg ready)
- M2: i18n (zh-TW/en) + Settings 4-tab refactor
- M3: Embedded Python 3.12 runtime (python-build-standalone) + KneronPLUS wheels
- M4: Windows Inno Setup script (build on Windows runner)
- M5: Linux AppImage script + udev rule (build on Linux runner)
- M6: ffmpeg (GPL, pending legal review) + yt-dlp bundled
- Lifecycle: watchServer health check, fatal native dialog,
            Wails IPC raise endpoint, stale process cleanup

.autoflow/: full PRD / Design Spec / Architecture / Testing docs
            (4 rounds tri-party discussion + cross review)
.github/workflows/: macOS / Windows / Linux build CI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 22:10:38 +08:00

107 lines
2.1 KiB
Go

package ws
import (
"encoding/json"
"sync"
"github.com/gorilla/websocket"
)
type Client struct {
Conn *websocket.Conn
Send chan []byte
}
type Subscription struct {
Client *Client
Room string
done chan struct{} // used by RegisterSync to wait for completion
}
type RoomMessage struct {
Room string
Message []byte
}
type Hub struct {
rooms map[string]map[*Client]bool
register chan *Subscription
unregister chan *Subscription
broadcast chan *RoomMessage
mu sync.RWMutex
}
func NewHub() *Hub {
return &Hub{
rooms: make(map[string]map[*Client]bool),
register: make(chan *Subscription, 10),
unregister: make(chan *Subscription, 10),
broadcast: make(chan *RoomMessage, 100),
}
}
func (h *Hub) Run() {
for {
select {
case sub := <-h.register:
h.mu.Lock()
if h.rooms[sub.Room] == nil {
h.rooms[sub.Room] = make(map[*Client]bool)
}
h.rooms[sub.Room][sub.Client] = true
h.mu.Unlock()
if sub.done != nil {
close(sub.done)
}
case sub := <-h.unregister:
h.mu.Lock()
if clients, ok := h.rooms[sub.Room]; ok {
if _, exists := clients[sub.Client]; exists {
delete(clients, sub.Client)
close(sub.Client.Send)
}
}
h.mu.Unlock()
case msg := <-h.broadcast:
h.mu.RLock()
if clients, ok := h.rooms[msg.Room]; ok {
for client := range clients {
select {
case client.Send <- msg.Message:
default:
close(client.Send)
delete(clients, client)
}
}
}
h.mu.RUnlock()
}
}
}
func (h *Hub) Register(sub *Subscription) {
h.register <- sub
}
// RegisterSync registers a subscription and blocks until the Hub has processed it,
// ensuring the client is in the room before returning.
func (h *Hub) RegisterSync(sub *Subscription) {
sub.done = make(chan struct{})
h.register <- sub
<-sub.done
}
func (h *Hub) Unregister(sub *Subscription) {
h.unregister <- sub
}
func (h *Hub) BroadcastToRoom(room string, data interface{}) {
jsonData, err := json.Marshal(data)
if err != nil {
return
}
h.broadcast <- &RoomMessage{Room: room, Message: jsonData}
}