Merge branch 'develop'

This commit is contained in:
kercylan98 2023-12-11 12:01:16 +08:00
commit ea9e261d82
15 changed files with 225 additions and 17 deletions

View File

@ -91,6 +91,11 @@ func main() {
### 分流服务器
分流服务器可以将消息分流到不同的分组上,每个分组中为串行处理,不同分组之间并行处理。
> 关于分流服务器的思考:
> - 当游戏需要以房间的形式进行时,应该确保相同房间的玩家处于同一分流中,不同房间的玩家处于不同分流中,这样可以避免不同房间的玩家之间的消息互相阻塞;
> - 这时候网络 IO 应该根据不同的游戏类型而进行不同的处理,例如回合制可以同步执行,而实时游戏应该采用异步执行;
> - 当游戏大部分时候以单人游戏进行时,应该每个玩家处于自身唯一的分流中,此时非互动的消息造成的网络 IO 采用同步执行即可,也不会阻塞到其他玩家的消息处理;
```go
package main

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

14
utils/maths/math_test.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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