diff --git a/README.md b/README.md index 4cdb30d..b15afec 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,11 @@ func main() { ### 分流服务器 分流服务器可以将消息分流到不同的分组上,每个分组中为串行处理,不同分组之间并行处理。 + +> 关于分流服务器的思考: +> - 当游戏需要以房间的形式进行时,应该确保相同房间的玩家处于同一分流中,不同房间的玩家处于不同分流中,这样可以避免不同房间的玩家之间的消息互相阻塞; +> - 这时候网络 IO 应该根据不同的游戏类型而进行不同的处理,例如回合制可以同步执行,而实时游戏应该采用异步执行; +> - 当游戏大部分时候以单人游戏进行时,应该每个玩家处于自身唯一的分流中,此时非互动的消息造成的网络 IO 采用同步执行即可,也不会阻塞到其他玩家的消息处理; ```go package main diff --git a/planner/pce/exporter/exporter.exe b/planner/pce/exporter/exporter.exe index 4093dbd..83626fa 100644 Binary files a/planner/pce/exporter/exporter.exe and b/planner/pce/exporter/exporter.exe differ diff --git a/planner/pce/tmpls/golang.go b/planner/pce/tmpls/golang.go index 4144bd6..2b225a5 100644 --- a/planner/pce/tmpls/golang.go +++ b/planner/pce/tmpls/golang.go @@ -48,13 +48,13 @@ func (slf *Golang) Render(templates ...*pce.TmplStruct) (string, error) { {{.Name}}Sign, {{- end}} } - mutex sync.Mutex + mutex sync.RWMutex ) var ( {{- range .Templates}} {{- if $.HasIndex .}} - {{.Name}} {{$.GetVariable .}} // {{.Desc}} + c{{.Name}} {{$.GetVariable .}} // {{.Desc}} {{- end}} {{- end}} ) @@ -62,7 +62,7 @@ func (slf *Golang) Render(templates ...*pce.TmplStruct) (string, error) { var ( {{- range .Templates}} {{- if $.HasIndex .}}{{- else}} - {{.Name}} *{{$.GetConfigName .}} // {{.Desc}} + c{{.Name}} *{{$.GetConfigName .}} // {{.Desc}} {{- end}} {{- end}} ) @@ -169,7 +169,7 @@ func (slf *Golang) Render(templates ...*pce.TmplStruct) (string, error) { {{- else}} temp := new({{$.GetConfigName .}}) {{- end}} - if err := json.Unmarshal(data, &{{.Name}}); err != nil { + if err := json.Unmarshal(data, &c{{.Name}}); err != nil { log.Error("Config", log.String("Name", "{{.Name}}"), log.Bool("Invalid", true), log.Err(err)) return } @@ -185,8 +185,8 @@ func (slf *Golang) Render(templates ...*pce.TmplStruct) (string, error) { cs := make(map[Sign]any) {{- range .Templates}} - {{.Name}} = _{{.Name}} - cs[{{.Name}}Sign] = {{.Name}} + c{{.Name}} = _{{.Name}} + cs[{{.Name}}Sign] = c{{.Name}} {{- end}} configs.Store(&cs) @@ -194,8 +194,8 @@ func (slf *Golang) Render(templates ...*pce.TmplStruct) (string, error) { // GetConfigs 获取所有配置 func GetConfigs() map[Sign]any { - mutex.Lock() - defer mutex.Unlock() + mutex.RLock() + defer mutex.RUnlock() return hash.Copy(*configs.Load()) } @@ -210,6 +210,25 @@ func (slf *Golang) Render(templates ...*pce.TmplStruct) (string, error) { defer mutex.Unlock() handle(hash.Copy(*configs.Load())) } + + {{- range .Templates}} + {{- if $.HasIndex .}} + // {{.Name}} 获取{{.Desc}} + func {{.Name}}() {{$.GetVariable .}} { + mutex.RLock() + defer mutex.RUnlock() + return c{{.Name}} + } + {{- else}} + // {{.Name}} 获取{{.Desc}} + func {{.Name}}() *{{$.GetConfigName .}} { + mutex.RLock() + defer mutex.RUnlock() + return c{{.Name}} + } + {{- end}} + {{- end}} + `, slf) } diff --git a/server/event.go b/server/event.go index 6a0e6f8..5d5cfdd 100644 --- a/server/event.go +++ b/server/event.go @@ -31,6 +31,7 @@ type ShuntChannelClosedEventHandler func(srv *Server, guid int64) type ConnectionPacketPreprocessEventHandler func(srv *Server, conn *Conn, packet []byte, abort func(), usePacket func(newPacket []byte)) type MessageExecBeforeEventHandler func(srv *Server, message *Message) bool type MessageReadyEventHandler func(srv *Server) +type OnDeadlockDetectEventHandler func(srv *Server, message *Message) func newEvent(srv *Server) *event { return &event{ @@ -50,6 +51,7 @@ func newEvent(srv *Server) *event { connectionPacketPreprocessEventHandlers: slice.NewPriority[ConnectionPacketPreprocessEventHandler](), messageExecBeforeEventHandlers: slice.NewPriority[MessageExecBeforeEventHandler](), messageReadyEventHandlers: slice.NewPriority[MessageReadyEventHandler](), + dedeadlockDetectEventHandlers: slice.NewPriority[OnDeadlockDetectEventHandler](), } } @@ -70,6 +72,7 @@ type event struct { connectionPacketPreprocessEventHandlers *slice.Priority[ConnectionPacketPreprocessEventHandler] messageExecBeforeEventHandlers *slice.Priority[MessageExecBeforeEventHandler] messageReadyEventHandlers *slice.Priority[MessageReadyEventHandler] + dedeadlockDetectEventHandlers *slice.Priority[OnDeadlockDetectEventHandler] consoleCommandEventHandlers map[string]*slice.Priority[ConsoleCommandEventHandler] consoleCommandEventHandlerInitOnce sync.Once @@ -435,6 +438,27 @@ func (slf *event) OnMessageReadyEvent() { }) } +// RegDeadlockDetectEvent 在死锁检测触发时立即执行被注册的事件处理函数 +func (slf *event) RegDeadlockDetectEvent(handler OnDeadlockDetectEventHandler, priority ...int) { + slf.dedeadlockDetectEventHandlers.Append(handler, slice.GetValue(priority, 0)) +} + +func (slf *event) OnDeadlockDetectEvent(message *Message) { + if slf.dedeadlockDetectEventHandlers.Len() == 0 { + return + } + defer func() { + if err := recover(); err != nil { + log.Error("Server", log.String("OnDeadlockDetectEvent", fmt.Sprintf("%v", err))) + debug.PrintStack() + } + }() + slf.dedeadlockDetectEventHandlers.RangeValue(func(index int, value OnDeadlockDetectEventHandler) bool { + value(slf.Server, message) + return true + }) +} + func (slf *event) check() { switch slf.network { case NetworkHttp, NetworkGRPC, NetworkNone: diff --git a/server/server.go b/server/server.go index 47fb883..fe346fc 100644 --- a/server/server.go +++ b/server/server.go @@ -695,6 +695,7 @@ func (slf *Server) dispatchMessage(dispatcher *dispatcher, msg *Message) { case <-ctx.Done(): if err := ctx.Err(); err == context.DeadlineExceeded { log.Warn("Server", log.String("MessageType", messageNames[msg.t]), log.String("Info", msg.String()), log.Any("SuspectedDeadlock", msg)) + slf.OnDeadlockDetectEvent(msg) } } }(ctx, msg) diff --git a/utils/file/file.go b/utils/file/file.go index 879ccb4..81603f5 100644 --- a/utils/file/file.go +++ b/utils/file/file.go @@ -181,12 +181,12 @@ func ReadLineWithParallel(filename string, chunkSize int64, handlerFunc func(str defer wg.Done() endMutex.Lock() - e := chunk[1] - chunk[0] + e := chunk[1] if e > end { end = e + 1 } endMutex.Unlock() - r := io.NewSectionReader(file, chunk[0], e) + r := io.NewSectionReader(file, chunk[0], e-chunk[0]) scanner := bufio.NewScanner(r) for scanner.Scan() { diff --git a/utils/log/encoder.go b/utils/log/encoder.go index 218cc49..0ebcca5 100644 --- a/utils/log/encoder.go +++ b/utils/log/encoder.go @@ -12,8 +12,10 @@ type Encoder struct { conf *Config } -func (slf *Encoder) Split(config *lumberjack.Logger) *Encoder { - slf.cores = append(slf.cores, zapcore.NewCore(slf.e, zapcore.AddSync(config), zapcore.DebugLevel)) +func (slf *Encoder) Split(config *lumberjack.Logger, level LevelEnabler) *Encoder { + slf.cores = append(slf.cores, zapcore.NewCore(slf.e, + zapcore.NewMultiWriteSyncer(zapcore.AddSync(config)), + level)) return slf } diff --git a/utils/maths/math.go b/utils/maths/math.go index 441f23c..6166c87 100644 --- a/utils/maths/math.go +++ b/utils/maths/math.go @@ -204,3 +204,14 @@ func IsOdd[V generic.Integer](n V) bool { func IsEven[V generic.Integer](n V) bool { return 0 == (int64(n) & 1) } + +// MakeLastDigitsZero 返回一个新的数,其中 num 的最后 digits 位数被设为零。 +// - 函数首先创建一个 10 的 digits 次方的遮罩,然后通过整除和乘以这个遮罩来使 num 的最后 digits 位归零。 +// - 当 T 类型为浮点型时,将被向下取整后再进行转换 +func MakeLastDigitsZero[T generic.Number](num T, digits int) T { + var mask int64 = 1 + for i := 0; i < digits; i++ { + mask *= 10 + } + return T(int64(num) / mask * mask) +} diff --git a/utils/maths/math_test.go b/utils/maths/math_test.go new file mode 100644 index 0000000..1e2893d --- /dev/null +++ b/utils/maths/math_test.go @@ -0,0 +1,14 @@ +package maths_test + +import ( + "github.com/kercylan98/minotaur/utils/maths" + "github.com/kercylan98/minotaur/utils/random" + "testing" +) + +func TestMakeLastDigitsZero(t *testing.T) { + for i := 0; i < 20; i++ { + n := float64(random.Int64(100, 999999)) + t.Log(n, 3, maths.MakeLastDigitsZero(n, 3)) + } +} diff --git a/utils/random/number.go b/utils/random/number.go index fd9c54b..2a42e4a 100644 --- a/utils/random/number.go +++ b/utils/random/number.go @@ -7,16 +7,25 @@ import ( // Int64 返回一个介于min和max之间的int64类型的随机数。 func Int64(min int64, max int64) int64 { + if min == max { + return min + } return min + rand.Int63n(max+1-min) } // Int 返回一个介于min和max之间的的int类型的随机数。 func Int(min int, max int) int { + if min == max { + return min + } return int(Int64(int64(min), int64(max))) } // Duration 返回一个介于min和max之间的的Duration类型的随机数。 func Duration(min int64, max int64) time.Duration { + if min == max { + return time.Duration(min) + } return time.Duration(Int64(min, max)) } diff --git a/utils/super/number.go b/utils/super/number.go index 8bbd565..d719206 100644 --- a/utils/super/number.go +++ b/utils/super/number.go @@ -2,8 +2,20 @@ package super import "reflect" +var ( + romeThousands = []string{"", "M", "MM", "MMM"} + romeHundreds = []string{"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"} + romeTens = []string{"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"} + romeOnes = []string{"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"} +) + // IsNumber 判断是否为数字 func IsNumber(v any) bool { kind := reflect.Indirect(reflect.ValueOf(v)).Kind() return kind >= reflect.Int && kind <= reflect.Float64 } + +// NumberToRome 将数字转换为罗马数字 +func NumberToRome(num int) string { + return romeThousands[num/1000] + romeHundreds[num%1000/100] + romeTens[num%100/10] + romeOnes[num%10] +} diff --git a/utils/super/number_test.go b/utils/super/number_test.go new file mode 100644 index 0000000..f31a76d --- /dev/null +++ b/utils/super/number_test.go @@ -0,0 +1,28 @@ +package super_test + +import ( + "github.com/kercylan98/minotaur/utils/super" + "testing" +) + +func TestNumberToRome(t *testing.T) { + tests := []struct { + input int + output string + }{ + {input: 1, output: "I"}, + {input: 5, output: "V"}, + {input: 10, output: "X"}, + {input: 50, output: "L"}, + {input: 100, output: "C"}, + {input: 500, output: "D"}, + {input: 1000, output: "M"}, + } + + for _, test := range tests { + result := super.NumberToRome(test.input) + if result != test.output { + t.Errorf("NumberToRome(%d) = %s; want %s", test.input, result, test.output) + } + } +} diff --git a/utils/super/version_benchmark_test.go b/utils/super/version_benchmark_test.go new file mode 100644 index 0000000..0968b3a --- /dev/null +++ b/utils/super/version_benchmark_test.go @@ -0,0 +1,18 @@ +package super_test + +import ( + "github.com/kercylan98/minotaur/utils/super" + "testing" +) + +func BenchmarkCompareVersion(b *testing.B) { + for i := 0; i < b.N; i++ { + super.CompareVersion("vfe2faf.d2ad5.dd3", "afe2faf.d2ad5.dd2") + } +} + +func BenchmarkOldVersion(b *testing.B) { + for i := 0; i < b.N; i++ { + super.OldVersion("vfe2faf.d2ad5.dd3", "vda2faf.d2ad5.dd2") + } +} diff --git a/utils/super/version_example_test.go b/utils/super/version_example_test.go new file mode 100644 index 0000000..05f4d64 --- /dev/null +++ b/utils/super/version_example_test.go @@ -0,0 +1,18 @@ +package super_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/super" +) + +func ExampleCompareVersion() { + result := super.CompareVersion("1.2.3", "1.2.2") + fmt.Println(result) + // Output: 1 +} + +func ExampleOldVersion() { + result := super.OldVersion("1.2.3", "1.2.2") + fmt.Println(result) + // Output: true +} diff --git a/utils/super/version_test.go b/utils/super/version_test.go index 326acf1..cc3be0a 100644 --- a/utils/super/version_test.go +++ b/utils/super/version_test.go @@ -5,9 +5,56 @@ import ( "testing" ) -func TestCompareVersion(t *testing.T) { - t.Log(super.CompareVersion("1", "2"), -1) - t.Log(super.CompareVersion("1", "vv2"), -1) - t.Log(super.CompareVersion("1", "vv2.3.1"), -1) - t.Log(super.CompareVersion("11", "vv2.3.1"), 1) +func TestOldVersion(t *testing.T) { + testCases := []struct { + version1 string + version2 string + want bool + }{ + {"1.2.3", "1.2.2", true}, + {"1.2.1", "1.2.2", false}, + {"1.2.3", "1.2.3", false}, + {"v1.2.3", "v1.2.2", true}, + {"v1.2.3", "v1.2.4", false}, + {"v1.2.3", "1.2.3", false}, + {"vxx2faf.d2ad5.dd3", "gga2faf.d2ad5.dd2", true}, + {"awd2faf.d2ad4.dd3", "vsd2faf.d2ad5.dd3", false}, + {"vxd2faf.d2ad5.dd3", "qdq2faf.d2ad5.dd3", false}, + {"1.2.3", "vdafe2faf.d2ad5.dd3", false}, + {"v1.2.3", "vdafe2faf.d2ad5.dd3", false}, + } + + for _, tc := range testCases { + got := super.OldVersion(tc.version1, tc.version2) + if got != tc.want { + t.Errorf("OldVersion(%q, %q) = %v; want %v", tc.version1, tc.version2, got, tc.want) + } + } +} + +func TestCompareVersion(t *testing.T) { + testCases := []struct { + version1 string + version2 string + want int + }{ + {"1.2.3", "1.2.2", 1}, + {"1.2.1", "1.2.2", -1}, + {"1.2.3", "1.2.3", 0}, + {"v1.2.3", "v1.2.2", 1}, + {"v1.2.3", "v1.2.4", -1}, + {"v1.2.3", "1.2.3", 0}, + {"vde2faf.d2ad5.dd3", "e2faf.d2ad5.dd2", 1}, + {"vde2faf.d2ad4.dd3", "vde2faf.d2ad5.dd3", -1}, + {"vfe2faf.d2ad5.dd3", "ve2faf.d2ad5.dd3", 0}, + {"1.2.3", "vdafe2faf.d2ad5.dd3", -1}, + {"v1.2.3", "vdafe2faf.d2ad5.dd3", -1}, + } + + for _, tc := range testCases { + got := super.CompareVersion(tc.version1, tc.version2) + if got != tc.want { + t.Errorf("CompareVersion(%q, %q) = %v; want %v", tc.version1, tc.version2, got, tc.want) + } + } }