diff options
Diffstat (limited to 'server')
| -rwxr-xr-x | server/cyberjump | bin | 0 -> 9442714 bytes | |||
| -rw-r--r-- | server/go.mod | 7 | ||||
| -rw-r--r-- | server/go.sum | 4 | ||||
| -rw-r--r-- | server/main.go | 263 |
4 files changed, 274 insertions, 0 deletions
diff --git a/server/cyberjump b/server/cyberjump Binary files differnew file mode 100755 index 0000000..00d4ec1 --- /dev/null +++ b/server/cyberjump diff --git a/server/go.mod b/server/go.mod new file mode 100644 index 0000000..5ef24cc --- /dev/null +++ b/server/go.mod @@ -0,0 +1,7 @@ +module cyberjump + +go 1.21 + +require github.com/gorilla/websocket v1.5.1 + +require golang.org/x/net v0.17.0 // indirect diff --git a/server/go.sum b/server/go.sum new file mode 100644 index 0000000..272772f --- /dev/null +++ b/server/go.sum @@ -0,0 +1,4 @@ +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= diff --git a/server/main.go b/server/main.go new file mode 100644 index 0000000..cd31356 --- /dev/null +++ b/server/main.go @@ -0,0 +1,263 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "log" + "net/http" + "sync" + "sync/atomic" + "time" + + "github.com/gorilla/websocket" +) + +type playerState struct { + ID string `json:"id"` + X float64 `json:"x"` + Y float64 `json:"y"` + VX float64 `json:"vx"` + VY float64 `json:"vy"` + Height float64 `json:"height"` + Layer string `json:"layer"` + Cycle int `json:"cycle"` + UpdatedAt int64 `json:"updatedAt"` +} + +type stateMessage struct { + Type string `json:"type"` + X float64 `json:"x"` + Y float64 `json:"y"` + VX float64 `json:"vx"` + VY float64 `json:"vy"` + Height float64 `json:"height"` + Layer string `json:"layer"` + Cycle int `json:"cycle"` +} + +type welcomeMessage struct { + Type string `json:"type"` + ID string `json:"id"` +} + +type snapshotMessage struct { + Type string `json:"type"` + Players []playerState `json:"players"` +} + +type client struct { + id string + conn *websocket.Conn + hub *hub + mu sync.Mutex +} + +type hub struct { + mu sync.RWMutex + clients map[string]*client + states map[string]playerState + ids atomic.Uint64 +} + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func newHub() *hub { + return &hub{ + clients: make(map[string]*client), + states: make(map[string]playerState), + } +} + +func (h *hub) addClient(conn *websocket.Conn) *client { + id := fmt.Sprintf("ghost-%04d", h.ids.Add(1)) + client := &client{id: id, conn: conn, hub: h} + + h.mu.Lock() + h.clients[id] = client + h.mu.Unlock() + + return client +} + +func (h *hub) removeClient(id string) { + h.mu.Lock() + delete(h.clients, id) + delete(h.states, id) + h.mu.Unlock() + h.broadcastSnapshot() +} + +func (h *hub) updateState(id string, message stateMessage) { + h.mu.Lock() + h.states[id] = playerState{ + ID: id, + X: message.X, + Y: message.Y, + VX: message.VX, + VY: message.VY, + Height: message.Height, + Layer: message.Layer, + Cycle: message.Cycle, + UpdatedAt: time.Now().UnixMilli(), + } + h.mu.Unlock() + h.broadcastSnapshot() +} + +func (h *hub) snapshot() []playerState { + h.mu.RLock() + defer h.mu.RUnlock() + + players := make([]playerState, 0, len(h.states)) + for _, state := range h.states { + players = append(players, state) + } + return players +} + +func (h *hub) clientCount() int { + h.mu.RLock() + defer h.mu.RUnlock() + return len(h.clients) +} + +func (h *hub) broadcastSnapshot() { + payload, err := json.Marshal(snapshotMessage{Type: "snapshot", Players: h.snapshot()}) + if err != nil { + log.Printf("snapshot marshal error: %v", err) + return + } + + h.mu.RLock() + clients := make([]*client, 0, len(h.clients)) + for _, connected := range h.clients { + clients = append(clients, connected) + } + h.mu.RUnlock() + + for _, connected := range clients { + connected.writeJSON(payload) + } +} + +func (c *client) writeJSON(payload []byte) { + c.mu.Lock() + defer c.mu.Unlock() + if err := c.conn.WriteMessage(websocket.TextMessage, payload); err != nil { + log.Printf("write error for %s: %v", c.id, err) + } +} + +func (c *client) sendWelcome() error { + payload, err := json.Marshal(welcomeMessage{Type: "welcome", ID: c.id}) + if err != nil { + return err + } + c.writeJSON(payload) + return nil +} + +func (c *client) readLoop() { + defer func() { + c.hub.removeClient(c.id) + _ = c.conn.Close() + }() + + if err := c.sendWelcome(); err != nil { + log.Printf("welcome error for %s: %v", c.id, err) + return + } + + for { + _, payload, err := c.conn.ReadMessage() + if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { + log.Printf("read error for %s: %v", c.id, err) + } + return + } + + var message stateMessage + if err := json.Unmarshal(payload, &message); err != nil { + continue + } + + if message.Type != "state" { + continue + } + + c.hub.updateState(c.id, message) + } +} + +func main() { + addr := flag.String("addr", ":8080", "HTTP service address") + flag.Parse() + + h := newHub() + mux := http.NewServeMux() + + mux.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Printf("websocket upgrade error: %v", err) + return + } + + client := h.addClient(conn) + go client.readLoop() + }) + + mux.HandleFunc("/ping", func(w http.ResponseWriter, _ *http.Request) { + withCORS(w) + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("pong")) + }) + + mux.HandleFunc("/status", func(w http.ResponseWriter, _ *http.Request) { + withCORS(w) + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]any{ + "status": "online", + "players": h.clientCount(), + }) + }) + + mux.HandleFunc("/servers", func(w http.ResponseWriter, r *http.Request) { + withCORS(w) + w.Header().Set("Content-Type", "application/json") + + host := r.Host + if host == "" { + host = "localhost:8080" + } + + scheme := "ws" + if r.TLS != nil { + scheme = "wss" + } + + _ = json.NewEncoder(w).Encode(map[string]any{ + "servers": []map[string]string{{ + "url": fmt.Sprintf("%s://%s/ws", scheme, host), + "region": "local", + }}, + }) + }) + + log.Printf("CyberJump relay listening on %s", *addr) + if err := http.ListenAndServe(*addr, mux); err != nil { + log.Fatal(err) + } +} + +func withCORS(w http.ResponseWriter) { + w.Header().Set("Access-Control-Allow-Origin", "*") +} |
