// Package cell 提供单房间 Room Cell 执行单元,负责把同一房间的状态变更串行化。 package cell import ( "context" "hyapp/services/room-service/internal/room/rank" "hyapp/services/room-service/internal/room/state" ) // taskResult 是 mailbox 内同步任务的返回值容器。 type taskResult struct { // value 承载任务返回的业务结果,调用方按约定做类型断言。 value any // err 承载任务执行失败原因,失败时 Room Cell 不应该替换当前状态。 err error } // task 表示一个需要在 Room Cell 内串行执行的闭包。 type task struct { // run 接收当前房间状态和本地榜单,所有房间内写操作都必须在该闭包里完成。 run func(*state.RoomState, *rank.LocalRank) (any, error) // resp 是同步等待结果的单次响应通道。 resp chan taskResult } // RoomCell 用 mailbox 串行化单房间内的全部状态变更。 type RoomCell struct { // roomID 是该 Cell 唯一负责的房间标识。 roomID string // state 是房间核心业务状态,只能由 loop goroutine 访问和替换。 state *state.RoomState // rank 是房间内本地礼物榜,和 state 同线程更新,避免榜单与房间热度错位。 rank *rank.LocalRank // mailbox 是单房间命令队列,所有任务按发送顺序执行。 mailbox chan task } // New 创建一个新的单房间执行单元。 func New(initialState *state.RoomState, initialRank *rank.LocalRank) *RoomCell { // 初始化时要求调用方已经完成恢复或创建,RoomCell 不直接访问 repository。 c := &RoomCell{ roomID: initialState.RoomID, state: initialState, rank: initialRank, mailbox: make(chan task), } // 每个房间一个 goroutine 串行处理任务,避免房间内状态用大锁到处包裹。 go c.loop() return c } // Do 把一个同步任务提交到 mailbox,并等待串行执行结果。 func (c *RoomCell) Do(ctx context.Context, run func(*state.RoomState, *rank.LocalRank) (any, error)) (any, error) { // 响应通道带 1 个缓冲,避免调用方 ctx 超时后 loop goroutine 卡在回写结果上。 resp := make(chan taskResult, 1) req := task{ run: run, resp: resp, } select { case <-ctx.Done(): // 命令还没进入 mailbox 前超时,不会对房间状态产生任何影响。 return nil, ctx.Err() case c.mailbox <- req: } select { case <-ctx.Done(): // 命令可能已经在 Cell 内执行完成;调用方超时只影响本次等待,不回滚已提交状态。 return nil, ctx.Err() case result := <-resp: return result.value, result.err } } // Snapshot 返回当前房间态和榜单的独立副本。 func (c *RoomCell) Snapshot(ctx context.Context) (*state.RoomState, *rank.LocalRank, error) { // 快照也走 mailbox,保证读取到的是某个命令边界上的一致状态。 value, err := c.Do(ctx, func(current *state.RoomState, currentRank *rank.LocalRank) (any, error) { return struct { state *state.RoomState rank *rank.LocalRank }{ state: current.Clone(), rank: currentRank.Clone(), }, nil }) if err != nil { return nil, nil, err } // Do 返回的是内部匿名结构,类型断言只在本函数私有闭包和读取点之间使用。 result := value.(struct { state *state.RoomState rank *rank.LocalRank }) return result.state, result.rank, nil } func (c *RoomCell) loop() { for task := range c.mailbox { // loop 是 state/rank 的唯一写入者,任务内部不需要再加状态锁。 value, err := task.run(c.state, c.rank) task.resp <- taskResult{ value: value, err: err, } } }