From 45c855a5160e1918707c2a6bef422b261486af72 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Wed, 26 Jul 2023 12:03:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=88=BF=E9=97=B4=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=99=A8=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/room/errors.go | 12 ++ game/room/events.go | 95 ++++++++++++ game/room/info.go | 8 + game/room/manager.go | 259 ++++++++++++++++++++++++++++++++ game/room/room.go | 30 ++++ utils/concurrent/balance_map.go | 103 ++++++++----- 6 files changed, 473 insertions(+), 34 deletions(-) create mode 100644 game/room/errors.go create mode 100644 game/room/events.go create mode 100644 game/room/info.go create mode 100644 game/room/manager.go create mode 100644 game/room/room.go diff --git a/game/room/errors.go b/game/room/errors.go new file mode 100644 index 0000000..2d7abeb --- /dev/null +++ b/game/room/errors.go @@ -0,0 +1,12 @@ +package room + +import "errors" + +var ( + // ErrRoomNotExist 房间不存在 + ErrRoomNotExist = errors.New("room not exist") + // ErrRoomPlayerFull 房间人数已满 + ErrRoomPlayerFull = errors.New("room player full") + // ErrPlayerNotExist 玩家不存在 + ErrPlayerNotExist = errors.New("player not exist") +) diff --git a/game/room/events.go b/game/room/events.go new file mode 100644 index 0000000..c8355cd --- /dev/null +++ b/game/room/events.go @@ -0,0 +1,95 @@ +package room + +import "github.com/kercylan98/minotaur/game" + +type ( + // PlayerJoinRoomEventHandle 玩家加入房间事件处理函数 + PlayerJoinRoomEventHandle[PID comparable, P game.Player[PID], R Room[PID, P]] func(room R, player P) + // PlayerLeaveRoomEventHandle 玩家离开房间事件处理函数 + PlayerLeaveRoomEventHandle[PID comparable, P game.Player[PID], R Room[PID, P]] func(room R, player P) + // PlayerKickedOutEventHandle 玩家被踢出房间事件处理函数 + PlayerKickedOutEventHandle[PID comparable, P game.Player[PID], R Room[PID, P]] func(room R, executor, kicked PID, reason string) +) + +func newEvent[PID comparable, P game.Player[PID], R Room[PID, P]]() *event[PID, P, R] { + return &event[PID, P, R]{ + playerJoinRoomEventRoomHandles: make(map[int64][]PlayerJoinRoomEventHandle[PID, P, R]), + playerLeaveRoomEventRoomHandles: make(map[int64][]PlayerLeaveRoomEventHandle[PID, P, R]), + playerKickedOutEventRoomHandles: make(map[int64][]PlayerKickedOutEventHandle[PID, P, R]), + } +} + +type event[PID comparable, P game.Player[PID], R Room[PID, P]] struct { + playerJoinRoomEventHandles []PlayerJoinRoomEventHandle[PID, P, R] + playerJoinRoomEventRoomHandles map[int64][]PlayerJoinRoomEventHandle[PID, P, R] + playerLeaveRoomEventHandles []PlayerLeaveRoomEventHandle[PID, P, R] + playerLeaveRoomEventRoomHandles map[int64][]PlayerLeaveRoomEventHandle[PID, P, R] + playerKickedOutEventHandles []PlayerKickedOutEventHandle[PID, P, R] + playerKickedOutEventRoomHandles map[int64][]PlayerKickedOutEventHandle[PID, P, R] +} + +func (slf *event[PID, P, R]) unReg(guid int64) { + delete(slf.playerJoinRoomEventRoomHandles, guid) + delete(slf.playerLeaveRoomEventRoomHandles, guid) + delete(slf.playerKickedOutEventRoomHandles, guid) +} + +// RegPlayerJoinRoomEvent 玩家进入房间时将立即执行被注册的事件处理函数 +func (slf *event[PID, P, R]) RegPlayerJoinRoomEvent(handle PlayerJoinRoomEventHandle[PID, P, R]) { + slf.playerJoinRoomEventHandles = append(slf.playerJoinRoomEventHandles, handle) +} + +// OnPlayerJoinRoomEvent 玩家进入房间时将立即执行被注册的事件处理函数 +func (slf *event[PID, P, R]) OnPlayerJoinRoomEvent(room R, player P) { + for _, handle := range slf.playerJoinRoomEventHandles { + handle(room, player) + } + for _, handle := range slf.playerJoinRoomEventRoomHandles[room.GetGuid()] { + handle(room, player) + } +} + +// RegPlayerJoinRoomEventWithRoom 玩家进入房间时将立即执行被注册的事件处理函数 +func (slf *event[PID, P, R]) RegPlayerJoinRoomEventWithRoom(room R, handle PlayerJoinRoomEventHandle[PID, P, R]) { + slf.playerJoinRoomEventRoomHandles[room.GetGuid()] = append(slf.playerJoinRoomEventRoomHandles[room.GetGuid()], handle) +} + +// RegPlayerLeaveRoomEvent 玩家离开房间时将立即执行被注册的事件处理函数 +func (slf *event[PID, P, R]) RegPlayerLeaveRoomEvent(handle PlayerLeaveRoomEventHandle[PID, P, R]) { + slf.playerLeaveRoomEventHandles = append(slf.playerLeaveRoomEventHandles, handle) +} + +// RegPlayerLeaveRoomEventWithRoom 玩家离开房间时将立即执行被注册的事件处理函数 +func (slf *event[PID, P, R]) RegPlayerLeaveRoomEventWithRoom(room R, handle PlayerLeaveRoomEventHandle[PID, P, R]) { + slf.playerLeaveRoomEventRoomHandles[room.GetGuid()] = append(slf.playerLeaveRoomEventRoomHandles[room.GetGuid()], handle) +} + +// OnPlayerLeaveRoomEvent 玩家离开房间时将立即执行被注册的事件处理函数 +func (slf *event[PID, P, R]) OnPlayerLeaveRoomEvent(room R, player P) { + for _, handle := range slf.playerLeaveRoomEventHandles { + handle(room, player) + } + for _, handle := range slf.playerLeaveRoomEventRoomHandles[room.GetGuid()] { + handle(room, player) + } +} + +// RegPlayerKickedOutEvent 玩家被踢出房间时将立即执行被注册的事件处理函数 +func (slf *event[PID, P, R]) RegPlayerKickedOutEvent(handle PlayerKickedOutEventHandle[PID, P, R]) { + slf.playerKickedOutEventHandles = append(slf.playerKickedOutEventHandles, handle) +} + +// RegPlayerKickedOutEventWithRoom 玩家被踢出房间时将立即执行被注册的事件处理函数 +func (slf *event[PID, P, R]) RegPlayerKickedOutEventWithRoom(room R, handle PlayerKickedOutEventHandle[PID, P, R]) { + slf.playerKickedOutEventRoomHandles[room.GetGuid()] = append(slf.playerKickedOutEventRoomHandles[room.GetGuid()], handle) +} + +// OnPlayerKickedOutEvent 玩家被踢出房间时将立即执行被注册的事件处理函数 +func (slf *event[PID, P, R]) OnPlayerKickedOutEvent(room R, executor, kicked PID, reason string) { + for _, handle := range slf.playerKickedOutEventHandles { + handle(room, executor, kicked, reason) + } + for _, handle := range slf.playerKickedOutEventRoomHandles[room.GetGuid()] { + handle(room, executor, kicked, reason) + } +} diff --git a/game/room/info.go b/game/room/info.go new file mode 100644 index 0000000..35fedd9 --- /dev/null +++ b/game/room/info.go @@ -0,0 +1,8 @@ +package room + +import "github.com/kercylan98/minotaur/game" + +type info[PlayerID comparable, P game.Player[PlayerID], R Room[PlayerID, P]] struct { + room R + playerLimit int // 玩家人数上限, <= 0 表示无限制 +} diff --git a/game/room/manager.go b/game/room/manager.go new file mode 100644 index 0000000..f343c75 --- /dev/null +++ b/game/room/manager.go @@ -0,0 +1,259 @@ +package room + +import ( + "github.com/kercylan98/minotaur/game" + "github.com/kercylan98/minotaur/utils/concurrent" +) + +// NewManager 创建房间管理器 +func NewManager[PID comparable, P game.Player[PID], R Room[PID, P]]() *Manager[PID, P, R] { + manager := &Manager[PID, P, R]{ + event: newEvent[PID, P, R](), + rooms: concurrent.NewBalanceMap[int64, *info[PID, P, R]](), + players: concurrent.NewBalanceMap[PID, P](), + pr: concurrent.NewBalanceMap[PID, map[int64]struct{}](), + } + + return manager +} + +// Manager 房间管理器 +type Manager[PID comparable, P game.Player[PID], R Room[PID, P]] struct { + *event[PID, P, R] + rooms *concurrent.BalanceMap[int64, *info[PID, P, R]] // 所有房间 + players *concurrent.BalanceMap[PID, P] // 所有加入房间的玩家 + pr *concurrent.BalanceMap[PID, map[int64]struct{}] // 玩家所在房间 + rp *concurrent.BalanceMap[int64, map[PID]struct{}] // 房间中的玩家 + +} + +// CreateRoom 创建房间 +func (slf *Manager[PID, P, R]) CreateRoom(room R) { + roomInfo := &info[PID, P, R]{room: room} + slf.rooms.Set(room.GetGuid(), roomInfo) +} + +// ReleaseRoom 释放房间 +func (slf *Manager[PID, P, R]) ReleaseRoom(guid int64) { + slf.unReg(guid) + slf.rooms.Delete(guid) +} + +// GetRoom 获取房间 +func (slf *Manager[PID, P, R]) GetRoom(guid int64) R { + return slf.rooms.Get(guid).room +} + +// Exist 检查房间是否存在 +func (slf *Manager[PID, P, R]) Exist(guid int64) bool { + return slf.rooms.Exist(guid) +} + +// GetRooms 获取所有房间 +func (slf *Manager[PID, P, R]) GetRooms() map[int64]R { + var rooms = make(map[int64]R) + slf.rooms.Atom(func(m map[int64]*info[PID, P, R]) { + for id, info := range m { + rooms[id] = info.room + } + }) + return rooms +} + +// GetRoomCount 获取房间数量 +func (slf *Manager[PID, P, R]) GetRoomCount() int { + return slf.rooms.Size() +} + +// GetRoomPlayerCount 获取房间中玩家数量 +func (slf *Manager[PID, P, R]) GetRoomPlayerCount(guid int64) int { + var count int + slf.rp.Atom(func(m map[int64]map[PID]struct{}) { + count = len(m[guid]) + }) + return count +} + +// ExistPlayer 检查玩家是否在任一房间内 +func (slf *Manager[PID, P, R]) ExistPlayer(id PID) bool { + return slf.players.Exist(id) +} + +// InRoom 检查玩家是否在指定房间内 +func (slf *Manager[PID, P, R]) InRoom(id PID, guid int64) bool { + var in bool + slf.pr.Atom(func(m map[PID]map[int64]struct{}) { + rooms, exist := m[id] + if !exist { + return + } + _, in = rooms[guid] + }) + return in +} + +// GetPlayer 获取玩家 +func (slf *Manager[PID, P, R]) GetPlayer(id PID) P { + return slf.players.Get(id) +} + +// GetPlayers 获取所有玩家 +func (slf *Manager[PID, P, R]) GetPlayers() *concurrent.BalanceMap[PID, P] { + return slf.players +} + +// GetPlayerCount 获取玩家数量 +func (slf *Manager[PID, P, R]) GetPlayerCount() int { + return slf.players.Size() +} + +// GetPlayerRoom 获取玩家所在房间 +func (slf *Manager[PID, P, R]) GetPlayerRoom(id PID) []R { + var result = make([]R, 0) + slf.pr.Atom(func(m map[PID]map[int64]struct{}) { + rooms, exist := m[id] + if !exist { + return + } + for id := range rooms { + result = append(result, slf.rooms.Get(id).room) + } + }) + return result +} + +// GetPlayerRoomCount 获取玩家所在房间数量 +func (slf *Manager[PID, P, R]) GetPlayerRoomCount(id PID) int { + var count int + slf.pr.Atom(func(m map[PID]map[int64]struct{}) { + count = len(m[id]) + }) + return count +} + +// GetRoomPlayer 获取房间中的玩家 +func (slf *Manager[PID, P, R]) GetRoomPlayer(roomId int64, playerId PID) P { + var player P + slf.rp.Atom(func(m map[int64]map[PID]struct{}) { + players, exist := m[roomId] + if !exist { + return + } + _, exist = players[playerId] + if !exist { + return + } + player = slf.players.Get(playerId) + }) + return player +} + +// GetRoomPlayers 获取房间中的玩家 +func (slf *Manager[PID, P, R]) GetRoomPlayers(guid int64) map[PID]P { + var result = make(map[PID]P) + slf.rp.Atom(func(m map[int64]map[PID]struct{}) { + players, exist := m[guid] + if !exist { + return + } + for id := range players { + result[id] = slf.players.Get(id) + } + }) + return result +} + +// GetRoomPlayerLimit 获取房间中的玩家数量上限 +func (slf *Manager[PID, P, R]) GetRoomPlayerLimit(guid int64) int { + return slf.rooms.Get(guid).playerLimit +} + +// Leave 使玩家离开房间 +func (slf *Manager[PID, P, R]) Leave(roomId int64, player P) { + slf.rooms.Atom(func(m map[int64]*info[PID, P, R]) { + room, exist := m[roomId] + if !exist { + return + } + slf.OnPlayerLeaveRoomEvent(room.room, player) + slf.pr.Atom(func(m map[PID]map[int64]struct{}) { + rooms, exist := m[player.GetID()] + if !exist { + return + } + delete(rooms, roomId) + }) + slf.rp.Atom(func(m map[int64]map[PID]struct{}) { + players, exist := m[roomId] + if !exist { + return + } + delete(players, player.GetID()) + }) + }) +} + +// Join 使玩家加入房间 +func (slf *Manager[PID, P, R]) Join(player P, roomId int64) error { + var err error + slf.rooms.Atom(func(m map[int64]*info[PID, P, R]) { + room, exist := m[roomId] + if !exist { + err = ErrRoomNotExist + return + } + if room.playerLimit > 0 && room.playerLimit <= slf.GetRoomPlayerCount(roomId) { + err = ErrRoomPlayerFull + return + } + slf.pr.Atom(func(m map[PID]map[int64]struct{}) { + rooms, exist := m[player.GetID()] + if !exist { + rooms = make(map[int64]struct{}) + m[player.GetID()] = rooms + } + rooms[roomId] = struct{}{} + }) + slf.rp.Atom(func(m map[int64]map[PID]struct{}) { + players, exist := m[roomId] + if !exist { + players = make(map[PID]struct{}) + m[roomId] = players + } + players[player.GetID()] = struct{}{} + }) + slf.players.Set(player.GetID(), player) + slf.OnPlayerJoinRoomEvent(room.room, player) + }) + return err +} + +// KickOut 以某种原因踢出特定玩家 +// - 该函数不会校验任何权限相关的内容,调用后将直接踢出玩家 +func (slf *Manager[PID, P, R]) KickOut(roomId int64, executor, kicked PID, reason string) error { + var err error + var room R + slf.rp.Atom(func(m map[int64]map[PID]struct{}) { + players, exist := m[roomId] + if !exist { + err = ErrPlayerNotExist + return + } + _, exist = players[executor] + if !exist { + err = ErrPlayerNotExist + return + } + _, exist = players[kicked] + if !exist { + return + } + room = slf.rooms.Get(roomId).room + }) + if err == nil { + return err + } + slf.OnPlayerKickedOutEvent(room, executor, kicked, reason) + slf.Leave(roomId, slf.players.Get(kicked)) + return nil +} diff --git a/game/room/room.go b/game/room/room.go new file mode 100644 index 0000000..a4d2633 --- /dev/null +++ b/game/room/room.go @@ -0,0 +1,30 @@ +package room + +import "github.com/kercylan98/minotaur/game" + +// Room 房间类似于简版的游戏世界(World),不过没有游戏实体 +type Room[PlayerID comparable, P game.Player[PlayerID]] interface { + // GetGuid 获取房间的唯一标识符 + GetGuid() int64 + // GetPlayerLimit 获取玩家人数上限 + GetPlayerLimit() int + // GetPlayer 根据玩家id获取玩家 + GetPlayer(id PlayerID) P + // GetPlayers 获取房间中的所有玩家 + GetPlayers() map[PlayerID]P + // GetPlayerCount 获取玩家数量 + GetPlayerCount() int + // IsExistPlayer 检查房间中是否存在特定玩家 + IsExistPlayer(id PlayerID) bool + // IsOwner 检查玩家是否是房主 + IsOwner(id PlayerID) bool + // ChangeOwner 设置玩家为房主 + ChangeOwner(id PlayerID) + + // Join 使特定玩家加入房间 + Join(player P) error + // Leave 使特定玩家离开房间 + Leave(id PlayerID) + // KickOut 将特定玩家踢出房间 + KickOut(id, target PlayerID, reason string) error +} diff --git a/utils/concurrent/balance_map.go b/utils/concurrent/balance_map.go index 39f07ca..24b43f1 100644 --- a/utils/concurrent/balance_map.go +++ b/utils/concurrent/balance_map.go @@ -21,150 +21,183 @@ func NewBalanceMap[Key comparable, value any](options ...BalanceMapOption[Key, v type BalanceMap[Key comparable, Value any] struct { lock sync.RWMutex data map[Key]Value + atom bool } // Set 设置一个值 func (slf *BalanceMap[Key, Value]) Set(key Key, value Value) { - slf.lock.Lock() - defer slf.lock.Unlock() + if !slf.atom { + slf.lock.Lock() + defer slf.lock.Unlock() + } slf.data[key] = value } // Get 获取一个值 func (slf *BalanceMap[Key, Value]) Get(key Key) Value { - slf.lock.RLock() - defer slf.lock.RUnlock() + if !slf.atom { + slf.lock.RLock() + defer slf.lock.RUnlock() + } return slf.data[key] } // Atom 原子操作 func (slf *BalanceMap[Key, Value]) Atom(handle func(m map[Key]Value)) { - slf.lock.Lock() + if !slf.atom { + slf.lock.Lock() + defer slf.lock.Unlock() + } handle(slf.data) - slf.lock.Unlock() } // Exist 判断是否存在 func (slf *BalanceMap[Key, Value]) Exist(key Key) bool { - slf.lock.RLock() + if !slf.atom { + slf.lock.RLock() + defer slf.lock.RUnlock() + } _, exist := slf.data[key] - slf.lock.RUnlock() return exist } // GetExist 获取一个值并判断是否存在 func (slf *BalanceMap[Key, Value]) GetExist(key Key) (Value, bool) { - slf.lock.RLock() + if !slf.atom { + slf.lock.RLock() + defer slf.lock.RUnlock() + } value, exist := slf.data[key] - slf.lock.RUnlock() return value, exist } // Delete 删除一个值 func (slf *BalanceMap[Key, Value]) Delete(key Key) { - slf.lock.Lock() + if !slf.atom { + slf.lock.Lock() + defer slf.lock.Unlock() + } delete(slf.data, key) - defer slf.lock.Unlock() } // DeleteGet 删除一个值并返回 func (slf *BalanceMap[Key, Value]) DeleteGet(key Key) Value { - slf.lock.Lock() + if !slf.atom { + slf.lock.Lock() + defer slf.lock.Unlock() + } v := slf.data[key] delete(slf.data, key) - slf.lock.Unlock() return v } // DeleteGetExist 删除一个值并返回是否存在 func (slf *BalanceMap[Key, Value]) DeleteGetExist(key Key) (Value, bool) { - slf.lock.Lock() + if !slf.atom { + slf.lock.Lock() + defer slf.lock.Unlock() + } v, exist := slf.data[key] delete(slf.data, key) - defer slf.lock.Unlock() return v, exist } // DeleteExist 删除一个值并返回是否存在 func (slf *BalanceMap[Key, Value]) DeleteExist(key Key) bool { - slf.lock.Lock() + if !slf.atom { + slf.lock.Lock() + defer slf.lock.Unlock() + } if _, exist := slf.data[key]; !exist { slf.lock.Unlock() return exist } delete(slf.data, key) - slf.lock.Unlock() return true } // Clear 清空 func (slf *BalanceMap[Key, Value]) Clear() { - slf.lock.Lock() + if !slf.atom { + slf.lock.Lock() + defer slf.lock.Unlock() + } for k := range slf.data { delete(slf.data, k) } - slf.lock.Unlock() } // ClearHandle 清空并处理 func (slf *BalanceMap[Key, Value]) ClearHandle(handle func(key Key, value Value)) { - slf.lock.Lock() + if !slf.atom { + slf.lock.Lock() + defer slf.lock.Unlock() + } for k, v := range slf.data { handle(k, v) delete(slf.data, k) } - slf.lock.Unlock() } // Range 遍历所有值,如果 handle 返回 true 则停止遍历 func (slf *BalanceMap[Key, Value]) Range(handle func(key Key, value Value) bool) { - slf.lock.Lock() + if !slf.atom { + slf.lock.Lock() + defer slf.lock.Unlock() + } for k, v := range slf.data { key, value := k, v if handle(key, value) { break } } - slf.lock.Unlock() } // Keys 获取所有的键 func (slf *BalanceMap[Key, Value]) Keys() []Key { - slf.lock.RLock() + if !slf.atom { + slf.lock.RLock() + defer slf.lock.RUnlock() + } var s = make([]Key, 0, len(slf.data)) for k := range slf.data { s = append(s, k) } - slf.lock.RUnlock() return s } // Slice 获取所有的值 func (slf *BalanceMap[Key, Value]) Slice() []Value { - slf.lock.RLock() + if !slf.atom { + slf.lock.RLock() + defer slf.lock.RUnlock() + } var s = make([]Value, 0, len(slf.data)) for _, v := range slf.data { s = append(s, v) } - slf.lock.RUnlock() return s } // Map 转换为普通 map func (slf *BalanceMap[Key, Value]) Map() map[Key]Value { - slf.lock.RLock() + if !slf.atom { + slf.lock.RLock() + defer slf.lock.RUnlock() + } var m = make(map[Key]Value) for k, v := range slf.data { m[k] = v } - slf.lock.RUnlock() return m } // Size 获取数量 func (slf *BalanceMap[Key, Value]) Size() int { - slf.lock.RLock() - defer slf.lock.RUnlock() + if !slf.atom { + slf.lock.RLock() + defer slf.lock.RUnlock() + } return len(slf.data) } @@ -175,11 +208,13 @@ func (slf *BalanceMap[Key, Value]) MarshalJSON() ([]byte, error) { func (slf *BalanceMap[Key, Value]) UnmarshalJSON(bytes []byte) error { var m = make(map[Key]Value) + if !slf.atom { + slf.lock.Lock() + slf.lock.Unlock() + } if err := json.Unmarshal(bytes, &m); err != nil { return err } - slf.lock.Lock() slf.data = m - slf.lock.Unlock() return nil }