refactor: activity 包重构,整体优化使用体验,活动支持提前展示、及延长展示、持久化、数据保留周期、循环活动等

This commit is contained in:
kercylan98 2023-11-23 19:42:50 +08:00
parent a9c84caa52
commit 4a41538460
10 changed files with 772 additions and 468 deletions

View File

@ -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)
}
}
}

View File

@ -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)
}
}

View File

@ -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)
}

112
game/activity/controller.go Normal file
View File

@ -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()
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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"` // 上次跨天时间
}

View File

@ -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
})
}

View File

@ -1,6 +0,0 @@
package activity
type activityInterface interface {
stopTicker()
refreshTicker(init bool)
}

View File

@ -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
}
}