diff --git a/common/socket/ServerEvent.go b/common/socket/ServerEvent.go index b64b7049..64f6773b 100644 --- a/common/socket/ServerEvent.go +++ b/common/socket/ServerEvent.go @@ -40,6 +40,11 @@ func (s *Server) Stop() error { } func (s *Server) OnClose(c gnet.Conn, _ error) (action gnet.Action) { + defer func() { + if r := recover(); r != nil { + fmt.Println("Recovered in f", r) + } + }() atomic.AddInt64(&s.connected, -1) //logging.Infof("conn[%v] disconnected", c.RemoteAddr().String()) v, ok := c.Context().(*player.ClientData) diff --git a/common/utils/concurrent_map.go b/common/utils/concurrent_map.go new file mode 100644 index 00000000..e12e3ccc --- /dev/null +++ b/common/utils/concurrent_map.go @@ -0,0 +1,370 @@ +package utils + +import ( + "encoding/json" + "fmt" + "sync" +) + +var SHARD_COUNT = 32 + +type Stringer interface { + fmt.Stringer + comparable +} + +// A "thread" safe map of type string:Anything. +// To avoid lock bottlenecks this map is dived to several (SHARD_COUNT) map shards. +type ConcurrentMap[K comparable, V any] struct { + shards []*ConcurrentMapShared[K, V] + sharding func(key K) uint32 +} + +// A "thread" safe string to anything map. +type ConcurrentMapShared[K comparable, V any] struct { + items map[K]V + sync.RWMutex // Read Write mutex, guards access to internal map. +} + +func create[K comparable, V any](sharding func(key K) uint32) ConcurrentMap[K, V] { + m := ConcurrentMap[K, V]{ + sharding: sharding, + shards: make([]*ConcurrentMapShared[K, V], SHARD_COUNT), + } + for i := 0; i < SHARD_COUNT; i++ { + m.shards[i] = &ConcurrentMapShared[K, V]{items: make(map[K]V)} + } + return m +} + +// Creates a new concurrent map. +func New[V any]() ConcurrentMap[string, V] { + return create[string, V](fnv32) +} + +// Creates a new concurrent map. +func NewStringer[K Stringer, V any]() ConcurrentMap[K, V] { + return create[K, V](strfnv32[K]) +} + +// Creates a new concurrent map. +func NewWithCustomShardingFunction[K comparable, V any](sharding func(key K) uint32) ConcurrentMap[K, V] { + return create[K, V](sharding) +} + +// GetShard returns shard under given key +func (m ConcurrentMap[K, V]) GetShard(key K) *ConcurrentMapShared[K, V] { + return m.shards[uint(m.sharding(key))%uint(SHARD_COUNT)] +} + +func (m ConcurrentMap[K, V]) MSet(data map[K]V) { + for key, value := range data { + shard := m.GetShard(key) + shard.Lock() + shard.items[key] = value + shard.Unlock() + } +} + +// Sets the given value under the specified key. +func (m ConcurrentMap[K, V]) Set(key K, value V) { + // Get map shard. + shard := m.GetShard(key) + shard.Lock() + shard.items[key] = value + shard.Unlock() +} + +// Callback to return new element to be inserted into the map +// It is called while lock is held, therefore it MUST NOT +// try to access other keys in same map, as it can lead to deadlock since +// Go sync.RWLock is not reentrant +type UpsertCb[V any] func(exist bool, valueInMap V, newValue V) V + +// Insert or Update - updates existing element or inserts a new one using UpsertCb +func (m ConcurrentMap[K, V]) Upsert(key K, value V, cb UpsertCb[V]) (res V) { + shard := m.GetShard(key) + shard.Lock() + v, ok := shard.items[key] + res = cb(ok, v, value) + shard.items[key] = res + shard.Unlock() + return res +} + +// Sets the given value under the specified key if no value was associated with it. +func (m ConcurrentMap[K, V]) SetIfAbsent(key K, value V) bool { + // Get map shard. + shard := m.GetShard(key) + shard.Lock() + _, ok := shard.items[key] + if !ok { + shard.items[key] = value + } + shard.Unlock() + return !ok +} + +// Get retrieves an element from map under given key. +func (m ConcurrentMap[K, V]) Get(key K) (V, bool) { + // Get shard + shard := m.GetShard(key) + shard.RLock() + // Get item from shard. + val, ok := shard.items[key] + shard.RUnlock() + return val, ok +} + +// Count returns the number of elements within the map. +func (m ConcurrentMap[K, V]) Count() int { + count := 0 + for i := 0; i < SHARD_COUNT; i++ { + shard := m.shards[i] + shard.RLock() + count += len(shard.items) + shard.RUnlock() + } + return count +} + +// Looks up an item under specified key +func (m ConcurrentMap[K, V]) Has(key K) bool { + // Get shard + shard := m.GetShard(key) + shard.RLock() + // See if element is within shard. + _, ok := shard.items[key] + shard.RUnlock() + return ok +} + +// Remove removes an element from the map. +func (m ConcurrentMap[K, V]) Remove(key K) { + // Try to get shard. + shard := m.GetShard(key) + shard.Lock() + delete(shard.items, key) + shard.Unlock() +} + +// RemoveCb is a callback executed in a map.RemoveCb() call, while Lock is held +// If returns true, the element will be removed from the map +type RemoveCb[K any, V any] func(key K, v V, exists bool) bool + +// RemoveCb locks the shard containing the key, retrieves its current value and calls the callback with those params +// If callback returns true and element exists, it will remove it from the map +// Returns the value returned by the callback (even if element was not present in the map) +func (m ConcurrentMap[K, V]) RemoveCb(key K, cb RemoveCb[K, V]) bool { + // Try to get shard. + shard := m.GetShard(key) + shard.Lock() + v, ok := shard.items[key] + remove := cb(key, v, ok) + if remove && ok { + delete(shard.items, key) + } + shard.Unlock() + return remove +} + +// Pop removes an element from the map and returns it +func (m ConcurrentMap[K, V]) Pop(key K) (v V, exists bool) { + // Try to get shard. + shard := m.GetShard(key) + shard.Lock() + v, exists = shard.items[key] + delete(shard.items, key) + shard.Unlock() + return v, exists +} + +// IsEmpty checks if map is empty. +func (m ConcurrentMap[K, V]) IsEmpty() bool { + return m.Count() == 0 +} + +// Used by the Iter & IterBuffered functions to wrap two variables together over a channel, +type Tuple[K comparable, V any] struct { + Key K + Val V +} + +// Iter returns an iterator which could be used in a for range loop. +// +// Deprecated: using IterBuffered() will get a better performence +func (m ConcurrentMap[K, V]) Iter() <-chan Tuple[K, V] { + chans := snapshot(m) + ch := make(chan Tuple[K, V]) + go fanIn(chans, ch) + return ch +} + +// IterBuffered returns a buffered iterator which could be used in a for range loop. +func (m ConcurrentMap[K, V]) IterBuffered() <-chan Tuple[K, V] { + chans := snapshot(m) + total := 0 + for _, c := range chans { + total += cap(c) + } + ch := make(chan Tuple[K, V], total) + go fanIn(chans, ch) + return ch +} + +// Clear removes all items from map. +func (m ConcurrentMap[K, V]) Clear() { + for item := range m.IterBuffered() { + m.Remove(item.Key) + } +} + +// Returns a array of channels that contains elements in each shard, +// which likely takes a snapshot of `m`. +// It returns once the size of each buffered channel is determined, +// before all the channels are populated using goroutines. +func snapshot[K comparable, V any](m ConcurrentMap[K, V]) (chans []chan Tuple[K, V]) { + //When you access map items before initializing. + if len(m.shards) == 0 { + panic(`cmap.ConcurrentMap is not initialized. Should run New() before usage.`) + } + chans = make([]chan Tuple[K, V], SHARD_COUNT) + wg := sync.WaitGroup{} + wg.Add(SHARD_COUNT) + // Foreach shard. + for index, shard := range m.shards { + go func(index int, shard *ConcurrentMapShared[K, V]) { + // Foreach key, value pair. + shard.RLock() + chans[index] = make(chan Tuple[K, V], len(shard.items)) + wg.Done() + for key, val := range shard.items { + chans[index] <- Tuple[K, V]{key, val} + } + shard.RUnlock() + close(chans[index]) + }(index, shard) + } + wg.Wait() + return chans +} + +// fanIn reads elements from channels `chans` into channel `out` +func fanIn[K comparable, V any](chans []chan Tuple[K, V], out chan Tuple[K, V]) { + wg := sync.WaitGroup{} + wg.Add(len(chans)) + for _, ch := range chans { + go func(ch chan Tuple[K, V]) { + for t := range ch { + out <- t + } + wg.Done() + }(ch) + } + wg.Wait() + close(out) +} + +// Items returns all items as map[string]V +func (m ConcurrentMap[K, V]) Items() map[K]V { + tmp := make(map[K]V) + + // Insert items to temporary map. + for item := range m.IterBuffered() { + tmp[item.Key] = item.Val + } + + return tmp +} + +// Iterator callbacalled for every key,value found in +// maps. RLock is held for all calls for a given shard +// therefore callback sess consistent view of a shard, +// but not across the shards +type IterCb[K comparable, V any] func(key K, v V) + +// Callback based iterator, cheapest way to read +// all elements in a map. +func (m ConcurrentMap[K, V]) IterCb(fn IterCb[K, V]) { + for idx := range m.shards { + shard := (m.shards)[idx] + shard.RLock() + for key, value := range shard.items { + fn(key, value) + } + shard.RUnlock() + } +} + +// Keys returns all keys as []string +func (m ConcurrentMap[K, V]) Keys() []K { + count := m.Count() + ch := make(chan K, count) + go func() { + // Foreach shard. + wg := sync.WaitGroup{} + wg.Add(SHARD_COUNT) + for _, shard := range m.shards { + go func(shard *ConcurrentMapShared[K, V]) { + // Foreach key, value pair. + shard.RLock() + for key := range shard.items { + ch <- key + } + shard.RUnlock() + wg.Done() + }(shard) + } + wg.Wait() + close(ch) + }() + + // Generate keys + keys := make([]K, 0, count) + for k := range ch { + keys = append(keys, k) + } + return keys +} + +// Reviles ConcurrentMap "private" variables to json marshal. +func (m ConcurrentMap[K, V]) MarshalJSON() ([]byte, error) { + // Create a temporary map, which will hold all item spread across shards. + tmp := make(map[K]V) + + // Insert items to temporary map. + for item := range m.IterBuffered() { + tmp[item.Key] = item.Val + } + return json.Marshal(tmp) +} +func strfnv32[K fmt.Stringer](key K) uint32 { + return fnv32(key.String()) +} + +func fnv32(key string) uint32 { + hash := uint32(2166136261) + const prime32 = uint32(16777619) + keyLength := len(key) + for i := 0; i < keyLength; i++ { + hash *= prime32 + hash ^= uint32(key[i]) + } + return hash +} + +// Reverse process of Marshal. +func (m *ConcurrentMap[K, V]) UnmarshalJSON(b []byte) (err error) { + tmp := make(map[K]V) + + // Unmarshal into a single map. + if err := json.Unmarshal(b, &tmp); err != nil { + return err + } + + // foreach key,value pair in temporary map insert into our concurrent map. + for key, val := range tmp { + m.Set(key, val) + } + return nil +} diff --git a/logic/controller/CreatePlayer.go b/logic/controller/CreatePlayer.go index c737dac7..a8f428f8 100644 --- a/logic/controller/CreatePlayer.go +++ b/logic/controller/CreatePlayer.go @@ -4,8 +4,6 @@ import ( "blazing/common/socket/errorcode" "blazing/cool" - "blazing/logic/service/common" - "blazing/logic/service/player" "blazing/logic/service/space" "blazing/logic/service/user" @@ -28,10 +26,10 @@ func (h *Controller) ChangePlayerName(data *user.ChangePlayerNameInboundInfo, c Nickname: newnice, UserID: c.Info.UserID, } - space.GetSpace(c.Info.MapID).Range(func(playerID uint32, player common.PlayerI) bool { - + for _, player := range space.GetSpace(c.Info.MapID).User.Items() { player.SendPack(data.Head.Pack(&result)) - return true - }) + + } + return result, 0 } diff --git a/logic/controller/login.go b/logic/controller/login.go index 8364bd42..c7871441 100644 --- a/logic/controller/login.go +++ b/logic/controller/login.go @@ -5,7 +5,6 @@ import ( "blazing/common/socket/errorcode" - "blazing/logic/service/common" "blazing/logic/service/user" "blazing/logic/service/maps" @@ -72,7 +71,7 @@ func (h *Controller) Login(data *user.MAIN_LOGIN_IN, c *player.Conn) (result *us t.CompleteLogin() //通知客户端登录成功 - glog.Debug(context.Background(), "登录成功,初始地图 人数:", space.GetSpace(t.Info.MapID).Len()) + glog.Debug(context.Background(), "登录成功,初始地图 人数:", space.GetSpace(t.Info.MapID).User.Count()) result = user.NewOutInfo() //设置登录消息 @@ -81,12 +80,14 @@ func (h *Controller) Login(data *user.MAIN_LOGIN_IN, c *player.Conn) (result *us tt := maps.NewOutInfo() //copier.Copy(t.Info, tt) t1 := player.NewTomeeHeader(2001, t.Info.UserID) - defer space.GetSpace(t.Info.MapID).Set(t.Info.UserID, t).Range(func(playerID uint32, player common.PlayerI) bool { - player.SendPack(t1.Pack(&tt)) - - return true - }) + defer func() { + space.GetSpace(t.Info.MapID).User.Set(t.Info.UserID, t) + space.GetSpace(t.Info.MapID).User.IterCb() + for _, v := range space.GetSpace(t.Info.MapID).User.Items() { + v.SendPack(t1.Pack(&tt)) + } + }() return result, 0 diff --git a/logic/controller/map.go b/logic/controller/map.go index 0ad79c23..b1f83aa2 100644 --- a/logic/controller/map.go +++ b/logic/controller/map.go @@ -3,7 +3,6 @@ package controller import ( "blazing/common/socket/errorcode" - "blazing/logic/service/common" "blazing/logic/service/maphot" "blazing/logic/service/maps" "blazing/logic/service/player" @@ -14,8 +13,9 @@ import ( func (h *Controller) MapEnter(data *maps.InInfo, c *player.Player) (result *maps.OutInfo, err errorcode.ErrorCode) { //这个时候player应该是空的 - c.Info.MapID = data.MapId //登录地图 - space.GetSpace(c.Info.MapID).Set(c.Info.UserID, c) //添加玩家 + c.Info.MapID = data.MapId //登录地图 + space.GetSpace(c.Info.MapID).User.Set(c.Info.UserID, c) //添加玩家 + result = maps.NewOutInfo() c.Info.Pos = data.Point copier.Copy(result, c.Info) @@ -37,7 +37,7 @@ func (h *Controller) MapLeave(data *maps.LeaveMapInboundInfo, c *player.Player) //result = &maps.LeaveMapOutboundInfo{UserID: c.GetUserID()} c.Canmon = false //可以刷怪 data.Broadcast(c.Info.MapID, space.LeaveMapOutboundInfo{UserID: c.Info.UserID}) //同步广播 - space.GetSpace(c.Info.MapID).Delete(c.Info.UserID) + space.GetSpace(c.Info.MapID).User.Remove(c.Info.UserID) // 如果有正在运行的刷怪协程,发送停止信号 c.Info.MapID = 0 // 重置当前地图 @@ -48,12 +48,11 @@ func (h *Controller) MapList(data *maps.ListMapPlayerInboundInfo, c *player.Play result = &maps.ListMapPlayerOutboundInfo{} result.Player = make([]maps.OutInfo, 0) - space.GetSpace(c.Info.MapID).Range(func(userID uint32, player common.PlayerI) bool { + for _, v := range space.GetSpace(c.Info.MapID).User.Items() { result1 := maps.NewOutInfo() - copier.Copy(result1, player.GetInfo()) + copier.Copy(result1, v.GetInfo()) result.Player = append(result.Player, *result1) - return true - }) + } c.Canmon = true //可以刷怪 return } diff --git a/logic/logic1 b/logic/logic similarity index 100% rename from logic/logic1 rename to logic/logic diff --git a/logic/service/maps/mapin.go b/logic/service/maps/mapin.go index a45fe76d..f975b528 100644 --- a/logic/service/maps/mapin.go +++ b/logic/service/maps/mapin.go @@ -1,7 +1,6 @@ package maps import ( - "blazing/logic/service/common" "blazing/logic/service/player" "blazing/logic/service/space" @@ -25,12 +24,11 @@ type InInfo struct { func (t *InInfo) Broadcast(mapid uint32, o OutInfo) { - space.GetSpace(mapid).Range(func(playerID uint32, player common.PlayerI) bool { + for _, v := range space.GetSpace(mapid).User.Items() { t.Head.Result = 0 - player.SendPack(t.Head.Pack(&o)) - return true - }) + v.SendPack(t.Head.Pack(&o)) + } } // 这里存储星球的map diff --git a/logic/service/maps/mapout.go b/logic/service/maps/mapout.go index 91d6b235..83a83070 100644 --- a/logic/service/maps/mapout.go +++ b/logic/service/maps/mapout.go @@ -1,7 +1,6 @@ package maps import ( - "blazing/logic/service/common" "blazing/logic/service/player" "blazing/logic/service/space" ) @@ -12,10 +11,9 @@ type LeaveMapInboundInfo struct { func (t *LeaveMapInboundInfo) Broadcast(mapid uint32, o space.LeaveMapOutboundInfo) { - space.GetSpace(mapid).Range(func(playerID uint32, player common.PlayerI) bool { + for _, v := range space.GetSpace(mapid).User.Items() { t.Head.Result = 0 - player.SendPack(t.Head.Pack(&o)) - return true - }) + v.SendPack(t.Head.Pack(&o)) + } } diff --git a/logic/service/maps/walk.go b/logic/service/maps/walk.go index c4688dc4..3596ef6b 100644 --- a/logic/service/maps/walk.go +++ b/logic/service/maps/walk.go @@ -1,7 +1,6 @@ package maps import ( - "blazing/logic/service/common" "blazing/logic/service/player" "blazing/logic/service/space" @@ -25,13 +24,12 @@ func (t *WalkInInfo) Broadcast(mapid uint32, o WalkOutInfo) { //tt := planetmap //g.Dump(GetSpace(mapid).Len()) - space.GetSpace(mapid).Range(func(playerID uint32, player common.PlayerI) bool { + for _, v := range space.GetSpace(mapid).User.Items() { t.Head.Result = 0 tt := t.Head.Pack(&o) - player.SendPack(tt) - //player.Cheak(err) - return true - }) + v.SendPack(tt) + + } } // PeopleWalkOutboundInfo PeopleWalkOutboundInfo类,实现OutboundMessage接口 diff --git a/logic/service/pet/pet.go b/logic/service/pet/pet.go index c3f12935..408e6369 100644 --- a/logic/service/pet/pet.go +++ b/logic/service/pet/pet.go @@ -1,7 +1,6 @@ package pet import ( - "blazing/logic/service/common" "blazing/logic/service/player" "blazing/logic/service/space" "blazing/modules/blazing/model" @@ -41,12 +40,11 @@ type PetShowInboundInfo struct { func (t *PetShowInboundInfo) Broadcast(mapid uint32, o PetShowOutboundInfo) { - space.GetSpace(mapid).Range(func(playerID uint32, player common.PlayerI) bool { + for _, v := range space.GetSpace(mapid).User.Items() { t.Head.Result = 0 - player.SendPack(t.Head.Pack(&o)) - return true - }) + v.SendPack(t.Head.Pack(&o)) + } } type PetShowOutboundInfo struct { diff --git a/logic/service/player/player.go b/logic/service/player/player.go index 87993823..47584631 100644 --- a/logic/service/player/player.go +++ b/logic/service/player/player.go @@ -263,16 +263,21 @@ func LeaveMap(c common.PlayerI) { if c == nil { return } + if c.GetInfo() == nil { + return + } + if c.GetInfo().MapID == 0 { + return + } t := NewTomeeHeader(2002, c.GetInfo().UserID) - space.GetSpace(c.GetInfo().MapID).Range(func(playerID uint32, player common.PlayerI) bool { - if playerID != c.GetInfo().UserID { - player.SendPack(t.Pack(&space.LeaveMapOutboundInfo{UserID: c.GetInfo().UserID})) + for k, v := range space.GetSpace(c.GetInfo().MapID).User.Items() { + if k != c.GetInfo().UserID { + v.SendPack(t.Pack(&space.LeaveMapOutboundInfo{UserID: c.GetInfo().UserID})) } - return true - }) - space.GetSpace(c.GetInfo().MapID).Delete(c.GetInfo().UserID) + } + space.GetSpace(c.GetInfo().MapID).User.Remove(c.GetInfo().UserID) } // Save 保存玩家数据 diff --git a/logic/service/player/server.go b/logic/service/player/server.go index 08814ba9..097115cc 100644 --- a/logic/service/player/server.go +++ b/logic/service/player/server.go @@ -8,15 +8,15 @@ func GetPlayer(c *Conn, userid uint32) *Player { //TODO 这里待优化,可能 //检查player初始化,是否为conn初始后取map,防止二次连接后存在两个player clientdata := c.MainConn.Context().(*ClientData) - if clientdata.Player != nil { - return clientdata.Player + if clientdata.Player == nil { + + clientdata.Player = NewPlayer( + + WithConn(c), //注入conn + ) + } - clientdata.Player = NewPlayer( - - WithConn(c), //注入conn - ) - // gff := socket.NewClientData() // gff.Player = clientdata.Player @@ -30,15 +30,18 @@ func KickPlayer(userid uint32) { //踢出玩家 //TODO 返回错误码 //var player *entity.Player if player1, ok := Mainplayer.Load(userid); ok { - //取成功,否则创建 - //player1.Save() //先保存数据再返回 - head := NewTomeeHeader(1001, userid) - head.Result = uint32(errorcode.ErrorCodes.ErrAccountLoggedInElsewhere) - //实际上这里有个问题,会造成重复保存问题 + if player1.IsLogin { + //取成功,否则创建 + //player1.Save() //先保存数据再返回 + head := NewTomeeHeader(1001, userid) + head.Result = uint32(errorcode.ErrorCodes.ErrAccountLoggedInElsewhere) + //实际上这里有个问题,会造成重复保存问题 + + player1.SendPack(head.Pack(nil)) + player1.MainConn.MainConn.Close() + // clientdata.Player = player + } - player1.SendPack(head.Pack(nil)) - player1.MainConn.MainConn.Close() - // clientdata.Player = player } //return player diff --git a/logic/service/player/wscodec.go b/logic/service/player/wscodec.go index b30e43e5..a5471392 100644 --- a/logic/service/player/wscodec.go +++ b/logic/service/player/wscodec.go @@ -13,6 +13,7 @@ import ( ) type Conn struct { + MainConn gnet.Conn `struc:"[0]pad"` //TODO 不序列化,,序列化下面的作为blob存数据库 Mu sync.Mutex } diff --git a/logic/service/space/hot.go b/logic/service/space/hot.go index f429ede5..87187526 100644 --- a/logic/service/space/hot.go +++ b/logic/service/space/hot.go @@ -23,10 +23,10 @@ func GetMapHot() []MapHotInfo { t1, ok := tt[uint32(v.Super)] if ok { - tt[uint32(v.Super)] = uint32(int(t1) + GetSpace(uint32(v.ID)).Len()) + tt[uint32(v.Super)] = uint32(int(t1) + GetSpace(uint32(v.ID)).User.Count()) } else { - tt[uint32(v.Super)] = uint32(GetSpace(uint32(v.ID)).Len()) + tt[uint32(v.Super)] = uint32(GetSpace(uint32(v.ID)).User.Count()) } } diff --git a/logic/service/space/space.go b/logic/service/space/space.go index 32e61e91..ab0f7541 100644 --- a/logic/service/space/space.go +++ b/logic/service/space/space.go @@ -7,74 +7,40 @@ import ( "blazing/logic/service/common" "blazing/modules/blazing/model" - "sync" ) // Space 针对Player的并发安全map,键为uint32类型 type Space struct { - mu sync.RWMutex // 读写锁,读多写少场景更高效 - data map[uint32]common.PlayerI // 存储玩家数据的map,键为玩家ID - CanRefresh bool //是否能够刷怪 - ID uint32 // 地图ID - Name string //地图名称 - DefaultPos model.Pos //默认位置DefaultPos - Positions map[uint32]model.Pos //从上一个地图跳转后默认位置 + User utils.ConcurrentMap[uint32, common.PlayerI] // 存储玩家数据的map,键为玩家ID + CanRefresh bool //是否能够刷怪 + ID uint32 // 地图ID + Name string //地图名称 + DefaultPos model.Pos //默认位置DefaultPos + + //Positions map[uint32]model.Pos //从上一个地图跳转后默认位置 无任何写操作 } // NewSyncMap 创建一个新的玩家同步map func NewSpace() *Space { return &Space{ - data: make(map[uint32]common.PlayerI), + User: utils.NewWithCustomShardingFunction[uint32, common.PlayerI](func(key uint32) uint32 { + return key + }), } } -// Get 根据玩家ID获取玩家实例 -// 读操作使用RLock,允许多个goroutine同时读取 -func (m *Space) Get(playerID uint32) (common.PlayerI, bool) { - m.mu.RLock() - defer m.mu.RUnlock() - val, exists := m.data[playerID] - return val, exists -} - -// Set 存储玩家实例(按ID) -// 写操作使用Lock,独占锁保证数据一致性 -func (m *Space) Set(playerID uint32, player common.PlayerI) *Space { - m.mu.Lock() - defer m.mu.Unlock() - m.data[playerID] = player - - return m -} - -// Delete 根据玩家ID删除玩家实例 -// 写操作使用Lock -func (m *Space) Delete(playerID uint32) { - m.mu.Lock() - defer m.mu.Unlock() - delete(m.data, playerID) -} - -// Len 获取当前玩家数量 -// 读操作使用RLock -func (m *Space) Len() int { - m.mu.RLock() - defer m.mu.RUnlock() - return len(m.data) -} - -// Range 遍历所有玩家并执行回调函数 -// 读操作使用RLock,遍历过程中不会阻塞其他读操作 -func (m *Space) Range(f func(playerID uint32, player common.PlayerI) bool) { - m.mu.RLock() - defer m.mu.RUnlock() - for id, player := range m.data { - // 若回调返回false,则停止遍历 - if !f(id, player) { - break - } - } -} +// // Range 遍历所有玩家并执行回调函数 +// // 读操作使用RLock,遍历过程中不会阻塞其他读操作 +// func (m *Space) Range(f func(playerID uint32, player common.PlayerI) bool) { +// m.mu.RLock() +// defer m.mu.RUnlock() +// for id, player := range m.User { +// // 若回调返回false,则停止遍历 +// if !f(id, player) { +// break +// } +// } +// } // 获取星球 func GetSpace(id uint32) *Space { @@ -95,14 +61,14 @@ func GetSpace(id uint32) *Space { for _, v := range xmlres.MapConfig.Maps { if v.ID == int(id) { //找到这个地图 t := NewSpace() - t.DefaultPos = model.Pos{X: uint32(v.X), Y: uint32(v.Y)} + //t.DefaultPos = model.Pos{X: uint32(v.X), Y: uint32(v.Y)} t.ID = uint32(v.ID) t.Name = v.Name - t.Positions = make(map[uint32]model.Pos) - for _, v := range v.Entries.Entries { //添加地图入口 - t.Positions[uint32(v.FromMap)] = model.Pos{X: uint32(v.PosX), Y: uint32(v.PosY)} + // t.Positions = make(map[uint32]model.Pos) + // for _, v := range v.Entries.Entries { //添加地图入口 + // t.Positions[uint32(v.FromMap)] = model.Pos{X: uint32(v.PosX), Y: uint32(v.PosY)} - } + // } planetmap.Store(id, t) return t