Skip to content

Commit

Permalink
refactor: split profile and session into own files
Browse files Browse the repository at this point in the history
  • Loading branch information
paralin committed Jan 27, 2018
1 parent 1b94c65 commit bc34426
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 186 deletions.
189 changes: 3 additions & 186 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/golang/protobuf/proto"
devents "github.com/paralin/go-dota2/events"
// gcmm "github.com/paralin/go-dota2/protocol/dota_gcmessages_common_match_management"
gcclm "github.com/paralin/go-dota2/protocol/dota_gcmessages_client"
gccm "github.com/paralin/go-dota2/protocol/dota_gcmessages_common"
gcm "github.com/paralin/go-dota2/protocol/dota_gcmessages_msgid"
gcsdkm "github.com/paralin/go-dota2/protocol/gcsdk_gcmessages"
Expand Down Expand Up @@ -51,7 +50,9 @@ func New(client *steam.Client, le *logrus.Entry) *Dota2 {
c := &Dota2{
le: le,
client: client,
state: state.Dota2State{ConnectionStatus: gcsdkm.GCConnectionStatus_GCConnectionStatus_NO_SESSION},
state: state.Dota2State{
ConnectionStatus: gcsdkm.GCConnectionStatus_GCConnectionStatus_NO_SESSION,
},
profileResponseHandlers: make(map[uint32][]chan<- *gccm.CMsgDOTAProfileCard),
}
c.buildHandlerMap()
Expand Down Expand Up @@ -106,137 +107,6 @@ func (d *Dota2) accessState(cb func(nextState *state.Dota2State) (bool, error))
return nil
}

// setConnectionStatus sets the connection status, and emits an event.
// NOTE: do not call from inside accessState.
func (d *Dota2) setConnectionStatus(
connStatus gcsdkm.GCConnectionStatus,
update *gcsdkm.CMsgConnectionStatus,
) {
_ = d.accessState(func(ns *state.Dota2State) (changed bool, err error) {
if ns.ConnectionStatus == connStatus {
return false, nil
}

oldState := ns.ConnectionStatus
d.le.WithField("old", oldState.String()).
WithField("new", connStatus.String()).
Debug("connection status changed")
d.emit(&devents.GCConnectionStatusChanged{
OldState: oldState,
NewState: connStatus,
Update: update,
})

ns.ClearState() // every time the state changes, we lose the lobbies / etc
ns.ConnectionStatus = connStatus
d.connectionCtxMtx.Lock()
if d.connectionCtxCancel != nil {
d.connectionCtxCancel()
d.connectionCtxCancel = nil
d.connectionCtx = nil
}
if connStatus == gcsdkm.GCConnectionStatus_GCConnectionStatus_HAVE_SESSION {
d.connectionCtx, d.connectionCtxCancel = context.WithCancel(context.Background())
}
d.connectionCtxMtx.Unlock()
return true, nil
})
}

// SetPlaying informs Steam we are playing / not playing Dota 2.
func (d *Dota2) SetPlaying(playing bool) {
if playing {
d.client.GC.SetGamesPlayed(AppID)
} else {
d.client.GC.SetGamesPlayed()
_ = d.accessState(func(ns *state.Dota2State) (changed bool, err error) {
ns.ClearState()
return true, nil
})
}
}

// SayHello says hello to the Dota2 server, in an attempt to get a session.
func (d *Dota2) SayHello() {
partnerAccType := gcsdkm.PartnerAccountType_PARTNER_NONE
engine := gcsdkm.ESourceEngine_k_ESE_Source2
var clientSessionNeed uint32 = 104
d.write(uint32(gcsm.EGCBaseClientMsg_k_EMsgGCClientHello), &gcsdkm.CMsgClientHello{
ClientLauncher: &partnerAccType,
Engine: &engine,
ClientSessionNeed: &clientSessionNeed,
})
}

// RequestProfileCard sends a request to the DOTA gc for a card and waits for the response.
// If you want to enable timeouts, use a context.WithTimeout
func (d *Dota2) RequestProfileCard(ctx context.Context, accountId uint32) (*gccm.CMsgDOTAProfileCard, error) {
cctx, err := d.validateConnectionContext()
if err != nil {
return nil, err
}

handler := make(chan *gccm.CMsgDOTAProfileCard)
{
d.profileResponseHandlersMtx.Lock()
handlerList := d.profileResponseHandlers[accountId]
handlerList = append(handlerList, handler)
d.profileResponseHandlers[accountId] = handlerList
d.profileResponseHandlersMtx.Unlock()
}

// When returning, delete the handler from the list
defer func() {
d.profileResponseHandlersMtx.Lock()
defer d.profileResponseHandlersMtx.Unlock()

handlers := d.profileResponseHandlers[accountId]
for i, h := range handlers {
if h == handler {
handlers[i] = handlers[len(handlers)-1]
handlers = handlers[:len(handlers)-1]
break
}
}
if len(handlers) == 0 {
delete(d.profileResponseHandlers, accountId)
} else {
d.profileResponseHandlers[accountId] = handlers
}
}()

d.write(uint32(gcm.EDOTAGCMsg_k_EMsgClientToGCGetProfileCard), &gcclm.CMsgClientToGCGetProfileCard{
AccountId: &accountId,
})

select {
case <-ctx.Done():
return nil, context.Canceled
case <-cctx.Done():
return nil, NotReadyErr
case r := <-handler:
return r, nil
}
}

// validateConnectionContext checks if the client is ready or not.
func (d *Dota2) validateConnectionContext() (context.Context, error) {
d.connectionCtxMtx.Lock()
defer d.connectionCtxMtx.Unlock()

cctx := d.connectionCtx
if cctx == nil {
return nil, NotReadyErr
}

select {
case <-cctx.Done():
return nil, NotReadyErr
default:
return cctx, nil
}
}

// unmarshalBody attempts to unmarshal a packet body.
func (d *Dota2) unmarshalBody(packet *gamecoordinator.GCPacket, msg proto.Message) (parseErr error) {
defer func() {
Expand All @@ -248,59 +118,6 @@ func (d *Dota2) unmarshalBody(packet *gamecoordinator.GCPacket, msg proto.Messag
return proto.Unmarshal(packet.Body, msg)
}

// handleClientWelcome handles an incoming client welcome event.
func (d *Dota2) handleClientWelcome(packet *gamecoordinator.GCPacket) error {
welcome := &gcsdkm.CMsgClientWelcome{}
if err := d.unmarshalBody(packet, welcome); err != nil {
return err
}

d.setConnectionStatus(gcsdkm.GCConnectionStatus_GCConnectionStatus_HAVE_SESSION, nil)
d.emit(&devents.ClientWelcomed{Welcome: welcome})
return nil
}

// handleConnectionStatus handles the connection status update event.
func (d *Dota2) handleConnectionStatus(packet *gamecoordinator.GCPacket) error {
stat := &gcsdkm.CMsgConnectionStatus{}
if err := d.unmarshalBody(packet, stat); err != nil {
return err
}

if stat.Status == nil {
return nil
}

d.setConnectionStatus(*stat.Status, stat)
return nil
}

// handleGetProfileCardResponse handles the response to the get profile card request
func (d *Dota2) handleGetProfileCardResponse(packet *gamecoordinator.GCPacket) error {
card := &gccm.CMsgDOTAProfileCard{}

if err := d.unmarshalBody(packet, card); err != nil {
return err
}
if card.AccountId == nil {
return errors.New("received a profile card response with nil account id")
}

accountID := *card.AccountId
d.profileResponseHandlersMtx.Lock()
handlers := d.profileResponseHandlers[accountID]
d.profileResponseHandlersMtx.Unlock()

for _, handler := range handlers {
select {
case handler <- card:
default:
}
}

return nil
}

// HandleGCPacket handles an incoming game coordinator packet.
func (d *Dota2) HandleGCPacket(packet *gamecoordinator.GCPacket) {
if packet.AppId != AppID {
Expand Down
89 changes: 89 additions & 0 deletions client_profile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package dota2

import (
"context"
"errors"

"github.com/Philipp15b/go-steam/protocol/gamecoordinator"
// gcmm "github.com/paralin/go-dota2/protocol/dota_gcmessages_common_match_management"
gcclm "github.com/paralin/go-dota2/protocol/dota_gcmessages_client"
gccm "github.com/paralin/go-dota2/protocol/dota_gcmessages_common"
gcm "github.com/paralin/go-dota2/protocol/dota_gcmessages_msgid"
)

// handleGetProfileCardResponse handles the response to the get profile card request
func (d *Dota2) handleGetProfileCardResponse(packet *gamecoordinator.GCPacket) error {
card := &gccm.CMsgDOTAProfileCard{}

if err := d.unmarshalBody(packet, card); err != nil {
return err
}
if card.AccountId == nil {
return errors.New("received a profile card response with nil account id")
}

accountID := *card.AccountId
d.profileResponseHandlersMtx.Lock()
handlers := d.profileResponseHandlers[accountID]
d.profileResponseHandlersMtx.Unlock()

for _, handler := range handlers {
select {
case handler <- card:
default:
}
}

return nil
}

// RequestProfileCard sends a request to the DOTA gc for a card and waits for the response.
// If you want to enable timeouts, use a context.WithTimeout
func (d *Dota2) RequestProfileCard(ctx context.Context, accountId uint32) (*gccm.CMsgDOTAProfileCard, error) {
cctx, err := d.validateConnectionContext()
if err != nil {
return nil, err
}

handler := make(chan *gccm.CMsgDOTAProfileCard)
{
d.profileResponseHandlersMtx.Lock()
handlerList := d.profileResponseHandlers[accountId]
handlerList = append(handlerList, handler)
d.profileResponseHandlers[accountId] = handlerList
d.profileResponseHandlersMtx.Unlock()
}

// When returning, delete the handler from the list
defer func() {
d.profileResponseHandlersMtx.Lock()
defer d.profileResponseHandlersMtx.Unlock()

handlers := d.profileResponseHandlers[accountId]
for i, h := range handlers {
if h == handler {
handlers[i] = handlers[len(handlers)-1]
handlers = handlers[:len(handlers)-1]
break
}
}
if len(handlers) == 0 {
delete(d.profileResponseHandlers, accountId)
} else {
d.profileResponseHandlers[accountId] = handlers
}
}()

d.write(uint32(gcm.EDOTAGCMsg_k_EMsgClientToGCGetProfileCard), &gcclm.CMsgClientToGCGetProfileCard{
AccountId: &accountId,
})

select {
case <-ctx.Done():
return nil, context.Canceled
case <-cctx.Done():
return nil, NotReadyErr
case r := <-handler:
return r, nil
}
}
Loading

0 comments on commit bc34426

Please sign in to comment.