Merge branch 'develop'

This commit is contained in:
kercylan98 2023-11-09 12:09:09 +08:00
commit d0b3f197c5
17 changed files with 752 additions and 298 deletions

142
game/task/condition.go Normal file
View File

@ -0,0 +1,142 @@
package task
import "time"
// Cond 创建任务条件
func Cond(k, v any) Condition {
return map[any]any{k: v}
}
// Condition 任务条件
type Condition map[any]any
// Cond 创建任务条件
func (slf Condition) Cond(k, v any) Condition {
slf[k] = v
return slf
}
// GetString 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetString(key any) string {
v, _ := slf[key].(string)
return v
}
// GetInt 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetInt(key any) int {
v, _ := slf[key].(int)
return v
}
// GetInt8 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetInt8(key any) int8 {
v, _ := slf[key].(int8)
return v
}
// GetInt16 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetInt16(key any) int16 {
v, _ := slf[key].(int16)
return v
}
// GetInt32 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetInt32(key any) int32 {
v, _ := slf[key].(int32)
return v
}
// GetInt64 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetInt64(key any) int64 {
v, _ := slf[key].(int64)
return v
}
// GetUint 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetUint(key any) uint {
v, _ := slf[key].(uint)
return v
}
// GetUint8 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetUint8(key any) uint8 {
v, _ := slf[key].(uint8)
return v
}
// GetUint16 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetUint16(key any) uint16 {
v, _ := slf[key].(uint16)
return v
}
// GetUint32 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetUint32(key any) uint32 {
v, _ := slf[key].(uint32)
return v
}
// GetUint64 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetUint64(key any) uint64 {
v, _ := slf[key].(uint64)
return v
}
// GetFloat32 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetFloat32(key any) float32 {
v, _ := slf[key].(float32)
return v
}
// GetFloat64 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetFloat64(key any) float64 {
v, _ := slf[key].(float64)
return v
}
// GetBool 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetBool(key any) bool {
v, _ := slf[key].(bool)
return v
}
// GetTime 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetTime(key any) time.Time {
v, _ := slf[key].(time.Time)
return v
}
// GetDuration 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetDuration(key any) time.Duration {
v, _ := slf[key].(time.Duration)
return v
}
// GetByte 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetByte(key any) byte {
v, _ := slf[key].(byte)
return v
}
// GetBytes 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetBytes(key any) []byte {
v, _ := slf[key].([]byte)
return v
}
// GetRune 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetRune(key any) rune {
v, _ := slf[key].(rune)
return v
}
// GetRunes 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetRunes(key any) []rune {
v, _ := slf[key].([]rune)
return v
}
// GetAny 获取特定类型的任务条件值,该值必须与预期类型一致,否则返回零值
func (slf Condition) GetAny(key any) any {
return slf[key]
}

View File

@ -1,14 +0,0 @@
package task
import "errors"
var (
// ErrTaskNotFinish 任务未完成
ErrTaskNotFinish = errors.New("task not finish")
// ErrTaskRewardReceived 任务奖励已领取
ErrTaskRewardReceived = errors.New("task reward received")
// ErrTaskNotStart 任务未开始
ErrTaskNotStart = errors.New("task not start")
// ErrTaskFail 任务失败
ErrTaskFail = errors.New("task fail")
)

View File

@ -1,35 +1,77 @@
package task
import (
"reflect"
)
type (
RefreshTaskCountEvent func(taskType int, increase int64)
RefreshTaskChildCountEvent func(taskType int, key any, increase int64)
RefreshTaskCounterEventHandler[Trigger any] func(taskType string, trigger Trigger, count int64) // 刷新任务计数器事件处理函数
RefreshTaskConditionEventHandler[Trigger any] func(taskType string, trigger Trigger, condition Condition) // 刷新任务条件事件处理函数
)
var (
refreshTaskCountEventHandles = make(map[int]RefreshTaskCountEvent)
refreshTaskChildCountEventHandles = make(map[int]RefreshTaskChildCountEvent)
refreshTaskCounterEventHandlers = make(map[string][]struct {
t reflect.Type
h func(taskType string, trigger any, count int64)
})
refreshTaskConditionEventHandlers = make(map[string][]struct {
t reflect.Type
h func(taskType string, trigger any, condition Condition)
})
)
// RegRefreshTaskCount 注册任务计数刷新事件
func RegRefreshTaskCount(taskType int, handler RefreshTaskCountEvent) {
refreshTaskCountEventHandles[taskType] = handler
// RegisterRefreshTaskCounterEvent 注册特定任务类型的刷新任务计数器事件处理函数
func RegisterRefreshTaskCounterEvent[Trigger any](taskType string, handler RefreshTaskCounterEventHandler[Trigger]) {
if refreshTaskCounterEventHandlers == nil {
refreshTaskCounterEventHandlers = make(map[string][]struct {
t reflect.Type
h func(taskType string, trigger any, count int64)
})
}
refreshTaskCounterEventHandlers[taskType] = append(refreshTaskCounterEventHandlers[taskType], struct {
t reflect.Type
h func(taskType string, trigger any, count int64)
}{reflect.TypeOf(handler).In(1), func(taskType string, trigger any, count int64) {
handler(taskType, trigger.(Trigger), count)
}})
}
// OnRefreshTaskCount 触发任务计数刷新事件
func OnRefreshTaskCount(taskType int, increase int64) {
if handler, ok := refreshTaskCountEventHandles[taskType]; ok {
handler(taskType, increase)
// OnRefreshTaskCounterEvent 触发特定任务类型的刷新任务计数器事件
func OnRefreshTaskCounterEvent(taskType string, trigger any, count int64) {
if handlers, exist := refreshTaskCounterEventHandlers[taskType]; exist {
for _, handler := range handlers {
if !reflect.TypeOf(trigger).AssignableTo(handler.t) {
continue
}
handler.h(taskType, trigger, count)
}
}
}
// RegRefreshTaskChildCount 注册任务子计数刷新事件
func RegRefreshTaskChildCount(taskType int, handler RefreshTaskChildCountEvent) {
refreshTaskChildCountEventHandles[taskType] = handler
// RegisterRefreshTaskConditionEvent 注册特定任务类型的刷新任务条件事件处理函数
func RegisterRefreshTaskConditionEvent[Trigger any](taskType string, handler RefreshTaskConditionEventHandler[Trigger]) {
if refreshTaskConditionEventHandlers == nil {
refreshTaskConditionEventHandlers = make(map[string][]struct {
t reflect.Type
h func(taskType string, trigger any, condition Condition)
})
}
refreshTaskConditionEventHandlers[taskType] = append(refreshTaskConditionEventHandlers[taskType], struct {
t reflect.Type
h func(taskType string, trigger any, condition Condition)
}{reflect.TypeOf(handler).In(1), func(taskType string, trigger any, condition Condition) {
handler(taskType, trigger.(Trigger), condition)
}})
}
// OnRefreshTaskChildCount 触发任务子计数刷新事件
func OnRefreshTaskChildCount(taskType int, key any, increase int64) {
if handler, ok := refreshTaskChildCountEventHandles[taskType]; ok {
handler(taskType, key, increase)
// OnRefreshTaskConditionEvent 触发特定任务类型的刷新任务条件事件
func OnRefreshTaskConditionEvent(taskType string, trigger any, condition Condition) {
if handlers, exist := refreshTaskConditionEventHandlers[taskType]; exist {
for _, handler := range handlers {
if !reflect.TypeOf(trigger).AssignableTo(handler.t) {
continue
}
handler.h(taskType, trigger, condition)
}
}
}

View File

@ -1,85 +1,82 @@
package task
import (
"github.com/kercylan98/minotaur/utils/offset"
"time"
)
// Option 任务选项
type Option func(task *Task)
// WithChildCount 通过初始化子计数的方式创建任务
func WithChildCount(key any, childCount int64) Option {
// WithType 设置任务类型
func WithType(taskType string) Option {
return func(task *Task) {
if task.childCount == nil {
task.childCount = make(map[any]int64)
task.Type = taskType
}
}
// WithCondition 设置任务完成条件,当满足条件时,任务状态为完成
// - 任务条件值需要变更时可通过 Task.AssignConditionValueAndRefresh 方法变更
// - 当多次设置该选项时,后面的设置会覆盖之前的设置
func WithCondition(condition Condition) Option {
return func(task *Task) {
if condition == nil {
return
}
if task.childCondition == nil {
task.childCondition = make(map[any]int64)
if task.Cond == nil {
task.Cond = condition
return
}
task.childCount[key] = childCount
}
}
// WithChild 通过指定子计数的方式创建任务
// - 只有当子计数与主计数均达到条件时,任务才会完成
// - 通常用于多条件的任务
func WithChild(key any, childCondition int64) Option {
return func(task *Task) {
if task.childCount == nil {
task.childCount = make(map[any]int64)
}
if task.childCondition == nil {
task.childCondition = make(map[any]int64)
}
task.childCondition[key] = childCondition
}
}
// WithDisableNotStartGetReward 禁止未开始的任务领取奖励
func WithDisableNotStartGetReward() Option {
return func(task *Task) {
task.disableNotStartGetReward = true
}
}
// WithCount 通过初始化计数的方式创建任务
func WithCount(count int64) Option {
return func(task *Task) {
task.SetCount(count)
}
}
// WithStartTime 通过指定开始时间的方式创建任务
// - 只有当时间在开始时间之后,任务才会开始计数
func WithStartTime(startTime time.Time) Option {
return func(task *Task) {
task.start = startTime
}
}
// WithOffsetTime 通过指定偏移时间的方式创建任务
func WithOffsetTime(offset *offset.Time) Option {
return func(task *Task) {
task.offset = offset
}
}
// WithLimitedTime 通过限时的方式创建任务
func WithLimitedTime(limitTime time.Duration) Option {
return func(task *Task) {
task.limitTime = limitTime
}
}
// WithFront 通过指定任务前置任务的方式创建任务
// - 当前置任务未完成时,当前任务不会开始计数
func WithFront(fronts ...*Task) Option {
return func(task *Task) {
if task.fronts == nil {
task.fronts = make(map[int64]*Task)
}
for _, front := range fronts {
task.fronts[front.GetID()] = front
for k, v := range condition {
task.Cond[k] = v
}
}
}
// WithCounter 设置任务计数器,当计数器达到要求时,任务状态为完成
// - 一些场景下,任务计数器可能会溢出,此时可通过 WithOverflowCounter 设置可溢出的任务计数器
// - 当多次设置该选项时,后面的设置会覆盖之前的设置
// - 如果需要初始化计数器的值,可通过 initCount 参数设置
func WithCounter(counter int64, initCount ...int64) Option {
return func(task *Task) {
task.Counter = counter
if len(initCount) > 0 {
task.CurrCount = initCount[0]
if task.CurrCount < 0 {
task.CurrCount = 0
} else if task.CurrCount > task.Counter {
task.CurrCount = task.Counter
}
}
}
}
// WithOverflowCounter 设置可溢出的任务计数器,当计数器达到要求时,任务状态为完成
// - 当多次设置该选项时,后面的设置会覆盖之前的设置
// - 如果需要初始化计数器的值,可通过 initCount 参数设置
func WithOverflowCounter(counter int64, initCount ...int64) Option {
return func(task *Task) {
task.Counter = counter
task.CurrOverflow = true
if len(initCount) > 0 {
task.CurrCount = initCount[0]
if task.CurrCount < 0 {
task.CurrCount = 0
}
}
}
}
// WithDeadline 设置任务截止时间,超过截至时间并且任务未完成时,任务状态为失败
func WithDeadline(deadline time.Time) Option {
return func(task *Task) {
task.Deadline = deadline
}
}
// WithLimitedDuration 设置任务限时,超过限时时间并且任务未完成时,任务状态为失败
func WithLimitedDuration(start time.Time, duration time.Duration) Option {
return func(task *Task) {
task.StartTime = start
task.LimitedDuration = duration
}
}

View File

@ -1,10 +0,0 @@
package task
const (
StateAccept State = iota + 1 // 已接受
StateFinish // 已完成
StateReward // 已领取
StateFail // 已失败
)
type State uint16

23
game/task/status.go Normal file
View File

@ -0,0 +1,23 @@
package task
const (
StatusAccept Status = iota + 1 // 已接受
StatusFailed // 已失败
StatusComplete // 已完成
StatusReward // 已领取奖励
)
var (
statusFormat = map[Status]string{
StatusAccept: "Accept",
StatusComplete: "Complete",
StatusReward: "Reward",
StatusFailed: "Failed",
}
)
type Status byte
func (slf Status) String() string {
return statusFormat[slf]
}

View File

@ -1,207 +1,152 @@
package task
import (
"github.com/kercylan98/minotaur/utils/hash"
"github.com/kercylan98/minotaur/utils/offset"
"time"
)
// NewTask 创建任务
func NewTask(id int64, taskType int, condition int64, options ...Option) *Task {
task := &Task{
id: id,
taskType: taskType,
condition: condition,
state: StateAccept,
}
// NewTask 生成任务
func NewTask(options ...Option) *Task {
task := new(Task)
for _, option := range options {
option(task)
}
if task.start.IsZero() {
if task.offset != nil {
task.start = task.offset.Now()
} else {
task.start = time.Now()
}
}
for key := range task.childCount {
if !hash.Exist(task.childCondition, key) {
delete(task.childCount, key)
}
}
if task.count == task.condition {
task.state = StateFinish
}
return task
return task.refreshTaskStatus()
}
// Task 通用任务数据结构
// Task 是对任务信息进行描述和处理的结构体
type Task struct {
id int64 // 任务ID
taskType int // 任务类型
count int64 // 任务主计数
condition int64 // 任务完成需要的计数条件
childCount map[any]int64 // 任务子计数
childCondition map[any]int64 // 任务子计数条件
state State // 任务状态
start time.Time // 任务开始时间
limitTime time.Duration // 任务限时
fronts map[int64]*Task // 任务前置任务
disableNotStartGetReward bool // 禁止未开始的任务领取奖励
offset *offset.Time // 任务偏移时间
Type string `json:"type,omitempty"` // 任务类型
Status Status `json:"status,omitempty"` // 任务状态
Cond Condition `json:"cond,omitempty"` // 任务条件
CondValue map[any]any `json:"cond_value,omitempty"` // 任务条件值
Counter int64 `json:"counter,omitempty"` // 任务要求计数器
CurrCount int64 `json:"curr_count,omitempty"` // 任务当前计数
CurrOverflow bool `json:"curr_overflow,omitempty"` // 任务当前计数是否允许溢出
Deadline time.Time `json:"deadline,omitempty"` // 任务截止时间
StartTime time.Time `json:"start_time,omitempty"` // 任务开始时间
LimitedDuration time.Duration `json:"limited_duration,omitempty"` // 任务限时
}
// GetID 获取任务ID
func (slf *Task) GetID() int64 {
return slf.id
// IsComplete 判断任务是否已完成
func (slf *Task) IsComplete() bool {
return slf.Status == StatusComplete
}
// GetType 获取任务类型
func (slf *Task) GetType() int {
return slf.taskType
// IsFailed 判断任务是否已失败
func (slf *Task) IsFailed() bool {
return slf.Status == StatusFailed
}
// Reset 重置任务
func (slf *Task) Reset() {
slf.count = 0
slf.state = StateAccept
for key := range slf.childCount {
delete(slf.childCount, key)
}
}
// GetFronts 获取前置任务
func (slf *Task) GetFronts() map[int64]*Task {
return slf.fronts
}
// GetFrontsWithState 获取特定状态的前置任务
func (slf *Task) GetFrontsWithState(state State) map[int64]*Task {
fronts := make(map[int64]*Task)
for id, front := range slf.fronts {
if front.GetState() == state {
fronts[id] = front
}
}
return fronts
}
// FrontsIsFinish 判断前置任务是否完成
func (slf *Task) FrontsIsFinish() bool {
for _, front := range slf.fronts {
state := front.GetState()
if state == StateAccept || state == StateFail {
return false
}
// IsReward 判断任务是否已领取奖励
func (slf *Task) IsReward() bool {
return slf.Status == StatusReward
}
// ReceiveReward 领取任务奖励,当任务状态为已完成时,才能领取奖励,此时返回 true并且任务状态变更为已领取奖励
func (slf *Task) ReceiveReward() bool {
if slf.Status != StatusComplete {
return false
}
slf.Status = StatusReward
return true
}
// GetReward 获取任务奖励
// - 当任务状态为 StateFinish 时,调用 rewardHandle 函数
// - 当任务状态不为 StateFinish 或奖励函数发生错误时,返回错误
func (slf *Task) GetReward(rewardHandle func() error) error {
if !slf.IsStart() {
return ErrTaskNotStart
// IncrementCounter 增加计数器的值,当 incr 为负数时,计数器的值不会发生变化
// - 如果需要溢出计数器,可通过 WithOverflowCounter 设置可溢出的任务计数器
func (slf *Task) IncrementCounter(incr int64) *Task {
if incr < 0 {
return slf
}
switch slf.GetState() {
case StateAccept:
return ErrTaskNotFinish
case StateReward:
return ErrTaskRewardReceived
case StateFail:
return ErrTaskFail
slf.CurrCount += incr
if !slf.CurrOverflow && slf.CurrCount > slf.Counter {
slf.CurrCount = slf.Counter
}
if err := rewardHandle(); err != nil {
return err
}
slf.state = StateReward
return nil
return slf.refreshTaskStatus()
}
// GetState 获取任务状态
func (slf *Task) GetState() State {
return slf.state
}
// IsStart 判断任务是否开始
func (slf *Task) IsStart() bool {
var current time.Time
if slf.offset != nil {
current = slf.offset.Now()
} else {
current = time.Now()
// DecrementCounter 减少计数器的值,当 decr 为负数时,计数器的值不会发生变化
func (slf *Task) DecrementCounter(decr int64) *Task {
if decr < 0 {
return slf
}
if current.Before(slf.start) {
return false
} else if slf.limitTime > 0 && current.Sub(slf.start) >= slf.limitTime {
return false
slf.CurrCount -= decr
if slf.CurrCount < 0 {
slf.CurrCount = 0
}
return true
return slf.refreshTaskStatus()
}
// SetCount 设置计数
func (slf *Task) SetCount(count int64) {
if !slf.IsStart() || !slf.FrontsIsFinish() {
return
// AssignConditionValueAndRefresh 分配条件值并刷新任务状态
func (slf *Task) AssignConditionValueAndRefresh(key, value any) *Task {
if slf.Cond == nil {
return slf
}
slf.count = count
if slf.count >= slf.condition {
slf.count = slf.condition
} else if slf.count < 0 {
slf.count = 0
if _, exist := slf.Cond[key]; !exist {
return slf
}
slf.refreshState()
}
// AddCount 增加计数
func (slf *Task) AddCount(count int64) {
slf.SetCount(slf.count + count)
}
// GetCount 获取计数
func (slf *Task) GetCount() int64 {
return slf.count
}
// GetCondition 获取计数条件
func (slf *Task) GetCondition() int64 {
return slf.condition
}
// SetChildCount 设置子计数
func (slf *Task) SetChildCount(key any, count int64) {
if !slf.IsStart() || !slf.FrontsIsFinish() || !hash.Exist(slf.childCondition, key) {
return
if slf.CondValue == nil {
slf.CondValue = make(map[any]any)
}
if condition := slf.childCondition[key]; count > condition {
count = condition
} else if count < 0 {
count = 0
}
slf.childCount[key] = count
slf.refreshState()
OnRefreshTaskCount(slf.taskType, count)
slf.CondValue[key] = value
return slf.refreshTaskStatus()
}
// AddChildCount 增加子计数
func (slf *Task) AddChildCount(key any, count int64) {
slf.SetChildCount(key, slf.childCount[key]+count)
OnRefreshTaskChildCount(slf.taskType, key, count)
// AssignConditionValueAndRefreshByCondition 分配条件值并刷新任务状态
func (slf *Task) AssignConditionValueAndRefreshByCondition(condition Condition) *Task {
if slf.Cond == nil {
return slf
}
if slf.CondValue == nil {
slf.CondValue = make(map[any]any)
}
for k, v := range condition {
if _, exist := slf.Cond[k]; !exist {
continue
}
slf.CondValue[k] = v
}
return slf.refreshTaskStatus()
}
// refreshState 刷新任务状态
func (slf *Task) refreshState() {
slf.state = StateFinish
if slf.count != slf.condition {
slf.state = StateAccept
return
// ResetStatus 重置任务状态
// - 该函数会将任务状态重置为已接受状态后,再刷新任务状态
// - 当任务条件变更,例如任务计数要求为 10已经完成的情况下将任务计数要求变更为 5 或 20此时任务状态由于是已完成或已领取状态不会自动刷新需要调用该函数刷新任务状态
func (slf *Task) ResetStatus() *Task {
slf.Status = StatusAccept
return slf.refreshTaskStatus()
}
// refreshTaskStatus 刷新任务状态
func (slf *Task) refreshTaskStatus() *Task {
curr := time.Now()
if (!slf.StartTime.IsZero() && curr.Before(slf.StartTime)) || (!slf.Deadline.IsZero() && curr.After(slf.Deadline)) || slf.Status >= StatusComplete {
return slf
}
for key, condition := range slf.childCondition {
if slf.childCount[key] != condition {
slf.state = StateAccept
return
slf.Status = StatusComplete
if slf.Counter > 0 && slf.CurrCount < slf.Counter {
slf.Status = StatusAccept
return slf
}
if slf.Cond != nil {
for k, v := range slf.Cond {
if v != slf.CondValue[k] {
slf.Status = StatusAccept
return slf
}
}
}
if !slf.Deadline.IsZero() && slf.Status == StatusAccept {
if slf.Deadline.After(curr) {
slf.Status = StatusFailed
return slf
}
}
if slf.LimitedDuration > 0 && slf.Status == StatusAccept {
if curr.Sub(slf.StartTime) > slf.LimitedDuration {
slf.Status = StatusFailed
return slf
}
}
return slf
}

48
game/task/task_test.go Normal file
View File

@ -0,0 +1,48 @@
package task
import (
"fmt"
"testing"
)
type Player struct {
tasks map[string][]*Task
}
type Monster struct {
}
func TestCond(t *testing.T) {
task := NewTask(WithType("T"), WithCounter(5), WithCondition(Cond("N", 5).Cond("M", 10)))
task.AssignConditionValueAndRefresh("N", 5)
task.AssignConditionValueAndRefresh("M", 10)
RegisterRefreshTaskCounterEvent[*Player](task.Type, func(taskType string, trigger *Player, count int64) {
fmt.Println("Player", count)
for _, t := range trigger.tasks[taskType] {
fmt.Println(t.CurrCount, t.IncrementCounter(count).Status)
}
})
RegisterRefreshTaskConditionEvent[*Player](task.Type, func(taskType string, trigger *Player, condition Condition) {
fmt.Println("Player", condition)
for _, t := range trigger.tasks[taskType] {
fmt.Println(t.CurrCount, t.AssignConditionValueAndRefresh("N", 5).Status)
}
})
RegisterRefreshTaskCounterEvent[*Monster](task.Type, func(taskType string, trigger *Monster, count int64) {
fmt.Println("Monster", count)
})
player := &Player{
tasks: map[string][]*Task{
task.Type: []*Task{task},
},
}
OnRefreshTaskCounterEvent(task.Type, player, 1)
OnRefreshTaskCounterEvent(task.Type, player, 2)
OnRefreshTaskCounterEvent(task.Type, player, 3)
OnRefreshTaskCounterEvent(task.Type, new(Monster), 3)
}

10
go.mod
View File

@ -11,7 +11,7 @@ require (
github.com/gorilla/websocket v1.5.0
github.com/json-iterator/go v1.1.12
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
github.com/nats-io/nats.go v1.30.2
github.com/nats-io/nats.go v1.31.0
github.com/panjf2000/ants/v2 v2.8.1
github.com/panjf2000/gnet v1.6.7
github.com/smartystreets/goconvey v1.8.1
@ -21,7 +21,7 @@ require (
github.com/xtaci/kcp-go/v5 v5.6.3
go.uber.org/atomic v1.10.0
go.uber.org/zap v1.25.0
golang.org/x/time v0.3.0
golang.org/x/time v0.4.0
google.golang.org/grpc v1.59.0
)
@ -49,8 +49,8 @@ require (
github.com/minio/highwayhash v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nats-io/jwt/v2 v2.5.2 // indirect
github.com/nats-io/nats-server/v2 v2.10.3 // indirect
github.com/nats-io/jwt/v2 v2.5.3 // indirect
github.com/nats-io/nats-server/v2 v2.10.4 // indirect
github.com/nats-io/nkeys v0.4.6 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
@ -72,7 +72,7 @@ require (
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect

9
go.sum
View File

@ -125,13 +125,18 @@ github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4=
github.com/nats-io/jwt/v2 v2.4.1/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI=
github.com/nats-io/jwt/v2 v2.5.2 h1:DhGH+nKt+wIkDxM6qnVSKjokq5t59AZV5HRcFW0zJwU=
github.com/nats-io/jwt/v2 v2.5.2/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI=
github.com/nats-io/jwt/v2 v2.5.3 h1:/9SWvzc6hTfamcgXJ3uYRpgj+QuY2aLNqRiqrKcrpEo=
github.com/nats-io/jwt/v2 v2.5.3/go.mod h1:iysuPemFcc7p4IoYots3IuELSI4EDe9Y0bQMe+I3Bf4=
github.com/nats-io/nats-server/v2 v2.9.16 h1:SuNe6AyCcVy0g5326wtyU8TdqYmcPqzTjhkHojAjprc=
github.com/nats-io/nats-server/v2 v2.9.16/go.mod h1:z1cc5Q+kqJkz9mLUdlcSsdYnId4pyImHjNgoh6zxSC0=
github.com/nats-io/nats-server/v2 v2.10.3 h1:nk2QVLpJUh3/AhZCJlQdTfj2oeLDvWnn1Z6XzGlNFm0=
github.com/nats-io/nats-server/v2 v2.10.3/go.mod h1:lzrskZ/4gyMAh+/66cCd+q74c6v7muBypzfWhP/MAaM=
github.com/nats-io/nats-server/v2 v2.10.4 h1:uB9xcwon3tPXWAdmTJqqqC6cie3yuPWHJjjTBgaPNus=
github.com/nats-io/nats-server/v2 v2.10.4/go.mod h1:eWm2JmHP9Lqm2oemB6/XGi0/GwsZwtWf8HIPUsh+9ns=
github.com/nats-io/nats.go v1.28.0 h1:Th4G6zdsz2d0OqXdfzKLClo6bOfoI/b1kInhRtFIy5c=
github.com/nats-io/nats.go v1.28.0/go.mod h1:XpbWUlOElGwTYbMR7imivs7jJj9GtK7ypv321Wp6pjc=
github.com/nats-io/nats.go v1.30.2/go.mod h1:dcfhUgmQNN4GJEfIb2f9R7Fow+gzBF4emzDHrVBd5qM=
github.com/nats-io/nats.go v1.31.0/go.mod h1:di3Bm5MLsoB4Bx61CBTsxuarI36WbhAwOm8QrW39+i8=
github.com/nats-io/nkeys v0.4.4 h1:xvBJ8d69TznjcQl9t6//Q5xXuVhyYiSos6RPtvQNTwA=
github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64=
github.com/nats-io/nkeys v0.4.6 h1:IzVe95ru2CT6ta874rt9saQRkWfe2nFj1NtvYSLqMzY=
@ -289,6 +294,8 @@ golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
@ -303,6 +310,8 @@ golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.4.0 h1:Z81tqI5ddIoXDPvVQ7/7CC9TnLM7ubaFG2qXYd5BbYY=
golang.org/x/time v0.4.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=

View File

@ -1,9 +1,12 @@
package server
import "github.com/gin-gonic/gin"
// NewHttpHandleWrapper 创建一个新的 http 处理程序包装器
// - 默认使用 server.HttpContext 作为上下文,如果需要依赖其作为新的上下文,可以通过 NewHttpContext 创建
func NewHttpHandleWrapper[Context any](srv *Server, packer ContextPacker[Context]) *Http[Context] {
return &Http[Context]{
gin: srv.ginServer,
HttpRouter: &HttpRouter[Context]{
srv: srv,
group: srv.ginServer,
@ -15,5 +18,10 @@ func NewHttpHandleWrapper[Context any](srv *Server, packer ContextPacker[Context
// Http 基于 gin.Engine 包装的 http 服务器
type Http[Context any] struct {
srv *Server
gin *gin.Engine
*HttpRouter[Context]
}
func (slf *Http[Context]) Gin() *gin.Engine {
return slf.gin
}

View File

@ -87,6 +87,13 @@ func (slf *Lockstep[ClientID, Command]) JoinClientWithFrame(client Client[Client
}
// GetClientCount 获取客户端数量
func (slf *Lockstep[ClientID, Command]) GetClientCount() int {
slf.clientLock.RLock()
defer slf.clientLock.RUnlock()
return len(slf.clients)
}
// DropCache 丢弃特定帧的缓存,当 handler 返回 true 时将丢弃缓存
func (slf *Lockstep[ClientID, Command]) DropCache(handler func(frame int64) bool) {
slf.frameCacheLock.Lock()

View File

@ -748,3 +748,8 @@ func (slf *Server) dispatchMessage(msg *Message) {
log.Warn("Server", log.String("not support message type", msg.t.String()))
}
}
// PushAsyncMessage 是 PushAsyncMessage 的快捷方式
func (slf *Server) PushAsyncMessage(caller func() error, callback func(err error), mark ...any) {
PushAsyncMessage(slf, caller, callback, mark...)
}

View File

@ -34,3 +34,8 @@ type Unsigned interface {
type Float interface {
~float32 | ~float64
}
// Basic 基本类型
type Basic interface {
Signed | Unsigned | Float | ~string | ~bool | ~byte
}

View File

@ -1,13 +1,14 @@
package survey
import (
"github.com/tidwall/gjson"
"strings"
"sync"
)
// Analyzer 分析器
type Analyzer struct {
v map[string]float64 // 记录了每个 key 的当前值
v map[string]any // 记录了每个 key 的当前值
vc map[string]int64 // 记录了每个 key 生效的计数数量
repeat map[string]struct{} // 去重信息
subs map[string]*Analyzer
@ -29,7 +30,106 @@ func (slf *Analyzer) Sub(key string) *Analyzer {
return sub
}
// SetValueIfGreaterThan 设置指定 key 的值,当新值大于旧值时
// - 当已有值不为 float64 时,将会被忽略
func (slf *Analyzer) SetValueIfGreaterThan(key string, value float64) {
slf.m.Lock()
defer slf.m.Unlock()
if slf.v == nil {
slf.v = make(map[string]any)
slf.vc = make(map[string]int64)
}
v, exist := slf.v[key]
if !exist {
slf.v[key] = value
slf.vc[key]++
return
}
switch v := v.(type) {
case float64:
if v < value {
slf.v[key] = value
slf.vc[key]++
}
}
}
// SetValueIfLessThan 设置指定 key 的值,当新值小于旧值时
// - 当已有值不为 float64 时,将会被忽略
func (slf *Analyzer) SetValueIfLessThan(key string, value float64) {
slf.m.Lock()
defer slf.m.Unlock()
if slf.v == nil {
slf.v = make(map[string]any)
slf.vc = make(map[string]int64)
}
v, exist := slf.v[key]
if !exist {
slf.v[key] = value
slf.vc[key]++
return
}
switch v := v.(type) {
case float64:
if v > value {
slf.v[key] = value
slf.vc[key]++
}
}
}
// SetValueIf 当表达式满足的时候将设置指定 key 的值为 value
func (slf *Analyzer) SetValueIf(key string, expression bool, value float64) {
if !expression {
return
}
slf.m.Lock()
defer slf.m.Unlock()
slf.v[key] = value
slf.vc[key]++
}
// SetValueStringIf 当表达式满足的时候将设置指定 key 的值为 value
func (slf *Analyzer) SetValueStringIf(key string, expression bool, value string) {
if !expression {
return
}
slf.m.Lock()
defer slf.m.Unlock()
slf.v[key] = value
slf.vc[key]++
}
// SetValue 设置指定 key 的值
func (slf *Analyzer) SetValue(key string, value float64) {
slf.m.Lock()
defer slf.m.Unlock()
if slf.v == nil {
slf.v = make(map[string]any)
slf.vc = make(map[string]int64)
}
slf.v[key] = value
slf.vc[key]++
}
// SetValueString 设置指定 key 的值
func (slf *Analyzer) SetValueString(key string, value string) {
slf.m.Lock()
defer slf.m.Unlock()
if slf.v == nil {
slf.v = make(map[string]any)
slf.vc = make(map[string]int64)
}
slf.v[key] = value
slf.vc[key]++
}
// Increase 在指定 key 现有值的基础上增加 recordKey 的值
// - 当分析器已经记录过相同 key 的值时,会根据已有的值类型进行不同处理
//
// 处理方式:
// - 当已有值类型为 string 时,将会使用新的值的 string 类型进行覆盖
// - 当已有值类型为 float64 时,当新的值类型不为 float64 时,将会被忽略
func (slf *Analyzer) Increase(key string, record R, recordKey string) {
slf.m.Lock()
defer slf.m.Unlock()
@ -37,10 +137,31 @@ func (slf *Analyzer) Increase(key string, record R, recordKey string) {
return
}
if slf.v == nil {
slf.v = make(map[string]float64)
slf.v = make(map[string]any)
slf.vc = make(map[string]int64)
}
slf.v[key] += record.GetFloat64(recordKey)
value, exist := slf.v[key]
if !exist {
result := gjson.Get(string(record), recordKey)
switch result.Type {
case gjson.String:
slf.v[key] = result.String()
case gjson.Number:
slf.v[key] = result.Float()
default:
return
}
slf.vc[key]++
return
}
switch v := value.(type) {
case string:
slf.v[key] = record.GetString(recordKey)
case float64:
slf.v[key] = v + record.GetFloat64(recordKey)
default:
return
}
slf.vc[key]++
}
@ -49,11 +170,20 @@ func (slf *Analyzer) IncreaseValue(key string, value float64) {
slf.m.Lock()
defer slf.m.Unlock()
if slf.v == nil {
slf.v = make(map[string]float64)
slf.v = make(map[string]any)
slf.vc = make(map[string]int64)
}
slf.v[key] += value
slf.vc[key]++
v, exist := slf.v[key]
if !exist {
slf.v[key] = value
slf.vc[key]++
return
}
switch v := v.(type) {
case float64:
slf.v[key] = v + value
slf.vc[key]++
}
}
// IncreaseNonRepeat 在指定 key 现有值的基础上增加 recordKey 的值,但是当去重维度 dimension 相同时,不会增加
@ -99,3 +229,33 @@ func (slf *Analyzer) IncreaseValueNonRepeat(key string, record R, value float64,
slf.m.Unlock()
slf.IncreaseValue(key, value)
}
// GetValue 获取当前记录的值
func (slf *Analyzer) GetValue(key string) float64 {
slf.m.Lock()
defer slf.m.Unlock()
value, exist := slf.v[key]
if !exist {
return 0
}
switch v := value.(type) {
case float64:
return v
}
return 0
}
// GetValueString 获取当前记录的值
func (slf *Analyzer) GetValueString(key string) string {
slf.m.Lock()
defer slf.m.Unlock()
value, exist := slf.v[key]
if !exist {
return ""
}
switch v := value.(type) {
case string:
return v
}
return ""
}

View File

@ -2,6 +2,7 @@ package survey
import (
"github.com/kercylan98/minotaur/utils/super"
"strings"
)
func newReport(analyzer *Analyzer) *Report {
@ -23,15 +24,23 @@ func newReport(analyzer *Analyzer) *Report {
// Report 分析报告
type Report struct {
analyzer *Analyzer
Name string // 报告名称(默认为 ROOT
Values map[string]float64 `json:"Values,omitempty"`
Counter map[string]int64 `json:"Count,omitempty"`
Subs []*Report `json:"Reports,omitempty"`
Name string // 报告名称(默认为 ROOT
Values map[string]any `json:"Values,omitempty"`
Counter map[string]int64 `json:"Count,omitempty"`
Subs []*Report `json:"Reports,omitempty"`
}
// Avg 计算平均值
func (slf *Report) Avg(key string) float64 {
return slf.Values[key] / float64(slf.Counter[key])
value, exist := slf.Values[key]
if !exist {
return 0
}
valF, ok := value.(float64)
if !ok {
return 0
}
return valF / float64(slf.Counter[key])
}
// Count 获取特定 key 的计数次数
@ -43,7 +52,15 @@ func (slf *Report) Count(key string) int64 {
func (slf *Report) Sum(keys ...string) float64 {
var sum float64
for _, key := range keys {
sum += slf.Values[key]
value, exist := slf.Values[key]
if !exist {
continue
}
valF, ok := value.(float64)
if !ok {
continue
}
sum += valF
}
return sum
}
@ -58,6 +75,19 @@ func (slf *Report) Sub(name string) *Report {
return nil
}
// ReserveSubByPrefix 仅保留特定前缀的子报告
func (slf *Report) ReserveSubByPrefix(prefix string) *Report {
report := newReport(slf.analyzer)
var newSub []*Report
for _, sub := range slf.Subs {
if strings.HasPrefix(sub.Name, prefix) {
newSub = append(newSub, sub)
}
}
report.Subs = newSub
return report
}
// ReserveSub 仅保留特定名称子报告
func (slf *Report) ReserveSub(names ...string) *Report {
report := newReport(slf.analyzer)
@ -78,7 +108,7 @@ func (slf *Report) ReserveSub(names ...string) *Report {
return report
}
// FilterSub 过滤特定名称的子报告
// FilterSub 将特定名称的子报告过滤掉
func (slf *Report) FilterSub(names ...string) *Report {
report := newReport(slf.analyzer)
var newSub []*Report

View File

@ -1,6 +1,11 @@
package super
import "time"
import (
"fmt"
"math"
"math/rand"
"time"
)
// Retry 根据提供的 count 次数尝试执行 f 函数,如果 f 函数返回错误,则在 interval 后重试,直到成功或者达到 count 次数
func Retry(count int, interval time.Duration, f func() error) error {
@ -14,6 +19,58 @@ func Retry(count int, interval time.Duration, f func() error) error {
return err
}
// RetryByRule 根据提供的规则尝试执行 f 函数,如果 f 函数返回错误,则根据 rule 的返回值进行重试
// - rule 将包含一个入参,表示第几次重试,返回值表示下一次重试的时间间隔,当返回值为 0 时,表示不再重试
// - rule 的 count 将在 f 首次失败后变为 1因此 rule 的入参将从 1 开始
func RetryByRule(f func() error, rule func(count int) time.Duration) error {
var count int
var err error
for {
if err = f(); err != nil {
count++
next := rule(count)
if next <= 0 {
break
}
time.Sleep(next)
} else {
break
}
}
return err
}
// RetryByExponentialBackoff 根据指数退避算法尝试执行 f 函数
// - maxRetries最大重试次数
// - baseDelay基础延迟
// - maxDelay最大延迟
// - multiplier延迟时间的乘数通常为 2
// - randomization延迟时间的随机化因子通常为 0.5
func RetryByExponentialBackoff(f func() error, maxRetries int, baseDelay, maxDelay time.Duration, multiplier, randomization float64) error {
retry := 0
for {
err := f()
if err == nil {
return nil
}
if retry >= maxRetries {
return fmt.Errorf("max retries reached: %v", err)
}
delay := float64(baseDelay) * math.Pow(multiplier, float64(retry))
jitter := (rand.Float64() - 0.5) * randomization * float64(baseDelay)
sleepDuration := time.Duration(delay + jitter)
if sleepDuration > maxDelay {
sleepDuration = maxDelay
}
time.Sleep(sleepDuration)
retry++
}
}
// RetryAsync 与 Retry 类似,但是是异步执行
// - 传入的 callback 函数会在执行完毕后被调用,如果执行成功,则 err 为 nil否则为错误信息
// - 如果 callback 为 nil则不会在执行完毕后被调用