Something went wrong. Try again.
home to your local SPACEGIRL 💫 arimelody.space
Something went wrong. Try again.
5.5 kB · 201 lines
Go
at dev
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202package cursor
import ( "arimelody-web/model" "fmt" "math/rand" "net/http" "strconv" "strings" "sync" "time"
"github.com/gorilla/websocket")
type CursorClient struct { ID int32 Conn *websocket.Conn Route string X float32 Y float32 Click bool Disconnected bool}
type CursorMessage struct { Data []byte Route string Exclude []*CursorClient}
func (client *CursorClient) Send(data []byte) { err := client.Conn.WriteMessage(websocket.TextMessage, data) if err != nil { client.Disconnect() }}
func (client *CursorClient) Disconnect() { client.Disconnected = true broadcast <- CursorMessage{ []byte(fmt.Sprintf("leave:%d", client.ID)), client.Route, []*CursorClient{}, }}
var clients = make(map[int32]*CursorClient)var broadcast = make(chan CursorMessage)var mutex = &sync.Mutex{}
func StartCursor(app *model.AppState) { var includes = func (clients []*CursorClient, client *CursorClient) bool { for _, c := range clients { if c.ID == client.ID { return true } } return false } log("Cursor message handler ready!")
for { message := <-broadcast mutex.Lock() for _, client := range clients { if client.Route != message.Route { continue } if includes(message.Exclude, client) { continue } client.Send(message.Data) } mutex.Unlock() }}
func handleClient(client *CursorClient) { msgType, message, err := client.Conn.ReadMessage() if err != nil { client.Disconnect() return } if msgType != websocket.TextMessage { return }
args := strings.Split(string(message), ":") if len(args) == 0 { return } switch args[0] { case "loc": if len(args) < 2 { return } client.Route = args[1]
mutex.Lock() for otherClientID, otherClient := range clients { if otherClientID == client.ID || otherClient.Route != client.Route { continue } client.Send([]byte(fmt.Sprintf("join:%d", otherClientID))) client.Send([]byte(fmt.Sprintf("pos:%d:%f:%f", otherClientID, otherClient.X, otherClient.Y))) } mutex.Unlock() broadcast <- CursorMessage{ []byte(fmt.Sprintf("join:%d", client.ID)), client.Route, []*CursorClient{ client }, } case "char": if len(args) < 2 { return } // haha, turns out using ':' as a separator means you can't type ':'s // i should really be writing byte packets, not this nonsense msg := byte(':') if len(args[1]) > 0 { msg = args[1][0] } broadcast <- CursorMessage{ []byte(fmt.Sprintf("char:%d:%c", client.ID, msg)), client.Route, []*CursorClient{ client }, } case "nochar": broadcast <- CursorMessage{ []byte(fmt.Sprintf("nochar:%d", client.ID)), client.Route, []*CursorClient{ client }, } case "click": if len(args) < 2 { return } click := 0 if args[1][0] == '1' { click = 1 } broadcast <- CursorMessage{ []byte(fmt.Sprintf("click:%d:%d", client.ID, click)), client.Route, []*CursorClient{ client }, } case "pos": if len(args) < 3 { return } x, err := strconv.ParseFloat(args[1], 32) y, err := strconv.ParseFloat(args[2], 32) if err != nil { return } client.X = float32(x) client.Y = float32(y) broadcast <- CursorMessage{ []byte(fmt.Sprintf("pos:%d:%f:%f", client.ID, client.X, client.Y)), client.Route, []*CursorClient{ client }, } }}
func Handler(app *model.AppState) http.HandlerFunc { var upgrader = websocket.Upgrader{ CheckOrigin: func (r *http.Request) bool { origin := r.Header.Get("Origin") return origin == app.Config.BaseUrl }, }
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log("Failed to upgrade to WebSocket connection: %v\n", err) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } defer conn.Close()
client := CursorClient{ ID: rand.Int31(), Conn: conn, X: 0.0, Y: 0.0, Disconnected: false, }
err = client.Conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("id:%d", client.ID))) if err != nil { client.Conn.Close() return }
mutex.Lock() clients[client.ID] = &client mutex.Unlock()
// log("Client connected: %s (%s)", fmt.Sprintf("0x%08x", client.ID), client.Conn.RemoteAddr().String())
for { if client.Disconnected { mutex.Lock() delete(clients, client.ID) client.Conn.Close() mutex.Unlock() return } handleClient(&client) } })}
func log(format string, args ...any) { logString := fmt.Sprintf(format, args...) fmt.Printf("[%s] [CURSOR] %s\n", time.Now().Format(time.UnixDate), logString)}