diff --git a/game/fight/round.go b/game/fight/round.go index 4baa9f7..bd68adb 100644 --- a/game/fight/round.go +++ b/game/fight/round.go @@ -5,6 +5,7 @@ import ( "github.com/kercylan98/minotaur/utils/random" "github.com/kercylan98/minotaur/utils/slice" "github.com/kercylan98/minotaur/utils/timer" + "sync" "time" ) @@ -65,6 +66,8 @@ type Round[Data RoundData] struct { changeEventHandles []RoundChangeEvent[Data] // 游戏回合变更事件 actionTimeoutEventHandles []RoundActionTimeoutEvent[Data] // 行动超时事件 actionRefreshEventHandles []RoundActionRefreshEvent[Data] // 行动刷新事件 + + actionMutex sync.Mutex } // GetData 获取游戏数据 @@ -77,6 +80,8 @@ func (slf *Round[Data]) GetData() Data { func (slf *Round[Data]) Start() { slf.currentEntity = -1 slf.round = 1 + slf.actionMutex.Lock() + defer slf.actionMutex.Unlock() slf.loop(false) } @@ -157,7 +162,11 @@ func (slf *Round[Data]) SkipCamp() { // ActionRefresh 刷新行动超时时间 func (slf *Round[Data]) ActionRefresh() { slf.actionTimeoutTime = time.Now().Add(slf.actionTimeout).Unix() - slf.ticker.After(slf.actionTimeoutTickerName, slf.actionTimeout, slf.loop, true) + slf.ticker.After(slf.actionTimeoutTickerName, slf.actionTimeout, func(timeout bool) { + slf.actionMutex.Lock() + defer slf.actionMutex.Unlock() + slf.loop(timeout) + }, true) } // ActionFinish 结束行动 @@ -166,6 +175,8 @@ func (slf *Round[Data]) ActionFinish() { if slf.shareAction { slf.currentEntity = -1 } + slf.actionMutex.Lock() + defer slf.actionMutex.Unlock() slf.loop(false) } diff --git a/game/room/manager.go b/game/room/manager.go index 57cd53e..09077ee 100644 --- a/game/room/manager.go +++ b/game/room/manager.go @@ -67,6 +67,9 @@ func (slf *Manager[PID, P, R]) ReleaseRoom(guid int64) { slf.pr.Atom(func(m map[PID]map[int64]struct{}) { for playerId := range players { delete(m[playerId], guid) + if len(m[playerId]) == 0 { + slf.players.Delete(playerId) + } } }) }) @@ -364,6 +367,9 @@ func (slf *Manager[PID, P, R]) Leave(roomId int64, player P) { return } delete(rooms, roomId) + if len(rooms) == 0 { + slf.players.Delete(player.GetID()) + } }) slf.rp.Atom(func(m map[int64]map[PID]struct{}) { players, exist := m[roomId] diff --git a/game/room/manager_test.go b/game/room/manager_test.go new file mode 100644 index 0000000..e47b6e9 --- /dev/null +++ b/game/room/manager_test.go @@ -0,0 +1,26 @@ +package room_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/game/room" + "testing" +) + +func TestNewManager(t *testing.T) { + m := room.NewManager[string, *Player, *Room]() + r := &Room{} + m.CreateRoom(r) + helper := m.GetHelper(r) + + helper.Join(&Player{ID: "player_01"}) + helper.Join(&Player{ID: "player_02"}) + helper.Join(&Player{ID: "player_03"}) + helper.Leave(helper.GetPlayer("player_02")) + helper.Join(&Player{ID: "player_02"}) + + helper.BroadcastExcept(func(player *Player) { + fmt.Println(player.GetID()) + }, func(player *Player) bool { + return false + }) +} diff --git a/game/room/seat_test.go b/game/room/seat_test.go index d479341..58ec3cc 100644 --- a/game/room/seat_test.go +++ b/game/room/seat_test.go @@ -8,10 +8,11 @@ import ( ) type Player struct { + ID string } func (slf *Player) GetID() string { - return "" + return slf.ID } func (slf *Player) GetConn() *server.Conn { diff --git a/game/space/room_controller.go b/game/space/room_controller.go new file mode 100644 index 0000000..b409352 --- /dev/null +++ b/game/space/room_controller.go @@ -0,0 +1,352 @@ +package space + +import ( + "github.com/kercylan98/minotaur/utils/generic" + "github.com/kercylan98/minotaur/utils/hash" + "github.com/kercylan98/minotaur/utils/slice" + "sync" +) + +func newRoomController[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]](manager *RoomManager[EntityID, RoomID, Entity, Room], room Room, options *RoomControllerOptions) *RoomController[EntityID, RoomID, Entity, Room] { + controller := &RoomController[EntityID, RoomID, Entity, Room]{ + manager: manager, + options: options, + entities: make(map[EntityID]Entity), + room: room, + } + + manager.roomsRWMutex.Lock() + defer manager.roomsRWMutex.Unlock() + manager.rooms[room.GetId()] = controller + + return controller +} + +// RoomController 对房间进行操作的控制器,由 RoomManager 接管后返回 +type RoomController[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] struct { + manager *RoomManager[EntityID, RoomID, Entity, Room] + options *RoomControllerOptions + room Room + entities map[EntityID]Entity + entitiesRWMutex sync.RWMutex + + vacancy []int // 空缺的座位 + seat []*EntityID // 座位上的玩家 +} + +// JoinSeat 设置特定对象加入座位,当具体的座位不存在的时候,将会自动分配座位 +// - 当目标座位存在玩家或未添加到房间中的时候,将会返回错误 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) JoinSeat(entityId EntityID, seat ...int) error { + slf.entitiesRWMutex.Lock() + defer slf.entitiesRWMutex.Unlock() + _, exist := slf.entities[entityId] + if !exist { + return ErrNotInRoom + } + var targetSeat int + if len(seat) > 0 { + targetSeat = seat[0] + if targetSeat < len(slf.seat) && slf.seat[targetSeat] != nil { + return ErrSeatNotEmpty + } + } else { + if len(slf.vacancy) > 0 { + targetSeat = slf.vacancy[0] + slf.vacancy = slf.vacancy[1:] + } else { + targetSeat = len(slf.seat) + } + } + + if targetSeat >= len(slf.seat) { + slf.seat = append(slf.seat, make([]*EntityID, targetSeat-len(slf.seat)+1)...) + } + + slf.seat[targetSeat] = &entityId + return nil +} + +// LeaveSeat 离开座位 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) LeaveSeat(entityId EntityID) { + slf.entitiesRWMutex.Lock() + defer slf.entitiesRWMutex.Unlock() + slf.leaveSeat(entityId) +} + +// leaveSeat 离开座位(无锁) +func (slf *RoomController[EntityID, RoomID, Entity, Room]) leaveSeat(entityId EntityID) { + for i, seat := range slf.seat { + if seat != nil && *seat == entityId { + slf.seat[i] = nil + slf.vacancy = append(slf.vacancy, i) + break + } + } +} + +// GetSeat 获取座位 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) GetSeat(entityId EntityID) int { + slf.entitiesRWMutex.RLock() + defer slf.entitiesRWMutex.RUnlock() + for i, seat := range slf.seat { + if seat != nil && *seat == entityId { + return i + } + } + return -1 +} + +// GetNotEmptySeat 获取非空座位 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) GetNotEmptySeat() []int { + slf.entitiesRWMutex.RLock() + defer slf.entitiesRWMutex.RUnlock() + var seats []int + for i, player := range slf.seat { + if player != nil { + seats = append(seats, i) + } + } + return seats +} + +// GetEmptySeat 获取空座位 +// - 空座位需要在有对象离开座位后才可能出现 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) GetEmptySeat() []int { + return slice.Copy(slf.vacancy) +} + +// HasSeat 判断是否有座位 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) HasSeat(entityId EntityID) bool { + return slf.GetSeat(entityId) != -1 +} + +// GetSeatEntityCount 获取座位上的实体数量 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) GetSeatEntityCount() int { + slf.entitiesRWMutex.RLock() + defer slf.entitiesRWMutex.RUnlock() + var count int + for _, seat := range slf.seat { + if seat != nil { + count++ + } + } + return count +} + +// GetSeatEntities 获取座位上的实体 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) GetSeatEntities() map[EntityID]Entity { + slf.entitiesRWMutex.RLock() + defer slf.entitiesRWMutex.RUnlock() + var entities = make(map[EntityID]Entity) + for _, entityId := range slf.seat { + if entityId != nil { + entities[*entityId] = slf.entities[*entityId] + } + } + return entities +} + +// GetSeatEntitiesByOrdered 有序的获取座位上的实体 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) GetSeatEntitiesByOrdered() []Entity { + slf.entitiesRWMutex.RLock() + defer slf.entitiesRWMutex.RUnlock() + var entities = make([]Entity, 0, len(slf.seat)) + for _, entityId := range slf.seat { + if entityId != nil { + entities = append(entities, slf.entities[*entityId]) + } + } + return entities +} + +// GetSeatEntitiesByOrderedAndContainsEmpty 获取有序的座位上的实体,包含空座位 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) GetSeatEntitiesByOrderedAndContainsEmpty() []Entity { + slf.entitiesRWMutex.RLock() + defer slf.entitiesRWMutex.RUnlock() + var entities = make([]Entity, len(slf.seat)) + for i, entityId := range slf.seat { + if entityId != nil { + entities[i] = slf.entities[*entityId] + } + } + return entities +} + +// GetSeatEntity 获取座位上的实体 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) GetSeatEntity(seat int) (entity Entity) { + slf.entitiesRWMutex.RLock() + defer slf.entitiesRWMutex.RUnlock() + if seat < len(slf.seat) { + eid := slf.seat[seat] + if eid != nil { + return slf.entities[*eid] + } + } + return entity +} + +// ContainEntity 房间内是否包含实体 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) ContainEntity(id EntityID) bool { + slf.entitiesRWMutex.RLock() + defer slf.entitiesRWMutex.RUnlock() + _, exist := slf.entities[id] + return exist +} + +// GetRoom 获取原始房间实例 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) GetRoom() Room { + return slf.room +} + +// GetEntities 获取所有实体 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) GetEntities() map[EntityID]Entity { + slf.entitiesRWMutex.RLock() + defer slf.entitiesRWMutex.RUnlock() + return hash.Copy(slf.entities) +} + +// HasEntity 判断是否有实体 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) HasEntity(id EntityID) bool { + slf.entitiesRWMutex.RLock() + defer slf.entitiesRWMutex.RUnlock() + _, exist := slf.entities[id] + return exist +} + +// GetEntity 获取实体 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) GetEntity(id EntityID) Entity { + slf.entitiesRWMutex.RLock() + defer slf.entitiesRWMutex.RUnlock() + return slf.entities[id] +} + +// GetEntityIDs 获取所有实体ID +func (slf *RoomController[EntityID, RoomID, Entity, Room]) GetEntityIDs() []EntityID { + slf.entitiesRWMutex.RLock() + defer slf.entitiesRWMutex.RUnlock() + return hash.KeyToSlice(slf.entities) +} + +// GetEntityCount 获取实体数量 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) GetEntityCount() int { + slf.entitiesRWMutex.RLock() + defer slf.entitiesRWMutex.RUnlock() + return len(slf.entities) +} + +// ChangePassword 修改房间密码 +// - 当房间密码为 nil 时,将会取消密码 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) ChangePassword(password *string) { + old := slf.options.password + slf.options.password = password + slf.manager.OnRoomChangePasswordEvent(slf, old, slf.options.password) +} + +// AddEntity 添加实体 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) AddEntity(entity Entity) error { + if slf.options.password != nil { + return ErrRoomPasswordNotMatch + } + slf.entitiesRWMutex.Lock() + defer slf.entitiesRWMutex.Unlock() + + if slf.options.maxEntityCount != nil && len(slf.entities) > *slf.options.maxEntityCount { + return ErrRoomFull + } + slf.entities[entity.GetId()] = entity + + slf.manager.OnRoomAddEntityEvent(slf, entity) + return nil +} + +// AddEntityByPassword 通过房间密码添加实体 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) AddEntityByPassword(entity Entity, password string) error { + if slf.options.password == nil || *slf.options.password != password { + return ErrRoomPasswordNotMatch + } + slf.entitiesRWMutex.Lock() + defer slf.entitiesRWMutex.Unlock() + + if slf.options.maxEntityCount != nil && len(slf.entities) > *slf.options.maxEntityCount { + return ErrRoomFull + } + slf.entities[entity.GetId()] = entity + + slf.manager.OnRoomAddEntityEvent(slf, entity) + return nil +} + +// RemoveEntity 移除实体 +// - 当实体被移除时如果实体在座位上,将会自动离开座位 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) RemoveEntity(id EntityID) { + slf.entitiesRWMutex.RLock() + defer slf.entitiesRWMutex.RUnlock() + slf.removeEntity(id) +} + +// removeEntity 移除实体(无锁) +func (slf *RoomController[EntityID, RoomID, Entity, Room]) removeEntity(id EntityID) { + slf.leaveSeat(id) + entity, exist := slf.entities[id] + delete(slf.entities, id) + if !exist { + return + } + slf.manager.OnRoomRemoveEntityEvent(slf, entity) +} + +// RemoveAllEntities 移除所有实体 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) RemoveAllEntities() { + slf.entitiesRWMutex.Lock() + defer slf.entitiesRWMutex.Unlock() + for id := range slf.entities { + slf.removeEntity(id) + delete(slf.entities, id) + } +} + +// Destroy 销毁房间 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) Destroy() { + slf.manager.roomsRWMutex.Lock() + defer slf.manager.roomsRWMutex.Unlock() + + delete(slf.manager.rooms, slf.room.GetId()) + slf.manager.OnRoomDestroyEvent(slf) + + slf.entitiesRWMutex.Lock() + defer slf.entitiesRWMutex.Unlock() + + for eid := range slf.entities { + slf.removeEntity(eid) + delete(slf.entities, eid) + } + + slf.entities = make(map[EntityID]Entity) + slf.seat = slf.seat[:] + slf.vacancy = slf.vacancy[:] +} + +// GetRoomManager 获取房间管理器 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) GetRoomManager() *RoomManager[EntityID, RoomID, Entity, Room] { + return slf.manager +} + +// GetRoomID 获取房间ID +func (slf *RoomController[EntityID, RoomID, Entity, Room]) GetRoomID() RoomID { + return slf.room.GetId() +} + +// Broadcast 广播消息 +func (slf *RoomController[EntityID, RoomID, Entity, Room]) Broadcast(handler func(Entity), conditions ...func(Entity) bool) { + slf.entitiesRWMutex.RLock() + entities := hash.Copy(slf.entities) + slf.entitiesRWMutex.RUnlock() + for _, entity := range entities { + for _, condition := range conditions { + if !condition(entity) { + continue + } + } + handler(entity) + } +} diff --git a/game/space/room_errors.go b/game/space/room_errors.go new file mode 100644 index 0000000..2d275bd --- /dev/null +++ b/game/space/room_errors.go @@ -0,0 +1,16 @@ +package space + +import "errors" + +var ( + // ErrRoomFull 房间已满 + ErrRoomFull = errors.New("room is full") + // ErrSeatNotEmpty 座位上已经有实体 + ErrSeatNotEmpty = errors.New("seat is not empty") + // ErrNotInRoom 实体不在房间中 + ErrNotInRoom = errors.New("not in room") + // ErrRoomPasswordNotMatch 房间密码不匹配 + ErrRoomPasswordNotMatch = errors.New("room password not match") + // ErrPermissionDenied 权限不足 + ErrPermissionDenied = errors.New("permission denied") +) diff --git a/game/space/room_manager.go b/game/space/room_manager.go new file mode 100644 index 0000000..a142e17 --- /dev/null +++ b/game/space/room_manager.go @@ -0,0 +1,105 @@ +package space + +import ( + "github.com/kercylan98/minotaur/utils/generic" + "github.com/kercylan98/minotaur/utils/hash" + "sync" +) + +// NewRoomManager 创建房间管理器 +func NewRoomManager[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]]() *RoomManager[EntityID, RoomID, Entity, Room] { + return &RoomManager[EntityID, RoomID, Entity, Room]{ + roomManagerEvents: new(roomManagerEvents[EntityID, RoomID, Entity, Room]), + rooms: make(map[RoomID]*RoomController[EntityID, RoomID, Entity, Room]), + } +} + +// RoomManager 房间管理器 +type RoomManager[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] struct { + *roomManagerEvents[EntityID, RoomID, Entity, Room] + roomsRWMutex sync.RWMutex + rooms map[RoomID]*RoomController[EntityID, RoomID, Entity, Room] +} + +// AssumeControl 将房间控制权交由 RoomManager 接管 +func (slf *RoomManager[EntityID, RoomID, Entity, Room]) AssumeControl(room Room, options ...*RoomControllerOptions) *RoomController[EntityID, RoomID, Entity, Room] { + controller := newRoomController(slf, room, mergeRoomControllerOptions(options...)) + slf.OnRoomAssumeControlEvent(controller) + return controller +} + +// DestroyRoom 销毁房间 +func (slf *RoomManager[EntityID, RoomID, Entity, Room]) DestroyRoom(id RoomID) { + slf.roomsRWMutex.Lock() + room, exist := slf.rooms[id] + slf.roomsRWMutex.Unlock() + if !exist { + return + } + room.Destroy() +} + +// GetRoom 获取房间 +func (slf *RoomManager[EntityID, RoomID, Entity, Room]) GetRoom(id RoomID) *RoomController[EntityID, RoomID, Entity, Room] { + slf.roomsRWMutex.RLock() + defer slf.roomsRWMutex.RUnlock() + return slf.rooms[id] +} + +// GetRooms 获取所有房间 +func (slf *RoomManager[EntityID, RoomID, Entity, Room]) GetRooms() map[RoomID]*RoomController[EntityID, RoomID, Entity, Room] { + slf.roomsRWMutex.RLock() + defer slf.roomsRWMutex.RUnlock() + return hash.Copy(slf.rooms) +} + +// GetRoomCount 获取房间数量 +func (slf *RoomManager[EntityID, RoomID, Entity, Room]) GetRoomCount() int { + slf.roomsRWMutex.RLock() + defer slf.roomsRWMutex.RUnlock() + return len(slf.rooms) +} + +// GetRoomIDs 获取所有房间ID +func (slf *RoomManager[EntityID, RoomID, Entity, Room]) GetRoomIDs() []RoomID { + slf.roomsRWMutex.RLock() + defer slf.roomsRWMutex.RUnlock() + return hash.KeyToSlice(slf.rooms) +} + +// HasEntity 判断特定对象是否在任一房间中 +func (slf *RoomManager[EntityID, RoomID, Entity, Room]) HasEntity(entityId EntityID) bool { + slf.roomsRWMutex.RLock() + rooms := hash.Copy(slf.rooms) + slf.roomsRWMutex.RUnlock() + for _, room := range rooms { + if room.HasEntity(entityId) { + return true + } + } + return false +} + +// GetEntityRooms 获取特定对象所在的房间 +func (slf *RoomManager[EntityID, RoomID, Entity, Room]) GetEntityRooms(entityId EntityID) map[RoomID]*RoomController[EntityID, RoomID, Entity, Room] { + slf.roomsRWMutex.RLock() + rooms := hash.Copy(slf.rooms) + slf.roomsRWMutex.RUnlock() + var result = make(map[RoomID]*RoomController[EntityID, RoomID, Entity, Room]) + for id, room := range rooms { + if room.HasEntity(entityId) { + result[id] = room + } + } + return result +} + +// Broadcast 向所有房间对象广播消息 +func (slf *RoomManager[EntityID, RoomID, Entity, Room]) Broadcast(handler func(Entity), conditions ...func(Entity) bool) { + slf.roomsRWMutex.RLock() + rooms := hash.Copy(slf.rooms) + slf.roomsRWMutex.RUnlock() + for _, room := range rooms { + room.Broadcast(handler, conditions...) + } +} diff --git a/game/space/room_manager_events.go b/game/space/room_manager_events.go new file mode 100644 index 0000000..942594b --- /dev/null +++ b/game/space/room_manager_events.go @@ -0,0 +1,79 @@ +package space + +import "github.com/kercylan98/minotaur/utils/generic" + +type ( + RoomAssumeControlEventHandle[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] func(controller *RoomController[EntityID, RoomID, Entity, Room]) + RoomDestroyEventHandle[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] func(controller *RoomController[EntityID, RoomID, Entity, Room]) + RoomAddEntityEventHandle[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] func(controller *RoomController[EntityID, RoomID, Entity, Room], entity Entity) + RoomRemoveEntityEventHandle[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] func(controller *RoomController[EntityID, RoomID, Entity, Room], entity Entity) + RoomChangePasswordEventHandle[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] func(controller *RoomController[EntityID, RoomID, Entity, Room], oldPassword, password *string) +) + +type roomManagerEvents[EntityID comparable, RoomID comparable, Entity generic.IdR[EntityID], Room generic.IdR[RoomID]] struct { + roomAssumeControlEventHandles []RoomAssumeControlEventHandle[EntityID, RoomID, Entity, Room] + roomDestroyEventHandles []RoomDestroyEventHandle[EntityID, RoomID, Entity, Room] + roomAddEntityEventHandles []RoomAddEntityEventHandle[EntityID, RoomID, Entity, Room] + roomRemoveEntityEventHandles []RoomRemoveEntityEventHandle[EntityID, RoomID, Entity, Room] + roomChangePasswordEventHandles []RoomChangePasswordEventHandle[EntityID, RoomID, Entity, Room] +} + +// RegRoomAssumeControlEvent 注册房间接管事件 +func (slf *roomManagerEvents[EntityID, RoomID, Entity, Room]) RegRoomAssumeControlEvent(handle RoomAssumeControlEventHandle[EntityID, RoomID, Entity, Room]) { + slf.roomAssumeControlEventHandles = append(slf.roomAssumeControlEventHandles, handle) +} + +// OnRoomAssumeControlEvent 房间接管事件 +func (slf *roomManagerEvents[EntityID, RoomID, Entity, Room]) OnRoomAssumeControlEvent(controller *RoomController[EntityID, RoomID, Entity, Room]) { + for _, handle := range slf.roomAssumeControlEventHandles { + handle(controller) + } +} + +// RegRoomDestroyEvent 注册房间销毁事件 +func (slf *roomManagerEvents[EntityID, RoomID, Entity, Room]) RegRoomDestroyEvent(handle RoomDestroyEventHandle[EntityID, RoomID, Entity, Room]) { + slf.roomDestroyEventHandles = append(slf.roomDestroyEventHandles, handle) +} + +// OnRoomDestroyEvent 房间销毁事件 +func (slf *roomManagerEvents[EntityID, RoomID, Entity, Room]) OnRoomDestroyEvent(controller *RoomController[EntityID, RoomID, Entity, Room]) { + for _, handle := range slf.roomDestroyEventHandles { + handle(controller) + } +} + +// RegRoomAddEntityEvent 注册房间添加对象事件 +func (slf *roomManagerEvents[EntityID, RoomID, Entity, Room]) RegRoomAddEntityEvent(handle RoomAddEntityEventHandle[EntityID, RoomID, Entity, Room]) { + slf.roomAddEntityEventHandles = append(slf.roomAddEntityEventHandles, handle) +} + +// OnRoomAddEntityEvent 房间添加对象事件 +func (slf *roomManagerEvents[EntityID, RoomID, Entity, Room]) OnRoomAddEntityEvent(controller *RoomController[EntityID, RoomID, Entity, Room], entity Entity) { + for _, handle := range slf.roomAddEntityEventHandles { + handle(controller, entity) + } +} + +// RegRoomRemoveEntityEvent 注册房间移除对象事件 +func (slf *roomManagerEvents[EntityID, RoomID, Entity, Room]) RegRoomRemoveEntityEvent(handle RoomRemoveEntityEventHandle[EntityID, RoomID, Entity, Room]) { + slf.roomRemoveEntityEventHandles = append(slf.roomRemoveEntityEventHandles, handle) +} + +// OnRoomRemoveEntityEvent 房间移除对象事件 +func (slf *roomManagerEvents[EntityID, RoomID, Entity, Room]) OnRoomRemoveEntityEvent(controller *RoomController[EntityID, RoomID, Entity, Room], entity Entity) { + for _, handle := range slf.roomRemoveEntityEventHandles { + handle(controller, entity) + } +} + +// RegRoomChangePasswordEvent 注册房间修改密码事件 +func (slf *roomManagerEvents[EntityID, RoomID, Entity, Room]) RegRoomChangePasswordEvent(handle RoomChangePasswordEventHandle[EntityID, RoomID, Entity, Room]) { + slf.roomChangePasswordEventHandles = append(slf.roomChangePasswordEventHandles, handle) +} + +// OnRoomChangePasswordEvent 房间修改密码事件 +func (slf *roomManagerEvents[EntityID, RoomID, Entity, Room]) OnRoomChangePasswordEvent(controller *RoomController[EntityID, RoomID, Entity, Room], oldPassword, password *string) { + for _, handle := range slf.roomChangePasswordEventHandles { + handle(controller, oldPassword, password) + } +} diff --git a/game/space/room_options.go b/game/space/room_options.go new file mode 100644 index 0000000..f563c5d --- /dev/null +++ b/game/space/room_options.go @@ -0,0 +1,38 @@ +package space + +// NewRoomControllerOptions 创建房间控制器选项 +func NewRoomControllerOptions() *RoomControllerOptions { + return &RoomControllerOptions{} +} + +// mergeRoomControllerOptions 合并房间控制器选项 +func mergeRoomControllerOptions(options ...*RoomControllerOptions) *RoomControllerOptions { + result := NewRoomControllerOptions() + for _, option := range options { + if option.maxEntityCount != nil { + result.maxEntityCount = option.maxEntityCount + } + } + return result +} + +type RoomControllerOptions struct { + maxEntityCount *int // 房间最大实体数量 + password *string // 房间密码 +} + +// WithMaxEntityCount 设置房间最大实体数量 +func (slf *RoomControllerOptions) WithMaxEntityCount(maxEntityCount int) *RoomControllerOptions { + if maxEntityCount > 0 { + slf.maxEntityCount = &maxEntityCount + } + return slf +} + +// WithPassword 设置房间密码 +func (slf *RoomControllerOptions) WithPassword(password string) *RoomControllerOptions { + if password != "" { + slf.password = &password + } + return slf +} diff --git a/utils/generic/id.go b/utils/generic/id.go new file mode 100644 index 0000000..6243418 --- /dev/null +++ b/utils/generic/id.go @@ -0,0 +1,14 @@ +package generic + +type IdR[ID comparable] interface { + GetId() ID +} + +type IdW[ID comparable] interface { + SetId(id ID) +} + +type IdRW[ID comparable] interface { + IdR[ID] + IdW[ID] +} diff --git a/utils/super/permission.go b/utils/super/permission.go new file mode 100644 index 0000000..360012a --- /dev/null +++ b/utils/super/permission.go @@ -0,0 +1,78 @@ +package super + +import ( + "github.com/kercylan98/minotaur/utils/generic" + "sync" +) + +// NewPermission 创建权限 +func NewPermission[Code generic.Integer, EntityID comparable]() *Permission[Code, EntityID] { + return &Permission[Code, EntityID]{ + permissions: make(map[EntityID]Code), + } +} + +type Permission[Code generic.Integer, EntityID comparable] struct { + permissions map[EntityID]Code + l sync.RWMutex +} + +// HasPermission 是否有权限 +func (slf *Permission[Code, EntityID]) HasPermission(entityId EntityID, permission Code) bool { + slf.l.RLock() + c, exist := slf.permissions[entityId] + slf.l.RUnlock() + if !exist { + return false + } + + return c&permission != 0 +} + +// AddPermission 添加权限 +func (slf *Permission[Code, EntityID]) AddPermission(entityId EntityID, permission ...Code) { + + slf.l.Lock() + defer slf.l.Unlock() + + userPermission, exist := slf.permissions[entityId] + if !exist { + userPermission = 0 + slf.permissions[entityId] = userPermission + } + + for _, p := range permission { + userPermission |= p + } + + slf.permissions[entityId] = userPermission +} + +// RemovePermission 移除权限 +func (slf *Permission[Code, EntityID]) RemovePermission(entityId EntityID, permission ...Code) { + slf.l.Lock() + defer slf.l.Unlock() + + userPermission, exist := slf.permissions[entityId] + if !exist { + return + } + + for _, p := range permission { + userPermission &= ^p + } + + slf.permissions[entityId] = userPermission +} + +// SetPermission 设置权限 +func (slf *Permission[Code, EntityID]) SetPermission(entityId EntityID, permission ...Code) { + slf.l.Lock() + defer slf.l.Unlock() + + var userPermission Code + for _, p := range permission { + userPermission |= p + } + slf.permissions[entityId] = userPermission +} diff --git a/utils/super/permission_test.go b/utils/super/permission_test.go new file mode 100644 index 0000000..0cd1e45 --- /dev/null +++ b/utils/super/permission_test.go @@ -0,0 +1,28 @@ +package super_test + +import ( + "github.com/kercylan98/minotaur/utils/super" + "testing" +) + +func TestNewPermission(t *testing.T) { + const ( + Read = 1 << iota + Write + Execute + ) + + p := super.NewPermission[int, int]() + p.AddPermission(1, Read, Write) + t.Log(p.HasPermission(1, Read)) + t.Log(p.HasPermission(1, Write)) + p.SetPermission(2, Read|Write) + t.Log(p.HasPermission(2, Read)) + t.Log(p.HasPermission(2, Execute)) + p.SetPermission(2, Execute) + t.Log(p.HasPermission(2, Execute)) + t.Log(p.HasPermission(2, Read)) + t.Log(p.HasPermission(2, Write)) + p.RemovePermission(2, Execute) + t.Log(p.HasPermission(2, Execute)) +}