|
|
|
|
@ -34,6 +34,11 @@ type Hub struct {
|
|
|
|
|
mu sync.RWMutex
|
|
|
|
|
logger *slog.Logger
|
|
|
|
|
|
|
|
|
|
// nearbySubscriptions maps target hero -> viewers subscribed to its movement updates.
|
|
|
|
|
nearbySubscriptions map[int64]map[int64]struct{}
|
|
|
|
|
// viewerSubscriptions maps viewer hero -> targets they are currently subscribed to.
|
|
|
|
|
viewerSubscriptions map[int64]map[int64]struct{}
|
|
|
|
|
|
|
|
|
|
// OnConnect is called when a client finishes registration.
|
|
|
|
|
// Set by the engine to push initial state. May be nil.
|
|
|
|
|
OnConnect func(heroID int64)
|
|
|
|
|
@ -69,6 +74,8 @@ func NewHub(logger *slog.Logger) *Hub {
|
|
|
|
|
broadcast: make(chan model.WSEnvelope, 256),
|
|
|
|
|
Incoming: make(chan model.ClientMessage, 256),
|
|
|
|
|
logger: logger,
|
|
|
|
|
nearbySubscriptions: make(map[int64]map[int64]struct{}),
|
|
|
|
|
viewerSubscriptions: make(map[int64]map[int64]struct{}),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -102,6 +109,9 @@ func (h *Hub) Run() {
|
|
|
|
|
remaining++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if remaining == 0 {
|
|
|
|
|
h.removeNearbySubscriptionsLocked(heroID)
|
|
|
|
|
}
|
|
|
|
|
h.mu.Unlock()
|
|
|
|
|
h.logger.Info("client disconnected", "hero_id", heroID, "remaining_same_hero", remaining)
|
|
|
|
|
|
|
|
|
|
@ -144,8 +154,36 @@ func (h *Hub) SendToHero(heroID int64, msgType string, payload any) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
env := model.NewWSEnvelope(msgType, payload)
|
|
|
|
|
|
|
|
|
|
var nearbyEnv *model.WSEnvelope
|
|
|
|
|
var nearbySubs map[int64]struct{}
|
|
|
|
|
if msgType == "hero_move" {
|
|
|
|
|
var move model.HeroMovePayload
|
|
|
|
|
switch v := payload.(type) {
|
|
|
|
|
case model.HeroMovePayload:
|
|
|
|
|
move = v
|
|
|
|
|
case *model.HeroMovePayload:
|
|
|
|
|
if v != nil {
|
|
|
|
|
move = *v
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
move = model.HeroMovePayload{}
|
|
|
|
|
}
|
|
|
|
|
nearbyPayload := model.NearbyHeroMovePayload{
|
|
|
|
|
HeroID: heroID,
|
|
|
|
|
X: move.X,
|
|
|
|
|
Y: move.Y,
|
|
|
|
|
TargetX: move.TargetX,
|
|
|
|
|
TargetY: move.TargetY,
|
|
|
|
|
Speed: move.Speed,
|
|
|
|
|
Heading: move.Heading,
|
|
|
|
|
}
|
|
|
|
|
envBuilt := model.NewWSEnvelope("nearby_hero_move", nearbyPayload)
|
|
|
|
|
nearbyEnv = &envBuilt
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h.mu.RLock()
|
|
|
|
|
defer h.mu.RUnlock()
|
|
|
|
|
nearbySubs = h.nearbySubscriptions[heroID]
|
|
|
|
|
for client := range h.clients {
|
|
|
|
|
if client.heroID == heroID {
|
|
|
|
|
select {
|
|
|
|
|
@ -156,8 +194,66 @@ func (h *Hub) SendToHero(heroID int64, msgType string, payload any) {
|
|
|
|
|
h.unregister <- c
|
|
|
|
|
}(client)
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if nearbyEnv != nil && nearbySubs != nil {
|
|
|
|
|
if _, ok := nearbySubs[client.heroID]; ok {
|
|
|
|
|
select {
|
|
|
|
|
case client.send <- *nearbyEnv:
|
|
|
|
|
default:
|
|
|
|
|
go func(c *Client) {
|
|
|
|
|
h.unregister <- c
|
|
|
|
|
}(client)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
h.mu.RUnlock()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SetNearbySubscriptions replaces the viewer's subscriptions with the provided target hero IDs.
|
|
|
|
|
func (h *Hub) SetNearbySubscriptions(viewerID int64, targets []int64) {
|
|
|
|
|
h.mu.Lock()
|
|
|
|
|
defer h.mu.Unlock()
|
|
|
|
|
h.removeNearbySubscriptionsLocked(viewerID)
|
|
|
|
|
if len(targets) == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
next := make(map[int64]struct{}, len(targets))
|
|
|
|
|
for _, targetID := range targets {
|
|
|
|
|
if targetID == viewerID || targetID <= 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
next[targetID] = struct{}{}
|
|
|
|
|
subs := h.nearbySubscriptions[targetID]
|
|
|
|
|
if subs == nil {
|
|
|
|
|
subs = make(map[int64]struct{})
|
|
|
|
|
h.nearbySubscriptions[targetID] = subs
|
|
|
|
|
}
|
|
|
|
|
subs[viewerID] = struct{}{}
|
|
|
|
|
}
|
|
|
|
|
if len(next) > 0 {
|
|
|
|
|
h.viewerSubscriptions[viewerID] = next
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Hub) removeNearbySubscriptionsLocked(viewerID int64) {
|
|
|
|
|
existing := h.viewerSubscriptions[viewerID]
|
|
|
|
|
if len(existing) == 0 {
|
|
|
|
|
delete(h.viewerSubscriptions, viewerID)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
for targetID := range existing {
|
|
|
|
|
subs := h.nearbySubscriptions[targetID]
|
|
|
|
|
if subs == nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
delete(subs, viewerID)
|
|
|
|
|
if len(subs) == 0 {
|
|
|
|
|
delete(h.nearbySubscriptions, targetID)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
delete(h.viewerSubscriptions, viewerID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// BroadcastAll sends an envelope to every connected client (rare: server announcements).
|
|
|
|
|
|