2026-04-25 01:18:30 +08:00

115 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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,
}
}
}