func (s *Session) Clear() {
	s.Lock()
	defer s.Unlock()
	s.uid = ""
	s.data = map[string]interface{}{}
	s.updateEncodedData()
}
Clear releases all data related to current session
package session
import (
	"context"
	"encoding/json"
	"net"
	"reflect"
	"sync"
	"sync/atomic"
	"time"
	"github.com/golang/protobuf/proto"
	nats "github.com/nats-io/nats.go"
	"github.com/topfreegames/pitaya/constants"
	"github.com/topfreegames/pitaya/logger"
	"github.com/topfreegames/pitaya/protos"
)
type NetworkEntity interface {
	Push(route string, v interface{}) error
	ResponseMID(ctx context.Context, mid uint, v interface{}, isError ...bool) error
	Close() error
	Kick(ctx context.Context) error
	RemoteAddr() net.Addr
	SendRequest(ctx context.Context, serverID, route string, v interface{}) (*protos.Response, error)
}
var (
	sessionBindCallbacks = make([]func(ctx context.Context, s *Session) error, 0)
	afterBindCallbacks   = make([]func(ctx context.Context, s *Session) error, 0)
	SessionCloseCallbacks = make([]func(s *Session), 0)
	sessionsByUID         sync.Map                 
	sessionsByID          sync.Map                 
	sessionIDSvc          = newSessionIDService()  
	SessionCount int64
)
type HandshakeClientData struct {
	Platform    string `json:"platform"`
	LibVersion  string `json:"libVersion"`
	BuildNumber string `json:"clientBuildNumber"`
	Version     string `json:"clientVersion"`
}
type HandshakeData struct {
	Sys  HandshakeClientData    `json:"sys"`
	User map[string]interface{} `json:"user,omitempty"`
}
type Session struct {
	sync.RWMutex                              
	id                int64                   
	uid               string                  
	lastTime          int64                   
	entity            NetworkEntity           
	data              map[string]interface{}  
	handshakeData     *HandshakeData          
	encodedData       []byte                  
	OnCloseCallbacks  []func()                
	IsFrontend        bool                    
	frontendID        string                  
	frontendSessionID int64                   
	Subscriptions     []*nats.Subscription    
}
type sessionIDService struct {
	sid int64
}
func newSessionIDService() *sessionIDService {
	return &sessionIDService{
		sid: 0,
	}
}
func (c *sessionIDService) sessionID() int64 {
	return atomic.AddInt64(&c.sid, 1)  
}
func New(entity NetworkEntity, frontend bool, UID ...string) *Session {
	s := &Session{
		id:               sessionIDSvc.sessionID(),  
		entity:           entity,
		data:             make(map[string]interface{}),
		handshakeData:    nil,
		lastTime:         time.Now().Unix(),
		OnCloseCallbacks: []func(){},
		IsFrontend:       frontend,
	}
	if frontend {
		sessionsByID.Store(s.id, s)        
		atomic.AddInt64(&SessionCount, 1)  
	}
	if len(UID) > 0 {
		s.uid = UID[0]
	}
	return s
}
func GetSessionByUID(uid string) *Session {
	if val, ok := sessionsByUID.Load(uid); ok {
		return val.(*Session)
	}
	return nil
}
func GetSessionByID(id int64) *Session {
	if val, ok := sessionsByID.Load(id); ok {
		return val.(*Session)
	}
	return nil
}
func OnSessionBind(f func(ctx context.Context, s *Session) error) {
	sf1 := reflect.ValueOf(f)
	for _, fun := range sessionBindCallbacks {
		sf2 := reflect.ValueOf(fun)
		if sf1.Pointer() == sf2.Pointer() {
			return
		}
	}
	sessionBindCallbacks = append(sessionBindCallbacks, f)
}
func OnAfterSessionBind(f func(ctx context.Context, s *Session) error) {
	sf1 := reflect.ValueOf(f)
	for _, fun := range afterBindCallbacks {
		sf2 := reflect.ValueOf(fun)
		if sf1.Pointer() == sf2.Pointer() {
			return
		}
	}
	afterBindCallbacks = append(afterBindCallbacks, f)
}
func OnSessionClose(f func(s *Session)) {
	sf1 := reflect.ValueOf(f)
	for _, fun := range SessionCloseCallbacks {
		sf2 := reflect.ValueOf(fun)
		if sf1.Pointer() == sf2.Pointer() {
			return
		}
	}
	SessionCloseCallbacks = append(SessionCloseCallbacks, f)
}
func CloseAll() {
	logger.Log.Debugf("closing all sessions, %d sessions", SessionCount)
	sessionsByID.Range(func(_, value interface{}) bool {
		s := value.(*Session)
		s.Close()
		return true
	})
	logger.Log.Debug("finished closing sessions")
}
func (s *Session) updateEncodedData() error {
	var b []byte
	b, err := json.Marshal(s.data)
	if err != nil {
		return err
	}
	s.encodedData = b
	return nil
}
func (s *Session) Push(route string, v interface{}) error {
	return s.entity.Push(route, v)
}
func (s *Session) ResponseMID(ctx context.Context, mid uint, v interface{}, err ...bool) error {
	return s.entity.ResponseMID(ctx, mid, v, err...)
}
func (s *Session) ID() int64 {
	return s.id
}
func (s *Session) UID() string {
	return s.uid
}
func (s *Session) GetData() map[string]interface{} {
	s.RLock()
	defer s.RUnlock()
	return s.data
}
func (s *Session) SetData(data map[string]interface{}) error {
	s.Lock()
	defer s.Unlock()
	s.data = data
	return s.updateEncodedData()
}
func (s *Session) GetDataEncoded() []byte {
	return s.encodedData
}
func (s *Session) SetDataEncoded(encodedData []byte) error {
	if len(encodedData) == 0 {
		return nil
	}
	var data map[string]interface{}
	err := json.Unmarshal(encodedData, &data)
	if err != nil {
		return err
	}
	return s.SetData(data)
}
func (s *Session) SetFrontendData(frontendID string, frontendSessionID int64) {
	s.frontendID = frontendID
	s.frontendSessionID = frontendSessionID
}
func (s *Session) Bind(ctx context.Context, uid string) error {
	if uid == "" {
		return constants.ErrIllegalUID
	}
	if s.UID() != "" {
		return constants.ErrSessionAlreadyBound
	}
	s.uid = uid
	for _, cb := range sessionBindCallbacks {
		err := cb(ctx, s)
		if err != nil {
			s.uid = ""
			return err
		}
	}
	for _, cb := range afterBindCallbacks {
		err := cb(ctx, s)
		if err != nil {
			s.uid = ""
			return err
		}
	}
	if s.IsFrontend {
		sessionsByUID.Store(uid, s)
	} else {
		err := s.bindInFront(ctx)
		if err != nil {
			logger.Log.Error("error while trying to push session to front: ", err)
			s.uid = ""
			return err
		}
	}
	return nil
}
func (s *Session) Kick(ctx context.Context) error {
	err := s.entity.Kick(ctx)
	if err != nil {
		return err
	}
	return s.entity.Close()
}
func (s *Session) OnClose(c func()) error {
	if !s.IsFrontend {
		return constants.ErrOnCloseBackend
	}
	s.OnCloseCallbacks = append(s.OnCloseCallbacks, c)
	return nil
}
func (s *Session) Close() {
	atomic.AddInt64(&SessionCount, -1)
	sessionsByID.Delete(s.ID())
	sessionsByUID.Delete(s.UID())
	if s.IsFrontend && s.Subscriptions != nil && len(s.Subscriptions) > 0 {
		for _, sub := range s.Subscriptions {
			err := sub.Unsubscribe()
			if err != nil {
				logger.Log.Errorf("error unsubscribing to user's messages channel: %s, this can cause performance and leak issues", err.Error())
			} else {
				logger.Log.Debugf("successfully unsubscribed to user's %s messages channel", s.UID())
			}
		}
	}
	s.entity.Close()
}
func (s *Session) RemoteAddr() net.Addr {
	return s.entity.RemoteAddr()
}
func (s *Session) Remove(key string) error {
	s.Lock()
	defer s.Unlock()
	delete(s.data, key)
	return s.updateEncodedData()
}
func (s *Session) Set(key string, value interface{}) error {
	s.Lock()
	defer s.Unlock()
	s.data[key] = value
	return s.updateEncodedData()
}
func (s *Session) HasKey(key string) bool {
	s.RLock()
	defer s.RUnlock()
	_, has := s.data[key]
	return has
}
func (s *Session) Get(key string) interface{} {
	s.RLock()
	defer s.RUnlock()
	v, ok := s.data[key]
	if !ok {
		return nil
	}
	return v
}
func (s *Session) Int(key string) int {
	s.RLock()
	defer s.RUnlock()
	v, ok := s.data[key]
	if !ok {
		return 0
	}
	value, ok := v.(int)
	if !ok {
		return 0
	}
	return value
}
func (s *Session) Int8(key string) int8 {
	s.RLock()
	defer s.RUnlock()
	v, ok := s.data[key]
	if !ok {
		return 0
	}
	value, ok := v.(int8)
	if !ok {
		return 0
	}
	return value
}
func (s *Session) Int16(key string) int16 {
	s.RLock()
	defer s.RUnlock()
	v, ok := s.data[key]
	if !ok {
		return 0
	}
	value, ok := v.(int16)
	if !ok {
		return 0
	}
	return value
}
func (s *Session) Int32(key string) int32 {
	s.RLock()
	defer s.RUnlock()
	v, ok := s.data[key]
	if !ok {
		return 0
	}
	value, ok := v.(int32)
	if !ok {
		return 0
	}
	return value
}
func (s *Session) Int64(key string) int64 {
	s.RLock()
	defer s.RUnlock()
	v, ok := s.data[key]
	if !ok {
		return 0
	}
	value, ok := v.(int64)
	if !ok {
		return 0
	}
	return value
}
func (s *Session) Uint(key string) uint {
	s.RLock()
	defer s.RUnlock()
	v, ok := s.data[key]
	if !ok {
		return 0
	}
	value, ok := v.(uint)
	if !ok {
		return 0
	}
	return value
}
func (s *Session) Uint8(key string) uint8 {
	s.RLock()
	defer s.RUnlock()
	v, ok := s.data[key]
	if !ok {
		return 0
	}
	value, ok := v.(uint8)
	if !ok {
		return 0
	}
	return value
}
func (s *Session) Uint16(key string) uint16 {
	s.RLock()
	defer s.RUnlock()
	v, ok := s.data[key]
	if !ok {
		return 0
	}
	value, ok := v.(uint16)
	if !ok {
		return 0
	}
	return value
}
func (s *Session) Uint32(key string) uint32 {
	s.RLock()
	defer s.RUnlock()
	v, ok := s.data[key]
	if !ok {
		return 0
	}
	value, ok := v.(uint32)
	if !ok {
		return 0
	}
	return value
}
func (s *Session) Uint64(key string) uint64 {
	s.RLock()
	defer s.RUnlock()
	v, ok := s.data[key]
	if !ok {
		return 0
	}
	value, ok := v.(uint64)
	if !ok {
		return 0
	}
	return value
}
func (s *Session) Float32(key string) float32 {
	s.RLock()
	defer s.RUnlock()
	v, ok := s.data[key]
	if !ok {
		return 0
	}
	value, ok := v.(float32)
	if !ok {
		return 0
	}
	return value
}
func (s *Session) Float64(key string) float64 {
	s.RLock()
	defer s.RUnlock()
	v, ok := s.data[key]
	if !ok {
		return 0
	}
	value, ok := v.(float64)
	if !ok {
		return 0
	}
	return value
}
func (s *Session) String(key string) string {
	s.RLock()
	defer s.RUnlock()
	v, ok := s.data[key]
	if !ok {
		return ""
	}
	value, ok := v.(string)
	if !ok {
		return ""
	}
	return value
}
func (s *Session) Value(key string) interface{} {
	s.RLock()
	defer s.RUnlock()
	return s.data[key]
}
func (s *Session) bindInFront(ctx context.Context) error {
	return s.sendRequestToFront(ctx, constants.SessionBindRoute, false)
}
func (s *Session) PushToFront(ctx context.Context) error {
	if s.IsFrontend {
		return constants.ErrFrontSessionCantPushToFront
	}
	return s.sendRequestToFront(ctx, constants.SessionPushRoute, true)
}