mirror of
https://github.com/duanhf2012/origin.git
synced 2026-02-03 22:45:13 +08:00
新增FrameTimer模块,支持暂停、恢复、加速
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
198
sysmodule/frametimer/FrameGroup.go
Normal file
198
sysmodule/frametimer/FrameGroup.go
Normal file
@@ -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()
|
||||
}
|
||||
199
sysmodule/frametimer/FrameTimerModule.go
Normal file
199
sysmodule/frametimer/FrameTimerModule.go
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user