Initialize Redis-backed RoomRegistry for room-to-pod assignment and add Pod-specific game.join handling logic with NATS subscriptions. Update cache-busting version for client assets.
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 2m30s
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 2m30s
This commit is contained in:
@@ -234,7 +234,7 @@ window.onWasmReady = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Cache Management - Version wird bei jedem Build aktualisiert
|
// Cache Management - Version wird bei jedem Build aktualisiert
|
||||||
const CACHE_VERSION = 1767553871926; // Wird durch Build-Prozess ersetzt
|
const CACHE_VERSION = 1767555402485; // Wird durch Build-Prozess ersetzt
|
||||||
|
|
||||||
// Fetch mit Cache-Busting
|
// Fetch mit Cache-Busting
|
||||||
async function fetchWithCache(url) {
|
async function fetchWithCache(url) {
|
||||||
|
|||||||
@@ -291,7 +291,7 @@
|
|||||||
<!-- WASM Execution -->
|
<!-- WASM Execution -->
|
||||||
<script>
|
<script>
|
||||||
// Cache-Busting für JavaScript-Dateien (wird beim Build aktualisiert)
|
// Cache-Busting für JavaScript-Dateien (wird beim Build aktualisiert)
|
||||||
const BUILD_VERSION = 1767553871926;
|
const BUILD_VERSION = 1767555402485;
|
||||||
document.write('<script src="wasm_exec.js?v=' + BUILD_VERSION + '"><\/script>');
|
document.write('<script src="wasm_exec.js?v=' + BUILD_VERSION + '"><\/script>');
|
||||||
document.write('<script src="game.js?v=' + BUILD_VERSION + '"><\/script>');
|
document.write('<script src="game.js?v=' + BUILD_VERSION + '"><\/script>');
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ var (
|
|||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
globalWorld *game.World
|
globalWorld *game.World
|
||||||
serverID string // Eindeutige Server-ID für diesen Pod
|
serverID string // Eindeutige Server-ID für diesen Pod
|
||||||
|
roomRegistry *RoomRegistry // Redis-basierte Room-Registry
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -46,7 +47,14 @@ func main() {
|
|||||||
if err := server.InitLeaderboard(redisAddr); err != nil {
|
if err := server.InitLeaderboard(redisAddr); err != nil {
|
||||||
log.Fatal("❌ Konnte nicht zu Redis verbinden: ", err)
|
log.Fatal("❌ Konnte nicht zu Redis verbinden: ", err)
|
||||||
}
|
}
|
||||||
log.Printf("✅ Verbunden mit Redis: %s", redisAddr)
|
log.Printf("✅ Verbunden mit Redis (Leaderboard): %s", redisAddr)
|
||||||
|
|
||||||
|
// 1c. Room-Registry initialisieren
|
||||||
|
var err error
|
||||||
|
roomRegistry, err = NewRoomRegistry(redisAddr, serverID)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("❌ Konnte Room-Registry nicht initialisieren: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 2. NATS VERBINDUNG mit Reconnect-Logik
|
// 2. NATS VERBINDUNG mit Reconnect-Logik
|
||||||
natsURL := getEnv("NATS_URL", "nats://localhost:4222")
|
natsURL := getEnv("NATS_URL", "nats://localhost:4222")
|
||||||
@@ -125,10 +133,25 @@ func main() {
|
|||||||
|
|
||||||
log.Printf("📥 JOIN empfangen: Name=%s, RoomID=%s (Pod: %s)", req.Name, roomID, serverID)
|
log.Printf("📥 JOIN empfangen: Name=%s, RoomID=%s (Pod: %s)", req.Name, roomID, serverID)
|
||||||
|
|
||||||
|
// Prüfe Room-Registry: Welcher Pod soll diesen Raum hosten?
|
||||||
|
claimed, assignedPod, err := roomRegistry.ClaimRoom(roomID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("❌ Room-Registry Fehler: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn Raum einem anderen Pod zugewiesen ist, ignoriere den JOIN
|
||||||
|
if !claimed && assignedPod != serverID {
|
||||||
|
log.Printf("⏩ Raum '%s' gehört zu Pod %s, ignoriere JOIN auf Pod %s", roomID, assignedPod, serverID)
|
||||||
|
// Republish JOIN an den richtigen Pod (spezifischer Channel)
|
||||||
|
ec.Publish("game.join."+assignedPod, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
// Raum finden oder erstellen
|
// Raum finden oder erstellen (nur wenn wir der Owner sind)
|
||||||
room, exists := rooms[roomID]
|
room, exists := rooms[roomID]
|
||||||
if !exists {
|
if !exists {
|
||||||
log.Printf("🆕 Erstelle neuen Raum: '%s' auf Pod %s", roomID, serverID)
|
log.Printf("🆕 Erstelle neuen Raum: '%s' auf Pod %s", roomID, serverID)
|
||||||
@@ -158,7 +181,45 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("❌ Fehler beim Subscribe auf game.join:", err)
|
log.Fatal("❌ Fehler beim Subscribe auf game.join:", err)
|
||||||
}
|
}
|
||||||
log.Printf("👂 Lausche auf 'game.join' in Queue Group 'game-servers'... (Sub Valid: %v)", sub.IsValid())
|
log.Printf("👂 Lausche auf 'game.join' in Queue Group 'room-handlers'... (Sub Valid: %v)", sub.IsValid())
|
||||||
|
|
||||||
|
// 3b. HANDLER: Pod-spezifische JOINs (für Weiterleitung)
|
||||||
|
podJoinChannel := "game.join." + serverID
|
||||||
|
_, err = ec.Subscribe(podJoinChannel, func(req *game.JoinRequest) {
|
||||||
|
playerID := req.Name
|
||||||
|
if playerID == "" {
|
||||||
|
playerID = "Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
roomID := req.RoomID
|
||||||
|
if roomID == "" {
|
||||||
|
roomID = "lobby"
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("📥 Pod-spezifischer JOIN empfangen: Name=%s, RoomID=%s (Pod: %s)", req.Name, roomID, serverID)
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
// Raum muss bereits existieren
|
||||||
|
room, exists := rooms[roomID]
|
||||||
|
if !exists {
|
||||||
|
log.Printf("⚠️ Raum '%s' existiert nicht auf Pod %s", roomID, serverID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spieler hinzufügen
|
||||||
|
room.AddPlayer(playerID, req.Name)
|
||||||
|
|
||||||
|
// Session speichern
|
||||||
|
playerSessions[playerID] = room
|
||||||
|
log.Printf("✅ Spieler '%s' ist Raum '%s' beigetreten (weitergeleitet zu Pod: %s).", playerID, roomID, serverID)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("❌ Fehler beim Subscribe auf "+podJoinChannel+":", err)
|
||||||
|
}
|
||||||
|
log.Printf("👂 Lausche auf pod-spezifische JOINs: %s", podJoinChannel)
|
||||||
|
|
||||||
// 4. HANDLER: GAME START (broadcast - alle Pods empfangen, nur der mit dem Raum reagiert)
|
// 4. HANDLER: GAME START (broadcast - alle Pods empfangen, nur der mit dem Raum reagiert)
|
||||||
_, _ = ec.Subscribe("game.start", func(req *game.StartRequest) {
|
_, _ = ec.Subscribe("game.start", func(req *game.StartRequest) {
|
||||||
|
|||||||
105
cmd/server/room_registry.go
Normal file
105
cmd/server/room_registry.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RoomRegistry verwaltet die Zuordnung von Räumen zu Pods via Redis
|
||||||
|
type RoomRegistry struct {
|
||||||
|
client *redis.Client
|
||||||
|
ctx context.Context
|
||||||
|
podID string // Eindeutige Pod-ID
|
||||||
|
ttl time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRoomRegistry erstellt eine neue Room-Registry
|
||||||
|
func NewRoomRegistry(redisAddr string, podID string) (*RoomRegistry, error) {
|
||||||
|
client := redis.NewClient(&redis.Options{
|
||||||
|
Addr: redisAddr,
|
||||||
|
Password: "", // kein Password
|
||||||
|
DB: 0, // default DB
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Test connection
|
||||||
|
_, err := client.Ping(ctx).Result()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Redis connection failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("✅ Redis verbunden: %s", redisAddr)
|
||||||
|
|
||||||
|
return &RoomRegistry{
|
||||||
|
client: client,
|
||||||
|
ctx: ctx,
|
||||||
|
podID: podID,
|
||||||
|
ttl: 5 * time.Minute, // Räume verfallen nach 5 Minuten Inaktivität
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClaimRoom versucht, einen Raum für diesen Pod zu claimen
|
||||||
|
// Gibt zurück: (claimed bool, assignedPodID string, error)
|
||||||
|
func (rr *RoomRegistry) ClaimRoom(roomID string) (bool, string, error) {
|
||||||
|
key := fmt.Sprintf("room:%s:pod", roomID)
|
||||||
|
|
||||||
|
// Versuche den Raum zu claimen (nur wenn noch nicht existiert)
|
||||||
|
success, err := rr.client.SetNX(rr.ctx, key, rr.podID, rr.ttl).Result()
|
||||||
|
if err != nil {
|
||||||
|
return false, "", fmt.Errorf("failed to claim room: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if success {
|
||||||
|
// Erfolgreich geclaimed!
|
||||||
|
log.Printf("🏠 Raum '%s' für Pod %s geclaimed", roomID, rr.podID)
|
||||||
|
return true, rr.podID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raum existiert bereits - hole den zugewiesenen Pod
|
||||||
|
assignedPod, err := rr.client.Get(rr.ctx, key).Result()
|
||||||
|
if err == redis.Nil {
|
||||||
|
// Key existiert nicht mehr - retry
|
||||||
|
return rr.ClaimRoom(roomID)
|
||||||
|
} else if err != nil {
|
||||||
|
return false, "", fmt.Errorf("failed to get room pod: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raum gehört einem anderen Pod
|
||||||
|
return false, assignedPod, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewRoom erneuert die TTL eines Raums
|
||||||
|
func (rr *RoomRegistry) RenewRoom(roomID string) error {
|
||||||
|
key := fmt.Sprintf("room:%s:pod", roomID)
|
||||||
|
return rr.client.Expire(rr.ctx, key, rr.ttl).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRoomPod gibt den Pod zurück, der einen Raum hostet
|
||||||
|
func (rr *RoomRegistry) GetRoomPod(roomID string) (string, error) {
|
||||||
|
key := fmt.Sprintf("room:%s:pod", roomID)
|
||||||
|
podID, err := rr.client.Get(rr.ctx, key).Result()
|
||||||
|
if err == redis.Nil {
|
||||||
|
return "", fmt.Errorf("room not found")
|
||||||
|
}
|
||||||
|
return podID, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReleaseRoom gibt einen Raum frei
|
||||||
|
func (rr *RoomRegistry) ReleaseRoom(roomID string) error {
|
||||||
|
key := fmt.Sprintf("room:%s:pod", roomID)
|
||||||
|
return rr.client.Del(rr.ctx, key).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsMyRoom prüft, ob dieser Pod der Owner eines Raums ist
|
||||||
|
func (rr *RoomRegistry) IsMyRoom(roomID string) bool {
|
||||||
|
podID, err := rr.GetRoomPod(roomID)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return podID == rr.podID
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user