package server import ( "encoding/gob" "encoding/json" "log" "golang.org/x/net/websocket" "mcquay.me/bandwidth" ) const maxMessageSize = 1024 type encoder interface { Encode(v interface{}) error } type decoder interface { Decode(v interface{}) error } // StreamCount is the wrapper we use around reads and writes to keep track of // bandwidths. type StreamCount struct { ws *websocket.Conn bw *bandwidth.Bandwidth } // StreamCount.Read implements io.Reader func (sc *StreamCount) Read(p []byte) (n int, err error) { n, err = sc.ws.Read(p) sc.bw.AddRx <- n return n, err } // StreamCount.Write implements io.Writer func (sc *StreamCount) Write(p []byte) (n int, err error) { n, err = sc.ws.Write(p) sc.bw.AddTx <- n return n, err } // Close is for cleanup func (sc *StreamCount) Close() error { return sc.ws.Close() } // ProtoTalker is the simplest form of struct that talks to consumers of the // service. There are two important methods here: Sender and Recv. type ProtoTalker struct { enc encoder dec decoder counter *StreamCount send chan Message Id string } func NewProtoTalker(id string, ws *websocket.Conn, bw *bandwidth.Bandwidth, encoding string) *ProtoTalker { var enc encoder var dec decoder comptroller := &StreamCount{ ws: ws, bw: bw, } if encoding == "json" { enc = json.NewEncoder(comptroller) dec = json.NewDecoder(comptroller) } else { enc = gob.NewEncoder(comptroller) dec = gob.NewDecoder(comptroller) } return &ProtoTalker{ send: make(chan Message, 16), enc: enc, dec: dec, counter: comptroller, Id: id, } } // Sender is the single implementation for data output to clients, both players // and spectators. func (pt *ProtoTalker) Sender() { log.Printf("%s: %T Sender launched", pt.Id, pt.enc) for things := range pt.send { err := pt.enc.Encode(things) if err != nil { log.Println(err) break } } pt.counter.Close() log.Printf("%s: Sender close", pt.Id) } // player uses protoTalker's Sender method, but adds a Recv that knows how to // deal with game play instructions from the player. type Player struct { Robots []*Robot Instruction Instruction ProtoTalker } func NewPlayer(id string, ws *websocket.Conn, bw *bandwidth.Bandwidth, encoding string) *Player { return &Player{ Robots: []*Robot{}, ProtoTalker: *NewProtoTalker(id, ws, bw, encoding), } } // Player.Recv is the function responsible for parsing out player instructions // and sending them to the game. func (p *Player) Recv() { log.Println("starting Recv") for { var msgs map[string]Instruction err := p.dec.Decode(&msgs) if err != nil { log.Printf("%s: %s", p.Id, err) break } for _, r := range p.Robots { msg, ok := msgs[r.Id] if !ok { continue } if msg.Repair != nil && *msg.Repair == true { r.ActiveScan = false r.TargetSpeed = 0 r.FireAt = nil r.MoveTo = nil if r.RepairCounter <= 0 { r.RepairCounter = 3.0 } } else if msg.Scan != nil && *msg.Scan == true { r.RepairCounter = 0 r.TargetSpeed = 0 r.FireAt = nil r.MoveTo = nil r.ActiveScan = true } else { r.RepairCounter = 0 r.ActiveScan = false // Reapiring halts all other activity if msg.MoveTo != nil { r.MoveTo = msg.MoveTo } if msg.Heading != nil { r.DesiredHeading = msg.Heading } if msg.FireAt != nil { r.FireAt = msg.FireAt } else { r.FireAt = nil } if msg.TargetSpeed != nil { r.TargetSpeed = *msg.TargetSpeed } else { r.TargetSpeed = r.Stats.Speed } } if msg.Probe != nil { r.Probe = msg.Probe r.ProbeResult = nil } else { r.Probe = nil } if msg.Message != nil { r.Message = *msg.Message } } } log.Printf("%s: Recv close", p.Id) p.counter.Close() } // Spectator merely sends out game state, does not receive meaningful // instructions from spectators. type Spectator struct { ProtoTalker } func NewSpectator(id string, ws *websocket.Conn, bw *bandwidth.Bandwidth, encoding string) *Spectator { return &Spectator{ ProtoTalker: *NewProtoTalker(id, ws, bw, encoding), } } // Spectator.Recv is an interesting beast. We had to add it as for whatever // reason the server would lock up if we weren't reading the empty responses // from spectators. func (s *Spectator) Recv() { for { var msgs map[string]Instruction err := s.dec.Decode(&msgs) if err != nil { log.Printf("%s: %s", s.Id, err) break } // After the first bit of handshaking, the rest of the messages should // only be "{}" for spectators, and the following could hold true: // // if string(buff[:n]) != "{}" { // log.Printf("protocol breach!!") // break // } } log.Printf("%s: Recv close", s.Id) s.counter.Close() }