mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 23:54:33 +01:00
127 lines
4.6 KiB
Go
127 lines
4.6 KiB
Go
package channelserver
|
|
|
|
import (
|
|
"erupe-ce/common/byteframe"
|
|
"erupe-ce/network/mhfpacket"
|
|
|
|
"sync"
|
|
)
|
|
|
|
// Semaphore is a multiplayer coordination mechanism for quests and events.
|
|
//
|
|
// Despite the name, Semaphore is NOT an OS synchronization primitive (like sync.Semaphore).
|
|
// Instead, it's a game-specific resource lock that coordinates multiplayer activities where:
|
|
// - Players must acquire a semaphore before participating
|
|
// - A limited number of participants are allowed (maxPlayers)
|
|
// - The semaphore tracks both active and reserved participants
|
|
//
|
|
// Use Cases:
|
|
// - Quest coordination: Ensures quest party size limits are enforced
|
|
// - Event coordination: Raviente, VS Tournament, Diva Defense
|
|
// - Global resources: Prevents multiple groups from starting conflicting events
|
|
//
|
|
// Semaphore vs Stage:
|
|
// - Stages are spatial (game rooms, areas). Players in a stage can see each other.
|
|
// - Semaphores are logical (coordination locks). Players in a semaphore are
|
|
// participating in the same activity but may be in different stages.
|
|
//
|
|
// Example: Raviente Event
|
|
// - Players acquire the Raviente semaphore to register for the event
|
|
// - Multiple quest stages exist (preparation, phase 1, phase 2, carving)
|
|
// - All participants share the same semaphore across different stages
|
|
// - The semaphore enforces the 32-player limit across all stages
|
|
//
|
|
// Thread Safety:
|
|
// Semaphore embeds sync.RWMutex. Use RLock for reads and Lock for writes.
|
|
type Semaphore struct {
|
|
sync.RWMutex // Protects semaphore state during concurrent access
|
|
|
|
// Semaphore identity
|
|
id_semaphore string // Semaphore ID string (identifies the resource/activity)
|
|
id uint32 // Numeric ID for client communication (auto-generated, starts at 7)
|
|
|
|
// Active participants
|
|
clients map[*Session]uint32 // Sessions actively using this semaphore -> character ID
|
|
|
|
// Reserved slots
|
|
// Players who have acquired the semaphore but may not be actively in the stage yet.
|
|
// The value is always nil; only the key (charID) matters. This is a set implementation.
|
|
reservedClientSlots map[uint32]interface{} // Character ID -> nil (set of reserved IDs)
|
|
|
|
// Capacity
|
|
maxPlayers uint16 // Maximum concurrent participants (e.g., 4 for quests, 32 for Raviente)
|
|
}
|
|
|
|
// NewSemaphore creates and initializes a new Semaphore for coordinating an activity.
|
|
//
|
|
// The semaphore is assigned an auto-incrementing ID from the server's semaphoreIndex.
|
|
// IDs 0-6 are reserved, so the first semaphore gets ID 7.
|
|
//
|
|
// Parameters:
|
|
// - s: The server (used to generate unique semaphore ID)
|
|
// - ID: Semaphore ID string (identifies the activity/resource)
|
|
// - MaxPlayers: Maximum participants allowed
|
|
//
|
|
// Returns a new Semaphore ready for client acquisition.
|
|
func NewSemaphore(s *Server, ID string, MaxPlayers uint16) *Semaphore {
|
|
sema := &Semaphore{
|
|
id_semaphore: ID,
|
|
id: s.NextSemaphoreID(),
|
|
clients: make(map[*Session]uint32),
|
|
reservedClientSlots: make(map[uint32]interface{}),
|
|
maxPlayers: MaxPlayers,
|
|
}
|
|
return sema
|
|
}
|
|
|
|
func (s *Semaphore) BroadcastRavi(pkt mhfpacket.MHFPacket) {
|
|
// Broadcast the data.
|
|
for session := range s.clients {
|
|
|
|
// Make the header
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteUint16(uint16(pkt.Opcode()))
|
|
|
|
// Build the packet onto the byteframe.
|
|
pkt.Build(bf, session.clientContext)
|
|
|
|
// Enqueue in a non-blocking way that drops the packet if the connections send buffer channel is full.
|
|
session.QueueSendNonBlocking(bf.Data())
|
|
}
|
|
}
|
|
|
|
// BroadcastMHF sends a packet to all active participants in the semaphore.
|
|
//
|
|
// This is used for event-wide announcements that all participants need to see,
|
|
// regardless of which stage they're currently in. Examples:
|
|
// - Raviente phase changes
|
|
// - Tournament updates
|
|
// - Event completion notifications
|
|
//
|
|
// Only active clients (in the clients map) receive broadcasts. Reserved clients
|
|
// who haven't fully joined yet are excluded.
|
|
//
|
|
// Parameters:
|
|
// - pkt: The MHFPacket to broadcast to all participants
|
|
// - ignoredSession: Optional session to exclude from broadcast
|
|
//
|
|
// Thread Safety: Caller should hold semaphore lock when iterating clients.
|
|
func (s *Semaphore) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
|
|
// Broadcast the data.
|
|
for session := range s.clients {
|
|
if session == ignoredSession {
|
|
continue
|
|
}
|
|
|
|
// Make the header
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteUint16(uint16(pkt.Opcode()))
|
|
|
|
// Build the packet onto the byteframe.
|
|
pkt.Build(bf, session.clientContext)
|
|
|
|
// Enqueue in a non-blocking way that drops the packet if the connections send buffer channel is full.
|
|
session.QueueSendNonBlocking(bf.Data())
|
|
}
|
|
}
|