initial upload
This commit is contained in:
384
Unity-Master/Assets/avatar_sync_server.go
Normal file
384
Unity-Master/Assets/avatar_sync_server.go
Normal file
@ -0,0 +1,384 @@
|
||||
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 := `
|
||||
<html>
|
||||
<head><title>Avatar Sync Server (Go Binary)</title></head>
|
||||
<body>
|
||||
<h1>Avatar Sync Server (Go Binary)</h1>
|
||||
<p>Server is running and using binary protocol for improved performance</p>
|
||||
<h2>Available Endpoints:</h2>
|
||||
<ul>
|
||||
<li>/player1 - Player 1 data (binary)</li>
|
||||
<li>/player2 - Player 2 data (binary)</li>
|
||||
<li>/status - Server status</li>
|
||||
</ul>
|
||||
<h2>Player Status:</h2>
|
||||
<ul>
|
||||
<li>Player 1: {{if .Player1}}Connected{{else}}No data{{end}}</li>
|
||||
<li>Player 2: {{if .Player2}}Connected{{else}}No data{{end}}</li>
|
||||
</ul>
|
||||
<p><strong>Note:</strong> This server uses binary protocol instead of JSON for faster data transfer.</p>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user