Merge branch 'develop'
This commit is contained in:
commit
ea9e261d82
|
@ -91,6 +91,11 @@ func main() {
|
|||
|
||||
### 分流服务器
|
||||
分流服务器可以将消息分流到不同的分组上,每个分组中为串行处理,不同分组之间并行处理。
|
||||
|
||||
> 关于分流服务器的思考:
|
||||
> - 当游戏需要以房间的形式进行时,应该确保相同房间的玩家处于同一分流中,不同房间的玩家处于不同分流中,这样可以避免不同房间的玩家之间的消息互相阻塞;
|
||||
> - 这时候网络 IO 应该根据不同的游戏类型而进行不同的处理,例如回合制可以同步执行,而实时游戏应该采用异步执行;
|
||||
> - 当游戏大部分时候以单人游戏进行时,应该每个玩家处于自身唯一的分流中,此时非互动的消息造成的网络 IO 采用同步执行即可,也不会阻塞到其他玩家的消息处理;
|
||||
```go
|
||||
package main
|
||||
|
||||
|
|
Binary file not shown.
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue