From 4a415384602263ed409a3df2c3a46557125fa636 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Thu, 23 Nov 2023 19:42:50 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20activity=20=E5=8C=85=E9=87=8D?= =?UTF-8?q?=E6=9E=84=EF=BC=8C=E6=95=B4=E4=BD=93=E4=BC=98=E5=8C=96=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E4=BD=93=E9=AA=8C=EF=BC=8C=E6=B4=BB=E5=8A=A8=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=8F=90=E5=89=8D=E5=B1=95=E7=A4=BA=E3=80=81=E5=8F=8A?= =?UTF-8?q?=E5=BB=B6=E9=95=BF=E5=B1=95=E7=A4=BA=E3=80=81=E6=8C=81=E4=B9=85?= =?UTF-8?q?=E5=8C=96=E3=80=81=E6=95=B0=E6=8D=AE=E4=BF=9D=E7=95=99=E5=91=A8?= =?UTF-8?q?=E6=9C=9F=E3=80=81=E5=BE=AA=E7=8E=AF=E6=B4=BB=E5=8A=A8=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/activity/activities.go | 77 ----- game/activity/activity.go | 430 ++++++++------------------ game/activity/activity_test.go | 84 +++-- game/activity/controller.go | 112 +++++++ game/activity/controller_interface.go | 42 +++ game/activity/controller_internal.go | 145 +++++++++ game/activity/data.go | 25 +- game/activity/events.go | 251 ++++++++++++++- game/activity/interface.go | 6 - game/activity/options.go | 68 ++-- 10 files changed, 772 insertions(+), 468 deletions(-) delete mode 100644 game/activity/activities.go create mode 100644 game/activity/controller.go create mode 100644 game/activity/controller_interface.go create mode 100644 game/activity/controller_internal.go delete mode 100644 game/activity/interface.go diff --git a/game/activity/activities.go b/game/activity/activities.go deleted file mode 100644 index bc75070..0000000 --- a/game/activity/activities.go +++ /dev/null @@ -1,77 +0,0 @@ -package activity - -import ( - "github.com/kercylan98/minotaur/utils/hash" - "github.com/kercylan98/minotaur/utils/offset" - "github.com/kercylan98/minotaur/utils/timer" -) - -const ( - TickerMark = "ACTIVITY_SYSTEM_TICKER" -) - -const ( - tickerStart = "MINOTAUR_ACTIVITY_%d_START_c8JDh23!df" - tickerStop = "MINOTAUR_ACTIVITY_%d_STOP_3d!fj2@#d" - tickerShowStart = "MINOTAUR_ACTIVITY_%d_SHOW_START_3d#f@2@#d" - tickerShowStop = "MINOTAUR_ACTIVITY_%d_SHOW_STOP_68!fj11#d" - tickerNewDay = "MINOTAUR_ACTIVITY_%d_NEW_DAY_3d!fd8@_d" -) - -var ( - ticker *timer.Ticker - activityOffset *offset.Time - activities map[int64]activityInterface -) - -// NoneData 空数据活动 -type NoneData struct{} - -func init() { - SetTicker(nil) - activityOffset = offset.GetGlobal() - activities = map[int64]activityInterface{} -} - -// Register 注册活动 -func Register[PlayerID comparable, ActivityData, PlayerData any](activity *Activity[PlayerID, ActivityData, PlayerData]) { - activity.Register() -} - -// Get 获取活动 -func Get[PlayerID comparable, ActivityData, PlayerData any](activityId int64) *Activity[PlayerID, ActivityData, PlayerData] { - return activities[activityId].(*Activity[PlayerID, ActivityData, PlayerData]) -} - -// HasActivity 检查活动是否存在 -func HasActivity(activityId int64) bool { - return hash.Exist(activities, activityId) -} - -// SetOffsetTime 设置活动的时间偏移时间 -func SetOffsetTime(time *offset.Time) { - activityOffset = time - for _, activity := range activities { - activity.stopTicker() - activity.refreshTicker(false) - } -} - -// SetTicker 通过指定特定的定时器取代默认的定时器 -// - 当传入的定时器为 nil 时,将使用默认的定时器 -func SetTicker(t *timer.Ticker) { - if ticker != nil { - if ticker.Mark() == TickerMark { - ticker.Release() - } - for _, activity := range activities { - activity.stopTicker() - } - } - if ticker = t; ticker == nil { - ticker = timer.GetTicker(30, timer.WithMark(TickerMark)) - for _, activity := range activities { - activity.refreshTicker(false) - } - } -} diff --git a/game/activity/activity.go b/game/activity/activity.go index bb4ebee..2fe3e81 100644 --- a/game/activity/activity.go +++ b/game/activity/activity.go @@ -1,322 +1,158 @@ package activity import ( + "errors" "fmt" "github.com/kercylan98/minotaur/utils/generic" + "github.com/kercylan98/minotaur/utils/timer" "github.com/kercylan98/minotaur/utils/times" + "reflect" + "sync" + "sync/atomic" "time" ) -// NewActivity 创建一个新的活动 -// - id: 活动 ID -// - period: 活动周期 -func NewActivity[PlayerID comparable, ActivityData, PlayerData any](id int64, period times.Period, options ...Option[PlayerID, ActivityData, PlayerData]) *Activity[PlayerID, ActivityData, PlayerData] { - activity := &Activity[PlayerID, ActivityData, PlayerData]{ - id: id, - data: newData[PlayerID, ActivityData, PlayerData](), - period: period, +const ( + stateClosed = byte(iota) // 已关闭 + stateUpcoming // 即将开始 + stateStarted // 已开始 + stateEnded // 已结束 + stateExtendedShowEnded // 延长展示结束 +) + +var ( + stateLine = []byte{stateClosed, stateUpcoming, stateStarted, stateEnded, stateExtendedShowEnded} + + ticker *timer.Ticker + tickerOnce sync.Once + isDebug atomic.Bool +) + +func init() { + ticker = timer.GetTicker(10) +} + +// SetDebug 设置是否开启调试模式 +func SetDebug(d bool) { + isDebug.Store(d) +} + +// SetTicker 设置自定义定时器,该方法必须在使用活动系统前调用,且只能调用一次 +func SetTicker(size int, options ...timer.Option) { + tickerOnce.Do(func() { + if ticker != nil { + ticker.Release() + } + ticker = timer.GetTicker(size, options...) + }) +} + +// LoadOrRefreshActivity 加载或刷新活动 +// - 通常在活动配置刷新时候将活动通过该方法注册或刷新 +func LoadOrRefreshActivity[Type, ID generic.Basic](activityType Type, activityId ID, options ...Option[Type, ID]) error { + register := getControllerRegister(activityType) + if register == nil { + return errors.New("activity type not registered") } + act, initFinishCallback := register(activityType, activityId) + activity := act.(*Activity[Type, ID]) for _, option := range options { option(activity) } - return activity -} - -// Activity 活动 -type Activity[PlayerID comparable, ActivityData, PlayerData any] struct { - id int64 - data *Data[PlayerID, ActivityData, PlayerData] - period times.Period - beforeShow, afterShow time.Time - - playerDataLoadHandle func(activity *Activity[PlayerID, ActivityData, PlayerData], playerId PlayerID) PlayerData - - startEventHandles []StartEventHandle[PlayerID, ActivityData, PlayerData] - stopEventHandles []StopEventHandle[PlayerID, ActivityData, PlayerData] - startShowEventHandles []StartShowEventHandle[PlayerID, ActivityData, PlayerData] - stopShowEventHandles []StopShowEventHandle[PlayerID, ActivityData, PlayerData] - playerJoinEventHandles []PlayerJoinEventHandle[PlayerID, ActivityData, PlayerData] - playerLeaveEventHandles []PlayerLeaveEventHandle[PlayerID, ActivityData, PlayerData] - newDayEventHandles []NewDayEventHandle[PlayerID, ActivityData, PlayerData] - playerNewDayEventHandles []PlayerNewDayEventHandle[PlayerID, ActivityData, PlayerData] -} - -// GetStart 获取活动开始时间 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) GetStart() time.Time { - return slf.period.Start() -} - -// GetEnd 获取活动结束时间 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) GetEnd() time.Time { - return slf.period.End() -} - -// GetPeriod 获取活动周期 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) GetPeriod() times.Period { - return slf.period -} - -// StoreData 通过特定的存储函数存储活动数据 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) StoreData(handle func(data *Data[PlayerID, ActivityData, PlayerData])) { - handle(slf.data) -} - -// GetActivityData 获取活动数据 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) GetActivityData() ActivityData { - return slf.data.Data -} - -// GetPlayerData 获取玩家数据 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) GetPlayerData(playerId PlayerID) PlayerData { - return slf.data.PlayerData[playerId] -} - -// ChangeTime 修改活动时间 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) ChangeTime(period times.Period) { - slf.period = period - slf.refreshTicker(false) -} - -// ChangeBeforeShowTime 修改活动开始前的展示时间 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) ChangeBeforeShowTime(beforeShow time.Time) { - slf.beforeShow = beforeShow - slf.refreshTicker(false) -} - -// ChangeAfterShowTime 修改活动结束后的展示时间 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) ChangeAfterShowTime(afterShow time.Time) { - slf.afterShow = afterShow - slf.refreshTicker(false) -} - -// IsShowAndOpen 判断活动是否展示并开启 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) IsShowAndOpen() bool { - return slf.IsShow() && slf.IsOpen() -} - -// IsShow 判断活动是否展示 -// - 活动展示时,活动并非一定开启。常用于活动的预告和结束后的成果展示 -// - 当活动没有设置展示时间时,活动展示与活动开启一致 -// - 如果活动仅设置了活动开始前的展示时间,则活动展示将从开始前的展示时间持续到活动结束 -// - 如果活动仅设置了活动结束后的展示时间,则活动展示将从活动开始持续到结束后的展示时间 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) IsShow() bool { - if slf.beforeShow.IsZero() && slf.afterShow.IsZero() { - return slf.IsOpen() - } else if slf.beforeShow.IsZero() { - return slf.IsOpen() && slf.afterShow.After(activityOffset.Now()) - } else if slf.afterShow.IsZero() { - return slf.IsOpen() && slf.beforeShow.Before(activityOffset.Now()) + initFinishCallback(activity) + if !activity.tl.Check(true, stateLine...) { + return errors.New("activity state timeline is invalid") } - return times.NewPeriod(slf.beforeShow, slf.afterShow).IsOngoing(activityOffset.Now()) + + stateTrigger := map[byte]func(){ + stateUpcoming: func() { OnUpcomingEvent(activity) }, + stateStarted: func() { OnStartedEvent(activity) }, + stateEnded: func() { OnEndedEvent(activity); OnExtendedShowStartedEvent(activity) }, + stateExtendedShowEnded: func() { OnExtendedShowEndedEvent(activity) }, + } + for _, state := range stateLine { + if activity.tl.HasState(state) { + activity.tl.AddTriggerToState(state, stateTrigger[state]) + continue + } + for next := state; next <= stateLine[len(stateLine)-1]; next++ { + if activity.tl.HasState(next) { + activity.tl.AddTriggerToState(next, stateTrigger[state]) + break + } + } + } + + activity.refresh() + return nil } -// IsOpen 判断活动是否开启 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) IsOpen() bool { - current := activityOffset.Now() - return slf.period.IsOngoing(current) +// LoadGlobalData 加载所有活动全局数据 +// - 一般用于持久化活动数据 +func LoadGlobalData(handler func(activityType, activityId, data any)) { + for _, f := range controllerGlobalDataReader { + f(handler) + } } -// IsInvalid 判断活动是否无效 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) IsInvalid() bool { - current := activityOffset.Now() - if slf.beforeShow.IsZero() && !slf.afterShow.IsZero() { - return current.After(slf.afterShow) || current.Equal(slf.afterShow) +// LoadEntityData 加载所有活动实体数据 +// - 一般用于持久化活动数据 +func LoadEntityData(handler func(activityType, activityId, entityId, data any)) { + for _, f := range controllerEntityDataReader { + f(handler) + } +} + +// Activity 活动描述 +type Activity[Type, ID generic.Basic] struct { + id ID // 活动 ID + t Type // 活动类型 + state byte // 活动状态 + tl *times.StateLine[byte] // 活动时间线 + loop time.Duration // 活动多久循环一次 + lazy bool // 是否懒加载 + tickerKey string // 定时器 key + retention time.Duration // 活动数据保留时间 + retentionKey string // 保留定时器 key + mutex sync.Mutex // 互斥锁 + + getLastNewDayTime func() time.Time // 获取上次新的一天的时间 + setLastNewDayTime func(time.Time) // 设置上次新的一天的时间 +} + +func (slf *Activity[Type, ID]) refresh() { + slf.mutex.Lock() + defer slf.mutex.Unlock() + curr := time.Now() + if slf.state = slf.tl.GetStateByTime(curr); slf.state == stateUpcoming { + ticker.StopTimer(slf.retentionKey) + resetActivityData(slf.t, slf.id) + } + + for _, f := range slf.tl.GetTriggerByState(slf.state) { + if f != nil { + f() + } + } + + next := slf.tl.GetNextTimeByState(slf.state) + if !next.IsZero() && next.After(curr) { + ticker.After(slf.tickerKey, next.Sub(curr)+time.Millisecond*100, slf.refresh) } else { - end := slf.GetEnd() - return current.After(end) || current.Equal(end) - } -} - -// Register 将该活动进行注册 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) Register() { - if slf.IsInvalid() { - return - } - if !generic.IsNil(slf.data) { - activities[slf.id] = slf - } - slf.refreshTicker(true) -} - -// Join 设置玩家加入活动 -// - 通常玩家应该在上线的时候加入活动 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) Join(playerId PlayerID) { - if slf.playerDataLoadHandle != nil { - playerData := slf.playerDataLoadHandle(slf, playerId) - if !generic.IsNil(playerData) { - slf.data.PlayerData[playerId] = playerData + ticker.StopTimer(slf.tickerKey) + ticker.StopTimer(fmt.Sprintf("activity:new_day:%d:%v", reflect.ValueOf(slf.t).Kind(), slf.id)) + if slf.loop > 0 { + slf.tl.Move(slf.loop * 2) + ticker.After(slf.tickerKey, slf.loop+time.Millisecond*100, slf.refresh) + return } - } - current := activityOffset.Now() - slf.OnPlayerJoinEvent(playerId) - slf.playerNewDay(playerId, current) -} - -// Leave 设置玩家离开活动 -// - 当玩家离开活动时,玩家的活动数据将会被清除 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) Leave(playerId PlayerID) { - slf.OnPlayerLeaveEvent(playerId) - delete(slf.data.PlayerData, playerId) -} - -// RegStartEvent 注册活动开始事件 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) RegStartEvent(handle StartEventHandle[PlayerID, ActivityData, PlayerData]) { - slf.startEventHandles = append(slf.startEventHandles, handle) -} - -func (slf *Activity[PlayerID, ActivityData, PlayerData]) OnStartEvent() { - for _, handle := range slf.startEventHandles { - handle(slf) - } -} - -// RegStopEvent 注册活动结束事件 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) RegStopEvent(handle StopEventHandle[PlayerID, ActivityData, PlayerData]) { - slf.stopEventHandles = append(slf.stopEventHandles, handle) -} - -func (slf *Activity[PlayerID, ActivityData, PlayerData]) OnStopEvent() { - slf.stopTicker() - for _, handle := range slf.stopEventHandles { - handle(slf) - } -} - -// RegStartShowEvent 注册活动开始展示事件 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) RegStartShowEvent(handle StartShowEventHandle[PlayerID, ActivityData, PlayerData]) { - slf.startShowEventHandles = append(slf.startShowEventHandles, handle) -} - -func (slf *Activity[PlayerID, ActivityData, PlayerData]) OnStartShowEvent() { - for _, handle := range slf.startShowEventHandles { - handle(slf) - } -} - -// RegStopShowEvent 注册活动结束展示事件 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) RegStopShowEvent(handle StopShowEventHandle[PlayerID, ActivityData, PlayerData]) { - slf.stopShowEventHandles = append(slf.stopShowEventHandles, handle) -} - -func (slf *Activity[PlayerID, ActivityData, PlayerData]) OnStopShowEvent() { - for _, handle := range slf.stopShowEventHandles { - handle(slf) - } -} - -// RegPlayerJoinEvent 注册玩家加入活动事件 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) RegPlayerJoinEvent(handle PlayerJoinEventHandle[PlayerID, ActivityData, PlayerData]) { - slf.playerJoinEventHandles = append(slf.playerJoinEventHandles, handle) -} - -func (slf *Activity[PlayerID, ActivityData, PlayerData]) OnPlayerJoinEvent(playerId PlayerID) { - for _, handle := range slf.playerJoinEventHandles { - handle(slf, playerId) - } -} - -// RegPlayerLeaveEvent 注册玩家离开活动事件 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) RegPlayerLeaveEvent(handle PlayerLeaveEventHandle[PlayerID, ActivityData, PlayerData]) { - slf.playerLeaveEventHandles = append(slf.playerLeaveEventHandles, handle) -} - -func (slf *Activity[PlayerID, ActivityData, PlayerData]) OnPlayerLeaveEvent(playerId PlayerID) { - for _, handle := range slf.playerLeaveEventHandles { - handle(slf, playerId) - } -} - -// RegNewDayEvent 注册新的一天事件 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) RegNewDayEvent(handle NewDayEventHandle[PlayerID, ActivityData, PlayerData]) { - slf.newDayEventHandles = append(slf.newDayEventHandles, handle) -} - -func (slf *Activity[PlayerID, ActivityData, PlayerData]) OnNewDayEvent() { - for _, handle := range slf.newDayEventHandles { - handle(slf) - } -} - -// RegPlayerNewDayEvent 注册玩家新的一天事件 -func (slf *Activity[PlayerID, ActivityData, PlayerData]) RegPlayerNewDayEvent(handle PlayerNewDayEventHandle[PlayerID, ActivityData, PlayerData]) { - slf.playerNewDayEventHandles = append(slf.playerNewDayEventHandles, handle) -} - -func (slf *Activity[PlayerID, ActivityData, PlayerData]) OnPlayerNewDayEvent(playerId PlayerID) { - for _, handle := range slf.playerNewDayEventHandles { - handle(slf, playerId) - } -} - -func (slf *Activity[PlayerID, ActivityData, PlayerData]) refreshTicker(init bool) { - var ( - current = activityOffset.Now() - start = slf.period.Start() - end = slf.period.End() - ) - - if slf.IsOpen() { - if init { - slf.OnStartEvent() - } - if distance := end.Sub(current); distance > 0 { - ticker.After(fmt.Sprintf(tickerStop, slf.id), distance, slf.OnStopEvent) - } - ticker.After(fmt.Sprintf(tickerNewDay, slf.id), times.GetNextDayInterval(current), slf.newDay) - } else { - if distance := start.Sub(current); distance > 0 { - ticker.After(fmt.Sprintf(tickerNewDay, slf.id), times.GetNextDayInterval(start), slf.newDay) - ticker.After(fmt.Sprintf(tickerStart, slf.id), distance, slf.OnStartEvent) - } - if end.Before(current) { - slf.OnStopEvent() - } - } - if slf.IsShow() { - if init { - slf.OnStartShowEvent() - } - if distance := slf.afterShow.Sub(current); distance > 0 { - ticker.After(fmt.Sprintf(tickerShowStop, slf.id), distance, slf.OnStopShowEvent) - } - } else { - if distance := slf.beforeShow.Sub(current); distance > 0 { - ticker.After(fmt.Sprintf(tickerShowStart, slf.id), distance, slf.OnStartShowEvent) - } - if slf.afterShow.Before(current) { - slf.OnStopShowEvent() + if slf.retention > 0 { + ticker.After(slf.tickerKey, slf.retention, func() { + ticker.StopTimer(slf.retentionKey) + resetActivityData(slf.t, slf.id) + }) } } } - -func (slf *Activity[PlayerID, ActivityData, PlayerData]) stopTicker() { - ticker.StopTimer(fmt.Sprintf(tickerStart, slf.id)) - ticker.StopTimer(fmt.Sprintf(tickerStop, slf.id)) - ticker.StopTimer(fmt.Sprintf(tickerShowStart, slf.id)) - ticker.StopTimer(fmt.Sprintf(tickerShowStop, slf.id)) - ticker.StopTimer(fmt.Sprintf(tickerNewDay, slf.id)) -} - -func (slf *Activity[PlayerID, ActivityData, PlayerData]) newDay() { - current := activityOffset.Now() - ticker.After(fmt.Sprintf(tickerNewDay, slf.id), times.GetNextDayInterval(current), slf.newDay) - - last := time.Unix(slf.data.LastNewDay, 0) - if !times.IsSameDay(last, times.GetToday(current)) { - slf.OnNewDayEvent() - } - - for playerId := range slf.data.PlayerData { - slf.playerNewDay(playerId, current) - } - -} - -func (slf *Activity[PlayerID, ActivityData, PlayerData]) playerNewDay(playerId PlayerID, current time.Time) { - last := time.Unix(slf.data.PlayerLastNewDay[playerId], 0) - if !times.IsSameDay(last, times.GetToday(current)) { - slf.OnPlayerNewDayEvent(playerId) - } -} diff --git a/game/activity/activity_test.go b/game/activity/activity_test.go index d84e21a..af551db 100644 --- a/game/activity/activity_test.go +++ b/game/activity/activity_test.go @@ -2,41 +2,61 @@ package activity_test import ( "github.com/kercylan98/minotaur/game/activity" - "github.com/kercylan98/minotaur/utils/offset" "github.com/kercylan98/minotaur/utils/times" - . "github.com/smartystreets/goconvey/convey" "testing" "time" ) -func TestActivity_IsInvalid(t *testing.T) { - Convey("TestActivity_IsInvalid", t, func() { - offsetTime := offset.NewTime(-time.Now().Sub(time.Date(2023, 06, 28, 13, 0, 0, 0, time.Local))) - activity.SetOffsetTime(offsetTime) - t.Log(offsetTime.Now()) - act := activity.NewActivity[int, activity.NoneData, activity.NoneData](1, - times.NewPeriod( - times.DateWithHMS(2023, 06, 21, 0, 0, 0), - times.DateWithHMS(2023, 06, 22, 0, 0, 0), - ), - ) - So(act.IsInvalid(), ShouldBeTrue) - - act = activity.NewActivity[int, activity.NoneData, activity.NoneData](1, - times.NewPeriod( - times.DateWithHMS(2023, 06, 28, 0, 0, 0), - times.DateWithHMS(2023, 06, 29, 0, 0, 0), - ), - ) - So(act.IsInvalid(), ShouldBeFalse) - - act = activity.NewActivity[int, activity.NoneData, activity.NoneData](1, - times.NewPeriod( - times.DateWithHMS(2023, 06, 26, 0, 0, 0), - times.DateWithHMS(2023, 06, 28, 0, 0, 0), - ), - activity.WithAfterShowTime[int, activity.NoneData, activity.NoneData](times.Day), - ) - So(act.IsInvalid(), ShouldBeFalse) - }) +type ActivityData struct { + players []string +} + +type PlayerData struct { + info string +} + +func TestRegTypeByGlobalData(t *testing.T) { + controller := activity.DefineGlobalDataActivity[int, int, *ActivityData](1, func(activityId int, data *activity.DataMeta[*ActivityData]) { + data.Data.players = append(data.Data.players, "temp") + }) + + activity.RegUpcomingEvent(1, func(activityId int) { + t.Log(controller.GetGlobalData(activityId).players) + t.Log("即将开始") + }) + + activity.RegStartedEvent(1, func(activityId int) { + t.Log("开始") + }) + + activity.RegEndedEvent(1, func(activityId int) { + t.Log(controller.GetGlobalData(activityId).players) + t.Log("结束") + }) + + activity.RegExtendedShowStartedEvent(1, func(activityId int) { + t.Log("延长展示开始") + }) + + activity.RegExtendedShowEndedEvent(1, func(activityId int) { + t.Log("延长展示结束") + }) + + activity.RegNewDayEvent(1, func(activityId int) { + t.Log("新的一天") + }) + + now := time.Now() + + if err := activity.LoadOrRefreshActivity(1, 1, + activity.WithUpcomingTime[int, int](now.Add(1*time.Second)), + activity.WithStartTime[int, int](now.Add(2*times.Second)), + activity.WithEndTime[int, int](now.Add(3*times.Second)), + activity.WithExtendedShowTime[int, int](now.Add(4*times.Second)), + activity.WithLoop[int, int](3*times.Second), + ); err != nil { + t.Fatal(err) + } + + time.Sleep(times.Week) } diff --git a/game/activity/controller.go b/game/activity/controller.go new file mode 100644 index 0000000..1c2bf3a --- /dev/null +++ b/game/activity/controller.go @@ -0,0 +1,112 @@ +package activity + +import "github.com/kercylan98/minotaur/utils/generic" + +type none byte + +// DefineNoneDataActivity 声明无数据的活动类型 +func DefineNoneDataActivity[Type, ID generic.Basic](activityType Type) NoneDataActivityController[Type, ID, none, none, none] { + return regController(activityType, &Controller[Type, ID, none, none, none]{ + t: activityType, + }) +} + +// DefineGlobalDataActivity 声明拥有全局数据的活动类型 +func DefineGlobalDataActivity[Type, ID generic.Basic, Data any](activityType Type, initializer func(activityId ID, data *DataMeta[Data])) GlobalDataActivityController[Type, ID, Data, none, none] { + return regController(activityType, &Controller[Type, ID, Data, none, none]{ + t: activityType, + globalData: make(map[ID]*DataMeta[Data]), + globalInit: initializer, + }) +} + +// DefineEntityDataActivity 声明拥有实体数据的活动类型 +func DefineEntityDataActivity[Type, ID, EntityID generic.Basic, EntityData any](activityType Type, initializer func(activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])) EntityDataActivityController[Type, ID, none, EntityID, EntityData] { + return regController(activityType, &Controller[Type, ID, none, EntityID, EntityData]{ + t: activityType, + entityData: make(map[ID]map[EntityID]*EntityDataMeta[EntityData]), + entityInit: initializer, + }) +} + +// DefineGlobalAndEntityDataActivity 声明拥有全局数据和实体数据的活动类型 +func DefineGlobalAndEntityDataActivity[Type, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any](activityType Type, globalInitializer func(activityId ID, data *DataMeta[Data]), entityInitializer func(activityId ID, entityId EntityID, data *EntityDataMeta[EntityData])) GlobalAndEntityDataActivityController[Type, ID, Data, EntityID, EntityData] { + return regController(activityType, &Controller[Type, ID, Data, EntityID, EntityData]{ + t: activityType, + globalData: make(map[ID]*DataMeta[Data]), + entityData: make(map[ID]map[EntityID]*EntityDataMeta[EntityData]), + globalInit: globalInitializer, + entityInit: entityInitializer, + }) +} + +// Controller 活动控制器 +type Controller[Type, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] struct { + t Type // 活动类型 + activities map[ID]*Activity[Type, ID] // 活动 + globalInit func(activityId ID, data *DataMeta[Data]) // 全局初始化器 + entityInit func(activityId ID, entityId EntityID, data *EntityDataMeta[EntityData]) // 实体初始化器 + globalDataLoader func(activityId any) // 全局数据加载器 + entityDataLoader func(activityId any, entityId any) // 实体数据加载器 + globalInitializer func(activityType Type, activityId ID, data map[ID]*DataMeta[Data]) // 全局数据初始化器 + entityInitializer func(activityType Type, activityId ID, data map[ID]*DataMeta[Data], entityData map[ID]map[EntityID]*EntityDataMeta[EntityData]) // 实体数据初始化器 + globalData map[ID]*DataMeta[Data] // 全局数据 + entityData map[ID]map[EntityID]*EntityDataMeta[EntityData] // 实体数据 +} + +// GetGlobalData 获取特定活动全局数据 +func (slf *Controller[Type, ID, Data, EntityID, EntityData]) GetGlobalData(activityId ID) Data { + slf.globalDataLoader(activityId) + return slf.globalData[activityId].Data +} + +// GetEntityData 获取特定活动实体数据 +func (slf *Controller[Type, ID, Data, EntityID, EntityData]) GetEntityData(activityId ID, entityId EntityID) EntityData { + slf.entityDataLoader(activityId, entityId) + return slf.entityData[activityId][entityId].Data +} + +// GetAllEntityData 获取特定活动所有实体数据 +func (slf *Controller[Type, ID, Data, EntityID, EntityData]) GetAllEntityData(activityId ID) map[EntityID]EntityData { + var entities = make(map[EntityID]EntityData) + for k, v := range slf.entityData[activityId] { + entities[k] = v.Data + } + return entities +} + +// IsOpen 活动是否开启 +func (slf *Controller[Type, ID, Data, EntityID, EntityData]) IsOpen(activityId ID) bool { + activity, exist := slf.activities[activityId] + if !exist { + return false + } + return activity.state == stateStarted +} + +// IsShow 活动是否展示 +func (slf *Controller[Type, ID, Data, EntityID, EntityData]) IsShow(activityId ID) bool { + activity, exist := slf.activities[activityId] + if !exist { + return false + } + return activity.state == stateUpcoming || (activity.state == stateEnded && activity.tl.HasState(stateExtendedShowEnded)) +} + +// IsOpenOrShow 活动是否开启或展示 +func (slf *Controller[Type, ID, Data, EntityID, EntityData]) IsOpenOrShow(activityId ID) bool { + activity, exist := slf.activities[activityId] + if !exist { + return false + } + return activity.state == stateStarted || activity.state == stateUpcoming || (activity.state == stateEnded && activity.tl.HasState(stateExtendedShowEnded)) +} + +// Refresh 刷新活动 +func (slf *Controller[Type, ID, Data, EntityID, EntityData]) Refresh(activityId ID) { + activity, exist := slf.activities[activityId] + if !exist { + return + } + activity.refresh() +} diff --git a/game/activity/controller_interface.go b/game/activity/controller_interface.go new file mode 100644 index 0000000..7493af5 --- /dev/null +++ b/game/activity/controller_interface.go @@ -0,0 +1,42 @@ +package activity + +import "github.com/kercylan98/minotaur/utils/generic" + +type BasicActivityController[Type, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface { + // IsOpen 活动是否开启 + IsOpen(activityId ID) bool + // IsShow 活动是否展示 + IsShow(activityId ID) bool + // IsOpenOrShow 活动是否开启或展示 + IsOpenOrShow(activityId ID) bool + // Refresh 刷新活动 + Refresh(activityId ID) +} + +// NoneDataActivityController 无数据活动控制器 +type NoneDataActivityController[Type, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface { + BasicActivityController[Type, ID, Data, EntityID, EntityData] +} + +// GlobalDataActivityController 全局数据活动控制器 +type GlobalDataActivityController[Type, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface { + BasicActivityController[Type, ID, Data, EntityID, EntityData] + // GetGlobalData 获取全局数据 + GetGlobalData(activityId ID) Data +} + +// EntityDataActivityController 实体数据活动控制器 +type EntityDataActivityController[Type, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface { + BasicActivityController[Type, ID, Data, EntityID, EntityData] + // GetEntityData 获取实体数据 + GetEntityData(activityId ID, entityId EntityID) EntityData +} + +// GlobalAndEntityDataActivityController 全局数据和实体数据活动控制器 +type GlobalAndEntityDataActivityController[Type, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any] interface { + BasicActivityController[Type, ID, Data, EntityID, EntityData] + // GetGlobalData 获取全局数据 + GetGlobalData(activityId ID) Data + // GetEntityData 获取实体数据 + GetEntityData(activityId ID, entityId EntityID) EntityData +} diff --git a/game/activity/controller_internal.go b/game/activity/controller_internal.go new file mode 100644 index 0000000..88814f1 --- /dev/null +++ b/game/activity/controller_internal.go @@ -0,0 +1,145 @@ +package activity + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/generic" + "github.com/kercylan98/minotaur/utils/times" + "reflect" + "sync" + "time" +) + +var ( + controllers map[any]any // type -> controller (特定类型活动控制器) + controllerRegisters map[any]func(activityType, activityId any) (act any, optionInitCallback func(activity any)) // type -> register (控制活动注册到特定类型控制器的注册机) + controllerGlobalDataReader []func(handler func(activityType, activityId, data any)) // 活动全局数据读取器 + controllerEntityDataReader []func(handler func(activityType, activityId, entityId, data any)) // 活动实体数据读取器 + controllerReset map[any]func(activityId any) // type -> reset (活动数据重置器) + controllersLock sync.RWMutex +) + +func init() { + controllers = make(map[any]any) + controllerRegisters = make(map[any]func(activityType, activityId any) (act any, optionInitCallback func(activity any))) + controllerGlobalDataReader = make([]func(handler func(activityType, activityId, data any)), 0) + controllerEntityDataReader = make([]func(handler func(activityType, activityId, entityId, data any)), 0) + controllerReset = make(map[any]func(activityId any)) +} + +// regController 注册活动类型 +func regController[Type, ID generic.Basic, Data any, EntityID generic.Basic, EntityData any](activityType Type, controller *Controller[Type, ID, Data, EntityID, EntityData]) *Controller[Type, ID, Data, EntityID, EntityData] { + controllersLock.Lock() + defer controllersLock.Unlock() + controller.activities = make(map[ID]*Activity[Type, ID]) + controllerGlobalDataReader = append(controllerGlobalDataReader, func(handler func(activityType, activityId, data any)) { + for activityId, data := range controller.globalData { + handler(activityType, activityId, data) + } + }) + controllerEntityDataReader = append(controllerEntityDataReader, func(handler func(activityType, activityId, entityId, data any)) { + for activityId, entities := range controller.entityData { + for entityId, data := range entities { + handler(activityType, activityId, entityId, data) + } + } + }) + + if controller.globalData != nil { + var d Data + var tof = reflect.TypeOf(d) + if tof.Kind() == reflect.Pointer { + tof = tof.Elem() + } + controller.globalDataLoader = func(activityId any) { + var id = activityId.(ID) + if _, exist := controller.globalData[id]; exist { + return + } + data := &DataMeta[Data]{ + Data: reflect.New(tof).Interface().(Data), + } + if controller.globalInit != nil { + controller.globalInit(id, data) + } + controller.globalData[id] = data + } + } + if controller.entityData != nil { + var d Data + var tof = reflect.TypeOf(d) + if tof.Kind() == reflect.Pointer { + tof = tof.Elem() + } + controller.entityDataLoader = func(activityId any, entityId any) { + var id, eid = activityId.(ID), entityId.(EntityID) + entities, exist := controller.entityData[id] + if !exist { + entities = make(map[EntityID]*EntityDataMeta[EntityData]) + controller.entityData[id] = entities + } + if _, exist = entities[eid]; exist { + return + } + data := &EntityDataMeta[EntityData]{ + Data: reflect.New(tof).Interface().(EntityData), + } + if controller.entityInit != nil { + controller.entityInit(id, eid, data) + } + entities[eid] = data + } + } + controllers[activityType] = controller + controllerRegisters[activityType] = func(activityType, activityId any) (act any, optionInitCallback func(activity any)) { + var at, ai = activityType.(Type), activityId.(ID) + activity, exist := controller.activities[ai] + if !exist { + activity = &Activity[Type, ID]{ + t: at, + id: ai, + tl: times.NewStateLine[byte](stateClosed), + tickerKey: fmt.Sprintf("activity:%d:%v", reflect.ValueOf(at).Kind(), ai), + getLastNewDayTime: func() time.Time { + return controller.globalData[ai].LastNewDay + }, + setLastNewDayTime: func(t time.Time) { + controller.globalData[ai].LastNewDay = t + }, + } + } + controller.activities[activity.id] = activity + return activity, func(activity any) { + act := activity.(*Activity[Type, ID]) + if !act.lazy { + controller.GetGlobalData(ai) + } + if act.retention > 0 { + act.retentionKey = fmt.Sprintf("%s:retention", act.tickerKey) + } + } + } + controllerReset[activityType] = func(activityId any) { + var id = activityId.(ID) + delete(controller.globalData, id) + delete(controller.entityData, id) + } + return controller +} + +// getControllerRegister 获取活动类型注册机 +func getControllerRegister[Type generic.Basic](activityType Type) func(activityType, activityId any) (act any, optionInitCallback func(activity any)) { + controllersLock.RLock() + defer controllersLock.RUnlock() + return controllerRegisters[activityType] +} + +// resetActivityData 重置活动数据 +func resetActivityData[Type, ID generic.Basic](activityType Type, activityId ID) { + controllersLock.RLock() + defer controllersLock.RUnlock() + reset, exist := controllerReset[activityType] + if !exist { + return + } + reset(activityId) +} diff --git a/game/activity/data.go b/game/activity/data.go index e91557d..b3e7c18 100644 --- a/game/activity/data.go +++ b/game/activity/data.go @@ -1,16 +1,19 @@ package activity -func newData[PlayerID comparable, ActivityData any, PlayerData any]() *Data[PlayerID, ActivityData, PlayerData] { - return &Data[PlayerID, ActivityData, PlayerData]{ - PlayerData: make(map[PlayerID]PlayerData), - PlayerLastNewDay: make(map[PlayerID]int64), - } +import "time" + +// DataMeta 全局活动数据 +type DataMeta[Data any] struct { + Start time.Time `json:"start,omitempty"` // 活动开始时间 + End time.Time `json:"end,omitempty"` // 活动结束时间 + Data Data `json:"data,omitempty"` // 活动数据 + LastNewDay time.Time `json:"lastNewDay,omitempty"` // 上次跨天时间 } -// Data 活动数据信息 -type Data[PlayerID comparable, ActivityData any, PlayerData any] struct { - Data ActivityData // 活动全局数据 - PlayerData map[PlayerID]PlayerData // 活动玩家数据 - LastNewDay int64 // 最后触发新的一天的时间戳 - PlayerLastNewDay map[PlayerID]int64 // 玩家最后触发新的一天的时间戳 +// EntityDataMeta 活动实体数据 +type EntityDataMeta[Data any] struct { + Start time.Time `json:"start,omitempty"` // 对象参与活动时间 + End time.Time `json:"end,omitempty"` // 对象结束活动时间 + Data Data `json:"data,omitempty"` // 活动数据 + LastNewDay time.Time `json:"lastNewDay,omitempty"` // 上次跨天时间 } diff --git a/game/activity/events.go b/game/activity/events.go index ec5c30f..d9cf694 100644 --- a/game/activity/events.go +++ b/game/activity/events.go @@ -1,12 +1,245 @@ package activity -type ( - StartEventHandle[PlayerID comparable, Data, PlayerData any] func(activity *Activity[PlayerID, Data, PlayerData]) - StopEventHandle[PlayerID comparable, Data, PlayerData any] func(activity *Activity[PlayerID, Data, PlayerData]) - StartShowEventHandle[PlayerID comparable, Data, PlayerData any] func(activity *Activity[PlayerID, Data, PlayerData]) - StopShowEventHandle[PlayerID comparable, Data, PlayerData any] func(activity *Activity[PlayerID, Data, PlayerData]) - PlayerJoinEventHandle[PlayerID comparable, Data, PlayerData any] func(activity *Activity[PlayerID, Data, PlayerData], playerId PlayerID) - PlayerLeaveEventHandle[PlayerID comparable, Data, PlayerData any] func(activity *Activity[PlayerID, Data, PlayerData], playerId PlayerID) - NewDayEventHandle[PlayerID comparable, Data, PlayerData any] func(activity *Activity[PlayerID, Data, PlayerData]) - PlayerNewDayEventHandle[PlayerID comparable, Data, PlayerData any] func(activity *Activity[PlayerID, Data, PlayerData], playerId PlayerID) +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/generic" + "github.com/kercylan98/minotaur/utils/log" + "github.com/kercylan98/minotaur/utils/slice" + "github.com/kercylan98/minotaur/utils/timer" + "github.com/kercylan98/minotaur/utils/times" + "reflect" + "time" ) + +type ( + UpcomingEventHandler[ID generic.Basic] func(activityId ID) // 即将开始的活动事件处理器 + StartedEventHandler[ID generic.Basic] func(activityId ID) // 活动开始事件处理器 + EndedEventHandler[ID generic.Basic] func(activityId ID) // 活动结束事件处理器 + ExtendedShowStartedEventHandler[ID generic.Basic] func(activityId ID) // 活动结束后延长展示开始事件处理器 + ExtendedShowEndedEventHandler[ID generic.Basic] func(activityId ID) // 活动结束后延长展示结束事件处理器 + NewDayEventHandler[ID generic.Basic] func(activityId ID) // 新的一天事件处理器 +) + +var ( + upcomingEventHandlers map[any]*slice.Priority[func(activityId any)] // 即将开始的活动事件处理器 + startedEventHandlers map[any]*slice.Priority[func(activityId any)] // 活动开始事件处理器 + endedEventHandlers map[any]*slice.Priority[func(activityId any)] // 活动结束事件处理器 + extShowStartedEventHandlers map[any]*slice.Priority[func(activityId any)] // 活动结束后延长展示开始事件处理器 + extShowEndedEventHandlers map[any]*slice.Priority[func(activityId any)] // 活动结束后延长展示结束事件处理器 + newDayEventHandlers map[any]*slice.Priority[func(activityId any)] // 新的一天事件处理器 +) + +func init() { + upcomingEventHandlers = make(map[any]*slice.Priority[func(activityId any)]) + startedEventHandlers = make(map[any]*slice.Priority[func(activityId any)]) + endedEventHandlers = make(map[any]*slice.Priority[func(activityId any)]) + extShowStartedEventHandlers = make(map[any]*slice.Priority[func(activityId any)]) + extShowEndedEventHandlers = make(map[any]*slice.Priority[func(activityId any)]) + newDayEventHandlers = make(map[any]*slice.Priority[func(activityId any)]) +} + +// RegUpcomingEvent 注册即将开始的活动事件处理器 +func RegUpcomingEvent[Type, ID generic.Basic](activityType Type, handler UpcomingEventHandler[ID], priority ...int) { + handlers, exist := upcomingEventHandlers[activityType] + if !exist { + handlers = slice.NewPriority[func(activityId any)]() + upcomingEventHandlers[activityType] = handlers + } + handlers.Append(func(activityId any) { + if !reflect.TypeOf(activityId).AssignableTo(reflect.TypeOf(handler).In(0)) { + return + } + handler(activityId.(ID)) + }, slice.GetValue(priority, 0)) +} + +// OnUpcomingEvent 即将开始的活动事件 +func OnUpcomingEvent[Type, ID generic.Basic](activity *Activity[Type, ID]) { + handlers, exist := upcomingEventHandlers[activity.t] + if !exist { + return + } + handlers.RangeValue(func(index int, value func(activityId any)) bool { + defer func() { + if err := recover(); err != nil { + log.Error("OnUpcomingEvent", log.Any("type", activity.t), log.Any("id", activity.id), log.Any("err", err)) + return + } + }() + value(activity.id) + return true + }) +} + +// RegStartedEvent 注册活动开始事件处理器 +func RegStartedEvent[Type, ID generic.Basic](activityType Type, handler StartedEventHandler[ID], priority ...int) { + handlers, exist := startedEventHandlers[activityType] + if !exist { + handlers = slice.NewPriority[func(activityId any)]() + startedEventHandlers[activityType] = handlers + } + handlers.Append(func(activityId any) { + if !reflect.TypeOf(activityId).AssignableTo(reflect.TypeOf(handler).In(0)) { + return + } + handler(activityId.(ID)) + }, slice.GetValue(priority, 0)) +} + +// OnStartedEvent 活动开始事件 +func OnStartedEvent[Type, ID generic.Basic](activity *Activity[Type, ID]) { + handlers, exist := startedEventHandlers[activity.t] + if !exist { + return + } + handlers.RangeValue(func(index int, value func(activityId any)) bool { + defer func() { + if err := recover(); err != nil { + log.Error("OnStartedEvent", log.Any("type", activity.t), log.Any("id", activity.id), log.Any("err", err)) + return + } + }() + value(activity.id) + return true + }) + + now := time.Now() + if !times.IsSameDay(now, activity.getLastNewDayTime()) { + OnNewDayEvent(activity) + } + ticker.Loop(fmt.Sprintf("activity:new_day:%d:%v", reflect.ValueOf(activity.t).Kind(), activity.id), times.GetNextDayInterval(now), times.Day, timer.Forever, func() { + OnNewDayEvent(activity) + }) +} + +// RegEndedEvent 注册活动结束事件处理器 +func RegEndedEvent[Type, ID generic.Basic](activityType Type, handler EndedEventHandler[ID], priority ...int) { + handlers, exist := endedEventHandlers[activityType] + if !exist { + handlers = slice.NewPriority[func(activityId any)]() + endedEventHandlers[activityType] = handlers + } + handlers.Append(func(activityId any) { + if !reflect.TypeOf(activityId).AssignableTo(reflect.TypeOf(handler).In(0)) { + return + } + handler(activityId.(ID)) + }, slice.GetValue(priority, 0)) +} + +// OnEndedEvent 活动结束事件 +func OnEndedEvent[Type, ID generic.Basic](activity *Activity[Type, ID]) { + handlers, exist := endedEventHandlers[activity.t] + if !exist { + return + } + handlers.RangeValue(func(index int, value func(activityId any)) bool { + defer func() { + if err := recover(); err != nil { + log.Error("OnEndedEvent", log.Any("type", activity.t), log.Any("id", activity.id), log.Any("err", err)) + return + } + }() + value(activity.id) + return true + }) +} + +// RegExtendedShowStartedEvent 注册活动结束后延长展示开始事件处理器 +func RegExtendedShowStartedEvent[Type, ID generic.Basic](activityType Type, handler ExtendedShowStartedEventHandler[ID], priority ...int) { + handlers, exist := extShowStartedEventHandlers[activityType] + if !exist { + handlers = slice.NewPriority[func(activityId any)]() + extShowStartedEventHandlers[activityType] = handlers + } + handlers.Append(func(activityId any) { + if !reflect.TypeOf(activityId).AssignableTo(reflect.TypeOf(handler).In(0)) { + return + } + handler(activityId.(ID)) + }, slice.GetValue(priority, 0)) +} + +// OnExtendedShowStartedEvent 活动结束后延长展示开始事件 +func OnExtendedShowStartedEvent[Type, ID generic.Basic](activity *Activity[Type, ID]) { + handlers, exist := extShowStartedEventHandlers[activity.t] + if !exist { + return + } + handlers.RangeValue(func(index int, value func(activityId any)) bool { + defer func() { + if err := recover(); err != nil { + log.Error("OnExtendedShowStartedEvent", log.Any("type", activity.t), log.Any("id", activity.id), log.Any("err", err)) + return + } + }() + value(activity.id) + return true + }) +} + +// RegExtendedShowEndedEvent 注册活动结束后延长展示结束事件处理器 +func RegExtendedShowEndedEvent[Type, ID generic.Basic](activityType Type, handler ExtendedShowEndedEventHandler[ID], priority ...int) { + handlers, exist := extShowEndedEventHandlers[activityType] + if !exist { + handlers = slice.NewPriority[func(activityId any)]() + extShowEndedEventHandlers[activityType] = handlers + } + handlers.Append(func(activityId any) { + if !reflect.TypeOf(activityId).AssignableTo(reflect.TypeOf(handler).In(0)) { + return + } + handler(activityId.(ID)) + }, slice.GetValue(priority, 0)) +} + +// OnExtendedShowEndedEvent 活动结束后延长展示结束事件 +func OnExtendedShowEndedEvent[Type, ID generic.Basic](activity *Activity[Type, ID]) { + handlers, exist := extShowEndedEventHandlers[activity.t] + if !exist { + return + } + handlers.RangeValue(func(index int, value func(activityId any)) bool { + defer func() { + if err := recover(); err != nil { + log.Error("OnExtendedShowEndedEvent", log.Any("type", activity.t), log.Any("id", activity.id), log.Any("err", err)) + return + } + }() + value(activity.id) + return true + }) +} + +// RegNewDayEvent 注册新的一天事件处理器 +func RegNewDayEvent[Type, ID generic.Basic](activityType Type, handler NewDayEventHandler[ID], priority ...int) { + handlers, exist := newDayEventHandlers[activityType] + if !exist { + handlers = slice.NewPriority[func(activityId any)]() + newDayEventHandlers[activityType] = handlers + } + handlers.Append(func(activityId any) { + if !reflect.TypeOf(activityId).AssignableTo(reflect.TypeOf(handler).In(0)) { + return + } + handler(activityId.(ID)) + }, slice.GetValue(priority, 0)) +} + +// OnNewDayEvent 新的一天事件 +func OnNewDayEvent[Type, ID generic.Basic](activity *Activity[Type, ID]) { + handlers, exist := newDayEventHandlers[activity.t] + if !exist { + return + } + handlers.RangeValue(func(index int, value func(activityId any)) bool { + defer func() { + if err := recover(); err != nil { + log.Error("OnNewDayEvent", log.Any("type", activity.t), log.Any("id", activity.id), log.Any("err", err)) + return + } + }() + value(activity.id) + return true + }) +} diff --git a/game/activity/interface.go b/game/activity/interface.go deleted file mode 100644 index b4af854..0000000 --- a/game/activity/interface.go +++ /dev/null @@ -1,6 +0,0 @@ -package activity - -type activityInterface interface { - stopTicker() - refreshTicker(init bool) -} diff --git a/game/activity/options.go b/game/activity/options.go index 55717a5..3eecb4d 100644 --- a/game/activity/options.go +++ b/game/activity/options.go @@ -1,60 +1,56 @@ package activity import ( + "github.com/kercylan98/minotaur/utils/generic" "time" ) -type Option[PlayerID comparable, ActivityData, PlayerData any] func(activity *Activity[PlayerID, ActivityData, PlayerData]) +// Option 活动选项 +type Option[Type, ID generic.Basic] func(*Activity[Type, ID]) -// WithData 通过指定活动数据的方式来创建活动 -func WithData[PlayerID comparable, ActivityData, PlayerData any](data *Data[PlayerID, ActivityData, PlayerData]) Option[PlayerID, ActivityData, PlayerData] { - return func(activity *Activity[PlayerID, ActivityData, PlayerData]) { - activity.data = data +// WithUpcomingTime 设置活动预告时间 +func WithUpcomingTime[Type, ID generic.Basic](t time.Time) Option[Type, ID] { + return func(activity *Activity[Type, ID]) { + activity.tl.AddState(stateUpcoming, t) } } -// WithActivityData 通过指定活动全局数据的方式来创建活动 -// - 该活动数据将会被作为活动的全局数据 -// - 默认情况下活动本身不包含任何数据 -func WithActivityData[PlayerID comparable, ActivityData, PlayerData any](data ActivityData) Option[PlayerID, ActivityData, PlayerData] { - return func(activity *Activity[PlayerID, ActivityData, PlayerData]) { - activity.data.Data = data +// WithStartTime 设置活动开始时间 +func WithStartTime[Type, ID generic.Basic](t time.Time) Option[Type, ID] { + return func(activity *Activity[Type, ID]) { + activity.tl.AddState(stateStarted, t) } } -// WithPlayerDataLoadHandle 通过指定玩家数据加载函数的方式来创建活动 -// - 该函数将会在玩家数据加载时被调用 -// - 活动中的玩家数据将会被按需加载,只有在玩家加入活动时才会被加载 -func WithPlayerDataLoadHandle[PlayerID comparable, ActivityData, PlayerData any](handle func(activity *Activity[PlayerID, ActivityData, PlayerData], playerId PlayerID) PlayerData) Option[PlayerID, ActivityData, PlayerData] { - return func(activity *Activity[PlayerID, ActivityData, PlayerData]) { - activity.playerDataLoadHandle = handle +// WithEndTime 设置活动结束时间 +func WithEndTime[Type, ID generic.Basic](t time.Time) Option[Type, ID] { + return func(activity *Activity[Type, ID]) { + activity.tl.AddState(stateEnded, t) } } -// WithBeforeShowTime 通过指定活动开始前的展示时间的方式来创建活动 -func WithBeforeShowTime[PlayerID comparable, ActivityData, PlayerData any](showTime time.Duration) Option[PlayerID, ActivityData, PlayerData] { - return func(activity *Activity[PlayerID, ActivityData, PlayerData]) { - activity.beforeShow = activity.period.Start().Add(-showTime) +// WithExtendedShowTime 设置延长展示时间 +func WithExtendedShowTime[Type, ID generic.Basic](t time.Time) Option[Type, ID] { + return func(activity *Activity[Type, ID]) { + activity.tl.AddState(stateExtendedShowEnded, t) } } -// WithAfterShowTime 通过指定活动结束后的展示时间的方式来创建活动 -func WithAfterShowTime[PlayerID comparable, ActivityData, PlayerData any](showTime time.Duration) Option[PlayerID, ActivityData, PlayerData] { - return func(activity *Activity[PlayerID, ActivityData, PlayerData]) { - activity.afterShow = activity.period.End().Add(showTime) +// WithLoop 设置活动循环,时间间隔小于等于 0 表示不循环 +// - 当活动状态展示结束后,会根据该选项设置的时间间隔重新开始 +func WithLoop[Type, ID generic.Basic](interval time.Duration) Option[Type, ID] { + return func(activity *Activity[Type, ID]) { + if interval <= 0 { + interval = 0 + } + activity.loop = interval } } -// WithLastNewDay 通过指定活动最后触发新的一天的时间戳的方式来创建活动 -func WithLastNewDay[PlayerID comparable, ActivityData, PlayerData any](lastNewDay int64) Option[PlayerID, ActivityData, PlayerData] { - return func(activity *Activity[PlayerID, ActivityData, PlayerData]) { - activity.data.LastNewDay = lastNewDay - } -} - -// WithPlayerLastNewDay 通过指定玩家最后触发新的一天的时间戳的方式来创建活动 -func WithPlayerLastNewDay[PlayerID comparable, ActivityData, PlayerData any](playerLastNewDay map[PlayerID]int64) Option[PlayerID, ActivityData, PlayerData] { - return func(activity *Activity[PlayerID, ActivityData, PlayerData]) { - activity.data.PlayerLastNewDay = playerLastNewDay +// WithLazy 设置活动数据懒加载 +// - 该选项仅用于全局数据,默认情况下,活动全局数据会在活动注册时候加载,如果设置了该选项,则会在第一次获取数据时候加载 +func WithLazy[Type, ID generic.Basic](lazy bool) Option[Type, ID] { + return func(activity *Activity[Type, ID]) { + activity.lazy = lazy } }