refactor: activity 包重构,整体优化使用体验,活动支持提前展示、及延长展示、持久化、数据保留周期、循环活动等
This commit is contained in:
parent
a9c84caa52
commit
4a41538460
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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"` // 上次跨天时间
|
||||
}
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
package activity
|
||||
|
||||
type activityInterface interface {
|
||||
stopTicker()
|
||||
refreshTicker(init bool)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue