115 lines
3.6 KiB
Go
115 lines
3.6 KiB
Go
// 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,
|
||
}
|
||
}
|
||
}
|