You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
hexmap/server/room/client.go

165 lines
6.2 KiB

package room
import (
"github.com/rs/xid"
"go.uber.org/zap"
)
// internalClient is used by the room itself to track information about a client.
type internalClient struct {
// id is the id that the client identifies itself with in all clientMessage instances it sends.
id xid.ID
// outgoingChannel is a channel that the room can send messages to the client on.
outgoingChannel chan<- Message
// privateChannel is true iff the room can close the outgoingChannel when the client and room have completed their
// close handshake.
privateChannel bool
// broadcast is true iff the client requested to be included on broadcasts on creation.
broadcast bool
}
type NewClientOptions struct {
// IncomingChannel is the channel to use as the room's channel to send messages to - the new Client's IncomingChannel.
// If this is non-nil, the room will not automatically close the IncomingChannel after a shutdown is negotiated.
// If this is nil, a new channel will be allocated on join and closed on shutdown.
IncomingChannel chan Message
// If AcceptBroadcasts is true, the room will send all broadcasts originating from other clients to this client.
AcceptBroadcasts bool
// If RequestStartingState is true, the room will send a copy of the current state as of when the JoinRequest was
// received in the JoinResponse that will be the first message the Client receives.
RequestStartingState bool
// If sets,
Logger *zap.Logger
}
// Client is the structure used by clients external to the Room package to communicate with the Room.
// It is not expected to be parallel-safe; to run it in parallel, use the NewClient method and send the new client to
// the new goroutine.
type Client struct {
// id is the ClientID used by the client for all communications.
id xid.ID
// roomId is the unique ID of the room (not its map).
roomId xid.ID
// incomingChannel is the channel that this client receives messages on.
incomingChannel <-chan Message
// outgoingChannel is the channel that this client sends messages on.
// Becomes nil if the client has been completely shut down.
outgoingChannel chan<- ClientMessage
// Once Leave or AcknowledgeShutdown have been triggered, this flag is set, preventing use of other messages.
shuttingDown bool
}
// ID is the ID used by this client to identify itself to the Room.
func (c *Client) ID() xid.ID {
return c.id
}
// RoomID is the ID used by the room to differentiate itself from other rooms.
// It is not the map's internal ID.
func (c *Client) RoomID() xid.ID {
return c.id
}
// IncomingChannel is the channel the client can listen on for messages from the room.
func (c *Client) IncomingChannel() <-chan Message {
return c.incomingChannel
}
// OutgoingChannel is the channel the client can send messages to the room on.
func (c *Client) OutgoingChannel() chan<- ClientMessage {
if c.outgoingChannel == nil {
panic("Already finished shutting down; no new messages should be sent")
}
return c.outgoingChannel
}
// newClientForRoom uses the necessary parameters to create and join a Client for the given room.
func newClientForRoom(roomId xid.ID, outgoingChannel chan<- ClientMessage, opts NewClientOptions) *Client {
var privateChannel bool
var incomingChannel chan Message
if opts.IncomingChannel != nil {
incomingChannel = opts.IncomingChannel
privateChannel = false
} else {
incomingChannel = make(chan Message, 1)
privateChannel = true
}
result := Client{
id: xid.New(),
roomId: roomId,
incomingChannel: incomingChannel,
outgoingChannel: outgoingChannel,
shuttingDown: false,
}
result.outgoingChannel <- JoinRequest{
id: result.id,
returnChannel: incomingChannel,
privateChannel: privateChannel,
broadcast: opts.AcceptBroadcasts,
wantCurrentState: opts.RequestStartingState,
}
return &result
}
// NewClient creates a new client belonging to the same room as this client with a random ID.
// The new client will be automatically joined to the channel.
func (c *Client) NewClient(opts NewClientOptions) *Client {
if c.shuttingDown {
panic("Already started shutting down; no new messages should be sent")
}
return newClientForRoom(c.roomId, c.outgoingChannel, opts)
}
// Refresh creates a message which causes the client to request a fresh copy of the state.
func (c *Client) Refresh() RefreshRequest {
if c.shuttingDown {
panic("Already started shutting down; no new messages should be sent")
}
return RefreshRequest{
id: c.id,
}
}
// Leave creates a message which causes the local client to signal that it is shutting down.
// It is important to Leave to avoid dangling clients having messages sent to nothing.
// After sending Leave, the client must confirm that it has been removed by waiting for a LeaveResponse, accompanied by
// the closing of the Client's IncomingChannel if it was a private channel.
// No further messages should be sent after Leave except AcknowledgeShutdown if Leave and requestShutdown crossed paths in midair.
func (c *Client) Leave() LeaveRequest {
if c.shuttingDown {
panic("Already started shutting down; no new messages should be sent")
}
c.shuttingDown = true
return LeaveRequest{
id: c.id,
}
}
// Stop creates a message which causes the local client to signal that it is shutting down.
// It is important to Stop when the room needs to be shut down.
// After sending Stop, the client must confirm that it has been removed by waiting for a ShutdownRequest, which should
// be handled normally.
// No further messages should be sent after Stop except AcknowledgeShutdown.
func (c *Client) Stop() StopRequest {
if c.shuttingDown {
panic("Already started shutting down; no new messages should be sent")
}
c.shuttingDown = true
return StopRequest{
id: c.id,
}
}
// AcknowledgeShutdown causes the local client to signal that it has acknowledged that the room is shutting down.
// No further messages can be sent after AcknowledgeShutdown; attempting to do so will block forever, as the
// OutgoingChannel has become nil.
func (c *Client) AcknowledgeShutdown() ShutdownResponse {
if c.outgoingChannel == nil {
panic("Already finished shutting down; no new messages should be sent")
}
c.shuttingDown = true
c.outgoingChannel = nil
return ShutdownResponse{
id: c.id,
}
}