package main import ( "bytes" "encoding/binary" "fmt" "html/template" "log" "net" "net/http" "sync" "time" ) // Vector3 represents a 3D vector type Vector3 struct { X, Y, Z float32 } // Quaternion represents a rotation quaternion type Quaternion struct { X, Y, Z, W float32 } // Transform represents a 3D transform type Transform struct { WorldPosition Vector3 WorldRotation Quaternion LocalScale Vector3 } // BoneData represents bone transformation data type BoneData struct { BoneIndex int32 Transform Transform } // BlendShapeData represents blend shape data type BlendShapeData struct { ShapeIndex int32 Weight float32 } // AvatarData represents the complete avatar data structure type AvatarData struct { RootTransform Transform Timestamp float64 BoneCount int32 Bones []BoneData BlendCount int32 BlendShapes []BlendShapeData ServerTime float64 } // AvatarSyncServer handles the avatar synchronization type AvatarSyncServer struct { playerData map[string]*AvatarData mutex sync.RWMutex } // NewAvatarSyncServer creates a new server instance func NewAvatarSyncServer() *AvatarSyncServer { return &AvatarSyncServer{ playerData: map[string]*AvatarData{ "player1": nil, "player2": nil, }, } } // SerializeAvatarData converts AvatarData to binary format func (a *AvatarData) SerializeAvatarData() ([]byte, error) { buf := new(bytes.Buffer) // Write root transform if err := binary.Write(buf, binary.LittleEndian, a.RootTransform); err != nil { return nil, err } // Write timestamp if err := binary.Write(buf, binary.LittleEndian, a.Timestamp); err != nil { return nil, err } // Write server time if err := binary.Write(buf, binary.LittleEndian, a.ServerTime); err != nil { return nil, err } // Write bone count if err := binary.Write(buf, binary.LittleEndian, a.BoneCount); err != nil { return nil, err } // Write bones for _, bone := range a.Bones { if err := binary.Write(buf, binary.LittleEndian, bone); err != nil { return nil, err } } // Write blend shape count if err := binary.Write(buf, binary.LittleEndian, a.BlendCount); err != nil { return nil, err } // Write blend shapes for _, blend := range a.BlendShapes { if err := binary.Write(buf, binary.LittleEndian, blend); err != nil { return nil, err } } return buf.Bytes(), nil } // DeserializeAvatarData converts binary data back to AvatarData func DeserializeAvatarData(data []byte) (*AvatarData, error) { buf := bytes.NewReader(data) avatar := &AvatarData{} // Read root transform if err := binary.Read(buf, binary.LittleEndian, &avatar.RootTransform); err != nil { return nil, err } // Read timestamp if err := binary.Read(buf, binary.LittleEndian, &avatar.Timestamp); err != nil { return nil, err } // Read server time if err := binary.Read(buf, binary.LittleEndian, &avatar.ServerTime); err != nil { return nil, err } // Read bone count if err := binary.Read(buf, binary.LittleEndian, &avatar.BoneCount); err != nil { return nil, err } // Read bones avatar.Bones = make([]BoneData, avatar.BoneCount) for i := int32(0); i < avatar.BoneCount; i++ { if err := binary.Read(buf, binary.LittleEndian, &avatar.Bones[i]); err != nil { return nil, err } } // Read blend shape count if err := binary.Read(buf, binary.LittleEndian, &avatar.BlendCount); err != nil { return nil, err } // Read blend shapes avatar.BlendShapes = make([]BlendShapeData, avatar.BlendCount) for i := int32(0); i < avatar.BlendCount; i++ { if err := binary.Read(buf, binary.LittleEndian, &avatar.BlendShapes[i]); err != nil { return nil, err } } return avatar, nil } // setCORSHeaders sets CORS headers for cross-origin requests func setCORSHeaders(w http.ResponseWriter) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "Content-Type") } // handleRoot serves the status page func (s *AvatarSyncServer) handleRoot(w http.ResponseWriter, r *http.Request) { setCORSHeaders(w) if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } w.Header().Set("Content-Type", "text/html") s.mutex.RLock() player1Connected := s.playerData["player1"] != nil player2Connected := s.playerData["player2"] != nil s.mutex.RUnlock() statusTemplate := `
Server is running and using binary protocol for improved performance
Note: This server uses binary protocol instead of JSON for faster data transfer.
` tmpl := template.Must(template.New("status").Parse(statusTemplate)) data := struct { Player1 bool Player2 bool }{ Player1: player1Connected, Player2: player2Connected, } tmpl.Execute(w, data) } // handleStatus provides server status information func (s *AvatarSyncServer) handleStatus(w http.ResponseWriter, r *http.Request) { setCORSHeaders(w) if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } w.Header().Set("Content-Type", "text/plain") s.mutex.RLock() defer s.mutex.RUnlock() currentTime := time.Now().Unix() status := fmt.Sprintf("Avatar Sync Server (Go Binary)\n") status += fmt.Sprintf("Server Time: %d\n", currentTime) status += fmt.Sprintf("Protocol: Binary (Little Endian)\n") status += fmt.Sprintf("Player 1: %s\n", func() string { if s.playerData["player1"] != nil { return fmt.Sprintf("Connected (last update: %.2f)", s.playerData["player1"].Timestamp) } return "No data" }()) status += fmt.Sprintf("Player 2: %s\n", func() string { if s.playerData["player2"] != nil { return fmt.Sprintf("Connected (last update: %.2f)", s.playerData["player2"].Timestamp) } return "No data" }()) w.Write([]byte(status)) } // handlePlayer handles both GET and POST requests for player data func (s *AvatarSyncServer) handlePlayer(w http.ResponseWriter, r *http.Request, playerID string) { setCORSHeaders(w) if r.Method == "OPTIONS" { w.WriteHeader(http.StatusOK) return } switch r.Method { case "GET": s.mutex.RLock() playerData := s.playerData[playerID] s.mutex.RUnlock() w.Header().Set("Content-Type", "application/octet-stream") if playerData != nil { // Update server timestamp before sending playerData.ServerTime = float64(time.Now().UnixNano()) / 1e9 data, err := playerData.SerializeAvatarData() if err != nil { http.Error(w, "Failed to serialize data", http.StatusInternalServerError) return } w.Write(data) } else { // Return empty avatar data structure emptyAvatar := &AvatarData{ RootTransform: Transform{ WorldPosition: Vector3{X: 0.0, Y: 0.0, Z: 0.0}, WorldRotation: Quaternion{X: 0.0, Y: 0.0, Z: 0.0, W: 1.0}, LocalScale: Vector3{X: 1.0, Y: 1.0, Z: 1.0}, }, Timestamp: 0.0, BoneCount: 0, Bones: []BoneData{}, BlendCount: 0, BlendShapes: []BlendShapeData{}, ServerTime: float64(time.Now().UnixNano()) / 1e9, } data, err := emptyAvatar.SerializeAvatarData() if err != nil { http.Error(w, "Failed to serialize empty data", http.StatusInternalServerError) return } w.Write(data) } case "POST": // Read binary data from request body buf := new(bytes.Buffer) _, err := buf.ReadFrom(r.Body) if err != nil { http.Error(w, "Failed to read request body", http.StatusBadRequest) return } // Deserialize avatar data avatarData, err := DeserializeAvatarData(buf.Bytes()) if err != nil { http.Error(w, "Failed to deserialize avatar data", http.StatusBadRequest) return } // Store the avatar data avatarData.ServerTime = float64(time.Now().UnixNano()) / 1e9 s.mutex.Lock() s.playerData[playerID] = avatarData s.mutex.Unlock() w.Header().Set("Content-Type", "text/plain") w.WriteHeader(http.StatusOK) w.Write([]byte(fmt.Sprintf("Data updated for %s at %.2f", playerID, avatarData.ServerTime))) log.Printf("Updated data for %s at %s", playerID, time.Now().Format("15:04:05")) default: http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } } // getLocalIP returns the local IP address func getLocalIP() string { conn, err := net.Dial("udp", "8.8.8.8:80") if err != nil { return "127.0.0.1" } defer conn.Close() localAddr := conn.LocalAddr().(*net.UDPAddr) return localAddr.IP.String() } func main() { server := NewAvatarSyncServer() // Set up routes http.HandleFunc("/", server.handleRoot) http.HandleFunc("/status", server.handleStatus) http.HandleFunc("/player1", func(w http.ResponseWriter, r *http.Request) { server.handlePlayer(w, r, "player1") }) http.HandleFunc("/player2", func(w http.ResponseWriter, r *http.Request) { server.handlePlayer(w, r, "player2") }) port := ":8080" localIP := getLocalIP() fmt.Println("Avatar Sync Server (Go Binary) starting...") fmt.Printf("Server running on:\n") fmt.Printf(" Local: http://127.0.0.1%s\n", port) fmt.Printf(" Network: http://%s%s\n", localIP, port) fmt.Printf(" External: http://0.0.0.0%s\n", port) fmt.Println() fmt.Println("Available endpoints:") fmt.Println(" GET /player1 - Get Player 1 avatar data (binary)") fmt.Println(" GET /player2 - Get Player 2 avatar data (binary)") fmt.Println(" POST /player1 - Update Player 1 avatar data (binary)") fmt.Println(" POST /player2 - Update Player 2 avatar data (binary)") fmt.Println(" GET /status - Server status") fmt.Println() fmt.Println("Protocol: Binary (Little Endian) for improved performance") fmt.Println("Press Ctrl+C to stop the server...") log.Fatal(http.ListenAndServe(port, nil)) }