diff --git a/event/eventtype.go b/event/eventtype.go index 5158668..8d9ee35 100644 --- a/event/eventtype.go +++ b/event/eventtype.go @@ -18,6 +18,7 @@ const ( Sys_Event_Retire EventType = -11 Sys_Event_EtcdDiscovery EventType = -12 Sys_Event_Gin_Event EventType = -13 + Sys_Event_FrameTick EventType = -14 Sys_Event_User_Define EventType = 1 ) diff --git a/sysmodule/frametimer/FrameGroup.go b/sysmodule/frametimer/FrameGroup.go new file mode 100644 index 0000000..b257f2e --- /dev/null +++ b/sysmodule/frametimer/FrameGroup.go @@ -0,0 +1,198 @@ +package frametimer + +import ( + "container/heap" + "context" + "errors" + "github.com/duanhf2012/origin/v2/event" + "github.com/duanhf2012/origin/v2/log" + "time" +) + +type TimerCB func(context.Context, FrameTimerID) + +type _timerHeap struct { + timers []*timerData +} + +func (h *_timerHeap) Len() int { + return len(h.timers) +} + +func (h *_timerHeap) Less(i, j int) bool { + return h.timers[i].frameNum < h.timers[j].frameNum +} + +func (h *_timerHeap) Swap(i, j int) { + h.timers[i], h.timers[j] = h.timers[j], h.timers[i] + h.timers[i].idx = i + h.timers[j].idx = j +} + +func (h *_timerHeap) Push(x interface{}) { + td := x.(*timerData) + h.timers = append(h.timers, td) + td.idx = len(h.timers) - 1 +} + +func (h *_timerHeap) Pop() (ret interface{}) { + l := len(h.timers) + h.timers, ret = h.timers[:l-1], h.timers[l-1] + return +} + +type FrameGroup struct { + groupID FrameGroupID + timerHeap _timerHeap + ft *FrameTimer + + preTickGlobalFrameNum FrameNumber // 上次tick全局帧 + preGlobalFrameNum FrameNumber // 上次设置的全局帧,用于更新FrameTimer.mapFrameGroup关系 + frameNum FrameNumber // 当前帧 + + pause bool // 暂停状态 + multiple uint8 // 位数,默认1倍,只允许1-5倍数 +} + +func (fg *FrameGroup) init() { + fg.timerHeap.timers = make([]*timerData, 0, 512) + fg.groupID = fg.ft.genGroupID() + fg.multiple = 1 + heap.Init(&fg.timerHeap) +} + +func (fg *FrameGroup) convertGlobalFrameNum(frameNum FrameNumber) FrameNumber { + return fg.ft.getGlobalFrameNumber() + (frameNum-fg.frameNum)/FrameNumber(fg.multiple) +} + +func (fg *FrameGroup) refreshMinFrame() { + if fg.timerHeap.Len() == 0 || fg.pause { + return + } + + globalFrameNum := fg.convertGlobalFrameNum(fg.timerHeap.timers[0].frameNum) + fg.ft.refreshGroupMinFrame(fg.groupID, fg.preGlobalFrameNum, globalFrameNum) + fg.preGlobalFrameNum = globalFrameNum +} + +func (fg *FrameGroup) tick(globalFrame FrameNumber) { + fg.frameNum = fg.frameNum + (globalFrame-fg.preTickGlobalFrameNum)*FrameNumber(fg.multiple) + fg.preTickGlobalFrameNum = globalFrame + + fg.onceTick() + + fg.refreshMinFrame() +} + +func (fg *FrameGroup) onceTick() { + for fg.timerHeap.Len() > 0 { + if fg.timerHeap.timers[0].frameNum > fg.frameNum { + break + } + + t := heap.Pop(&fg.timerHeap).(*timerData) + + ev := event.NewEvent() + ev.Type = event.Sys_Event_FrameTick + ev.Data = t + fg.ft.NotifyEvent(ev) + fg.ft.removeTimerData(t.timerID) + + if t.tickerFrameNum != 0 { + fg.addTicker(t.timerID, t.tickerFrameNum, t.ctx, t.cb) + } + } +} + +func (fg *FrameGroup) addTimer(timerID FrameTimerID, frame FrameNumber, ctx context.Context, cb TimerCB) { + nextFrame := fg.frameNum + frame + + td := fg.ft.addTimerData(timerID, nextFrame, 0, ctx, cb) + heap.Push(&fg.timerHeap, td) +} + +func (fg *FrameGroup) addTicker(timerID FrameTimerID, frame FrameNumber, ctx context.Context, cb TimerCB) { + nextFrame := fg.frameNum + frame + + td := fg.ft.addTimerData(timerID, nextFrame, frame, ctx, cb) + heap.Push(&fg.timerHeap, td) +} + +// SetMultiple 设置倍数,允许倍数范围1-5 +func (fg *FrameGroup) SetMultiple(multiple uint8) error { + if fg.multiple == multiple { + return nil + } + + if multiple < 0 || multiple > maxMultiple { + return errors.New("invalid multiplier") + } + + fg.multiple = multiple + + fg.refreshMinFrame() + return nil +} + +// FrameAfterFunc 创建After定时器 +func (fg *FrameGroup) FrameAfterFunc(timerID *FrameTimerID, d time.Duration, ctx context.Context, cb TimerCB) { + fg.ft.locker.Lock() + defer fg.ft.locker.Unlock() + + frame := FrameNumber(d / fg.ft.oneFrameTime) + newTimerID := fg.ft.genTimerID() + + fg.addTimer(newTimerID, frame, ctx, cb) + *timerID = newTimerID + fg.refreshMinFrame() +} + +// FrameNewTicker 创建Ticker定时器 +func (fg *FrameGroup) FrameNewTicker(timerID *FrameTimerID, d time.Duration, ctx context.Context, cb TimerCB) { + fg.ft.locker.Lock() + defer fg.ft.locker.Unlock() + + frame := FrameNumber(d / fg.ft.oneFrameTime) + newTimerID := fg.ft.genTimerID() + + fg.addTicker(newTimerID, frame, ctx, cb) + *timerID = newTimerID + fg.refreshMinFrame() +} + +// Pause 暂停定时器组 +func (fg *FrameGroup) Pause() { + fg.ft.locker.Lock() + defer fg.ft.locker.Unlock() + + fg.pause = true + fg.ft.removeGroup(fg.groupID, fg.preGlobalFrameNum) + fg.preGlobalFrameNum = 0 +} + +// Resume 唤醒定时器组 +func (fg *FrameGroup) Resume() { + fg.ft.locker.Lock() + defer fg.ft.locker.Unlock() + + fg.pause = false + fg.refreshMinFrame() + fg.preTickGlobalFrameNum = fg.ft.globalFrameNum +} + +// CancelTimer 关闭定时器 +func (fg *FrameGroup) CancelTimer(timerID FrameTimerID) { + fg.ft.locker.Lock() + defer fg.ft.locker.Unlock() + + td := fg.ft.getTimerData(timerID) + if td == nil { + log.Error("cannot find timer", log.Uint64("timerID", uint64(timerID))) + return + } + + heap.Remove(&fg.timerHeap, td.idx) + fg.ft.removeGroup(fg.groupID, fg.preGlobalFrameNum) + fg.preGlobalFrameNum = 0 + fg.refreshMinFrame() +} diff --git a/sysmodule/frametimer/FrameTimerModule.go b/sysmodule/frametimer/FrameTimerModule.go new file mode 100644 index 0000000..0b93dca --- /dev/null +++ b/sysmodule/frametimer/FrameTimerModule.go @@ -0,0 +1,199 @@ +package frametimer + +import ( + "context" + "github.com/duanhf2012/origin/v2/event" + "github.com/duanhf2012/origin/v2/log" + "github.com/duanhf2012/origin/v2/service" + "sync" + "time" +) + +const defaultFps = 50 +const defaultSleepInterval = time.Millisecond * 3 +const maxFps = 1000 +const maxMultiple = 5 + +type FrameGroupID uint64 +type FrameTimerID uint64 +type FrameNumber uint64 + +type timerData struct { + frameNum FrameNumber + timerID FrameTimerID + idx int + cb TimerCB + tickerFrameNum FrameNumber + ctx context.Context +} + +type FrameTimer struct { + service.Module + fps uint32 + oneFrameTime time.Duration + ticker *time.Ticker + + mapFrameGroup map[FrameNumber]map[FrameGroupID]struct{} + mapGroup map[FrameGroupID]*FrameGroup + globalFrameNum FrameNumber // 当前帧 + + maxTimerID FrameTimerID + maxGroupID FrameGroupID + + mapTimer map[FrameTimerID]*timerData + timerDataPool *sync.Pool + + locker sync.Mutex + sleepInterval time.Duration +} + +func (ft *FrameTimer) getTimerData(timerID FrameTimerID) *timerData { + return ft.mapTimer[timerID] +} + +func (ft *FrameTimer) addTimerData(timerID FrameTimerID, frameNum FrameNumber, tickerFrameNum FrameNumber, ctx context.Context, cb TimerCB) *timerData { + td := ft.timerDataPool.Get().(*timerData) + td.timerID = timerID + td.frameNum = frameNum + td.cb = cb + td.idx = -1 + td.tickerFrameNum = tickerFrameNum + + ft.mapTimer[timerID] = td + return td +} + +func (ft *FrameTimer) removeTimerData(timerID FrameTimerID) { + td := ft.mapTimer[timerID] + if td == nil { + return + } + + ft.timerDataPool.Put(td) +} + +func (ft *FrameTimer) genGroupID() FrameGroupID { + ft.maxGroupID++ + return ft.maxGroupID +} + +func (ft *FrameTimer) genTimerID() FrameTimerID { + ft.maxTimerID++ + return ft.maxTimerID +} + +func (ft *FrameTimer) getGlobalFrameNumber() FrameNumber { + return ft.globalFrameNum +} + +func (ft *FrameTimer) frameTick() { + preFrameNum := ft.globalFrameNum + ft.globalFrameNum++ + + ft.locker.Lock() + defer ft.locker.Unlock() + for i := preFrameNum; i <= ft.globalFrameNum; i++ { + mapGroup := ft.mapFrameGroup[i] + delete(ft.mapFrameGroup, i) + for groupID := range mapGroup { + group := ft.mapGroup[groupID] + if group == nil { + continue + } + + group.tick(ft.globalFrameNum) + } + } +} + +func (ft *FrameTimer) removeGroup(groupID FrameGroupID, frameNum FrameNumber) { + delete(ft.mapFrameGroup[frameNum], groupID) +} + +func (ft *FrameTimer) refreshGroupMinFrame(groupID FrameGroupID, preFrameNum FrameNumber, newFrameNum FrameNumber) { + ft.removeGroup(groupID, preFrameNum) + + mapGroup := ft.mapFrameGroup[newFrameNum] + if mapGroup == nil { + mapGroup = make(map[FrameGroupID]struct{}, 6) + ft.mapFrameGroup[newFrameNum] = mapGroup + } + + mapGroup[groupID] = struct{}{} +} + +func (ft *FrameTimer) OnInit() error { + ft.mapFrameGroup = make(map[FrameNumber]map[FrameGroupID]struct{}, 1024) + ft.mapGroup = make(map[FrameGroupID]*FrameGroup, 1024) + ft.mapTimer = make(map[FrameTimerID]*timerData, 2048) + ft.timerDataPool = &sync.Pool{ + New: func() any { + return &timerData{} + }, + } + + if ft.fps == 0 { + ft.fps = defaultFps + } + + ft.GetEventProcessor().RegEventReceiverFunc(event.Sys_Event_FrameTick, ft.GetEventHandler(), func(e event.IEvent) { + ev := e.(*event.Event) + td, ok := ev.Data.(*timerData) + if !ok { + log.Error("convert *timerData error") + return + } + td.cb(td.ctx, td.timerID) + event.DeleteEvent(e) + }) + + ft.oneFrameTime = time.Second / time.Duration(ft.fps) + ft.ticker = time.NewTicker(ft.oneFrameTime) + + if ft.sleepInterval == 0 { + ft.sleepInterval = defaultSleepInterval + } + + go func() { + preTime := time.Now() + var preFrame FrameNumber + + for { + time.Sleep(ft.sleepInterval) + frameMax := FrameNumber(time.Now().Sub(preTime) / ft.oneFrameTime) + for i := preFrame + 1; i <= frameMax; i++ { + ft.frameTick() + } + + preFrame = frameMax + } + }() + + return nil +} + +// SetFps 设置帧率,越大误差越低。如果有倍数加速需求,可以适当加大fps,以减少误差。默认50fps +func (ft *FrameTimer) SetFps(fps uint32) { + if fps > maxFps { + fps = maxFps + } + + ft.fps = fps +} + +// SetAccuracyInterval 设置时间间隔精度,在循环中sleep该时间进行判断。实际上因为sleep有误差,所以暂时不使用fps得出。默认为3ms +func (ft *FrameTimer) SetAccuracyInterval(interval time.Duration) { + ft.sleepInterval = interval +} + +// NewGroup 创建定时器组 +func (ft *FrameTimer) NewGroup() *FrameGroup { + var group FrameGroup + group.ft = ft + group.init() + + ft.locker.Lock() + defer ft.locker.Unlock() + ft.mapGroup[group.groupID] = &group + return &group +}