From 589a424491dc5b150bed49bc33d1849030dec373 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Wed, 1 Nov 2023 18:13:50 +0800 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20lockstep=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E5=B8=A7=E5=90=8C=E6=AD=A5=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF=E6=95=B0=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/lockstep/lockstep.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/lockstep/lockstep.go b/server/lockstep/lockstep.go index ca8e054..7218876 100644 --- a/server/lockstep/lockstep.go +++ b/server/lockstep/lockstep.go @@ -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() From 85176f32f918b3ad02ace7fa6217b55b6d457d0e Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 6 Nov 2023 20:26:12 +0800 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20survey=20=E5=8C=85=E7=9A=84=20Analy?= =?UTF-8?q?zer=20=E5=88=86=E6=9E=90=E5=99=A8=E5=A2=9E=E5=8A=A0=E5=A4=A7?= =?UTF-8?q?=E9=87=8F=E8=BE=85=E5=8A=A9=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/log/survey/analyzer.go | 172 +++++++++++++++++++++++++++++++++-- utils/log/survey/report.go | 44 +++++++-- 2 files changed, 203 insertions(+), 13 deletions(-) diff --git a/utils/log/survey/analyzer.go b/utils/log/survey/analyzer.go index 7cdf9ae..c626820 100644 --- a/utils/log/survey/analyzer.go +++ b/utils/log/survey/analyzer.go @@ -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 "" +} diff --git a/utils/log/survey/report.go b/utils/log/survey/report.go index 111e6ab..3cfac94 100644 --- a/utils/log/survey/report.go +++ b/utils/log/survey/report.go @@ -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 From 6b2a753e67f3605db2cdeb0760fbf30db937037b Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 6 Nov 2023 20:26:37 +0800 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20server=20=E5=8C=85=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E8=8E=B7=E5=8F=96=E5=88=B0=20HTTP=20=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=99=A8=E7=9A=84=20Gin=20=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/http.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/http.go b/server/http.go index 38ba4ee..7ef78e0 100644 --- a/server/http.go +++ b/server/http.go @@ -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 +} From d191dabfd3b23c5da171d24d49b69ee646123cf0 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 6 Nov 2023 20:27:08 +0800 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20super=20=E5=8C=85=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=A7=84=E5=88=99=E9=87=8D=E8=AF=95=E5=8F=8A=E9=80=80=E9=81=BF?= =?UTF-8?q?=E6=8C=87=E6=95=B0=E9=87=8D=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/super/retry.go | 59 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/utils/super/retry.go b/utils/super/retry.go index db66b32..9e3f835 100644 --- a/utils/super/retry.go +++ b/utils/super/retry.go @@ -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,则不会在执行完毕后被调用 From 0b77cc9907210ff527d570527071e57cc1804d3c Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 6 Nov 2023 20:27:50 +0800 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20server.Server=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=20PushAsyncMessage=20=E5=BF=AB=E6=8D=B7?= =?UTF-8?q?=E5=8F=91=E5=B8=83=E5=BC=82=E6=AD=A5=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/server.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/server.go b/server/server.go index 0f251d1..88d7acc 100644 --- a/server/server.go +++ b/server/server.go @@ -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...) +} From d405cae73f527e636f544fffb6e8f9b16965d2ce Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Tue, 7 Nov 2023 10:56:07 +0800 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20generic=20=E5=8C=85=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20Basic=20=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/generic/type.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/utils/generic/type.go b/utils/generic/type.go index 320b31e..cbcd1c8 100644 --- a/utils/generic/type.go +++ b/utils/generic/type.go @@ -34,3 +34,8 @@ type Unsigned interface { type Float interface { ~float32 | ~float64 } + +// Basic 基本类型 +type Basic interface { + Signed | Unsigned | Float | ~string | ~bool | ~byte +} From 2079e9595e1782b28415b36e92c6be7d3dfa1f1c Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Wed, 8 Nov 2023 09:44:02 +0800 Subject: [PATCH 7/8] other: xkeys seal encryption used fixed key for all encryption #6 --- go.mod | 10 +++++----- go.sum | 9 +++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 7c8150e..3dc5cb1 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index b4ae8d9..bd5b1ea 100644 --- a/go.sum +++ b/go.sum @@ -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= From 98c1f39ce6a40abb4097842c545216dc180342a4 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Thu, 9 Nov 2023 12:08:27 +0800 Subject: [PATCH 8/8] =?UTF-8?q?refactor:=20#60=20=E9=87=8D=E6=9E=84=20game?= =?UTF-8?q?/task=20=E5=8C=85=EF=BC=8C=E6=94=AF=E6=8C=81=E6=9B=B4=E7=81=B5?= =?UTF-8?q?=E6=B4=BB=E7=9A=84=E4=BB=BB=E5=8A=A1=E9=85=8D=E7=BD=AE=E6=96=B9?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/task/condition.go | 142 +++++++++++++++++++++ game/task/errors.go | 14 --- game/task/events.go | 78 +++++++++--- game/task/options.go | 139 ++++++++++----------- game/task/state.go | 10 -- game/task/status.go | 23 ++++ game/task/task.go | 277 +++++++++++++++++------------------------ game/task/task_test.go | 48 +++++++ 8 files changed, 452 insertions(+), 279 deletions(-) create mode 100644 game/task/condition.go delete mode 100644 game/task/errors.go delete mode 100644 game/task/state.go create mode 100644 game/task/status.go create mode 100644 game/task/task_test.go diff --git a/game/task/condition.go b/game/task/condition.go new file mode 100644 index 0000000..c3c5d5f --- /dev/null +++ b/game/task/condition.go @@ -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] +} diff --git a/game/task/errors.go b/game/task/errors.go deleted file mode 100644 index a0c6230..0000000 --- a/game/task/errors.go +++ /dev/null @@ -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") -) diff --git a/game/task/events.go b/game/task/events.go index c3925ff..b266fcb 100644 --- a/game/task/events.go +++ b/game/task/events.go @@ -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) + } } } diff --git a/game/task/options.go b/game/task/options.go index 2182ae2..d50d5a9 100644 --- a/game/task/options.go +++ b/game/task/options.go @@ -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 + } +} diff --git a/game/task/state.go b/game/task/state.go deleted file mode 100644 index 017126f..0000000 --- a/game/task/state.go +++ /dev/null @@ -1,10 +0,0 @@ -package task - -const ( - StateAccept State = iota + 1 // 已接受 - StateFinish // 已完成 - StateReward // 已领取 - StateFail // 已失败 -) - -type State uint16 diff --git a/game/task/status.go b/game/task/status.go new file mode 100644 index 0000000..0de8a8c --- /dev/null +++ b/game/task/status.go @@ -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] +} diff --git a/game/task/task.go b/game/task/task.go index a0fbf55..101330d 100644 --- a/game/task/task.go +++ b/game/task/task.go @@ -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 } diff --git a/game/task/task_test.go b/game/task/task_test.go new file mode 100644 index 0000000..55f3c4e --- /dev/null +++ b/game/task/task_test.go @@ -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) + +}